Here's my (poor) solution. I started out thinking "Hey, that's
easy!", and only when I was done did I realize that my solution
cannot make the guesses necessary to "try out" numbers to fill in
empty spaces to try and progress.
What it does do is solve the puzzle if there's always a simple
logical next step (i.e. at least one row, column, or tile with
exactly one number missing).
I failed the quiz, but feel the need to share my results anyhow :)
(The board that I load happens to be the Quiz board, with the first
row and third tile filled in just enough for my solver to be able to
handle it.)
module Soduku
class Board
attr_reader :spaces, :tiles, :rows, :cols
def initialize( board_to_load=nil )
@spaces = (0..80).to_a.map{ |i| Space.new }
@rows = []
0.step( 80, 9 ){ |i|
@rows << @spaces[ i..(i+8) ]
}
@cols = []
0.upto( 8 ){ |i|
@cols << col = []
0.step( 80, 9 ){ |j|
col << @spaces[ i+j ]
}
}
@tiles = []
0.step(54,27){ |a|
0.step(6,3){ |b|
@tiles << tile = []
corner = a+b
0.step(18,9){ |row_offset|
0.upto(2){ |col_offset|
tile << @spaces[ corner + row_offset +
col_offset ]
}
}
}
}
if board_to_load
values = board_to_load.scan( /[\d_]/ )
raise "Supplied board does not have 81 distinct
values" unless values.length == 81
values.each_with_index{ |v,i|
@spaces[i].value = v.to_i if v != '_'
}
end
end
def solve
unsolved_count = 81
iteration = 1
row_solved = {}
col_solved = {}
tile_solved = {}
while unsolved_count > 0 && iteration < 100
puts "Iteration #{iteration}" if $DEBUG
unsolved_count = 81 - @spaces.select{ |s|
s.value }.length
puts "\t#{unsolved_count} spaces unsolved" if $DEBUG
@rows.each_with_index{ |row,i|
unless row_solved[i]
if solve_set( row )
row_solved[i] = true
end
end
}
@cols.each_with_index{ |col,i|
unless col_solved[i]
if solve_set( col )
col_solved[i] = true
end
end
}
@tiles.each_with_index{ |tile,i|
unless tile_solved[i]
if solve_set( tile )
tile_solved[i] = true
end
end
}
iteration += 1
end
end
def synchronize
@spaces.each{ |s| s.synchronize }
end
def to_s
row_sep = "+-------+-------+-------+\n"
out = ''
@spaces.each_with_index{ |s,i|
out << row_sep if i % 27 == 0
out << '| ' if i % 3 == 0
out << s.to_s + ' '
out << "|\n" if i % 9 == 8
}
out << row_sep
out
end
private
def solve_set( spaces )
unknown_spaces = spaces.select{ |s| !s.value }
return true if unknown_spaces.length == 0
known_spaces = spaces - unknown_spaces
known_values = known_spaces.collect{ |s| s.value }
unknown_spaces.each{ |s|
possibles = s.possibles
known_values.each{ |v| possibles.delete( v ) }
s.synchronize
}
# Recheck now that they've all been synchronized
unknown_spaces = spaces.select{ |s| !s.value }
return true if unknown_spaces.length == 0
return false
end
end
class Space
attr_accessor :value, :possibles
def initialize( value=nil )
@possibles = {}
unless @value = value
1.upto(9){ |i| @possibles[i]=true }
end
end
def synchronize
possible_numbers = @possibles.keys
if possible_numbers.length == 1
@value = possible_numbers.first
end
end
def to_s
@value ? @value.to_s : '_'
end
end
end
b = Soduku::Board.new( <<ENDBOARD )
+-------+-------+-------+
| 9 6 3 | 1 7 4 | 2 5 _ |
| _ _ 8 | 3 _ 5 | 6 4 9 |
| 2 _ _ | _ _ _ | 7 _ 1 |
+-------+-------+-------+
| 8 _ _ | 4 _ 7 | _ _ 6 |
| _ _ 6 | _ _ _ | 3 _ _ |
| 7 _ _ | 9 _ 1 | _ _ 4 |
+-------+-------+-------+
| 5 _ _ | _ _ _ | _ _ 2 |
| _ _ 7 | 2 _ 6 | 9 _ _ |
| _ 4 _ | 5 _ 8 | _ 7 _ |
+-------+-------+-------+
ENDBOARD
$DEBUG = true
puts b
b.solve
puts b