class Doku::Puzzle

@abstract Use the {Sudoku}, {Hexadoku}, or {Hexamurai}

subclasses or make a subclass to represent your own type
of Sudoku-like puzzle.

This in abstract class for creating classes that represent Sudoku-like puzzles.

Every subclass of {Doku::Puzzle} represents a Sudoku-like puzzle consisting of a set of glyphs, a set of squares, and a set of groups of squares. For example, the {Doku::Sudoku} subclass represents the famous 9x9 puzzle, Sudoku.

Every instance of a subclass of Puzzle represents a particular state of that type of puzzle, i.e. a record of which glyph is assigned to each square.

Attributes

glyphs[R]

Returns an array of all the valid glyphs for this class of puzzle. A glyph can be any type of Ruby object, and it is meant to represent a symbol which can be drawn inside a square in a Sudoku-like puzzle.

For example, the glyphs for {Sudoku} are the Ruby integers 1, 2, 3, 4, 5, 6, 7, 8, and 9.

The glyphs, squares, and groups, are defined at the class level, in the subclasses of Doku::Puzzle. @return [Array] Array of objects representing glyphs.

groups[R]

Returns an array of all the groups for this class of puzzle. A group should be a Set object that contains some squares. A group represents a constraint on solutions to the puzzle: every glyph must appear exactly once in every group.

For example, the groups of the {Sudoku} class represent the nie columns, nine rows, and nine 3x3 boxes of Sudoku.

The glyphs, squares, and groups, are defined at the class level, in the subclasses of Doku::Puzzle. @return [Array] Array of glyphs.

squares[R]

Returns an array of all the valid squares in this class of puzzle. A square can be any type of Ruby object, and it is meant to represent a square in which glyphs are drawn in a Sudoku-like puzzle.

For example, there are 81 squares defined in the {Sudoku} class, one for each square on the 9x9 Sudoku grid.

The glyphs, squares, and groups, are defined at the class level, in the subclasses of Doku::Puzzle. @return [Array] Array of objects representing squares.

glyph_state[R]

A hash that associates squares to glyphs, representing the arrangement of glyphs in the puzzle.

Public Class Methods

new(glyph_state = {}) click to toggle source

Creates a new instance of the puzzle.

@param [Hash] glyph_state The state of the puzzle, represented as a hash where the keys are squares and the values are nil or glyphs in the context of this puzzle class. For example, this represents what numbers have been written in the boxes of a {Sudoku} puzzle.

# File lib/doku/puzzle.rb, line 38
def initialize(glyph_state = {})
  @glyph_state = {}
  # We initialize glyph_state this way so that the data gets validated.
  glyph_state.each { |square, glyph| self[square] = glyph }
end

Private Class Methods

define_group(squares) click to toggle source
# File lib/doku/puzzle.rb, line 242
def self.define_group(squares)
  group = Set.new(squares)
  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 
  @groups ||= []
  @groups << group unless @groups.include?(group)
end
define_square(square) click to toggle source

This should be called inside the definition of a Puzzle subclass to define what squares the puzzle has. This method defines one square at a time. See also has_squares.

# File lib/doku/puzzle.rb, line 222
def self.define_square(square)
  raise ArgumentError, "square should not be nil" if square.nil?
  @squares ||= []
  @squares << square
end
has_glyphs(glyphs) click to toggle source

This should be called inside the definition of a Puzzle subclass to define what glyphs the puzzle has.

# File lib/doku/puzzle.rb, line 215
def self.has_glyphs(glyphs)
  @glyphs = glyphs
end
has_squares(squares) click to toggle source

This should be called inside the definition of a Puzzle subclass to define what squares the puzzle has. This method defines all the squares at once. See also define_square.

# File lib/doku/puzzle.rb, line 231
def self.has_squares(squares)
  raise ArgumentError, "list of squares should not contain nil" if squares.include? nil
  @squares = squares.uniq
end

Public Instance Methods

==(puzzle) click to toggle source

