doku 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.
- data/Gemfile +32 -0
 - data/README.rdoc +15 -0
 - data/Rakefile +49 -0
 - data/VERSION +1 -0
 - data/lib/doku.rb +4 -0
 - data/lib/doku/dancing_links.rb +561 -0
 - data/lib/doku/grid.rb +241 -0
 - data/lib/doku/hexadoku.rb +56 -0
 - data/lib/doku/hexamurai.rb +95 -0
 - data/lib/doku/puzzle.rb +250 -0
 - data/lib/doku/solver.rb +103 -0
 - data/lib/doku/sudoku.rb +42 -0
 - data/spec/dancing_links_spec.rb +212 -0
 - data/spec/hexadoku_spec.rb +71 -0
 - data/spec/hexamurai_spec.rb +38 -0
 - data/spec/puzzle_spec.rb +147 -0
 - data/spec/solution_spec.rb +278 -0
 - data/spec/spec_helper.rb +19 -0
 - data/spec/sudoku_spec.rb +52 -0
 - data/spec/watch.rb +9 -0
 - metadata +198 -0
 
    
        data/lib/doku/solver.rb
    ADDED
    
    | 
         @@ -0,0 +1,103 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'backports' unless defined?(require_relative) and defined?(Enumerator)
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'dancing_links'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Doku
         
     | 
| 
      
 5 
     | 
    
         
            +
              # This module is included into the {Puzzle} class to provide methods
         
     | 
| 
      
 6 
     | 
    
         
            +
              # for solving the puzzles using the {DancingLinks} algorithm.
         
     | 
| 
      
 7 
     | 
    
         
            +
              module SolvableWithDancingLinks
         
     | 
| 
      
 8 
     | 
    
         
            +
                # @return (Puzzle)
         
     | 
| 
      
 9 
     | 
    
         
            +
                # Returns the first solution found by the Dancing Links algorithm,
         
     | 
| 
      
 10 
     | 
    
         
            +
                # or nil if there is no solution.
         
     | 
| 
      
 11 
     | 
    
         
            +
                def solve
         
     | 
| 
      
 12 
     | 
    
         
            +
                  each_solution { |s| return s }
         
     | 
| 
      
 13 
     | 
    
         
            +
                  return nil
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                # An enumerator for all the solutions to the puzzle.
         
     | 
| 
      
 17 
     | 
    
         
            +
                # @return (Enumerable)
         
     | 
| 
      
 18 
     | 
    
         
            +
                def solutions
         
     | 
| 
      
 19 
     | 
    
         
            +
                  Enumerator.new do |y|
         
     | 
| 
      
 20 
     | 
    
         
            +
                    each_solution do |solution|
         
     | 
| 
      
 21 
     | 
    
         
            +
                      y << solution
         
     | 
| 
      
 22 
     | 
    
         
            +
                    end
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                # This method lets you iterate over each solution.
         
     | 
| 
      
 27 
     | 
    
         
            +
                # Each solution is a puzzle object of the same class
         
     | 
| 
      
 28 
     | 
    
         
            +
                # such that solution.solution_for?(puzzle) is true.
         
     | 
| 
      
 29 
     | 
    
         
            +
                #
         
     | 
| 
      
 30 
     | 
    
         
            +
                # @yield [solution]
         
     | 
| 
      
 31 
     | 
    
         
            +
                def each_solution
         
     | 
| 
      
 32 
     | 
    
         
            +
                  to_link_matrix.each_exact_cover do |exact_cover|
         
     | 
| 
      
 33 
     | 
    
         
            +
                    yield exact_cover_to_solution exact_cover
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                # Returns a {DancingLinks::LinkMatrix} that represents this puzzle.
         
     | 
| 
      
 38 
     | 
    
         
            +
                # Every row is a {SquareAndGlyph} object representing a choice to
         
     | 
| 
      
 39 
     | 
    
         
            +
                # assign a certain glyph to a certain square.
         
     | 
| 
      
 40 
     | 
    
         
            +
                # Every column is a {GroupAndGlyph} object representing the
         
     | 
