4
\$\begingroup\$

I built a Tic-Tac-Toe game in Python. The board is made of ASCII characters and it is the user versus an AI. The user chooses a number (1-9) and if the corresponding spot is open then either an 'X' or an 'O' will be placed there. I wrote it in Python 2.7, then upgraded to Python 3.4, so I made it compatible with that. I'm not sure if it is still compatible with Python 2.7. I think that I was able to clean up most of the code, but I feel like my AI code is a bit messy. The whole game is here on Github.

AI_turn.py

import random

def AI_turn_easy(board, possible_nums, AI_XO):

    # generates a random, available space on the board and makes it an 'O'
    randChoice = random.choice(possible_nums)
    possible_nums.remove(randChoice)
    board.spaces[randChoice] = AI_XO

    return possible_nums


def AI_turn_hard(board, possible_nums, AI_XO):

    # All of the possible winning configurations
    possible_configs = [["1", "2", "3"],
                        ["1", "4", "7"],
                        ["1", "5", "9"],
                        ["2", "5", "8"],
                        ["3", "6", "9"],
                        ["3", "5", "7"],
                        ["4", "5", "6"],
                        ["7", "8", "9"]]

    # Shuffles the configurations so the computer doesn't run through the same order every time
    random.shuffle(possible_configs)

    # Check each configuration; if there is two of the three spaces equal each other, put an 'O' in the other space
    for config in possible_configs:
        if board.spaces[config[0]] == board.spaces[config[1]]:
            if board.spaces[config[2]] in possible_nums:
                possible_nums.remove(board.spaces[config[2]])
                board.spaces[config[2]] = AI_XO
                return possible_nums
        elif board.spaces[config[0]] == board.spaces[config[2]]:
            if board.spaces[config[1]] in possible_nums:
                possible_nums.remove(board.spaces[config[1]])
                board.spaces[config[1]] = AI_XO
                return possible_nums
        elif board.spaces[config[1]] == board.spaces[config[2]]:
            if board.spaces[config[0]] in possible_nums:
                possible_nums.remove(board.spaces[config[0]])
                board.spaces[config[0]] = AI_XO
                return possible_nums
        else:
            pass

    # If there aren't any matches of two in any of the rows, choose a random, available space
    return AI_turn_easy(board, possible_nums, AI_XO)


def AI_turn_impossible(board, possible_nums, AI_XO):
    # All of the possible winning configurations
    possible_configs = [["1", "2", "3"],
                        ["1", "4", "7"],
                        ["1", "5", "9"],
                        ["2", "5", "8"],
                        ["3", "6", "9"],
                        ["3", "5", "7"],
                        ["4", "5", "6"],
                        ["7", "8", "9"]]

    # Shuffles the configurations so the computer doesn't run through the same order every time
    random.shuffle(possible_configs)

    # Check each configuration; if there is two of the three spaces equal each other, put an 'O' in the other space
    for config in possible_configs:
        if board.spaces[config[0]] == board.spaces[config[1]]:
            if board.spaces[config[2]] in possible_nums:
                possible_nums.remove(board.spaces[config[2]])
                board.spaces[config[2]] = AI_XO
                return possible_nums
        elif board.spaces[config[0]] == board.spaces[config[2]]:
            if board.spaces[config[1]] in possible_nums:
                possible_nums.remove(board.spaces[config[1]])
                board.spaces[config[1]] = AI_XO
                return possible_nums
        elif board.spaces[config[1]] == board.spaces[config[2]]:
            if board.spaces[config[0]] in possible_nums:
                possible_nums.remove(board.spaces[config[0]])
                board.spaces[config[0]] = AI_XO
                return possible_nums
        else:
            pass

    # Take either the middle or a corner piece, which makes it impossible to win (I think)
    for space in random.shuffle(['5', '1', '7', '3', '9']):
        if space in possible_nums:
            possible_nums.remove(space)
            board.spaces[space] = AI_XO
            return possible_nums

    return AI_turn_easy(board, possible_nums, AI_XO)

This is the code that runs the game

game.py

from board_class import Board
from difficulty import difficulty
from chooseXO import chooseXO
from AI_turn import AI_turn_easy, AI_turn_hard, AI_turn_impossible
from user_turn import p1_turn
from check_win import check_win
from play_again import play_again
import time

