[QUIZ] Word Blender (#108)

R

Ruby Quiz

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Ben Bleything

This is a riff on the Jumble puzzle found in many (US) newspapers. More
specifically, it's based on the game TextTwist[1], made by GameHouse[2] and
published in various places around the web.

The mechanic of TextTwist is simple. The player is given six letters and is
tasked with unscrambling those letters into as many words as possible. If the
player can use all six letters in a word, they proceed to the next round.

Your task is to build the back-end engine to run a TextTwist clone. Effectively,
this means that you must generate a list of three- to six-letter words that can
all be constructed from the same six letters. This list must contain at least
one six-letter word.

Bonus points for building a completely functional game!

[1]: http://games.yahoo.com/games/texttwist.html (just one example, java)
[2]: http://www.gamehouse.com/
 
J

James Edward Gray II

Is the goal to get the most points or to get to the highest round?
Do you
get points based on the number of letters used (as in Upwords) or
do you get
points based on the obscurity of the letter to be used (as in
scrabble)?

My solution doesn't track "points" per say, just the rounds really.
So for my code, it's getting to the highest round.

Feel free to add some scoring though. I would probably score based
on words found, with bigger words earning more points.

James Edward Gray II
 
M

Matthew Moss

I'd be interested in trying this, but I've avoided dictionary-type
quizzes in the past for lack of a good dictionary file. Does anyone
have links to decent word/dictionary files? Or perhaps does Mac OS X
come with one?
 
O

Oin Maple

--nextPart2163778.e0IMhpiCrT
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

I'd be interested in trying this, but I've avoided dictionary-type
quizzes in the past for lack of a good dictionary file. Does anyone
have links to decent word/dictionary files? Or perhaps does Mac OS X
come with one?

I'm using the ones here: http://wordlist.sourceforge.net/=20
I don't know how good they are, but they're ok for this particular quiz IMH=
O=20

--nextPart2163778.e0IMhpiCrT
Content-Type: application/pgp-signature

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.3 (GNU/Linux)

iD4DBQBFnndpJRmulx2zJOARAjtAAJ9J2pbDNx9Ww5LZnV7Jd3NGDUAxzwCY/FOM
/aQXwyix/WsxNt9PBO9z1A==
=+WNu
-----END PGP SIGNATURE-----

--nextPart2163778.e0IMhpiCrT--
 
O

Oin Maple

--nextPart1609770.pp5jnVaF98
Content-Type: text/plain;
charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

did anyone find a unix-friendly text-twist?
the yahoo one doesn't like me: "Note: TextTwist is not compatible with Unix=
or=20
Macintosh computers."
and every other site with texttwist seems to use the yahoo one.

--nextPart1609770.pp5jnVaF98
Content-Type: application/pgp-signature

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.3 (GNU/Linux)

iD8DBQBFn6d7JRmulx2zJOARAuAkAJ0fvVPTuN8O7/P6xCJvDNlmXhvXOQCdHA5c
aZ0BgbI6cgjgGmCqlymS63c=
=Ddjm
-----END PGP SIGNATURE-----

--nextPart1609770.pp5jnVaF98--
 
I

Ionut Artarisi

--nextPart5383656.7x0zoIkBIs
Content-Type: multipart/mixed;
boundary="Boundary-01=_P/OoFcloq9N5gzb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

--Boundary-01=_P/OoFcloq9N5gzb
Content-Type: text/plain;
charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

This is my first ruby program that actually does all it's supposed to do. I=
t=20
doesn't have a lot of style, but hopefully my next programs will look bette=
r.=20
The wordlist is from wordlist.sourceforge.net and the first part of the=20
program reads the word.lst file and puts all the words in an array.

Hope it does what it's supposed to do, as i couldn't see the texttwist on=20
yahoo.
I'm also attaching the Unnoficial Jargon File Wordlist word.lst file.

=2DIonu=C5=A3 Ar=C5=A3=C4=83ri=C5=9Fi



--Boundary-01=_P/OoFcloq9N5gzb
Content-Type: application/x-ruby;
name="texttwist.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="texttwist.rb"

### getting words from the list in word.lst
# assuming that each word in the file is on a different line
# and using the Unofficial Jargon File Word Lists from http://wordlist.sourceforge.net
#
f = File.open("word.lst", "r")
fi = f.read.gsub("\n", " ").scan(/\w+/).to_a
f.close
allwords = Array.new
fi.each {|word| (if ((word.length >= 3) and (word.length <= 6))
allwords << word.upcase
end)
}
## now they're all in an array
gwords = Array.new
n = 6
while n > 2 do
gwords[n] = Array.new
allwords.each {|word| (if (word.scan(/./).length == n)
gwords[n] << word
end)
}
gwords[n].collect{|c| c.upcase!}
n -= 1
end
# code above sorts words with n letters into gwords[n]

i = 1
score = 0
star = ""
while (i == 1) do
puts "Pick a number between 0 and " + gwords[6].length.to_s
m = gets.chop
if (m != "")
n = m.to_i
wordie = gwords[6][n].to_s.scan(/./).sort_by{rand}.to_s.upcase # word scramble

puts "You have chosen the word: " + wordie
foo = 0
all = allwords.dup
while (foo == 0)

puts "Form words! Type EXIT0 to pick another word."

guess = gets.chop.upcase
o = 0
while (o < guess.length) # tests if the letters used are legal
guessy = guess.dup
gwords[6][n].to_s.scan(/./).collect{|e| (guessy = guessy.sub(e , ""))}
o += 1

end
if guessy == ""
if (all.include? guess) # tests if the guess is in the wordlist
all.delete(guess)
score += guess.length
puts "Your score is now: " + score.to_s

if (guess == gwords[6][n]) #tests and rewards if the guess is the original word
star << "*"
foo = 1337
puts " and you have " + star + " stars."
end
elsif (guess == "EXIT0")
foo = 1337
else
puts "Incorrect! Try Again!"
end

elsif (guess == "EXIT0")
foo = 1337
else
puts "The letters used were illegal."
end


end
gwords[6].delete_at(n)
else
i = 0
puts "Your final score was: " + score.to_s + " and you have gathered " + star + " stars. (One star for each original 6 letter word.)"
end
end
















--Boundary-01=_P/OoFcloq9N5gzb
Content-Type: text/plain;
charset="utf-8";
name="word.lst"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="word.lst"

AFAIK
AFAIKs
AI
AIDS
AIDSes
AIs
ANSI
ANSIs
ASCII
ASCIIs
Ada
Adas
Amiga
BASIC
BASICs
BBS
BBSes
BITNET
BITNETs
BLT
BLTs
BSD
BSDs
Borg
Borgs
C
COBOL
COBOLs
Cs
DDT
DDTs
DEC
DECed
DECing
DECs
DP
DPs
Datamation
Datamations
Dilbert
Dilberts
English
Englishes
Eris
Erises
FAQ
FAQs
FM
FMs
FUD
FUDs
G
GIGO
Guido
IBM
IMHO
Internet
Internets
Java
K
Knuth
Knuths
Ks
Linus
Linux
Linuxes
M
MEGO
MEGOs
MIPS
MIPSes
Mars
Marses
Microsoft
Multics
Multicses
N
NeWS
NeWSes
OS
OSes
OTOH
PARC
PARCs
PD
PM
PMed
PMing
PMs
Pascal
Pascals
Pentium
Pentiums
Perl
Perls
Python
QWERTY
RFC
RFCs
RTFM
RTFMed
RTFMing
RTFMs
SO
SOS
SOSes
SOs
Sun
Suns
T
TELNET
TELNETTed
TELNETTing
TELNETs
TeX
TeXes
URL
URLs
Unix
Unixes
Usenet
Usenets
VAX
VAXes
WYSIWYG
Winchester
Winchesters
X
Xes
YMMV
abbrev
abbrevs
accumulator
accumulators
acolyte
acolytes
admin
admins
alt
alts
amoeba
amoebae
amoebas
app
apps
arena
arenas
asbestos
atomic
avatar
avatars
backgammon
background
backgrounds
bandwidth
bandwidths
bang
bangs
banner
banners
bar
barf
barfed
barfing
barfs
barn
barney
barneys
barns
baroque
bars
batch
baud
bauds
bazaar
bazaars
beam
beamed
beaming
beams
beep
beeps
benchmark
benchmarks
beta
betas
bible
bibles
biff
biffed
biffing
biffs
bigot
bigots
bit
bits
blast
blasted
blasting
blasts
blat
blats
blink
blinked
blinking
blinks
blivet
blivets
block
blocked
blocking
blocks
boa
board
boards
boas
bob
bobs
bogus
boink
boinked
boinking
boinks
bomb
bombed
bombing
bombs
boot
booted
booting
boots
bot
bounce
bounced
bounces
bouncing
boustrophedon
boustrophedons
box
boxen
boxes
break
breaking
breaks
brittle
brittler
brittlest
broke
broken
browser
browsers
bug
bugs
bulletproof
bum
bummed
bumming
bump
bumped
bumping
bumps
bums
burble
burbled
burbles
burbling
buzz
buzzed
buzzes
buzzing
byte
bytes
calculator
calculators
can
canned
canning
canonical
cans
cascade
cascades
cat
catatonic
cathedral
cathedrals
cats
catted
catting
chad
chads
chain
chained
chaining
chains
channel
channels
char
chars
check
checks
cheerfully
chemist
chemists
choke
choked
chokes
choking
chomp
chomped
chomper
chompers
chomping
chomps
chrome
chromes
chug
chugged
chugging
chugs
clean
cleaned
cleaner
cleanest
cleaning
cleans
clobber
clobbered
clobbering
clobbers
clock
clocked
clocking
clocks
clone
cloned
clones
cloning
coaster
coasters
code
codes
compact
compacter
compactest
compo
compos
compress
compressed
compresses
compressing
con
condom
condoms
confuser
confusers
cons
consed
conses
consing
console
consoles
cookbook
cookbooks
cookie
cookies
copper
coppers
core
cores
cowboy
cowboys
cracker
crackers
cracking
crackings
crank
cranked
cranking
cranks
crash
crashed
crashes
crashing
cray
crayola
crayolas
crayon
crayons
crays
creationism
creationisms
creep
creeping
creeps
crept
cretin
cretinous
cretins
crippleware
cripplewares
crock
crocks
crumb
crumbs
crunch
crunched
crunches
crunching
cube
cubes
cubing
cubinged
cubinging
cubings
cyberpunk
cyberpunks
cyberspace
cyberspaces
cycle
cycled
cycles
cycling
daemon
daemons
dd
dded
dding
dds
dead
deader
deadest
deadlock
deadlocks
decay
decays
deckle
deckles
defenestration
defenestrations
delta
deltas
demented
demigod
demigods
demo
demoed
demoing
demon
demons
demos
deprecated
diddle
diddled
diddles
diddling
die
died
dies
diff
diffed
diffing
diffs
digit
digits
dike
diked
dikes
diking
ding
dings
dink
dinker
dinkest
dinosaur
dinosaurs
disclaimer
disclaimers
distribution
distributions
doc
docs
documentation
documentations
dodgier
dodgiest
dodgy
dongle
dongles
donuts
donutses
doorstop
doorstops
down
downed
downing
download
downloaded
downloading
downloads
downs
dragon
dragons
drain
drained
draining
drains
driver
drivers
droid
droids
drone
drones
drugged
drum
drums
dump
dumps
dying
earthquake
earthquakes
echo
echoes
echos
ed
eds
elegant
elephantine
elite
elvish
elvishes
email
emailed
emailing
emails
emoticon
emoticons
empire
empires
engine
engines
enhancement
enhancements
epoch
epochs
epsilon
epsilons
erotics
eroticses
evil
eviler
evilest
eviller
evillest
excl
excls
exec
execked
execking
execs
exploit
exploits
factor
factors
fairings
fairingses
fan
fans
faradize
faradized
faradizes
faradizing
farming
farmings
fascist
faultier
faultiest
faulty
feature
features
fence
fences
filter
filters
fine
finer
finest
finger
fingered
fingering
fingers
firefighting
firefightings
firmware
firmwares
fish
fishes
fix
fixes
flag
flags
flakier
flakiest
flaky
flame
flamed
flamer
flamers
flames
flaming
flap
flapped
flapping
flaps
flat
flatten
flattened
flattening
flattens
flatter
flattest
flavor
flavorful
flavors
flippies
flippy
flood
flooded
flooding
floods
flowchart
flowcharts
flush
flushed
flushes
flushing
flytrap
flytraps
followup
followups
foo
fool
fools
footprint
footprints
fora
foreground
foregrounded
foregrounding
foregrounds
forked
forum
forums
fossil
fossils
frag
fragile
fragiler
fragilest
frags
freeware
freewares
freeze
freezes
freezing
fried
fries
frog
frogging
frogginged
frogginging
froggings
frogs
froze
frozen
fry
frying
fudge
fudged
fudges
fudging
fum
fums
funkier
funkiest
funky
fuzzball
fuzzballs
gag
gagged
gagging
gags
gas
gaseous
gases
gassed
gasses
gassing
gen
generate
generated
generates
generating
gens
gig
gigs
gillion
gillions
glass
glasses
glitch
glitched
glitches
glitching
glob
globed
globing
globs
glue
glues
gnarlier
gnarliest
gnarly
gobble
gobbled
gobbles
gobbling
golden
goldener
goldenest
gonk
gonked
gonking
gonks
gonzo
gopher
gophers
gorp
gorps
gotcha
gotchas
gribble
gribbles
grind
grinding
grinds
grok
grokked
grokking
groks
ground
grovel
groveled
groveling
grovelled
grovelling
grovels
grue
grues
grunge
grunges
gun
gunned
gunning
guns
guru
gurus
h
hack
hacked
hacker
hackers
hacking
hacks
hair
hairball
hairballs
hairier
hairiest
hairs
hairy
hammer
hammered
hammering
hammers
hamster
hamsters
handle
handles
handshaking
handshakings
hang
hanged
hanging
hangs
happily
hardwired
hat
hats
heartbeat
heartbeats
heavyweight
hex
hexadecimal
hexadecimals
hexes
highly
hing
hings
hirsute
hoarding
hoardings
hobbit
hobbits
hog
hogs
hole
holes
hook
hooks
hop
hopped
hopping
hops
hose
hosed
hoses
hosing
hotlink
hotlinks
huff
huffed
huffing
huffs
hung
hyperspace
hyperspaces
ice
ices
idempotent
inc
incantation
incantations
inced
incing
include
included
includes
including
incs
infinite
infinities
infinity
inflate
inflated
inflates
inflating
interesting
interrupt
interrupts
intro
intros
iron
ironmonger
ironmongers
irons
jaggies
jaggieses
jello
jellos
jiffies
jiffy
jock
jocks
kahuna
kahunas
ken
kens
kick
kicked
kicking
kicks
kit
kits
kludge
kludged
kludges
kludging
kluge
kluged
kluges
kluging
knobs
koan
koans
lag
lags
lamer
lamers
lase
lased
lases
lasing
laundromat
laundromats
leak
leaks
leech
leeches
legal
legalese
legaleses
letterbomb
letterbombs
life
lightweight
lint
linted
linting
lints
live
liver
lives
livest
liveware
livewares
lobotomies
lobotomy
logical
lose
loser
losers
loses
losing
loss
losses
lost
lurker
lurkers
machinable
macro
macrologies
macrology
macros
magic
magics
mailbomb
mailbombed
mailbombing
mailbombs
mainframe
mainframes
management
managements
manged
mangeds
mangle
mangled
mangler
manglers
mangles
mangling
marbles
marginal
marginally
martian
martians
massage
massaged
massages
massaging
meg
megs
meme
memes
meta
mickey
mickeys
microfloppies
microfloppieses
minifloppies
minifloppieses
misfeature
misfeatures
mockingbird
mockingbirds
mod
modded
modding
mode
modes
mods
modulo
monstrosities
monstrosity
mu
multitask
multitasks
mumble
munch
munched
munches
munching
munchkin
munchkins
mundane
mundanes
mung
munged
munging
mungs
music
musics
mutter
muttered
muttering
mutters
naive
naiver
naivest
nanobot
nanobots
nanotechnologies
nanotechnology
nature
natures
neophilia
neophilias
nerd
nerds
netiquette
netiquettes
netter
netters
newbie
newbies
newsgroup
newsgroups
nick
nickle
nickles
nicks
noddy
node
nodes
nonlinear
nontrivial
notwork
notworks
nude
nuder
nudest
nuke
nuked
nukes
nuking
numbers
numberses
nybble
nybbled
nybbles
nybbling
nyetwork
nyetworks
obscure
obscurer
obscurest
offline
op
open
opens
ops
optimism
optimisms
orphan
orphans
orthogonal
overrun
overruns
parse
parsed
parses
parsing
pastie
pasties
patch
patched
patches
patching
path
pathological
paths
payware
paywares
peek
peeks
peon
peons
pessimal
pessimaled
pessimaling
pessimals
phage
phages
phase
phases
phreaking
phreakings
ping
pinged
pinging
pings
pipe
pipes
pistol
pistols
playpen
playpens
plonk
plonked
plonking
plonks
plumbing
plumbings
pod
pods
poke
pokes
poll
polled
polling
polls
pop
popped
popping
pops
poser
posers
post
posted
posting
postings
postmaster
postmasters
posts
priesthood
priesthoods
print
printed
printing
prints
profile
profiles
program
programming
programmings
programs
proprietary
protocol
protocols
prowler
prowlers
pseudo
pseudos
puff
puffed
puffing
puffs
punt
punted
punting
punts
push
pushed
pushes
pushing
quad
quads
quantifiers
quarter
quarters
ques
queses
quine
quines
quotient
quotients
random
randomness
randomnesses
randoms
rape
raped
rapes
raping
rave
raved
raves
raving
real
realer
realest
reaper
reapers
recursion
recursions
replicator
replicators
replies
reply
restriction
restrictions
rip
ripoff
ripoffs
ripped
ripping
rips
roach
roached
roaches
roaching
robot
robots
robust
robuster
robustest
rococo
rogue
rogues
root
roots
rude
ruder
rudest
runes
runic
sacred
saga
sagas
said
salt
salts
samizdat
samizdats
samurai
samurais
sandbox
sandboxes
say
saying
says
scag
scagged
scagging
scags
scratch
scratched
scratches
scratching
screen
screens
screw
screws
scribble
scribbles
scrog
scrogged
scrogging
scrogs
segment
segmented
segmenting
segments
selvage
selvages
semi
semis
server
servers
shareware
sharewares
shebang
shebangs
shell
shells
shim
shims
showstopper
showstoppers
shriek
shrieks
sidecar
sidecars
silicon
silicons
silo
silos
skulker
skulkers
slab
slabbed
slabbing
slabs
slack
slacks
slash
slashes
sleep
sleeping
sleeps
slept
slim
slims
slop
slops
slurp
slurped
slurping
slurps
smart
smarter
smartest
smiley
smileys
smoke
smoked
smokes
smoking
smurf
smurfs
snail
snailed
snailing
snails
snap
snapped
snapping
snaps
snarf
snarfed
snarfing
snarfs
snark
snarks
sneaker
sneakers
sniff
sniffed
sniffing
sniffs
softies
softy
spam
spammed
spamming
spams
spangle
spangles
spawn
spawns
speedometer
speedometers
spell
spells
spiffier
spiffiest
spiffy
spike
spiked
spikes
spiking
spin
spinning
spins
splat
splats
spoiler
spoilers
sponge
sponges
spoof
spoofed
spoofing
spoofs
spool
spooled
spooling
spools
spun
stack
stacks
state
states
stoppage
stoppages
store
stores
stroke
strokes
strudel
strudels
studlier
studliest
studly
stunning
suit
suits
sunspots
sunspotses
support
supports
surf
surfed
surfing
surfs
swab
swabbed
swabbing
swabs
swap
swapped
swapping
swaps
swizzle
swizzled
swizzles
swizzling
sync
syncs
sysop
sysops
system
systems
tanked
taste
tastes
tee
tees
tense
tenser
tensest
tentacle
tentacles
test
tests
text
texts
theologies
theology
theories
theory
thrash
thrashed
thrashes
thrashing
thread
threads
thud
thuds
thumb
thumbs
thunk
thunks
tick
ticks
toad
toadded
toadding
toads
toast
toasted
toaster
toasters
toasting
toasts
toggle
toggled
toggles
toggling
tool
tooled
tooling
tools
tourist
touristic
tourists
toy
toys
trampoline
trampolines
trap
trapped
trapping
traps
trash
trashed
trashes
trashing
trawl
trawled
trawling
trawls
trivial
troglodyte
troglodytes
troll
trolled
trolling
trolls
tron
tronned
tronning
trons
tube
tubes
tune
tuned
tunes
tuning
tweak
tweaked
tweaking
tweaks
tweeter
tweeters
twiddle
twiddled
twiddles
twiddling
twink
twinks
uninteresting
up
upload
uploaded
uploading
uploads
upped
upping
ups
urchin
urchins
user
users
vanilla
vaporware
vaporwares
var
vars
verbiage
verbiages
videotex
videotexes
virgin
virtual
virus
viruses
visionaries
visionary
voice
voiced
voices
voicing
wabbit
wabbits
waldo
waldoes
waldos
walk
walks
wall
walled
walling
wallpaper
wallpapers
walls
wank
wanked
wanking
wanks
wannabee
wannabees
warez
warezes
wart
warts
weasel
weasels
wedged
wedgie
wedgies
weeds
weedses
weenie
weenies
wetware
wetwares
whack
whacked
whacker
whackers
whacking
whacks
whales
whaleses
wheel
wheels
widget
widgets
wiggles
wiggleses
win
winner
winners
winning
wins
wired
wireds
wizard
wizardly
wizards
womble
wombles
won
wonkier
wonkiest
wonky
woofer
woofers
workaround
workarounds
worm
wormhole
wormholes
worms
zap
zapped
zapping
zaps
zen
zenned
zenning
zens
zero
zeroed
zeroes
zeroing
zeros
zeroth
zigamorph
zigamorphs
zip
zipped
zipping
zips
zombie
zombies
zorch
zorched
zorches
zorching

--Boundary-01=_P/OoFcloq9N5gzb--

--nextPart5383656.7x0zoIkBIs
Content-Type: application/pgp-signature

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.3 (GNU/Linux)

iD8DBQBFoO/WJRmulx2zJOARArIJAJ4wHYy+m06mUr6eJimOXCYW3Frh1wCfVRwV
58cRuRlhVne0tB3yC/pXKjQ=
=IWAx
-----END PGP SIGNATURE-----

--nextPart5383656.7x0zoIkBIs--
 
J

James Edward Gray II

Here's the solution I wrote while considering this quiz (it requires
Unix):