| 
      
 41 
     | 
    
         
            +
                # requirements that needs to be satisfied (every group must have
         
     | 
| 
      
 42 
     | 
    
         
            +
                # exactly one of each glyph assigned to a square in the group).
         
     | 
| 
      
 43 
     | 
    
         
            +
                # @return (DancingLinks::LinkMatrix)
         
     | 
| 
      
 44 
     | 
    
         
            +
                def to_link_matrix
         
     | 
| 
      
 45 
     | 
    
         
            +
                  # Create the link matrix.  This is a generic matrix
         
     | 
| 
      
 46 
     | 
    
         
            +
                  # that does not take in to account square.given_glyph.
         
     | 
| 
      
 47 
     | 
    
         
            +
                  sm = DancingLinks::LinkMatrix.from_sets sets_for_exact_cover_problem
         
     | 
| 
      
 48 
     | 
    
         
            +
                  
         
     | 
| 
      
 49 
     | 
    
         
            +
                  # Take into account square.given_glyph by covering certain
         
     | 
| 
      
 50 
     | 
    
         
            +
                  # rows (removing the row and all columns it touches).
         
     | 
| 
      
 51 
     | 
    
         
            +
                  each do |square, glyph|
         
     | 
| 
      
 52 
     | 
    
         
            +
                    sm.remove_row SquareAndGlyph.new(square,glyph)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  sm
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                # Converts an exact cover (an array of {SquareAndGlyph} objects) to a
         
     | 
| 
      
 59 
     | 
    
         
            +
                # solution of the puzzle.
         
     | 
| 
      
 60 
     | 
    
         
            +
                # @return (Puzzle)
         
     | 
| 
      
 61 
     | 
    
         
            +
                def exact_cover_to_solution(exact_cover)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  solution = dup
         
     | 
| 
      
 63 
     | 
    
         
            +
                  exact_cover.each do |sg|
         
     | 
| 
      
 64 
     | 
    
         
            +
                    solution[sg.square] = sg.glyph
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  solution
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                private
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                def sets_for_exact_cover_problem
         
     | 
| 
      
 73 
     | 
    
         
            +
                  sets = {}
         
     | 
| 
      
 74 
     | 
    
         
            +
                  squares.each do |square|
         
     | 
| 
      
 75 
     | 
    
         
            +
                    groups_with_square = groups.select { |g| g.include? square }
         
     | 
| 
      
 76 
     | 
    
         
            +
                    
         
     | 
| 
      
 77 
     | 
    
         
            +
                    glyphs.each do |glyph|
         
     | 
| 
      
 78 
     | 
    
         
            +
                      sets[SquareAndGlyph.new(square, glyph)] = [square] +
         
     | 
| 
      
 79 
     | 
    
         
            +
                        groups_with_square.collect do |group|
         
     | 
| 
      
 80 
     | 
    
         
            +
                          GroupAndGlyph.new group, glyph
         
     | 
| 
      
 81 
     | 
    
         
            +
                        end
         
     | 
| 
      
 82 
     | 
    
         
            +
                    end
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  sets
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
                
         
     | 
| 
      
 88 
     | 
    
         
            +
                # This is a simple class that just represents the choice of a puzzle's
         
     | 
| 
      
 89 
     | 
    
         
            +
                # square and a puzzle's glyph.  These are identified with rows in
         
     | 
| 
      
 90 
     | 
    
         
            +
                # the {DancingLinks::LinkMatrix} when solving puzzles, and there they
         
     | 
| 
      
 91 
     | 
    
         
            +
                # represent the choice to assign a particula glyph to a particular square.
         
     | 
| 
      
 92 
     | 
    
         
            +
                class SquareAndGlyph < Struct.new(:square, :glyph)
         
     | 
| 
      
 93 
     | 
    
         
            +
                end
         
     | 
| 
      
 94 
     | 
    
         
            +
                
         
     | 
| 
      
 95 
     | 
    
         
            +
                # This is a simple class that just represents the choice of a puzzle's
         
     | 
| 
      
 96 
     | 
    
         
            +
                # group of squares and a glyph.  These are identifies with columns in
         
     | 
