Skip to main content
6 of 6
added 19 characters in body; edited tags; edited title
Jamal
  • 34.9k
  • 13
  • 133
  • 237

Reversi move class

I redid a Python Reversi move preview routine that is online. I could use comments about using Python better and/or better OOP design since the original did not use classes. There are three main classes:

  1. Board for the Reversi board
  2. Location to hold an x,y coordinate (0-7,0-7)
  3. Move to store a location to put a disc on a board and how many discs would be affected by that move

I separated out Move because I would like to keep a sequence of Moves in memory to play forward or reverse. Board knows about where the discs are in two bitfields, one for each player, and knows how to turn over discs and return if a disc is at a location.

import unittest
import copy
from bitarray import bitarray

INITIAL_BOARD = """
|_|_|_|_|_|_|_|_|
|_|_|_|_|_|_|_|_|
|_|_|_|_|_|_|_|_|
|_|_|_|B|W|_|_|_|
|_|_|_|W|B|_|_|_|
|_|_|_|_|_|_|_|_|
|_|_|_|_|_|_|_|_|
|_|_|_|_|_|_|_|_|
"""

def other_player(playerId):
    return 'B' if playerId == 'W' else 'W'

class Location:
    def __init__(self,x=0,y=0):
        self.valid = True
        self.x = x
        self.y = y
        self.offset = self.convertToOffset(self.x,self.y)
        
    def convertToOffset(self,x,y):
        if self.valid:
            self.valid = (x >= 0 and x <= 7 and y >= 0 and y <= 7)
        return x + y * 8

    def translate(self,dx,dy):
        self.x += dx
        self.y += dy
        self.offset = self.convertToOffset(self.x,self.y)

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        else:
            return False

    def __ne__(self, other):
        return not self.__eq__(other)
    
class Board:
    def __init__(self, board=INITIAL_BOARD):
        self.discs = self.parse_board(board)
        
    def make_bitstring(self,board,color_current):
        other_color = other_player(color_current)
        def parse_to_bit(square):
            if square == color_current:
                return '1'
            elif square == '_' or square == other_color:
                return '0'
            return ''
        return bitarray(''.join(map(parse_to_bit,board)))

    def parse_board(self,board):
        return (self.make_bitstring(board,'B'),
            self.make_bitstring(board,'W'))
    
    def show(self):
        print self.discs    
        
    def at(self, location, player):
        return location.valid and self.discs[int(player=='W')][location.offset]

    def empty(self, location):
        return not location.valid or (not self.at (location,'B') and not self.at (location,'W'))

    def turnoverDisc(self, location):
        offset = location.offset
        self.discs[0][offset], self.discs[1][offset] = self.discs[1][offset], self.discs[0][offset]

    def placeDisc(self, location, player):
        self.discs[int(player=='W')][location.offset] = 1

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        else:
            return False

    def __ne__(self, other):
        return not self.__eq__(other)

class Move:
    def __init__(self, board, location, color_player):    
        self.location = location
        self.player = color_player
        self.board = board
        self.discDelta = self.makeDiscDelta()
        
    def makeDiscDelta(self):
        result = []
        other = other_player(self.player)
        if not self.board.empty(self.location):
            return result
        for dir in [(1,0),(1,-1),(0,-1),(-1,-1),(-1,0),(-1,1),(0,1),(1,1)]:
            next_location = copy.copy(self.location)
            next_location.translate(dir[0],dir[1])
            dirDelta = []
            while self.board.at(next_location,other):
                dirDelta.append(copy.copy(next_location))
                next_location.translate(dir[0],dir[1])
            if self.board.at(next_location,self.player):
                result.extend(dirDelta)
        return result
        
    def makeBoard(self):
        result = copy.deepcopy(self.board)
        for location in self.discDelta:
            result.turnoverDisc(location)
        result.placeDisc(self.location,self.player)
        return result

class ReversiTest(unittest.TestCase):

    def testMovePreviewWhite(self):
        board = Board()
        location = Location(4,5)
        move = Move(board,location,'W')        
        self.assertEquals([Location(4,4)], move.discDelta)

    def testMovePreviewBlack(self):
        board = Board()
        location = Location(3,2)
        move = Move(board,location,'W')        
        self.assertEquals([Location(3,3)], move.discDelta)
    
    def testMove(self):
        board = Board()
        expected_board = """
            |_|_|_|_|_|_|_|_|
            |_|_|_|_|_|_|_|_|
            |_|_|_|_|_|_|_|_|
            |_|_|_|B|W|_|_|_|
            |_|_|_|W|W|_|_|_|
            |_|_|_|_|W|_|_|_|
            |_|_|_|_|_|_|_|_|
            |_|_|_|_|_|_|_|_|
            """
        expected = Board(expected_board)
        location = Location(4,5)
        move = Move(board,location,'W') 
        result = move.makeBoard()
        self.assertEquals(expected, result)
        self.assertEquals(board, Board())
        self.assertEquals(location, Location(4,5))
jlim
  • 103
  • 1
  • 8