#!/usr/bin/env ruby -w

require "io/wait"

# game date cache
CACHE_FILE = ".game_words"

if File.exist? CACHE_FILE # load from cache
word_list = File.open(CACHE_FILE) { |file| Marshal.load(file) }
else # build word list
# prepare data structure
words_by_signature = Hash.new { |words, sig| words[sig] = Array.new }

# read dictionary
File.foreach(ARGV.shift || "/usr/share/dict/words") do |word|
word.downcase!
word.delete!("^a-z")

next unless word.length.between? 3, 6

(words_by_signature[word.split("").sort.join] << word).uniq!
end

# prepare recursive signature search
def choices(sig, seen = Hash.new { |all, cur| all[cur] = true;
false }, &blk)
sig.length.times do |i|
shorter = sig[0...i] + sig[(i+1)...sig.length]
unless seen[shorter]
blk[shorter]
choices(shorter, seen, &blk) unless shorter.length == 3
end
end
end

# prepare game data structure
word_list = Hash.new

# build game choices
words_by_signature.keys.grep(/\A.{6}\Z/) do |possible|
word_list[possible] = words_by_signature[possible]

choices(possible) do |shorter_signature|
if words_by_signature.include? shorter_signature
word_list[possible].push(*words_by_signature
[shorter_signature])
end
end
end

