patternpythonMinor
Optimizing a Tic-Tac-Toe AI
Viewed 0 times
tacticoptimizingtoe
Problem
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
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
Solution
You have a massive block or repeated code, instead make a function and call it two times:
Modularization:
Keep indentation low: move the
Avoid magic numbers:
Don't lag:
Minor idiom:
Shadow built-ins for better usability:
Again about modularity: A function like the following would make the code clearer and simpler to read.
Use dictionaries: A dictionary would be less verbose here:
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 mainAvoid 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 == NoneShadow 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)Code Snippets
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)def clear_screen():
print('\n' * 500)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"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)Context
StackExchange Code Review Q#83324, answer score: 2
Revisions (0)
No revisions yet.