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.
@@ -0,0 +1,241 @@
1
+ module Doku
2
+ # This module is meant to be included in subclasses of {Puzzle} where the
3
+ # squares are arranged in a grid.
4
+ #
5
+ # The {Puzzle} class contains only very abstract code, dealing with
6
+ # the abstract concepts of {Puzzle.squares squares},
7
+ # {Puzzle.glyphs glyphs}, and {Puzzle.groups groups}.
8
+ # The {Puzzle} class can represent a wide variety of puzzles, including
9
+ # three-dimensional puzzles or puzzles that don't have any particular
10
+ # spatial arrangement.
11
+ # However, most of the puzzles we are interested in studying are arranged
12
+ # in a grid, and this module contains code that makes it easy to define
13
+ # and work with those puzzles.
14
+ #
15
+ # Every {Puzzle.squares square} in a {PuzzleOnGrid} puzzle is a
16
+ # {SquareOnGrid} with x and y coordinates to represent its position on the grid.
17
+ # The x coordinate is 0 for the first row, 1 for the second row, etc.
18
+ # The y coordinate is 0 for the first column, 1 for the second column, etc.
19
+ #
20
+ # See the {ClassMethods} module for the class methods that are added to each
21
+ # class that includes PuzzleOnGrid.
22
+ module PuzzleOnGrid
23
+ # These are the separators that can appear in the {ClassMethods#template template}
24
+ # string or the string argument to the {#initialize} to make it more readable.
25
+ Separators = ['-', '+', '|']
26
+
27
+ private
28
+ def self.included(klass)
29
+ klass.extend ClassMethods
30
+ end
31
+
32
+ def self.parse_grid_string(string)
33
+ y = 0
34
+ string.lines.each_with_index do |line, line_number|
35
+ line.chomp!
36
+ next if (line.chars.to_a - Separators).empty?
37
+
38
+ x = 0
39
+ line.chars.each_with_index do |char, char_number|
40
+ next if Separators.include?(char)
41
+
42
+ yield char, char_number, line_number, x, y
43
+
44
+ x += 1
45
+ end
46
+ y += 1
47
+ end
48
+ end
49
+ public
50
+
51
+ module ClassMethods
52
+ # @return (Array) Array with the line number and character number
53
+ # of the given square in the {#template} string.
54
+ def coordinates_in_grid_string(square)
55
+ [@line_number[square.y], @char_number[square.x]]
56
+ end
57
+
58
+ # This is a multi-line string that defines all the squares in the puzzle and the
59
+ # {Separators} to use to make the puzzle more readable in {#to_grid_string}.
60
+ # A period character ('.') represents a square.
61
+ # This is defined in the class by using the {#has_template} method.
62
+ # @return (String) The template string from the class definition.
63
+ attr_reader :template
64
+
65
+ # This is an array of characters (strings of length 1) which are used in
66
+ # multi-line grid strings to represent {Puzzle.glyphs glyphs} in a square.
67
+ # The order of this array must correspond to the order of the {Puzzle.glyphs glyphs}.
68
+ # For example, for {Sudoku}, this is
69
+ # ['1', '2', '3', '4', '5', '6', '7', '8', '9'].
70
+ # This is defined in the class definition using the {#has_glyph_chars} method.
71
+ # @return (Array) Array of characters.
72
+ attr_reader :glyph_chars
73
+
74
+ # Selects all the squares in this puzzle which match
75
+ # the specified condition.
76
+ # @param conditions (Hash) See {SquareOnGrid#matches?}.
77
+ # @return (Array) Array of squares.
78
+ def squares_matching(conditions)
79
+ squares.select { |sq| sq.matches? conditions }
80
+ end
81
+
82
+ # Selects all the squares in this puzzle that are within a certain
83
+ # square area on the grid.
84
+ # @param leftmost_x (Integer) The x coordinate of the left-most column in the square area.
85
+ # @param top_y (Integer) The y coordinate of the top-most row in the square area.
86
+ # @param size (Integer) The width and height of the square.
87
+ # @return (Array) Array of squares.
88
+ def square_group(leftmost_x, top_y, size=Math.sqrt(glyphs.size))
89
+ squares_matching :x => leftmost_x...(leftmost_x+size), :y => top_y...(top_y+size)
90
+ end
91
+
92
+ # Selects some squares with a specific y coordinate.
93
+ # @param leftmost_x (Integer) The x coordinate of the left-most square.
94
+ # @param size (Integer) The width of the row.
95
+ # @return (Array) Array of squares.
96
+ def row(y, leftmost_x=0, size=glyphs.size)
97
+ squares_matching :x => leftmost_x...(leftmost_x+size), :y => y
98
+ end
99
+
100
+ # Selects some squares with a specific x coordinate.
101
+ # @param top_y (Integer) The y coordinate of the top-most square.
102
+ # @param size (Integer) The height of the column.
103
+ # @return (Array) Array of squares.
104
+ def column(x, top_y=0, size=glyphs.size)
105
+ squares_matching :x => x, :y => top_y...(top_y+size)
106
+ end
107
+
108
+ # @return (String) The character that represents the given {Puzzle.glyphs glyph}.
109
+ def glyph_char(glyph)
110
+ glyph_chars.at(glyphs.index(glyph) || (raise ArgumentError, "Invalid glyph #{glyph}."))
111
+ end
112
+
113
+ # @return (Object) The {Puzzle.glyphs glyph} represented by the given character.
114
+ def glyph_parse(char)
115
+ glyphs.at(glyph_chars.index(char.upcase) || (raise ArgumentError, "Invalid character '#{char}'."))
116
+ end
117
+
118
+ private
119
+
120
+ # This is called in the class definition to define the {#template} string,
121
+ # thus defining which squares are in the puzzle.
122
+ def has_template(string)
123
+ @template = string.freeze
124
+ define_squares_from_template
125
+ end
126
+
127
+ # This is called in the class definition to define the {#glyph_chars}.
128
+ # This function converts the characters to upper case to make it
129
+ # easier to parse strings provided by the user in a case insensitive way.
130
+ def has_glyph_chars(chars)
131
+ @glyph_chars = chars.collect &:upcase
132
+ end
133
+
134
+ def define_square_on_grid(x, y, line_num, char_num)
135
+ define_square SquareOnGrid.new x, y
136
+
137
+ @line_number ||= []
138
+ @char_number ||= []
139
+ @line_number[y] = line_num
140
+ @char_number[x] = char_num
141
+ end
142
+
143
+ # Using the {#template} provided for the puzzle, this method
144
+ # defines objects to represent each of the different squares.
145
+ def define_squares_from_template
146
+ PuzzleOnGrid.parse_grid_string(template) do |char, char_number, line_number, x, y|
147
+ if char == '.'
148
+ define_square_on_grid x, y, line_number, char_number
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ # Creates a new instance of the puzzle.
155
+ # @param grid_string (String) A multi-line string defining which glyphs are
156
+ # currently written in which squares. You can use {Separators}
157
+ # to make this string more readable.
158
+ # This parameter is provided to make it easy to manually type in puzzles.
159
+ # If you are not typing the puzzle in, you should probably use {#set} instead.
160
+ # A good way to type this string is to copy the class's
161
+ # {ClassMethods#template template} and replace some of the periods with
162
+ # {ClassMethods#glyph_chars glyph_chars} (e.g. replace a '.' with '3').
163
+ def initialize(grid_string=nil)
164
+ super()
165
+ parse_initial_grid_string grid_string if grid_string
166
+ end
167
+
168
+ # @return (String) A multi-line string representation of the puzzle suitable
169
+ # for displaying, based on the {ClassMethods#template template}.
170
+ def to_grid_string
171
+ lines = self.class.template.split("\n")
172
+ each do |square, glyph|
173
+ line_number, char_number = self.class.coordinates_in_grid_string square
174
+ lines[line_number][char_number] = self.class.glyph_char glyph
175
+ end
176
+ lines.join "\n"
177
+ end
178
+
179
+ # Assigns a glyph to a square.
180
+ # This will modify the state of the puzzle, overwriting the previous
181
+ # glyph assignment.
182
+ # @param x (Integer) The x coordinate of the square.
183
+ # @param y (Integer) The y coordinate of the square.
184
+ # @param glyph The {Puzzle.glyphs glyph} to assign to that square, or nil.
185
+ def set(x, y, glyph)
186
+ self[SquareOnGrid.new(x, y)] = glyph
187
+ end
188
+
189
+ # Gets the glyph assignment for a given square.
190
+ # @param x (Integer) The x coordinate of the square.
191
+ # @param x (Integer) The y coordinate of the square.
192
+ # @return (Object) The {Puzzle.glyphs glyph} assigned to that square, or nil if
193
+ # none is assigned.
194
+ def get(x, y)
195
+ self[SquareOnGrid.new(x, y)]
196
+ end
197
+
198
+ # @return (String) The same as {#to_grid_string}.
199
+ def to_s
200
+ to_grid_string
201
+ end
202
+
203
+ private
204
+ def parse_initial_grid_string(grid_string)
205
+ PuzzleOnGrid.parse_grid_string(grid_string) do |original_char, char_number, line_number, x, y|
206
+ square = SquareOnGrid.new(x, y)
207
+
208
+ char = original_char.upcase
209
+
210
+ if !squares.include?(square)
211
+ raise "Line #{line_number}, character #{char_number}: Invalid character. Expected space." if char != ' '
212
+ elsif char == '.'
213
+ # No glyph specified for this square.
214
+ elsif self.class.glyph_chars.include?(char)
215
+ self[square] = self.class.glyph_parse(char)
216
+ else
217
+ raise ArgumentError, "Line #{line_number}, character #{char_number}: Invalid character '#{original_char}'. Expected period (.) or glyph (#{self.class.glyph_chars.join ','})."
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ # Represents a square on a grid.
224
+ # Any two instances with the same x and y coordinates are considered
225
+ # to be equal, which makes it convenient to use SquareOnGrid instances
226
+ # as a key in a hash table.
227
+ # This class is used by the {PuzzleOnGrid} module to represent the
228
+ # {Puzzle.squares squares} in grid-based {Puzzle}s.
229
+ class SquareOnGrid < Struct.new(:x, :y)
230
+ # @param conditions (Hash) Should be a hash where the keys are
231
+ # :x or :y and the values are either Integers or Integer ranges.
232
+ # @return (Boolean) True if the square matches all the conditions.
233
+ def matches?(conditions)
234
+ conditions.all? { |property, values| values === send(property) }
235
+ end
236
+
237
+ def to_s
238
+ "Square(#{x}, #{y})"
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,56 @@
1
+ require 'backports' unless defined?(require_relative)
2
+ require_relative 'puzzle'
3
+ require_relative 'grid'
4
+
5
+ module Doku
6
+ # This class represents Hexadoku, otherwise known as 16x16 {Sudoku}.
7
+ # This is a more complex version of Sudoku with a 16x16 grid, using
8
+ # hex digits 0 through F.
9
+ # Each instance of this class represents a particular arrangement of
10
+ # numbers written in the boxes.
11
+ class Hexadoku < Puzzle
12
+ include PuzzleOnGrid
13
+ extend PuzzleOnGrid::ClassMethods # improves generated docs
14
+
15
+ has_glyphs (0..15).to_a
16
+ has_glyph_chars glyphs.collect { |s| '%x'%[s] }
17
+
18
+ has_template <<END
19
+ ....|....|....|....
20
+ ....|....|....|....
21
+ ....|....|....|....
22
+ ....|....|....|....
23
+ ----+----+----+----
24
+ ....|....|....|....
25
+ ....|....|....|....
26
+ ....|....|....|....
27
+ ....|....|....|....
28
+ ----+----+----+----
29
+ ....|....|....|....
30
+ ....|....|....|....
31
+ ....|....|....|....
32
+ ....|....|....|....
33
+ ----+----+----+----
34
+ ....|....|....|....
35
+ ....|....|....|....
36
+ ....|....|....|....
37
+ ....|....|....|....
38
+ END
39
+
40
+ # Define row and column groups.
41
+ 0.upto(15).each do |n|
42
+ define_group row(n)
43
+ define_group column(n)
44
+ end
45
+
46
+ # Define the 4x4 groups.
47
+ 0.step(12,4).each do |x|
48
+ 0.step(12,4).each do |y|
49
+ define_group square_group(x, y)
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
@@ -0,0 +1,95 @@
1
+ require 'backports' unless defined?(require_relative)
2
+ require_relative 'puzzle'
3
+ require_relative 'grid'
4
+
5
+ module Doku
6
+ # This class represents the Hexamurai, a puzzle that consists of
7
+ # five {Hexadoku} puzzles superimposed on eachother.
8
+ # The {http://www.elektor.com/magazines/2011/july-047-august/hexamurai.1852786.lynkx Hexamurai puzzle appeared in the July/August issue of Elektor magazine}.
9
+ # Each instance of this class represents a particular arrangement of
10
+ # numbers written in the boxes.
11
+ class Hexamurai < Puzzle
12
+ include PuzzleOnGrid
13
+ extend Doku::PuzzleOnGrid::ClassMethods # improves generated docs
14
+
15
+ has_glyphs (0..15).to_a
16
+ has_glyph_chars glyphs.collect { |s| "%X"%[s] }
17
+
18
+ has_template <<END
19
+ |........|........|
20
+ |........|........|
21
+ |........|........|
22
+ |........|........|
23
+ |........|........|
24
+ |........|........|
25
+ |........|........|
26
+ |........|........|
27
+ --------+--------+--------+--------
28
+ ........|........|........|........
29
+ ........|........|........|........
30
+ ........|........|........|........
31
+ ........|........|........|........
32
+ ........|........|........|........
33
+ ........|........|........|........
34
+ ........|........|........|........
35
+ ........|........|........|........
36
+ --------+--------+--------+--------
37
+ ........|........|........|........
38
+ ........|........|........|........
39
+ ........|........|........|........
40
+ ........|........|........|........
41
+ ........|........|........|........
42
+ ........|........|........|........
43
+ ........|........|........|........
44
+ ........|........|........|........
45
+ --------+--------+--------+--------
46
+ |........|........|
47
+ |........|........|
48
+ |........|........|
49
+ |........|........|
50
+ |........|........|
51
+ |........|........|
52
+ |........|........|
53
+ |........|........|
54
+ END
55
+
56
+ # A track of 32 rows that runs down the center, and
57
+ # a track of 32 columns that runs through the center.
58
+ 0.upto(31) do |n|
59
+ define_group row(n, 8)
60
+ define_group column(n, 8)
61
+ end
62
+
63
+ # The columns and rows that weren't included in the
64
+ # defintitions above.
65
+ 0.upto(15) do |n|
66
+ define_group row(n+8, 0) # for the left Hexadoku
67
+ define_group row(n+8, 16) # for the right Hexadoku
68
+ define_group column(n+8, 0) # for the top Hexadoku
69
+ define_group column(n+8, 16) # for the bottom Hexadoku
70
+ end
71
+
72
+ # A track of 32 4x4 groups that runs from top-to-bottom
73
+ # and a track of 32 4x4 groups that runs from left-to-right.
74
+ # These two tracks intersect, but that's ok because
75
+ # define_group checks for uniqueness of groups.
76
+ 0.step(28, 4) do |n|
77
+ 8.step(20, 4) do |m|
78
+ define_group square_group(n, m)
79
+ define_group square_group(m, n)
80
+ end
81
+ end
82
+
83
+ # Deduced groups!
84
+ # These groups are not explicitly stated in the puzzle, but
85
+ # we can use logic to deduce that they are groups (no
86
+ # glyph can appear twice in them).
87
+ # TODO: see if these deduced groups actually speed up algorithm!
88
+ 8.upto(23) do |n|
89
+ define_group row(n, 0, 8) + row(n, 24, 8)
90
+ define_group column(n, 0, 8) + column(n, 24, 8)
91
+ end
92
+
93
+ end
94
+ end
95
+
@@ -0,0 +1,250 @@
1
+ require 'set'
2
+ require 'backports' unless defined?(require_relative)
3
+ require_relative 'solver'
4
+
5
+ # The Doku module contains everything defined by the doku gem, including
6
+ # classes for puzzles, and a general-purpose implementation of the
7
+ # {DancingLinks} algorithm for finding exact covers.
8
+ module Doku
9
+
10
+ # @abstract Use the {Sudoku}, {Hexadoku}, or {Hexamurai}
11
+ # subclasses or make a subclass to represent your own type
12
+ # of Sudoku-like puzzle.
13
+ #
14
+ # This in abstract class for creating classes that represent
15
+ # Sudoku-like puzzles.
16
+ #
17
+ # Every subclass of {Doku::Puzzle} represents a Sudoku-like puzzle consisting
18
+ # of a set of glyphs, a set of squares, and a set of groups of squares.
19
+ # For example, the {Doku::Sudoku} subclass represents the famous 9x9 puzzle,
20
+ # Sudoku.
21
+ #
22
+ # Every instance of a subclass of Puzzle represents a particular state
23
+ # of that type of puzzle, i.e. a record of which glyph is assigned
24
+ # to each square.
25
+ class Puzzle
26
+ include SolvableWithDancingLinks
27
+
28
+ # A hash that associates squares to glyphs, representing
29
+ # the arrangement of glyphs in the puzzle.
30
+ attr_reader :glyph_state
31
+
32
+ # Creates a new instance of the puzzle.
33
+ #
34
+ # @param [Hash] glyph_state The state of the puzzle, represented as a hash
35
+ # where the keys are squares and the values are nil or glyphs in the context
36
+ # of this puzzle class. For example, this represents what numbers have been
37
+ # written in the boxes of a {Sudoku} puzzle.
38
+ def initialize(glyph_state = {})
39
+ @glyph_state = {}
40
+ # We initialize glyph_state this way so that the data gets validated.
41
+ glyph_state.each { |square, glyph| self[square] = glyph }
42
+ end
43
+
44
+ class << self
45
+
46
+ # Returns an array of all the valid glyphs for this class of puzzle.
47
+ # A glyph can be any type of Ruby object, and it is meant to
48
+ # represent a symbol which can be drawn inside a square in a
49
+ # Sudoku-like puzzle.
50
+ #
51
+ # For example, the glyphs for {Sudoku} are the Ruby integers 1, 2, 3,
52
+ # 4, 5, 6, 7, 8, and 9.
53
+ #
54
+ # The glyphs, squares, and groups, are defined at the class level, in the subclasses of Doku::Puzzle.
55
+ # @return [Array] Array of objects representing glyphs.
56
+ attr_reader :glyphs
57
+
58
+ # Returns an array of all the valid squares in this class of puzzle.
59
+ # A square can be any type of Ruby object, and it is meant to
60
+ # represent a square in which glyphs are drawn in a Sudoku-like puzzle.
61
+ #
62
+ # For example, there are 81 squares defined in the {Sudoku} class,
63
+ # one for each square on the 9x9 Sudoku grid.
64
+ #
65
+ # The glyphs, squares, and groups, are defined at the class level, in the subclasses of Doku::Puzzle.
66
+ # @return [Array] Array of objects representing squares.
67
+ attr_reader :squares
68
+
69
+ # Returns an array of all the groups for this class of puzzle.
70
+ # A group should be a Set object that contains some squares.
71
+ # A group represents a constraint on solutions to the puzzle:
72
+ # every glyph must appear exactly once in every group.
73
+ #
74
+ # For example, the groups of the {Sudoku} class represent the
75
+ # nie columns, nine rows, and nine 3x3 boxes of Sudoku.
76
+ #
77
+ # The glyphs, squares, and groups, are defined at the class level, in the subclasses of Doku::Puzzle.
78
+ # @return [Array] Array of glyphs.
79
+ attr_reader :groups
80
+ end
81
+
82
+ # Shortcut for calling the {Puzzle.glyphs} class method.
83
+ # @return [Array] Array of glyphs.
84
+ def glyphs
85
+ self.class.glyphs
86
+ end
87
+
88
+ # Shortcut for calling the {Puzzle.squares} class method.
89
+ # @return [Array] Array of squares.
90
+ def squares
91
+ self.class.squares
92
+ end
93
+
94
+ # Shortcut for calling the {Puzzle.groups} class method.
95
+ # @return [Array] Array of groups.
96
+ def groups
97
+ self.class.groups
98
+ end
99
+
100
+ # Gets the glyph assigned to the given square.
101
+ # @param square Must be one of the {Puzzle.squares} for this puzzle.
102
+ # @return The glyph that is assigned to the given square
103
+ # (one of the {Puzzle.glyphs} defined for this puzzle),
104
+ # or nil if no glyph is assigned.
105
+ def [](square)
106
+ raise IndexError, "Square not found in #{self.class.name}: #{square}." if !squares.include?(square)
107
+ @glyph_state[square]
108
+ end
109
+
110
+ # Sets the glyph assigned to the given square.
111
+ # @param square Must be one of the {Puzzle.squares} for this puzzle.
112
+ # @param glyph Must be one of the {Puzzle.glyphs} for this puzzle, or nil.
113
+ def []=(square, glyph)
114
+ raise IndexError, "Square not found in #{self.class}: #{square}." if !squares.include?(square)
115
+ raise ArgumentError, "Value must be a glyph in this puzzle or nil." if !glyph.nil? && !glyphs.include?(glyph)
116
+
117
+ # Do NOT store nils as values in the hash, because we
118
+ # don't want them to affect equality comparisons.
119
+ if glyph == nil
120
+ @glyph_state.delete square
121
+ else
122
+ @glyph_state[square] = glyph
123
+ end
124
+ end
125
+
126
+ # This method allows you to iterate over every square that has a
127
+ # glyph assigned to it.
128
+ #
129
+ # @yield [square, glyph]
130
+ # @yieldparam square A square that has a glyph assigned to it.
131
+ # @yieldparam glyph The glyph that is assigned to the square.
132
+ def each(&block)
133
+ @glyph_state.each(&block)
134
+ end
135
+
136
+ # @return [Fixnum] Returns a hash code based on the glyph assignments.
137
+ def hash
138
+ @glyph_state.hash
139
+ end
140
+
141
+ # Two puzzles are equal if they have the same class and glyph assignments.
142
+ # @return [Boolean] True if the two puzzles are equal.
143
+ def eql?(puzzle)
144
+ self.class == puzzle.class and glyph_state == puzzle.glyph_state
145
+ end
146
+
147
+ # Same as {#eql?}.
148
+ def == (puzzle)
149
+ eql? puzzle
150
+ end
151
+
152
+ # Returns true if the puzzle's glyphs assignments are a subset
153
+ # of the given puzzle's glyph assignments and the two puzzles are
154
+ # the same class.
155
+ #
156
+ # Every puzzle is a subset of itself.
157
+ #
158
+ # For example, if you find a Sudoku puzzle and start working on it,
159
+ # you have changed the original puzzle into a new puzzle.
160
+ # The original puzzle will be a subset of the new puzzle,
161
+ # assuming you didn't erase any numbers.
162
+ #
163
+ # @return [Boolean]
164
+ def subset?(puzzle)
165
+ self.class == puzzle.class and glyph_assignment_subset?(puzzle)
166
+ end
167
+
168
+ # Returns true if this puzzle is completely filled in,
169
+ # which means every square has a glyph assigned to it.
170
+ # For example, a Sudoku puzzle is considered to be filled after
171
+ # you have written a number in every box, regardless of whether
172
+ # the numbers obey the rules of Sudoku or not.
173
+ # See also {#solution?} and {#solution_for?}.
174
+ #
175
+ # @return [Boolean]
176
+ def filled?
177
+ squares.size == glyph_state.keys.size
178
+ end
179
+
180
+ # Returns true if this puzzle follows the rules.
181
+ # A puzzle is valid if no glyph appears twice in any group.
182
+ # For example, a {Sudoku} puzzle would be invalid if you
183
+ # wrote a "3" twice in the same column.
184
+ #
185
+ # @return [Boolean]
186
+ def valid?
187
+ groups.all? do |group|
188
+ glyphs = group.collect { |square| self[square] } - [nil]
189
+ glyphs.uniq.size == glyphs.size
190
+ end
191
+ end
192
+
193
+ # Returns true if the puzzle is {#filled?} and {#valid?}.
194
+ # @return [Boolean]
195
+ def solution?
196
+ filled? and valid?
197
+ end
198
+
199
+ # Returns true if the puzzle is valid solution for the given puzzle.
200
+ #
201
+ # @return [Boolean]
202
+ def solution_for?(puzzle)
203
+ solution? and puzzle.subset?(self)
204
+ end
205
+
206
+ private
207
+
208
+ # This is called when the puzzle is duped or cloned.
209
+ def initialize_copy(source)
210
+ @glyph_state = @glyph_state.dup
211
+ end
212
+
213
+ # This should be called inside the definition of a Puzzle subclass
214
+ # to define what glyphs the puzzle has.
215
+ def self.has_glyphs(glyphs)
216
+ @glyphs = glyphs
217
+ end
218
+
219
+ # This should be called inside the definition of a Puzzle subclass
220
+ # to define what squares the puzzle has.
221
+ # This method defines one square at a time. See also #has_squares.
222
+ def self.define_square(square)
223
+ raise ArgumentError, "square should not be nil" if square.nil?
224
+ @squares ||= []
225
+ @squares << square
226
+ end
227
+
228
+ # This should be called inside the definition of a Puzzle subclass
229
+ # to define what squares the puzzle has.
230
+ # This method defines all the squares at once. See also #define_square.
231
+ def self.has_squares(squares)
232
+ raise ArgumentError, "list of squares should not contain nil" if squares.include? nil
233
+ @squares = squares.uniq
234
+ end
235
+
236
+ def glyph_assignment_subset?(puzzle)
237
+ glyph_state.all? do |square, glyph|
238
+ glyph == puzzle[square]
239
+ end
240
+ end
241
+
242
+ def self.define_group(squares)
243
+ group = Set.new(squares)
244
+ raise ArgumentError, "Expected groups to be of size #{glyphs.size} but got one of size #{group.size}. squares = #{group.inspect}" if group.size != glyphs.size
245
+ @groups ||= []
246
+ @groups << group unless @groups.include?(group)
247
+ end
248
+
249
+ end
250
+ end