\ DAWGDEMO.F - Directed Acyclic Word Graph
\
\ By Ian Osgood iano@quirkster.com
\
\ Top level commands
\ tdtrav - interactively traverse a TRIE/DAWG
\ word? - lookup a word in the dawg
\ Boggle sample program
\ random-board - fill the board with random letters
\ fill-board - set the board to a particular state
\ .board - show the board
\ solve-board - use the DAWG to find all the words
\ of length min-len or greater
include dawg.f
VARIABLE prefix-len
CREATE prefix 16 CHARS ALLOT
: prefix-len+ prefix-len @ CHARS + ;
: .prefix ." '" prefix prefix-len @ TYPE ." '" ;
\
\ TRIE/DAWG checker
\
: .block ( block -- )
CELL-
BEGIN CELL+ DUP @
DUP EOW IF [CHAR] A ELSE [CHAR] a THEN
OVER Let 1- + EMIT
EOB
UNTIL DROP ;
: trav ( index -- command[0^-.] )
DUP 0= IF .prefix ." End of line." CR EXIT THEN
CELLS dawg @ + 0 ( block^ command )
BEGIN DROP .prefix ." [" DUP .block ." ^-.] "
KEY CR
DUP [CHAR] a [CHAR] z 1+ WITHIN
IF DUP prefix prefix-len+ C!
c>let OVER letter-in-block DUP
IF 1 prefix-len +!
@ Ind RECURSE
-1 prefix-len +!
DUP [CHAR] - = OVER 8 = OR OVER 127 = OR
IF DROP 0 THEN
THEN
THEN
DUP [CHAR] ^ =
OVER [CHAR] - = OR OVER 8 = OR OVER 127 = OR
OVER [CHAR] . = OR
UNTIL NIP ;
: tdtrav
0 prefix-len ! BEGIN dawg @ @ trav [CHAR] . = UNTIL ;
\
\ spell check
\
: is-word? ( addr len -- TF )
OVER + SWAP ( bounds)
dawg @ ( end cur node-addr )
BEGIN
@ Ind ?DUP 0= IF 2DROP FALSE THEN \ word too long
dawg@i OVER C@ c>Let SWAP letter-in-block
?DUP 0= IF 2DROP FALSE EXIT THEN \ word not found
>R CHAR+ 2DUP = R> SWAP
UNTIL
@ EOW IF 2DROP TRUE ELSE 2DROP FALSE THEN ; \ word maybe too short
: word? BL PARSE is-word? IF ." Yes" ELSE ." No" THEN ;
\
\ Boggle solver
\ (start with "random-board" or "fill-board abcd efgh ijkl mnop")
\
4 VALUE min-len
6 5 * 1+ CHARS CONSTANT board-size
CREATE board board-size ALLOT
\ 0 , 0 , 0 , 0 , 0 ,
\ 0 , 1 , 2 , 3 , 4 ,
\ 0 , 5 , 6 , 7 , 8 ,
\ 0 , 9 , 10 , 11 , 12 ,
\ 0 , 13 , 14 , 15 , 16 ,
\ 0 , 0 , 0 , 0 , 0 , 0 ,
\ random numbers
HERE VALUE seed
: RANDOM ( -- u ) seed $107465 * $234567 + DUP TO seed ;
: CHOOSE ( n -- 0 <= u < n ) RANDOM UM* NIP ;
\ UI
: .line CHARS board + 4 TYPE CR ;
: .board CR 6 .line 11 .line 16 .line 21 .line ;
: fill-line ( index "abcd" -- ) board
BL PARSE 4 MIN ROT CHARS board + SWAP CMOVE ;
: fill-board board board-size ERASE
6 fill-line 11 fill-line 16 fill-line 21 fill-line .board ;
: rand-letter ( -- a-z ) 26 CHOOSE 1+ let>c ;
: rlc!+ ( sq -- sq+1 ) rand-letter OVER C! CHAR+ ;
: rand-line ( index -- )
CHARS board + rlc!+ rlc!+ rlc!+ rlc!+ DROP ;
: random-board board board-size ERASE
6 rand-line 11 rand-line 16 rand-line 21 rand-line .board ;
\ results (sorted list, unique words)
0 VALUE found-words
0 VALUE size-words \ allocated size
0 VALUE num-words
: grow-words
size-words 0= IF 16 DUP CELLS ALLOCATE DROP
ELSE size-words 2* found-words OVER CELLS RESIZE DROP
THEN TO found-words TO size-words ;
: allocate-string ( addr len -- c-str )
DUP 1+ ALLOCATE DROP DUP >R 2DUP C! CHAR+ SWAP CMOVE R> ;
: insert-word ( n addr len -- ) num-words size-words = IF grow-words THEN
allocate-string
SWAP DUP >R CELLS found-words +
DUP DUP CELL+ num-words R> - CELLS MOVE
! num-words 1+ TO num-words ;
: add-word ( addr len -- ) \ / binary search
2>R num-words 0 BEGIN 2DUP - WHILE
2DUP + 2/ DUP 2R@ ROT CELLS found-words + @ COUNT COMPARE
DUP 0= IF 2DROP 2DROP 2R> 2DROP EXIT THEN
0< IF ROT DROP SWAP ELSE 1+ SWAP DROP THEN
REPEAT DROP 2R> insert-word ;
: add-prefix prefix prefix-len @ add-word ;
: clear-words
num-words 0 ?DO
I CELLS found-words + @ FREE DROP
LOOP 0 TO num-words ;
: .words
num-words 0 ?DO
I CELLS found-words + @ COUNT TYPE SPACE
LOOP CR ;
\ smarts
: solve-square ( block sq -- block sq )
DUP C@ 0= IF EXIT THEN \ edge or already used
\ can traverse to letter on this square?
2DUP C@ c>let SWAP letter-in-block ?DUP 0= IF EXIT THEN
\ OK: add letter to prefix ( sq block-node )
OVER C@ prefix prefix-len+ C! 1 prefix-len +!
\ found a word?
DUP @ EOW IF min-len prefix-len @ <= IF add-prefix THEN THEN
\ no more suffixes?
@ Ind ?DUP 0= IF -1 prefix-len +! EXIT THEN
\ continue to surrounding squares
dawg@i OVER ( next-block next-sq )
0 OVER C! \ mark used
6 CHARS - RECURSE CHAR+ RECURSE CHAR+ RECURSE
3 CHARS + RECURSE 2 CHARS + RECURSE
3 CHARS + RECURSE CHAR+ RECURSE CHAR+ RECURSE
2DROP -1 prefix-len +! \ mark usable again
prefix prefix-len+ C@ OVER C! ;
: solve-line ( root sq -- root sq+5 )
solve-square CHAR+ solve-square CHAR+
solve-square CHAR+ solve-square CHAR+ CHAR+ ;
: solve-board
0 prefix-len !
clear-words dawg-root 6 CHARS board +
solve-line solve-line solve-line solve-line 2DROP
CR .words ;