home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.cs.arizona.edu
/
ftp.cs.arizona.edu.tar
/
ftp.cs.arizona.edu
/
icon
/
historic
/
v941.tgz
/
icon.v941src.tar
/
icon.v941src
/
ipl
/
progs
/
makepuzz.icn
< prev
next >
Wrap
Text File
|
2001-05-02
|
11KB
|
331 lines
############################################################################
#
# File: makepuzz.icn
#
# Subject: Program to make find-the-word puzzle
#
# Author: Richard L. Goerwitz
#
# Date: May 2, 2001
#
###########################################################################
#
# This file is in the public domain.
#
############################################################################
#
# Version: 1.19
#
###########################################################################
#
# This program doesn't do anything fancy. It simply takes a list
# of words, and constructs out of them one of those square
# find-the-word puzzles that some people like to bend their minds
# over. Usage is:
#
# makepuzz [-f input-file] [-o output-file] [-h puzzle-height]
# -w puzzle-width] [-t how-many-seconds-to-keep-trying]
# [-r maximum-number-of-rejects] [-s] [-d]
#
# where input-file is a file containing words, one to a line
# (defaults to &input), and output-file is the file you would like the
# puzzle written to (defaults to &output). Puzzle-height and width
# are the basic dimensions you want to try to fit your word game into
# (default 20x20). If the -s argument is present, makepuzz will
# scramble its output, by putting random letters in all the blank
# spaces. The -t tells the computer when to give up, and construct
# the puzzle (letting you know if any words didn't make it in).
# Defaults to 60 (i.e. one minute). The -r argument tells makepuzz to
# run until it arrives at a solution with number-of-rejects or less
# un-inserted words. -d turns on certain diagnostic messages.
#
# Most of these options can safely be ignored. Just type
# something like "makepuzz -f wordlist," where wordlist is a file
# containing about sixty words, one word to a line. Out will pop a
# "word-find" puzzle. Once you get the hang of what is going on,
# try out the various options.
#
# The algorithm used here is a combination of random insertions
# and mindless, brute-force iterations through possible insertion
# points and insertion directions. If you don't like makepuzz's per-
# formance on one run, run it again. If your puzzle is large, try
# increasing the timeout value (see -t above).
#
############################################################################
#
# Links: options, random, colmize
#
############################################################################
link options
link random
link colmize
global height, width, _debug_
procedure main(a)
local usage, opttbl, inputfile, outputfile, maxrejects, puzzle,
wordlist, rejects, master_list, word, timeout, x, y, l_puzzle,
l_wordlist, l_rejects, no_ltrs, l_no_ltrs, try, first_time
# Filename is the only mandatory argument; they can come in any order.
usage := "makepuzz [-f infile] [-o outfile] [-h height] [-w width] _
[-t secs] [-r rejects] [-s]"
# Set up puzzle height and width (default 20x20); set up defaults
# such as the input & output files, time to spend, target reject
# count, etc.
opttbl := options(a, "w+h+f:o:t+sr+d") # stop(usage)
width := \opttbl["w"] | 20
height := \opttbl["h"] | 20
timeout := &time + (1000 * (\opttbl["t"] | 60))
inputfile := open(\opttbl["f"], "r") | &input
outputfile := open(\opttbl["o"], "w") | &output
maxrejects := \opttbl["r"] | 0
_debug_ := \opttbl["d"] & try := 0
first_time := 1
# Set random number seed.
randomize()
# Read, check, and sort word list hardest to easiest.
master_list := list()
every word := "" ~== trim(map(!inputfile)) do {
upto(~(&lcase++&ucase), word) &
stop("makepuzz: non-letter found in ", word)
write(&errout, "makepuzz: warning, ",3 > *word,
"-letter word (", word, ")")
put(master_list, word)
}
master_list := sort_words(master_list)
if \_debug_ then write(&errout, "makepuzz: thinking...")
# Now, try to insert the words in the master list into a puzzle.
# Stop when the timeout limit is reached (see -t above).
until &time > timeout & /first_time do {
first_time := &null
wordlist := copy(master_list); rejects := list()
puzzle := list(height); every !puzzle := list(width)
blind_luck_insert(puzzle, wordlist, rejects)
brute_force_insert(puzzle, wordlist, rejects, timeout)
# Count the number of letters left over.
no_ltrs := 0; every no_ltrs +:= *(!wordlist | !rejects)
l_no_ltrs := 0; every l_no_ltrs +:= *(!\l_wordlist | !\l_rejects)
# If our last best try at making a puzzle was worse...
if /l_puzzle |
(*\l_wordlist + *l_rejects) > (*wordlist + *rejects) |
((*\l_wordlist + *l_rejects) = (*wordlist + *rejects) &
l_no_ltrs > no_ltrs)
then {
# ...then save the current (better) one.
l_puzzle := puzzle
l_wordlist := wordlist
l_rejects := rejects
}
# Tell the user how we're doing.
if \_debug_ then
write(&errout, "makepuzz: try number ", try +:= 1, "; ",
*wordlist + *rejects, " rejects")
# See the -r argument above. Stop if we get to a number of
# rejects deemed acceptable to the user.
if (*\l_wordlist + *l_rejects) <= maxrejects then break
}
# Signal to user that we're done, and set puzzle, wordlist, and
# rejects to their best values in this run of makepuzz.
write(&errout, "makepuzz: done")
puzzle := \l_puzzle
wordlist := \l_wordlist
rejects := \l_rejects
# Print out original word list, and list of words that didn't make
# it into the puzzle.
write(outputfile, "Original word list (sorted hardest-to-easiest): \n")
every write(outputfile, colmize(master_list))
write(outputfile, "")
if *rejects + *wordlist > 0 then {
write(outputfile, "Couldn't insert the following words: \n")
every write(outputfile, colmize(wordlist ||| rejects))
write(outputfile, "")
}
# Scramble (i.e. put in letters for remaining spaces) if the user
# put -s on the command line.
if \opttbl["s"] then {
every y := !puzzle do
every x := 1 to *y do
/y[x] := ?&ucase
# Print out puzzle structure (answers in lowercase).
every y := !puzzle do {
every x := !y do
writes(outputfile, \x | " ", " ")
write(outputfile, "")
}
write(outputfile, "")
}
# Print out puzzle structure, all lowercase.
every y := !puzzle do {
every x := !y do
writes(outputfile, map(\x) | " ", " ")
write(outputfile, "")
}
# Exit with default OK status for this system.
every close(inputfile | outputfile)
exit()
end
procedure sort_words(wordlist)
local t, t2, word, sum, l
# Obtain a rough character count.
t := table(0)
every t[!!wordlist] +:= 1
t2 := table()
# Obtain weighted values for each word, essentially giving longer
# words and words with uncommon letters the highest values. Later
# we'll reverse the order (-> hardest-to-easiest), and return a list.
every word := !wordlist do {
"" == word & next
sum := 0
every sum +:= t[!word]
insert(t2, word, (sum / *word) - (2 * *word))
}
t2 := sort(t2, 4)
l := list()
# Put the hardest words first. These will get laid down when the
# puzzle is relatively empty. Save the small, easy words for last.
every put(l, t2[1 to *t2-1 by 2])
return l
end
procedure blind_luck_insert(puzzle, wordlist, rejects)
local s, s2, s3, begy, begx, y, x, diry, dirx, diry2, dirx2, i
# global height, width
# Try using blind luck to make as many insertions as possible.
while s := get(wordlist) do {
# First try squares with letters already on them, but don't
# try every direction yet (we're relying on luck just now).
# Start at a random spot in the puzzle, and wrap around.
begy := ?height; begx := ?width
every y := (begy to height) | (1 to begy - 1) do {
every x := (begx to width) | (1 to begx - 1) do {
every i := find(\puzzle[y][x], s) do {
diry := ?3; dirx := ?3
s2 := s[i:0]
diry2 := 4 > (diry + 2) | 0 < (diry - 2) | 2
dirx2 := 4 > (dirx + 2) | 0 < (dirx - 2) | 2
s3 := reverse(s[1:i+1])
if insert_word(puzzle, s2, diry, dirx, y, x) &
insert_word(puzzle, s3, diry2, dirx2, y, x)
then break { break break next }
}
}
}
# If the above didn't work, give up on spaces with characters
# in them; use blank squares as well.
every 1 to 512 do
if insert_word(puzzle, s, ?3, ?3, ?height, ?width) then
break next
# If this word doesn't submit to easy insertion, save it for
# later.
put(rejects, s)
}
# Nothing useful to return (puzzle, wordlist, and rejects objects
# are themselves modified; not copies of them).
return
end
procedure brute_force_insert(puzzle, wordlist, rejects, timeout)
local s, start, dirs, begy, begx, y, x
# Use brute force on the remaining forms.
if *rejects > 0 then {
wordlist |||:= rejects; rejects := []
while s := pop(wordlist) do {
start := ?3; dirs := ""
every dirs ||:= ((start to 3) | (1 to start-1))
begy := ?height; begx := ?width
every y := (begy to height) | (1 to begy - 1) do {
if &time > timeout then fail
every x := (begx to width) | (1 to begx - 1) do {
if insert_word(puzzle, s, !dirs, !dirs, y, x) then
break { break next }
}
}
# If we can't find a place for s, put it in the rejects list.
put(rejects, s)
}
}
# Nothing useful to return (puzzle, wordlist, and rejects objects
# are themselves modified; not copies of them).
return
end
procedure insert_word(puzzle, s, ydir, xdir, y, x)
local incry, incrx, firstchar
# If s is zero length, we've matched it in it's entirety!
if *s = 0 then {
return
} else {
# Make sure there's enough space in the puzzle in the direction
# we're headed.
case ydir of {
"3": if (height - y) < (*s - 1) then fail
"1": if y < (*s - 1) then fail
}
case xdir of {
"3": if (width - x) < (*s - 1) then fail
"1": if x < (*s - 1) then fail
}
# Check to be sure everything's in range, and that both the x and
# y increments aren't zero (in which case, we aren't headed in any
# direction at all...).
incry := (ydir - 2); incrx := (xdir - 2)
if incry = 0 & incrx = 0 then fail
height >= y >= 1 | fail
width >= x >= 1 | fail
# Try laying the first char in s down at puzzle[y][x]. If it
# works, head off in some direction, and try laying down the rest
# of s along that vector. If at any point we fail, we must
# reverse the assignment (<- below).
firstchar := !s
((/puzzle[y][x] <- firstchar) | (\puzzle[y][x] == firstchar)) &
insert_word(puzzle, s[2:0], ydir, xdir, y + incry, x + incrx) &
suspend
fail
}
end