Chapter 11 - Othello

How to Play Othello

Othello is a board game that is played on a grid (so we will use a Cartesian coordinate system with XY coordinates, like we did with Sonar.) It is a game played with two players. Our version of the game will have a computer AI that is more complicated than the AI we made for Tic Tac Toe. In fact, this AI is so good that it will probably beat you almost every time you play. (I know I lose whenever I play against it!)

Othello has an 8 x 8 board with tiles that are black on one side and white on the other (our game will use O's and X's though). The starting board looks like this:

Each player takes turn placing down a new tile of their color. Any of the opponent's tiles that are between the new tile and the other tiles of that color is flipped. For example, say the white player places a new white tile on space 5, 6:

The black tile at 5, 5 is in between the new white tile and the existing white tile at 5, 4. That black tile is flipped over and becomes a new white tile:

Tiles in all directions are flipped as long as they are in between the player's new tile and existing tile. Below, the white player places a tile at 3, 6 and flips black tiles in both directions (marked by the red lines.)

As you can see, each player can quickly grab a majority of the tiles on the board. But the more tiles of one color there are, the more that can be taken by the opponent:

Players must always make a move that captures at least one tile. The game ends when a player either cannot make a move, or the board is completely full. In most games, the board fills up to end the game. The player with the most tiles of their color wins.

The basic strategy of Othello is to look at which move would turn over the most tiles. But you should also consider taking a move that will not let your opponent recapture many tiles after your move. Placing a tile on the sides or, even better, the corners is good because there is less chance that those tiles will end up between your opponent's tiles.

The AI we make for this game will simply look for any corner moves they can take. If there are no corner moves available, then the computer will select the move that claims the most tiles.

Sample Run

Welcome to Othello!
Do you want to be X or O?
x
The player will go first.
    1   2   3   4   5   6   7   8
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
1 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
2 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
3 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
4 |   |   |   | X | O |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
5 |   |   |   | O | X |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
6 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
7 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
8 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
You have 2 points. The computer has 2 points.
Enter your move, or type quit to end the game, or hints to turn off/on hints.
53
    1   2   3   4   5   6   7   8
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
1 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
2 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
3 |   |   |   |   | X |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
4 |   |   |   | X | X |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
5 |   |   |   | O | X |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
6 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
7 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
8 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
You have 4 points. The computer has 1 points.
Press Enter to see the computer's move.
    1   2   3   4   5   6   7   8
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
1 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
2 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
3 |   |   |   | O | X |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
4 |   |   |   | O | X |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
5 |   |   |   | O | X |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
6 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
7 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
8 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
You have 3 points. The computer has 3 points.
Enter your move, or type quit to end the game, or hints to turn off/on hints.
35
    1   2   3   4   5   6   7   8
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
1 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
2 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
3 |   |   |   | O | X |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
4 |   |   |   | X | X |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
5 |   |   | X | X | X |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
6 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
7 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
8 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
You have 6 points. The computer has 1 points.
Press Enter to see the computer's move.


...skipped for brevity...


    1   2   3   4   5   6   7   8
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
1 | O | O | O | O | O | O | O | O |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
2 | O | O | O | O | O | O | O | O |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
3 | O | O | O | O | O | O | O | O |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
4 | O | O | X | O | O | O | O | O |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
5 | O | O | O | X | O | X | O | X |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
6 | O | X | O | X | X | O | O |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
7 | O | X | X | O | O | O | O | O |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
8 | O | X | X | O |   |   | X |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
You have 12 points. The computer has 48 points.
Enter your move, or type quit to end the game, or hints to turn off/on hints.
86
X scored 15 points. O scored 46 points.
You lost. The computer beat you by 31 points.
Do you want to play again? (yes or no)
no

As you can see, the AI was pretty good at beating me. To help the player out, we'll program our game to provide hints. If the player types 'hints' as their move, they can toggle the hints mode on and off. When hints mode is on, all the possible moves the player can make will show up on the board as '.' characters, like this:

    1   2   3   4   5   6   7   8
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
1 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
2 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
3 |   | . | . | . |   | X | O | . |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
4 | O | O | O | O | O | O | O |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
5 |   |   |   | X | O | X |   | . |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
6 |   |   | X | . | O | X |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
7 |   |   |   | . |   | . |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
  |   |   |   |   |   |   |   |   |
8 |   |   |   |   |   |   |   |   |
  |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+
You have 5 points. The computer has 10 points.
Enter your move, or type quit to end the game, or hints to turn off/on hints.

Source Code

