4
\$\begingroup\$

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':
 board[0].add(X(board, turn))
 else:
 board[1].add(O(board, turn))
 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:
 # Iterate over the blank squares, to get the best square to play
 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()
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Sep 19, 2014 at 15:20
\$\endgroup\$
1
  • \$\begingroup\$ [![Some Problem is in your code ](i.sstatic.net/pJjnQ.png)](https://i.sstatic.net/pJjnQ.png) 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 \$\endgroup\$ Commented Sep 15, 2016 at 14:02

2 Answers 2

2
\$\begingroup\$
  • 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.

answered Sep 22, 2014 at 19:21
\$\endgroup\$
3
\$\begingroup\$

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]

what about:

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!

answered Sep 19, 2014 at 17:27
\$\endgroup\$
2
  • \$\begingroup\$ Thanks for the review! What would you prefer instead of the system call? \$\endgroup\$ Commented Sep 19, 2014 at 18:40
  • \$\begingroup\$ I simply would not clear the screen... Or look for some alternative solution, maybe as shown here: forum.codecall.net/topic/… \$\endgroup\$ Commented Sep 20, 2014 at 11:32

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.