sliding_puzzle 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/README.md +269 -0
 - data/lib/sliding_puzzle/base.rb +136 -0
 - data/lib/sliding_puzzle/oracle.rb +109 -0
 - data/lib/sliding_puzzle.rb +6 -0
 - metadata +75 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA256:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: b1d7484d10c76bbf7e0f8be38bb08988ea6a5973e4d523c8cbbd114a5516c1dc
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 850ae2da5a00df634aa1e7ef850ad2171a33a912925ad61c9a0999b697577269
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 8185f6328ed9afca9cdff45dd7fad6e9e6c8e270e7a0cea049eac5a3ff4ce0023e8e9604e93f6a35c4084fcfe51091db51a267daccbd64e9059c24319b69c6cd
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 94215e1af28e0f89f6acf436e2c71d214093fa42221078b2a7dad5ec3914eb6e6cf16a4f29e0f283b7603a1e584e7b88658ef5cf3274c6437973cf6454dec7c6
         
     | 
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,269 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ## Sliding Puzzle ##
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            A Ruby gem for manipulating and solving sliding tile puzzles.
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            **TODO - this gem is a work in progress**
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            ### Overview ###
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            You might have come across sliding tile puzzles before. They're usually cheap,
         
     | 
| 
      
 10 
     | 
    
         
            +
            plastic-y toys that can be rearranged by sliding their tiles around. Here's an
         
     | 
| 
      
 11 
     | 
    
         
            +
            example containing a picture of a frog:
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            <br/><p align="center">
         
     | 
| 
      
 14 
     | 
    
         
            +
              <img src="https://raw.githubusercontent.com/tuzz/sliding_puzzle/master/frog.jpg" />
         
     | 
| 
      
 15 
     | 
    
         
            +
            </p><br/>
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            One of the pieces is blank which means an adjacent tile can move into its place.
         
     | 
| 
      
 18 
     | 
    
         
            +
            After repeating this a few times, these puzzles can be tricky to solve. We can
         
     | 
| 
      
 19 
     | 
    
         
            +
            also think of these puzzles as grids of numbers:
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            <br/><p align="center">
         
     | 
| 
      
 22 
     | 
    
         
            +
              <img src="https://raw.githubusercontent.com/tuzz/sliding_puzzle/master/grid.jpg" />
         
     | 
| 
      
 23 
     | 
    
         
            +
            </p><br/>
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            The challenge is to find a sequence of moves to rearrange the 'start state' into
         
     | 
| 
      
 26 
     | 
    
         
            +
            the 'goal state' in as few moves as possible. This gem lets you play with these
         
     | 
| 
      
 27 
     | 
    
         
            +
            puzzles and it solves them in an optimal number of moves.
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            In this example the blank is in the the upper-left corner of the goal state, but
         
     | 
| 
      
 30 
     | 
    
         
            +
            it's arbitrary where it's located.
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            ### Usage ###
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            Puzzles are represented as arrays of numbers:
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 37 
     | 
    
         
            +
            puzzle = SlidingPuzzle.new(
         
     | 
| 
      
 38 
     | 
    
         
            +
              [1, 2, 0], # <-- the 0 represents blank
         
     | 
| 
      
 39 
     | 
    
         
            +
              [3, 4, 5],
         
     | 
| 
      
 40 
     | 
    
         
            +
              [6, 7, 8],
         
     | 
| 
      
 41 
     | 
    
         
            +
            )
         
     | 
| 
      
 42 
     | 
    
         
            +
            ```
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            You can slide tiles around and print the result:
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 47 
     | 
    
         
            +
            puzzle.slide!(:right)
         
     | 
| 
      
 48 
     | 
    
         
            +
            puzzle.print
         
     | 
| 
      
 49 
     | 
    
         
            +
            # [1, 0, 2]
         
     | 
| 
      
 50 
     | 
    
         
            +
            # [3, 4, 5]
         
     | 
| 
      
 51 
     | 
    
         
            +
            # [6, 7, 8]
         
     | 
| 
      
 52 
     | 
    
         
            +
            ```
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
            The `#slide` method will return a new `SlidingPuzzle` whereas `#slide!` will
         
     | 
