Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Base Bot #55

Merged
merged 24 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e012607
Most simple bots
brandongasser Dec 21, 2023
28c26e0
Fixed MineController
brandongasser Dec 21, 2023
9cef8ab
Added autoselling
brandongasser Dec 21, 2023
6d2698f
Added take_action to OreOccupiableStation
brandongasser Dec 21, 2023
157a461
Fixes and allow multiple moves
brandongasser Dec 21, 2023
1b40835
passed correct reference
JeanAEckelberg Dec 21, 2023
3fbd05f
Used a result to get action list
JeanAEckelberg Dec 21, 2023
c9b16c1
Drop rate mining
KingPhilip14 Dec 21, 2023
e21d728
thr.result default added
JeanAEckelberg Dec 22, 2023
bbf2429
Fixed cashing in and updated base client
brandongasser Dec 22, 2023
874e93f
Merge branch 'master' into base-bot
JeanAEckelberg Dec 25, 2023
08b85ae
Patched scores and checked name length
JeanAEckelberg Dec 25, 2023
d1ebf64
Switched bots to pure chaos, and allowed random
JeanAEckelberg Dec 25, 2023
e06f74c
removed out.mp4 and the generation cause. Fixed ore generation to all…
JeanAEckelberg Dec 25, 2023
46ae498
Merge branch 'master' into base-bot
KingPhilip14 Dec 28, 2023
a1166b7
bot updates
brandongasser Dec 28, 2023
0dfa0a7
Fixed crashing
brandongasser Dec 29, 2023
df6596a
Copied to base_client_2
brandongasser Dec 29, 2023
aa8a662
Merge branch 'master' into base-bot
brandongasser Dec 29, 2023
897ec85
Fixed tests
brandongasser Dec 29, 2023
d2cd458
Made requested changes
brandongasser Jan 3, 2024
44e6aa5
Fixed tests
brandongasser Jan 3, 2024
4401318
Merge branch 'master' into base-bot
JeanAEckelberg Jan 3, 2024
e417383
Fixed activated sprites
JeanAEckelberg Jan 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 65 additions & 2 deletions base_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import random

from game.client.user_client import UserClient
from game.common.enums import *
from game.utils.vector import Vector


class State(Enum):
MINING = auto()
SELLING = auto()


class Client(UserClient):
Expand All @@ -12,7 +20,16 @@ def team_name(self):
Allows the team to set a team name.
:return: Your team name
"""
return 'Team 1'
return 'The King\'s Lambdas 3'

def first_turn_init(self, world, avatar):
"""
This is where you can put setup for things that should happen at the beginning of the first turn
"""
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 = world.get_objects(self.my_station_type)[0][0]

# This is where your AI will decide what to do
def take_turn(self, turn, actions, world, avatar):
Expand All @@ -22,4 +39,50 @@ def take_turn(self, turn, actions, world, avatar):
:param actions: This is the actions object that you will add effort allocations or decrees to.
:param world: Generic world information
"""
pass
if turn == 1:
self.first_turn_init(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...
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
actions = [ActionType.MINE]
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):
"""
This function will generate a path between the start and end position. It does not consider walls and will
try to walk directly to the end position.
:param start_position: Position to start at
:param end_position: Position to get to
:param vertical_first: True if the path should be vertical first, False if the path should be horizontal first
:return: Path represented as a list of ActionType
"""
dx = end_position.x - start_position.x
dy = end_position.y - start_position.y

horizontal = [ActionType.MOVE_LEFT] * -dx if dx < 0 else [ActionType.MOVE_RIGHT] * dx
vertical = [ActionType.MOVE_UP] * -dy if dy < 0 else [ActionType.MOVE_DOWN] * dy

return vertical + horizontal if vertical_first else horizontal + vertical
67 changes: 65 additions & 2 deletions base_client_2.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import random

from game.client.user_client import UserClient
from game.common.enums import *
from game.utils.vector import Vector


class State(Enum):
MINING = auto()
SELLING = auto()


class Client(UserClient):
Expand All @@ -12,7 +20,16 @@ def team_name(self):
Allows the team to set a team name.
:return: Your team name
"""
return 'Team 2'
return 'Unpaid Intern'

def first_turn_init(self, world, avatar):
"""
This is where you can put setup for things that should happen at the beginning of the first turn
"""
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 = world.get_objects(self.my_station_type)[0][0]

# This is where your AI will decide what to do
def take_turn(self, turn, actions, world, avatar):
Expand All @@ -22,4 +39,50 @@ def take_turn(self, turn, actions, world, avatar):
:param actions: This is the actions object that you will add effort allocations or decrees to.
:param world: Generic world information
"""
pass
if turn == 1:
self.first_turn_init(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...
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
actions = [ActionType.MINE]
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):
"""
This function will generate a path between the start and end position. It does not consider walls and will
try to walk directly to the end position.
:param start_position: Position to start at
:param end_position: Position to get to
:param vertical_first: True if the path should be vertical first, False if the path should be horizontal first
:return: Path represented as a list of ActionType
"""
dx = end_position.x - start_position.x
dy = end_position.y - start_position.y

