I created a game which can play tic-tac-toe with: 0 players (AI vs AI), 1 player (human vs AI) or 2 player (human vs human). Here is the AI function which wraps around my innermost function. This evaluates all the candidate board positions and returns the best move.
I searched to make sure this has not been implemented before.
Could I have feedback on my Python code in my GitHub repo.
- I would be particularly interested in knowing how to make my code clearer to read and understand.
I would be interested to know how I could improve this code, or the code which evaluates a given boardstate. For example I would like to make
score_one_gamestate
be more elegant.I have a vague feeling that I could use something instead of creating many deepcopies of the game board in the
score_one_gamestate
function. But I do not know how to do this. Additionally I do not know whether attempting this would even be a good idea.
def optimal_ai_move(board: np.array, x_or_o: str, available: list):
"""
The heart of the robot brain.
Given a board state, will return the optimal move.
:param board: numpy array of the board.
:param x_or_o: which side is the AI playing on? #This isn't exactly true in recursive calls.
:param available: available[(row, col)] = True, if the space hasn't been taken yet.
:return: best_move: (row, col). The move with the greatest number of simulated wins.
"""
possibility_dict = {}
for candidate_move in available:
deeper = copy.deepcopy(board)
deeper[candidate_move] = x_or_o
available = find_available_moves(deeper)
possibility_dict[candidate_move] = score_one_gamestate(deeper,
turn_flipper(x_or_o),
available,
x_or_o,
score_dict=None)
metric_dict = {candidate_move: (score_dict['wins'] + score_dict['draws']) / sum(score_dict.values())
for candidate_move, score_dict in possibility_dict.items()}
best_move = max(metric_dict, key=metric_dict.get)
return best_move, possibility_dict
def score_one_gamestate(board: np.array, x_or_o: str, available: list, ai_player_choice: str, score_dict=None,
weight=1) -> int:
if score_dict is None:
score_dict = {'wins': 0, 'losses': 0, 'draws': 0}
is_finished = game_over(board)
if is_finished == ai_player_choice: # This might be wrong, we want ai_player_choice
score_dict['wins'] += 1 / weight
return score_dict
elif is_finished == turn_flipper(ai_player_choice):
score_dict['losses'] += 1 / weight
return score_dict
elif is_finished == 'draw':
score_dict['draws'] += 1 / weight
return score_dict
else:
weight *= len(available)
for candidate_move in available:
deeper = copy.deepcopy(board)
deeper[candidate_move] = x_or_o
available_moves = find_available_moves(deeper)
score_dict = score_one_gamestate(deeper,
turn_flipper(x_or_o),
available_moves,
ai_player_choice,
score_dict,
weight)
return score_dict