# cache for faster loads
File.open(CACHE_FILE, "w") { |file| Marshal.dump(word_list, file) }
end

### game interface (requires Unix) ###
TERMINAL_STATE = `stty -g`
system "stty raw -echo cbreak"
at_exit { system "stty #{TERMINAL_STATE}" }
clear = `clear`

# a raw mode savvy puts
def out(*args) print(*(args + ["\r\n"])) end

# for easy selection
words = word_list.keys

rounds = 0
loop do
# select letters
letters = current = words[rand(words.size)]
while word_list.include? letters
letters = letters.split("").sort_by { rand }.join
end
letters.gsub!(/.(?=.)/, '\0 ')

# round data
advance = false
matches = Array.new
current_match = String.new
start = Time.now
message = nil
last_update = start - 1

# round event loop
until Time.now >= start + 2 * 60
# game display
if last_update <= Time.now - 1
print clear

out "Your letters: #{letters}"
out " Time left: #{120 - (Time.now - start).round} seconds"
out " Your words: #{matches.join(', ')}"
out
unless message.nil?
out message
out
end
print current_match
$stdout.flush

last_update = Time.now
end

# input handler
if $stdin.ready?
char = $stdin.getc
case char
when ?a..?z, ?A..?Z # read input
current_match << char.chr.downcase
message = nil
last_update = start - 1
when ?\b, 127 # backspace/delete
current_match = current_match[0..-2]
message = nil
last_update = start - 1
when ?\r, ?\n # test entered word
if word_list[current].include? current_match
matches << current_match
matches = matches.sort_by { |word| [word.size, word] }
if not advance and current_match.length == 6
advance = true
message = "You will advance to the next round!"
else
message = nil
end
else
message = "Unknown word."
end
current_match = String.new
last_update = start - 1
end
end
end

