Alla simppeli graafisella käyttöliittymällä varustettu Sudokun ratkaisija 8th ohjelmointikielellä. Ulkoasultaan ohjelma näyttää tältä.
Edit: Lisäsin yksinkertaisen parserin Sudokun lukemiseen datatiedostosta.
\
\ loader.8th
\
needs file/getc
ns?
ns: loader
[ 10, 13, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 ] constant chars
: init-vars
1 "column" t:!
1 "line" t:!
0 "cell" t:!
a:new "data" t:! ;
: column+=1
"column" t:@ n:1+
"column" t:! ;
: cell+=1
"cell" t:@ n:1+
"cell" t:! ;
: data@
"data" t:@ ;
: cr
column+=1 ;
: newline
"line" t:@ n:1+
"line" t:!
1 "column" t:! ;
: zero
data@ 0 a:push drop
column+=1
cell+=1 ;
: one
data@ 1 a:push drop
column+=1
cell+=1 ;
: two
data@ 2 a:push drop
column+=1
cell+=1 ;
: three
data@ 3 a:push drop
column+=1
cell+=1 ;
: four
data@ 4 a:push drop
column+=1
cell+=1 ;
: five
data@ 5 a:push drop
column+=1
cell+=1 ;
: six
data@ 6 a:push drop
column+=1
cell+=1 ;
: seven
data@ 7 a:push drop
column+=1
cell+=1 ;
: eight
data@ 8 a:push drop
column+=1
cell+=1 ;
: nine
data@ 9 a:push drop
column+=1
cell+=1 ;
[ ' newline , ' cr , ' zero , ' zero , ' one , ' two ,
' three , ' four , ' five , ' six , ' seven , ' eight , ' nine ] constant actions
: load \ s -- a
init-vars
f:open
repeat
f:getc null? not if
chars swap ' n:= a:indexof nip
null? if
1 "column" t:@ "line" t:@ "Impossible character on line: %d at column: %d" s:strfmt t:err!
2drop drop null false ;;
then
actions caseof
"cell" t:@ 81 n:> if
2 "Can't fit to the Sudoku grid." t:err!
2drop null false ;;
then
else
drop
then
f:eof? not
while! drop
"cell" t:@ 81 n:= not if
3 "Not enough values to fill the Sudoku grid." t:err!
null false
else
0 "No error, Sudoku loaded!" t:err!
"data" t:@ true
then ;
ns\
\ solver.8th
\
needs array/each-slice
ns?
ns: solver
[ 00, 00, 00, 03, 03, 03, 06, 06, 06,
00, 00, 00, 03, 03, 03, 06, 06, 06,
00, 00, 00, 03, 03, 03, 06, 06, 06,
27, 27, 27, 30, 30, 30, 33, 33, 33,
27, 27, 27, 30, 30, 30, 33, 33, 33,
27, 27, 27, 30, 30, 30, 33, 33, 33,
54, 54, 54, 57, 57, 57, 60, 60, 60,
54, 54, 54, 57, 57, 57, 60, 60, 60,
54, 54, 54, 57, 57, 57, 60, 60, 60 ] constant top-left-cell
\ Bit number presentations
a:new 2 b:new b:clear a:push ( 2 b:new b:clear swap 1 b:bit! a:push ) 0 8 loop constant posbit
: posbit? \ n -- b
posbit swap a:@ nip const ;
: search \ b -- n
null swap
(
dup -rot b:bit@ if
rot drop break
else
nip
then
) 0 8 loop swap ;
: b-or \ b b -- b
' n:bor b:op ;
: b-and \ b b -- b
' n:band b:op ;
: b-xor \ b b -- b
b:xor
[ xff, x01 ] b:new
b-and ;
: b-not \ b -- b
xff b:xor
[ xff, x01 ] b:new
b-and ;
: b-any \ a -- b
' b-or 0 posbit? a:reduce ;
: row \ a row -- a
9 n:* 9 a:slice ;
: col \ a col -- a
-1 9 a:slice+ ;
: -rot9+
-rot 9 n:+ ;
\ For testing sub boards
: sub \ a n -- a
top-left-cell swap a:@ nip over over 3 a:slice
-rot9+ 2dup 3 a:slice
-rot9+ 3 a:slice
a:+ a:+ ;
\ Possible numbers for a cell
: candidates? \ n -- s
dup dup 9 n:/ n:int swap 9 n:mod \ row col
"solver-board" t:@ swap col b-any
"solver-board" t:@ rot row b-any
b-or
"solver-board" t:@ rot sub b-any
b-or
b-not ;
: num-of-candidates? \ b -- n
0 swap ( b:bit@ rot n:+ swap ) 0 8 loop drop ;
\ If found: -- n T
\ If not found: -- T
: find-free-cell
a:new "solver-board" t:@
(
tuck a:@ 0 posbit? b:= if
-rot a:push swap
else
nip
then
) 0 80 loop swap nip
a:len 0 n:= not if
( candidates? num-of-candidates? swap candidates? num-of-candidates? n:cmp ) a:sort
a:pop nip true
else
drop
false
then ;
: check
true
"solver-board" t:@
( dup -rot a:@ 0 posbit? b:= not if
over a:@ >r over 0 posbit? a:! over candidates? r@ b-and 0 posbit? b:= not if
swap r> a:!
else
swap r> a:!
nip
false swap
break
then
else
nip
then ) 0 80 loop drop ;
: validate
true
"solver-board" t:@
( dup -rot a:@ swap 2 pick 0 posbit? a:! 2 pick candidates? 2 pick b:= if
-rot a:!
else
2drop drop
false swap
break
then ) 0 80 loop drop ;
: solve
"solver-board" t:!
check not if
false ;;
then
a:new "history" t:!
repeat
find-free-cell if
dup candidates?
repeat
search null? if
drop "solver-board" t:@ -rot a:! drop
"history" t:@ a:len 0 n:= if
drop false ;;
then
a:pop nip
a:open
else
n:1+ posbit?
dup
"solver-board" t:@ 4 pick rot a:! drop
b-xor
2 a:close
"history" t:@ swap a:push drop
break
then
again
else
validate
break
then
again ;
ns\
\ sudoku.8th
\
needs nk/gui
needs nk/widgets
needs nk/keyboard
needs gui/filebrowser
libbin font/Roboto-Regular.ttf
"loader.8th" f:include
"solver.8th" f:include
: secs
d:ticks d:ticks/sec n:/ ;
a:new ( 0 solver:posbit? a:push ) 81 times var, gui-board
: new-win
{
name: "main",
wide: 460,
high: 560,
resizable: false,
fonts: {
f1: {
font: @font/Roboto-Regular.ttf
}
},
fontheight: 24,
font: "f1",
title: "Sudoku Solver"
}
nk:win ;
: centered \ rect s font -- rect' s font
2dup nk:measure-font swap 2 a:close >r
rot nk:rect-center nip r@ \ s font p1 dim
( -2 n:/ ) a:map ' n:+ a:2map
r> a:+ -rot ;
: center-rect \ r1 r2 -- r3
nk:rect>size >r
dup nk:rect>pos
swap
nk:rect>size r@
( n:- 2 n:/ ) a:2map
' n:+ a:2map
r> a:+ ;
[ @scan:UP , @scan:DOWN , @scan:LEFT , @scan:RIGHT , @scan:0 , @scan:1 , @scan:2 , @scan:3 , @scan:4 , @scan:5 , @scan:6 , @scan:7 , @scan:8 , @scan:9 ] constant NUMBERKEYS
: key-state-changed? \ s a -- a
nk:scancode?
( if 1 else 0 then ) a:map over nk:get over ?:
rot 2 pick nk:set
( n:cmp ) a:2map ;
: *9
9 n:* ;
: /9
over 9 n:/ n:int ;
: 9mod+
9 n:mod n:+ ;
: rotdropswap
rot drop swap ;
: up
/9 n:1- 0 8 n:clamp 9 n:*
2 pick 9mod+
rotdropswap ;
: down
/9 n:1+ 0 8 n:clamp 9 n:*
2 pick 9mod+
rotdropswap ;
: left
/9 *9
2 pick 9 n:mod n:1- 0 8 n:clamp n:+
rotdropswap ;
: right
/9 *9
2 pick 9 n:mod n:1+ 0 8 n:clamp n:+
rotdropswap ;
: number? \ -- n | null
null "keystates" NUMBERKEYS key-state-changed?
(
swap a:pop 1 n:= if
break rot drop
else
nip
then
) 4 2 pick a:len nip n:1- loop-
over null? if
drop
(
1 n:= if
[ ' up , ' down , ' left , ' right ] caseof
else
drop
then
) a:each drop
else
2drop
4 n:-
then ;
: cell-hovered-select? \ n1 n2 rect -- n
dup [ 0, 0, -8, -8 ] ' n:+ a:2map center-rect
dup nk:hovered? if
dup dup 0 1 "black" nk:stroke-rect
nk:BUTTON_LEFT swap false nk:clicked? if
-rot over null? if
drop nip dup rot
else
over n:= not if
nip dup
else
drop null swap
then
rot
then
' noop nk:do
then
then
-rot over null? not if
n:= if
swap 0 3 "black" nk:stroke-rect
else
nip
then
else
2drop nip
then ;
: generate-grid \ w h xoffs yoffs -- a
a:new ( >r ( 5 pick n:* 3 pick n:+ r@ 5 pick n:* 3 pick n:+ 6 pick 6 pick 4 a:close a:push ) 0 8 loop rdrop ) 0 8 loop
nip nip nip nip ;
var solver-task-id
"" var, message
true var, input-enabled
50 constant CELL-WIDTH
50 constant CELL-HEIGHT
5 constant X-OFFS
100 constant Y-OFFS
var start-time
CELL-WIDTH CELL-HEIGHT X-OFFS Y-OFFS generate-grid constant SUDOKU-GRID
: sudoku-grid \ n | null -- n | null
SUDOKU-GRID
( dup 0 "white" nk:fill-rect
dup 0 1 "black" nk:stroke-rect
dup 2 pick gui-board @ swap a:@ nip solver:search nip null? not if \ if bit is set
[ "1","2", "3", "4", "5", "6", "7", "8", "9" ] swap caseof
else \ no bits set
drop ""
then
"f1" centered "white" "black" nk:draw-text
input-enabled @ if \ input is enabled if solver task is not running
cell-hovered-select?
else
2drop
then
) a:each drop ;
: draw-subgrid-borders \ w h xoffs yoffs --
3 pick 3 n:* 2 pick n:+ over 2 a:close dup 0 6 pick 9 n:* 2 a:close ' n:+ a:2map 3 "black" nk:stroke-line
3 pick 6 n:* 2 pick n:+ over 2 a:close dup 0 6 pick 9 n:* 2 a:close ' n:+ a:2map 3 "black" nk:stroke-line
over 4 pick 3 n:* 2 pick n:+ 2 a:close dup 4 pick 9 n:* 0 2 a:close ' n:+ a:2map 3 "black" nk:stroke-line
over 4 pick 6 n:* 2 pick n:+ 2 a:close dup 4 pick 9 n:* 0 2 a:close ' n:+ a:2map 3 "black" nk:stroke-line
2drop 2drop ;
: solver-ok
solver-task-id @ t:result nip const gui-board !
secs start-time @ n:- "Sudoku solved in %f seconds!" s:strfmt message !
true input-enabled ! ;
: solver-fail
"No solution!" message !
true input-enabled ! ;
: solver-task
solver:solve if
"solver-board" t:@
' solver-ok nk:do
else
' solver-fail nk:do
then ;
: start-solver-task
false input-enabled !
"selected" null nk:set
"Working..." message !
secs start-time !
gui-board @ const 1 ' solver-task t:task-n solver-task-id ! ;
: clear-board
gui-board @ a:clear ( 0 solver:posbit? a:push ) 81 times drop ;
: main-render
{
title: "test",
padding: [4,4],
flags: [ @nk:WINDOW_NO_SCROLLBAR ],
\ for the filebrowser
chosen: null,
showing: false,
restrict: false,
fb: {
root: @getcwd ,
rowheight: 24 ,
multi: false ,
filter: ["*.txt"]
}
}
nk:begin
"showing" nk:get input-enabled @ and if
nk:win-high 1 nk:layout-row-dynamic
["fb","restrict"] get a:open if "top" getcwd m:! then g:filebrowser null? if drop else
if
\ user chose something, an array is on TOS
a:len 0 n:= not if
a:pop dup "chosen" swap set
loader:load if
( solver:posbit? a:push ) a:each! drop gui-board !
"Sudoku loaded succesfully!" message !
else
drop
t:err? "msg" m:@ nip message !
then
else
drop
then
else
"chosen" null nk:set
then
"showing" false nk:set
then
else
nk:get-row-height 3 nk:layout-row-dynamic
"Load" ( input-enabled @ if "showing" true nk:set then ) nk:button-label
"Clear" ( input-enabled @ if clear-board "" message ! then ) nk:button-label
"Solve" ( input-enabled @ if start-solver-task then ) nk:button-label
nk:get-row-height 1 nk:layout-row-dynamic
message @ 50 nk:EDIT_SIMPLE nk:EDIT_READ_ONLY n:bor nk:PLUGIN_FILTER_DEFAULT nk:edit-string 2drop
"selected" nk:get sudoku-grid "selected" swap nk:set
CELL-WIDTH CELL-HEIGHT X-OFFS Y-OFFS draw-subgrid-borders
"selected" nk:get null? not if
number? null? not input-enabled @ and if
solver:posbit? gui-board @ -rot a:! drop
' noop nk:do
else
drop
dup "selected" nk:get n:= not if
"selected" swap nk:set
' noop nk:do
else
drop
then
then
else
drop
then
then
nk:end ;
: app:main
new-win ' main-render -1 nk:render-loop ;Aihe on jo aika vanha, joten et voi enää vastata siihen.