moved into library, added readme information and better docs

This commit is contained in:
Adrian Gunnar Lauterer 2024-06-22 20:36:10 +02:00
parent 767714e9c5
commit 06eaba6d79
4 changed files with 630 additions and 307 deletions

View File

@ -5,3 +5,13 @@ A base implementation of a ozai bot.
Can easily be extended with new filters.
If you are new here. the only thing you need to make a new bot is to create a new filter function, that returns true if any given move to it seems like a bad move. All the possible moves are generated, and passed trough the filter, and any found to not be deemed bad, will be tried at random. If no valid moves are left after filtering, a complete random move will be done.
the main thing to do if you want to make your own bot is to make your own filter function in the ozai_bot.py, and add it to the last if else ladder for filter functions to select from.
To know how to run the bot run this command
python ozai_bot.py --help
The ozai_lib.py is the library function. You probably do not need to interface with it other than if you want to use its logic to build your completely own bot withoul doing all the gameserver interface work yourself, or if you want to know what to use when making a filter.

307
main.py
View File

@ -1,307 +0,0 @@
#!/usr/bin/env python3
from time import sleep
import requests
import numpy as np
import random
import argparse
ozai_url = 'http://localhost:8000/api/'
# ozai classes (some are just storing json data because i am )
class Player:
def __init__(self, name, gamedata_player_json):
self.name = name
self.ready = gamedata_player_json['ready']
self.points = gamedata_player_json['points']
self.pattern_lines = gamedata_player_json['pattern_lines'] #'pattern_lines': [{'color': None, 'number': 0}, {'color': None, 'number': 0}, {'color': None, 'number': 0}, {'color': None, 'number': 0}, {'color': None, 'number': 0}],
self.wall = gamedata_player_json['wall'] # 'wall': [[False, False, False, False, False], [False, False, False, False, False], [False, False, False, False, False], [False, False, False, False, False], [False, False, False, False, False]]
self.floor = gamedata_player_json['floor']
def __str__(self):
return f"Player(name={self.name}, ready={self.ready}, points={self.points}, pattern_lines={self.pattern_lines}, wall={self.wall}, floor={self.floor})"
class Bag:
def __init__(self, bag_json):
self.blue = bag_json['blue']
self.yellow = bag_json['yellow']
self.red = bag_json['red']
self.black = bag_json['black']
self.white = bag_json['white']
def __str__(self):
return f"Bag(blue={self.blue}, yellow={self.yellow}, red={self.red}, black={self.black}, white={self.white})"
class Factory:
def __init__(self, factory_json):
self.blue = factory_json['blue']
self.yellow = factory_json['yellow']
self.red = factory_json['red']
self.black = factory_json['black']
self.white = factory_json['white']
def __str__(self):
return f"Factory(blue={self.blue}, yellow={self.yellow}, red={self.red}, black={self.black}, white={self.white})"
class Market:
def __init__(self, market_json):
self.start = market_json['start']
self.blue = market_json['blue']
self.yellow = market_json['yellow']
self.red = market_json['red']
self.black = market_json['black']
self.white = market_json['white']
def __str__(self):
return f"Market(start={self.start}, blue={self.blue}, yellow={self.yellow}, red={self.red}, black={self.black}, white={self.white})"
class PatternLine:
def __init__(self, pattern_line_json):
self.color = pattern_line_json['color']
self.number = pattern_line_json['number']
def __str__(self):
return f"PatternLine(color={self.color}, number={self.number})"
class Wall:
def __init__(self, wall_json):
self.wall = wall_json
def __str__(self):
return f"Wall(wall={self.wall})"
class Floor:
def __init__(self, floor_json):
self.start = floor_json['start']
self.blue = floor_json['blue']
self.yellow = floor_json['yellow']
self.red = floor_json['red']
self.black = floor_json['black']
self.white = floor_json['white']
def __str__(self):
return f"Floor(start={self.start}, blue={self.blue}, yellow={self.yellow}, red={self.red}, black={self.black}, white={self.white})"
class GameState:
def __init__(self, gamedata_json):
json = gamedata_json
self.n_players = json['n_players']
self.current_player = json['current_player']
self.starting_player = json['starting_player']
self.player_names = json['player_names']
self.game_end = json['game_end']
self.rounds = json['rounds']
self.days = json['days']
self.bag = Bag(json['bag'])
self.lid = Bag(json['lid']) # Assuming lid has the same structure as bag
self.factories = [Factory(factory_json) for factory_json in json['factories']]
self.market = Market(json['market'])
self.players = [Player(name, player_json) for name, player_json in json['players'].items()]
def __str__(self):
return f"GameState(n_players={self.n_players}, current_player={self.current_player}, starting_player={self.starting_player}, player_names={self.player_names}, game_end={self.game_end}, rounds={self.rounds}, days={self.days}, bag={self.bag}, lid={self.lid}, factories={self.factories}, market={self.market}, players={self.players})"
def init_game(names=["a", "b"]):
#initialize a game with the given names and return the game_id
player_names = {"player_names": names}
response = requests.post(ozai_url + 'game', json=player_names)
if response.status_code == 200:
game_id = response.json()
print("Game ID:", game_id)
return game_id
else:
return None
def join_game(game_id, player_name):
#gets a initialized game and joins the game with the given player name
response = requests.get(ozai_url + 'game/' + game_id + '?player=' + player_name)
if response.status_code != 200:
return None
players = response.json().get('players')
return players
def create_game(names=["a", "b"]):
# Create a game with the players given in names, by initializing it and joining the players
game_id = init_game(names)
for name in names:
join_game(game_id, name)
return game_id
def submit_action(game_id, player_name, source=0, destination=0, color="start", policy="strict"):
# Submit an action to the game. The action is a dictionary with the keys being the action type and the values being the arguments.
# Example: {"player": 0, "market": true, "factory": 0, "color": "blue", "patternLine": 0}
if source != "market":
if source < 0 or source > 4:
return False
if destination != "floor":
if destination < 0 or destination > 4:
return False
action = {
'player': str(player_name),
'policy': policy,
'color': str(color).lower(),
'source': source,
'destination': destination
}
response = requests.put(ozai_url + 'game/' + game_id, json=action)
if response.status_code == 200:
return True
else:
return False
def get_gamestate(game_id) -> GameState:
response = requests.get(ozai_url + 'game/' + game_id)
if response.status_code == 200:
game_state = response.json()
game_state = GameState(game_state)
return game_state
else:
return None
def get_score(game_id, player_name):
GameState = get_gamestate(game_id)
for player in GameState.players:
if player_name == player.name:
return player.points
def game_over(game_id):
try:
GameState = get_gamestate(game_id)
return GameState.game_end
except:
return False
def get_all_valid_moves(game_id, player_name):
# Get the game state
GameState = get_gamestate(game_id)
# Generate a list of all possible moves
sources = ["market"] + [i for i, factory in enumerate(GameState.factories) if any([factory.blue, factory.yellow, factory.red, factory.black, factory.white])]
colors = ["blue", "yellow", "red", "black", "white"]
destinations = ["floor"] + list(range(len(GameState.factories)))
valid_moves = []
for source in sources:
for color in colors:
for destination in destinations:
# Check if the source has the color we want to move
if source == "market":
amount = getattr(GameState.market, color)
if amount > 0:
valid_moves.append((source, destination, color, amount))
else:
amount = getattr(GameState.factories[source], color)
if amount > 0:
valid_moves.append((source, destination, color, amount))
# Filter out invalid destinations if the destinaiton is a pattern line, and the wall contains the color
wall_colors = [
['blue', 'yellow', 'red', 'black', 'white'],
['white', 'blue', 'yellow', 'red', 'black'],
['black', 'white', 'blue', 'yellow', 'red'],
['red', 'black', 'white', 'blue', 'yellow'],
['yellow', 'red', 'black', 'white', 'blue'],
]
moves = []
player = [player for player in GameState.players if player.name == player_name][0]
for move in valid_moves:
if move[1] == "floor":
moves.append(move)
else:
# print(player.pattern_lines[move[1]])
#if element in wall is tru convert it to the correct color
player.wall = [[wall_colors[i][j] if player.wall[i][j] else False for j in range(5)] for i in range(5)]
#check that the wall does not contain the color on the same row as the pattern line, and that the pattern line does not contain another color
if not move[2] in player.wall[move[1]]:
if player.pattern_lines[move[1]]['color'] == move[2] or player.pattern_lines[move[1]]['color'] == None:
moves.append(move)
valid_moves = moves
return valid_moves
###
# strategies for filtering in the do_move solution.
###
def strategy_1(move, GameState,player):
#strategy 1 most fit always 530points 27-3
if move[1] == 'floor':
return True
remaining_space = move[1] + 1 - player.pattern_lines[move[1]]['number']
if move[3] >= remaining_space-1 and move[3] <= remaining_space +1:
return True
else:
return False
def strategy_2(move,GameState,player):
#strategy 2 tierdoff fitting. 983 30-0
if move[1] == 'floor':
return True
remaining_space = move[1] + 1 - player.pattern_lines[move[1]]['number']
if move[1] == 4:
if move[3] <= remaining_space and move[3] >= remaining_space -2:
return True
if move[1] == 3:
if move[3] <= remaining_space and move[3] >= remaining_space -1:
return True
if move[1] < 3:
if move[3] <= remaining_space and move[3] >= remaining_space:
return True
return False
def strategy_random(move, GameState,player):
return True #do not filter any moves
def do_move(game_id, player_name, filter_strategy=strategy_random):
moves = get_all_valid_moves(game_id, player_name)
try:
GameState = get_gamestate(game_id)
player = [player for player in GameState.players if player.name == player_name][0]
filtered_moves = []
for move in moves:
if filter_strategy(move,GameState,player):
filtered_moves.append(move)
# Submit a random move, of the filtered ones.
move = random.choice(filtered_moves)
submit_action(game_id, player_name, move[0], move[1], move[2])
return move
except:
#if filtered all moves, just submit a random move from the moves
result = random.choice(moves)
return result
def play_game(gameid, players, strategy):
print(f"Playing game {gameid} with players {players} and strategy {strategy.__name__}")
while not game_over(gameid):
for player in players:
mov = do_move(gameid, player, filter_strategy=strategy)
if game_over(game_id):
#get the score of the players
score = []
for player in players:
score.append(get_score(gameid, player))
print(f"Game Over, scores: {score}")
return score
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Play a game with specified strategies.')
parser.add_argument('--game_id', type=int, default=0, help='The id of the game to play.')
parser.add_argument('--players', nargs='+', default=["a", "b"], help='The names of the players.')
parser.add_argument('--strategy', type=str, default="", help='The strategy to use. Can be "1", "2", or "" for random.')
parser.add_argument('--ozai_url', type=str, default='http://localhost:8000/api/', help='The url to the ozai server.')
args = parser.parse_args()
game_id = args.game_id
players = args.players
strategy = args.strategy
#use global ozai url and update it
ozai_url = args.ozai_url
if game_id == 0:
game_id = create_game(names=players)
if strategy == "1":
play_game(game_id, players, strategy_1)
elif strategy == "2":
play_game(game_id, players, strategy_2)
else:
play_game(game_id, players, strategy_random)

