# Tic-Tac-Toe game using the minimax algorithm

I have written a Tic-Tac-Toe game in Python that contains, among others, a player that uses the minimax algorithm. I am not exactly a beginner at Python, but I'm not very experienced with it - so I want to know if my code follows bad practices and style. Any feedback about it is welcome.

# Play tic-tac-toe. The first player will be always X.
# Each tic-tac-toe board is represented by a sequence of three values:
# (set of squares that have an X, set of squares that have a y, board's width)
import random
import os
import string

def TicTacToe(X, O, width=3):
"""Play a tic-tac-toe game between the two given functions. After each
turn, yield the new board.
Each function should get a tic-tac-toe board and a char - 'X' or 'O',
that represents the current turn, and return the number of
square where it wants to put a sign.
width is the board's width and length - it's 3 as default.

X, O -- functions
width -- natural number
"""
board = (set(), set(), width)
turn = 'X'
while result(board) == False:
if turn == 'X':
else:
yield board
turn = list({'X', 'O'} - set([turn]))[0]

def displayTicTacToe(X, O, width=3):
"""Play a tic-tac-toe game (see TicTacToe's docstring for explanation) and
display the new board to the user when a player plays, and the result of
the game after its end.

X, O - functions
width - natural number"""
for board in TicTacToe(X, O, width):
os.system('cls' if os.name == 'nt' else 'clear')  # clearscreen
print str_board(board)
winner = result(board)
if winner in {'X', 'O'}: print winner + ' won!'
elif winner == None: print 'Tie!'
else: raise ValueError("The game didn't end")

def result(board):
"""Return 'X' if X won in the given board, 'O' if O won, None if the game
ended with a tie, False if the game didn't end yet, and raise an exception
if it looks like X and O won both (the board cannot be reached using a
legal game)."""
x_squares, o_squares, width = board
rows = [{width*row+col+1 for col in range(width)} for row in range(width)]
cols = [{width*row+col+1 for row in range(width)} for col in range(width)]
diagonals = [{width*i+i+1 for i in range(width)},
{width*i+width-i for i in range(width)}]
lines = rows + cols + diagonals

x_won = any(line.issubset(x_squares) for line in lines)
o_won = any(line.issubset(o_squares) for line in lines)
if x_won:
if o_won:
raise ValueError("Illegal board")
return 'X'
if o_won:
return 'O'
if x_squares | o_squares == set(range(1, width**2+1)):
# Nobody won, but the board is full
return None  # Tie
return False

def str_board(board):
"""Return the board in a string representation, to print it."""
return_str = ''
x_squares, o_squares, width = board
for row in range(width):
for col in range(width):
square = width*row+col+1
return_str += 'X' if square in x_squares else 'O' if square in \
o_squares else ' '
if col != width-1: return_str += ' | '
if row != width-1: return_str += '\n'+'--+-'*(width-1)+'-\n'
return return_str

def human_player(board, turn):
"""Display the board to the user and ask him where does he want to put a
sign. Return the square."""
x_squares, o_squares, width = board
os.system('cls' if os.name == 'nt' else 'clear')  # clear screen
print str_board(board)
while True:
try:
square = int(raw_input('Where do you want to add ' + turn + '? '))
assert 0 < square <= width**2 and \
square not in x_squares | o_squares
return square  # this will happen if there were no exceptions
except:
print ('You should write an integer between 1 and '+str(width**2)+
', that represents a blank square.')

def minimax_player(board, turn):
"""Return a square where it's worthwhile to play according to the minimax
algorithm."""
return minimax_best_square(board, turn)[0]

def minimax_score_board(board, turn):
"""Return 1, 0 or -1 according to the minimax algorithm -- 1 if the player
that has the given turn has a winning strategy, 0 if he doesn't have a
winning strategy but he has a tie strategy, and -1 if he will lose anyway
(assuming his opponent is playing a perfect game)."""
if result(board) == turn:
return 1
if result(board) == None:
return 0
if result(board) != False:
return -1
return minimax_best_square(board, turn)[1]

def minimax_best_square(board, turn):
"""Choose a square where it's worthwhile to play in the given board and
turn, and return a tuple of the square's number and it's score according
to the minimax algorithm."""
x_squares, o_squares, width = board
max_score = -2
opponent = list({'X', 'O'} - set([turn]))[0]
squares = list(set(range(1, width**2+1)) - (x_squares | o_squares))
random.shuffle(squares)
for square in squares:
new_board = (x_squares | set([square] if turn=='X' else []),) + \
(o_squares | set([square] if turn=='O' else []), width)
score = -minimax_score_board(new_board, opponent)
if score == 1: return (square, 1)
if score > max_score:
max_score, max_square = score, square
return (max_square, max_score)

displayTicTacToe(X = minimax_player, O = human_player, width = 3)
raw_input()

• [![Some Problem is in your code ](i.stack.imgur.com/pJjnQ.png)](http://i.stack.imgur.com/… For some scenario it's not picking proper move .. scenario--> place your move so that computer win in that case computer will not place his move on appropriate place to win – shivam Sep 15 '16 at 14:02

• Don't use assertions to validate user input. Assertions can be disabled at runtime.
• Don't catch all exceptions with a bare except: clause. It is now impossible to stop your program with Ctrl-C. Catching ValueError should be enough to deal with input that can't be converted to integer, and you can raise one as well when the input is out of range.
• Name your constants. Using 'X' and 'O' directly means Python won't give you an error message if you mistype 'x' for instance.
• Speaking of constants, result returns None for tie and False for unfinished game. These meanings are not at all obvious. Using a named constant such as TIE would make your code more self-documenting. See how the comment here

return None  # Tie


becomes redundant here

return TIE


None is commonly used in place of a missing value. It would be quite logical, and clear enough, to return None from result when the game is still on, as there is no result then.

I think your code is very well written. Also very nice structure. I enjoyed reading it.

Just two notes.

One. This seems too much involved, to my taste:

turn = list({'X', 'O'} - set([turn]))[0]


turn = 'O' if turn=='X' else 'X'


or maybe

turn = {'O': 'X', 'X': 'O'}[turn]


(added: the difficulty to achieve a DRY result is an indication that the variable turn could better be an integer in (0,1) which can be used as an index in the "OX" string.)

Two. This:

    os.system('cls' if os.name == 'nt' else 'clear')  # clearscreen


is a thing I would avoid. Since the program is so clean and self contained, I would keep it as abstract as possible. Maybe in 5 years there will be an OS which could run python happily but which does not have a 'clear' command... what a pity if your program will break for such a triviality!

And if you insist in using a system call, wrap it in a function, don't write it twice!

• Thanks for the review! What would you prefer instead of the system call? – MrHezi Sep 19 '14 at 18:40
• I simply would not clear the screen... Or look for some alternative solution, maybe as shown here: forum.codecall.net/topic/… – Emanuele Paolini Sep 20 '14 at 11:32