diff options
| -rw-r--r-- | oo.py | 195 | 
1 files changed, 123 insertions, 72 deletions
| @@ -6,6 +6,31 @@ class ooPuzzle:      No rendering information is stored or interpreted here. +    The following are all the get and set methods, +        categorized with their inverses: + +        DATA CALLS +        get_piece                       set_piece +        get_orient                      set_orient +        get_piece_orient                set_piece_orient + +        HELPER METHODS +        get_adj_pos +        get_edge_pair + +        INITIALIZATION +        set_pieces_from_game_id +        set_pieces_from_edges + +    The following are all the user interface methods. +        For more description, see their documentation. + +        rotate_cw       : rotates a piece clockwise +        random_orients  : randomly orients all pieces +        check_edge_pair : verifies an edge pair matches properly +        check_piece     : verifies edges on a piece connect to other pieces +        is_solved       : verifies all edges on all pieces are connected +      All attributes are intended to be accessed through methods.      If direct access is necessary, here are their explanations: @@ -32,18 +57,23 @@ class ooPuzzle:          Y is an integer for the range of y-positions -        game_id is an integer used by set_pieces_from_game_id -            It uniquely corresponds to the solution. -          toroidal is a bool indicating whether              the border of the puzzle loops back to the opposite side -    (the following attributes are variable during normal use) +        game_id is an integer +            It uniquely corresponds to the solution in edges. +            if     toroidal: in range(2**( 2*X*Y )) +            if not toroidal: in range(2**( (X-1)*Y + X*(Y-1) ))          pieces is a dictionary mapping:              each position of form (x in range(self.X),                                     y in range(self.Y))              to a piece id in range(6) +             +        orients is a dictionary mapping: +            each position of form (x in range(self.X), +                                   y in range(self.Y)) +            to a direction index in range(4)          inverted_pieces is a closely related mapping to pieces,          where all edges swap filled state @@ -51,11 +81,6 @@ class ooPuzzle:          not_inverted_pieces is similar, but only swaps border edges          NOT IMPLEMENTED -             -        orients is a dictionary mapping: -            each position of form (x in range(self.X), -                                   y in range(self.Y)) -            to a direction index in range(4)      """      def __init__(self, X, Y, game_id=None, toroidal=False): @@ -63,14 +88,23 @@ class ooPuzzle:          Arguments.                   X,Y : horizontal and vertical size of the puzzle -             game_id : passed to ooPuzzle.set_pieces_from_game_id +             game_id : int corresponding to solution +                     : if None, a random game_id is generated              toroidal : bool for looping around the end of the puzzle          """          self.X, self.Y = X, Y +        self.game_id = game_id          self.toroidal = toroidal +          self.pieces  = {}          self.orients = {} -        self.set_pieces_from_game_id(game_id) +        self.set_data_from_game_id(random_game_id = (game_id == None)) + +    #### +    # +    # The following are constants for converting between various data sets. +    # +    ####      # Changing conceptual directions into indices.      DIRECTIONS = {'LEFT' : 0, @@ -141,8 +175,8 @@ class ooPuzzle:      ####      # -    # The following methods are low-level, -    # and require position inputs to be forced in range. +    # The following get and set methods are low-level data calls, +    # and force the input positions into the appropriate range.      #      #### @@ -161,6 +195,30 @@ class ooPuzzle:          y %= self.Y          return self.pieces[x, y], self.orients[x, y] +    def set_piece(self, x, y, value): +        x %= self.X +        y %= self.Y +        self.pieces[x, y] = value + +    def set_orient(self, x, y, value): +        x %= self.X +        y %= self.Y +        self.orients[x, y] = value + +    def set_piece_orient(self, x, y, value): +        x %= self.X +        y %= self.Y +        self.pieces[x, y], self.orients[x, y] = value + +    #### +    # +    # The following two get methods are helper methods. +    # +    #   get_adj_pos is  low-level, forcing inputs into the appropriate range. +    # get_edge_pair is high-level, using low-level methods when needed. +    # +    #### +      def get_adj_pos(self, x0, y0, direction,              return_is_internal=False):          """Returns the position adjacent to (x0, y0) in direction. @@ -187,21 +245,47 @@ class ooPuzzle:              return x1, y1, is_internal          return x1, y1 +         +    def get_edge_pair(self, x0, y0, direction): +        """Returns the edge pair's filled status in direction and is_internal. -    def set_piece(self, x, y, value): -        x %= self.X -        y %= self.Y -        self.pieces[x, y] = value +        direction may be any string or integer in the dictionary +            self.DIRECTIONS (case insensitive) -    def set_orient(self, x, y, value): -        x %= self.X -        y %= self.Y -        self.orients[x, y] = value +        The first output is the status of the edge of (x0, y0) in direction. -    def set_piece_orient(self, x, y, value): -        x %= self.X -        y %= self.Y -        self.pieces[x, y], self.orients[x, y] = value +        The second output is the status of the adjacent edge, +            that is, of the piece adjacent to (x0, y0) in direction, +            the status of the edge in the opposite direction. + +        The third output is a bool for whether both edges are internal. +            (If false, then the edges are across a border.) +        """ +        if type(direction) is str: +            direction = self.DIRECTIONS[direction.upper()] + +        piece, orient = self.get_piece_orient(x0, y0) +        edge0 = self.PIECE_ORIENT_TO_EDGES[piece, orient][direction] + +        x1, y1, is_internal = \ +                self.get_adj_pos(x0, y0, direction, +                                 return_is_internal=True) +        piece, orient = self.get_piece_orient(x1, y1) +        direction = (direction + 2) % 4 +        edge1 = self.PIECE_ORIENT_TO_EDGES[piece, orient][direction] + +        return edge0, edge1, is_internal + +    #### +    # +    # The following get and set methods are for initialization. +    # +    # They allow any of the following three data sets to set the other two: +    #     game_id : self.game_id                      : int +    #       edges : self.horiz_edges, self.vert_edges : dicts +    #      pieces : self.pieces, self.orients         : dicts +    # +    ####      def set_pieces_from_edges(self, horiz_edges, vert_edges):          """Convert edge dictionaries into a puzzle state. @@ -236,18 +320,19 @@ class ooPuzzle:                  self.pieces [x, y] = piece                  self.orients[x, y] = orient -    def set_pieces_from_game_id(self, game_id=None): +    def set_pieces_from_game_id(self, random_game_id=False):          """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 +        if random_game_id, +            then a random game_id is chosen from the appropriate range, +            and pieces are set from that          """          # compute:          #  n_horiz, the number of horizontal edge pairs          #  n_vert,  the number of  vertical  edge pairs +        # +        # generate random game_id if needed          shift = -int(not self.toroidal)          n_col = self.X + shift @@ -255,11 +340,8 @@ class ooPuzzle:          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 +        if random_game_id: +            self.game_id = random.randrange(0, 2**(n_horiz + n_vert))          # prepare to record edges for self.set_pieces_from_edges @@ -277,6 +359,8 @@ class ooPuzzle:          # set the edges determined by game_id +        game_id = self.game_id +          for i in range(n_vert):              row, x = divmod(i, self.X)              game_id, bit = divmod(game_id, 2) @@ -295,16 +379,13 @@ class ooPuzzle:          for x in range(self.X):              vert_edges[x, 0] = vert_edges[x, self.Y] -        # turn edges into pieces +        # set the pieces from the edges          self.set_pieces_from_edges(horiz_edges, vert_edges)      ####      # -    # The following methods are high-level, -    # and no longer require position inputs to be forced in range. -    # -    # (They call the low-level functions when needed.) +    # The rest of the methods are for user interface.      #      #### @@ -319,36 +400,6 @@ class ooPuzzle:          for x in range(self.X):              for y in range(self.Y):                  self.set_orient(x, y, random.randrange(4)) -         -    def get_edge_pair(self, x0, y0, direction): -        """Returns the edge pair's filled status in direction and is_internal. - -        direction may be any string or integer in the dictionary -            self.DIRECTIONS (case insensitive) - -        The first output is the status of the edge of (x0, y0) in direction. - -        The second output is the status of the adjacent edge, -            that is, of the piece adjacent to (x0, y0) in direction, -            the status of the edge in the opposite direction. - -        The third output is a bool for whether both edges are internal. -            (If false, then the edges are across a border.) -        """ -        if type(direction) is str: -            direction = self.DIRECTIONS[direction.upper()] - -        piece, orient = self.get_piece_orient(x0, y0) -        edge0 = self.PIECE_ORIENT_TO_EDGES[piece, orient][direction] - -        x1, y1, is_internal = \ -                self.get_adj_pos(x0, y0, direction, -                                 return_is_internal=True) -        piece, orient = self.get_piece_orient(x1, y1) -        direction = (direction + 2) % 4 -        edge1 = self.PIECE_ORIENT_TO_EDGES[piece, orient][direction] - -        return edge0, edge1, is_internal      def check_edge_pair(self, x, y, direction, if_filled=False):          """Check the edge pair's validity around (x, y) in direction. @@ -390,7 +441,7 @@ class ooPlay:          """Create a new ooPlay instance.          Arguments. -            scr : curses screen object used for display +            screen : curses screen object used for display          """          self.screen = screen          self.Y, self.X = self.screen.getmaxyx() @@ -465,7 +516,7 @@ class ooPlay:                          break                  else:                      break -                self.puzzle.set_pieces_from_game_id() +                self.puzzle.set_pieces_from_game_id(random_game_id=True)          self.puzzle.random_orients()          # reset some flags | 