| 
      
 97 
     | 
    
         
            +
                # the {DancingLinks::LinkMatrix} when solving puzzles, and they
         
     | 
| 
      
 98 
     | 
    
         
            +
                # represent a requirement that must be satisfied; every group must
         
     | 
| 
      
 99 
     | 
    
         
            +
                # has one of every glyph assigned to a square in it.
         
     | 
| 
      
 100 
     | 
    
         
            +
                class GroupAndGlyph < Struct.new(:group, :glyph)
         
     | 
| 
      
 101 
     | 
    
         
            +
                end
         
     | 
| 
      
 102 
     | 
    
         
            +
              end
         
     | 
| 
      
 103 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/doku/sudoku.rb
    ADDED
    
    | 
         @@ -0,0 +1,42 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'backports' unless defined?(require_relative)
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'puzzle'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'grid'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Doku
         
     | 
| 
      
 6 
     | 
    
         
            +
              # This class represents {http://en.wikipedia.org/wiki/Sudoku Sudoku}.
         
     | 
| 
      
 7 
     | 
    
         
            +
              # Each instance of this class represents a particular arrangement of
         
     | 
| 
      
 8 
     | 
    
         
            +
              # numbers written in the boxes.
         
     | 
| 
      
 9 
     | 
    
         
            +
              class Sudoku < Puzzle
         
     | 
| 
      
 10 
     | 
    
         
            +
                include PuzzleOnGrid
         
     | 
| 
      
 11 
     | 
    
         
            +
                extend PuzzleOnGrid::ClassMethods # improves generated docs
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                has_glyphs (1..9).to_a
         
     | 
| 
      
 14 
     | 
    
         
            +
                has_glyph_chars glyphs.collect &:to_s
         
     | 
| 
      
 15 
     | 
    
         
            +
                
         
     | 
| 
      
 16 
     | 
    
         
            +
                has_template <<END
         
     | 
| 
      
 17 
     | 
    
         
            +
            ...|...|...
         
     | 
| 
      
 18 
     | 
    
         
            +
            ...|...|...
         
     | 
| 
      
 19 
     | 
    
         
            +
            ...|...|...
         
     | 
| 
      
 20 
     | 
    
         
            +
            ---+---+---
         
     | 
| 
      
 21 
     | 
    
         
            +
            ...|...|...
         
     | 
| 
      
 22 
     | 
    
         
            +
            ...|...|...
         
     | 
| 
      
 23 
     | 
    
         
            +
            ...|...|...
         
     | 
| 
      
 24 
     | 
    
         
            +
            ---+---+---
         
     | 
| 
      
 25 
     | 
    
         
            +
            ...|...|...
         
     | 
| 
      
 26 
     | 
    
         
            +
            ...|...|...
         
     | 
| 
      
 27 
     | 
    
         
            +
            ...|...|...
         
     | 
| 
      
 28 
     | 
    
         
            +
            END
         
     | 
| 
      
 29 
     | 
    
         
            +
                
         
     | 
| 
      
 30 
     | 
    
         
            +
                0.upto(8) do |n|
         
     | 
| 
      
 31 
     | 
    
         
            +
                  define_group row(n)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  define_group column(n)
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
                
         
     | 
| 
      
 35 
     | 
    
         
            +
                0.step(6,3).each do |x|
         
     | 
| 
      
 36 
     | 
    
         
            +
                  0.step(6,3).each do |y|
         
     | 
| 
      
 37 
     | 
    
         
            +
                    define_group square_group(x, y)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
            end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,212 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'backports' unless defined? require_relative
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'spec_helper'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            describe Doku::DancingLinks::LinkMatrix do
         
     | 
| 
      
 5 
     | 
    
         
            +
              context "when created from scratch" do
         
     | 
| 
      
 6 
     | 
    
         
            +
                before do
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @m = Doku::DancingLinks::LinkMatrix.new
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                it "has no columns (i.e. it is empty)" do
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @m.columns.to_a.size.should == 0
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @m.should be_empty
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              describe ".from_sets" do
         
     | 
