import curses import random class ooPuzzle: """Encapsulates a oo puzzle state. No rendering information is stored or interpreted here. Attributes. X,Y : horizontal and vertical size of the puzzle pieces : dictionary mapping (x,y) to range(6) orients : dictionary mapping (x,y) to range(4) """ def __init__(self, X, Y, game_id=None, inverted=False, toroidal=False): """Create a new ooPuzzle instance. Arguments. X,Y : horizontal and vertical size of the puzzle game_id : passed to ooPuzzle.set_pieces_from_game_id inverted : bool for swapping to "dark mode" toroidal : bool for looping around the end of the puzzle NotImplemented: inverted, toroidal """ self.X, self.Y = X, Y self.inverted = inverted self.toroidal = toroidal self.pieces = {} self.orients = {} self.set_pieces_from_game_id(game_id) EDGES_TO_PIECE_ORIENT = { (0, 0, 0, 0) : (0, 0), (1, 0, 0, 0) : (1, 0), (0, 1, 0, 0) : (1, 1), (0, 0, 1, 0) : (1, 2), (0, 0, 0, 1) : (1, 3), (1, 1, 0, 0) : (2, 0), (0, 1, 1, 0) : (2, 1), (0, 0, 1, 1) : (2, 2), (1, 0, 0, 1) : (2, 3), (1, 0, 1, 0) : (3, 0), (0, 1, 0, 1) : (3, 1), (0, 1, 1, 1) : (4, 0), (1, 0, 1, 1) : (4, 1), (1, 1, 0, 1) : (4, 2), (1, 1, 1, 0) : (4, 3), (1, 1, 1, 1) : (5, 0) } PIECE_ORIENT_TO_EDGES = { (0, 0) : (0, 0, 0, 0), (0, 1) : (0, 0, 0, 0), (0, 2) : (0, 0, 0, 0), (0, 3) : (0, 0, 0, 0), (1, 0) : (1, 0, 0, 0), (1, 1) : (0, 1, 0, 0), (1, 2) : (0, 0, 1, 0), (1, 3) : (0, 0, 0, 1), (2, 0) : (1, 1, 0, 0), (2, 1) : (0, 1, 1, 0), (2, 2) : (0, 0, 1, 1), (2, 3) : (1, 0, 0, 1), (3, 0) : (1, 0, 1, 0), (3, 1) : (0, 1, 0, 1), (3, 2) : (1, 0, 1, 0), (3, 3) : (0, 1, 0, 1), (4, 0) : (1, 1, 1, 1), (4, 1) : (1, 1, 1, 1), (4, 2) : (1, 1, 1, 1), (4, 3) : (1, 1, 1, 1) } def set_pieces_from_edges(self, horiz_edges, vert_edges): """Convert edge dictionaries into a puzzle state. Adjacent edges in a solution must be either both filled or both unfilled, and so a bit of data is assigned to each pair. vert_edges is the dictionary for vertical pairs key: (x, row) x: the usual x-coordinate of the piece row: the y-coordinate or y+1, depending on whether you want the pair above or below the piece : row == 0 and row == self.Y are border edges so it's good to think of row as 1-indexed horiz_edges is the dictionary for horizontal pairs key: (col, y) col: the x-coord or x+1, same as row but left or right y: the usual y-coord """ for x in range(self.X): for y in range(self.Y): left = horiz_edges[x , y] right = horiz_edges[x+1, y] up = vert_edges[x, y ] down = vert_edges[x, y+1] piece, orient = \ self.EDGES_TO_PIECE_ORIENT[left, up, right, down] self.pieces [x, y] = piece self.orients[x, y] = orient #TODO: properly account for the borders if toroidal def set_pieces_from_game_id(self, game_id=None): """Convert game_id value into a solution puzzle state. game_id is an integer if toroidal: in range(2**( 2*X*Y )) if not toroidal: in range(2**( (X-1)*Y + X*(Y-1) )) if None, then a random game_id is generated """ # compute: # n_horiz, the number of horizontal edge pairs # n_vert, the number of vertical edge pairs shift = -int(not self.toroidal) n_col = self.X + shift n_row = self.Y + shift n_horiz = n_col * self.Y n_vert = self.X * n_row # generate and record game_id if game_id == None: game_id = random.randrange(0, 2**(n_horiz + n_vert)) self.game_id = game_id # prepare to record edges for self.set_pieces_from_edges vert_edges = {} horiz_edges = {} # set the border edges # if toroidal, these will be overwritten for x in range(self.X): vert_edges[x, 0] = 0 vert_edges[x, self.Y] = 0 for y in range(self.Y): horiz_edges[ 0, y] = 0 horiz_edges[self.X, y] = 0 # set the edges determined by game_id for i in range(n_vert): row, x = divmod(i, self.X) game_id, bit = divmod(game_id, 2) vert_edges[x, row+1] = bit for i in range(n_horiz): col, y = divmod(i, self.Y) game_id, bit = divmod(game_id, 2) horiz_edges[col+1, y] = bit # turn edges into pieces self.set_pieces_from_edges(horiz_edges, vert_edges) def random_state(self): """Randomly rotate the current pieces.""" for x in range(self.X): for y in range(self.Y): self.pieces[x, y] = random.randint(4) def is_solved(self): """Checks whether the puzzle is in a solved state.""" pass class ooPlay: """Encapsulates an oo game instance. Renders and interacts with an ooPuzzle instance. """ def __init__(self, scr): """Create a new ooPlay instance. Arguments. scr : curses screen object used for display """ self.scr = scr self.Y, self.X = self.scr.getmaxyx() self.puzzle = ooPuzzle(self.X, self.Y-2) self.puzzle.random_state() self.scr.clear() # draw the help area and board state border_line = self.X * "═" self.scr.addstr(self.Y - 2, 0, border_line) self.display() PIECE_ORIENT_TO_STRING = \ [" ", "╸╵╺╷", "┙┕┍┑", "━│━│", "┝┯┥┷", "┿┿┿┿"] def display(self): """Display the state of the board, refresh screen.""" pass def keyloop(self): """Wait for and parse keypress.""" pass def main(): curses.wrapper(ooPlay)