| 
      
 55 
     | 
    
         
            +
            mutate the existing one.
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
            ### Moves ###
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
            You can return an array of possible moves for a sliding puzzle:
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 62 
     | 
    
         
            +
            puzzle = SlidingPuzzle.new(
         
     | 
| 
      
 63 
     | 
    
         
            +
              [1, 2, 0],
         
     | 
| 
      
 64 
     | 
    
         
            +
              [3, 4, 5],
         
     | 
| 
      
 65 
     | 
    
         
            +
              [6, 7, 8],
         
     | 
| 
      
 66 
     | 
    
         
            +
            )
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
            puzzle.moves
         
     | 
| 
      
 69 
     | 
    
         
            +
            #=> [:right, :up]
         
     | 
| 
      
 70 
     | 
    
         
            +
            ```
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
            If you try a move that isn't valid, an error is raised:
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 75 
     | 
    
         
            +
            puzzle.slide(:left)
         
     | 
| 
      
 76 
     | 
    
         
            +
            # SlidingPuzzle::InvalidMoveError, "unable to slide left"
         
     | 
| 
      
 77 
     | 
    
         
            +
            ```
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
            ### Scrambling ###
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
            You can scramble a puzzle:
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 84 
     | 
    
         
            +
            puzzle.scramble!
         
     | 
| 
      
 85 
     | 
    
         
            +
            ```
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
            By default, this will perform 100 random moves, but you can set this:
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 90 
     | 
    
         
            +
            puzzle.scramble!(moves: 3)
         
     | 
| 
      
 91 
     | 
    
         
            +
            ```
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
            The `#scramble` method will return a new `SlidingPuzzle` whereas `#scramble!`
         
     | 
| 
      
 94 
     | 
    
         
            +
            will mutate the existing one.
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
            ### Dimensions ###
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
            Puzzles can have different dimensions:
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 101 
     | 
    
         
            +
            two_by_four = SlidingPuzzle.new(
         
     | 
| 
      
 102 
     | 
    
         
            +
              [1, 2, 3, 4],
         
     | 
| 
      
 103 
     | 
    
         
            +
              [5, 6, 7, 0],
         
     | 
| 
      
 104 
     | 
    
         
            +
            )
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
            two_by_four.slide!(:down)
         
     | 
| 
      
 107 
     | 
    
         
            +
            two_by_four.print
         
     | 
| 
      
 108 
     | 
    
         
            +
            # [1, 2, 3, 0]
         
     | 
| 
      
 109 
     | 
    
         
            +
            # [5, 6, 7, 4]
         
     | 
| 
      
 110 
     | 
    
         
            +
            ```
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
            Puzzles must be rectangular and contain a single blank.
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
            ### Solving ###
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
            Finding the shortest solution for a sliding puzzle is
         
     | 
| 
      
 117 
     | 
    
         
            +
            [a hard problem](https://en.wikipedia.org/wiki/15_puzzle#Solvability). This gem
         
     | 
| 
      
 118 
     | 
    
         
            +
            provides 'oracles' to find these solutions:
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 121 
     | 
    
         
            +
            goal_state = SlidingPuzzle.new(
         
     | 
| 
      
 122 
     | 
    
         
            +
              [1, 2, 0],
         
     | 
| 
      
 123 
     | 
    
         
            +
              [3, 4, 5],
         
     | 
| 
      
 124 
     | 
    
         
            +
              [6, 7, 8],
         
     | 
| 
      
 125 
     | 
    
         
            +
            )
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
            oracle = SlidingPuzzle.oracle(goal_state)
         
     | 
| 
      
 128 
     | 
    
         
            +
            ```
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
            This 'oracle' finds the shortest solution from any start state:
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 133 
     | 
    
         
            +
            start_state = SlidingPuzzle.new(
         
     | 
| 
      
 134 
     | 
    
         
            +
              [1, 4, 2],
         
     | 
| 
      
 135 
     | 
    
         
            +
              [3, 7, 5],
         
     | 
| 
      
 136 
     | 
    
         
            +
              [6, 0, 8],
         
     | 
| 
      
 137 
     | 
    
         
            +
            )
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
            oracle.solve(start_state)
         
     | 