othello.py
  1. # Othello
  2. import random
  3. import sys
  4. def drawBoard(board):
  5.     # This function prints out the board that it was passed. Returns None.
  6.     HLINE = '  +---+---+---+---+---+---+---+---+'
  7.     VLINE = '  |   |   |   |   |   |   |   |   |'
  8.     print '    1   2   3   4   5   6   7   8'
  9.     print HLINE
  10.     for y in range(8):
  11.         print VLINE
  12.         print y+1,
  13.         for x in range(8):
  14.             print '| %s' % (board[x][y]),
  15.         print '|'
  16.         print VLINE
  17.         print HLINE
  18. def resetBoard(board):
  19.     # Blanks out the board it is passed, except for the original starting position.
  20.     for x in range(8):
  21.         for y in range(8):
  22.             board[x][y] = ' '
  23.     # Starting pieces:
  24.     board[3][3] = 'X'
  25.     board[3][4] = 'O'
  26.     board[4][3] = 'O'
  27.     board[4][4] = 'X'
  28. def getNewBoard():
  29.     # Creates a brand new, blank board data structure.
  30.     board = []
  31.     for i in range(8):
  32.         board.append([' '] * 8)
  33.     return board
  34. def isValidMove(board, tile, xstart, ystart):
  35.     # Returns False if the player's move on space xstart, ystart is invalid.
  36.     # If it is a valid move, returns a list of spaces that would become the player's if they made a move here.
  37.     if board[xstart][ystart] != ' ' or not isOnBoard(xstart, ystart):
  38.         return False
  39.     board[xstart][ystart] = tile # temporarily set the tile on the board.
  40.     if tile == 'X':
  41.         otherTile = 'O'
  42.     else:
  43.         otherTile = 'X'
  44.     tilesToFlip = []
  45.     for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]:
  46.         x, y = xstart, ystart
  47.         x += xdirection # first step in the direction
  48.         y += ydirection # first step in the direction
  49.         if isOnBoard(x, y) and board[x][y] == otherTile:
  50.             # There is a piece belonging to the other player next to our piece.
  51.             x += xdirection
  52.             y += ydirection
  53.             if not isOnBoard(x, y):
  54.                 continue
  55.             while board[x][y] == otherTile:
  56.                 x += xdirection
  57.                 y += ydirection
  58.                 if not isOnBoard(x, y): # break out of while loop, then continue in for loop
  59.                     break
  60.             if not isOnBoard(x, y):
  61.                 continue
  62.             if board[x][y] == tile:
  63.                 # There are pieces to flip over. Go in the reverse direction until we reach the original space, noting all the tiles along the way.
  64.                 while True:
  65.                     x -= xdirection
  66.                     y -= ydirection
  67.                     if x == xstart and y == ystart:
  68.                         break
  69.                     tilesToFlip.append([x, y])
  70.     board[xstart][ystart] = ' ' # restore the empty space
  71.     if len(tilesToFlip) == 0: # If no tiles were flipped, this is not a valid move.
  72.         return False
  73.     return tilesToFlip
  74. def isOnBoard(x, y):
  75.     # Returns True if the coordinates are located on the board.
  76.     return x >= 0 and x <= 7 and y >= 0 and y <=7
  77. def getBoardWithValidMoves(board, tile):
  78.     # Returns a new board with . marking the valid moves the given player can make.
  79.     dupeBoard = getBoardCopy(board)
  80.     for x, y in getValidMoves(dupeBoard, tile):
  81.         dupeBoard[x][y] = '.'
  82.     return dupeBoard
  83. def getValidMoves(board, tile):
  84.     # Returns a list of [x,y] lists of valid moves for the given player on the given board.
  85.     validMoves = []
  86.     for x in range(8):
  87.         for y in range(8):
  88.             if isValidMove(board, tile, x, y) != False:
  89.                 validMoves.append([x, y])
  90.     return validMoves
  91. def getScoreOfBoard(board):
  92.     # Determine the score. Returns a dictionary with keys 'X' and 'O'.
  93.     xscore = 0
  94.     oscore = 0
  95.     for x in range(8):
  96.         for y in range(8):
  97.             if board[x][y] == 'X':
  98.                 xscore += 1
  99.             if board[x][y] == 'O':
  100.                 oscore += 1
  101.     return {'X':xscore, 'O':oscore}
  102. def enterPlayerTile():
  103.     # Let's the player type which tile they want to be.
  104.     # Returns a list with the player's tile as the first item, and the computer's tile as the second.
  105.     tile = ''
  106.     while not (tile == 'X' or tile == 'O'):
  107.         print 'Do you want to be X or O?'
  108.         tile = raw_input().upper()
  109.     # the first element in the tuple is the player's tile, the second is the computer's tile.
  110.     if tile == 'X':
  111.         return ['X', 'O']
  112.     else:
  113.         return ['O', 'X']
  114. def whoGoesFirst():
  115.     # Randomly choose the player who goes first.
  116.     if random.randint(0, 1) == 0:
  117.         return 'computer'
  118.     else:
  119.         return 'player'
  120. def playAgain():
  121.     # This function returns True if the player wants to play again, otherwise it returns False.
  122.     print 'Do you want to play again? (yes or no)'
  123.     return raw_input().lower().startswith('y')
  124. def makeMove(board, tile, xstart, ystart):
  125.     # Place the tile on the board at xstart, ystart, and flip any of the opponent's pieces.
  126.     # Returns False if this is an invalid move, True if it is valid.
  127.     tilesToFlip = isValidMove(board, tile, xstart, ystart)
  128.     if tilesToFlip == False:
  129.         return False
  130.     board[xstart][ystart] = tile
  131.     for x, y in tilesToFlip:
  132.         board[x][y] = tile
  133.     return True
  134. def getBoardCopy(board):
  135.     # Make a duplicate of the board list and return the duplicate.
  136.     dupeBoard = getNewBoard()
  137.     for x in range(8):
  138.         for y in range(8):
  139.             dupeBoard[x][y] = board[x][y]
  140.     return dupeBoard
  141. def isOnCorner(x, y):
  142.     # Returns True if the position is in one of the four corners.
  143.     return (x == 0 and y == 0) or (x == 7 and y == 0) or (x == 0 and y == 7) or (x == 7 and y == 7)
  144. def getPlayerMove(board, playerTile):
  145.     # Let the player type in their move.
  146.     # Returns the move as [x, y] (or returns the strings 'hints' or 'quit')
  147.     DIGITS1TO8 = '1 2 3 4 5 6 7 8'.split()
  148.     while True:
  149.         print 'Enter your move, or type quit to end the game, or hints to turn off/on hints.'
  150.         move = raw_input().lower()
  151.         if move == 'quit':
  152.             return 'quit'
  153.         if move == 'hints':
  154.             return 'hints'
  155.         if len(move) == 2 and move[0] in DIGITS1TO8 and move[1] in DIGITS1TO8:
  156.             x = int(move[0]) - 1
  157.             y = int(move[1]) - 1
  158.             if isValidMove(board, playerTile, x, y) == False:
  159.                 continue
  160.             else:
  161.                 break
  162.         else:
  163.             print 'That is not a valid move. Type the x digit (1-8), then the y digit (1-8).'
  164.             print 'For example, 81 will be the top-right corner.'    
  165.     return [x, y]
  166. def getComputerMove(board, computerTile):
  167.     # Given a board and the computer's tile, determine where to
  168.     # move and return that move as a [x, y] list.
  169.     possibleMoves = getValidMoves(board, computerTile)
  170.     # randomize the order of the possible moves
  171.     random.shuffle(possibleMoves)
  172.     # always go for a corner if available.
  173.     for x, y in possibleMoves:
  174.         if isOnCorner(x, y):
  175.             return [x, y]
  176.     # Go through all the possible moves and remember the best scoring move
  177.     bestScore = -1
  178.     for x, y in possibleMoves:
  179.         dupeBoard = getBoardCopy(board)
  180.         makeMove(dupeBoard, computerTile, x, y)
  181.         score = getScoreOfBoard(dupeBoard)[computerTile]
  182.         if score > bestScore:
  183.             bestMove = [x, y]
  184.             bestScore = score
  185.     return bestMove
  186. def showPoints(playerTile, computerTile):
  187.     # Prints out the current score.
  188.     scores = getScoreOfBoard(mainBoard)
  189.     print 'You have %s points. The computer has %s points.' % (scores[playerTile], scores[computerTile])
  190. print 'Welcome to Othello!'
  191. while True:
  192.     # Reset the board and game.
  193.     mainBoard = getNewBoard()
  194.     resetBoard(mainBoard)
  195.     playerTile, computerTile = enterPlayerTile()
  196.     showHints = False
  197.     turn = whoGoesFirst()
  198.     print 'The ' + turn + ' will go first.'
  199.     while True:
  200.         if turn == 'player':
  201.             # Player's turn.
  202.             if showHints:
  203.                 validMovesBoard = getBoardWithValidMoves(mainBoard, playerTile)
  204.                 drawBoard(validMovesBoard)
  205.             else:
  206.                 drawBoard(mainBoard)
  207.             showPoints(playerTile, computerTile)
  208.             move = getPlayerMove(mainBoard, playerTile)
  209.             if move == 'quit':
  210.                 print 'Thanks for playing!'
  211.                 sys.exit() # terminate the program
  212.             elif move == 'hints':
  213.                 showHints = not showHints
  214.                 continue
  215.             else:
  216.                 makeMove(mainBoard, playerTile, move[0], move[1])
  217.             if getValidMoves(mainBoard, computerTile) == []:
  218.                 break
  219.             else:
  220.                 turn = 'computer'
  221.         else:
  222.             # Computer's turn.
  223.             drawBoard(mainBoard)
  224.             showPoints(playerTile, computerTile)
  225.             raw_input('Press Enter to see the computer\'s move.')
  226.             x, y = getComputerMove(mainBoard, computerTile)
  227.             makeMove(mainBoard, computerTile, x, y)
  228.             if getValidMoves(mainBoard, playerTile) == []:
  229.                 break
  230.             else:
  231.                 turn = 'player'
  232.     # Display the final score.
  233.     drawBoard(mainBoard)
  234.     scores = getScoreOfBoard(mainBoard)
  235.     print 'X scored %s points. O scored %s points.' % (scores['X'], scores['O'])
  236.     if scores[playerTile] > scores[computerTile]:
  237.         print 'You beat the computer by %s points! Congratulations!' % (scores[playerTile] - scores[computerTile])
  238.     elif scores[playerTile] < scores[computerTile]:
  239.         print 'You lost. The computer beat you by %s points.' % (scores[computerTile] - scores[playerTile])
  240.     else:
  241.         print 'The game was a tie!'
  242.     
  243.     if not playAgain():
  244.         break

