Micro Project: Reversi

This is the 3rd game in series game I intend to make.
For those who don’t know how to play Reversi, go here. You have to know how to play the game in order to code it, otherwise you will not understand what I’m gonna talk.
Basically, this is a take-turn game. In this game you play against a simple AI. Although it’s simple, but it will beat you almost every game you play. I’ve tried very hard, but can only make a tie game with it (I rarely win it).
Ok, after you know how to play, I’ll show you the code and then explain every single line.

Here’s the code: (Python 2.7)

import sys
import random

class Reversi:
    def __init__(self):
        self.player_tile = ''
        self.computer_tile = ''
        self.board = self.get_new_board()
        self.game_state = 'CONTINUE'
        self.reset_board(self.board)

    def draw_board(self, board):
        HLINE = '   ' + '+---'*8+'+'

        print '     ' + '   '.join(map(str, range(1, 9)))
        print HLINE
        for i in xrange(8):
            print str(i+1) + ' ',
            for j in xrange(8):
                print '| %s' % (board[i][j]),
            print '|'
            print HLINE

    def reset_board(self, board):
        for i in xrange(8):
            for j in xrange(8):
                board[i][j] = ' '
        board[3][3] = 'X'
        board[4][3] = 'O'
        board[3][4] = 'O'
        board[4][4] = 'X'

    def get_new_board(self):
        board = []
        for i in xrange(8):
            board.append([' ']*8)
        return board

    def is_valid_move(self, board, char, xstart, ystart):
        if not self.is_on_board(xstart, ystart) or board[xstart][ystart] != ' ':
            return False
        other_tile = 'X' if char == 'O' else 'O'
        tiles_to_flip = []

        for xdirection, ydirection in [[0,1], [1,1], [1,0], [1,-1], [0,-1], [-1,-1], [-1,0], [-1,1]]:
            x,y = xstart, ystart
            x += xdirection
            y += ydirection

            if self.is_on_board(x,y) and board[x][y] == other_tile:
                x += xdirection
                y += ydirection
                if not self.is_on_board(x,y):
                    continue

                while board[x][y] == other_tile:
                    x += xdirection
                    y += ydirection
                    if not self.is_on_board(x, y):
                        break

                if not self.is_on_board(x, y):
                    continue

                if board[x][y] == char:
                    while True:
                        x -= xdirection
                        y -= ydirection
                        if x == xstart and y == ystart:
                            break
                        tiles_to_flip.append([x,y])
        board[xstart][ystart] = ' '
        if len(tiles_to_flip) == 0:
            return False
        return tiles_to_flip

    def is_on_board(self, x, y):
        return x >= 0 and x <= 7 and y >= 0 and y <= 7     def get_valid_moves(self, board, tile):         valid_moves = []         for i in xrange(8):             for j in xrange(8):                 if self.is_valid_move(board,tile,i,j) != False:                     valid_moves.append([i,j])         return valid_moves     def get_board_with_validmoves(self, board, tile):         copy_board = self.get_board_copy(board)         for x,y in self.get_valid_moves(copy_board, tile):             copy_board[x][y] = '.'         return copy_board     def get_score(self, board):         x_score, o_score = 0, 0         for x in xrange(8):             for y in range(8):                 if board[x][y] == 'X':                     x_score += 1                 if board[x][y] == 'O':                     o_score += 1         return {'X':x_score, 'O':o_score}     def get_board_copy(self, board):         copy_board = self.get_new_board()         for x in xrange(8):             for y in xrange(8):                 copy_board[x][y] = board[x][y]         return copy_board     def player_choose_tile(self):         tile = ''         while not (tile == 'X' or tile == 'O'):             print 'Do you want to play \'X\' or \'O\'?'             tile = raw_input().strip()         self.player_tile = 'X' if tile == 'X' else 'O'         self.computer_tile = 'X' if self.player_tile == 'O' else 'O'     def who_goes_first(self):         if random.randint(0, 1) == 1:             return 'computer'         return 'player'     def play_again(self):         print 'Do you want to play again? (y,n)'         return raw_input().strip().startswith('y')     def make_move(self, board, tile, xstart, ystart):         tiles_to_flip = self.is_valid_move(board,tile,xstart,ystart)         if tiles_to_flip == False:             return False         board[xstart][ystart] = tile         for x,y in tiles_to_flip:             board[x][y] = tile         return True     def is_corner(self, x, y):         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 get_player_move(self, board, player_tile):         DIGITS = ' '.join(map(str, range(1, 9))).split()         while True:             print 'Enter your move, or type quit to end game, or hints to get hints'             move = raw_input().strip().lower()             if move == 'quit':                 return 'quit'             if move == 'hints':                 return 'hints'             if len(move) == 2 and move[0] in DIGITS and move[1] in DIGITS:                 x = int(move[0]) - 1                 y = int(move[1]) - 1                 if self.is_valid_move(board, player_tile, x, y):                     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 bottom left corner'         return [x, y]     def get_computer_move(self, board, computer_tile):         possible_moves = self.get_valid_moves(board, computer_tile)         random.shuffle(possible_moves)         for x,y in possible_moves:             if self.is_corner(x,y):                 return [x, y]         best_score = -1         best_move = []         for x, y in possible_moves:             copy_board = self.get_board_copy(board)             self.make_move(copy_board, computer_tile, x, y)             score = self.get_score(copy_board)[computer_tile]             if score > best_score:
                best_move = [x,y]
                best_score = score
        return best_move

    def show_points(self, player_tile, computer_tile, board):
        score = self.get_score(board)
        print 'You have %s points. The compute has %s points' % (score[player_tile], score[computer_tile])

    def reset(self):
        self.board = self.get_new_board()
        self.reset_board(self.board)
        self.player_tile = ''
        self.computer_tile = ''

    def start_game(self):
        while self.game_state == 'CONTINUE':
            self.reset()
            self.player_choose_tile()
            turn = self.who_goes_first()
            self.draw_board(self.board)
            while True:
                if turn == 'player':
                    move = self.get_player_move(self.board, self.player_tile)
                    if move == 'quit':
                        print 'Thanks for playing'
                        sys.exit()
                    elif move == 'hints':
                        print 'Hints:'
                        self.draw_board(self.get_board_with_validmoves(self.board, self.player_tile))
                        print
                        continue
                    else:
                        self.make_move(self.board, self.player_tile, move[0], move[1])
                        self.draw_board(self.board)
                        self.show_points(self.player_tile, self.computer_tile, self.board)
                    if self.get_valid_moves(self.board, self.computer_tile) == []:
                        break
                    else:
                        turn = 'computer'
                else:
                    raw_input('Press enter to see computer move')
                    x,y = self.get_computer_move(self.board, self.computer_tile)
                    print (x+1), (y+1)
                    self.make_move(self.board, self.computer_tile, x, y)
                    self.draw_board(self.board)
                    self.show_points(self.player_tile, self.computer_tile, self.board)
                    if self.get_valid_moves(self.board, self.player_tile) == []:
                        break
                    else:
                        turn = 'player'

            self.draw_board(self.board)
            scores = self.get_score(self.board)
            print 'X scored %s, O scored %s' % (scores['X'], scores['O'])
            if scores[self.player_tile] > scores[self.computer_tile]:
                print 'You beat the computer by %s points. Congratulations!' %  (scores[self.player_tile]-scores[self.computer_tile])
                self.game_state = 'WIN'
            elif scores[self.player_tile] < scores[self.computer_tile]:
                print 'You lost. Computer beat you by %s points' % (scores[self.computer_tile] - scores[self.player_tile])
                self.game_state = 'GAME_OVER'
            else:
                print 'I don\'t know how but this was a tie'
            if self.play_again():
                self.game_state == 'CONTINUE'

