Chapter 10 - Othello
Source Code
- # Othello
- import random
- import sys
- def drawBoard(board):
- # This function prints out the board that it was passed. Returns None.
- hline = ' +---+---+---+---+---+---+---+---+'
- vline = ' | | | | | | | | |'
- print ' 1 2 3 4 5 6 7 8'
- print hline
- for y in range(8):
- print vline
- print y+1,
- for x in range(8):
- print '| %s' % (board[x][y]),
- print '|'
- print vline
- print hline
- def resetBoard(board):
- # Blanks out the board it is passed, except for the original starting position.
- for x in range(8):
- for y in range(8):
- board[x][y] = ' '
- # Starting pieces:
- board[3][3] = 'X'
- board[3][4] = 'O'
- board[4][3] = 'O'
- board[4][4] = 'X'
- return board
- def getNewBoard():
- # Creates a brand new, blank board data structure.
- board = []
- for i in range(8):
- board.append([' '] * 8)
- return board
- def isValidMove(board, letter, startx, starty):
- # Returns True if the move to space x,y is a valid one.
- if not isOnBoard(startx, starty) or board[startx][starty] != ' ':
- return False
- directions = [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]
- if letter == 'X':
- otherLetter = 'O'
- else:
- otherLetter = 'X'
- for d in directions:
- DX = d[0]
- DY = d[1]
- x, y = startx, starty
- x += DX
- y += DY
- if isOnBoard(x, y):
- if board[x][y] == otherLetter:
- # There is a piece belonging to the other player next to our piece.
- x += DX
- y += DY
- if not isOnBoard(x, y):
- continue
- while board[x][y] == otherLetter:
- x += DX
- y += DY
- if not isOnBoard(x, y): # double loop break
- break
- if not isOnBoard(x, y):
- continue
- if board[x][y] == letter:
- return True
- return False
- def isOnBoard(x, y):
- # Returns True if the coordinates are located on the board.
- return x >= 0 and x <= 7 and y >= 0 and y <=7
- def getBoardWithValidMoves(board, letter):
- # Returns a new board with . marking the valid moves the given player can make.
- dupeBoard = getBoardCopy(board)
- for x, y in getValidMoves(dupeBoard, letter):
- dupeBoard[x][y] = '.'
- return dupeBoard
- def getValidMoves(board, letter):
- # Returns a list of [x,y] lists of valid moves for the given player on the given board.
- validMoves = []
- for x in range(8):
- for y in range(8):
- if isValidMove(board, letter, x, y):
- validMoves.append([x, y])
- return validMoves
- def getScoreOfBoard(board):
- # Determine the score. Returns a dictionary with keys 'X' and 'O'.
- xscore = 0
- oscore = 0
- for x in range(8):
- for y in range(8):
- if board[x][y] == 'X':
- xscore += 1
- if board[x][y] == 'O':
- oscore += 1
- return {'X':xscore, 'O':oscore}
- def enterPlayerLetter():
- # Let's the player type which letter they want to be.
- # Returns a list with the player's letter as the first item, and the computer's letter as the second.
- letter = ''
- while not (letter == 'X' or letter == 'O'):
- print 'Do you want to be X or O?'
- letter = raw_input().upper()
- # the first element in the tuple is the player's letter, the second is the computer's letter.
- if letter == 'X':
- return ['X', 'O']
- else:
- return ['O', 'X']
- def whoGoesFirst():
- # Randomly choose the player who goes first.
- if random.randint(0, 1) == 0:
- return 'computer'
- else:
- return 'player'
- def playAgain():
- # This function returns True if the player wants to play again, otherwise it returns False.
- print 'Do you want to play again? (yes or no)'
- return raw_input().lower().startswith('y')
- def makeMove(board, letter, startx, starty):
- # Place the letter on the board at startx, starty, and flip any of the opponent's pieces.
- # Returns False if this is an invalid move, returns True if it is valid.
- if not isValidMove(board, letter, startx, starty):
- return False
- board[startx][starty] = letter
- directions = [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]
- if letter == 'X':
- otherLetter = 'O'
- else:
- otherLetter = 'X'
- for d in directions:
- DX = d[0]
- DY = d[1]
- x, y = startx, starty
- x += DX
- y += DY
- if isOnBoard(x, y):
- if board[x][y] == otherLetter:
- # There is a piece belonging to the other player next to our piece.
- x += DX
- y += DY
- if not isOnBoard(x, y):
- continue
- while board[x][y] == otherLetter:
- x += DX
- y += DY
- if not isOnBoard(x, y): # break, then continue
- break
- if not isOnBoard(x, y):
- continue
- if board[x][y] == letter:
- # There are pieces to flip over. Go in the reverse direction.
- while True:
- x -= DX
- y -= DY
- if x == startx and y == starty:
- break
- board[x][y] = letter
- return True
- def getBoardCopy(board):
- # Make a duplicate of the board list and return the duplicate.
- dupeBoard = getNewBoard()
- for x in range(8):
- for y in range(8):
- dupeBoard[x][y] = board[x][y]
- return dupeBoard
- def isOnCorner(x, y):
- # Returns True if the position is in one of the four corners.
- return (x == 0 and y == 0) or (x == 7 and y == 0) or (x == 0 and y == 7) or (x == 7 and y == 7)
- def isSpaceFree(board, x, y):
- # Return true if the passed move is free on the passed board.
- return board[x][y] == ' '
- def getPlayerMove(board, playerLetter):
- # Let the player type in their move.
- # Returns the move as [x, y] (or returns the strings 'hints' or 'quits')
- digits1to8 = '1 2 3 4 5 6 7 8'.split()
- move = ''
- while True:
- print 'Enter your move, or type quit to end the game, or hints to turn off/on hints.'
- move = raw_input()
- if move.lower() == 'quit':
- return 'quit'
- if move.lower() == 'hints':
- return 'hints'
- if len(move) == 2 and move[0] in digits1to8 and move[1] in digits1to8:
- x = int(move[0]) - 1
- y = int(move[1]) - 1
- if not isValidMove(board, playerLetter, x, y):
- continue
- else:
- break
- else:
- print 'That is not a valid move. Type the x digit (1-8), then the y digit (1-8).'
- print 'For example, 81 will be the top-right corner.'
- return [x, y]
- def getComputerMove(board, computerLetter):
- # Given a board and the computer's letter, determine where to
- # move and return that move as a [x, y] list.
- if computerLetter == 'X':
- playerLetter = 'O'
- else:
- playerLetter = 'X'
- possibleMoves = getValidMoves(board, computerLetter)
- # create a list of moves that are in the corners, and
- # always go for a corner if available.
- cornerMoves = []
- for move in possibleMoves:
- if isOnCorner(move[0], move[1]):
- cornerMoves.append(move)
- if len(cornerMoves) > 0:
- return random.choice(getHighestScoringMoves(board, computerLetter, cornerMoves))
- # From the list of highest scoring moves, choose one at random.
- return random.choice(getHighestScoringMoves(board, computerLetter, possibleMoves))
- def getHighestScoringMoves(board, letter, moves):
- # Returns a list of the highest scoring moves from the list of moves.
- # For example, if out of the list of six moves, the highest scoring move gives
- # five more points, but there are three different moves that give five more points,
- # then return a list with those three moves.
- if len(moves) == 1:
- return moves
- currentScore = getScoreOfBoard(board)[letter]
- bestScore = -1
- allScores = []
- for i in range(len(moves)):
- dupeBoard = getBoardCopy(board)
- makeMove(dupeBoard, letter, moves[i][0], moves[i][1])
- scoreIncrease = getScoreOfBoard(dupeBoard)[letter] - currentScore
- allScores.append(scoreIncrease)
- if scoreIncrease > bestScore:
- bestScore = scoreIncrease
- topMoves = []
- for i in range(len(allScores)):
- if allScores[i] == bestScore:
- topMoves.append(moves[i])
- return topMoves
- def isBoardDone(board, letter):
- # Return True if the player with the given letter can make a move on the board. Otherwise return False.
- if getValidMoves(board, letter) == []:
- return True
- for x in range(8):
- for y in range(8):
- if board[x][y] == ' ':
- return False
- return True
- def showPoints(playerLetter, computerLetter):
- # Prints out the current score.
- scores = getScoreOfBoard(theBoard)
- print 'You have %s points. The computer has %s points.' % (scores[playerLetter], scores[computerLetter])
- print 'Welcome to Othello!'
- while True:
- # Reset the board and game.
- theBoard = getNewBoard()
- resetBoard(theBoard)
- playerLetter, computerLetter = enterPlayerLetter()
- showHints = False
- turn = whoGoesFirst()
- print 'The ' + turn + ' will go first.'
- while True
- if turn == 'player':
- # Player's turn.
- if showHints:
- validMovesBoard = getBoardWithValidMoves(theBoard, playerLetter)
- drawBoard(validMovesBoard)
- else:
- drawBoard(theBoard)
- showPoints(playerLetter, computerLetter)
- move = getPlayerMove(theBoard, playerLetter)
- if move == 'quit':
- print 'Thanks for playing!'
- sys.exit() # terminate the program
- elif move == 'hints':
- showHints = not showHints
- continue
- else:
- x, y = move
- makeMove(theBoard, playerLetter, x, y)
- if isBoardDone(theBoard, computerLetter): # computerLetter, not playerLetter
- break
- else:
- turn = 'computer'
- else:
- # Computer's turn.
- x, y = getComputerMove(theBoard, computerLetter)
- drawBoard(theBoard)
- showPoints(playerLetter, computerLetter)
- raw_input('Press Enter to see the computer\'s move.')
- makeMove(theBoard, computerLetter, x, y)
- if isBoardDone(theBoard, playerLetter): # playerLetter, not computerLetter
- break
- else:
- turn = 'player'
- # Display the final score.
- scores = getScoreOfBoard(theBoard)
- print 'X scored %s points. O scored %s points.' % (scores['X'], scores['O'])
- if scores[playerLetter] > scores[computerLetter]:
- print 'You beat the computer by %s points! Congratulations!' % (scores[playerLetter] - scores[computerLetter])
- elif scores[playerLetter] < scores[computerLetter]:
- print 'You lost. The computer beat you by %s points.' % (scores[computerLetter] - scores[playerLetter])
- else:
- print 'The game was a tie!'
-
- if not playAgain():
- break
Code Explanation
- # Othello
- import random
- import sys
- def drawBoard(board):
- # This function prints out the board that it was passed. Returns None.
- hline = ' +---+---+---+---+---+---+---+---+'
- vline = ' | | | | | | | | |'
- print ' 1 2 3 4 5 6 7 8'
- print hline
- for y in range(8):
- print vline
- print y+1,
- for x in range(8):
- print '| %s' % (board[x][y]),
- print '|'
- print vline
- print hline
- def resetBoard(board):
- # Blanks out the board it is passed, except for the original starting position.
- for x in range(8):
- for y in range(8):
- board[x][y] = ' '
- # Starting pieces:
- board[3][3] = 'X'
- board[3][4] = 'O'
- board[4][3] = 'O'
- board[4][4] = 'X'
- return board
- def getNewBoard():
- # Creates a brand new, blank board data structure.
- board = []
- for i in range(8):
- board.append([' '] * 8)
- return board
- def isValidMove(board, letter, startx, starty):
- # Returns True if the move to space x,y is a valid one.
- if not isOnBoard(startx, starty) or board[startx][starty] != ' ':
- return False
- directions = [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]
- if letter == 'X':
- otherLetter = 'O'
- else:
- otherLetter = 'X'
- for d in directions:
- DX = d[0]
- DY = d[1]
- x, y = startx, starty
- x += DX
- y += DY
- if isOnBoard(x, y):
- if board[x][y] == otherLetter:
- # There is a piece belonging to the other player next to our piece.
- x += DX
- y += DY
- if not isOnBoard(x, y):
- continue
- while board[x][y] == otherLetter:
- x += DX
- y += DY
- if not isOnBoard(x, y): # double loop break
- break
- if not isOnBoard(x, y):
- continue
- if board[x][y] == letter:
- return True
- return False
- def isOnBoard(x, y):
- # Returns True if the coordinates are located on the board.
- return x >= 0 and x <= 7 and y >= 0 and y <=7
- def getBoardWithValidMoves(board, letter):
- # Returns a new board with . marking the valid moves the given player can make.
- dupeBoard = getBoardCopy(board)
- for x, y in getValidMoves(dupeBoard, letter):
- dupeBoard[x][y] = '.'
- return dupeBoard
- def getValidMoves(board, letter):
- # Returns a list of [x,y] lists of valid moves for the given player on the given board.
- validMoves = []
- for x in range(8):
- for y in range(8):
- if isValidMove(board, letter, x, y):
- validMoves.append([x, y])
- return validMoves
- def getScoreOfBoard(board):
- # Determine the score. Returns a dictionary with keys 'X' and 'O'.
- xscore = 0
- oscore = 0
- for x in range(8):
- for y in range(8):
- if board[x][y] == 'X':
- xscore += 1
- if board[x][y] == 'O':
- oscore += 1
- return {'X':xscore, 'O':oscore}
- def enterPlayerLetter():
- # Let's the player type which letter they want to be.
- # Returns a list with the player's letter as the first item, and the computer's letter as the second.
- letter = ''
- while not (letter == 'X' or letter == 'O'):
- print 'Do you want to be X or O?'
- letter = raw_input().upper()
- # the first element in the tuple is the player's letter, the second is the computer's letter.
- if letter == 'X':
- return ['X', 'O']
- else:
- return ['O', 'X']
- def whoGoesFirst():
- # Randomly choose the player who goes first.
- if random.randint(0, 1) == 0:
- return 'computer'
- else:
- return 'player'
- def playAgain():
- # This function returns True if the player wants to play again, otherwise it returns False.
- print 'Do you want to play again? (yes or no)'
- return raw_input().lower().startswith('y')
- def makeMove(board, letter, startx, starty):
- # Place the letter on the board at startx, starty, and flip any of the opponent's pieces.
- # Returns False if this is an invalid move, returns True if it is valid.
- if not isValidMove(board, letter, startx, starty):
- return False
- board[startx][starty] = letter
- directions = [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]
- if letter == 'X':
- otherLetter = 'O'
- else:
- otherLetter = 'X'
- for d in directions:
- DX = d[0]
- DY = d[1]
- x, y = startx, starty
- x += DX
- y += DY
- if isOnBoard(x, y):
- if board[x][y] == otherLetter:
- # There is a piece belonging to the other player next to our piece.
- x += DX
- y += DY
- if not isOnBoard(x, y):
- continue
- while board[x][y] == otherLetter:
- x += DX
- y += DY
- if not isOnBoard(x, y): # break, then continue
- break
- if not isOnBoard(x, y):
- continue
- if board[x][y] == letter:
- # There are pieces to flip over. Go in the reverse direction.
- while True:
- x -= DX
- y -= DY
- if x == startx and y == starty:
- break
- board[x][y] = letter
- return True
- def getBoardCopy(board):
- # Make a duplicate of the board list and return the duplicate.
- dupeBoard = getNewBoard()
- for x in range(8):
- for y in range(8):
- dupeBoard[x][y] = board[x][y]
- return dupeBoard
- def isOnCorner(x, y):
- # Returns True if the position is in one of the four corners.
- return (x == 0 and y == 0) or (x == 7 and y == 0) or (x == 0 and y == 7) or (x == 7 and y == 7)
- def isSpaceFree(board, x, y):
- # Return true if the passed move is free on the passed board.
- return board[x][y] == ' '
- def getPlayerMove(board, playerLetter):
- # Let the player type in their move.
- # Returns the move as [x, y] (or returns the strings 'hints' or 'quits')
- digits1to8 = '1 2 3 4 5 6 7 8'.split()
- move = ''
- while True:
- print 'Enter your move, or type quit to end the game, or hints to turn off/on hints.'
- move = raw_input()
- if move.lower() == 'quit':
- return 'quit'
- if move.lower() == 'hints':
- return 'hints'
- if len(move) == 2 and move[0] in digits1to8 and move[1] in digits1to8:
- x = int(move[0]) - 1
- y = int(move[1]) - 1
- if not isValidMove(board, playerLetter, x, y):
- continue
- else:
- break
- else:
- print 'That is not a valid move. Type the x digit (1-8), then the y digit (1-8).'
- print 'For example, 81 will be the top-right corner.'
- return [x, y]
- def getComputerMove(board, computerLetter):
- # Given a board and the computer's letter, determine where to
- # move and return that move as a [x, y] list.
- if computerLetter == 'X':
- playerLetter = 'O'
- else:
- playerLetter = 'X'
- possibleMoves = getValidMoves(board, computerLetter)
- # create a list of moves that are in the corners, and
- # always go for a corner if available.
- cornerMoves = []
- for move in possibleMoves:
- if isOnCorner(move[0], move[1]):
- cornerMoves.append(move)
- if len(cornerMoves) > 0:
- return random.choice(getHighestScoringMoves(board, computerLetter, cornerMoves))
- # From the list of highest scoring moves, choose one at random.
- return random.choice(getHighestScoringMoves(board, computerLetter, possibleMoves))
- def getHighestScoringMoves(board, letter, moves):
- # Returns a list of the highest scoring moves from the list of moves.
- # For example, if out of the list of six moves, the highest scoring move gives
- # five more points, but there are three different moves that give five more points,
- # then return a list with those three moves.
- if len(moves) == 1:
- return moves
- currentScore = getScoreOfBoard(board)[letter]
- bestScore = -1
- allScores = []
- for i in range(len(moves)):
- dupeBoard = getBoardCopy(board)
- makeMove(dupeBoard, letter, moves[i][0], moves[i][1])
- scoreIncrease = getScoreOfBoard(dupeBoard)[letter] - currentScore
- allScores.append(scoreIncrease)
- if scoreIncrease > bestScore:
- bestScore = scoreIncrease
- topMoves = []
- for i in range(len(allScores)):
- if allScores[i] == bestScore:
- topMoves.append(moves[i])
- return topMoves
- def isBoardDone(board, letter):
- # Return True if the player with the given letter can make a move on the board. Otherwise return False.
- if getValidMoves(board, letter) == []:
- return True
- for x in range(8):
- for y in range(8):
- if board[x][y] == ' ':
- return False
- return True
- def showPoints(playerLetter, computerLetter):
- # Prints out the current score.
- scores = getScoreOfBoard(theBoard)
- print 'You have %s points. The computer has %s points.' % (scores[playerLetter], scores[computerLetter])
- print 'Welcome to Othello!'
- while True:
- # Reset the board and game.
- theBoard = getNewBoard()
- resetBoard(theBoard)
- playerLetter, computerLetter = enterPlayerLetter()
- showHints = False
- turn = whoGoesFirst()
- print 'The ' + turn + ' will go first.'
- while True
- if turn == 'player':
- # Player's turn.
- if showHints:
- validMovesBoard = getBoardWithValidMoves(theBoard, playerLetter)
- drawBoard(validMovesBoard)
- else:
- drawBoard(theBoard)
- showPoints(playerLetter, computerLetter)
- move = getPlayerMove(theBoard, playerLetter)
- if move == 'quit':
- print 'Thanks for playing!'
- sys.exit() # terminate the program
- elif move == 'hints':
- showHints = not showHints
- continue
- else:
- x, y = move
- makeMove(theBoard, playerLetter, x, y)
- if isBoardDone(theBoard, computerLetter): # computerLetter, not playerLetter
- break
- else:
- turn = 'computer'
- else:
- # Computer's turn.
- x, y = getComputerMove(theBoard, computerLetter)
- drawBoard(theBoard)
- showPoints(playerLetter, computerLetter)
- raw_input('Press Enter to see the computer\'s move.')
- makeMove(theBoard, computerLetter, x, y)
- if isBoardDone(theBoard, playerLetter): # playerLetter, not computerLetter
- break
- else:
- turn = 'player'
- # Display the final score.
- scores = getScoreOfBoard(theBoard)
- print 'X scored %s points. O scored %s points.' % (scores['X'], scores['O'])
- if scores[playerLetter] > scores[computerLetter]:
- print 'You beat the computer by %s points! Congratulations!' % (scores[playerLetter] - scores[computerLetter])
- elif scores[playerLetter] < scores[computerLetter]:
- print 'You lost. The computer beat you by %s points.' % (scores[computerLetter] - scores[playerLetter])
- else:
- print 'The game was a tie!'
- if not playAgain():
- break
Things Covered In This Chapter: