\ utility
: pow2 ( n -- 2^n ) 1 swap lshift ;
: bits ( mask -- n )
0 begin swap dup while dup 1- and swap 1+ repeat drop ;
: log2 ( mask -- n )
0 begin swap dup while 2/ swap 1+ repeat drop ;
\ 0 constant empty
3 constant sqSize
sqSize sqSize * constant dim
dim dim * constant size
: fillWithString ( addr len dest -- ) over size <> abort" wrong size!"
tuck >R move
R@ size + R> do i c@ [char] 0 - i c! loop ;
create sqMap size allot
s" 000111222000111222000111222333444555333444555333444555666777888666777888666777888"
sqMap fillWithString
create sqInds size allot \ square number to indexes (inverse of sqMap)
: setInds
sqInds size 255 fill
size 0 do
i sqMap + c@ dim * sqInds + 1-
begin 1+ dup c@ 255 = until
i swap c!
loop ;
setInds
create rowMask dim cells allot
create colMask dim cells allot
create sqMask dim cells allot
: rowMaskAt ( n -- ^mask ) dim / cells rowMask + ;
: colMaskAt ( n -- ^mask ) dim mod cells colMask + ;
: sqMaskAt ( n -- ^mask ) sqMap + c@ cells sqMask + ;
: usedMask ( n -- mask )
dup rowMaskAt @
over colMaskAt @ or
swap sqMaskAt @ or ;
: availMask
usedMask [ dim 1+ pow2 1- 1- ] literal xor ;
create stack size 2* allot \ stack used for backtracking
variable top
: initStack stack top ! ;
: push top @ c! 1 top +! ;
: pop -1 top +! top @ c@ ;
: .stack stack begin dup top @ < while dup c@ . 1+ repeat drop ;
create puzzle size allot
: p@ puzzle + c@ ;
: p! dup push puzzle + c! ;
: 0p! 0 swap puzzle + c! ;
: .board ( xt -- )
size 0 do
i sqSize mod 0= if
i dim mod 0= if cr
i dim 3 * = i dim 6 * = or if
dim 2* 1+ 0 do [char] - emit loop cr
then
else space then
then
i over execute
loop cr drop ;
: .sq ( i -- ) p@ . ;
: .puzzle ['] .sq .board ;
: updateMasks ( mask i -- )
2dup rowMaskAt +!
2dup colMaskAt +!
sqMaskAt +! ;
: addMasks ( i -- )
dup p@ pow2 swap updateMasks ;
: initMasks
rowMask dim cells erase colMask dim cells erase sqMask dim cells erase
size 0 do
i p@ if i addMasks then
loop ;
: setPuzzle ( addr len -- ) puzzle fillWithString initMasks initStack ;
\ smarts
: findForced ( -- i T | F )
size 0 do
i p@ 0= if i availMask bits 1 = if i true unloop exit then then
loop false ;
: findRow ( mask -1 -- mask i | -1 )
dim 0 do \ sqs
over i cells rowMask + @ and 0= if
i 1+ dim * dup dim - do
i p@ 0= if
over i colMaskAt @ i sqMaskAt @ or and 0= if
dup 0< if drop i else drop -1 leave then
then
then
loop
dup 0< 0= if leave then
then
loop ;
: findCol ( mask -1 -- mask i | -1 )
dim 0 do \ sqs
over i cells colMask + @ and 0= if
size i do
i p@ 0= if
over i rowMaskAt @ i sqMaskAt @ or and 0= if
dup 0< if drop i else drop -1 leave then
then
then
dim +loop
dup 0< 0= if leave then
then
loop ;
: findSq ( mask -1 -- mask i | -1 )
dim 0 do
over i cells sqMask + @ and 0= if
i 1+ dim * sqInds + dup dim - do
i c@ p@ 0= if
over i c@ colMaskAt @ i c@ rowMaskAt @ or and 0= if
dup 0< if drop i c@ else drop -1 leave then
then
then
loop
dup 0< 0= if leave then
then
loop ;
: findEliminated ( -- i n | 0 )
dim 0 do \ foreach number
i 1+ pow2 -1 ( mask i )
findRow dup 0< 0= if nip i 1+ unloop ( 2dup . .i ." row" cr ) exit then
findCol dup 0< 0= if nip i 1+ unloop ( 2dup . .i ." col" cr ) exit then
findSq dup 0< 0= if nip i 1+ unloop ( 2dup . .i ." sq" cr ) exit then
2drop
loop 0 ;
: allForcedMoves
begin
begin findForced while \ dup .i ." forced" cr
dup availMask 2/ log2
over p! addMasks
\ .puzzle key drop
repeat
findEliminated dup
while
over p! addMasks
\ .puzzle key drop
repeat drop ;
\ backtracking
\ stack of tried indexes
\ find index of smallest available (0: stuck, -1: done)
: findMostConstrained ( -- i possibilities )
-1 9
size 0 do
i p@ 0= if i availMask bits over < if 2drop i i availMask bits then then
loop ;
255 constant sentinal
: undoToSentinal
begin pop dup sentinal <> while
dup p@ pow2 negate over updateMasks 0p!
repeat ( sentinal ) push ;
: solve
allForcedMoves \ no more forced moves? try some recursion and backtracking
findMostConstrained 0= if ( ." dead end" cr) drop exit then
dup 0< if drop ." solution:" .puzzle exit then
dup availMask swap ( mask i )
sentinal push
dim 0 do
over i pow2 and if \ i . dup .i ." trying" .s
i over p! dup addMasks
\ .puzzle key drop
recurse \ ." undoing" .s cr
undoToSentinal
then
loop
pop ( sentinal ) drop 2drop ;
\ dev, testing, and examples
: xy>i dim * + ;
: .i dim /mod '( emit swap . . ') emit space ;
: time: ( params time: "word" -- ) \ gforth
utime 2>r ' execute utime 2r> d-
<# # # # # # # '. hold #s #> type ." elapsed" ;
s" 960300800850010009003096020100000002000270180609084003006700050000028304040005060"
setPuzzle \ mhx, easy: only requires findForced
.puzzle time: solve
s" 090004007000007900800000000405800000300000002000009706000000004003500000200600080"
setPuzzle \ spykerman
.puzzle time: solve
: .ok ( n -- )
availMask 10 1 do
dup i pow2 and 0= if i . then
loop drop ;
: .sqCount ( i -- ) dup p@ if drop 0 else availMask bits then . ;
: .counts ['] .sqCount .board ;
: .nok ( mask xt i -- mask xt )
dup p@ if drop 0 else availMask >r over r> and if 1 else 0 then then . ;
: .navail ( n -- ) pow2 ['] .nok .board drop ;