Same as {#eql?}.

# File lib/doku/puzzle.rb, line 148
def == (puzzle)
  eql? puzzle
end
[](square) click to toggle source

Gets the glyph assigned to the given square. @param square Must be one of the {Puzzle.squares} for this puzzle. @return The glyph that is assigned to the given square

(one of the {Puzzle.glyphs} defined for this puzzle),
or nil if no glyph is assigned.
# File lib/doku/puzzle.rb, line 105
def [](square)
  raise IndexError, "Square not found in #{self.class.name}: #{square}." if !squares.include?(square)
  @glyph_state[square]
end
[]=(square, glyph) click to toggle source

Sets the glyph assigned to the given square. @param square Must be one of the {Puzzle.squares} for this puzzle. @param glyph Must be one of the {Puzzle.glyphs} for this puzzle, or nil.

# File lib/doku/puzzle.rb, line 113
def []=(square, glyph)
  raise IndexError, "Square not found in #{self.class}: #{square}." if !squares.include?(square)
  raise ArgumentError, "Value must be a glyph in this puzzle or nil." if !glyph.nil? && !glyphs.include?(glyph)

  # Do NOT store nils as values in the hash, because we
  # don't want them to affect equality comparisons.
  if glyph == nil
    @glyph_state.delete square
  else
    @glyph_state[square] = glyph
  end
end
each(&block) click to toggle source

This method allows you to iterate over every square that has a glyph assigned to it.

@yield [square, glyph] @yieldparam square A square that has a glyph assigned to it. @yieldparam glyph The glyph that is assigned to the square.

# File lib/doku/puzzle.rb, line 132
def each(&block)
  @glyph_state.each(&block)
end
eql?(puzzle) click to toggle source

Two puzzles are equal if they have the same class and glyph assignments. @return [Boolean] True if the two puzzles are equal.

# File lib/doku/puzzle.rb, line 143
def eql?(puzzle)
  self.class == puzzle.class and glyph_state == puzzle.glyph_state
end
filled?() click to toggle source

Returns true if this puzzle is completely filled in, which means every square has a glyph assigned to it. For example, a Sudoku puzzle is considered to be filled after you have written a number in every box, regardless of whether the numbers obey the rules of Sudoku or not. See also {#solution?} and {#solution_for?}.

@return [Boolean]

# File lib/doku/puzzle.rb, line 176
def filled?
  squares.size == glyph_state.keys.size
end
glyphs() click to toggle source

Shortcut for calling the {Puzzle.glyphs} class method. @return [Array] Array of glyphs.

# File lib/doku/puzzle.rb, line 84
def glyphs
  self.class.glyphs
end
groups() click to toggle source

Shortcut for calling the {Puzzle.groups} class method. @return [Array] Array of groups.

# File lib/doku/puzzle.rb, line 96
def groups
  self.class.groups
end
hash() click to toggle source

@return [Fixnum] Returns a hash code based on the glyph assignments.

# File lib/doku/puzzle.rb, line 137
def hash
  @glyph_state.hash
end
solution?() click to toggle source

Returns true if the puzzle is {#filled?} and {#valid?}. @return [Boolean]

# File lib/doku/puzzle.rb, line 195
def solution?
  filled? and valid?
end
solution_for?(puzzle) click to toggle source

Returns true if the puzzle is valid solution for the given puzzle.

@return [Boolean]

# File lib/doku/puzzle.rb, line 202
def solution_for?(puzzle)
  solution? and puzzle.subset?(self)
end
squares() click to toggle source

Shortcut for calling the {Puzzle.squares} class method. @return [Array] Array of squares.

# File lib/doku/puzzle.rb, line 90
def squares
  self.class.squares
end
subset?(puzzle) click to toggle source

Returns true if the puzzle’s glyphs assignments are a subset of the given puzzle’s glyph assignments and the two puzzles are the same class.

Every puzzle is a subset of itself.

For example, if you find a Sudoku puzzle and start working on it, you have changed the original puzzle into a new puzzle. The original puzzle will be a subset of the new puzzle, assuming you didn’t erase any numbers.

@return [Boolean]

# File lib/doku/puzzle.rb, line 164
def subset?(puzzle)
  self.class == puzzle.class and glyph_assignment_subset?(puzzle)
end
valid?() click to toggle source

Returns true if this puzzle follows the rules. A puzzle is valid if no glyph appears twice in any group. For example, a {Sudoku} puzzle would be invalid if you wrote a “3” twice in the same column.

@return [Boolean]

# File lib/doku/puzzle.rb, line 186
def valid?
  groups.all? do |group|
    glyphs = group.collect { |square| self[square] } - [nil]
    glyphs.uniq.size == glyphs.size
  end
end

Private Instance Methods

glyph_assignment_subset?(puzzle) click to toggle source
# File lib/doku/puzzle.rb, line 236
def glyph_assignment_subset?(puzzle)
  glyph_state.all? do |square, glyph|
    glyph == puzzle[square]
  end
end
initialize_copy(source) click to toggle source

This is called when the puzzle is duped or cloned.

# File lib/doku/puzzle.rb, line 209
def initialize_copy(source)
  @glyph_state = @glyph_state.dup
end