# Modified from original version by Matt Dunlap to conform to standards # #To use this file simply put it in the directory with two python modules and #run it with the names of the modules (note the lack of ".py" extensions) #as arguments: # # python OthelloModerator.py ai1 ai2 [secondsPerTurn=120] __author__="M@" __date__ ="$Nov 4, 2009 6:11:36 PM$" import sys import KThread def reverse(list): """I really shouldn't have to write this, but the existing reverse doesn't return the array after reversing it, so I'm left with no choice""" list.reverse() return list class OthelloBoard: def __init__(self): self.board = [0 for i in range(64)] self.board[27] = 1 self.board[28] = 2 self.board[35] = 2 self.board[36] = 1 self.history =[] def __repr__(self): rows = [] for i in range(8): rows.append("".join([" " if square == 0 else str(square) for square in self.board[i*8:i*8+8]])) return "\r\n".join(rows) def getQueensMoves(self, index): """get a list of all the chips in each direction starting at index""" row = index / 8 col = index % 8 directions = {} directions['n'] = reverse([self.board[i] for i in range(64) if i < index and i%8 == col]) directions['s'] = [self.board[i] for i in range(64) if i > index and i%8 == col] directions['e'] = [self.board[i] for i in range(64) if i > index and i/8 == row] directions['w'] = reverse([self.board[i] for i in range(64) if i < index and i/8 == row]) directions['ne'] = reverse([self.board[i] for i in range(64) if i < index and row-i/8 == i%8-col]) directions['sw'] = [self.board[i] for i in range(64) if i > index and i/8-row == col-i%8] directions['nw'] = reverse([self.board[i] for i in range(64) if i < index and row-i/8 == col-i%8]) directions['se'] = [self.board[i] for i in range(64) if i > index and i/8-row == i%8-col] directions['deltas'] = { 'n':-8, 'ne':-7, 'e':1, 'se':9, 's':8, 'sw':7, 'w':-1, 'nw':-9 } return directions def isLegalMove(self, index, player): if self.board[index] != 0 or player!=1 and player!=2: return False for direction in self.getQueensMoves(index).itervalues(): if self.capturesInArray(player, direction): return True return False def capturesInArray(self, player, list): """returns the number of pieces captured in the list""" otherPlayer = 2 if player == 1 else 1 totalCaptures = 0 for square in list: if square == otherPlayer: totalCaptures += 1 elif square == 0: return 0 elif square == player: return totalCaptures return 0 def captureQueensMoves(self, player, index, queensMoves): """take care of capturing for player playing at index""" for name, direction in queensMoves.iteritems(): if(self.capturesInArray(player, direction)): delta = queensMoves['deltas'][name] self.captureAlongDeltaPath(player, index+delta, delta) def captureAlongDeltaPath(self, player, index, delta): """take care of capturing in a single direction. index is where to capture and delta is the direction of the capture (ie away from the original placement). the values from delta should only come from the deltas collection returned by getQueensMoves""" if index in range(64) and self.board[index] != player and self.board[index] != 0: self.board[index] = player self.captureAlongDeltaPath(player, index+delta, delta) def makeMoveIfLegal(self, index, player): if self.isLegalMove(index, player): self.board[index] = player self.history.append((index, player)) queensMoves = self.getQueensMoves(index) self.captureQueensMoves(player, index, queensMoves) return True return False def getBoardState(self): return self.board[:] def setBoardState(self, state): self.board = state[:] def getHistory(self): return self.history[:] def isComplete(self): return not (self.getLegalMovesForPlayer(1) or self.getLegalMovesForPlayer(2)) def getLegalMovesForPlayer(self, player): return [i for i in range(64) if self.isLegalMove(i, player)] def getWinner(self): ones = self.board.count(1) twos = self.board.count(2) if ones == twos: return "nobody, it's a tie!" return "player 1!" if ones > twos else "player 2!" class OthelloModerator: def __init__(self, p1ModName, p2ModName, seconds): self.board = OthelloBoard() self.secondsPerTurn = seconds self.currentPlayer = 1 m1 = __import__(p1ModName) self.playerOne = m1.OthelloPlayer() self.playerOneCall = self.playerOne.makeMove self.playerOneObj = {} self.playerOneObj["playerNumber"] = 1 self.playerOneObj["turnLimit"] = seconds m2 = __import__(p2ModName) self.playerTwo = m2.OthelloPlayer() self.playerTwoCall = self.playerTwo.makeMove self.playerTwoObj = {} self.playerTwoObj["playerNumber"] = 2 self.playerTwoObj["turnLimit"] = seconds def play(self): while not self.board.isComplete(): call = self.playerOneCall if self.currentPlayer == 1 else self.playerTwoCall obj = self.playerOneObj if self.currentPlayer == 1 else self.playerTwoObj if self.board.getLegalMovesForPlayer(self.currentPlayer): self.takeTurn(call, obj) self.currentPlayer = 2 if self.currentPlayer == 1 else 1 print self.board print " " print "And the winner is........ " + self.board.getWinner() def takeTurn(self, call, args): result = {} #have to use a dictionary here because assigning to an int in a closure doesn't work (python thinks you're declaring a new int) def storeResult(): result["value"] = call(self.board.getBoardState(),args["playerNumber"]) args["boardState"] = self.board.getBoardState() thread = KThread.KThread(None, storeResult) thread.start() thread.join(self.secondsPerTurn) if thread.isAlive(): thread.kill() print "thread killed" elif "value" in result and result["value"] in range(64): self.board.makeMoveIfLegal(result["value"], self.currentPlayer) if __name__ == "__main__": seconds = 120 if len(sys.argv) >= 4: seconds = int(sys.argv[3]) if len(sys.argv) >= 3: for i in range(100): om = OthelloModerator(sys.argv[1], sys.argv[2], seconds) om.play() else: print " python OthelloModerator.py ai1 ai2 [secondsPerTurn=120]"