| 
      
 140 
     | 
    
         
            +
            #=> [:down, :down, :left]
         
     | 
| 
      
 141 
     | 
    
         
            +
            ```
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
            ### Oracles ###
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
            Oracles aren't magic. They are the result of precomputing solutions in advance.
         
     | 
| 
      
 146 
     | 
    
         
            +
            This gem provides oracles for puzzles with up to eight tiles:
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 149 
     | 
    
         
            +
            goal_state = SlidingPuzzle.new(
         
     | 
| 
      
 150 
     | 
    
         
            +
              [1, 2, 3, 4],
         
     | 
| 
      
 151 
     | 
    
         
            +
              [5, 0, 6, 7],
         
     | 
| 
      
 152 
     | 
    
         
            +
            )
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
            oracle = SlidingPuzzle.oracle(goal_state)
         
     | 
| 
      
 155 
     | 
    
         
            +
            ```
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
            The numbers of the goal state must be sequential, but the blank can be anywhere.
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
            The `#oracle` method will return `nil` for a puzzle with more than eight tiles,
         
     | 
| 
      
 160 
     | 
    
         
            +
            or if the numbers aren't sequential.
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
            ### Impossible puzzles ###
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
            Some starting positions are
         
     | 
| 
      
 165 
     | 
    
         
            +
            [impossible to solve](https://en.wikipedia.org/wiki/15_puzzle#Solvability). For
         
     | 
| 
      
 166 
     | 
    
         
            +
            example, if you swap any two tiles from the goal state, there's no way to solve
         
     | 
| 
      
 167 
     | 
    
         
            +
            the puzzle:
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 170 
     | 
    
         
            +
            unsolvable = SlidingPuzzle.new(
         
     | 
| 
      
 171 
     | 
    
         
            +
              [2, 1, 0],
         
     | 
| 
      
 172 
     | 
    
         
            +
              [3, 4, 5],
         
     | 
| 
      
 173 
     | 
    
         
            +
              [6, 7, 8],
         
     | 
| 
      
 174 
     | 
    
         
            +
            )
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
            oracle.solve(unsolvable)
         
     | 
| 
      
 177 
     | 
    
         
            +
            #=> nil
         
     | 
| 
      
 178 
     | 
    
         
            +
            ```
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
      
 180 
     | 
    
         
            +
            In total, there are [N!](https://en.wikipedia.org/wiki/Factorial) possible
         
     | 
| 
      
 181 
     | 
    
         
            +
            configurations for a puzzle with N tiles (including the blank), but only half of
         
     | 
| 
      
 182 
     | 
    
         
            +
            these are solvable.
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
            For the 3x3 puzzle, there are 9! / 2 = 181,400 solvable configurations.
         
     | 
| 
      
 185 
     | 
    
         
            +
             
     | 
| 
      
 186 
     | 
    
         
            +
            ### Precomputing ###
         
     | 
| 
      
 187 
     | 
    
         
            +
             
     | 
| 
      
 188 
     | 
    
         
            +
            For dimensions with no oracles, you can precompute your own:
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 191 
     | 
    
         
            +
            goal_state = SlidingPuzzle.new(
         
     | 
| 
      
 192 
     | 
    
         
            +
              [0, 1,  2, 3],
         
     | 
| 
      
 193 
     | 
    
         
            +
              [4, 5,  6, 7],
         
     | 
| 
      
 194 
     | 
    
         
            +
              [8, 9, 10, 11],
         
     | 
| 
      
 195 
     | 
    
         
            +
            )
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
            oracle = SlidingPuzzle.precompute(goal_state)
         
     | 
| 
      
 198 
     | 
    
         
            +
            ```
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
            You can then write the result to a file:
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 203 
     | 
    
         
            +
            oracle.write("path/to/file")
         
     | 
| 
      
 204 
     | 
    
         
            +
            ```
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
            And read it in later:
         
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
      
 208 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 209 
     | 
    
         
            +
            oracle = SlidingPuzzle.read("path/to/file")
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
            start_state = SlidingPuzzle.new(
         
     | 
| 
      
 212 
     | 
    
         
            +
              [1, 5,  2,  3],
         
     | 
| 
      
 213 
     | 
    
         
            +
              [4, 0,  6,  7],
         
     | 
| 
      
 214 
     | 
    
         
            +
              [8, 9, 10, 11],
         
     | 
| 
      
 215 
     | 
    
         
            +
            )
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
            oracle.solve(start_state)
         
     | 
| 
      
 218 
     | 
    
         
            +
            #=> [:down, :right]
         
     | 
| 
      
 219 
     | 
    
         
            +
            ```
         
     | 
| 
      
 220 
     | 
    
         
            +
             
     | 
| 
      
 221 
     | 
    
         
            +
            For puzzles with greater than 12 tiles, you won't be able to precompute an
         
     | 
| 
      
 222 
     | 
    
         
            +
            oracle in a reasonable amount of time. The 4x4 puzzle will take more than 40,000
         
     | 
| 
      
 223 
     | 
    
         
            +
            times longer to precompute than the 3x4 puzzle and require terrabytes of RAM.
         
     | 
| 
      
 224 
     | 
    
         
            +
            The `debug` flag will reveal if it's ever likely to finish:
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 227 
     | 
    
         
            +
            SlidingPuzzle.precompute(goal_state, debug: true)
         
     | 
| 
      
 228 
     | 
    
         
            +
            # queue size: 1
         
     | 
| 
      
 229 
     | 
    
         
            +
            # queue size: 2
         
     | 
| 
      
 230 
     | 
    
         
            +
            # queue size: 3
         
     | 
| 
      
 231 
     | 
    
         
            +
            # ...
         
     | 
| 
      
 232 
     | 
    
         
            +
            ```
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
            If it just keeps growing, it's unlikely to finish.
         
     | 
| 
      
 235 
     | 
    
         
            +
             
     | 
| 
      
 236 
     | 
    
         
            +
            ### Other methods ###
         
     | 
| 
      
 237 
     | 
    
         
            +
             
     | 
| 
      
 238 
     | 
    
         
            +
            There are a few other methods that may be useful:
         
     | 
| 
      
 239 
     | 
    
         
            +
             
     | 
| 
      
 240 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 241 
     | 
    
         
            +
            # Get the number on the first row and second column:
         
     | 
| 
      
 242 
     | 
    
         
            +
            puzzle.get(0, 1)
         
     | 
| 
      
 243 
     | 
    
         
            +
            #=> 5
         
     | 
| 
      
 244 
     | 
    
         
            +
             
     | 
| 
      
 245 
     | 
    
         
            +
            # Find the row and column of the number 5:
         
     | 
| 
      
 246 
     | 
    
         
            +
            puzzle.find(5)
         
     | 
| 
      
 247 
     | 
    
         
            +
            #=> [0, 1]
         
     | 
| 
      
 248 
     | 
    
         
            +
             
     | 
| 
      
 249 
     | 
    
         
            +
            # Return a clone of the array of tiles:
         
     | 
| 
      
 250 
     | 
    
         
            +
            puzzle.tiles
         
     | 
| 
      
 251 
     | 
    
         
            +
            #=> [[1, 5,  2,  3], [4, 0,  6,  7], [8, 9, 10, 11]]
         
     | 
| 
      
 252 
     | 
    
         
            +
             
     | 
| 
      
 253 
     | 
    
         
            +
            # Return a clone of the puzzle:
         
     | 
| 
      
 254 
     | 
    
         
            +
            puzzle.clone
         
     | 
| 
      
 255 
     | 
    
         
            +
            #=> #<SlidingPuzzle:object_id>
         
     | 
| 
      
 256 
     | 
    
         
            +
            ```
         
     | 
| 
      
 257 
     | 
    
         
            +
             
     | 
| 
      
 258 
     | 
    
         
            +
            ### Ideas to try ###
         
     | 
| 
      
 259 
     | 
    
         
            +
             
     | 
| 
      
 260 
     | 
    
         
            +
            I hope you have fun with this gem. Here are some things to try:
         
     | 
| 
      
 261 
     | 
    
         
            +
             
     | 
| 
      
 262 
     | 
    
         
            +
            1) Find an algorithm to solve a puzzle without using an oracle
         
     | 
