Class: Xudoku::Board

Inherits:
Array
  • Object
show all
Includes:
Utils
Defined in:
lib/xudoku/board.rb

Overview

Represents a Sudoku board as a flat array of 81 cell values.

Values are stored as zero-based integers (0–8) internally, where nil denotes an empty cell. All factory methods accept one-based input (1–9) and convert automatically.

Boards can be constructed via Board.from_string, Board.from_array, or from_empty, and serialised back via to_s, to_a, or to_json.

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils

#bits_to_numbers, #guesses_from, #pick_better, #pos

Class Method Details

.empty {|board, value, index| ... } ⇒ Board

Creates a new empty board, optionally populated via a block.

Without a block, returns a board with all 81 cells set to nil. When a block is given, each cell is yielded in order and set to the block’s return value, allowing custom initialisation logic.

Examples:

Empty board

Board.empty

Seeded from a predefined solution array

Board.empty { |_board, _value, i| solution[i] }

Randomly fill a subset of cells

Board.empty { |_board, _value, i| i < 10 ? rand(1..9) : nil }

Yields:

  • (board, value, index)

    called once per cell during initialisation

Yield Parameters:

  • board (Board)

    the board being constructed

  • value (nil)

    the current cell value (always nil on an empty board)

  • index (Integer)

    the zero-based cell index (0–80)

Yield Returns:

  • (Integer, nil)

    the value to assign to the cell; use nil for empty

Returns:

  • (Board)

    a new Board instance



102
103
104
105
106
107
108
109
110
111
112
# File 'lib/xudoku/board.rb', line 102

def self.empty
  board = new(81.times.map { nil })

  return board unless block_given?

  board.each_with_index do |value, index|
    board[index] = yield(board, value, index)
  end

  board
end

.from_array(array) ⇒ Board

Creates a new board instance from an array of cell values.

Accepts either a flat array or a nested array of rows, which is automatically flattened. Values are converted to zero-based indices, and nil entries are preserved as empty cells.

Examples:

Flat array

Board.from_array([5, 3, nil, nil, 7, nil, ...])

Nested rows

Board.from_array([
  [5, 3, nil, nil, 7, nil, nil, nil, nil],
  [6, nil, nil, 1, 9, 5, nil, nil, nil],
  ...
])

Parameters:

  • array (Array<Integer, nil>, Array<Array<Integer, nil>>)

    a flat or nested array of 81 cell values, where each value is an integer (1–9) or nil for an empty cell

Returns:

  • (Board)

    a new Board instance initialised with the parsed cell values



75
76
77
78
# File 'lib/xudoku/board.rb', line 75

def self.from_array(array)
  array = array.flatten if array.first.is_a?(Array)
  new(array.map { |i| i.nil? ? i : i - 1 })
end

.from_string(board_string) ⇒ Board

Creates a new board instance by parsing a formatted board string.

Scans the string token by token, ignoring border characters (- + | =), and collects up to 81 cell values. Digit characters are converted to zero-based values (so "1" becomes 0, "9" becomes 8, and non-digit tokens are treated as empty cells).

Examples:

Board.from_string(<<~BOARD)
  8 7 2  5 1 3  6 9 4
  6 3 1  9 4 8  5 7 2
  4 9 5  2 7 6  3 8 1

  3 2 4  7 5 1  9 6 8
  9 1 8  6 3 2  4 5 7
  7 5 6  8 9 4  1 2 3

  5 6 3  4 8 7  2 1 9
  1 8 9  3 2 5  7 4 6
  2 4 7  1 6 9  8 3 5
BOARD

Parameters:

  • board_string (String)

    a whitespace-delimited string representing the board, optionally decorated with border characters

Returns:

  • (Board)

    a new Board instance initialised with the parsed cell values



41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/xudoku/board.rb', line 41

def self.from_string(board_string)
  result = []
  board_string.split.each do |char|
    next if char.match?(/[-+|=]/)

    result << (char.to_i - 1 if char.match?(/\d/))

    break if result.size == 81
  end

  new(result)
end

Instance Method Details

#deduceObject



119
120
121
# File 'lib/xudoku/board.rb', line 119

def deduce
  Deducer.new(self).deduce
end

#missing_from_axis(x, axis) ⇒ Object



123
124
125
126
127
128
129
130
131
# File 'lib/xudoku/board.rb', line 123

def missing_from_axis(x, axis)
  bits = 0
  9.times do |y|
    value = value_at(x, y, axis)
    bits |= (1 << value) unless value.nil?
  end

  511 ^ bits
end

#numbers_missingObject



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/xudoku/board.rb', line 133

def numbers_missing
  allowed = 81.times.map do |x|
    if self[x].nil?
      511
    else
      0
    end
  end
  needed = []

  [0, 1, 2].each do |axis|
    9.times do |x|
      bits = missing_from_axis(x, axis)
      needed << bits
      9.times do |y|
        allowed[pos(x, y, axis)] &= bits
      end
    end
  end

  [allowed, needed]
end

#to_aObject



156
157
158
# File 'lib/xudoku/board.rb', line 156

def to_a
  Formatter::Array.new(self).format
end

#to_json(*_args) ⇒ Object



164
165
166
# File 'lib/xudoku/board.rb', line 164

def to_json(*_args)
  Formatter::JSON.new(self).format
end

#to_sObject



160
161
162
# File 'lib/xudoku/board.rb', line 160

def to_s
  Formatter::String.new(self).format
end

#value_at(x, y, axis) ⇒ Object

:stopdoc:



115
116
117
# File 'lib/xudoku/board.rb', line 115

def value_at(x, y, axis)
  self[pos(x, y, axis)]
end