def main():
    def tic_tac_toe():
        # print("\n" * n) - makes it appear as if it is a new screen

        print("\n" * 500)

        diff = difficulty()
        time.sleep(1.5)

        board = Board()
        turns_taken = 0
        possible_nums = [str(i) for i in range (1,10)]
        last_move, AI_XO, p1_XO = chooseXO()

        while turns_taken < 9:
            print("\n" * 200)
            board.print_board()

            if last_move == "p1":
                print("AI's turn")
                time.sleep(1.5)
                if diff == "E":
                    possible_nums = AI_turn_easy(board, possible_nums, AI_XO)
                elif diff == "H":
                    possible_nums = AI_turn_hard(board, possible_nums, AI_XO)
                elif diff == "I":
                    possible_nums = AI_turn_impossible(board, possible_nums, AI_XO)
                last_move = "AI"    

            elif last_move == "AI":
                possible_nums = p1_turn(board, possible_nums, p1_XO)
                last_move = "p1"

            win = check_win(board, turns_taken)
            if win == None:
                pass
            else:
                break

            turns_taken += 1

        print("\n" * 200)
        board.print_board()

        if win == AI_XO:
            print("AI wins. You lose :(")
        elif win == p1_XO:
            print("You win :) Congratulations!")
        elif win == "draw":
            print("It was a draw")

        time.sleep(2)

    tic_tac_toe()

    times_played = 1
    while times_played < 10:
        print("\n" * 200)
        if play_again():
            tic_tac_toe()
            times_played += 1
        else:
            break

    print("Goodbye")
    time.sleep(1.5)
    quit()

if __name__ == "__main__":
    main()
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

You have a massive block or repeated code, instead make a function and call it two times:

def avoid_losing_and_win_if_possible(board):
    random.shuffle(POSSIBLE_CONFIGS)

    # Check each configuration; if there is two of the three spaces equal each other, put an 'O' in the other space
    for config in possible_configs:
        if board.spaces[config[0]] == board.spaces[config[1]]:
            if board.spaces[config[2]] in possible_nums:
                possible_nums.remove(board.spaces[config[2]])
                board.spaces[config[2]] = AI_XO
                return possible_nums
        elif board.spaces[config[0]] == board.spaces[config[2]]:
            if board.spaces[config[1]] in possible_nums:
                possible_nums.remove(board.spaces[config[1]])
                board.spaces[config[1]] = AI_XO
                return possible_nums
        elif board.spaces[config[1]] == board.spaces[config[2]]:
            if board.spaces[config[0]] in possible_nums:
                possible_nums.remove(board.spaces[config[0]])
                board.spaces[config[0]] = AI_XO
                return possible_nums
        else:
            pass
    return None


def AI_turn_hard(board, possible_nums, AI_XO):
    move = avoid_losing_and_win_if_possible(board)
    return move if move is not None else AI_turn_easy(board, possible_nums, AI_XO)


def AI_turn_impossible(board, possible_nums, AI_XO):
    move = avoid_losing_and_win_if_possible(board)
    if move: return move

    for space in random.shuffle(['5', '1', '7', '3', '9']):
        if space in possible_nums:
            possible_nums.remove(space)
            board.spaces[space] = AI_XO
            return possible_nums

    return AI_turn_easy(board, possible_nums, AI_XO)

Modularization: print("\n" * 500) is repeated many times in your code, you should name a small function:

def clear_screen():
    print('\n' * 500) 

Keep indentation low: move the tic_tac_toe definition outside of main


Avoid magic numbers: 9 should be a constant NUMBER_OF_SQUARES, remember to use possible_nums = [str(i) for i in range (1,NUMBER_OF_SQUARES+1)] also 1.5 should be TIME_TO_WAIT.


Don't lag: time.sleep(1.5) why are you wilfully lagging?


Minor idiom: thing is None is better than thing == None


Shadow built-ins for better usability: board.print_board() is weird, modify your class in order to be able to call print(board) by shadowing __repr__


Again about modularity: A function like the following would make the code clearer and simpler to read.

def generate_end_message(win):
    if win == AI_XO:
        return "AI wins. You lose :("
    elif win == p1_XO:
        return "You win :) Congratulations!"
    elif win == "draw":
        return "It was a draw"

Use dictionaries: A dictionary would be less verbose here:

        if diff == "E":
            possible_nums = AI_turn_easy(board, possible_nums, AI_XO)
        elif diff == "H":
            possible_nums = AI_turn_hard(board, possible_nums, AI_XO)
        elif diff == "I":
            possible_nums = AI_turn_impossible(board, possible_nums, AI_XO)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.