if __name__ == '__main__':
    game = Reversi()
    game.start_game()

Again, I made use of using OOP (Object-Oriented Programming), so the entire game is inside a class named Reversi. This way you can reuse the code later.

  • Init

Initialize player_tile, computer_tile, board and game_state

  • draw_board:

Simply draw the 8×8 board in console. The board will look like this:
screen-shot-2016-10-08-at-10-12-59-am

  • reset_board:

Reset the board so that the game can start. The board at start looks like this:

screen-shot-2016-10-08-at-10-17-31-am

  • get_new_board:

Create a 8×8 board, with all cells hold the value ‘ ‘

  • is_valid_move:

Check if a move is valid.
The move x,y is valid if x,y is on the board, and board[x][y] == ‘ ‘, and there must be at least one cell will be flipped.
In order to check if there are any cells will be flipped, we check all 8 directions, starting from the move x,y.  In the for loop, first we move to the adjacent cell according to the direction. If that cell is not on the board, we continue to other direction. Otherwise, we keep moving until we hit the wall (x,y out side the board) or we meet the cell having the same tile. If we hit the wall, means nothing will be flipped, but if we meet the cell having same value, everything between the starting point and this point will be flipped. We do that by going backward, append the coordinates of those cells into a list. After traversing all 8 directions, we reset the move (just imagine we’ve just made a temporary move in order to check if we make that move, there are some cells can be flipped), and return the coordinates of all those cells that can be flipped. But before we return those coordinates, we check if there is no cell can be flipped, means that is not a valid move.
Simple, hah?

  • is_on_board:

This method is sort and simple, check if x,y is on the board. By checking if they are in the bound.

  • get_valid_moves:

We make use of is_valid_move method above, traverse through all cells on the board, check if it is valid, then we append that coordinates into a list. Finally, return the list.

  • get_board_with_validmoves

This method is used to give player some hints if they’re stuck. By marking all valid moves with ‘.’, player can easily make a new move.

  • get_score:

Go through all cells on the board, count how many X there are, how many O there are. And return the result as a dictionary.

  • get_board_copy:

The name says it all. This method is used to help computer AI make a move, you’ll see soon.

  • who_goes_first:

Randomly pick computer or player.

  • play_again:

Ahh, no need to explain. Just ask if player want to play again.

  • make_move:

If xstart,ystart is an invalid move, return False. Otherwise mark board[xstart][ystart] = tile, and flip all cells in the tiles_to_flip list, and then return True.

  • is_corner:

Check if a move is on the corner of the board. This is important, if you know how to play the game well, you can figure it out while we have to do this. We do this simply because we try to help our cell avoid being flipped. That’s all.

  • get_player_move:

Just check until player enter a valid move.

  • get_computer_move:

This is the brain of our AI. First we get all possible moves, then shuffle it, and check if one of the move is on the corner. If none of them are on the corner, we choose the best move of all possible moves. The best move is the move with the give the computer the best score after playing that move. You got the idea.

I think you can understand the rest of the code now. One more thing, you can easily create a real game with graphics by using this code. Just use tkinter to make an 8×8 buttons grid. You know how to do it, right?

 

Advertisements