| 
      
 17 
     | 
    
         
            +
                it "can create a matrix from a set of sets" do
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @m = Doku::DancingLinks::LinkMatrix.from_sets(
         
     | 
| 
      
 19 
     | 
    
         
            +
                    Set.new([ Set.new([1, 2]), Set.new([2, 3]) ]) )
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                it "filters out duplicate column ids" do
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @m = Doku::DancingLinks::LinkMatrix.from_sets [ [1,2,2] ]
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @m.row([1,2,2]).nodes.to_a.size.should == 2
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
              describe "find_exact_cover" do
         
     | 
| 
      
 30 
     | 
    
         
            +
                it "can find one exact cover" do
         
     | 
| 
      
 31 
     | 
    
         
            +
                  m = Doku::DancingLinks::LinkMatrix.from_sets [[1,2], [2,3], [3,4]]
         
     | 
| 
      
 32 
     | 
    
         
            +
                  m.find_exact_cover.sort.should == [[1,2], [3,4]]
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                it "returns nil if there are no exact covers" do
         
     | 
| 
      
 36 
     | 
    
         
            +
                  m = Doku::DancingLinks::LinkMatrix.from_sets [[1,2], [2,3]]
         
     | 
| 
      
 37 
     | 
    
         
            +
                  m.find_exact_cover.should == nil
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                it "it finds the trivial exact cover for the trivial matrix" do
         
     | 
| 
      
 41 
     | 
    
         
            +
                  Doku::DancingLinks::LinkMatrix.new.find_exact_cover.should == []
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
              describe "exact_covers" do
         
     | 
| 
      
 46 
     | 
    
         
            +
                it "returns an Enumerable" do
         
     | 
| 
      
 47 
     | 
    
         
            +
                  Doku::DancingLinks::LinkMatrix.new.exact_covers.should be_a Enumerable
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                it "can find all exact covers" do
         
     | 
| 
      
 51 
     | 
    
         
            +
                  m = Doku::DancingLinks::LinkMatrix.from_sets [[1,2], [2,3], [3,4], [4,1]]
         
     | 
| 
      
 52 
     | 
    
         
            +
                  m.exact_covers.collect{|ec| ec.sort}.sort.should ==
         
     | 
| 
      
 53 
     | 
    
         
            +
                    [ [ [1,2], [3,4] ],
         
     | 
| 
      
 54 
     | 
    
         
            +
                      [ [2,3], [4,1] ] ]
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                it "it finds the trivial exact cover for the trivial matrix" do
         
     | 
| 
      
 58 
     | 
    
         
            +
                  Doku::DancingLinks::LinkMatrix.new.exact_covers.to_a.should == [[]]
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
              end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
              describe "each_exact_cover" do
         
     | 
| 
      
 63 
     | 
    
         
            +
                it "does not yield if there are no exact covers" do
         
     | 
| 
      
 64 
     | 
    
         
            +
                  m = Doku::DancingLinks::LinkMatrix.from_sets [[1,2], [2,3]]
         
     | 
| 
      
 65 
     | 
    
         
            +
                  m.each_exact_cover { |ec| true.should == false }
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                it "finds the trivial exact cover for the trivial matrix" do
         
     | 
| 
      
 69 
     | 
    
         
            +
                  already_yielded = false
         
     | 
| 
      
 70 
     | 
    
         
            +
                  Doku::DancingLinks::LinkMatrix.new.each_exact_cover do |ec|
         
     | 
| 
      
 71 
     | 
    
         
            +
                    ec.should == []
         
     | 
| 
      
 72 
     | 
    
         
            +
                    already_yielded.should == false
         
     | 
| 
      
 73 
     | 
    
         
            +
                    already_yielded = true
         
     | 
| 
      
 74 
     | 
    
         
            +
                  end
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
              end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
              describe "find_exact_cover_recursive" do
         
     | 
| 
      
 79 
     | 
    
         
            +
                it "find the trivial cover for the trivial matrix" do
         
     | 
| 
      
 80 
     | 
    
         
            +
                  Doku::DancingLinks::LinkMatrix.new.find_exact_cover_recursive.should == []
         
     | 