79
ozai_bot.py Executable file
View File

@ -0,0 +1,79 @@
#!/usr/bin/env python3
from time import sleep
import requests
import numpy as np
import random
import argparse
#if you wonder about anything about this library logic and what is available in the GameState object see here, or print it out.
import ozai_lib as oz
###
# strategies for filtering in the do_move solution.
# the move is a tuple with the first indicating the source,
# the second the color
# and the third the destination
#
# Gamestate is a large object with pretty mouch all the information about the game.
# (look at the library or print it out if you wonder what it contains)
###
def strategy_1(move, GameState,player):
#strategy 1 most fit always 530points 27-3
if move[1] == 'floor':
return True
remaining_space = move[1] + 1 - player.pattern_lines[move[1]]['number']
if move[3] >= remaining_space-1 and move[3] <= remaining_space +1:
return True
else:
return False
def strategy_2(move,GameState,player):
#strategy 2 tierdoff fitting. 983 30-0
if move[1] == 'floor':
return True
remaining_space = move[1] + 1 - player.pattern_lines[move[1]]['number']
if move[1] == 4:
if move[3] <= remaining_space and move[3] >= remaining_space -2:
return True
if move[1] == 3:
if move[3] <= remaining_space and move[3] >= remaining_space -1:
return True
if move[1] < 3:
if move[3] <= remaining_space and move[3] >= remaining_space:
return True
return False
def strategy_random(move, GameState,player):
return True #do not filter any moves
#taking arguments, so you can do python main.py --help to get the help message of arguments defined here.
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Play a game with specified strategies.')
parser.add_argument('--game_id', type=int, default=0, help='The id of the game to play.')
parser.add_argument('--players', nargs='+', default=["a", "b"], help='The names of the players.')
parser.add_argument('--strategy', type=str, default="", help='The strategy to use. Can be "1", "2", or "" for random.')
parser.add_argument('--ozai_url', type=str, default='http://localhost:8000/api/', help='The url to the ozai server.')
args = parser.parse_args()
game_id = args.game_id
players = args.players
strategy = args.strategy
#use global ozai url and update it
oz.ozai_url = args.ozai_url
#create a game with the players if the game id does not exist.
if game_id == 0:
game_id = oz.create_game(names=players)
#apply the correct strategy from what was given at the terminal argument.
if strategy == "1":
oz.play_game(game_id, players, strategy_1)
elif strategy == "2":
oz.play_game(game_id, players, strategy_2)
else:
oz.play_game(game_id, players, strategy_random)

