redsquirrel/nest

Refactoring and commenting

1 parent 384b1b4 commit e08572827119e24bbf8dec2a7950bef2da91ad86 Dave Hoover committed Jan 2, 2009
Showing with 57 additions and 23 deletions.
1. +22 −12 sudoku/brain.rb
2. +20 −2 sudoku/brain_cell.rb
3. +3 −1 sudoku/grids.rb
4. +5 −3 sudoku/solver.rb
5. +7 −5 sudoku/solvers.rb
 @@ -1,50 +1,60 @@ +# The Brain is basically a collection of BrainCells class Brain < Array - def initialize - 9.times { |i| self[i] = (1..9).map { BrainCell.new([*1..9]) } } + def initialize(board) + board.each_with_index do |row, y| + self[y] = [] + board.each_with_index do |cell, x| + self[y][x] = BrainCell.new([*1..9], [x, y]) + end + end end def narrow(blanks, board, grids) blanks.each do |x, y| brain_cell = self[y][x] - brain_cell.narrow(x, y, board, grids) + brain_cell.narrow(board, grids) end end + # Solvers are a collection of BrainCells that have only one possibility def solvers(blanks) solvers = Solvers.new blanks.each do |x, y| brain_cell = self[y][x] if brain_cell.solved? - solvers << OpenStruct.new(:x => x, :y => y, :value => brain_cell.solved_value) + solvers << brain_cell end end solvers end + # Sometimes in Sudoku, you have to guess in order to move forward def guess(board, blanks, state) - brain_cells = sorted_cells_with_coords_from(blanks) - brain_cells.each do |brain_cell, coords| - brain_cell.size.times do |i| - board[coords.last][coords.first] = brain_cell[i] + # I sort these as an optimization, it makes more sense to guess + # with the BrainCells with less possibilities first. + sorted_cells_from(blanks).each do |brain_cell| + brain_cell.each do |possibility| if state.finished return else + # I made this concurrent as an optimization for evil puzzles. Thread.new do + # Here is where I "guess", by setting the spot on the board. + board.set(brain_cell.x, brain_cell.y, possibility) think(board, self, state) end end end - board[coords.last][coords.first] = 0 end end private - def sorted_cells_with_coords_from(blanks) + def sorted_cells_from(blanks) blanks.map { |x, y| - [self[y][x], [x, y]] - }.sort_by { |brain_cell, coords| + self[y][x] + }.sort_by { |brain_cell| brain_cell.size } end
 @@ -1,8 +1,14 @@ -# Would be nice to have coords in the BrainCell +# A brain cell holds the current possibilities for a specific spot on the board class BrainCell < Array class EmptyException < Exception; end - def narrow(x, y, board, grids) + def initialize(possibilities, coords) + super(possibilities) + @coords = coords + end + + # This is where we narrow down the possibilities + def narrow(board, grids) row = board[y] col = board.map { |r| r[x] } reject! { |n| row.include?(n) || col.include?(n) } @@ -21,4 +27,16 @@ def solved_value raise "Not solved: #{self.join(", ")}" unless solved? first end + + def x + @coords.first + end + + def y + @coords.last + end + + def ==(other) + self.x == other.x && self.y == other.y + end end
 @@ -1,5 +1,7 @@ class Grids - SETUP = [[0..2, 0..2, []],[3..5, 0..2, []], [6..8, 0..2, []], [0..2, 3..5, []],[3..5, 3..5, []], [6..8, 3..5, []], [0..2, 6..8, []],[3..5, 6..8, []], [6..8, 6..8, []]] + SETUP = [[0..2, 0..2, []],[3..5, 0..2, []], [6..8, 0..2, []], + [0..2, 3..5, []],[3..5, 3..5, []], [6..8, 3..5, []], + [0..2, 6..8, []],[3..5, 6..8, []], [6..8, 6..8, []]] def initialize(board) @grids = SETUP.deep_copy
 @@ -10,18 +10,20 @@ def solve(start) board = Board.new(start) fail("Bad board") unless board.size == 9 && board[4].size == 9 - brain = Brain.new + brain = Brain.new(board) fail("Bad brain: rows=#{brain.size}, cols=#{brain[7].size}") unless brain.size == 9 && brain[7].size == 9 && brain[3][1].size == 9 state = OpenStruct.new think(board, brain, state) - return state.finished.to_s end def think(board, brain, state) + # I make copies of the board and brain because I need to make guesses + # that I don't want to have to clean up board = board.deep_copy brain = brain.deep_copy + grids = Grids.new(board) blanks = board.blanks @@ -46,7 +48,7 @@ def think(board, brain, state) begin solvers.check_for_conflicts(grids) solvers.solve(board) - rescue Solver::ConflictException + rescue Solvers::ConflictException return end end
 @@ -1,3 +1,5 @@ +# A collection of BrainCells that have just one possibility left +# and therefore are "Solvers" class Solvers < Array class ConflictException < Exception; end @@ -6,17 +8,17 @@ def check_for_conflicts(grids) grid1 = grids.get(s1.x, s1.y) each do |s2| - next if s1.x == s2.x && s1.y == s2.y + next if s1 == s2 - if s1.x == s2.x && s1.value == s2.value + if s1.x == s2.x && s1.solved_value == s2.solved_value raise ConflictException end - if s1.y == s2.y && s1.value == s2.value + if s1.y == s2.y && s1.solved_value == s2.solved_value raise ConflictException end grid2 = grids.get(s2.x, s2.y) - if grid1 == grid2 && s1.value == s2.value + if grid1 == grid2 && s1.solved_value == s2.solved_value raise ConflictException end end @@ -25,7 +27,7 @@ def check_for_conflicts(grids) def solve(board) each do |s| - board.set(s.x, s.y, s.value) + board.set(s.x, s.y, s.solved_value) end end end