Skip to main content
1 of 6
jlim
  • 103
  • 1
  • 8

Python: 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: Board for the Reversi board, Location to hold an x,y coordinate, and 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 a bitfield 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.offset = self.convertToOffset(x,y)
        
    def convertToOffset(self,x,y):
        self.invalid = (x < 0 or x > 7 or y < 0 or y > 7)
        return x + y * 8

    def translate(self,dx,dy):
        self.invalid = ((self.offset < 8 and dy == -1) or 
            (self.offset > 56 and dy == 1) or
            (self.offset % 8 == 0 and dx == -1) or
            (self.offset % 8 == 7 and dx == 1))
        self.offset = self.offset + dx + dy * 8

    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_bitstclass ReversiTest(unittest.TestCase):ring(board,'B'),
            self.make_bitstring(board,'W'))
    
    def show(self):
        print self.discs    
        
    def at(self, location, player):
        return not location.invalid and self.discs[int(player=='W')][location.offset]

    def empty(self, location):
        return location.invalid 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
        next_location = Location()
        for dir in [(1,0),(1,-1),(0,-1),(-1,-1),(-1,0),(-1,1),(0,1),(1,1)]:
            next_location.offset = self.location.offset
            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