| 
      
 81 
     | 
    
         
            +
                end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                it "works even if final(k) < max(k)" do
         
     | 
| 
      
 84 
     | 
    
         
            +
                  # This makes sure we call collect on o[0...k] instead of on o.
         
     | 
| 
      
 85 
     | 
    
         
            +
                  m = Doku::DancingLinks::LinkMatrix.from_sets [
         
     | 
| 
      
 86 
     | 
    
         
            +
                      [1,2,     ],
         
     | 
| 
      
 87 
     | 
    
         
            +
                      [  2,3,   ],
         
     | 
| 
      
 88 
     | 
    
         
            +
                      [    3,4, ],
         
     | 
| 
      
 89 
     | 
    
         
            +
                      [      4,5],
         
     | 
| 
      
 90 
     | 
    
         
            +
                      [1,2,3,4,5] ]
         
     | 
| 
      
 91 
     | 
    
         
            +
                  m.find_exact_cover_recursive.sort.should == [[1, 2, 3, 4, 5]]
         
     | 
| 
      
 92 
     | 
    
         
            +
                end
         
     | 
| 
      
 93 
     | 
    
         
            +
              end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
              shared_examples_for "figure 3 from Knuth" do
         
     | 
| 
      
 96 
     | 
    
         
            +
                it "has 7 columns" do
         
     | 
| 
      
 97 
     | 
    
         
            +
                  @m.columns.to_a.size.should == 7
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                it "has the expected columns" do
         
     | 
| 
      
 101 
     | 
    
         
            +
                  @m.columns.collect(&:id).should == @universe
         
     | 
| 
      
 102 
     | 
    
         
            +
                end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                it "has the expected structure" do
         
     | 
| 
      
 105 
     | 
    
         
            +
                  # This test is not exhaustive.
         
     | 
| 
      
 106 
     | 
    
         
            +
                  columns = @m.columns.to_a
         
     | 
| 
      
 107 
     | 
    
         
            +
                  columns[0].down.should_not == columns[0]
         
     | 
| 
      
 108 
     | 
    
         
            +
                  columns[0].up.should_not == columns[0]
         
     | 
| 
      
 109 
     | 
    
         
            +
                  columns[0].nodes.to_a.size.should == 2
         
     | 
| 
      
 110 
     | 
    
         
            +
                  columns[0].up.up.should == columns[0].down
         
     | 
| 
      
 111 
     | 
    
         
            +
                  columns[0].up.should == columns[0].down.down
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                  columns[0].down.right.up.should == columns[3]
         
     | 
| 
      
 114 
     | 
    
         
            +
                  columns[3].down.left.up.should == columns[0]
         
     | 
| 
      
 115 
     | 
    
         
            +
                  columns[0].down.down.right.up.up.should == columns[3]
         
     | 
| 
      
 116 
     | 
    
         
            +
                  columns[0].down.down.right.right.right.down.down.should == columns[3]
         
     | 
| 
      
 117 
     | 
    
         
            +
                  columns[2].up.right.down.should == columns[5]
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                  columns[6].down.down.down.left.up.left.down.left.down.down.should == columns[1]
         
     | 
| 
      
 120 
     | 
    
         
            +
                end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                it "every row has a reference to the column" do
         
     | 
| 
      
 123 
     | 
    
         
            +
                  @m.columns.each do |column|
         
     | 
| 
      
 124 
     | 
    
         
            +
                    column.nodes.each do |node|
         
     | 
| 
      
 125 
     | 
    
         
            +
                      node.column.should == column
         
     | 
| 
      
 126 
     | 
    
         
            +
                    end
         
     | 
| 
      
 127 
     | 
    
         
            +
                  end
         
     | 
| 
      
 128 
     | 
    
         
            +
                end
         
     | 
| 
      
 129 
     | 
    
         
            +
              end
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
              context "given figure 3 from Knuth" do
         
     | 
| 
      
 132 
     | 
    
         
            +
                before do
         
     | 
| 
      
 133 
     | 
    
         
            +
                  @universe = [1,2,3,4,5,6,7]
         
     | 
