Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Branch: master
Fetching contributors…

Cannot retrieve contributors at this time

2441 lines (2179 sloc) 51.216 kB
# Copyright (C) 2005-2012, Parrot Foundation.
=pod
=head1 TITLE
Sudoku - A sudoku solver
=head1 OVERVIEW
This program implements scanning and blocked rows or columns invalidation.
It does not consider all effects of multiple number blocking, where a
combination of invalid numbers blocks a row or column. In such cases a
simple backtracking algorithm continues to solve the sudoku.
=head1 SYNOPSIS
parrot sudoku.pir [--options] [file]
If no file is given a builtin game is run.
Valid options are:
=over 4
=item --version
Print version information and exit.
=item --help
Print help hint and exit.
=item --debug
Print debug output and game progress to stdout.
=item --inv=n..
Print additionally invalid state of given number(s).
=item --pairs
Print additionally fields with uniqe pairs of numbers.
=item --builtin=name
Run builtin game. If no name is given a list of builtins is printed.
=item --nc
Use ncurses for display and single step through progress. Press any key
for next display.
=back
=head1 DESCRIPTION
The game state is held in multiple views which share one Integer PMC
per common position. Thus updating a row sets also the column or square
information. These three views are list of lists.
=head1 GAME FILES
Game files may contain comments (hash sign in the first column)
digits, and dots for empty fields. E.g:
# std020.sud
# der standard 020 - leicht
2.1..678.
...2...36
8.9.3...5
.7...4..2
..6.9.5..
9..5...6.
5...4.9.7
71...3...
.987..2.3
=head1 PARROT
=head2 Parrot features used
=over 4
=item Parrot OO
The solver is an object as well as the display.
=item Freeze/thaw
For deep copying the game state for backtracking.
=item Multi Method Dispatch
The display classes define multiple methods with the name I<print>,
which take different types and amounts of arguments.
=item Libraries
The program uses Getopt/Obj and the ncurses library.
=item Exception handling
To turn off ncurses just in case.
=back
=head2 Variable indices
Column, rows, and sqares have zero-based indices. Squares are
numbered from top left to bottom right.
=head2 Sudoku Class attributes
=over 4
=item I<rows, cols, sqrs>
LoL of 0 = free, 1..9 = number
=item I<i_rows, i_cols, i_sqrs>
LoL of a bitmask of invalid numbers per field. Bits are starting at bit
one not zero.
=item I<all>
Hash referencing these 6 items - used for backtracking.
=item I<opt>
The option hash.
=item I<disp>
Holds an instance of the display class (I<Dummy>, I<NCurses>) to use.
=back
=head1 AUTHOR
Leopold Toetsch
=head1 COPYRIGHT
Same as parrot.
=cut
.const string VERSION="0.2.3"
.loadlib 'bit_ops'
.loadlib 'io_ops'
.sub _main :main
.param pmc argv
.local int argc
.local string raw_given
.local pmc opt
opt = parse_options(argv)
argc = elements argv
dec argc
if argc < 0 goto get_default
$I0 = defined opt["builtin"]
if $I0 goto get_default
$S0 = argv[argc]
raw_given = read_given($S0)
goto done_input
get_default:
raw_given = builtin_game(opt)
done_input:
run_game(raw_given, opt)
.end
# create game class, object, display, and run
.sub run_game
.param string raw_given
.param pmc opt
.local pmc ar
ar = verify_input(raw_given)
.local pmc cl, self, disp
cl = newclass "Sudoku"
addattribute cl, "orig"
addattribute cl, "all"
addattribute cl, "cols"
addattribute cl, "rows"
addattribute cl, "sqrs"
addattribute cl, "i_cols"
addattribute cl, "i_rows"
addattribute cl, "i_sqrs"
addattribute cl, "opt"
addattribute cl, "disp"
self = new "Sudoku"
setattribute self, "opt", opt
disp = self."new_display"()
##push_eh nc_stop
self."create"(ar)
self."display"()
$I0 = self."verify"()
unless $I0 goto err
if $I0 == 1 goto ok
if $I0 == 2 goto fin
ok:
disp."print"("init ok\n")
$I0 = self."solve"()
if $I0 == 1 goto nok
if $I0 == 0 goto nc_stop
fin:
self."display"()
disp."print"("solved\n")
disp."wait"()
nc_stop:
self."end_display"()
end
nok:
# need backtracking
.local pmc tries, all
tries = new 'ResizablePMCArray'
all = getattribute self, "all"
push tries, all
$I0 = self."back_track"(tries)
if $I0 == 2 goto fin
disp."print"("failed\n")
goto nc_stop
err:
printerr "inconsistent start\n"
self."end_display"()
die 3, 100
.end
# read game from file
# - ignore comment lines w/ hash sign in first col
# - valid chars are dots (empty) and digits 1..9
.sub read_given
.param string file_name
.local pmc io
.local string line, result, c
.local int i, len
result = ""
io = open file_name, 'r'
$I0 = defined io
unless $I0 goto err
loop:
line = readline io
unless io goto done
substr c, line, 0, 1
if c == '#' goto loop
len = length line
i = 0
lp2:
substr c, line, i, 1
if c != '.' goto no_dot
result .= c
no_dot:
if c < '1' goto no_num
if c > '9' goto no_num
result .= c
no_num:
inc i
if i < len goto lp2
goto loop
done:
.return(result)
err:
printerr "read '"
printerr file_name
printerr "' failed\n"
die 3, 100
.end
# get commandline options
.sub parse_options
.param pmc argv
load_bytecode "Getopt/Obj.pbc"
.local string prog
prog = shift argv
# Specification of command line arguments.
# --version, --debug, --inv=nnn, --builtin=name, --nc, --help
.local pmc getopts
getopts = new ["Getopt";"Obj"]
push getopts, "version"
push getopts, "debug"
push getopts, "pairs"
push getopts, "inv=s"
push getopts, "builtin:s" # optional
push getopts, "nc"
push getopts, "help"
.local pmc opt
opt = getopts."get_options"(argv)
$I0 = defined opt['version']
unless $I0 goto n_ver
print prog
print " "
print VERSION
print "\n"
end
n_ver:
$I0 = defined opt['help']
unless $I0 goto n_help
print "usage: "
print prog
print " [options...] [file]\n"
print "see\n\tperldoc -F "
print prog
print "\nfor more\n"
end
n_help:
$I0 = defined opt['debug']
unless $I0 goto n_deb
print "debugging on\n"
n_deb:
.return (opt)
.end
.include "iterator.pasm"
# return one of the builtin games
.sub builtin_game
.param pmc opt
.local string raw_given, name
.local pmc b, it
b = get_builtins()
$I0 = exists opt["builtin"]
if $I0 goto some
$S0 = b['wikipedia']
.return ($S0)
some:
name = opt["builtin"]
if name == "1" goto list_names
if name goto sel_name
list_names:
it = iter b
it = .ITERATE_FROM_START
loop:
unless it goto fin
$S0 = shift it
print $S0
print " "
goto loop
fin:
say ''
end
sel_name:
$I0 = exists b[name]
if $I0 goto ok
printerr "no such builtin: '"
printerr name
printerr "'\n"
die 3, 100
ok:
$S0 = b[name]
.return ($S0)
.end
.sub get_builtins
.local pmc b
.local string raw_given
b = new 'Hash'
# sudokusan malicious 26.6
raw_given = "..9...8.."
raw_given .= "....85..."
raw_given .= "4..23...1"
raw_given .= ".4....9.."
raw_given .= ".75...34."
raw_given .= "..2....8."
raw_given .= "1...59..4"
raw_given .= "...17...."
raw_given .= "..3...6.."
b["san_m0626"] = raw_given
# sudokusan atrocious 20.6
raw_given = "..2.1..5."
raw_given .= "95...736."
raw_given .= ".3......8"
raw_given .= ".8.6.1..."
raw_given .= "5...2...3"
raw_given .= "...7.5.1."
raw_given .= "8......3."
raw_given .= ".451...29"
raw_given .= ".1..6.4.."
b["san_a0620"] = raw_given
# sudokusan atrocious 24.6
raw_given = ".83......"
raw_given .= "....4...3"
raw_given .= "..79..8.6"
raw_given .= "....2.3.."
raw_given .= ".6.8.3.1."
raw_given .= "..5.6...."
raw_given .= "4.6..97.."
raw_given .= "3...8...."
raw_given .= "......12."
b["san_a0624"] = raw_given
# sudokusan atrocious 26.6
raw_given = "5.93....7"
raw_given .= "....9...."
raw_given .= "....64..1"
raw_given .= "..6.....5"
raw_given .= ".58...27."
raw_given .= "2.....4.."
raw_given .= "7..51...."
raw_given .= "....4...."
raw_given .= "8....61.9"
b["san_a0626"] = raw_given
# sudoku-san 4th aug 2006 - atrocious - Y-WING
raw_given = ".....1..."
raw_given .= "6..7....5"
raw_given .= ".82..49.."
raw_given .= ".734...8."
raw_given .= "........."
raw_given .= ".5...736."
raw_given .= "..16..23."
raw_given .= "9....5..1"
raw_given .= "...8....."
b["san_a0804"] = raw_given
# wikipedia - (one of ) the smallest (17 clues) known sudoku
raw_given = "1........"
raw_given .= "..274...."
raw_given .= "...5....4"
raw_given .= ".3......."
raw_given .= "75......."
raw_given .= ".....96.."
raw_given .= ".4...6..."
raw_given .= ".......71"
raw_given .= ".....1.3."
b["wikipedia"] = raw_given
# derstandard 019 schwer
raw_given = "....8..5."
raw_given .= ".123....6"
raw_given .= ".456....."
raw_given .= ".789....."
raw_given .= "5.......4"
raw_given .= ".....123."
raw_given .= ".....456."
raw_given .= "3....789."
raw_given .= ".5..3...."
b["std019"] = raw_given
# derstandard 018 mittel
raw_given = "....247.."
raw_given .= ".1.....5."
raw_given .= "..8.....3"
raw_given .= "..25....7"
raw_given .= ".4..3..8."
raw_given .= "6....91.."
raw_given .= "7.....9.."
raw_given .= ".3.....6."
raw_given .= "..581...."
b["std018"] = raw_given
# "unsolvable" 3 - Y-Wing
raw_given = "...8....6"
raw_given .= "..162.43."
raw_given .= "4...71..2"
raw_given .= "..72...8."
raw_given .= "....1...."
raw_given .= ".1...62.."
raw_given .= "1..73...4"
raw_given .= ".26.481.."
raw_given .= "3....5..."
b["uns3"] = raw_given
.return (b)
.end
# count zero bits
.sub bits0
.param int c
.local int i, n, b
i = 0
n = 0
loop:
c >>= 1 # bits start at 1
b = c & 1
if b goto is_set
inc n
is_set:
inc i
if i < 9 goto loop
.return (n)
.end
# count one bits (3 max - zero based)
.sub bits1
.param int c
.local int i, n, b
i = 0
n = 0
loop:
b = c & 1
c >>= 1
unless b goto not_set
inc n
not_set:
inc i
if i < 3 goto loop
.return (n)
.end
# make sure the game is valid
.sub verify_input
.param string raw
.local int i, c
i = length raw
if i != 81 goto len_err
.local pmc ar
ar = new 'FixedIntegerArray'
ar = 81
i = 0
loop:
$I0 = ord raw, i
if $I0 != 0x2e goto not_dot
c = 0
goto set_it
not_dot:
if $I0 < 0x30 goto err
if $I0 > 0x39 goto err
c = $I0 - 0x30
set_it:
ar[i] = c
inc i
if i < 81 goto loop
.return (ar)
err:
printerr "ill char: '"
substr $S0, raw, i, 1
printerr $S0
printerr "\n"
die 3, 100
len_err:
printerr "length != 81 found : "
printerr i
printerr "\n"
die 3, 100
.end
#
# game methods
#
.namespace ["Sudoku"]
# return true if we single-step
.sub "step" :method
.local pmc opt
opt = getattribute self, "opt"
$I0 = defined opt['debug']
unless $I0 goto check_nc
.return ($I0)
check_nc:
$I0 = defined opt['nc']
.return ($I0)
.end
# return true if debugging is on
.sub "debug" :method
.local pmc opt
opt = getattribute self, "opt"
$I0 = defined opt['debug']
.return ($I0)
.end
# create 9x9 LoL
.sub create_1 :method
.param string what
.local pmc rcss, rcs, all
rcss = new 'FixedPMCArray'
rcss = 9
setattribute self, what, rcss
all = getattribute self, "all"
all[what] = rcss
.local int y
# create arrays
y = 0
ly1:
rcs = new 'FixedPMCArray'
rcs = 9
rcss[y] = rcs
inc y
if y < 9 goto ly1
.end
# create all arrays
.sub create :method
.param pmc ar
.local int x, y, p, c
.local pmc cols, rows, sqrs, e, col, row, sqr, all
.local pmc i_cols, i_rows, i_sqrs, i_col, i_row, i_sqr, inv
setattribute self, "orig", ar
all = new 'Hash'
setattribute self, "all", all
self.'create_1'("rows")
self.'create_1'("cols")
self.'create_1'("sqrs")
self.'create_1'("i_rows")
self.'create_1'("i_cols")
self.'create_1'("i_sqrs")
rows = getattribute self, "rows"
cols = getattribute self, "cols"
sqrs = getattribute self, "sqrs"
i_rows = getattribute self, "i_rows"
i_cols = getattribute self, "i_cols"
i_sqrs = getattribute self, "i_sqrs"
# now fill em
y = 0
ly2:
x = 0
lx2:
p = y * 9
p += x
c = ar[p]
# the entries 'e' and 'inv' are common to all 3 views of the sudoku
e = new 'Integer'
e = c
inv = new 'Integer'
# set row
row = rows[y]
i_row = i_rows[y]
row[x] = e
i_row[x] = inv
# set col
col = cols[x]
i_col = i_cols[x]
col[y] = e
i_col[y] = inv
# set square
$I2 = square_of(x, y)
sqr = sqrs[$I2]
i_sqr = i_sqrs[$I2]
$I0 = x % 3
$I1 = y % 3
$I1 *= 3
$I2 = $I0 + $I1
sqr[$I2] = e
i_sqr[$I2] = inv
inc x
if x < 9 goto lx2
inc y
if y < 9 goto ly2
.end
# display
# TODO disp 2nd in different color, use curses or shell escapes
.sub display :method
.local pmc ar, rows, row, opt, disp
.local string s, c
.local int i, x, y, c1, c2, r, deb_pairs
.local string deb_n
deb_n = "" # print inv for that
self."create_inv"()
opt = getattribute self, "opt"
disp = getattribute self, "disp"
$I0 = defined opt["inv"]
unless $I0 goto no_deb
deb_n = opt["inv"]
no_deb:
deb_pairs = defined opt["pairs"]
i = 0
y = 0
s = ""
r = 0
# orig is a linear array 0..80
ar = getattribute self, "orig"
rows = getattribute self, "rows"
loop_y:
$I0 = y % 3
if $I0 goto no_line
disp."print"(r,0,"+---------+---------+---------+\n")
inc r
no_line:
row = rows[y]
x = 0
loop_x:
c1 = ar[i]
c2 = row[x]
if c1 == 0 goto ok
if c2 == 0 goto ok
if c1 != c2 goto intern_err
ok:
c = "."
if c1 == 0 goto nxt
$I0 = c1 + 0x30
c = chr $I0
goto set
nxt:
if c2 == 0 goto set
$I0 = c2 + 0x30
c = chr $I0
set:
$I0 = i % 3
if $I0 goto sp1
s .= '| '
goto sp2
sp1:
s .= ' '
sp2:
s .= c
s .= ' '
inc i
inc x
if x < 9 goto loop_x
s .= '|'
disp."print"(r,0, s)
unless deb_n goto not_deb_n
self."deb_inv"(y, deb_n)
not_deb_n:
unless deb_pairs goto not_deb_pairs
self."deb_pairs"(y)
not_deb_pairs:
disp."print"("\n")
inc r
s = ""
inc y
if y < 9 goto loop_y
disp."print"(r,0,"+---------+---------+---------+\n")
inc r
disp."wait"()
.return()
intern_err:
printerr "diff between ar and try\n"
die 3, 100
.end
# print invalid for given row and number(s)
.sub deb_inv :method
.param int y
.param string ns
.local pmc invs, inv
.local int b, x, c, i, len, n
i = 0
len = length ns
lp_inv:
substr $S0, ns, i, 1
n = $S0
print " "
invs = getattribute self, "i_rows"
inv = invs[y]
x = 0
loop:
$I0 = x % 3
if $I0 goto nosp
print " "
nosp:
c = inv[x]
b = 1 << n
$I0 = c & b
if $I0 goto is_set
print "."
goto nxt
is_set:
print n
nxt:
inc x
if x < 9 goto loop
inc i
if i < len goto lp_inv
.end
# print pairs for given row
.sub deb_pairs :method
.param int y
.local pmc invs, inv
.local int x
print " "
invs = getattribute self, "i_rows"
inv = invs[y]
x = 0
loop:
$I0 = x % 3
if $I0 goto nosp
print " "
nosp:
.local int el, bits, i, b
el = inv[x]
bits = bits0(el)
if bits == 2 goto isa_pair
print ".."
goto nxt_x
isa_pair:
i = 1
bit_loop:
el >>= 1 # bits start at 1
b = el & 1
if b goto is_set
$I0 = i + 0x30
$S0 = chr $I0
print $S0
is_set:
inc i
if i <= 9 goto bit_loop
nxt_x:
inc x
print " "
if x < 9 goto loop
.end
# verify numbers
# returns:
# 0 ... failure
# 1 ... ok
# 2 ... finished
.sub verify :method
.local pmc rcss
.local int r, done
done = 2
rcss = getattribute self, "rows"
r = self."verify_1"(rcss)
unless r goto err
if r == 2 goto fin1
done = 1
fin1:
rcss = getattribute self, "cols"
r = self."verify_1"(rcss)
unless r goto err
if r == 2 goto fin2
done = 1
fin2:
rcss = getattribute self, "sqrs"
r = self."verify_1"(rcss)
unless r goto err
if r == 2 goto fin3
done = 1
fin3:
.return (done)
err:
.return (0)
.end
# verify rows, cols, or sqrs
.sub verify_1 :method
.param pmc rcss
.local int x, y, result
.local pmc one, e, seen, s
result = 2 # finished
y = 0
lpy:
one = rcss[y]
seen = new 'Hash'
x = 0
lpx:
e = one[x]
unless e goto nxtx
$I0 = exists seen[e]
unless $I0 goto not_seen
s = seen[e]
inc s
goto nxtx
not_seen:
seen[e] = 1
nxtx:
inc x
if x < 9 goto lpx
$I0 = elements seen
if $I0 == 9 goto done
result = 1
done:
$I0 = check_seen(seen)
unless $I0 goto ret_0
inc y
if y < 9 goto lpy
.return (result)
ret_0:
.return (0)
.end
# count seen in one
.sub check_seen
.param pmc seen
.local pmc it
it = iter seen
it = .ITERATE_FROM_START
loop:
unless it goto ok
$S0 = shift it
$I0 = seen[$S0]
if $I0 > 1 goto err
goto loop
ok:
.return(1)
err:
.return(0)
.end
# create invalid bits
.sub create_inv :method
.local pmc rcss, i_rcss
rcss = getattribute self, "rows"
i_rcss = getattribute self, "i_rows"
self."create_inv_1"(rcss, i_rcss, "row")
rcss = getattribute self, "cols"
i_rcss = getattribute self, "i_cols"
self."create_inv_1"(rcss, i_rcss, "col")
rcss = getattribute self, "sqrs"
i_rcss = getattribute self, "i_sqrs"
self."create_inv_1"(rcss, i_rcss, "sqr")
.end
# create row, cols, or sqrs of invalid numbers
# one bit per invalid
.sub create_inv_1 :method
.param pmc ars
.param pmc invs
.param string what
.local int x, y, n, i, c
.local pmc ar, inv
y = 0
lpy:
ar = ars[y]
inv = invs[y]
x = 0
lpx:
c = ar[x]
unless c goto nxt_x
$P0 = inv[x]
$P0 |= 0b1111111110
nxt_x:
inc x
if x < 9 goto lpx
n = 1
lpn:
$I0 = self."contains"(ar, n)
unless $I0 goto nxt_n
i = 0
fill:
$I0 = 1 << n
$P1 = inv[i]
$P1 |= $I0
inc i
if i < 9 goto fill
nxt_n:
inc n
if n <= 9 goto lpn
self."create_inv_n"(ar, inv, y, what)
inc y
if y < 9 goto lpy
.end
# if inv contains 2 identical entries and exactly 2 nums are
# allowed these 2 positions are invalid for all other digits
.sub create_inv_n :method
.param pmc ar
.param pmc inv
.param int y
.param string what
.local int x, x1, x2, n, m, msk
.local pmc d, e1, e2, empty_2, digs
# transpose into a digit-base array with positions as bits
# this should simplify the test for 2 empty squares with
# same digit
digs = new 'FixedPMCArray'
digs = 9
n = 0
lpn:
msk = 2 << n
d = new 'Integer'
digs[n] = d
x = 0
# don't bother looking further if n is already set
$I1 = n + 1
$I0 = self."contains"(ar, $I1)
if $I0 goto nxt_n
lpx:
e1 = inv[x]
$I0 = e1
$I0 &= msk
unless $I0 goto nxt_x
$I1 = 2 << x
d |= $I1
nxt_x:
inc x
if x < 9 goto lpx
nxt_n:
inc n
if n < 9 goto lpn
x1 = 0
lpx1:
empty_2 = new 'ResizablePMCArray'
e1 = digs[x1]
n = bits0(e1)
if n != 2 goto nxt_x1
m = 1
x2 = x1 + 1
lpx2:
e2 = digs[x2]
if e1 != e2 goto nxt_x2
inc m
if m > 2 goto nxt_x1
push empty_2, e1
push empty_2, x1
push empty_2, x2
nxt_x2:
inc x2
if x2 < 9 goto lpx2
$I0 = elements empty_2
unless $I0 goto nxt_x1
if m != 2 goto nxt_x1
goto done
nxt_x1:
inc x1
if x1 < 8 goto lpx1
$I0 = elements empty_2
unless $I0 goto ret
done:
.local int d1, d2, pos_msk, changed
pos_msk = empty_2[0] # positions 1 based
d1 = empty_2[1] # 0 based
d2 = empty_2[2] # 0 based
x = 0
changed = 0
lpx3:
$I0 = 2 << x
$I0 &= pos_msk
if $I0 goto nxt_x3
e1 = inv[x]
n = 0
lpn3:
if n == d1 goto nxt_n3
if n == d2 goto nxt_n3
# invalidate all but d1, d2 at the 2 positions
$I0 = 2 << n
$I1 = e1
$I1 &= $I0
if $I1 goto no_c
e1 |= $I0
changed = 1
no_c:
nxt_n3:
inc n
if n < 9 goto lpn3
nxt_x3:
inc x
if x < 9 goto lpx3
$I0 = self."debug"()
unless $I0 goto ret
unless changed goto ret
# reuse array for debug reports
unshift empty_2, y
unshift empty_2, what
$S0 = sprintf "*** found inv_2 %s %d: %#b %d %d\n", empty_2
print $S0
ret:
.end
# return 1 if row/col/sqr contains num n
.sub contains :method
.param pmc ar
.param int n
.local int i, c
i = 0
lp:
c = ar[i]
if c == n goto ret_1
inc i
if i < 9 goto lp
.return (0)
ret_1:
.return (1)
.end
# main solver method
# returns
# 0 ... err
# 1 ... incomplete
# 2 ... finito
.sub solve :method
.local int r
#self."sanity_check"()
loop:
self."create_inv"()
r = self."scan"()
if r == -1 goto err
unless r goto done
$I0 = self."step"()
unless $I0 goto loop
self."display"()
goto loop
done:
#self."sanity_check"()
$I0 = self."verify"()
# if yet unfished, try advanced methods before back_tracking starts
unless $I0 == 1 goto no_adv
r = self."adv_scan"()
# if changes, start over with "normal" stuff
if r == 1 goto loop
no_adv:
.return ($I0)
err:
print "mismatch\n"
.return (0)
.end
# scan for forced numbers
# returns
# -1 ... err
# 0 ... no change
# 1 ... changes
.sub scan :method
.local int any, y, x, m
any = 0
.local pmc rcss, i_rcss
rcss = getattribute self, "rows"
i_rcss = getattribute self, "i_rows"
$I0 = self."scan_1"(rcss, i_rcss, "rows")
if $I0 == -1 goto err
any |= $I0
$I0 = self."scan_dbl"(rcss, i_rcss, "rows")
any |= $I0
rcss = getattribute self, "cols"
i_rcss = getattribute self, "i_cols"
$I0 = self."scan_1"(rcss, i_rcss, "cols")
if $I0 == -1 goto err
any |= $I0
$I0 = self."scan_dbl"(rcss, i_rcss, "cols")
any |= $I0
rcss = getattribute self, "sqrs"
i_rcss = getattribute self, "i_sqrs"
$I0 = self."scan_1"(rcss, i_rcss, "sqrs")
if $I0 == -1 goto err
any |= $I0
$I0 = self."step"()
unless $I0 goto nd2
self."display"()
nd2:
$I0 = self."scan_blocked"(rcss, i_rcss, "sqrs")
any |= $I0
(y, x, m) = self."best_pos"()
if m != 1 goto not_uniq
self."set_uniq"(y, x)
any = 1
not_uniq:
.return (any)
err:
.return (-1)
.end
# scan for advanced methods
# returns
# -1 ... err
# 0 ... no change
# 1 ... changes
.sub adv_scan :method
$I0 = self."y_wing"()
# TODO try more stuff
.return ($I0)
.end
.sub "y_wing" :method
# scan for pairs all over
.local int x, y, bits, el, res
.local pmc i_rows, i_row
res = 0
y = 0
i_rows = getattribute self, "i_rows"
loop_y:
x = 0
i_row = i_rows[y]
loop_x:
el = i_row[x]
bits = bits0(el)
if bits != 2 goto nxt_x
$I0 = self."check_y_wing"(x, y, el)
res |= $I0
nxt_x:
inc x
if x < 9 goto loop_x
inc y
if y < 9 goto loop_y
.return (res)
.end
.sub pair_vals
.param int el
.local int i, b, A, B
A = 0
i = 1 # A, B are 1-based
loop:
el >>= 1 # bits start at 1
b = el & 1
if b goto next
if A goto A_is_set
A = i
goto next
A_is_set:
B = i
.return (A, B)
next:
inc i
if i <= 9 goto loop
printerr "failed to fined pair"
exit 1
.end
# get the square # of coors (x,y) TODO reuse
.sub square_of
.param int x
.param int y
x /= 3
y /= 3
y *= 3
$I0 = x + y
.return ($I0)
.end
# given the square # and idx inside, return coors (x,y) TODO reuse
.sub square_to_xy
.param int sq
.param int idx
.local int x, y
x = sq % 3
x *= 3
$I0 = idx % 3
x += $I0
y = sq / 3
y *= 3
$I1 = idx / 3
y += $I1
.return (x, y)
.end
# look for another pair AC (A,C != B)
# return C and the position in i_rcs
.sub "y_wing-pair" :method
.param pmc i_rcs
.param int A
.param int not_B
.local int x, el, bits, p1, p2
x = 0
loop:
el = i_rcs[x]
bits = bits0(el)
if bits != 2 goto next
(p1, p2) = pair_vals(el)
if p1 == not_B goto next
if p2 == not_B goto next
if p1 != A goto check_p2
.return (p2, x)
check_p2:
if p2 != A goto next
.return (p1, x)
next:
inc x
if x < 9 goto loop
.return (0,0)
.end
# look for another pair BC
# return 0/1 and the position in i_rcs
.sub "y_wing-pair_BC" :method
.param pmc i_rcs
.param int B
.param int C
.local int x, el, bits, p1, p2
x = 0
loop:
el = i_rcs[x]
bits = bits0(el)
if bits != 2 goto next
(p1, p2) = pair_vals(el)
if p1 == B goto ok1
if p2 == B goto ok2
goto next
ok1:
if p2 != C goto next
.return (1, x)
ok2:
if p1 != C goto next
.return (1, x)
next:
inc x
if x < 9 goto loop
.return (0,0)
.end
# invalidate C from the given [start,end] range#
# return 1 if something changed
.sub "y_wing_inv" :method
.param pmc i_rcs
.param int C
.param int start
.param int end
.local int changed, b
.local pmc el
changed = 0
b = 1 << C
loop:
el = i_rcs[start]
$I0 = el
$I1 = $I0 & b
if $I1 goto next
el |= b
changed = 1
next:
inc start
if start <= end goto loop
.return (changed)
.end
# find C for A B
# and invalidate C if found
.sub "find_C_y_wing_1" :method
.param int x
.param int y
.param int A
.param int B
# check same row, col, or sqr for a pair with A and not B
.local pmc i_rcss, i_rcs
i_rcss = getattribute self, "i_sqrs"
.local int sq, changed
changed = 0
sq = square_of(x, y) # TODO reuse this func
.local int C, c
i_rcs = i_rcss[sq]
(C, c) = self."y_wing-pair"(i_rcs, A, B)
unless C goto check_row # TODO row, col
# convert the square coordinate to (x, y)
.local int cx, cy, bx, by, has_bc
(cx, cy) = square_to_xy(sq, c) # AC
if x == cx goto try_row
# check col and row at AB for a BC pair
i_rcss = getattribute self, "i_cols"
i_rcs = i_rcss[x]
(has_bc, c) = self."y_wing-pair_BC"(i_rcs, B, C)
unless has_bc goto try_row
bx = x
by = c
# but B have to be in a different square too
$I0 = square_of(bx, by)
if sq == $I0 goto try_row
.local int start, end
# invalidate col x in sqr(x,y)
sq = square_of(x, y)
($I0, start) = square_to_xy(sq, 0)
end = start + 2
changed = self."y_wing_inv"(i_rcs, C, start, end)
# invalidate col x at BC
i_rcs = i_rcss[cx]
sq = square_of(bx, by)
($I0, start) = square_to_xy(sq, 0)
end = start + 2
$I0 = self."y_wing_inv"(i_rcs, C, start, end)
changed |= $I0
goto show_debug
try_row:
if y == cy goto nope
i_rcss = getattribute self, "i_rows"
i_rcs = i_rcss[y]
(has_bc, c) = self."y_wing-pair_BC"(i_rcs, B, C)
unless has_bc goto nope
bx = c
by = y
.local int start, end
# TODO invalidate row y too
i_rcs = i_rcss[cx]
sq = square_of(bx, by)
($I0, start) = square_to_xy(sq, 0)
end = start + 2
changed = self."y_wing_inv"(i_rcs, C, start, end)
show_debug:
$I0 = self."debug"()
unless $I0 goto ex
$S0 = "CHG"
if changed goto chg_ok
$S0 = "noC"
chg_ok:
print $S0
print " Y-WING A "
print A
print " B "
print B
print " C "
print C
print " at x "
print x
print " y "
print y
print " cx "
print cx
print " cy "
print cy
print " bx "
print bx
print " by "
say by
self."display"()
goto ex
check_row:
i_rcss = getattribute self, "i_rows"
i_rcs = i_rcss[y]
# XXX TODO check that A is in a forced pair
(C, c) = self."y_wing-pair"(i_rcs, A, B)
cx = c
cy = y
unless C goto check_col
i_rcss = getattribute self, "i_cols"
i_rcs = i_rcss[x]
# XXX TODO check that B is in a forced pair
(has_bc, by) = self."y_wing-pair_BC"(i_rcs, B, C)
bx = cx
unless has_bc goto check_col
i_rcs = i_rcss[cx]
changed = self."y_wing_inv"(i_rcs, C, by, by)
if changed goto show_debug
check_col:
# TODO
nope:
ex:
.return (changed)
.end
# find C for A, or B
.sub "find_C_y_wing" :method
.param int x
.param int y
.param int A
.param int B
.local int changed
changed = self."find_C_y_wing_1"(x, y, A, B)
unless changed goto not_A
.return (changed)
not_A:
.tailcall self."find_C_y_wing_1"(x, y, B, A)
.end
# check, if we find another pair with 1 digit in common with el
.sub "check_y_wing" :method
.param int x
.param int y
.param int el
# ok - we have a pair at col/row (x,y) with inv_bits el
# assume, we are at the corner of the Y
# let one number be A, the other B
.local int A, B, C
#trace 1
(A, B) = pair_vals(el)
# now find another pair in
# - another *this* row, col, or square
# - AC or BC giving another unique element C
.tailcall self."find_C_y_wing"(x, y, A, B)
.end
# the quare y has a uniq digit at x - set it
.sub set_uniq :method
.param int y
.param int x
.local pmc sqrs, sqr, e
.local int n, b
sqrs = getattribute self, "i_sqrs"
sqr = sqrs[y]
b = sqr[x]
n = 1
loop:
$I0 = 1 << n
$I1 = b & $I0
if $I1 goto nxt
sqrs = getattribute self, "sqrs"
sqr = sqrs[y]
e = sqr[x]
e = n
$I0 = self."debug"()
unless $I0 goto nd
print "uniq sqr="
print y
print " x="
print x
print " n="
print n
print "\n"
nd:
.return()
nxt:
inc n
if n <= 9 goto loop
.end
# check inv of rows,cols,sqrs for forced numbers
# returns
# -1 ... err
# 0 ... no change
# 1 ... changes
.sub scan_1 :method
.param pmc rcss
.param pmc i_rcss
.param string what
.local int x, y, c, m, n, b, xx, any
.local pmc one, inv, e
any = 0
y = 0
lpy:
one = rcss[y]
inv = i_rcss[y]
n = 1
lpn:
x = 0
b = 0
lpx:
c = inv[x]
$I0 = 1 << n
$I1 = c & $I0
if $I1 goto nxt_x
inc b
xx = x
nxt_x:
inc x
if x < 9 goto lpx
if b != 1 goto nxt_n
any = 1
$P0 = one[xx]
unless $P0 goto ok
if $P0 != n goto err
goto nxt_n
ok:
$P0 = n
$I0 = self."debug"()
unless $I0 goto nxt_n
print "found "
print what
print " y="
print y
print " x="
print xx
print " n="
say n
nxt_n:
inc n
if n <= 9 goto lpn
inc y
if y < 9 goto lpy
.return (any)
err:
.return (-1)
.end
# check double invs of rows,cols for forced rows/cols
# returns
# 0 ... no change
# 1 ... changes
# this implements half of TODO item 1 (digit '7' ...)
# scan_dbl finds both occurrences of the blocked '7' but needs more testing still
.sub scan_dbl :method
.param pmc rcss
.param pmc i_rcss
.param string what
.local pmc inv, bits
.local int n, y, x, sx, sy, el, retval
retval = 0
n = 1
# for all digits
lpn:
sx = 0
# when scanning cols, sx is horizontal
# need 3 cols at a time
lpsx:
bits = new 'FixedIntegerArray'
bits = 3
x = 0
lpx:
$I0 = sx * 3
$I0 += x
inv = i_rcss[$I0]
sy = 0
lpsy:
y = 0
lpy:
$I1 = sy * 3
$I1 += y
el = inv[$I1]
# if n is allowed, set a bit in bits
$I2 = 1 << n
$I2 &= el
if $I2 goto blocked
$I6 = bits[sy]
$I5 = 1 << x
$I6 |= $I5
bits[sy] = $I6
blocked:
inc y
if y < 3 goto lpy
inc sy
if sy < 3 goto lpsy
inc x
if x < 3 goto lpx
$I3 = 0
lp_c:
$I4 = bits[$I3]
if $I4 == 0 goto no_check
inc $I3
if $I3 < 3 goto lp_c
#$S1 = sprintf "bits %x %x %x\n", bits
#print $S1
$I10 = self."check_dbl"(i_rcss, bits, sx, n, what)
retval |= $I10
no_check:
inc sx
if sx < 3 goto lpsx
nxt_n:
inc n
if n <= 9 goto lpn
.return (retval)
.end
# check if this is validly dbl blocking
.sub check_dbl :method
.param pmc i_rcss
.param pmc bits
.param int sx
.param int n
.param string what
# we must have 2 masks with the same 2 bits set and another one
# where the clear one is also set e.g. 3 7 3
.local int m0, m1, m2, b
#trace 1
m0 = bits[0]
m1 = bits[1]
m2 = bits[2]
if m0 != m1 goto m02
# m0 == m1
b = bits1(m0)
if b != 2 goto m02
$I0 = bits1(m2)
if $I0 != 3 goto m02
.tailcall self.'inv_dbl'(i_rcss, n, m0, sx, 2, what)
m02:
if m0 != m2 goto m12
# m0 == m2
b = bits1(m0)
if b != 2 goto m12
$I0 = bits1(m1)
if $I0 != 3 goto m12
.tailcall self.'inv_dbl'(i_rcss, n, m0, sx, 1, what)
m12:
if m1 != m2 goto ret
# m1 == m2
b = bits1(m1)
if b != 2 goto ret
$I0 = bits1(m0)
if $I0 != 3 goto ret
.tailcall self.'inv_dbl'(i_rcss, n, m1, sx, 0, what)
ret:
.return (0)
.end
# invalidate results found from check_dbl
.sub inv_dbl :method
.param pmc i_rcss
.param int n
.param int msk
.param int sx
.param int sy
.param string what
.local int x, y, b
.local pmc inv, el
x = sx * 3
b = 0
lpb:
$I0 = 1 << b
$I0 &= msk
unless $I0 goto not_set
$I2 = x + b
inv = i_rcss[$I2]
y = 0
lpy:
$I1 = sy * 3
$I1 += y
el = inv[$I1]
$I3 = 1 << n
el |= $I3
inc y
if y < 3 goto lpy
not_set:
inc b
if b < 3 goto lpb
$I0 = self."debug"()
unless $I0 goto nd
print "inv_dbl "
print what
print " n "
print n
print " msk "
print msk
print " sx "
print sx
print " sy "
say sy
self."display"()
nd:
.return (1)
.end
# check for blocked rows or colums
# returns
# 0 ... no change
# 1 ... changes
.sub scan_blocked :method
.param pmc sqrs
.param pmc i_sqrs
.param string what
.local int x, y, c, m, n, b, sh, any
.local pmc one, inv, e
.local int sr, sc, cbl, rbl, nulb, nulc
any = 0
y = 0
lpy:
one = sqrs[y]
inv = i_sqrs[y]
n = 1
lpn:
x = 0
b = 0
rbl = 7 # blocked is reset per square row/col
cbl = 7
nulb = 0 # empty are set
nulc = 0
lpx:
sc = x % 3 # square col and row
sr = x / 3
c = inv[x]
$I0 = 1 << n
$I1 = c & $I0
if $I1 goto nxt_x
$I0 = 1 << sr
nulb |= $I0
$I1 = bnot $I0
rbl &= $I1
$I0 = 1 << sc
nulc |= $I0
$I1 = bnot $I0
cbl &= $I1
nxt_x:
inc x
if x < 9 goto lpx
b = 0
loop_br:
sh = 1 << b
$I0 = rbl ~ 7
$I0 &= 7
# need to have 2 blocked and one not-filled
if $I0 != sh goto nbr
if nulb != sh goto nbr
$I0 = self."inv_row"(y, b, n)
any |= $I0
nbr:
$I0 = cbl ~ 7
$I0 &= 7
if $I0 != sh goto nbc
if nulc != sh goto nbc
$I0 = self."inv_col"(y, b, n)
any |= $I0
nbc:
inc b
if b < 3 goto loop_br
nxt_n:
inc n
if n <= 9 goto lpn
inc y
if y < 9 goto lpy
.return (any)
.end
# set rest of row invalid due to blocked square
# skip the square itself
.sub "inv_row" :method
.param int y
.param int b
.param int n
.local pmc rows
.local int r, sx
rows = getattribute self, "i_rows"
r = y / 3 # row of square y
r *= 3
r += b
sx = y % 3 # skip / 3
$I0 = self."inv_rc"(rows, r, sx, n, "row")
.return ($I0)
.end
# set rest of col invalid due to blocked square
.sub "inv_col" :method
.param int y
.param int b
.param int n
.local pmc cols
.local int r, sx
cols = getattribute self, "i_cols"
r = y % 3 # col of square y
r *= 3
r += b
sx = y / 3
$I0 = self."inv_rc"(cols, r, sx, n, "col")
.return ($I0)
.end
# set rest of row/col invalid due to blocked square
.sub "inv_rc" :method
.param pmc rcs
.param int r
.param int sx
.param int n
.param string what
.local pmc rc
.local int r, x, bb, any
rc = rcs[r]
bb = 1 << n
any = 0
x = 0
loop:
$I0 = x / 3 # skip this square
if $I0 == sx goto nxt
$P0 = rc[x]
$I0 = $P0
$I1 = $I0 & bb
if $I1 goto nxt
any = 1
$P0 |= bb
nxt:
inc x
if x < 9 goto loop
unless any goto ret
$I0 = self."debug"()
unless $I0 goto ret
print "found blocked "
print what
print "="
print r
print " n="
say n
ret:
.return (any)
.end
# check that pmcs in rows, cols and sqrs are the same
.sub sanity_check :method
.local pmc rows, cols
rows = getattribute self, "rows"
cols = getattribute self, "cols"
self."sanity_check_rc"(rows, cols)
rows = getattribute self, "i_rows"
cols = getattribute self, "i_cols"
self."sanity_check_rc"(rows, cols)
.end
.sub sanity_check_rc :method
.param pmc rows
.param pmc cols
.param string what
.local pmc row, col, e1, e2
.local int x, y
y = 0
lpy:
row = rows[y]
x = 0
lpx:
col = cols[x]
e1 = row[x]
e2 = col[y]
eq_addr e1, e2, ok
printerr "pmc borken rc y="
printerr y
printerr " x="
printerr x
printerr "\n"
die 3, 100
ok:
inc x
if x < 9 goto lpx
inc y
if y < 9 goto lpy
.end
# backtrack progress
.sub progress :method
.param int size
.param int y
.param int x
.param int n
.param int m
print "back_tracking "
print size
print " y="
print y
print " x="
print x
print " n="
print n
print " m="
print m
print "\n"
.end
# back_track tries
.sub back_track :method
.param pmc tries
.local pmc tos, all, sqrs, sqr, e
.local int r, size, x, y, n, m
.local string state
size = elements tries
dec size
tos = tries[size]
state = freeze tos
(y, x, m) = self."best_pos"()
if y >= 0 goto start
# this shouldn't happen
self."progress"(size, y, x, -1)
.return (0)
start:
n = 1 # try all numbers at best pos
loop:
all = thaw state # restore state
self.'set_attrs'(all) # set attributes to this state
sqrs = getattribute self, "sqrs"
sqr = sqrs[y]
e = sqr[x]
e = n
r = self."verify"()
unless r goto nxt
self."progress"(size, y, x, n, m)
$I0 = self."step"()
unless $I0 goto nd
self."display"()
nd:
r = self."solve"()
if r == 0 goto nxt
if r == 2 goto fin
push tries, all
r = self."back_track"(tries)
if r == 2 goto fin
$P0 = pop tries
nxt:
inc n
if n <= 9 goto loop
$I0 = self."debug"()
unless $I0 goto nd2
print "back "
print size
print "\n"
nd2:
.return (0)
fin:
.return (r)
.end
# return the square coors of the minimum freedom
# used for backtracking
# if m == 1 this is a forced uniq position
.sub best_pos :method
.local pmc sqrs, sqr
.local int x, y, n, c, mx, my, mb
sqrs = getattribute self, "i_sqrs"
y = 0
mb = 10
mx = -1
my = -1
lpy:
sqr = sqrs[y]
x = 0
lpx:
c = sqr[x]
n = bits0(c)
unless n goto no_min
if n >= mb goto no_min
mb = n
mx = x
my = y
no_min:
inc x
if x < 9 goto lpx
inc y
if y < 9 goto lpy
.return (my, mx, mb)
.end
.sub set_attrs :method
.param pmc all
.local pmc e
e = all["rows"]
setattribute self, "rows", e
e = all["cols"]
setattribute self, "cols", e
e = all["sqrs"]
setattribute self, "sqrs", e
e = all["i_rows"]
setattribute self, "i_rows", e
e = all["i_cols"]
setattribute self, "i_cols", e
e = all["i_sqrs"]
setattribute self, "i_sqrs", e
.end
# display support
.sub new_display :method
.local pmc stdscr, opt, cl, p, s, it, f, gl
opt = getattribute self, "opt"
$I0 = defined opt["nc"]
unless $I0 goto out
stdscr = nc_start()
cl = newclass "NCurses"
addattribute cl, "win"
p = new "NCurses"
setattribute p, "win", stdscr
setattribute self, "disp", p
.return(p)
out:
cl = newclass "Dummy"
p = new "Dummy"
setattribute self, "disp", p
.return(p)
.end
.sub end_display :method
.local pmc opt
opt = getattribute self, "opt"
$I0 = defined opt["nc"]
unless $I0 goto out
nc_end()
out:
.end
.namespace ["Dummy"]
.sub "print" :multi(_, int, int, string) :method
.param int r
.param int c
.param string s
print s
.end
.sub "print" :multi(_, string) :method
.param string s
print s
.end
.sub "print" :multi(_, int) :method
.param int s
print s
.end
.sub "wait" :method
.end
.namespace ["NCurses"]
# TODO remember last position, parse newlines to increment row
# this should better be all in a new library
.sub "print" :multi(_, int, int, string) :method
.param int r
.param int c
.param string s
.local pmc win, f
win = getattribute self, "win"
f = get_global ["ncurses"], "mvwaddstr"
f(win, r, c, s)
.end
.sub "print" :multi(_, string) :method
.param string s
.local pmc win, f
win = getattribute self, "win"
f = get_global ["ncurses"], "waddstr"
f(win, s)
.end
.sub "print" :multi(_, int) :method
.param int i
.local string s
.local pmc win, f
s = i
win = getattribute self, "win"
f = get_global ["ncurses"], "waddstr"
f(win, s)
.end
.sub "wait" :method
.local pmc f
.local int key
f = get_global ["ncurses"], "getch"
key = f()
.end
.namespace []
# ncurses support
.sub nc_start
.local pmc stdscr
load_bytecode 'ncurses.pbc'
stdscr = _init_curses()
.return(stdscr)
.end
.sub nc_end
.local pmc endwin, curs_set
curs_set = get_global ["ncurses"], "curs_set"
curs_set(1)
endwin = get_global ["ncurses"], "endwin"
endwin()
.end
.sub _init_curses
.local pmc INITSCR
.local pmc START_COLOR
.local pmc INIT_PAIR
.local pmc COLOR_PAIR
.local pmc WATTRON
.local pmc CURS_SET
.local pmc NODELAY
.local pmc KEYPAD
.local pmc STDSCR
INITSCR = get_global ["ncurses"], "initscr"
START_COLOR = get_global ["ncurses"], "start_color"
INIT_PAIR = get_global ["ncurses"], "init_pair"
COLOR_PAIR = get_global ["ncurses"], "COLOR_PAIR"
WATTRON = get_global ["ncurses"], "wattron"
CURS_SET = get_global ["ncurses"], "curs_set"
NODELAY = get_global ["ncurses"], "nodelay"
KEYPAD = get_global ["ncurses"], "keypad"
STDSCR = INITSCR()
START_COLOR()
# Color pair 1, dark green fg, black background
INIT_PAIR(1, 2, 0)
$I0 = COLOR_PAIR(1)
# We pass what's returned from COLOR_PAIR straight on
## WATTRON($I0)
CURS_SET(0) # turn off cursor
## NODELAY(STDSCR, 1) # set nodelay mode
## KEYPAD(STDSCR, 1) # set keypad mode
.return(STDSCR)
.end
=head1 Advanced checks
=head2 Double blocked rows/columns
Consider this one:
# daily sudoku 16-nov-2005 very hard
.5..3.9..
.394.....
.....964.
.6...84..
5.......8
..19...2.
.826.....
.....576.
..5.9..8.
It got solved until here, then backtracking began (and succeeded).
+---------+---------+---------+
| 4 5 6 | 8 3 . | 9 . . | 777 77. 7..
| . 3 9 | 4 . . | 8 . . | .77 7.. 7..
| . . 8 | . . 9 | 6 4 3 | ..7 ..7 777
+---------+---------+---------+
| . 6 . | . . 8 | 4 . . | .7. ..7 7..
| 5 . . | . . . | . . 8 | 7.. ... 7.7
| 8 . 1 | 9 . . | . 2 6 | 7.7 7.. 777 <<<<<<<<<<
+---------+---------+---------+
| . 8 2 | 6 . . | . . . | .77 7.. 777
| . . . | 2 8 5 | 7 6 . | 777 777 777
| 6 . 5 | . 9 . | 2 8 . | 7.7 .7. 777
+---------+---------+---------+
Have a look at the marked row 5. '3' and '5' can't be in col 1.
So '3' and '5' have to be at the right side of the row.
Now take a look at the '7' - invalid positions are shown above already
(dumped with the --inv=7 option).
In both squares 0 and 6 the '7' can only be in columns 0 or 1. This
implies that a '7' has to be in col 2, row 3 or 4. Looking at
square 5, the '7' is also in row 3 or 4. Therefore the '7' in the
middle square (4) has to be too in row 5.
Voila we have 3 numbers (3,5,7) which are somewhere on the right
side of row 5 and we get a unique number in row 5, col 1 - the '4'.
And then it's easy.
One part (the '7') is implemented in C<scan_dbl>, which
boils down this case to the other one below.
=head2 Blocking due to multiple others
Given this sudoku:
# daily sudoku 16-nov-2005 very hard
.5..3.9..
.394.....
.....964.
.6...84..
5.......8
..19...2.
.826.....
.....576.
..5.9..8.
Earlier sudoku.pir started backtracking at:
+---------+---------+---------+
| . . 1 | 3 8 5 | . . . |
| 6 8 7 | . 1 . | . 9 . |
| 2 3 5 | 6 9 7 | . . 1 |
+---------+---------+---------+
| 1 . . | 9 7 3 | . 5 . |
| . 7 6 | 5 . 8 | 1 3 . |
| . 5 . | . 6 1 | . . . |
+---------+---------+---------+
| 7 1 . | 8 . . | . . 4 |
| . . . | 7 . . | . 1 8 |
| . . . | 1 . 9 | 7 . . |
+---------+---------+---------+
In columns 7 the digits (9,5,3) are blocking this column in square 8
so that the digits (2,6) have to be in column 7 too. Which implies
that in square 2 we have a unique '7' at row 0, col 7:
+---------+---------+---------+
| . . 1 | 3 8 5 | x 7 y | (x,y) = (2|6)
| 6 8 7 | . 1 . | . 9 . |
| 2 3 5 | 6 9 7 | . . 1 |
+---------+---------+---------+
| 1 . . | 9 7 3 | . 5 . |
| . 7 6 | 5 . 8 | 1 3 . |
| . 5 . | . 6 1 | . . . |
+---------+---------+---------+
| 7 1 . | 8 . . | a . 4 | (a,b,c) = (3|5|9)
| . . . | 7 . . | b 1 8 |
| . . . | 1 . 9 | 7 . c |
+---------+---------+---------+
Now the tests in "create_inv_n" invalidate illegal positions
due to multiple-blocking and other tests are likely to proceed.
=head2 Y-WING
(This is partially still TODO)
Given this suduku:
# "unsolvable" 3 - Y-Wing
. . . 8 . . . . 6
. . 1 6 2 . 4 3 .
4 . . . 7 1 . . 2
. . 7 2 . . . 8 .
. . . . 1 . . . .
. 1 . . . 6 2 . .
1 . . 7 3 . . . 4
. 2 6 . 4 8 1 . .
3 . . . . 5 . . .
It started backtracking at:
+---------+---------+---------+
| . 3 . | 8 5 4 | . 1 6 | .. .. 29 .. .. .. 79 .. ..
| . . 1 | 6 2 9 | 4 3 . | .. .. .. .. .. .. .. .. ..
| 4 6 . | 3 7 1 | . . 2 | .. .. .. .. .. .. .. 59 ..
+---------+---------+---------+
| . 4 7 | 2 9 3 | . 8 1 | 56 .. .. .. .. .. 56 .. ..
| . . . | . 1 7 | 3 . . | .. .. .. 45 .. .. .. .. 59
| . 1 3 | . 8 6 | 2 . . | 59 .. .. 45 .. .. .. .. ..
+---------+---------+---------+
| 1 . . | 7 3 2 | . . 4 | .. .. .. .. .. .. .. .. ..
| . 2 6 | 9 4 8 | 1 . 3 | 57 .. .. .. .. .. .. 57 ..
| 3 . 4 | 1 6 5 | . 2 . | .. .. .. .. .. .. .. .. ..
+---------+---------+---------+
The numbers on the right side are showing squares with unique pairs.
Having a look at the columns 7 and 8, we see these pairs (79, 59, and 57)
Let's label these numbers as A, B, and C:
+---------+---------+---------+
| . 3 . | 8 5 4 | AC 1 6 |
| . . 1 | 6 2 9 | 4 3 . |
| 4 6 . | 3 7 1 | . AB 2 |
+---------+---------+---------+
| . 4 7 | 2 9 3 | . 8 1 |
| . . . | . 1 7 | 3 . . |
| . 1 3 | . 8 6 | 2 . . |
+---------+---------+---------+
| 1 . . | 7 3 2 | X . 4 |
| . 2 6 | 9 4 8 | 1 BC 3 |
| 3 . 4 | 1 6 5 | X 2 . |
+---------+---------+---------+
When we now try to fill row 2, column 7 with A or B, we see that at
positions X, a C can't be valid. Either it's blocked via the column
or directly via the last square. Thus we can eliminate digit 7 from
positions X.
=head2 TODO
# daily sudoku wed 28-dec-2005, very hard
# backtracking
...52.63.
.5.....7.
9....8..2
.17..4...
.9.....6.
...8..31.
1..6....5
.4.....9.
.86.95...
This one starts backtracking early. The '6' is an 'X-Wing' like
configuration (col 1 and row 2 with a common corner have just 2
possible positions, just one is valid, when you try both).
The same happens a bit later with '9'.
+---------+---------+---------+
| . 7 . | 5 2 . | 6 3 . | 666 666 666
| . 5 . | . . . | . 7 . | .66 6.. 666
| 9 . . | . . 8 | . . 2 | 6.6 6.6 666
+---------+---------+---------+
| . 1 7 | . . 4 | . . . | .66 6.6 666
| . 9 . | . . . | . 6 . | 666 666 666
| . . . | 8 . . | 3 1 . | ..6 6.. 666
+---------+---------+---------+
| 1 . 9 | 6 . . | . . 5 | 666 666 666
| . 4 . | . . . | . 9 6 | 666 666 666
| . 8 6 | . 9 5 | . . 3 | 666 666 666
+---------+---------+---------+
Here is starts backtracking. A possible improvement would be:
- detect such digit configuration
- only backtrack try this digit ('6') above
=head2 TODO deadly square
See also std331.sud
=head2 TODO Generalization
A Sudoku has 2 dimensions and 3 connected views (row, column, and
square). There are 1-dim tests, which work for all views. 2-dim tests
are a bit more tricky to generalize and not yet done properly.
Basically: as only 2 views are independent, all these tests can
work on 2 of 3 views:
square, row
square, column
row, columm
Now the problem is, how to generalize the possible other direction.
Let's call it the 'neighbour'. A neighbour is always 'towards' the
second view. A row has 9 column neighbours and 3
square neighbours. A square has 3 row and 3 column neighbours.
(Maybe neighbour is a bad term as it does contain itself).
C<scan_dbl> can now easily be reviewed and generalized:
For all neighbours (n): If in the view (v0) a digit is valid in only
one of (n)'s views: giving (v1), this digit is invalid in the
complement of the intersection (v0 & v1).
NB: it seems to be simpler to just hack the code as to utter the
idea in $human_lang.
This is trivial if these views are (row, column) as the intersection
is just one point, but it is the generalization of the 'inv_1' code.
Another example of a 2-dim test is of course Y-Wing.
=cut
# Local Variables:
# mode: pir
# fill-column: 100
# End:
# vim: expandtab shiftwidth=4 ft=pir:
Jump to Line
Something went wrong with that request. Please try again.