diff --git a/base_client.py b/base_client.py index e01d3b83..fb3a2c85 100644 --- a/base_client.py +++ b/base_client.py @@ -29,10 +29,10 @@ def first_turn_init(self, world, avatar): self.company = avatar.company self.my_station_type = ObjectType.TURING_STATION if self.company == Company.TURING else ObjectType.CHURCH_STATION self.current_state = State.MINING - self.base_position = self.find_all_by_type(world, self.my_station_type)[0] + self.base_position = world.get_objects(self.my_station_type)[0][0] # This is where your AI will decide what to do - def take_turn(self, turn: int, actions: ActionType, world, avatar): + 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. @@ -44,23 +44,30 @@ def take_turn(self, turn: int, actions: ActionType, world, avatar): current_tile = world.game_map[avatar.position.y][avatar.position.x] # set current tile to the tile that I'm standing on - # If I start the turn on my station, I should start mining + # If I start the turn on my station, I should... if current_tile.occupied_by.object_type == self.my_station_type: + # buy Improved Mining tech if I can... + if avatar.science_points >= avatar.get_tech_info('Improved Drivetrain').cost and not avatar.is_researched('Improved Drivetrain'): + return [ActionType.BUY_IMPROVED_DRIVETRAIN] + # otherwise set my state to mining self.current_state = State.MINING + # If I have at least 5 items in my inventory, set my state to selling + if len([item for item in world.inventory_manager.get_inventory(self.company) if item is not None]) >= 5: + self.current_state = State.SELLING + # Make action decision for this turn if self.current_state == State.SELLING: # actions = [ActionType.MOVE_LEFT if self.company == Company.TURING else ActionType.MOVE_RIGHT] # If I'm selling, move towards my base actions = self.generate_moves(avatar.position, self.base_position, turn % 2 == 0) else: if current_tile.occupied_by.object_type == ObjectType.ORE_OCCUPIABLE_STATION: - # If I'm mining and I'm standing on an ore, mine it and set my state to selling + # If I'm mining and I'm standing on an ore, mine it actions = [ActionType.MINE] - self.current_state = State.SELLING else: # If I'm mining and I'm not standing on an ore, move randomly actions = [random.choice([ActionType.MOVE_LEFT, ActionType.MOVE_RIGHT, ActionType.MOVE_UP, ActionType.MOVE_DOWN])] - + return actions def generate_moves(self, start_position, end_position, vertical_first): @@ -79,26 +86,3 @@ def generate_moves(self, start_position, end_position, vertical_first): vertical = [ActionType.MOVE_UP] * -dy if dy < 0 else [ActionType.MOVE_DOWN] * dy return vertical + horizontal if vertical_first else horizontal + vertical - - def find_all_by_type(self, world, object_type): - """ - Finds all tiles on the board with the given object_type - :param world: Generic world information - :param object_type: The desired object type - :return: List of all Vectors for positions with Tiles with the object type - """ - return self.find_all(world, lambda tile: tile.occupied_by is not None and tile.occupied_by.object_type == object_type) - - def find_all(self, world, criteria): - """ - Finds all tiles on the board that match the given criteria - :param world: Generic world information - :param criteria: The required criteria as a function that takes a Tile and returns a bool - :return: List of all Vectors for positions with Tiles that meet the criteria - """ - result = [] - for y in range(len(world.game_map)): - for x in range(len(world.game_map[y])): - if criteria(world.game_map[y][x]): - result.append(Vector(x=x, y=y)) - return result diff --git a/base_client_2.py b/base_client_2.py index 3dd9a565..2bd2c171 100644 --- a/base_client_2.py +++ b/base_client_2.py @@ -29,10 +29,10 @@ def first_turn_init(self, world, avatar): self.company = avatar.company self.my_station_type = ObjectType.TURING_STATION if self.company == Company.TURING else ObjectType.CHURCH_STATION self.current_state = State.MINING - self.base_position = self.find_all_by_type(world, self.my_station_type)[0] + self.base_position = world.get_objects(self.my_station_type)[0][0] # This is where your AI will decide what to do - def take_turn(self, turn: int, actions: ActionType, world, avatar): + 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. @@ -44,23 +44,30 @@ def take_turn(self, turn: int, actions: ActionType, world, avatar): current_tile = world.game_map[avatar.position.y][avatar.position.x] # set current tile to the tile that I'm standing on - # If I start the turn on my station, I should start mining + # If I start the turn on my station, I should... if current_tile.occupied_by.object_type == self.my_station_type: + # buy Improved Mining tech if I can... + if avatar.science_points >= avatar.get_tech_info('Improved Drivetrain').cost and not avatar.is_researched('Improved Drivetrain'): + return [ActionType.BUY_IMPROVED_DRIVETRAIN] + # otherwise set my state to mining self.current_state = State.MINING + # If I have at least 5 items in my inventory, set my state to selling + if len([item for item in world.inventory_manager.get_inventory(self.company) if item is not None]) >= 5: + self.current_state = State.SELLING + # Make action decision for this turn if self.current_state == State.SELLING: # actions = [ActionType.MOVE_LEFT if self.company == Company.TURING else ActionType.MOVE_RIGHT] # If I'm selling, move towards my base actions = self.generate_moves(avatar.position, self.base_position, turn % 2 == 0) else: if current_tile.occupied_by.object_type == ObjectType.ORE_OCCUPIABLE_STATION: - # If I'm mining and I'm standing on an ore, mine it and set my state to selling + # If I'm mining and I'm standing on an ore, mine it actions = [ActionType.MINE] - self.current_state = State.SELLING else: # If I'm mining and I'm not standing on an ore, move randomly actions = [random.choice([ActionType.MOVE_LEFT, ActionType.MOVE_RIGHT, ActionType.MOVE_UP, ActionType.MOVE_DOWN])] - + return actions def generate_moves(self, start_position, end_position, vertical_first): @@ -79,26 +86,3 @@ def generate_moves(self, start_position, end_position, vertical_first): vertical = [ActionType.MOVE_UP] * -dy if dy < 0 else [ActionType.MOVE_DOWN] * dy return vertical + horizontal if vertical_first else horizontal + vertical - - def find_all_by_type(self, world, object_type): - """ - Finds all tiles on the board with the given object_type - :param world: Generic world information - :param object_type: The desired object type - :return: List of all Vectors for positions with Tiles with the object type - """ - return self.find_all(world, lambda tile: tile.occupied_by is not None and tile.occupied_by.object_type == object_type) - - def find_all(self, world, criteria): - """ - Finds all tiles on the board that match the given criteria - :param world: Generic world information - :param criteria: The required criteria as a function that takes a Tile and returns a bool - :return: List of all Vectors for positions with Tiles that meet the criteria - """ - result = [] - for y in range(len(world.game_map)): - for x in range(len(world.game_map[y])): - if criteria(world.game_map[y][x]): - result.append(Vector(x=x, y=y)) - return result diff --git a/game/common/avatar.py b/game/common/avatar.py index 5efbe318..115dbdef 100644 --- a/game/common/avatar.py +++ b/game/common/avatar.py @@ -301,7 +301,8 @@ def __unlock_trap_defusal(self) -> None: # Helper method to create a dictionary that stores bool values for which abilities the player unlocked def __create_abilities_dict(self) -> dict: - abilities = {'Improved Drivetrain': False, + abilities = {'Mining Robotics': True, + 'Improved Drivetrain': False, 'Superior Drivetrain': False, 'Overdrive Drivetrain': False, 'Improved Mining': False, @@ -336,9 +337,6 @@ def buy_new_tech(self, tech_name: str) -> bool: return successful - def get_tech_tree(self) -> TechTree: - return self.__tech_tree - def is_researched(self, tech_name: str) -> bool: """Returns if the given tech was researched.""" return self.__tech_tree.is_researched(tech_name) @@ -350,6 +348,13 @@ def get_researched_techs(self) -> list[str]: def get_all_tech_names(self) -> list[str]: """Returns a list of all possible tech names in a Tech Tree.""" return self.__tech_tree.tech_names() + + def get_tech_info(self, tech_name: str) -> TechInfo | None: + """ + Returns a TechInfo object about the tech with the given name if the tech is found in the tree. + Returns None if the tech isn't found + """ + return self.__tech_tree.tech_info(tech_name) # Dynamite placing functionality ---------------------------------------------------------------------------------- # if avatar calls place dynamite, set to true, i.e. they want to place dynamite @@ -393,7 +398,8 @@ def from_json(self, data: dict) -> Self: self.movement_speed = data['movement_speed'] self.drop_rate = data['drop_rate'] self.abilities = data['tech_tree'] - self.__tech_tree = data['tech_tree'] + self.__tech_tree = self.__create_tech_tree() + self.__tech_tree.from_json(data['tech_tree']) self.dynamite_active_ability = DynamiteActiveAbility().from_json(data['dynamite_active_ability']) self.landmine_active_ability = LandmineActiveAbility().from_json(data['landmine_active_ability']) self.emp_active_ability = EMPActiveAbility().from_json(data['emp_active_ability']) diff --git a/game/config.py b/game/config.py index 8a618c33..4df8c47e 100644 --- a/game/config.py +++ b/game/config.py @@ -15,7 +15,7 @@ MAX_SECONDS_PER_TURN = 0.1 # max number of basic operations clients have for their turns -MAX_NUMBER_OF_ACTIONS_PER_TURN = 1000 # master_controller will be handling max actions enforcement for Byte-le 2024 "Quarry Rush" +MAX_NUMBER_OF_ACTIONS_PER_TURN = 5 # master_controller will be handling max actions enforcement for Byte-le 2024 "Quarry Rush" MIN_CLIENTS_START = None # minimum number of clients required to start running the game; should be None when SET_NUMBER_OF_CLIENTS is used MAX_CLIENTS_START = None # maximum number of clients required to start running the game; should be None when SET_NUMBER_OF_CLIENTS is used diff --git a/game/controllers/buy_tech_controller.py b/game/controllers/buy_tech_controller.py index e1dd14a3..fd23aa9b 100644 --- a/game/controllers/buy_tech_controller.py +++ b/game/controllers/buy_tech_controller.py @@ -45,7 +45,8 @@ def handle_actions(self, action: ActionType, client: Player, world: GameBoard): case ActionType.BUY_TRAP_DEFUSAL: tech_name = 'Trap Defusal' - client.avatar.buy_new_tech(tech_name) # buy the tech specified + if tech_name != '': + client.avatar.buy_new_tech(tech_name) # buy the tech specified def __is_on_home_base(self, client: Player, world: GameBoard): avatar_pos: Vector = client.avatar.position # get the position of the avatar diff --git a/game/controllers/master_controller.py b/game/controllers/master_controller.py index 0e4fbe01..97cb8253 100644 --- a/game/controllers/master_controller.py +++ b/game/controllers/master_controller.py @@ -14,6 +14,8 @@ from game.controllers.controller import Controller from game.controllers.interact_controller import InteractController from game.controllers.mine_controller import MineController +from game.controllers.buy_tech_controller import BuyTechController +from game.controllers.place_controller import PlaceController from game.common.map.game_board import GameBoard from game.config import MAX_NUMBER_OF_ACTIONS_PER_TURN from game.utils.vector import Vector @@ -63,6 +65,8 @@ def __init__(self): self.dynamite_controller: DynamiteController = DynamiteController() self.defuse_controller: DefuseController = DefuseController() self.mine_controller: MineController = MineController() + self.buy_tech_controller: BuyTechController = BuyTechController() + self.place_controller: PlaceController = PlaceController() # Receives all clients for the purpose of giving them the objects they will control def give_clients_objects(self, clients: list[Player], world: dict): @@ -139,6 +143,10 @@ def turn_logic(self, clients: list[Player], turn): 'game_board']) self.defuse_controller.handle_actions(client.actions[i], client, self.current_world_data[ 'game_board']) + self.buy_tech_controller.handle_actions(client.actions[i], client, self.current_world_data[ + 'game_board']) + self.place_controller.handle_actions(client.actions[i], client, self.current_world_data[ + 'game_board']) except IndexError: pass diff --git a/game/quarry_rush/entity/ancient_tech.py b/game/quarry_rush/entity/ancient_tech.py index a9d00083..d13168fd 100644 --- a/game/quarry_rush/entity/ancient_tech.py +++ b/game/quarry_rush/entity/ancient_tech.py @@ -7,6 +7,6 @@ class AncientTech(Item): """ Class for generic Ancient Tech item """ - def __init__(self, science_point_value: int = 1, quantity: int = 1, stack_size: int = 1, durability: int | None = None, position: Vector | None = None, name: str | None = None): + def __init__(self, science_point_value: int = 10, quantity: int = 1, stack_size: int = 1, durability: int | None = None, position: Vector | None = None, name: str | None = None): super().__init__(0, science_point_value, quantity, stack_size, durability, position, name) self.object_type = ObjectType.ANCIENT_TECH