| 
      
 134 
     | 
    
         
            +
                  @subsets = [[    3,  5,6  ],
         
     | 
| 
      
 135 
     | 
    
         
            +
                              [1,    4,    7],
         
     | 
| 
      
 136 
     | 
    
         
            +
                              [  2,3,    6  ],
         
     | 
| 
      
 137 
     | 
    
         
            +
                              [1,    4      ],
         
     | 
| 
      
 138 
     | 
    
         
            +
                              [  2,        7],
         
     | 
| 
      
 139 
     | 
    
         
            +
                      Set.new([      4,5,  7]),
         
     | 
| 
      
 140 
     | 
    
         
            +
                             ]
         
     | 
| 
      
 141 
     | 
    
         
            +
                  @m = Doku::DancingLinks::LinkMatrix.from_sets @subsets, @universe
         
     | 
| 
      
 142 
     | 
    
         
            +
                end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                it_should_behave_like "figure 3 from Knuth"
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                it "can find an exact cover" do
         
     | 
| 
      
 147 
     | 
    
         
            +
                  result = @m.find_exact_cover
         
     | 
| 
      
 148 
     | 
    
         
            +
                  result.collect(&:sort).sort.should == [[1, 4], [2, 7], [3, 5, 6]]
         
     | 
| 
      
 149 
     | 
    
         
            +
                end
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                # TODO: test this using a matrix that has multiple exact covers
         
     | 
| 
      
 152 
     | 
    
         
            +
                it "can find all exact covers" do
         
     | 
| 
      
 153 
     | 
    
         
            +
                  @m.exact_covers.to_a.sort.should == [[[1, 4], [3, 5, 6], [2,7]]]
         
     | 
| 
      
 154 
     | 
    
         
            +
                end
         
     | 
| 
      
 155 
     | 
    
         
            +
             
     | 
| 
      
 156 
     | 
    
         
            +
                context "after running each_exact_cover" do
         
     | 
| 
      
 157 
     | 
    
         
            +
                  before do
         
     | 
| 
      
 158 
     | 
    
         
            +
                    # If we let each_exact_cover run all the way through, it restores
         
     | 
| 
      
 159 
     | 
    
         
            +
                    # the matrix to its original state.
         
     | 
| 
      
 160 
     | 
    
         
            +
                    @m.each_exact_cover { }
         
     | 
| 
      
 161 
     | 
    
         
            +
                  end
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                  it_should_behave_like "figure 3 from Knuth"
         
     | 
| 
      
 164 
     | 
    
         
            +
                end
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                context "with one row covered" do
         
     | 
| 
      
 167 
     | 
    
         
            +
                  before do
         
     | 
| 
      
 168 
     | 
    
         
            +
                    @m.column(@universe[3]).cover
         
     | 
| 
      
 169 
     | 
    
         
            +
                  end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                  it "has only 6 columns" do
         
     | 
| 
      
 172 
     | 
    
         
            +
                    @m.columns.to_a.size.should == 6
         
     | 
| 
      
 173 
     | 
    
         
            +
                  end
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                  # @m will now look like (minus means a covered element)
         
     | 
| 
      
 176 
     | 
    
         
            +
                  # 0 0 1 - 1 1 0
         
     | 
| 
      
 177 
     | 
    
         
            +
                  # - - - - - - -
         
     | 
| 
      
 178 
     | 
    
         
            +
                  # 0 1 1 - 0 1 0
         
     | 
| 
      
 179 
     | 
    
         
            +
                  # - - - - - - -
         
     | 
| 
      
 180 
     | 
    
         
            +
                  # 0 1 0 - 0 0 1
         
     | 
| 
      
 181 
     | 
    
         
            +
                  # - - - - - - -
         
     | 
| 
      
 182 
     | 
    
         
            +
                  it "has the expected column sizes" do
         
     | 
| 
      
 183 
     | 
    
         
            +
                    @universe.collect { |e| @m.column(e).size }.should == [0, 2, 2, 3, 1, 2, 1]
         
     | 
