#!/usr/bin/env python3 # todo list: # - fix all the todos in the document # - move 'position' data to its own class with dynamic conversion between class Board: unplayed = 0 cross = 1 nought = 2 mappings = { 'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6, 'h': 7, 'i': 8, } reverse = {i[1]:i[0] for i in mappings.items()} def __init__(self): self.board = [[[ [Board.unplayed for i in range(3)] for j in range(3) ] for k in range(3) ] for l in range(3) ] self.movestring = [] self.turn = Board.cross def push(self, movestring): pos = self.translateGlobal(movestring) self.movestring.append(movestring) self.board[pos[0]][pos[1]][pos[2]][pos[3]] = self.turn if self.turn == Board.cross: self.turn = Board.nought else: self.turn = Board.cross def subboardWin(self, x, y): b = self.board for i in range(3): if b[x][y][i][0] == b[x][y][i][1] == b[x][y][i][2]: if b[x][y][i][0] != Board.unplayed: return b[x][y][i][0] if b[x][y][0][i] == b[x][y][1][i] == b[x][y][2][i]: if b[x][y][0][i] != Board.unplayed: return b[x][y][0][i] if b[x][y][0][0] == b[x][y][1][1] == b[x][y][2][2]: if b[x][y][0][0] != Board.unplayed: return b[x][y][0][0] if b[x][y][2][0] == b[x][y][1][1] == b[x][y][0][2]: if b[x][y][2][0] != Board.unplayed: return b[x][y][2][0] return Board.unplayed def allEmpty(self): buf = [] for i in range(3): for j in range(3): for k in range(3): for l in range(3): if self.board[i][j][k][l] == Board.unplayed: buf.append(self.translateLocal((i, j, k, l))) def possibleMoves(self): # get last move try: lastpos = self.movestring[-1] except IndexError: # if no moves have been made, return the whole board ret = [] for i in range(9): for j in range(9): buf = [] buf.append(Board.reverse[i]) buf.append(str(j)) ret.append(''.join(buf)) return ret # get global tuple lastpos = self.translateGlobal(lastpos) # check for a subboard win condition in the indicated # position; if so, just return self.allEmpty() # WARNING: this will select positions in already won boards that are empty # needs to be fixed if self.subboardWin(lastpos[0], lastpos[1]): return self.allEmpty() # get all squares in metasquare # this doesn't work because it's getting the *current* square # and not the square the move is referring to # this needs to be fixed square = self.returnInSquare(self.movestring[-1]) buf = [] for i in square: # if empty, we can move if self.isEmpty(i): buf.append(i) return buf def translateGlobal(self, movestring): globcoords = ( Board.mappings[movestring[0]], int(movestring[1]) - 1, ) subboard = ( int(globcoords[0] / 3), int(globcoords[1] / 3), ) localpos = ( globcoords[0] % 3, globcoords[1] % 3, ) return (subboard[0], subboard[1], localpos[0], localpos[1]) def translateLocal(self, globaltuple): globcoords = ( globaltuple[0] * 3 + globaltuple[2], globaltuple[1] * 3 + globaltuple[3] + 1, ) movestring = [] movestring.append(Board.reverse[globcoords[0]]) movestring.append(str(globcoords[1])) return ''.join(movestring) def returnInSquare(self, movestring, convert=True): orig = self.translateGlobal(movestring) buf = [] for i in range(3): for j in range(3): buf.append((orig[0], orig[1], i, j)) if convert: rbuf = [] for i in buf: rbuf.append(Board.translateLocal(self, i)) return rbuf return buf def isEmpty(self, movestring): orig = self.translateGlobal(movestring) item = self.board[orig[0]][orig[1]][orig[2]][orig[3]] if item == Board.unplayed: return True return False def isFull(self, movestring): return not self.isEmpty(movestring) def getInnerBoard(self, x, y): pass