541
ozai_lib.py Executable file
View File

@ -0,0 +1,541 @@
#!/usr/bin/env python3
import requests
import random
import argparse
from ozai_bot import *
#the url the gameserver is hosted at.
ozai_url = 'http://localhost:8000/api/'
###
# ozai classes (some are just storing json data in some of the subvalues)
#these are mostly only to represent the same classes the game server uses to clarify
# what they contain, and for some internal use here.
###
class Player:
"""
A class to represent a Player in a azul game.
...
Attributes
----------
name : str
a string representing the player's name
ready : bool
a boolean indicating if the player is ready
points : int
an integer representing the player's points
pattern_lines : list
a list of dictionaries representing the player's pattern lines
wall : list
a 2D list representing the player's wall
floor : list
a list representing the player's floor
Methods
-------
__str__():
Returns a string representation of the Player object.
"""
def __init__(self, name, gamedata_player_json):
self.name = name
self.ready = gamedata_player_json['ready']
self.points = gamedata_player_json['points']
self.pattern_lines = gamedata_player_json['pattern_lines']
self.wall = gamedata_player_json['wall']
self.floor = gamedata_player_json['floor']
def __str__(self):
return f"Player(name={self.name}, ready={self.ready}, points={self.points}, pattern_lines={self.pattern_lines}, wall={self.wall}, floor={self.floor})"
class Bag:
"""
A class to representing the bag with tiles in azul.
Attributes
----------
blue : int
an integer representing the number of blue tiles in the bag
yellow : int
an integer representing the number of yellow tiles in the bag
red : int
an integer representing the number of red tiles in the bag
black : int
an integer representing the number of black tiles in the bag
white : int
an integer representing the number of white tiles in the bag
Methods
-------
__str__():
Returns a string representation of the Bag object.
"""
def __init__(self, bag_json):
self.blue = bag_json['blue']
self.yellow = bag_json['yellow']
self.red = bag_json['red']
self.black = bag_json['black']
self.white = bag_json['white']
def __str__(self):
return f"Bag(blue={self.blue}, yellow={self.yellow}, red={self.red}, black={self.black}, white={self.white})"
class Factory:
"""
A class to represent a Factory in azul.
Any given factory should not have more than 4 tiles at one point.
...
Attributes
----------
blue : int
an integer representing the number of blue tiles in the given factory
yellow : int
an integer representing the number of yellow tiles in the given factory
red : int
an integer representing the number of red tiles in the given factory
black : int
an integer representing the number of black tiles in the given factory
white : int
an integer representing the number of white tiles in the given factory
"""
def __init__(self, factory_json):
self.blue = factory_json['blue']
self.yellow = factory_json['yellow']
self.red = factory_json['red']
self.black = factory_json['black']
self.white = factory_json['white']
def __str__(self):
return f"Factory(blue={self.blue}, yellow={self.yellow}, red={self.red}, black={self.black}, white={self.white})"
class Market:
"""
A class to represent a azul Market.
...
Attributes
----------
start : int
an integer stating the amount of start (special) tiles in the market (should be 1 or 0)
blue : int
an integer representing the number of blue tiles in the market
yellow : int
an integer representing the number of yellow tiles in the market
red : int
an integer representing the number of red tiles in the market
black : int
an integer representing the number of black tiles in the market
white : int
an integer representing the number of white tiles in the market
"""
def __init__(self, market_json):
self.start = market_json['start']
self.blue = market_json['blue']
self.yellow = market_json['yellow']
self.red = market_json['red']
self.black = market_json['black']
self.white = market_json['white']
def __str__(self):
return f"Market(start={self.start}, blue={self.blue}, yellow={self.yellow}, red={self.red}, black={self.black}, white={self.white})"
class PatternLine:
"""
A class to represent a pattern line.
Attributes
----------
color : str
The color of the pattern line.
number : int
The number of the pattern line.
Methods
-------
__init__(self, pattern_line_json: dict) -> None:
Initializes PatternLine with the color and number from a JSON object.
"""
def __init__(self, pattern_line_json: dict):
self.color = pattern_line_json['color']
self.number = pattern_line_json['number']
def __str__(self):
return f"PatternLine(color={self.color}, number={self.number})"
class Wall:
"""
A class to represent a azul Wall of any player.
Attributes
----------
wall : dict
The JSON representation of the wall.
Can be abstracted into a 2d matrix of tiles. See api docs on ozai server for more details.
Methods
-------
__init__(self, wall_json: dict) -> None:
Initializes Wall with the JSON representation.
__str__(self) -> str:
Returns a string representation of the Wall instance.
"""
def __init__(self, wall_json):
self.wall = wall_json
def __str__(self):
return f"Wall(wall={self.wall})"
class Floor:
"""
This class represents a floor in the game Azul.
Attributes:
start (int): The number of start (special) tiles on the floor.
blue (int): The number of blue tiles on the floor.
yellow (int): The number of yellow tiles on the floor.
red (int): The number of red tiles on the floor.
black (int): The number of black tiles on the floor.
white (int): The number of white tiles on the floor.
"""
def __init__(self, floor_json):
self.start = floor_json['start']
self.blue = floor_json['blue']
self.yellow = floor_json['yellow']
self.red = floor_json['red']
self.black = floor_json['black']
self.white = floor_json['white']
def __str__(self):
return f"Floor(start={self.start}, blue={self.blue}, yellow={self.yellow}, red={self.red}, black={self.black}, white={self.white})"
class GameState:
"""
This class represents the state of a game in the Ozai library collected from the ozai server.
Attributes:
n_players (int): The number of players in the game.
current_player: The name of the current player.
starting_player: The name of the starting player.
player_names (list): A list of strings of the names of the players.
game_end (bool): A flag indicating whether the game has ended.
rounds (int): The number of rounds that have been played.
days (int): The number of days that have passed in the game.
bag (Bag): An instance of the Bag class representing the bag of tiles.
lid (Bag): An instance of the Bag class representing the lid of the bag.
factories (list): A list of Factory instances representing the factories in the game.
market (Market): An instance of the Market class representing the market.
players (list): A list of Player instances representing the players in the game.
"""
def __init__(self, gamedata_json):
json = gamedata_json
self.n_players = json['n_players']
self.current_player = json['current_player']
self.starting_player = json['starting_player']
self.player_names = json['player_names']
self.game_end = json['game_end']
self.rounds = json['rounds']
self.days = json['days']
self.bag = Bag(json['bag'])
self.lid = Bag(json['lid']) # Assuming lid has the same structure as bag
self.factories = [Factory(factory_json) for factory_json in json['factories']]
self.market = Market(json['market'])
self.players = [Player(name, player_json) for name, player_json in json['players'].items()]
def __str__(self):
return f"GameState(n_players={self.n_players}, current_player={self.current_player}, starting_player={self.starting_player}, player_names={self.player_names}, game_end={self.game_end}, rounds={self.rounds}, days={self.days}, bag={self.bag}, lid={self.lid}, factories={self.factories}, market={self.market}, players={self.players})"
def init_game(names=["a", "b"]):
"""
Initialize a game with the given player names and return the game_id.
This function sends a POST request to the Ozai game server to create a new game.
The server responds with a game_id which is returned by this function.
If the server responds with a status code other than 200, this function returns None.
Parameters:
names (list of str): The names of the players in the game. Defaults to ["a", "b"].
Returns:
str: The game_id of the newly created game, or None if the game could not be created.
"""
player_names = {"player_names": names}
response = requests.post(ozai_url + 'game', json=player_names)
if response.status_code == 200:
game_id = response.json()
print("Game ID:", game_id)
return game_id
else:
return None
def join_game(game_id, player_name):
"""
Join an initialized game with the given player name.
This function sends a GET request to the Ozai game server to join an existing game.
The server responds with the current state of the game.
If the server responds with a status code other than 200, this function returns None.
Parameters:
game_id (str): The ID of the game to join.
player_name (str): The name of the player joining the game.
Returns:
list: The list of players in the game, or None if the game could not be joined.
"""
response = requests.get(ozai_url + 'game/' + game_id + '?player=' + player_name)
if response.status_code != 200:
return None
players = response.json().get('players')
return players
def create_game(names=["a", "b"]):
"""
Create a game with the players given in names, by initializing it and joining the players.
This function first initializes a game with the given player names by calling the init_game function.
Then, it joins each player to the game by calling the join_game function for each player name.
Finally, it returns the game_id of the created game.
Parameters:
names (list of str): The names of the players. Defaults to ["a", "b"].
Returns:
str: The game_id of the newly created game.
"""
game_id = init_game(names)
for name in names:
join_game(game_id, name)
return game_id
def submit_action(game_id, player_name, source=0, destination=0, color="start", policy="strict"):
"""
Submit an action to the game.
This function sends a PUT request to the Ozai game server to submit an action for a player.
The action is a dictionary with the keys being the action type and the values being the arguments.
The server responds with the status of the action submission.
If the server responds with a status code of 200, this function returns True, otherwise it returns False.
Parameters:
game_id (str): The ID of the game.
player_name (str): The name of the player submitting the action.
source (int or str): The source of the action. Defaults to 0. (0 = market, and increasing number for factories in order they come in)
destination (int or str): The destination of the action. Defaults to 0 (What pattern line or floor to place onto).
color (str): The color of the tile to take. Defaults to "start" (actually illegal to take the start tile though).
policy (str): The policy of the action. Defaults to "strict" (loose will drop tiles to the floor if the move was invalid.).
Returns:
bool: True if the action was successfully submitted, False otherwise.
"""
if source != "market":
if source < 0 or source > 4:
return False
if destination != "floor":
if destination < 0 or destination > 4:
return False
action = {
'player': str(player_name),
'policy': policy,
'color': str(color).lower(),
'source': source,
'destination': destination
}
response = requests.put(ozai_url + 'game/' + game_id, json=action)
if response.status_code == 200:
return True
else:
return False
def get_gamestate(game_id) -> GameState:
"""
Get the current state of the game.
This function sends a GET request to the Ozai game server to get the current state of the game.
The server responds with the game state in JSON format, which is then converted to a GameState object.
If the server responds with a status code other than 200, this function returns None.
Parameters:
game_id (str): The ID of the game.
Returns:
GameState: The current state of the game, or None if the game state could not be retrieved.
"""
response = requests.get(ozai_url + 'game/' + game_id)
if response.status_code == 200:
game_state = response.json()
game_state = GameState(game_state)
return game_state
else:
return None
def get_score(game_id, player_name):
"""
Get the score of a player in the game.
This function first gets the current state of the game by calling the get_gamestate function.
Then, it iterates over the players in the game state and returns the points of the player with the given name.
Parameters:
game_id (str): The ID of the game.
player_name (str): The name of the player.
Returns:
int: The points of the player, or None if the player could not be found.
"""
GameState = get_gamestate(game_id)
for player in GameState.players:
if player_name == player.name:
return player.points
def game_over(game_id):
"""
Check if the game is over.
This function first gets the current state of the game by calling the get_gamestate function.
Then, it returns the game_end attribute of the game state.
If the game state could not be retrieved, this function returns False.
Parameters:
game_id (str): The ID of the game.
Returns:
bool: True if the game is over, False otherwise.
"""
try:
GameState = get_gamestate(game_id)
return GameState.game_end
except:
return False
def get_all_valid_moves(game_id, player_name):
"""
Get all valid moves for a player in the game.
This function first gets the current state of the game by calling the get_gamestate function.
Then, it generates a list of all possible moves by iterating over all sources, colors, and destinations.
It checks if the source has the color we want to move, and if so, it adds the move to the list of valid moves.
Finally, it filters out invalid destinations if the destination is a pattern line and the wall contains the color.
Parameters:
game_id (str): The ID of the game.
player_name (str): The name of the player.
Returns:
list: The list of all valid moves for the player.
"""
# Get the game state
GameState = get_gamestate(game_id)
# Generate a list of all possible moves
sources = ["market"] + [i for i, factory in enumerate(GameState.factories) if any([factory.blue, factory.yellow, factory.red, factory.black, factory.white])]
colors = ["blue", "yellow", "red", "black", "white"]
destinations = ["floor"] + list(range(len(GameState.factories)))
valid_moves = []
for source in sources:
for color in colors:
for destination in destinations:
# Check if the source has the color we want to move
if source == "market":
amount = getattr(GameState.market, color)
if amount > 0:
valid_moves.append((source, destination, color, amount))
else:
amount = getattr(GameState.factories[source], color)
if amount > 0:
valid_moves.append((source, destination, color, amount))
# Filter out invalid destinations if the destinaiton is a pattern line, and the wall contains the color
wall_colors = [
['blue', 'yellow', 'red', 'black', 'white'],
['white', 'blue', 'yellow', 'red', 'black'],
['black', 'white', 'blue', 'yellow', 'red'],
['red', 'black', 'white', 'blue', 'yellow'],
['yellow', 'red', 'black', 'white', 'blue'],
]
moves = []
player = [player for player in GameState.players if player.name == player_name][0]
for move in valid_moves:
if move[1] == "floor":
moves.append(move)
else:
# print(player.pattern_lines[move[1]])
#if element in wall is tru convert it to the correct color
player.wall = [[wall_colors[i][j] if player.wall[i][j] else False for j in range(5)] for i in range(5)]
#check that the wall does not contain the color on the same row as the pattern line, and that the pattern line does not contain another color
if not move[2] in player.wall[move[1]]:
if player.pattern_lines[move[1]]['color'] == move[2] or player.pattern_lines[move[1]]['color'] == None:
moves.append(move)
valid_moves = moves
return valid_moves
def do_move(game_id, player_name, filter_strategy=strategy_random):
"""
Perform a move for a player in the game.
This function first gets all valid moves for the player by calling the get_all_valid_moves function.
Then, it filters the moves based on the given filter strategy.
If there are any filtered moves, it randomly selects one and submits it by calling the submit_action function.
If there are no filtered moves, it randomly selects a move from all valid moves and returns it.
Parameters:
game_id (str): The ID of the game.
player_name (str): The name of the player.
filter_strategy (function): The strategy to filter the moves. Defaults to strategy_random.
Returns:
tuple: The move that was performed.
"""
moves = get_all_valid_moves(game_id, player_name)
try:
GameState = get_gamestate(game_id)
player = [player for player in GameState.players if player.name == player_name][0]
filtered_moves = []
for move in moves:
if filter_strategy(move,GameState,player):
filtered_moves.append(move)
# Submit a random move, of the filtered ones.
move = random.choice(filtered_moves)
submit_action(game_id, player_name, move[0], move[1], move[2])
return move
except:
#if filtered all moves, just submit a random move from the moves
result = random.choice(moves)
return result
def play_game(gameid, players, strategy):
"""
Play a game with the given players and strategy.
This function first prints the game ID, player names, and strategy name.
Then, it repeatedly performs moves for each player by calling the do_move function, until the game is over.
When the game is over, it gets the score of each player by calling the get_score function, prints the scores, and returns them.
Parameters:
gameid (str): The ID of the game.
players (list of str): The names of the players.
strategy (function): The strategy to filter the moves.
Returns:
list: The scores of the players.
"""
print(f"Playing game {gameid} with players {players} and strategy {strategy.__name__}")
while not game_over(gameid):
for player in players:
mov = do_move(gameid, player, filter_strategy=strategy)
if game_over(gameid):
#get the score of the players
score = []
for player in players:
score.append(get_score(gameid, player))
print(f"Game Over, scores: {score}")
return score