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,

    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
        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,
    return score_dict

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.