451 lines
17 KiB
Python
Executable File
451 lines
17 KiB
Python
Executable File
#!/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)
|