#!/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) #delay sleep(7) 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="random"): # 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 } print(f"action {action}") response = requests.put(ozai_url + 'game/' + game_id, json=action) print(f"response {response}") if response.status_code != 200: action = { 'player': str(player_name), 'policy': "loose", 'color': str(color).lower(), 'source': source, 'destination': destination } response = requests.put(ozai_url + 'game/' + game_id, json=action) return response 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_advanced_old(move, GameState, player): if move[1] == 'floor': # Avoid overfilling the floor if sum(player.floor.values()) + move[3] > 7: return False return True remaining_space = move[1] + 1 - player.pattern_lines[move[1]]['number'] # Prioritize moves that fill the pattern line exactly, enabling a wall tile placement. if move[3] == remaining_space: return True # Deprioritize moves that will overfill a pattern line if move[3] > remaining_space: return False # Ensure that placing the tile does not block you from placing other colors on the same row row_color_in_wall = any(player.wall[move[1]][i] == move[2] for i in range(5)) if row_color_in_wall: return False # Prioritize moves that fill pattern lines near completion (1 tile left) if player.pattern_lines[move[1]]['number'] == remaining_space - 1: return True return True def strategy_advanced(move, GameState, player): # Destructure move for clarity factory, pattern_line, tile_color, tile_count = move # Floor management: Avoid overfilling the floor line if pattern_line == 'floor': if sum(player.floor.values()) + tile_count > 7: return False return True # Calculate the remaining space in the selected pattern line remaining_space = pattern_line + 1 - player.pattern_lines[pattern_line]['number'] # If the move exactly fills the pattern line, prioritize it if tile_count == remaining_space: return True # Deprioritize moves that overfill a pattern line (tiles that would spill over) if tile_count > remaining_space: return False # Check if placing the tile blocks placing other colors in the same row # This prevents placing a tile in a pattern line where the corresponding wall row already has that color if any(player.wall[pattern_line][i] == tile_color for i in range(5)): return False # Prioritize filling pattern lines that are close to completion (one tile away) if remaining_space == 1: return True # If none of the conditions above trigger, accept the move return True def strategy_lookahead(move, GameState, player): def simulate_future_state(GameState, move, player): """ Simulate the GameState after making the current move and return the hypothetical player's state. """ future_GameState = GameState # Assume deep copy or similar for actual implementation # Apply the move if move[1] == 'floor': future_floor_count = sum(player.floor.values()) + move[3] if future_floor_count > 7: return None # Overfilling the floor is a bad move else: player.pattern_lines[move[1]]['number'] += move[3] if player.pattern_lines[move[1]]['number'] == move[1] + 1: # Tile will be placed on the wall in the next turn player.wall[move[1]][player.wall[move[1]].index(False)] = move[2] player.pattern_lines[move[1]]['number'] = 0 # Reset the pattern line # Simulate scoring or any other immediate effect # (Depending on how your game state works, update this appropriately) return player def evaluate_future_state(future_player): """ Evaluate the future state for the player and return a score. """ # Simple heuristic: prioritize a mix of empty spaces filled, tiles on the wall, and penalty on the floor score = sum(sum(row) for row in future_player.wall) - sum(future_player.floor.values()) return score # Evaluate the immediate impact immediate_impact = strategy_advanced(move, GameState, player) if not immediate_impact: return False # Simulate the game state after the current move future_player_state = simulate_future_state(GameState, move, player) if future_player_state is None: return False # Avoid moves that lead to a bad future state # Evaluate the future state future_score = evaluate_future_state(future_player_state) # Set a threshold or comparison logic to decide whether the move is good enough if future_score > evaluate_future_state(player): # Basic comparison with current state 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) if len(filtered_moves) <= 1: raise Exception("No valid moves") # Submit a random move, of the filtered ones. move = random.choice(filtered_moves) return move except: #if filtered all moves, just submit a random move from the moves move = random.choice(moves) return move 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) submit_action(gameid, player, mov[0], mov[1], mov[2]) print(f"Player {player} did move {mov}, current score: {get_score(gameid, player)}, current round {get_gamestate(gameid).rounds}") 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 if __name__ == "__main__": parser = argparse.ArgumentParser(description='Play a game with specified strategies.') parser.add_argument('--game_id', type=str, default="", 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", "advanced", "lookahead", 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 == "": 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) elif strategy == "advanced": play_game(game_id, players, strategy_advanced) elif strategy == "lookahead": play_game(game_id, players, strategy_lookahead) elif strategy == "ai": import ml play_game(game_id, players, ml.strategy_ai) else: play_game(game_id, players, strategy_random)