# round results
print clear
missed = word_list[current] - matches
unless missed.empty?
out "Other words using \"#{letters}:\""
out missed.sort_by { |word| [word.size, word] }.join(", ")
out
end
if advance
rounds += 1

out "You made #{matches.size} word#{'s' if matches.size != 1}, ",
"including at least one six letter word. Nice work."
out "Press any key to begin the next round."

$stdin.getc
else
out "You made #{matches.size} word#{'s' if matches.size != 1}, ",
"but failed to find a six letter word."

break # end game
end
end

# game results
out "You completed #{rounds} round#{'s' if rounds != 1}. Thanks for
playing."

__END__

James Edward Gray II
 
B

Ben Bleything

--d6Gm4EdcadzBjdND
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

did anyone find a unix-friendly text-twist?
the yahoo one doesn't like me: "Note: TextTwist is not compatible with Un= ix or=20
Macintosh computers."
and every other site with texttwist seems to use the yahoo one.

It's just a java game. I'm on a mac and it works fine. If you have a
current JRE and an appropriate browser plugin, it should work.

Ben

--d6Gm4EdcadzBjdND
Content-Type: application/pgp-signature; name="signature.asc"
Content-Description: Digital signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----

iD8DBQFFoUqnNRFiSXP2RtkRAoqpAJ0VbPkdrO+2dv9pwYb0PYitbVgTJgCeJxij
3/KJu7f9mQU/sypAh+xh/4Y=
=v6gQ
-----END PGP SIGNATURE-----