| 
      
 184 
     | 
    
         
            +
                    @m.columns.collect { |c| c.size }.should == [0, 2, 2, 1, 2, 1]
         
     | 
| 
      
 185 
     | 
    
         
            +
                  end
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                  it "has the expected structure" do
         
     | 
| 
      
 188 
     | 
    
         
            +
                    columns = @m.columns.to_a
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
                    # Column 0 is empty.
         
     | 
| 
      
 191 
     | 
    
         
            +
                    columns[0].down.should == columns[0]
         
     | 
| 
      
 192 
     | 
    
         
            +
                    columns[0].up.should == columns[0]
         
     | 
| 
      
 193 
     | 
    
         
            +
                    columns[0].nodes.to_a.should be_empty
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
      
 195 
     | 
    
         
            +
                    columns[1].down.right.up.up.should == columns[2]
         
     | 
| 
      
 196 
     | 
    
         
            +
                    columns[2].down.right.up.should == columns[3]
         
     | 
| 
      
 197 
     | 
    
         
            +
                    columns[3].up.right.down.down.should == columns[4]
         
     | 
| 
      
 198 
     | 
    
         
            +
                    columns[5].down.right.down.should == columns[1]
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                    columns[5].up.left.up.right.up.right.right.down.down.should == columns[4]
         
     | 
| 
      
 201 
     | 
    
         
            +
                  end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                  context "and then uncovered" do
         
     | 
| 
      
 204 
     | 
    
         
            +
                    before do
         
     | 
| 
      
 205 
     | 
    
         
            +
                      @m.column(@universe[3]).uncover          
         
     | 
| 
      
 206 
     | 
    
         
            +
                    end
         
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
      
 208 
     | 
    
         
            +
                    it_should_behave_like "figure 3 from Knuth"
         
     | 
| 
      
 209 
     | 
    
         
            +
                  end
         
     | 
| 
      
 210 
     | 
    
         
            +
                end
         
     | 
| 
      
 211 
     | 
    
         
            +
              end
         
     | 
| 
      
 212 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,71 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'backports' unless defined? require_relative
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'spec_helper'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            describe Doku::Hexadoku do
         
     | 
| 
      
 5 
     | 
    
         
            +
              it 'has the upper-case glyph characters' do
         
     | 
| 
      
 6 
     | 
    
         
            +
                Doku::Hexadoku.glyph_chars.should == %w{0 1 2 3 4 5 6 7 8 9 A B C D E F}
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              it 'rejects invalid glyphs in glyph_char' do
         
     | 
| 
      
 10 
     | 
    
         
            +
                lambda { Doku::Hexadoku.glyph_char(19) }.should raise_error ArgumentError, "Invalid glyph 19."
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              it 'rejects invalid characters in glyph_parse' do
         
     | 
| 
      
 14 
     | 
    
         
            +
                lambda { Doku::Hexadoku.glyph_parse('H') }.should raise_error ArgumentError, "Invalid character 'H'."
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              it 'accepts both cases in glyph_parse' do
         
     | 
| 
      
 18 
     | 
    
         
            +
                Doku::Hexadoku.glyph_parse('d').should == 0xD
         
     | 
| 
      
 19 
     | 
    
         
            +
                Doku::Hexadoku.glyph_parse('D').should == 0xD
         
     | 
| 
      
 20 
     | 
    
         
            +
              end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
              it 'rejects invalid characters in the constructor' do
         
     | 
| 
      
 23 
     | 
    
         
            +
                grid_string = <<END
         
     | 
| 
      
 24 
     | 
    
         
            +
            ABCD|....|....|....
         
     | 
| 
      
 25 
     | 
    
         
            +
            ....|abcd|....|....
         
     | 
| 
      
 26 
     | 
    
         
            +
            X...|....|....|....
         
     | 
| 
      
 27 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 28 
     | 
    
         
            +
            ----+----+----+----
         
     | 
| 
      
 29 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 30 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 31 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 32 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 33 
     | 
    
         
            +
            ----+----+----+----
         
     | 