horizontal = [ActionType.MOVE_LEFT] * -dx if dx < 0 else [ActionType.MOVE_RIGHT] * dx
vertical = [ActionType.MOVE_UP] * -dy if dy < 0 else [ActionType.MOVE_DOWN] * dy

return vertical + horizontal if vertical_first else horizontal + vertical
20 changes: 13 additions & 7 deletions game/common/avatar.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,11 @@ def position(self) -> Vector | None:
return self.__position

@property
def movement_speed(self):
def movement_speed(self) -> int:
return self.__movement_speed

@property
def drop_rate(self):
def drop_rate(self) -> int:
return self.__drop_rate

@property
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -394,7 +399,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'])
Expand Down
4 changes: 3 additions & 1 deletion game/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 2 # max number of actions per turn is currently set to 2
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
Expand All @@ -29,11 +29,13 @@

ALLOWED_MODULES = ["game.client.user_client", # modules that clients are specifically allowed to access
"game.common.enums",
"game.utils.vector",
"numpy",
"scipy",
"pandas",
"itertools",
"functools",
"random",
]

RESULTS_FILE_NAME = "results.json" # Name and extension of results file
Expand Down
3 changes: 2 additions & 1 deletion game/controllers/buy_tech_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion game/controllers/interact_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from game.common.stations.station import Station
from game.common.map.game_board import GameBoard
from game.utils.vector import Vector
from game.quarry_rush.station.ore_occupiable_station import OreOccupiableStation


class InteractController(Controller):
Expand Down Expand Up @@ -64,5 +65,5 @@ def handle_actions(self, action: ActionType, client: Player, world: GameBoard, t

stat: Station | None = world.game_map[vector.y][vector.x].get_occupied_by(target)

if stat is not None and isinstance(stat, Station):
if stat is not None and isinstance(stat, Station) and not isinstance(stat, OreOccupiableStation):
stat.take_action(client.avatar, world.inventory_manager)
22 changes: 21 additions & 1 deletion game/controllers/master_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
from game.controllers.movement_controller import MovementController
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
Expand Down Expand Up @@ -61,6 +64,9 @@ def __init__(self):
self.interact_controller: InteractController = InteractController()
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):
Expand Down Expand Up @@ -119,17 +125,31 @@ def turn_logic(self, clients: list[Player], turn):
# during turn logic; handling controller logic
for client in clients:
client.avatar.state = 'idle' # set the state to idle to aid the visualizer
if len(client.actions) == 0:
continue
first = client.actions[0]
if first in [ActionType.MOVE_LEFT, ActionType.MOVE_RIGHT, ActionType.MOVE_UP, ActionType.MOVE_DOWN]:
client.actions = [action for action in client.actions if action in [ActionType.MOVE_LEFT, ActionType.MOVE_RIGHT, ActionType.MOVE_UP, ActionType.MOVE_DOWN]][:client.avatar.movement_speed]
else:
client.actions = [client.actions[0]]
client.actions.append(ActionType.INTERACT_CENTER)
for i in range(MAX_NUMBER_OF_ACTIONS_PER_TURN):
try:
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'])
self.mine_controller.handle_actions(client.actions[i], client, self.current_world_data[
'game_board'])
self.defuse_controller.handle_actions(client.actions[i], client, self.current_world_data[
'game_board'])
KingPhilip14 marked this conversation as resolved.
Show resolved Hide resolved
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

avatars: dict[Company, Avatar] = {client.avatar.company: client.avatar for client in clients}
self.current_world_data['game_board'].trap_detonation_control(avatars)

Expand Down
9 changes: 5 additions & 4 deletions game/controllers/mine_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,21 @@ def handle_actions(self, action: ActionType, client: Player, world: GameBoard) -
"""

# don't mine anything if the inventory is full
if len(world.inventory_manager.get_inventory(client.avatar.company)) == 50:
if len(list(filter(lambda item: item is not None, world.inventory_manager.get_inventory(client.avatar.company)))) == 50:
return

match action:
case ActionType.MINE:
client.avatar.state = 'mining'
InteractController().handle_actions(ActionType.INTERACT_CENTER, client, world)
tile: Tile = world.game_map[client.avatar.position.y][client.avatar.position.x]
station: OreOccupiableStation = tile.occupied_by # Will return the OreOccupiableStation if it isn't

if station is None:
if station is None or station.object_type != ObjectType.ORE_OCCUPIABLE_STATION:
return

# remove the OreOccupiableStation from the game board
station.take_action(client.avatar, world.inventory_manager)

# try to remove the OreOccupiableStation from the game board
station.remove_from_game_board(tile)
case _: # default case
return
7 changes: 6 additions & 1 deletion game/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,12 @@ def tick(self):
thr.join(time_remaining)

# Go through each thread and check if they are still alive

client: Player
thr: Thread
for client, thr in zip(self.clients, threads):
# Load actions into player
client.actions = thr.result if thr.result is not None else []
# If thread is no longer alive, mark it as non-functional, preventing it from receiving future turns
if thr.is_alive():
client.functional = False
Expand Down Expand Up @@ -287,7 +292,7 @@ def shutdown(self, source=None):
if source:
output = "\n"
for client in self.clients:
if client.error != None:
if client.error is not None:
output += client.error + "\n"
print(f'\nGame has ended due to {source}: [{output}].')

Expand Down
Loading