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

Training Data #9

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ __pycache__


# ignore any CSV files written to the data directory:
data/*.csv
data/*/*.csv
56 changes: 0 additions & 56 deletions app/ai/README.md

This file was deleted.

24 changes: 15 additions & 9 deletions app/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ def __repr__(self):

"""

@property
def notation(self) -> str:
"""
Represents the board's current state in simple string format like "-X-O-X-OX".

Position corresponds with square names ['A1','B1','C1','A2','B2','C2','A3','B3','C3'] and indices [0,1,2,3,4,5,6,7,8].
"""
return "".join([square.notation for square in self.squares])
#@property
#def notation(self) -> str:
# """
# Represents the board's current state in simple string format like "-X-O-X-OX".
#
# Position corresponds with square names ['A1','B1','C1','A2','B2','C2','A3','B3','C3'] and indices [0,1,2,3,4,5,6,7,8].
# """
# return "".join([square.notation for square in self.squares])


def get_square(self, square_name):
Expand All @@ -57,7 +57,13 @@ def get_square(self, square_name):
def get_squares(self, square_names):
return [square for square in self.squares if square.name in square_names]

def set_square(self, square_name, player_letter):
def set_square(self, square_name: str, player_letter: str):
"""
Params:
square_name

player_letter
"""
square = self.get_square(square_name)
if not square.letter:
square.letter = player_letter
Expand Down
6 changes: 3 additions & 3 deletions app/game.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

from itertools import cycle
from copy import deepcopy

from app.board import Board
from app.player import select_player
Expand Down Expand Up @@ -46,9 +47,8 @@ def take_turn(self, turn: tuple):
Pass the turn param as a tuple in the form of (player_letter, square_name).
"""
player_letter, square_name = turn
initial_board_state = self.board.notation # important to note this before changing the board

move = Move(board_state=initial_board_state, active_player=player_letter, selected_square=square_name)
initial_board = deepcopy(self.board) # important to note this before changing the board
move = Move(board=initial_board, active_player=player_letter, selected_square=square_name)

# make the move / change the board state:
self.board.set_square(square_name, player_letter)
Expand Down
113 changes: 113 additions & 0 deletions app/jobs/generate_moves.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@



import os

from pandas import DataFrame

from app import OPPOSITE_LETTERS
#from app.board import SQUARE_NAMES
from app.game import Game
from app.player import select_player
from app.jobs.timer import Timer


# for the strategies, use "RANDOM" for random moves, or "MINIMAX-AB" for expert moves
X_STRATEGY = os.getenv("X_STRATEGY", default="RANDOM")
O_STRATEGY = os.getenv("O_STRATEGY", default="RANDOM")

GAME_COUNT = int(os.getenv("GAME_COUNT", default="1_000"))



class EvaluatedGame(Game):
@property
def player_rewards(self):
if self.winner:
# reward the winner and punish the loser:
winning_letter = self.winner["letter"]
losing_letter = OPPOSITE_LETTERS[winning_letter]
return {winning_letter: 1, losing_letter: 0}
else:
# give neutral scores to both players:
return {"X": 0.5, "O": 0.5}


class MoveEvaluator:
def __init__(self):
self.timer = Timer()
self.players = [
select_player(letter="X", strategy=X_STRATEGY),
select_player(letter="O", strategy=O_STRATEGY),
]
self.GAME_COUNT = GAME_COUNT
self.moves_df = None

def perform(self, export=True):
self.timer.start()
records = []
for game_counter in range(0, self.GAME_COUNT):
game = EvaluatedGame(players=self.players)
game.play()

player_rewards = game.player_rewards
for move_counter, move in enumerate(game.move_history):
active_player = move.active_player

# if the active player takes this move they will get the outcome
move.board.set_square(square_name=move.selected_square, player_letter=active_player)

records.append({
"game_id": game_counter + 1, # start ids at 1 instead of 0
"move_id": move_counter + 1, # start ids at 1 instead of 0
#"board_state": move.board.notation,
"a1": move.board.get_square("A1").notation,
"b1": move.board.get_square("B1").notation,
"c1": move.board.get_square("C1").notation,
"a2": move.board.get_square("A2").notation,
"b2": move.board.get_square("B2").notation,
"c2": move.board.get_square("C2").notation,
"a3": move.board.get_square("A3").notation,
"b3": move.board.get_square("B3").notation,
"c3": move.board.get_square("C3").notation,
"player": active_player,
#"square_name": move.selected_square,
#"square_idx": SQUARE_NAMES.index(move.selected_square), # translate squares to index 0-8 to match board notation (maybe)
"outcome": player_rewards[active_player],
})

self.timer.end()
print("------------------------")
print("PLAYED", self.GAME_COUNT, "GAMES", f"IN {self.timer.duration_seconds} SECONDS")
print("TOTAL MOVES:", len(records))
self.moves_df = DataFrame(records)
print(self.moves_df.head())

if export:
print("------------------------")
print("SAVING DATA TO FILE...")
#csv_filename = f"{self.players[0].letter}_{self.players[0].player_type.replace('-','')}"
#csv_filename += "_vs_"
#csv_filename += f"_{self.players[1].letter}_{self.players[1].player_type.replace('-','')}"

csv_filename = self.players[0].player_type.replace('-','') + "_"
csv_filename += self.players[1].player_type.replace('-','')
csv_filename += f"_{self.GAME_COUNT}.csv"
csv_filename = csv_filename.lower()
csv_filepath = os.path.join(os.path.dirname(__file__), "..", "..", "data", "moves", csv_filename)

self.moves_df.to_csv(csv_filepath, index=False)
print(os.path.abspath(csv_filepath))

return self.moves_df




if __name__ == "__main__":



job = MoveEvaluator()

job.perform()
92 changes: 0 additions & 92 deletions app/jobs/play_moves.py

This file was deleted.

10 changes: 5 additions & 5 deletions app/move.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@

class Move:

def __init__(self, board_state, active_player, selected_square):
def __init__(self, board, active_player, selected_square):
"""
Params

board_state (str) the initial board state before the player made the move
board (Board) the initial board state before the player made the move

active_player (str) the letter of the player who made the move ("X" or "O")

selected_square (str) the name of the square the player selected (e.g "A1")
"""
self.board_state = board_state #> "XX-OO----"
self.active_player = active_player #> "X"
self.selected_square = selected_square #> "C1"
self.board = board
self.active_player = active_player
self.selected_square = selected_square

def __repr__(self):
return f"<Move '{self.active_player}' to {self.selected_square} >"
Loading