| 
      
 34 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 35 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 36 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 37 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 38 
     | 
    
         
            +
            ----+----+----+----
         
     | 
| 
      
 39 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 40 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 41 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 42 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 43 
     | 
    
         
            +
            END
         
     | 
| 
      
 44 
     | 
    
         
            +
                lambda { Doku::Hexadoku.new(grid_string) }.should raise_error ArgumentError, "Line 2, character 0: Invalid character 'X'.  Expected period (.) or glyph (0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F)."
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
              it 'accepts lower-case or upper-case in the consturctor' do
         
     | 
| 
      
 48 
     | 
    
         
            +
                grid_string = <<END.strip
         
     | 
| 
      
 49 
     | 
    
         
            +
            ABCD|....|....|....
         
     | 
| 
      
 50 
     | 
    
         
            +
            ....|abcd|....|....
         
     | 
| 
      
 51 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 52 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 53 
     | 
    
         
            +
            ----+----+----+----
         
     | 
| 
      
 54 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 55 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 56 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 57 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 58 
     | 
    
         
            +
            ----+----+----+----
         
     | 
| 
      
 59 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 60 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 61 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 62 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 63 
     | 
    
         
            +
            ----+----+----+----
         
     | 
| 
      
 64 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 65 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 66 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 67 
     | 
    
         
            +
            ....|....|....|....
         
     | 
| 
      
 68 
     | 
    
         
            +
            END
         
     | 
| 
      
 69 
     | 
    
         
            +
                Doku::Hexadoku.new(grid_string).to_s.should == grid_string.upcase
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,38 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'backports' unless defined? require_relative
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'spec_helper'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            describe Doku::Hexamurai do
         
     | 
| 
      
 5 
     | 
    
         
            +
              it 'has 768 squares' do
         
     | 
| 
      
 6 
     | 
    
         
            +
                Doku::Hexamurai.squares.size.should == 768
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              it 'has 16 squares in the first row' do
         
     | 
| 
      
 10 
     | 
    
         
            +
                first_row = Doku::Hexamurai.squares_matching :y => 0
         
     | 
| 
      
 11 
     | 
    
         
            +
                first_row.size.should == 16
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              it 'has 16 squares in the first column of the top hexadoku' do
         
     | 
| 
      
 15 
     | 
    
         
            +
                column = Doku::Hexamurai.squares_matching :x => 8, :y => (0..15)
         
     | 
| 
      
 16 
     | 
    
         
            +
                column.size.should == 16
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              it 'has the right number of groups' do
         
     | 
| 
      
 20 
     | 
    
         
            +
                # A hexadoku has 3*16 groups (16 columns, 16 rows, 16 boxes)
         
     | 
| 
      
 21 
     | 
    
         
            +
                # There are 5 hexadokus.
         
     | 
| 
      
 22 
     | 
    
         
            +
                # The reckoning above counted the 16 rows and 16 columns of the 
         
     | 
| 
      
 23 
     | 
    
         
            +
                # center hexadoku twice (-32), and counted the 16 boxes of the
         
     | 
| 
      
 24 
     | 
    
         
            +
                # center hexaodoku thrice (-32), so subtract 64.
         
     | 
| 
      
 25 
     | 
    
         
            +
                # There are 2*16 inferred groups (16 columns, 16 rows).
         
     | 
| 
      
 26 
     | 
    
         
            +
                Doku::Hexamurai.groups.size.should == (5*3*16 - 64 + 2*16)
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
              it 'has valid line and char numbers' do
         
     | 
| 
      
 30 
     | 
    
         
            +
                lines = Doku::Hexamurai.template.split("\n")
         
     | 
| 
      
 31 
     | 
    
         
            +
                Doku::Hexamurai.squares.each do |square|
         
     | 
| 
      
 32 
     | 
    
         
            +
                  line_number, char_number = Doku::Hexamurai.coordinates_in_grid_string(square)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  line = lines[line_number]
         
     | 
| 
      
 34 
     | 
    
         
            +
                  line.should_not be_nil
         
     | 
| 
      
 35 
     | 
    
         
            +
                  line.size.should > char_number
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
            end
         
     |