diff --git a/base_client.py b/base_client.py index f60f1ba..3781317 100644 --- a/base_client.py +++ b/base_client.py @@ -15,7 +15,7 @@ def team_name(self): return 'Team Name' # This is where your AI will decide what to do - def take_turn(self, turn, actions, world): + def take_turn(self, turn, actions, world, avatar): """ This is where your AI will decide what to do. :param turn: The current turn of the game. diff --git a/base_client_2.py b/base_client_2.py index f60f1ba..3781317 100644 --- a/base_client_2.py +++ b/base_client_2.py @@ -15,7 +15,7 @@ def team_name(self): return 'Team Name' # This is where your AI will decide what to do - def take_turn(self, turn, actions, world): + def take_turn(self, turn, actions, world, avatar): """ This is where your AI will decide what to do. :param turn: The current turn of the game. diff --git a/game/client/user_client.py b/game/client/user_client.py index 302e5a5..04ff2a8 100644 --- a/game/client/user_client.py +++ b/game/client/user_client.py @@ -4,7 +4,7 @@ class UserClient: def __init__(self): - self.debug_level = DebugLevel.client + self.debug_level = DebugLevel.CLIENT self.debug = True def print(self, *args): @@ -15,5 +15,5 @@ def print(self, *args): def team_name(self): return "No_Team_Name_Available" - def take_turn(self, turn, actions, world): + def take_turn(self, turn, actions, world, avatar): raise NotImplementedError("Implement this in subclass") diff --git a/game/common/avatar.py b/game/common/avatar.py index 064e3bf..6aba3f3 100644 --- a/game/common/avatar.py +++ b/game/common/avatar.py @@ -185,10 +185,10 @@ def from_json(self, data: dict) -> Self: self.held_item = None return self - match held_item['object_type']: + match ObjectType(held_item['object_type']): case ObjectType.ITEM: - self.held_item = Item().from_json(data['held_item']) + self.held_item = Item().from_json(held_item) case _: - raise ValueError(f'{self.__class__.__name__}.held_item needs to be an item.') + raise ValueError(f'held_item needs to be an item, not {held_item}.') return self diff --git a/game/common/enums.py b/game/common/enums.py index 622a881..dcb5e3b 100644 --- a/game/common/enums.py +++ b/game/common/enums.py @@ -28,6 +28,9 @@ class ObjectType(Enum): OCCUPIABLE = auto() STATION = auto() OCCUPIABLE_STATION = auto() + STATION_EXAMPLE = auto() + STATION_RECEIVER_EXAMPLE = auto() + OCCUPIABLE_STATION_EXAMPLE = auto() class ActionType(Enum): diff --git a/game/common/game_object.py b/game/common/game_object.py index 08906ab..66dde54 100644 --- a/game/common/game_object.py +++ b/game/common/game_object.py @@ -18,14 +18,14 @@ def to_json(self) -> dict: data = dict() data['id'] = self.id - data['object_type'] = self.object_type + data['object_type'] = self.object_type.value return data def from_json(self, data: dict) -> Self: # It is recommended call this using super() in child implementations self.id = data['id'] - self.object_type = data['object_type'] + self.object_type = ObjectType(data['object_type']) return self def obfuscate(self): diff --git a/game/common/items/item.py b/game/common/items/item.py index d9d870d..958dd4b 100644 --- a/game/common/items/item.py +++ b/game/common/items/item.py @@ -60,7 +60,7 @@ class Item(GameObject): *** Refer to avatar.py for a more in-depth explanation of how picking up items work with examples. *** """ - def __init__(self, value: int = 1, durability: int | None = 100, quantity: int = 1, stack_size: int = 1): + def __init__(self, value: int = 1, durability: int | None = None, quantity: int = 1, stack_size: int = 1): super().__init__() self.__quantity = None # This is here to prevent an error self.__durability = None # This is here to prevent an error @@ -87,7 +87,7 @@ def stack_size(self) -> int: return self.__stack_size @durability.setter - def durability(self, durability: int | None): + def durability(self, durability: int | None) -> None: if durability is not None and not isinstance(durability, int): raise ValueError(f'{self.__class__.__name__}.durability must be an int or None.') if durability is not None and self.stack_size != 1: @@ -145,16 +145,16 @@ def pick_up(self, item: Self) -> Self | None: def to_json(self) -> dict: data: dict = super().to_json() + data['stack_size'] = self.stack_size data['durability'] = self.durability data['value'] = self.value data['quantity'] = self.quantity - data['stack_size'] = self.stack_size return data def from_json(self, data: dict) -> Self: super().from_json(data) self.durability: int | None = data['durability'] - self.value: int = data['value'] - self.quantity: int = data['quantity'] self.stack_size: int = data['stack_size'] + self.quantity: int = data['quantity'] + self.value: int = data['value'] return self diff --git a/game/common/map/game_board.py b/game/common/map/game_board.py index 8f78211..d368a5a 100644 --- a/game/common/map/game_board.py +++ b/game/common/map/game_board.py @@ -263,7 +263,8 @@ def generate_event(self, start: int, end: int) -> None: self.event_active = random.randint(start, end) def __from_json_helper(self, data: dict) -> GameObject: - match data['object_type']: + temp:ObjectType = ObjectType(data['object_type']) + match temp: case ObjectType.WALL: return Wall().from_json(data) case ObjectType.OCCUPIABLE_STATION: @@ -287,6 +288,6 @@ def from_json(self, data: dict) -> Self: zip(data["location_vectors"], data["location_objects"])} if data["location_vectors"] is not None else None self.walled: bool = data["walled"] self.event_active: int = data['event_active'] - self.game_map: list[list[Tile]] = list( - list(map(lambda tile: Tile().from_json(tile), y)) for y in temp) if temp is not None else None + self.game_map: list[list[Tile]] = [ + [Tile().from_json(tile) for tile in y] for y in temp] if temp is not None else None return self diff --git a/game/common/map/tile.py b/game/common/map/tile.py index 632f9b3..51a83fe 100644 --- a/game/common/map/tile.py +++ b/game/common/map/tile.py @@ -21,20 +21,20 @@ def __init__(self, occupied_by: GameObject = None): def from_json(self, data: dict) -> Self: super().from_json(data) - occupied_by: dict = data['occupied_by'] + occupied_by: dict | None = data['occupied_by'] if occupied_by is None: self.occupied_by = None return self # Add all possible game objects that can occupy a tile here (requires python 3.10) - match occupied_by['object_type']: + match ObjectType(occupied_by['object_type']): case ObjectType.AVATAR: - self.occupied_by: Avatar = Avatar().from_json(data['occupied_by']) + self.occupied_by: Avatar = Avatar().from_json(occupied_by) case ObjectType.OCCUPIABLE_STATION: - self.occupied_by: OccupiableStation = OccupiableStation().from_json(data['occupied_by']) + self.occupied_by: OccupiableStation = OccupiableStation().from_json(occupied_by) case ObjectType.STATION: - self.occupied_by: Station = Station().from_json(data['occupied_by']) + self.occupied_by: Station = Station().from_json(occupied_by) case ObjectType.WALL: - self.occupied_by: Wall = Wall().from_json(data['occupied_by']) + self.occupied_by: Wall = Wall().from_json(occupied_by) case _: - raise Exception(f'Could not parse occupied_by: {self.occupied_by}') + raise Exception(f'Could not parse occupied_by: {occupied_by}') return self \ No newline at end of file diff --git a/game/common/player.py b/game/common/player.py index 28f9afa..7cfc2f0 100644 --- a/game/common/player.py +++ b/game/common/player.py @@ -19,7 +19,7 @@ def __init__(self, code: object | None = None, team_name: str | None = None, act self.team_name: str | None = team_name self.code: object = code # self.action: Action = action - self.actions: list[ActionType] | list = actions + self.actions: list[ActionType] = actions self.avatar: Avatar | None = avatar @property @@ -69,7 +69,7 @@ def avatar(self, avatar: Avatar) -> None: @property def object_type(self) -> ObjectType: - return self.object_type + return self.__object_type @object_type.setter def object_type(self, object_type: ObjectType) -> None: @@ -83,7 +83,7 @@ def to_json(self): data['functional'] = self.functional # data['error'] = self.error # .to_json() if self.error is not None else None data['team_name'] = self.team_name - data['actions'] = self.actions + data['actions'] = [act.value for act in self.actions] data['avatar'] = self.avatar.to_json() if self.avatar is not None else None return data @@ -95,7 +95,7 @@ def from_json(self, data): # self.error = data['error'] # .from_json(data['action']) if data['action'] is not None else None self.team_name = data['team_name'] - self.actions: list[ActionType] | list = data['actions'] + self.actions: list[ActionType] = [ObjectType(action) for action in data['actions']] avatar: Avatar | None = data['avatar'] if avatar is None: self.avatar = None @@ -110,7 +110,7 @@ def from_json(self, data): # raise Exception(f'Could not parse action: {self.action}') # match case for avatar - match avatar['object_type']: + match ObjectType(avatar['object_type']): case ObjectType.AVATAR: self.avatar = Avatar().from_json(data['avatar']) case None: diff --git a/game/common/stations/occupiable_station.py b/game/common/stations/occupiable_station.py index f8065f2..1c916fe 100644 --- a/game/common/stations/occupiable_station.py +++ b/game/common/stations/occupiable_station.py @@ -26,13 +26,13 @@ def from_json(self, data: dict) -> Self: self.occupied_by = None return self # Add all possible game objects that can occupy a tile here (requires python 3.10) - match occupied_by['object_type']: + match ObjectType(occupied_by['object_type']): case ObjectType.AVATAR: - self.occupied_by: Avatar = Avatar().from_json(data['occupied_by']) + self.occupied_by: Avatar = Avatar().from_json(occupied_by) case ObjectType.OCCUPIABLE_STATION: - self.occupied_by: OccupiableStation = OccupiableStation().from_json(data['occupied_by']) + self.occupied_by: OccupiableStation = OccupiableStation().from_json(occupied_by) case ObjectType.STATION: - self.occupied_by: Station = Station().from_json(data['occupied_by']) + self.occupied_by: Station = Station().from_json(occupied_by) case _: - raise Exception(f'Could not parse occupied_by: {self.occupied_by}') + raise Exception(f'Could not parse occupied_by: {occupied_by}') return self diff --git a/game/common/stations/occupiable_station_example.py b/game/common/stations/occupiable_station_example.py new file mode 100644 index 0000000..ee7659a --- /dev/null +++ b/game/common/stations/occupiable_station_example.py @@ -0,0 +1,13 @@ +from game.common.avatar import Avatar +from game.common.enums import ObjectType +from game.common.items.item import Item +from game.common.stations.occupiable_station import OccupiableStation + +# create example of occupiable_station that gives item +class OccupiableStationExample(OccupiableStation): + def __init__(self, held_item: Item | None = None): + super().__init__(held_item=held_item) + self.object_type = ObjectType.STATION_EXAMPLE + + def take_action(self, avatar: Avatar) -> Item | None: + avatar.pick_up(self.held_item) \ No newline at end of file diff --git a/game/common/stations/station.py b/game/common/stations/station.py index 4c003be..9a63492 100644 --- a/game/common/stations/station.py +++ b/game/common/stations/station.py @@ -28,9 +28,9 @@ def held_item(self, held_item: Item) -> None: raise ValueError(f'{self.__class__.__name__}.held_item must be an Item or None, not {held_item}.') self.__item = held_item - # take action method + # base of take action method, defined in classes that extend Station (StationExample demonstrates this) def take_action(self, avatar: Avatar) -> Item | None: - return self.held_item + pass # json methods def to_json(self) -> dict: @@ -47,9 +47,9 @@ def from_json(self, data: dict) -> Self: return self # framework match case for from json, can add more object types that can be item - match held_item['object_type']: + match ObjectType(held_item['object_type']): case ObjectType.ITEM: - self.held_item = Item().from_json(data['held_item']) + self.held_item = Item().from_json(held_item) case _: - raise Exception(f'Could not parse held_item: {self.held_item}') + raise Exception(f'Could not parse held_item: {held_item}') return self diff --git a/game/common/stations/station_example.py b/game/common/stations/station_example.py new file mode 100644 index 0000000..d76206b --- /dev/null +++ b/game/common/stations/station_example.py @@ -0,0 +1,13 @@ +from game.common.avatar import Avatar +from game.common.enums import ObjectType +from game.common.items.item import Item +from game.common.stations.station import Station + +# create example of station that gives item to avatar +class StationExample(Station): + def __init__(self, held_item: Item | None = None): + super().__init__(held_item=held_item) + self.object_type = ObjectType.STATION_EXAMPLE + + def take_action(self, avatar: Avatar) -> Item | None: + avatar.pick_up(self.held_item) \ No newline at end of file diff --git a/game/common/stations/station_receiver_example.py b/game/common/stations/station_receiver_example.py new file mode 100644 index 0000000..4012abf --- /dev/null +++ b/game/common/stations/station_receiver_example.py @@ -0,0 +1,15 @@ +from game.common.avatar import Avatar +from game.common.enums import ObjectType +from game.common.items.item import Item +from game.common.stations.station import Station + +# create example of station that takes held_item from avatar at inventory slot 0 +class StationReceiverExample(Station): + def __init__(self, held_item: Item | None = None): + super().__init__(held_item=held_item) + self.object_type = ObjectType.STATION_RECEIVER_EXAMPLE + + def take_action(self, avatar: Avatar) -> None: + self.held_item = avatar.held_item + # assuming held_item is stored at index 0 in inventory + avatar.inventory.pop(0) \ No newline at end of file diff --git a/game/controllers/master_controller.py b/game/controllers/master_controller.py index 36ce537..283c005 100644 --- a/game/controllers/master_controller.py +++ b/game/controllers/master_controller.py @@ -12,6 +12,7 @@ from game.controllers.interact_controller import InteractController from game.common.map.game_board import GameBoard from game.config import MAX_NUMBER_OF_ACTIONS_PER_TURN +from game.utils.vector import Vector class MasterController(Controller): @@ -65,8 +66,12 @@ def __init__(self): # Receives all clients for the purpose of giving them the objects they will control def give_clients_objects(self, clients: list[Player], world: dict): # starting_positions = [[3, 3], [3, 9]] # would be done in generate game - for index, client in enumerate(clients): - client.avatar = Avatar(position=world[index]) + gb: GameBoard = world['game_board'] + avatars: list[Avatar] = gb.get_objects(ObjectType.AVATAR) + for avatar, client in zip(avatars,clients): + avatar.position = Vector(1,1) + client.avatar = avatar + # Generator function. Given a key:value pair where the key is the identifier for the current world and the value is # the state of the world, returns the key that will give the appropriate world information @@ -108,8 +113,14 @@ def client_turn_arguments(self, client: Player, turn): def turn_logic(self, clients: list[Player], turn): for client in clients: for i in range(MAX_NUMBER_OF_ACTIONS_PER_TURN): - self.movement_controller.handle_actions(client.actions[i], client, self.current_world_data["game_board"]) - self.interact_controller.handle_actions(client.actions[i], client, self.current_world_data["game_board"]) + try: + self.movement_controller.handle_actions(client.actions[i], client, self.current_world_data["game_board"]) + except IndexError: + pass + try: + self.interact_controller.handle_actions(client.actions[i], client, self.current_world_data["game_board"]) + except IndexError: + pass # checks event logic at the end of round # self.handle_events(clients) diff --git a/game/engine.py b/game/engine.py index 0b4c06c..f1e9220 100644 --- a/game/engine.py +++ b/game/engine.py @@ -177,16 +177,11 @@ def pre_tick(self): # Increment the tick self.tick_number += 1 - # Retrieve current world info - if self.current_world_key not in self.world: - raise KeyError('Given generated world key does not exist inside the world.') - current_world = self.world[self.current_world_key] - # Send current world information to master controller for purposes if SET_NUMBER_OF_CLIENTS_START == 1: - self.master_controller.interpret_current_turn_data(self.clients[0], current_world, self.tick_number) + self.master_controller.interpret_current_turn_data(self.clients[0], self.world, self.tick_number) else: - self.master_controller.interpret_current_turn_data(self.clients, current_world, self.tick_number) + self.master_controller.interpret_current_turn_data(self.clients, self.world, self.tick_number) # Does actions like lets the player take their turn and asks master controller to perform game logic def tick(self): diff --git a/game/test_suite/tests/test_interact_controller.py b/game/test_suite/tests/test_interact_controller.py index 77003d9..218c0a1 100644 --- a/game/test_suite/tests/test_interact_controller.py +++ b/game/test_suite/tests/test_interact_controller.py @@ -7,7 +7,10 @@ from game.utils.vector import Vector from game.common.map.wall import Wall from game.common.stations.station import Station +from game.common.stations.station_example import StationExample +from game.common.stations.station_receiver_example import StationReceiverExample from game.common.stations.occupiable_station import OccupiableStation +from game.common.stations.occupiable_station_example import OccupiableStationExample from game.common.game_object import GameObject from game.common.action import ActionType from game.common.player import Player @@ -24,12 +27,14 @@ def setUp(self) -> None: self.item: Item = Item(10, None) self.wall: Wall = Wall() self.occupiable_station: OccupiableStation = OccupiableStation(self.item) + self.station_example: StationExample = StationExample(self.item) + self.occupiable_station_example = OccupiableStationExample(self.item) self.avatar: Avatar = Avatar(None, Vector(5, 5)) self.locations: dict[tuple[Vector]:list[GameObject]] = { (Vector(1, 1),): [Station(None)], - (Vector(5, 4),): [self.occupiable_station], - (Vector(6, 5),): [Station(self.item)], - (Vector(4, 5),): [Station(None)], + (Vector(5, 4),): [self.occupiable_station_example], + (Vector(6, 5),): [self.station_example], + (Vector(4, 5),): [StationReceiverExample()], (Vector(5, 5),): [self.avatar], (Vector(5, 6),): [self.wall] } @@ -45,15 +50,16 @@ def test_interact_nothing(self): # interact and pick up from an occupiable_station def test_interact_item_occupible_station(self): self.interact_controller.handle_actions(ActionType.INTERACT_UP, self.player, self.game_board) - self.assertEqual(self.avatar.held_item.object_type, ObjectType.ITEM) + self.assertEqual(self.avatar.inventory[0].object_type, ObjectType.ITEM) # interact and pick up from a station def test_interact_item_station(self): self.interact_controller.handle_actions(ActionType.INTERACT_RIGHT, self.player, self.game_board) - self.assertEqual(self.avatar.held_item.object_type, ObjectType.ITEM) + self.assertEqual(self.avatar.inventory[0].object_type, ObjectType.ITEM) # interact and get item then dump item def test_interact_dump_item(self): self.interact_controller.handle_actions(ActionType.INTERACT_RIGHT, self.player, self.game_board) + self.avatar.held_item = self.avatar.inventory[0] self.interact_controller.handle_actions(ActionType.INTERACT_LEFT, self.player, self.game_board) - self.assertEqual(self.avatar.held_item, None) + self.assertEqual(len(self.avatar.inventory), 0) diff --git a/game/test_suite/tests/test_station.py b/game/test_suite/tests/test_station.py new file mode 100644 index 0000000..2ca86d9 --- /dev/null +++ b/game/test_suite/tests/test_station.py @@ -0,0 +1,50 @@ +import unittest + +from game.common.stations.station import Station +from game.common.stations.station_example import StationExample +from game.common.items.item import Item +from game.controllers.inventory_controller import InventoryController +from game.common.avatar import Avatar +from game.common.player import Player +from game.common.map.game_board import GameBoard +from game.utils.vector import Vector +from game.common.enums import ActionType +from game.common.enums import ObjectType + +# class that tests stations and its methods +class TestStation(unittest.TestCase): + def setUp(self) -> None: + self.station = Station() + self.item = Item(10, None, 2, 64) + self.station_example = StationExample(self.item) + self.avatar = Avatar(None, Vector(2, 2), [], 10) + self.inventory: list[Item] = [Item(0), Item(1), Item(2), Item(3), Item(4), Item(5), Item(6), Item(7), Item(8), Item(9)] + self.player = Player(avatar=self.avatar) + self.avatar.inventory = self.inventory + self.game_board = GameBoard(None, Vector(4, 4), None, False) + self.inventory_controller = InventoryController() + + # test adding item to station + def test_item_occ(self): + self.station.held_item = self.item + self.assertEqual(self.station.held_item.object_type, ObjectType.ITEM) + + # test adding something not an item + def test_item_occ_fail(self): + with self.assertRaises(ValueError) as e: + self.station.held_item = 'wow' + self.assertEqual(str(e.exception), 'Station.held_item must be an Item or None, not wow.') + + # test base take action method works + def test_take_action(self): + self.inventory_controller.handle_actions(ActionType.SELECT_SLOT_0, self.player, self.game_board) + self.station_example.take_action(self.avatar) + self.assertEqual(self.avatar.held_item.object_type, self.item.object_type) + + # test json method + def test_json(self): + self.station.held_item = self.item + data: dict = self.station.to_json() + station: Station = Station().from_json(data) + self.assertEqual(self.station.object_type, station.object_type) + self.assertEqual(self.station.held_item.object_type, station.held_item.object_type) \ No newline at end of file diff --git a/game/utils/generate_game.py b/game/utils/generate_game.py index 16c3bee..822d773 100644 --- a/game/utils/generate_game.py +++ b/game/utils/generate_game.py @@ -1,5 +1,6 @@ import random - +from game.common.avatar import Avatar +from game.utils.vector import Vector from game.config import * from game.utils.helpers import write_json_file from game.common.map.game_board import GameBoard @@ -8,10 +9,9 @@ def generate(seed: int = random.randint(0, 1000000000)): print('Generating game map...') - temp: GameBoard = GameBoard(seed) + temp: GameBoard = GameBoard(seed,map_size=Vector(6,6), locations={(Vector(1,1),):[Avatar()]}, walled=True) temp.generate_map() - data: dict = temp.to_json() - + data: dict = {'game_board':temp.to_json()} # for x in range(1, MAX_TICKS + 1): # data[x] = 'data'