--d6Gm4EdcadzBjdND--
 
B

Ben Ford

Here is my solution. Takes the filename of the dictionary to use as the
first argument and optionally a word to solve for as the second
argument. If no second argument is provided, you play the game.

--

class String
def lettercount
split(//).uniq.map{|c| [c, count(c)]}
end
def fisher_yates_shuffle
a = self.dup
(length-1).downto(0){|i|
j = rand(i+1)
a, a[j] = a[j], a if i != j
}
a
end
end

class Array
def random_element
self[rand(length)]
end
end

class Dictionary
def initialize(filename)
@words = []
IO.foreach(filename) do |line|
word = line.chomp
@words << word.downcase if word.length.between?(3, 6)
end
end
def blend(word)
@words.select{|x|
x.count(word.downcase) == x.length &&
x.lettercount.all?{|c, n|
n <= word.downcase.lettercount.assoc(c).last }
}
end
def randomword
@words.select{|x| x.length == 6}.random_element
end
end

class WordBlender
def initialize(dictionary)
@dictionary = dictionary
end
def blend_to_s(word)
word_blend = @dictionary.blend(word)
puts "WordBlender: '#{word}' has #{word_blend.length} answers."
puts
max = -1
word_blend.sort_by{|x| [x.length, x]}.each do |x|
if x.length > max
max = x.length
puts "Words of length #{max}:"
end
puts " #{x}"
end
end
def play
puts "Welcome to WordBlender! (enter a blank line to quit)"
puts
round = 0
points = 0
continue = true
while continue do
points = points + 10 * round
round = round + 1
word = @dictionary.randomword
word_blend = @dictionary.blend(word)
word_shuffled = word.fisher_yates_shuffle
puts "Round: #{round} - Blend: '#{word_shuffled}' - Total Score:
#{points}"
current_word = ""
current_words = []
current_continue = true
while continue && current_continue do
current_word = STDIN.gets.chomp.downcase
if current_word == ""
puts
puts "Final Word: '#{word}' - Final Score: #{points}"
continue = false
elsif current_words.include?(current_word)
puts "'#{current_word}' already used."
elsif word_blend.include?(current_word)
current_words << current_word
points = points + current_word.length * current_word.length
current_continue = (current_word.length < word.length)
elsif current_word.count(word) == current_word.length
puts "'#{current_word}' not in dictionary."
else
puts "'#{current_word}' not found in '#{word_shuffled}'."
end
end
end
end
end

if ARGV.size == 0
puts "Usage: wordblender.rb <filename> - play WordBlender with the
specified dictionary"
puts "Usage: wordblender.rb <filename> <word> - show all blends for
the word using the dictionary"
elsif ARGV.size == 1
WordBlender.new(Dictionary.new(ARGV[0])).play
elsif ARGV.size >= 2
WordBlender.new(Dictionary.new(ARGV[0])).blend_to_s(ARGV[1])
end


The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Ben Bleything

This is a riff on the Jumble puzzle found in many (US) newspapers. More
specifically, it's based on the game TextTwist[1], made by GameHouse[2] and
published in various places around the web.

The mechanic of TextTwist is simple. The player is given six letters and is
tasked with unscrambling those letters into as many words as possible. If the
player can use all six letters in a word, they proceed to the next round.

Your task is to build the back-end engine to run a TextTwist clone. Effectively,
this means that you must generate a list of three- to six-letter words that can
all be constructed from the same six letters. This list must contain at least
one six-letter word.

Bonus points for building a completely functional game!

[1]:http://games.yahoo.com/games/texttwist.html(just one example, java)
[2]:http://www.gamehouse.com/
 
E

Eric I.

This solution simply chooses a six-letter word (or allows the user to
choose one) and then displays a list of words that can be composed with
a subset of the letters (size >= 3). Here is output from a sample run:

ape
lap
pal
pea
sap
sea
see
else
leap
pale
peas
peel
sale
seal
slap
sleep
asleep
please

Eric
------------
Interested in Ruby training with a well-reviewed instructor and
training materials? www.LearnRuby.com
============

# Given an array of letters, a whole/partial word built up so far, and
# a hash, adds to the hash all permutations of subsets built from the
# partial word and the array of letters. If a block is given it acts
# as a filter since the words must produce a true result when submitted
# to the block in order to be added to the hash.
def permute(letters, word, possible_words, &filter_block)
possible_words[word] = true if filter_block.nil? ||
filter_block.call(word)
return if letters.empty?

letters.each_with_index do |letter, i|
(new_letters = letters.dup).delete_at(i)
permute(new_letters, word + letter, possible_words, &filter_block)
end
end

# Verify that a filename was provided as the first argument and that
# it is a readable file
if ARGV[0].nil?
$stderr.puts("Usage: #{$0} dictionary-file [word]")
exit 1
elsif ! File.file?(ARGV[0]) || ! File.readable?(ARGV[0])
$stderr.puts("Error: \"#{ARGV[0]}\" is not a readable file.")
exit 2
end

# Build list of all six-letter words from dictionary file
words6 = Array.new
open(ARGV[0], "r") do |f|
f.each_line { |w| words6 << w if w.chomp! =~ /^[a-z]{6}$/ }
end

# Determine whether a random six-letter word is chosen or the user
# specifies one.
if ARGV[1]
# user attempted to specify a word; check its validity
if words6.include?(ARGV[1])
word = ARGV[1]
else
$stderr.puts("Error: \"#{ARGV[1]}\" is not a known six-letter
word.")
exit 3
end
else
word = words6[rand(words6.size)] # choose a random word
end

# Generate a hash of all three- to six-letter permutations using the
# letters of the chosen six-letter word. Note: most will not be valid
# words.
possible_words = Hash.new
permute(word.split(""), "", possible_words) { |w| w.length >= 3 }

# Generate a list of all valid words that are also permutations of
# subsets of the chosen six-letter word. This is done by
# re-reading the word file and testing each word against the
# possible permutations.
actual_words = Array.new
open(ARGV[0], "r") do |f|
f.each_line { |w| actual_words << w if possible_words[w.chomp!] }
end

# Display the resulting actual words sorted first by length and then
# alphabetically.
puts actual_words.sort_by { |w| [w.length, w] }
 
D

Dan Manges

Here is my solution. I'm not too good with regular expressions, so the
word selection script is a little bit slow.


#!/usr/bin/env ruby
#
# Author: Dan Manges - http://www.dcmanges.com
# Ruby Quiz #108 - http://rubyquiz.com/quiz108.html

class WordList
include Enumerable

attr_accessor :file
attr_reader :filters

def initialize(file = nil)
@file, @filters = file, []
end

def each
File.open(@file, "r") do |file|
while line = file.gets
yield apply_filters(line.chomp)
end
end
end

protected

def apply_filters(word)
@filters.inject(word) do |word, filter|
filter.call(word)
end
end

end

# Module to select words based on length and letter composition.
module WordFinder
# Finds words of length +size+ which can be composed with the letters
in +base_word+
def find_words(size, base_word)
letter_counts = base_word.split(//).inject(Hash.new(0)) {
|hash,letter| hash[letter] += 1; hash }
regexp = Regexp.new("^" + letter_counts.map { |letter,count|
"#{letter}{0,#{count}}"}.sort.join + "$")
select { |word| word.to_s.size == size && word.split(//).sort.join
=~ regexp }
end

# Finds a random word of the given size
def random_word_of_size(size)
words = find_words(size, (('a'..'z').to_a * 3).join)
words[rand(words.size)]
end
end

WordList.send:)include, WordFinder)

# Dictionary file from: http://wordlist.sourceforge.net/
# http://prdownloads.sourceforge.net/wordlist/alt12dicts-4.tar.gz
@wordlist = WordList.new("/Users/dan/Desktop/alt12dicts/2of12full.txt")
# This particular wordlist has an offset
@wordlist.filters << lambda { |word| word[17..-1] }
# Skip proper names, contractions, etc.
@wordlist.filters << lambda { |word| word =~ /^[a-z]+$/ ? word : "" }

module WordBlender
class Round
def initialize(wordlist, word_size = (3..6))
@wordlist = wordlist
@min_size, @max_size = word_size.first, word_size.last
@qualified = false
@hits = Hash.new { |h,k| h[k] = [] }
load_words
end

def qualified?
@qualified
end

def guess?(word)
word = word.to_s.strip
dup?(word) || qualify?(word) || hit?(word) || :miss
end

def letters
@base.split(//).sort
end

def status
result = []
@min_size.upto(@max_size) do |size|
result << [size, @hits[size].size, @words[size].size]
end
result.map { |data| "#{data[0]} letters: #{data[1]} of
#{data[2]}"}.join(", ")
end

protected

def dup?(word)
:dup if @hits[word.size].include?(word)
end

def qualify?(word)
if @words[word.size].include?(word) and word.size == @max_size
@hits[word.size] << word
@qualified = true
:qualify
end
end

def hit?(word)
if @words[word.size].include?(word)
@hits[word.size] << word
:hit
end
end

def load_base_word
@base = @wordlist.random_word_of_size(@max_size)
end

def load_words
@words = Hash.new([])
load_base_word
@min_size.upto(@max_size) do |size|
@words[size] = @wordlist.find_words(size, @base)
end
end
end

class Game
def initialize(wordlist)
@wordlist = wordlist
reset
end

def start!
help
start_round
print 'guess> '
while input = gets
input = input.strip
break if input == ".quit"
if input[0,1] == "." && respond_to?(input[1..-1])
send(input[1..-1])
print 'guess> '
next
end
result = @round.guess?(input)
puts case result
when :miss
"Wrong!"
when :dup
"Already guessed that!"
when :hit
"You got it!"
when :qualify
"You got it! And you qualify for the next round!"
end + " " + input
status unless result == :miss
print 'guess> '
end
puts "Goodbye!"
end
alias :play! :start!

protected

def letters
puts "Available Letters: " + @round.letters.sort_by {rand}.join(',
')
end

def next
if @round.qualified?
start_round
else
puts "You have not yet qualified for the next round!"
end
end

def help
puts <<-END_HELP
When prompted, either enter a word or a command.
The following commands are available:
.quit => quits the game
.help => display this help
.next => goes to the next round (if qualified)
.letters => display available letters
.status => show the current status of this round
END_HELP
end

def reset
@round_number = 0
end

def start_round
@round_number += 1
@round = Round.new(@wordlist)
puts "Beginning Round #{@round_number}!"
letters
end

def status
puts @round.status
end
end
end

@blender = WordBlender::Game.new(@wordlist)
@blender.play!
 
M

Martin Boese

My game will play almost like the one you can play at yahoo without score. Has
cheating and giving-up commands and draws possible solutions on the screen.
Run it with wordblender.rb <dictfile>.

martin



# wordblender.rb
#
# Usage: wordblender.rb [dictfile]
#

class String
# Checks if string can be build out of these characters.
#
# "hello".build_outof?("llohe") => true
# "world".build_outof?("dlrowl") => true
def build_outof?(other)
return false if self.length > other.length
o = other.clone
self.each_byte do |c|
return false unless o.include?(c.chr)
o[o.index(c.chr)] = 0
end
true
end

# Shuffle a word.
#
# "hello".shuffle => "oellh"
def shuffle
return self.scan(/./).sort_by{rand}.to_s
end
end

class WordBlenderGame

attr_reader :words

# limits for words for the game
MINCHARACTERS = 3
MAXCHARACTERS = 6

# time limit per game
TIME_PER_GAME = 90

# how to display the board
DISPLAY_COLUMNS = 5

# read the dictionary from a file.
# we also keep words with length of MAXCHARACTERS to find
# good initial letters quickly.
def initialize(dictionary)
@words, @maxwords = [], []
File.open(dictionary).each do |line|
l = line.strip.downcase
@words << l if (l.length >= MINCHARACTERS && l.length <= MAXCHARACTERS)
@maxwords << l if l.length == MAXCHARACTERS
end
end

# this generates a bunch of letters to play with and looks up words
# that can be build by them from the dictionary ("candidates").
def prepare_game()
@letters = @maxwords[rand(@maxwords.size-1)].shuffle
@candidates = []
@words.each { |w| @candidates << w if w.build_outof?(@letters) }
@candidates = @candidates.uniq # this fixed duplicated entries
@candidates = @candidates.sort {|x,y| x.length <=> y.length }
@found_candidates = @candidates.collect { false }
end

#
# This is to display the candidates to the screen. Draws it into columns
# and returns a string.
#
def get_board(solution=false, title="Words to find")
result = "" ; i = 0
sempty = ' '*(DISPLAY_COLUMNS*(MAXCHARACTERS+2))
s = String.new(sempty)
result << title << ":\n"

@found_candidates.each_index do |idx|
f = @found_candidates[idx] || solution
s[i.modulo(DISPLAY_COLUMNS)*(MAXCHARACTERS+2)] =
f ? "[#{@candidates[idx]}]" : "["+(' '*@candidates[idx].length)+"]"
if i.modulo(DISPLAY_COLUMNS) == DISPLAY_COLUMNS-1 then
result << (s + "\n")
s = String.new(sempty)
end
i+=1
end
result << s if s.include?('[')
result << "\n"
end


# This plays one round of the game, returns true if won
def play
self.prepare_game
message = "Press RETURN to shuffle the letters, '!' to give up, '?' to
cheat."

# start the time.
@time = TIME_PER_GAME
timer = Thread.new { while true do @time-=1; sleep 1 end }

# game loop
while @found_candidates.include?(false) do

# print board and other stuff
puts get_board
puts
puts "Time: " + @time.to_s
puts "Msg: " + message if message != ''
puts "Use: " + @letters
print "Try: "

# get user's guess and handle it
$stdout.flush
s = STDIN.gets.downcase.strip

if @time <= 0 then
puts "Time's up!"
break
end

if s == "" then
@letters = @letters.shuffle
message = "Letters shuffled!"
next
end

break if s == '!'

if s == '?' then
puts get_board(true)
message = "Cheater!"
next
end

if !s.build_outof?(@letters) then
message = "Invalid word!"
next
end

if @candidates.include?(s) then
@found_candidates[@candidates.index(s)] = true
message = "#{s} Found!"
else
message = "#{s} not listed!"
end
end

Thread.kill(timer)

# print solution
puts get_board(true, "Solution is")

# Check if player found a word with all characters
@found_candidates.each_index do |idx|
return true if @found_candidates[idx] && @candidates[idx].length ==
MAXCHARACTERS
end
false
end
end

print "Loading game...";$stdout.flush
game = WordBlenderGame.new(ARGV[1] || '/usr/share/dict/words')
puts "#{game.words.size} words found."

while game.play do
puts "You won, press any key to play next round."
gets
end

puts "Game over!"

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz
until 48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone on Ruby Talk follow the discussion. Please reply to the original
quiz message, if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
=-=-=

by Ben Bleything

This is a riff on the Jumble puzzle found in many (US) newspapers. More
specifically, it's based on the game TextTwist[1], made by GameHouse[2] and
published in various places around the web.

The mechanic of TextTwist is simple. The player is given six letters and is
tasked with unscrambling those letters into as many words as possible. If
the player can use all six letters in a word, they proceed to the next
round.

Your task is to build the back-end engine to run a TextTwist clone.
Effectively, this means that you must generate a list of three- to
six-letter words that can all be constructed from the same six letters.
This list must contain at least one six-letter word.

Bonus points for building a completely functional game!

[1]: http://games.yahoo.com/games/texttwist.html (just one example, java)
[2]: http://www.gamehouse.com/
 
I

Ionut Artarisi

--nextPart17441531.fdFuUJYqkk
Content-Type: multipart/mixed;
boundary="Boundary-01=_XHkoFct0nb8S5KP"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

--Boundary-01=_XHkoFct0nb8S5KP
Content-Type: text/plain;
charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

This is my first ruby program that actually does all it's supposed to do.
It doesn't have a lot of style, but hopefully my next programs will look
better. The wordlist is from wordlist.sourceforge.net and the first part = of
the program reads the word.lst file and puts all the words in an array.

Hope it does what it's supposed to do, as i couldn't see the texttwist on
yahoo.
I'm also attaching the Unnoficial Jargon File Wordlist word.lst file.

-Ionu=C5=A3 Ar=C5=A3=C4=83ri=C5=9Fi

I remembered a mistake that i had done. Well not actually a mistake. I just=
=20
wrote some 10 lines of code that did some extra work that i never actually=
=20
used later in the program. I deleted those lines now, which resulted in a=20
unidimensional array (it was bidimensional before). This version should be=
=20
more readable and should make more sense though there's no extra=20
functionallity. So the version on rubyquiz.com should be replaced with this=
=20
one, for the sake of future readers if it's not too much of a trouble.

Thank you. And thank you for rubyquiz.com. It's fantastic!



--Boundary-01=_XHkoFct0nb8S5KP
Content-Type: application/x-ruby;
name="texttwist-1.1.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="texttwist-1.1.rb"

### getting words from the list in word.lst
# assuming that each word in the file is on a different line
# and using the Unofficial Jargon File Word Lists from http://wordlist.sourceforge.net
#
f = File.open("word.lst", "r")
fi = f.read.gsub("\n", " ").scan(/\w+/).to_a
f.close
allwords = Array.new
fi.each {|word| (if ((word.length >= 3) and (word.length <= 6))
allwords << word.upcase
end)
}
## now they're all in an array
bwords = Array.new
allwords.each {|word| (if (word.scan(/./).length == 6)
bwords << word
end)
}
bwords.collect{|c| c.upcase!}


# code above sorts words with 6 letters into bwords

i = 1
score = 0
star = ""
while (i == 1) do
puts "Pick a number between 0 and " + bwords.length.to_s + " or press Enter to end game."
m = gets.chop
if (m != "")
n = m.to_i
wordie = bwords[n].scan(/./).sort_by{rand}.to_s.upcase # word scramble

puts "You have chosen the word: " + wordie
foo = 0
all = allwords.dup
while (foo == 0)

puts "Form words! Type 0 to pick another word."

guess = gets.chop.upcase
o = 0
while (o < guess.length) # tests if the letters used are legal
guessy = guess.dup
bwords[n].scan(/./).collect{|e| (guessy = guessy.sub(e , ""))}
o += 1

end
if guessy == ""
if (all.include? guess) # tests if the guess is in the wordlist
all.delete(guess)
score += guess.length
puts "Your score is now: " + score.to_s

if (guess == bwords[n]) #tests and rewards if the guess is the original word
star << "*"
foo = 1337
puts " and you have " + star + " stars."
end
elsif (guess == "EXIT0")
foo = 1337
else
puts "Incorrect! Try Again!"
end

elsif (guess == "EXIT0")
foo = 1337
else
puts "The letters used were illegal. Try again."
end


end
bwords.delete_at(n)
else
i = 0
puts "Your final score was: " + score.to_s + " and you have gathered " + star + " stars. (One star for each original 6 letter word found.)"
end
end
















--Boundary-01=_XHkoFct0nb8S5KP--

--nextPart17441531.fdFuUJYqkk
Content-Type: application/pgp-signature

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.3 (GNU/Linux)

iD8DBQBFokHkJRmulx2zJOARAjRhAJ48rzySMu+H1xuKBNzbgl/NcgyKpACfc0jy
kTzqUtOmta1YAAoQGCGCBQQ=
=jZh/
-----END PGP SIGNATURE-----

--nextPart17441531.fdFuUJYqkk--
 
B

Bob Showalter

Here's my submission. There are two programs: the first builds a set
of puzzles from a decent word list I found on line. Files like
/usr/dict/words have too many obscure words. The second is a (very)
simplistic game interface.

A "puzzle" is just a string of six or more 3-6 letter words, sorted by
length. The output of the first program is just one line per puzzle,
with the words separated by colons. The words are ROT13 encoded. The
puzzles.rb program generates 905 different puzzles.

Bob Showalter

---------- puzzles.rb ----------

# puzzles.rb
# generate puzzles for use by wordblend.rb program
#
# usage: ruby puzzles.rb >puzzles.dat

require 'open-uri'
require 'yaml'

# these urls point to text files with lists of 2000 commonest
# English word "families", including plurals and other forms.
# this ends up generating reasonably good puzzles.
URIS = %w{
http://www1.harenet.ne.jp/~waring/vocab/wordlists/full1000.txt
http://www1.harenet.ne.jp/~waring/vocab/wordlists/full2000.txt
}

# minimum number of words necessary to form a puzzle
MIN_SIZE = 6

# define some helper functions
class String

# returns string with characters in sorted order
def sort
split(//).sort.join
end

# returns true if s is a subword of the string. both
# the string and s must be sorted!
def subword?(s)
i = j = 0
while j < s.length
i += 1 while i < length and self != s[j]
i < length or return false
j += 1
i += 1
end
true
end

end

# grab the 3-6 letter words from word lists. sort each word by
# character (e.g. 'test' becomes 'estt'), and then accumulate
STDERR.puts "Fetching words..."
words = Hash.new {|h,k| h[k] = []}
URIS.each do |uri|
open(uri) do |f|
f.read.split.select {|w| w.length >= 3 and w.length <= 6}.each do |word|
word.upcase!
sword = word.sort
words[sword] << word
end
end
end

# find puzzles by looking at which sorted words are contained in
# other six-character sorted words.
STDERR.puts "Finding puzzles..."
n = 0
words.keys.select {|w| w.length == 6}.each do |key|
puzzle = words.select {|ssub, subs| key.subword?(ssub)}.collect {|a|
a.last}.flatten.sort_by {|w| "#{w.length}#{w}"}
next if puzzle.size < MIN_SIZE
puts puzzle.join(':')
end

---------- wordblend.rb ----------

# wordblend.rb
# simplistic Word Blend puzzle game
# uses puzzles.dat file created by separate puzzles.rb program

class String
def rot13
tr 'A-Za-z', 'N-ZA-Mn-za-m'
end
end

class Puzzle

attr_reader :words, :letters, :board

def self.pick
@@puzzles ||= IO.readlines('puzzles.dat')
new(@@puzzles[rand(@@puzzles.size)].chomp.rot13.split(':'))
end

def initialize(words)
@words = words
scramble
@board = words.collect {|w| w.gsub(/./, '-')}
end

def scramble
@letters = words.last.split(//).sort_by {rand}.join
scramble if words.include? @letters
end

def help
puts "Enter 'Q' to give up, 'S' to scramble letters"
end

def play
help
turn while board != words
puts board
end

def turn
puts board
puts
puts letters
while true
print "? "
guess = gets.strip.upcase
if guess == ''
help
redo
end
if guess == 'S'
scramble
puts letters
redo
end
@board = words.dup if guess == 'Q'
i = words.index(guess) and board = guess
break
end
end

end

# play a random game
p = Puzzle.pick
p.play
 
B

Bob Showalter

# Find words that use the same letters
selectedWords = dict.scan(/^[#{baseWord}]{3,6}$/)


I was really impressed when I first saw this. It doesn't quite work if you
want to exclude reusing the same letter more than once
("hhh".scan(/^[hello]{3,6}$/) => ["hhh"]) but it comes so close to something
I've only ever thought about implementing as a recursive method.
Unfortunately I don't know much about this but now I wonder if it's possible
to find all partial permutations of a word with a regexp.

Here's a revision to Daniel's approach that seems to work well:

# Open and read the dictionary.
dict = IO.read("/usr/share/dict/words").scan(/^[a-z]{3,6}$/)

# Pick a random word with 6 letters.
baseWord = dict.grep(/^[a-z]{6}$/).rand_elem

# Find words that use the same letters
sortWord = baseWord.scan(/./).sort.to_s
selectedWords = dict.select {|w|
Regexp.new(w.scan(/./).sort.join('.*')).match(sortWord) }

I started by just extracting only the 3-6 letter words.

Then I sort the base word so the letters are in order. Let's say the
baseWord is "parlor". Then sortWord would be "aloprr".

Now for each word in the dictionary, sort it in letter order and
create a regex. Suppose the word is "roar". The sorted version is
"aorr" and the regex is "a.*o.*r.*r". If that regex matches the
sortWord, we found a valid subword.

This could be sped up by precompiling the regexes I would guess.

Bob
 
K

Ken Bloom

This is a riff on the Jumble puzzle found in many (US) newspapers. More
specifically, it's based on the game TextTwist[1], made by GameHouse[2] and
published in various places around the web.

The mechanic of TextTwist is simple. The player is given six letters and is
tasked with unscrambling those letters into as many words as possible. If the
player can use all six letters in a word, they proceed to the next round.

Your task is to build the back-end engine to run a TextTwist clone. Effectively,
this means that you must generate a list of three- to six-letter words that can
all be constructed from the same six letters. This list must contain at least
one six-letter word.

Bonus points for building a completely functional game!

[1]: http://games.yahoo.com/games/texttwist.html (just one example, java)
[2]: http://www.gamehouse.com/

require 'rubygems'
require 'facets/core/enumerable/permutation'
require 'set'

#usage: texttwist [word]
# specifying a word will use /usr/share/dict/words to solve a TextTwist
# problem. If no word is specified, a random word will be selected, to
# generate an round of texttwist.

#load dictionary
matcher=Set.new
allwords=Array.new
open("/usr/share/dict/words") do |f|
f.each do |line|
line.chomp!
next if line !~ /^[a-z]+$/
matcher << line if line.length<=6
allwords << line if line.length==6
end
end

#generate subwords of a word
word=ARGV[0] || allwords[rand(allwords.length)]
thiswordmatcher=Set.new
word.split(//).each_permutation do |perm|
perm=perm.join
(3..6).each do |len|
candidate=perm[0,len]
if matcher.include?(candidate)
thiswordmatcher << candidate
end
end
end

#output
puts word
puts "======"
thiswordmatcher.each do |subword|
puts subword
end
 
J

James Edward Gray II

If you are using *nix (unix, linux) then you can run "time <ruby
program>" at the command line. This might work with Macs as well
because they are based on unix but I don't have one so I'm not sure.

It works on a Mac, sure.

James Edward Gray II
 
J

James Edward Gray II

It just so happened that I was learning ruby last summer and wrote
a program to cheat for me at TextTwist. Needless to say the game
got boring really fast, but it was neat writing the program. I've
modified it a bit to instead play the game, but I'm a fairly new
user and would appreciate any feedback, both ruby related and
general programming.

Looking pretty good to me.
Also, I have the problem that when running this on Windows it
doesn't allow me to manipulate files with Ruby, giving me a
Permission Denied error on File.open. Any idea why this might be?

Well, you pass open() three arguments:

File.open("reducedwordlist#{i}.txt", "w", File::CREAT) do |fout|

I'm pretty sure that form has the third argument being the
permissions of the new file. Take out the File::CREAT, the "w" mode
string handles that automatically, and see if that fixes it up.

Hope that helps.

James Edward Gray II
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top