| 
      
 263 
     | 
    
         
            +
            2) Compare the number of moves your algorithm makes with the oracle
         
     | 
| 
      
 264 
     | 
    
         
            +
            3) Write a web server for interacting with sliding puzzles
         
     | 
| 
      
 265 
     | 
    
         
            +
            4) Read
         
     | 
| 
      
 266 
     | 
    
         
            +
            [more](https://www.ijcai.org/Proceedings/03/Papers/267.pdf)
         
     | 
| 
      
 267 
     | 
    
         
            +
            [about](https://www.ijcai.org/Proceedings/03/Papers/267.pdf)
         
     | 
| 
      
 268 
     | 
    
         
            +
            techniques for coping with larger dimensions
         
     | 
| 
      
 269 
     | 
    
         
            +
            5) Write a user interface to slide tiles around
         
     | 
| 
         @@ -0,0 +1,136 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class SlidingPuzzle
         
     | 
| 
      
 2 
     | 
    
         
            +
              def self.oracle(goal_state)
         
     | 
| 
      
 3 
     | 
    
         
            +
                Oracle.lookup(goal_state)
         
     | 
| 
      
 4 
     | 
    
         
            +
              end
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              def self.precompute(goal_state, **options)
         
     | 
| 
      
 7 
     | 
    
         
            +
                Oracle.precompute(goal_state, **options)
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              def self.read(path)
         
     | 
| 
      
 11 
     | 
    
         
            +
                Oracle.read(path)
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              def initialize(*tiles)
         
     | 
| 
      
 15 
     | 
    
         
            +
                self.tiles = flatten_tiles(tiles)
         
     | 
| 
      
 16 
     | 
    
         
            +
                self.max_row = @tiles.size - 1
         
     | 
| 
      
 17 
     | 
    
         
            +
                self.max_column = @tiles.first.size - 1
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                must_be_rectangular!
         
     | 
| 
      
 20 
     | 
    
         
            +
                must_contain_one_blank!
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              def slide!(direction)
         
     | 
| 
      
 24 
     | 
    
         
            +
                unless moves.include?(direction)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  raise InvalidMoveError, "unable to slide #{direction}"
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                x1, y1 = find(0)
         
     | 
| 
      
 29 
     | 
    
         
            +
                x2, y2 = x1, y1
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                y2 += 1 if direction == :left
         
     | 
| 
      
 32 
     | 
    
         
            +
                y2 -= 1 if direction == :right
         
     | 
| 
      
 33 
     | 
    
         
            +
                x2 += 1 if direction == :up
         
     | 
| 
      
 34 
     | 
    
         
            +
                x2 -= 1 if direction == :down
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                @tiles[x1][y1], @tiles[x2][y2] = @tiles[x2][y2], @tiles[x1][y1]
         
     | 
| 
      
 37 
     | 
    
         
            +
                self
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
              def slide(direction)
         
     | 
| 
      
 41 
     | 
    
         
            +
                clone.slide!(direction)
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
              def clone
         
     | 
| 
      
 45 
     | 
    
         
            +
                self.class.new(*tiles)
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              def print
         
     | 
| 
      
 49 
     | 
    
         
            +
                Kernel.print @tiles.map(&:inspect).join("\n")
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              def moves
         
     | 
| 
      
 53 
     | 
    
         
            +
                row, column = find(0)
         
     | 
| 
      
 54 
     | 
    
         
            +
                moves = []
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                moves.push(:left) unless column == max_column
         
     | 
| 
      
 57 
     | 
    
         
            +
                moves.push(:right) unless column.zero?
         
     | 
| 
      
 58 
     | 
    
         
            +
                moves.push(:up) unless row == max_row
         
     | 
| 
      
 59 
     | 
    
         
            +
                moves.push(:down) unless row.zero?
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                moves
         
     | 
| 
      
 62 
     | 
    
         
            +
              end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
              def scramble!(moves: 100)
         
     | 
| 
      
 65 
     | 
    
         
            +
                moves.times do
         
     | 
| 
      
 66 
     | 
    
         
            +
                  slide!(self.moves.sample)
         
     | 
| 
      
 67 
     | 
    
         
            +
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                self
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
              def scramble(moves: 100)
         
     | 
| 
      
 73 
     | 
    
         
            +
                clone.scramble!(moves: moves)
         
     | 
| 
      
 74 
     | 
    
         
            +
              end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
              def tiles
         
     | 
| 
      
 77 
     | 
    
         
            +
                JSON.parse(JSON.generate(@tiles))
         
     | 
| 
      
 78 
     | 
    
         
            +
              end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
              def get(row, column)
         
     | 
| 
      
 81 
     | 
    
         
            +
                @tiles[row][column]
         
     | 
| 
      
 82 
     | 
    
         
            +
              end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
              def find(number)
         
     | 
| 
      
 85 
     | 
    
         
            +
                tiles.each.with_index do |numbers, row|
         
     | 
| 
      
 86 
     | 
    
         
            +
                  numbers.each.with_index do |n, column|
         
     | 
| 
      
 87 
     | 
    
         
            +
                    return [row, column] if n == number
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                nil
         
     | 
| 
      
 92 
     | 
    
         
            +
              end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
              def ==(other)
         
     | 
| 
      
 95 
     | 
    
         
            +
                tiles == other.tiles
         
     | 
| 
      
 96 
     | 
    
         
            +
              end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
              alias :eql? :==
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
              def hash
         
     | 
| 
      
 101 
     | 
    
         
            +
                tiles.hash
         
     | 
| 
      
 102 
     | 
    
         
            +
              end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
              private
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
              def flatten_tiles(tiles)
         
     | 
| 
      
 107 
     | 
    
         
            +
                if tiles[0].is_a?(Array) && tiles[0][0].is_a?(Array)
         
     | 
| 
      
 108 
     | 
    
         
            +
                  tiles.flatten(1)
         
     | 
| 
      
 109 
     | 
    
         
            +
                else
         
     | 
| 
      
 110 
     | 
    
         
            +
                  tiles
         
     | 
| 
      
 111 
     | 
    
         
            +
                end
         
     | 
| 
      
 112 
     | 
    
         
            +
              end
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
              def must_be_rectangular!
         
     | 
| 
      
 115 
     | 
    
         
            +
                sizes = tiles.map(&:size)
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                if sizes.uniq.size > 1
         
     | 
| 
      
 118 
     | 
    
         
            +
                  raise NotRectangularError, "puzzle must be rectangular"
         
     | 
| 
      
 119 
     | 
    
         
            +
                end
         
     | 
| 
      
 120 
     | 
    
         
            +
              end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
              def must_contain_one_blank!
         
     | 
| 
      
 123 
     | 
    
         
            +
                blanks = tiles.flatten.count(0)
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                unless blanks == 1
         
     | 
| 
      
 126 
     | 
    
         
            +
                  raise BlankError, "puzzle must contain a single blank"
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
              end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
              attr_accessor :max_row, :max_column
         
     | 
| 
      
 131 
     | 
    
         
            +
              attr_writer :tiles
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
              class InvalidMoveError < StandardError; end
         
     | 
| 
      
 134 
     | 
    
         
            +
              class NotRectangularError < StandardError; end
         
     | 
| 
      
 135 
     | 
    
         
            +
              class BlankError < StandardError; end
         
     | 
| 
      
 136 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,109 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class SlidingPuzzle
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Oracle
         
     | 
| 
      
 3 
     | 
    
         
            +
                OPPOSITES = {
         
     | 
| 
      
 4 
     | 
    
         
            +
                  left: :right,
         
     | 
| 
      
 5 
     | 
    
         
            +
                  right: :left,
         
     | 
| 
      
 6 
     | 
    
         
            +
                  up: :down,
         
     | 
| 
      
 7 
     | 
    
         
            +
                  down: :up,
         
     | 
| 
      
 8 
     | 
    
         
            +
                }
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                def self.precompute(goal_state, debug: false)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  goal_state = goal_state.clone
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  queue = [goal_state]
         
     | 
| 
      
 14 
     | 
    
         
            +
                  lookup_table = { goal_state => :goal }
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  until queue.empty?
         
     | 
| 
      
 17 
     | 
    
         
            +
                    puts "queue size: #{queue.size}" if debug
         
     | 
| 
      
 18 
     | 
    
         
            +
                    puzzle = queue.shift
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                    puzzle.moves.each do |direction|
         
     | 
| 
      
 21 
     | 
    
         
            +
                      next_puzzle = puzzle.slide(direction)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                      unless lookup_table[next_puzzle]
         
     | 
| 
      
 24 
     | 
    
         
            +
                        lookup_table[next_puzzle] = OPPOSITES[direction]
         
     | 
| 
      
 25 
     | 
    
         
            +
                        queue.push(next_puzzle)
         
     | 
| 
      
 26 
     | 
    
         
            +
                      end
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  new(lookup_table)
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def self.precompute_all(max_tiles: 8, directory: "oracles", debug: false)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  FileUtils.mkdir_p("oracles")
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  1.upto(5) do |rows|
         
     | 
| 
      
 37 
     | 
    
         
            +
                    1.upto(5) do |columns|
         
     | 
| 
      
 38 
     | 
    
         
            +
                      number_of_tiles = rows * columns - 1
         
     | 
| 
      
 39 
     | 
    
         
            +
                      next if number_of_tiles > max_tiles
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                      numbers = 1.upto(number_of_tiles).to_a
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                      0.upto(number_of_tiles) do |position|
         
     | 
| 
      
 44 
     | 
    
         
            +
                        numbers_with_blank = numbers.dup.insert(position, 0)
         
     | 
| 
      
 45 
     | 
    
         
            +
                        tiles = numbers_with_blank.each_slice(columns).to_a
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                        goal_state = SlidingPuzzle.new(tiles)
         
     | 
| 
      
 48 
     | 
    
         
            +
                        path = "#{directory}/#{basename(goal_state)}.dat"
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                        print "Precomputing #{path}... " if debug
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                        oracle = precompute(goal_state)
         
     | 
| 
      
 53 
     | 
    
         
            +
                        oracle.write(path)
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                        puts "Done." if debug
         
     | 
| 
      
 56 
     | 
    
         
            +
                      end
         
     | 
| 
      
 57 
     | 
    
         
            +
                    end
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                def self.lookup(goal_state)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  filename = "#{basename(goal_state)}.dat"
         
     | 
| 
      
 63 
     | 
    
         
            +
                  path = File.expand_path("#{__dir__}/../../oracles/#{filename}")
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  read(path) if File.exist?(path)
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def self.basename(puzzle)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  puzzle.tiles.map { |row| row.join(",") }.join(":")
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                def initialize(lookup_table)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  self.lookup_table = lookup_table
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                def solve(sliding_puzzle)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  moves = []
         
     | 
| 
      
 78 
     | 
    
         
            +
                  next_puzzle = sliding_puzzle
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  loop do
         
     | 
| 
      
 81 
     | 
    
         
            +
                    direction = lookup_table[next_puzzle]
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                    return nil unless direction
         
     | 
| 
      
 84 
     | 
    
         
            +
                    return moves if direction == :goal
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                    moves.push(direction)
         
     | 
| 
      
 87 
     | 
    
         
            +
                    next_puzzle = next_puzzle.slide(direction)
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                def write(path)
         
     | 
| 
      
 92 
     | 
    
         
            +
                  data = Marshal.dump(self)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  gzip = Zlib::Deflate.deflate(data)
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  File.open(path, "wb") { |f| f.write(gzip) }
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                def self.read(path)
         
     | 
| 
      
 99 
     | 
    
         
            +
                  gzip = File.binread(path)
         
     | 
| 
      
 100 
     | 
    
         
            +
                  data = Zlib::Inflate.inflate(gzip)
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  Marshal.load(data)
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                private
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                attr_accessor :lookup_table
         
     | 
| 
      
 108 
     | 
    
         
            +
              end
         
     | 
| 
      
 109 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,75 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: sliding_puzzle
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.0.0
         
     | 
| 
      
 5 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 6 
     | 
    
         
            +
            authors:
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Chris Patuzzo
         
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 9 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 10 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2018-01-29 00:00:00.000000000 Z
         
     | 
| 
      
 12 
     | 
    
         
            +
            dependencies:
         
     | 
| 
      
 13 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 14 
     | 
    
         
            +
              name: rspec
         
     | 
| 
      
 15 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 16 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 17 
     | 
    
         
            +
                - - '='
         
     | 
| 
      
 18 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 19 
     | 
    
         
            +
                    version: 3.7.0
         
     | 
| 
      
 20 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 21 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 22 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 23 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 24 
     | 
    
         
            +
                - - '='
         
     | 
| 
      
 25 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 26 
     | 
    
         
            +
                    version: 3.7.0
         
     | 
| 
      
 27 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 28 
     | 
    
         
            +
              name: pry
         
     | 
| 
      
 29 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 30 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 31 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 32 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 33 
     | 
    
         
            +
                    version: 0.11.3
         
     | 
| 
      
 34 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 35 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 36 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 37 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 38 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 39 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 40 
     | 
    
         
            +
                    version: 0.11.3
         
     | 
| 
      
 41 
     | 
    
         
            +
            description: A Ruby gem for manipulating and solving sliding tile puzzles.
         
     | 
| 
      
 42 
     | 
    
         
            +
            email: chris@patuzzo.co.uk
         
     | 
| 
      
 43 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 44 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 45 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 46 
     | 
    
         
            +
            files:
         
     | 
| 
      
 47 
     | 
    
         
            +
            - README.md
         
     | 
| 
      
 48 
     | 
    
         
            +
            - lib/sliding_puzzle.rb
         
     | 
| 
      
 49 
     | 
    
         
            +
            - lib/sliding_puzzle/base.rb
         
     | 
| 
      
 50 
     | 
    
         
            +
            - lib/sliding_puzzle/oracle.rb
         
     | 
| 
      
 51 
     | 
    
         
            +
            homepage: https://github.com/tuzz/sliding_puzzle
         
     | 
| 
      
 52 
     | 
    
         
            +
            licenses:
         
     | 
| 
      
 53 
     | 
    
         
            +
            - MIT
         
     | 
| 
      
 54 
     | 
    
         
            +
            metadata: {}
         
     | 
| 
      
 55 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 56 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 57 
     | 
    
         
            +
            require_paths:
         
     | 
| 
      
 58 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 59 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 60 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 61 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 62 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 63 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 64 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 65 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 66 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 67 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 68 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 69 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 70 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 71 
     | 
    
         
            +
            rubygems_version: 2.7.3
         
     | 
| 
      
 72 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 73 
     | 
    
         
            +
            specification_version: 4
         
     | 
| 
      
 74 
     | 
    
         
            +
            summary: Sliding Puzzle
         
     | 
| 
      
 75 
     | 
    
         
            +
            test_files: []
         
     |