Code Explanation

Before we get into the code, we should talk about the board data structure. This data structure is a list of lists, just like the one in our previous Sonar game. The list is created so that board[x][y] will represent the character on space located at XY. This character can either be a ' ' space character (to represent a blank space), a '.' period character (to represent a hint), or a 'X' or 'O' (to represent a player's tile). Whenever you see a parameter named board, that parameter variable is meant to be this list of lists board data structure.

  1. # Othello
  2. import random
  3. import sys

We import the random module for its randint() and choice() functions and the sys module for its exit() function.


  1. def drawBoard(board):
  2.     # This function prints out the board that it was passed. Returns None.
  3.     HLINE = '  +---+---+---+---+---+---+---+---+'
  4.     VLINE = '  |   |   |   |   |   |   |   |   |'
  5.     print '    1   2   3   4   5   6   7   8'
  6.     print HLINE

The drawBoard() function will print out the current game board based on the data structure in board. Notice that each square of the board looks like this:

+---+
|   |
| X | (or maybe an O instead of X)
|   |
+---+

Since we are going to print the string with the horizontal line (and plus signs at the intersections) over and over again, we will store that in a constant variable named HLINE. There are also lines above and below the very center of X or O tile that are nothing but '|' characters (called "pipes") with three spaces in between. We will store this string in a constant named HLINE.

Line 11 is the first print statement executed, and it prints out the labels for the X-axis along the top of the board. Line 12 prints the top horizontal line of the board.

  1.     for y in range(8):
  2.         print VLINE
  3.         print y+1,
  4.         for x in range(8):
  5.             print '| %s' % (board[x][y]),
  6.         print '|'
  7.         print VLINE
  8.         print HLINE

Printing each row of spaces on the board is fairly repetitive, so we can use a loop here. We will loop eight times, once for each row. Line 15 prints the label for the Y-axis on the left side of the board, and has a comma at the end of it to prevent a new line. This is so we can have another loop (which again loops eight times, once for each space) print out each space (along with the 'X', 'O', or ' ' character for that space depending on what is stored in board.

The print statement inside the inner loop also has a comma at the end of it, meaning a space character is printed instead of a newline character. This produces the second space in the pipe-space-tile-space string that we print out, over and over for eight times. That will produce a single line on the screen that looks like '| X | X | X | X | X | X | X | X '. After the inner loop is done, the print statement on line 18 prints out the final '|' character along with a newline (since it does not end with a comma).

(The print statement forces us to always print a newline character or a space at the end of everything we print. If we do not want this last character, then we can always use the sys.stdout.write() function, which has a single string parameter that it prints out. Be sure to import sys first before calling this function.)

The code inside the outer print loop that begins on line 13 prints out an entire row of the board like this:

|   |   |   |   |   |   |   |   |
| X | X | X | X | X | X | X | X |
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+

When printed out eight times, it forms the entire board (of course, some of the spaces on the board will have 'O' or ' ' instead of 'X'.:

|   |   |   |   |   |   |   |   |
| X | X | X | X | X | X | X | X |
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
| X | X | X | X | X | X | X | X |
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
| X | X | X | X | X | X | X | X |
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
| X | X | X | X | X | X | X | X |
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
| X | X | X | X | X | X | X | X |
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
| X | X | X | X | X | X | X | X |
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
| X | X | X | X | X | X | X | X |
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |
| X | X | X | X | X | X | X | X |
|   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+
  1. def resetBoard(board):
  2.     # Blanks out the board it is passed, except for the original starting position.
  3.     for x in range(8):
  4.         for y in range(8):
  5.             board[x][y] = ' '

Here we use a loop inside a loop to set the board data structure to be all blanks. We will call the resetBoard() function whenever we start a new game and want to remove the tiles from a previous game.

  1.     # Starting pieces:
  2.     board[3][3] = 'X'
  3.     board[3][4] = 'O'
  4.     board[4][3] = 'O'
  5.     board[4][4] = 'X'

When we start a new game of Othello, it isn't enough to have a completely blank board. At the very beginning, each player has two tiles already laid down in the very center, so we will also have to set those.

We do not have to return the board variable, because board is a reference to a list. Even when we make changes inside the local function's scope, these changes happen in the global scope to the list that was passed as an argument. (Remember, this is one way list variables are different from variables that do not contain lists.)


  1. def getNewBoard():
  2.     # Creates a brand new, blank board data structure.
  3.     board = []
  4.     for i in range(8):
  5.         board.append([' '] * 8)
  6.     return board

The board function creates a new board data structure and returns it. Line 39 creates the outer list and assigns a reference to this list to board. Line 41 create the inner lists using list replication. ([' '] * 8 is the same as [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '] but with less typing.) The for loop here runs line 41 eight times to create the eight inner lists. The spaces represent a completely empty game board.


  1. def isValidMove(board, tile, xstart, ystart):
  2.     # Returns False if the player's move on space xstart, ystart is invalid.
  3.     # If it is a valid move, returns a list of spaces that would become the player's if they made a move here.
  4.     if not isOnBoard(xstart, ystart) or board[xstart][ystart] != ' ':
  5.         return False
  6.     board[xstart][ystart] = tile # temporarily set the tile on the board.
  7.     if tile == 'X':
  8.         otherTile = 'O'
  9.     else:
  10.         otherTile = 'X'
  11.     tilesToFlip = []

isValidmove() is one of the more complicated functions. Given a board data structure, the player's tile, and the XY coordinates for player's move, this function should return True if the Othello game rules allow that move and False if they don't.

The easiest check we can do to disqualify a move is to see if the XY coordinates are on the game board and if the space at XY is empty. This is what the board statement on line 47 checks for. board is a function we will write that makes sure both the X and Y coordinates are between 1 and 8.

For the purposes of this function, we will go ahead and mark the XY coordinate pointed to by xstart and ystart with the player's tile. We set this place on the board back to a space before we leave this function.

The player's tile has been passed to us, but we will need to be able to identify the other player's tile. If the player's tile is 'X' then obviously the other player's tile is 'O'. And it is the same the other way.

Finally, if the given XY coordinate ends up as a valid position, we will return a list of all the opponent's tiles that would be flipped by this move.


  1.     for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]:

The for loop iterates through a list of lists which represent directions you can move on the game board. The game board is a Cartesian coordinate system with an X and Y direction. There are eight directions you can move: up, down, left, right, and the four diagonal directions. We will move around the board in a direction by adding the first value in the two-item list to our X coordinate, and the second value to our Y coordinate.

Because the X coordinates increase as you go to the right, you can "move" to the right by adding 1 to the X coordinate. Moving to the left is the opposite, you would subtract 1 (or add -1) from the X coordinate.

We can move up, down, left, and right by adding or subtracting to only one coordinate at a time. But to move diagonally, we need to add to both coordinates. For example, adding 1 to the X coordinate to move right and adding -1 to the Y coordinate to move up would result in moving to the up-right diagonal direction.

Here is a diagram to make it easier to remember which two-item list represents which direction:


  1.     for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]:
  2.         x, y = xstart, ystart
  3.         x += xdirection # first step in the direction
  4.         y += ydirection # first step in the direction

Line 60 sets an x and y variable to be the same value as xstart and ystart, respectively. We will change x and y to "move" in the direction that xdirection and ydirection dictate. xstart and ystart will stay the same so we can remember which space we originally intended to check. (Remember, we need to set this place back to a space character, so we shouldn't overwrite the values in them.)

We make the first step in the direction as the first part of our algorithm.


  1.         if isOnBoard(x, y) and board[x][y] == otherTile:
  2.             # There is a piece belonging to the other player next to our piece.
  3.             x += xdirection
  4.             y += ydirection
  5.             if not isOnBoard(x, y):
  6.                 continue # skip to next direction

Remember, in order for this to be a valid move, the first step in this direction must be 1) on the board and 2) must be occupied by the other player's tile. Otherwise there is no chance to flip over any of the opponent's tiles. In that case, the if statement on line 63 is not True and execution goes back to the for statement for the next direction.

But if the first space does have the other player's tile, then we should keep proceeding in that direction until we reach on of our own tiles. If we move off of the board, then we should continue back to the for statement to try the next direction.


  1.             while board[x][y] == otherTile:
  2.                 x += xdirection
  3.                 y += ydirection
  4.                 if not isOnBoard(x, y): # break out of while loop, then continue in for loop
  5.                     break
  6.             if not isOnBoard(x, y):
  7.                 continue

The while loop on line 69 ensures that x and y keep going in the current direction as long as we keep seeing a trail of the other player's tiles. If x and y move off of the board, we break out of the for loop and the flow of execution moves to line 74. What we really want to do is not break out of the for loop but continue in the for loop. But if we put a continue statement on line 73, that would only continue to the while loop on line 69.

Instead, we recheck not isOnBoard(x, y) on line 74 and then continue from there, which goes to the next direction in the for statement. It is important to know that break and continue will only break or continue in the loop they are called from, and not an outer loops that contain the loop they are called from.


  1.             if board[x][y] == tile:
  2.                 # There are pieces to flip over. Go in the reverse direction until we reach the original space, noting all the tiles along the way.
  3.                 while True:
  4.                     x -= xdirection
  5.                     y -= ydirection
  6.                     if x == xstart and y == ystart:
  7.                         break
  8.                     tilesToFlip.append([x, y])

If the while loop on line 69 stopped looping because the condition was False, then we have found a space on the board that holds our own tile or a blank space. Line 76 checks if this space on the board holds one of our tiles. If it does, then we have found a valid move. We start a new while loop, this time subtracting x and y to move them in the opposite direction they were originally going. We note each space between our tiles on the board by appending the space to the tilesToFlip list.

We break out of the while loop once x and y have returned to the original position (which is still stored in xstart and ystart).


  1.     board[xstart][ystart] = ' ' # restore the empty space
  2.     if len(tilesToFlip) == 0: # If no tiles were flipped, this is not a valid move.
  3.         return False
  4.     return tilesToFlip

After moving in all eight directions, the tilesToFlip list will contain the XY coordiantes all of our opponent's tiles that would be flipped if the player moved on xstart, ystart. Remember, the isValidMove() function is only checking to see if the original move was valid, it does not actually change the data structure of the game board.

If none of the eight directions ended up flipping at least one of the opponent's tiles, then tilesToFlip would be an empty list and this move would not be valid. In that case, isValidMove() should return False. Otherwise, we should return tilesToFlip.


  1. def isOnBoard(x, y):
  2.     # Returns True if the coordinates are located on the board.
  3.     return x >= 0 and x <= 7 and y >= 0 and y <=7

isOnBoard() is called from isValidMove(), and is just shorthand for the rather complicated boolean expression that returns True if both x and y are in between 0 and 7.


  1. def getBoardWithValidMoves(board, tile):
  2.     # Returns a new board with . marking the valid moves the given player can make.
  3.     dupeBoard = getBoardCopy(board)
  4.     for x, y in getValidMoves(dupeBoard, tile):
  5.         dupeBoard[x][y] = '.'
  6.     return dupeBoard

getBoardWithValidMoves() is used to return a game board data structure that has '.' characters for all valid moves on the board. This is used by the hints mode to display to the player a board with all possible moves marked on it.

Notice that this function creates a duplicate game board data structure instead of modifying the one passed to it by the board parameter.


  1. def getValidMoves(board, tile):
  2.     # Returns a list of [x,y] lists of valid moves for the given player on the given board.
  3.     validMoves = []
  4.     for x in range(8):
  5.         for y in range(8):
  6.             if isValidMove(board, tile, x, y):
  7.                 validMoves.append([x, y])
  8.     return validMoves

The for function returns a list of two-item lists that hold the XY coordinates for all valid moves for tile's player, given a particular game board board.

This function uses two loops to check every single XY coordinate (all sixty four of them) by calling tile on that space and checking if it returns False or a list of possible moves (in which case it is a valid move). Each valid XY coordinate is appended to the list, validMoves.

The bool() Function

Remember how you could use the int() and str() functions to get the integer and string value of other data types? For example, str(42) would return the string '42', and int('100') would return the integer 100.

There is a similar function for the boolean data type, bool(). Most other data types have one value that is considered the False value for that data type, and every other value is consider True. The integer 0, the floating point number 0.0, the empty string, the empty list, and the empty dictionary are all considered to be False when used as the condition for an if or loop statement. All other values are True. Try typing the following into the interactive shell:

bool(0)
bool(0.0)
bool('')
bool([])
bool({})

bool(1)
bool('Hello')
bool([1, 2, 3, 4, 5])
bool({'spam':'cheese', 'fizz':'buzz'})

When use these other data types as a condition, you do not even need to use the bool() function. The data types are automatically interpreted as boolean values. This is similar to how print statements can be passed non-string values and will automatically interpret them as strings when they print. if 'Hello': is treated the same as if True:, you do not need to type the full if bool('Hello'):.

However, when used in conditions that have comparison operators (such as == or !=) and boolean operators (and, or, and not), you should use the bool() function. if 'Hello' == True: or if '' == False: are both print conditions, because it is comparing values of two different data types (which are of course not equal, just as 42 and '42' are different values.

Generally, if the condition is more than a single variable by itself, use the bool() function. But for line 111, whose condition is a single function call is isValidMove(board, tile, x, y) that by itself evaluates to a single value (either False or a list of XY coordinates), not using bool() is fine. If in doubt, use bool().


  1. def getScoreOfBoard(board):
  2.     # Determine the score. Returns a dictionary with keys 'X' and 'O'.
  3.     xscore = 0
  4.     oscore = 0
  5.     for x in range(8):
  6.         for y in range(8):
  7.             if board[x][y] == 'X':
  8.                 xscore += 1
  9.             if board[x][y] == 'O':
  10.                 oscore += 1
  11.     return {'X':xscore, 'O':oscore}

The getScoreOfBoard() function uses nested for loops to check all sixty four spaces on the board and see which tile (if any) is on them. For each 'X' tile, the code increments xscore. For each 'O' tile, the code increments oscore.

Notice that this function does not return a two-item list of the scores (that might be a bit confusing. Instead the function returns a dictionary with keys 'X' and 'O' whose values are the respective scores.


  1. def enterPlayerTile():
  2.     # Let's the player type which tile they want to be.
  3.     # Returns a list with the player's tile as the first item, and the computer's tile as the second.
  4.     tile = ''
  5.     while not (tile == 'X' or tile == 'O'):
  6.         print 'Do you want to be X or O?'
  7.         tile = raw_input().upper()

This function asks the player which tile they want to be, either for or for. The for loop will keep looping until the player types in for or for.

  1.     # the first element in the tuple is the player's tile, the second is the computer's tile.
  2.     if tile == 'X':
  3.         return ['X', 'O']
  4.     else:
  5.         return ['O', 'X']

The enterPlayerTile() function then returns a two-item list, where the player's tile choice is the first item and the computer's tile is the second. We use a list here instead of a dictionary so that the assignment statement calling this function can use the multiple assignment trick. (See line 252.)


  1. def whoGoesFirst():
  2.     # Randomly choose the player who goes first.
  3.     if random.randint(0, 1) == 0:
  4.         return 'computer'
  5.     else:
  6.         return 'player'

The whoGoesFirst() function randomly selects who goes first, and returns either 'computer' or 'player'.


  1. def playAgain():
  2.     # This function returns True if the player wants to play again, otherwise it returns False.
  3.     print 'Do you want to play again? (yes or no)'
  4.     return raw_input().lower().startswith('y')

We have used the playAgain() in our previous games. If the player types in something that begins with 'y', then the function returns False. Otherwise the function returns False.


  1. def makeMove(board, tile, xstart, ystart):
  2.     # Place the tile on the board at xstart, ystart, and flip any of the opponent's pieces.
  3.     # Returns False if this is an invalid move, True if it is valid.
  4.     tilesToFlip = isValidMove(board, tile, xstart, ystart)

makeMove() is the function we call when we want to place down a tile on the board, flipping tiles as dictated by the rules. This function modifies the board data structure that is passed as a parameter directly. Changes made to the board variable (because it is a list) will be made to the global scope as well. Most of the work is done by isValidMove(), which returns a list of XY coordinates (in a two-item list) of tiles that need to be flipped.


  1.     if tilesToFlip == False:
  2.         return False
  3.     board[xstart][ystart] = tile
  4.     for x, y in tilesToFlip:
  5.         board[x][y] = tile
  6.     return True

If the return value of isValidMove() was False, then the X and Y coordinates that were passed to makeMove() (which we passed to isValidMove()) were for an invalid move. In that case, we return False from makeMove().

Otherwise, isValidMove() would have returned a list of spaces on the board to put down our tiles (the 'X' or 'O' string in tile). Line 166 sets the space that the player has moved on, and the for loop after that sets all the tiles that are in tilesToFlip.


  1. def getBoardCopy(board):
  2.     # Make a duplicate of the board list and return the duplicate.
  3.     dupeBoard = getNewBoard()
  4.     for x in range(8):
  5.         for y in range(8):
  6.             dupeBoard[x][y] = board[x][y]
  7.     return dupeBoard

getBoardCopy() is different from getNewBoard(). getNewBoad() will create a new game board data structure which is blank. getBoardCopy() will create a new game board data structure, but then copy all of the pieces in the board parameter. This function is used by our AI to have a game board that it can change around without changing the real game board (much like how you may imagine making moves on a copy of the board in your mind, but not actually put pieces down on the real board).

A call to getNewBoard() handles getting a fresh game board data structure. Then the nested for loops copy the tiles from board to our duplicate board, dupeBoard.


  1. def isOnCorner(x, y):
  2.     # Returns True if the position is in one of the four corners.
  3.     return (x == 0 and y == 0) or (x == 7 and y == 0) or (x == 0 and y == 7) or (x == 7 and y == 7)

This function is much like isOnBoard(). Because all Othello boards are 8 x 8 in size, we only need the XY coordinates to be passed to this function, not a game board data structure itself. This function returns True if the coordinates are on either (0,0), (7,0), (0,7) or (7,7). Otherwise isOnCorner() returns False.


  1. def getPlayerMove(board, playerTile):
  2.     # Let the player type in their move.
  3.     # Returns the move as [x, y] (or returns the strings 'hints' or 'quit')
  4.     DIGITS1TO8 = '1 2 3 4 5 6 7 8'.split()

The getPlayerMove() function is called to let the player type in the coordinates of their next move (and it checks to make sure that move is valid). The player can also type in 'hints' to turn hints mode on (if it is off) or off (if it is on), or 'quit' to quit the game.

The DIGITS1TO8 constant is a list of numbers one to eight as strings, which we will use in this function.


  1.     while True:
  2.         print 'Enter your move, or type quit to end the game, or hints to turn off/on hints.'
  3.         move = raw_input().lower()
  4.         if move == 'quit':
  5.             return 'quit'
  6.         if move == 'hints':
  7.             return 'hints'

The False loop will keep looping until the player has typed in a valid move. First we check if the player wants to quit or toggle hints mode, and return the strings False and False, respectively. We use the lower() method on the string returned by raw_input() so the player can type 'HINTS' or 'Quit' but still have the command understood by our game.

The code that calls getPlayerMove() will handle what to do if the player wants to quit or toggle hints mode.


  1.         if len(move) == 2 and move[0] in DIGITS1TO8 and move[1] in DIGITS1TO8:
  2.             x = int(move[0]) - 1
  3.             y = int(move[1]) - 1
  4.             if isValidMove(board, playerTile, x, y) == False:
  5.                 continue
  6.             else:
  7.                 break

Our game is expecting that the player would have typed in the XY coordinates of their move as two numbers without anything in between them. The if statement first checks that the size of the string the player typed in is 2. After that, the if statement also checks that both move[0] (the first character in the string) and move[1] (the second character in the string) are strings that exist in DIGITS1TO8, which we defined at the beginning of the function.

Remember that our game board data structures have indexes from 0 to 7, not 1 to 8. We show 1 to 8 when we print the board using drawBoard() because people are used to numbers beginning at 1 instead of 0. So when we convert the strings in DIGITS1TO8 and DIGITS1TO8 to integers, we also subtract 1.

Even if the player typed in a correct move, we still need to check that the move is allowed by the rules of Othello. We do this by calling isValidMove(), passing the game board data structure, the player's tile, and the XY coordinates of the move. If isValidMove() returns False, then we execute the continue statement so that the flow of execution goes back to the beginning of the while loop and asks the player for the move again.

If isValidMove() does not return False, then we know the player typed in a valid move and we should break out of the while loop.


  1.         else:
  2.             print 'That is not a valid move. Type the x digit (1-8), then the y digit (1-8).'
  3.             print 'For example, 81 will be the top-right corner.'    

If the if statement's condition on line 201 was False, then the player did not type in a valid move. We should display a message instructing them how to type in moves that our Othello program can understand. Afterwards, the execution moves back to the while statement on line 193 because line 210 is not only the last line in the else-block, but also the last line in the while-block.


  1.     return [x, y]

Finally, getPlayerMove() returns a two-item list with the XY coordinates of the player's valid move.


  1. def getComputerMove(board, computerTile):
  2.     # Given a board and the computer's tile, determine where to
  3.     # move and return that move as a [x, y] list.
  4.     possibleMoves = getValidMoves(board, computerTile)

getComputerMove() and is where our Othello AI is implemented. The getValidMoves() function is very helpful for our AI. Normally we use the results from getValidMoves() for hints move. Hints mode will print '.' period characters on the board to show the player all the potential moves they can make. But if we call getValidMoves() with the computer AI's tile (in computerTile), we can get all the possible moves that the computer can make. We will select the best move from this list.


The random.shuffle() Function

  1.     # randomize the order of the possible moves
  2.     random.shuffle(possibleMoves)

First, we are going to use the random.shuffle() function to randomize the order of moves in the possibleMoves list. This is a function in the random module which will reorder the list that you pass to it. For example, try typing the following into the interactive shell:

import random
spam = [1, 2, 3, 4, 5, 6, 7, 8]
spam
random.shuffle(spam)
spam
random.shuffle(spam)
spam
random.shuffle(spam)
spam

Your results may be different, because the reshuffling is random. As you can see, random.shuffle() itself does not have a return value. It modifies the list directly, much like our resetBoard() function does. This is why you must type spam into the shell to see the new value it has taken on.

Code Explanation continued...

We will explain why we want to shuffle the possibleMoves list, but first let's look at our algorithm.

  1.     # always go for a corner if available.
  2.     for x, y in possibleMoves:
  3.         if isOnCorner(x, y):
  4.             return [x, y]

First, we loop through every move in possibleMoves and if any of them are on the corner, we return that as our move. Corner moves are a good idea because once a tile has been placed on the corner, it can never be flipped over. Since possibleMoves is a list of two-item lists, we use the multiple assignment trick in our for loop to set x and y.

Because we immediately return on finding the first corner move in random, if random contains multiple corner moves we always go with the first one. But since possibleMoves was shuffled on line 220, it is completely random which corner move is first in the list.


  1.     # Go through all the possible moves and remember the best scoring move
  2.     bestScore = -1
  3.     for x, y in possibleMoves:
  4.         dupeBoard = getBoardCopy(board)
  5.         makeMove(dupeBoard, computerTile, x, y)
  6.         score = getScoreOfBoard(dupeBoard)[computerTile]
  7.         if score > bestScore:
  8.             bestMove = [x, y]
  9.             bestScore = score
  10.     return bestMove

If there are no corner moves, we will go through the entire list and find out which move gives us the highest score. The for loop will set x and y to every move in possibleMoves. bestMove will be set to the highest scoring move we've found so far, and bestScore will be set to its score. When the code in the loop finds a move that scores higher than bestScore, we will store that move and score as the new values of bestMove and bestScore.

In order to figure out the score of the possible move we are currently iterating on, we first make a duplicate game board data structure by calling getBoardCopy(). We want a copy so we can modify without changing the real game board data structure stored in board.

Then we call makeMove(), passing the duplicate board instead of the real board. makeMove() will handle placing the computer's tile and the flipping the player's tile on the duplicate board.

We call getScoreOfBoard() with the duplicate board, which returns a dictionary where the keys are 'X' and 'O', and the values are the scores. getScoreOfBoard() does not know if the computer is 'X' or 'O', which is why it returns a dictionary.

By making a duplicate board, we can simulate a future move and test the results of that move without changing the actual game board data structure. This is very helpful in deciding which move is the best possible move to make.

Pretend that getScoreOfBoard() returns the dictionary {'X':22, 'O':8} and computerTile is 'X'. Then getScoreOfBoard(dupeBoard)[computerTile] would evaluate to {'X':22, 'O':8}['X'], which would then evaluate to 22. If 22 is larger than bestScore, bestScore is set to 22 and bestMove is set to the current x and y values we are looking at.

You may have noticed that on line 228 we first set bestScore to -1. This is so that the first move we look at in our for loop over possibleMoves will be set to the first bestMove. This will guarantee that bestMove is set to one of the moves when we return it.

Say that the highest scoring move in possibleMoves would give the computer a score of 42. What if there was more than one move in possibleMoves that would give this score? The for loop we use would always go with the first move that scored 42 points, because bestMove and bestScore only change if the move is greater than the highest score. A tie will not change bestMove and bestScore.

We do not always want to go with the first move in the possibleMoves list, because that would make our AI predictable by the player. But it is random, because on line 220 we shuffled the possibleMoves list. Even though our code always chooses the first of these tied moves, is random which of the moves will be first in the list because the order is random. This ensures that the AI will not be predictable when there is more than one best move.


  1. def showPoints(playerTile, computerTile):
  2.     # Prints out the current score.
  3.     scores = getScoreOfBoard(mainBoard)
  4.     print 'You have %s points. The computer has %s points.' % (scores[playerTile], scores[computerTile])

showPoints() simply calls the getScoreOfBoard() function and then prints out the player's score and the computer's score. Remember that getScoreOfBoard() returns a dictionary with the keys 'X' and 'O' and values of the scores for the X and O players.

That's all the functions we define for our Othello game. The code starting on line 246 will implement the actual game and make calls to these functions when they are needed.


  1. print 'Welcome to Othello!'
  2. while True:
  3.     # Reset the board and game.
  4.     mainBoard = getNewBoard()
  5.     resetBoard(mainBoard)
  6.     playerTile, computerTile = enterPlayerTile()
  7.     showHints = False
  8.     turn = whoGoesFirst()
  9.     print 'The ' + turn + ' will go first.'

The while loop on line 248 is the main game loop. The program will loop back to line 248 each time we want to start a new game. First we get a new game board data structure by calling getNewBoard() and set the starting tiles by calling resetBoard(). mainBoard is the main game board data structure we will use for this program. The call to enterPlayerTile() will let the player type in whether they want to be 'X' or 'O', which is then stored in playerTile and computerTile.

showHints is a boolean value that determines if hints mode is on or off. We originally set it to off by setting showHints to False.

The turn variable is a string will either have the value 'player' or 'computer', and will keep track of whose turn it is. We set turn to the return value of whoGoesFirst(), which randomly chooses who will go first. We then print out who goes first to the player.

  1.     while True:
  2.         if turn == 'player':
  3.             # Player's turn.
  4.             if showHints:
  5.                 validMovesBoard = getBoardWithValidMoves(mainBoard, playerTile)
  6.                 drawBoard(validMovesBoard)
  7.             else:
  8.                 drawBoard(mainBoard)
  9.             showPoints(playerTile, computerTile)

The while loop that starts on line 257 will keep looping each time the player or computer takes a turn. We will break out of this loop when the current game is over.

We have an if statement with the code that runs if it is the player's turn. (The else-block that starts on line 282 has the code for the computer's turn.) The first thing we want to do is display the board to the player. If hints mode is on (which it is if showHints is True), then we want to get a board data structure that has '.' period characters on every space the player could go.

Our getBoardWithValidMoves() function does that, all we have to do is pass the game board data structure and it will return a copy that also contains '.' period characters. We then pass this board to the drawBoard() function.

If hints mode is off, then we just pass mainBoard to drawBoard().

After printing out the game board to the player, we also want to print out the current score by calling showPoints().


  1.             move = getPlayerMove(mainBoard, playerTile)

Next we let the player type in their move. getPlayerMove() handles this, and its return value is a two-item list of the X and Y coordinate of the player's move. getPlayerMove() makes sure that the move the player typed in is a valid move, so we don't have to worry about it here.


  1.             if move == 'quit':
  2.                 print 'Thanks for playing!'
  3.                 sys.exit() # terminate the program
  4.             elif move == 'hints':
  5.                 showHints = not showHints
  6.                 continue
  7.             else:
  8.                 makeMove(mainBoard, playerTile, move[0], move[1])

If the player typed in the string 'quit' for their move, then getPlayerMove() would have returned the string 'quit'. In that case, we should call the sys.exit() to terminate the program.

If the player typed in the string 'hints' for their move, then getPlayerMove() would have returned the string 'hints'. In that case, we want to turn hints mode on (if it was off) or off (if it was on). The showHints = not showHints assignment statement handles both of these cases, because not False evaluates to True and not True evaluates to False. Then we run the continue statement to loop back (turn has not changed, so it will still be the player's turn after we loop).

Otherwise, if the player did not quit or toggle hints mode, then we will call makeMove() to make the player's move on the board.


  1.             if getValidMoves(mainBoard, computerTile) == []:
  2.                 break
  3.             else:
  4.                 turn = 'computer'

After making the player's move, we call False to see if the computer could possibly make any moves. If False returns a blank list, then there are no more moves left that the computer could make (most likely because the board is full). In that case, we break out of the while loop and end the current game.

Otherwise, we set turn to 'computer'. The flow of execution skips the else-block and reaches the end of the while-block, so execution jumps back to the while statement on line 257. This time, however, it will be the computer's turn.


  1.         else:
  2.             # Computer's turn.
  3.             drawBoard(mainBoard)
  4.             showPoints(playerTile, computerTile)
  5.             raw_input('Press Enter to see the computer\'s move.')
  6.             x, y = getComputerMove(mainBoard, computerTile)
  7.             makeMove(mainBoard, computerTile, x, y)

The first thing we do when it is the computer's turn is call drawBoard() to print out the board to the player. Why do we do this now? Because either the computer was selected to make the first move of the game, in which case we should display the original starting picture of the board to the player before the computer makes its move. Or the player has gone first, and we want to show what the board looks like after the player has moved but before the computer has gone.

After printing out the board with drawBoard(), we also want to print out the current score with a call to showPoints().

Next we have a call to raw_input() to pause the script while the player can look at the board. This is much like how we use raw_input() to pause the program in our Jokes chapter. Often you may want to print out a string before asking the player to type something in, so raw_input() has an optional string parameter that it will print before waiting for the player to type something in. The string we pass raw_input() in this call is 'Press Enter to see the computer\'s move.'.

After the player has looked at the board and pressed Enter (any text the player typed is ignored since we do not assign the return value of raw_input() to anything), we call getComputerMove() to get the X and Y coordinates of the computer's next move. We store these coordinates in variables x and y, respectively.

Finally, we pass x and y, along with the game board data structure and the computer's tile to the makeMove() function to change the game board to reflect the computer's move. Our call to getComputerMove() got the computer's move, and the call to makeMove() makes the move on the board.


  1.             if getValidMoves(mainBoard, playerTile) == []:
  2.                 break
  3.             else:
  4.                 turn = 'player'

Lines 289 to 292 are very similar to lines 276 to 279. After the computer has made its move, we check if there exist any possible moves the human player can make. If getValidMoves() returns an empty list, then there are no possible moves. That means the game is over, and we should break out of the while loop that we are in.

Otherwise, there is at least one possible move the player should make, so we should set turn to 'player'. There is no more code in the while-block after line 292, so execution loops back to the while statement on line 257.


  1.     # Display the final score.
  2.     drawBoard(mainBoard)
  3.     scores = getScoreOfBoard(mainBoard)
  4.     print 'X scored %s points. O scored %s points.' % (scores['X'], scores['O'])
  5.     if scores[playerTile] > scores[computerTile]:
  6.         print 'You beat the computer by %s points! Congratulations!' % (scores[playerTile] - scores[computerTile])
  7.     elif scores[playerTile] < scores[computerTile]:
  8.         print 'You lost. The computer beat you by %s points.' % (scores[computerTile] - scores[playerTile])
  9.     else:
  10.         print 'The game was a tie!'

Line 294 is the first line beyond the while-block that started on line 257. This code is executed when we have broken out of that while loop, either on line 290 or 277. (The while statement's condition on line 257 is simply the value True, so we can only exit the loop through break statements.)

At this point, the game is over. We should print out the board and scores, and determine who won the game. getScoreOfBoard() will return a dictionary with keys 'X' and 'O' and values of both players' scores. By checking if the player's score is greater than, less than, or equal to the computer's score, we can know if the player won, if the player lost, or if the player and computer tied.

Subtracting one score from the other is an easy way to see by how much one player won over the other. Our print statements on lines 29 and 301 use string interpolation to put the result of this subtraction into the string that is printed.


  1.     if not playAgain():
  2.         break

The game is now over and the winner has been declared. We should call our playAgain() function, which returns True if the player typed in that they want to play another game. If playAgain() returns False (which makes the if statement's condition True), we break out of the while loop (the one that started on line 248), and since there are no more lines of code after this while-block, the program terminates.

Otherwise, playAgain() has returned True (which makes the if statement's condition False), and so execution loops back to the while statement on line 248 and a new game board is created.

Tips for Inventing Your Own Games

That does it for this book as far as games go. The next chapter expands on creating new Othello AIs, and having AIs play against each other instead of against a human player. Using the programming techniques in this book, you can start building your own simple games. Here are a few pointers:

Things Covered In This Chapter: