mirror of https://github.com/tLDP/LDP
463 lines
11 KiB
Bash
463 lines
11 KiB
Bash
#!/bin/bash
|
|
# qky.sh
|
|
|
|
##############################################################
|
|
# QUACKEY: a somewhat simplified version of Perquackey [TM]. #
|
|
# #
|
|
# Author: Mendel Cooper <thegrendel@theriver.com> #
|
|
# version 0.1.02 03 May, 2008 #
|
|
# License: GPL3 #
|
|
##############################################################
|
|
|
|
WLIST=/usr/share/dict/word.lst
|
|
# ^^^^^^^^ Word list file found here.
|
|
# ASCII word list, one word per line, UNIX format.
|
|
# A suggested list is the script author's "yawl" word list package.
|
|
# http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz
|
|
# or
|
|
# http://personal.riverusers.com/~thegrendel/yawl-0.3.2.tar.gz
|
|
|
|
NONCONS=0 # Word not constructable from letter set.
|
|
CONS=1 # Constructable.
|
|
SUCCESS=0
|
|
NG=1
|
|
FAILURE=''
|
|
NULL=0 # Zero out value of letter (if found).
|
|
MINWLEN=3 # Minimum word length.
|
|
MAXCAT=5 # Maximum number of words in a given category.
|
|
PENALTY=200 # General-purpose penalty for unacceptable words.
|
|
total=
|
|
E_DUP=70 # Duplicate word error.
|
|
|
|
TIMEOUT=10 # Time for word input.
|
|
|
|
NVLET=10 # 10 letters for non-vulnerable.
|
|
VULET=13 # 13 letters for vulnerable (not yet implemented).
|
|
|
|
declare -a Words
|
|
declare -a Status
|
|
declare -a Score=( 0 0 0 0 0 0 0 0 0 0 0 )
|
|
|
|
|
|
letters=( a n s r t m l k p r b c i d s i d z e w u e t f
|
|
e y e r e f e g t g h h i t r s c i t i d i j a t a o l a
|
|
m n a n o v n w o s e l n o s p a q e e r a b r s a o d s
|
|
t g t i t l u e u v n e o x y m r k )
|
|
# Letter distribution table shamelessly borrowed from "Wordy" game,
|
|
#+ ca. 1992, written by a certain fine fellow named Mendel Cooper.
|
|
|
|
declare -a LS
|
|
|
|
numelements=${#letters[@]}
|
|
randseed="$1"
|
|
|
|
instructions ()
|
|
{
|
|
clear
|
|
echo "Welcome to QUACKEY, the anagramming word construction game."; echo
|
|
echo -n "Do you need instructions? (y/n) "; read ans
|
|
|
|
if [ "$ans" = "y" -o "$ans" = "Y" ]; then
|
|
clear
|
|
echo -e '\E[31;47m' # Red foreground. '\E[34;47m' for blue.
|
|
cat <<INSTRUCTION1
|
|
|
|
QUACKEY is a variant of Perquackey [TM].
|
|
The rules are the same, but the scoring is simplified
|
|
and plurals of previously played words are allowed.
|
|
"Vulnerable" play is not yet implemented,
|
|
but it is otherwise feature-complete.
|
|
|
|
As the game begins, the player gets 10 letters.
|
|
The object is to construct valid dictionary words
|
|
of at least 3-letter-length from the letterset.
|
|
Each word-length category
|
|
-- 3-letter, 4-letter, 5-letter, ... --
|
|
fills up with the fifth word entered,
|
|
and no further words in that category are accepted.
|
|
|
|
The penalty for too-short (two-letter), duplicate, unconstructable,
|
|
and invalid (not in dictionary) words is -200. The same penalty applies
|
|
to attempts to enter a word in a filled-up category.
|
|
|
|
INSTRUCTION1
|
|
|
|
echo -n "Hit ENTER for next page of instructions. "; read az1
|
|
|
|
cat <<INSTRUCTION2
|
|
|
|
The scoring mostly corresponds to classic Perquackey:
|
|
The first 3-letter word scores 60, plus 10 for each additional one.
|
|
The first 4-letter word scores 120, plus 20 for each additional one.
|
|
The first 5-letter word scores 200, plus 50 for each additional one.
|
|
The first 6-letter word scores 300, plus 100 for each additional one.
|
|
The first 7-letter word scores 500, plus 150 for each additional one.
|
|
The first 8-letter word scores 750, plus 250 for each additional one.
|
|
The first 9-letter word scores 1000, plus 500 for each additional one.
|
|
The first 10-letter word scores 2000, plus 2000 for each additional one.
|
|
|
|
Category completion bonuses are:
|
|
3-letter words 100
|
|
4-letter words 200
|
|
5-letter words 400
|
|
6-letter words 800
|
|
7-letter words 2000
|
|
8-letter words 10000
|
|
This is a simplification of the absurdly complicated Perquackey bonus
|
|
scoring system.
|
|
|
|
INSTRUCTION2
|
|
|
|
echo -n "Hit ENTER for final page of instructions. "; read az1
|
|
|
|
cat <<INSTRUCTION3
|
|
|
|
|
|
Hitting just ENTER for a word entry ends the game.
|
|
|
|
Individual word entry is timed to a maximum of 10 seconds.
|
|
*** Timing out on an entry ends the game. ***
|
|
Other than that, the game is untimed.
|
|
|
|
--------------------------------------------------
|
|
Game statistics are automatically saved to a file.
|
|
--------------------------------------------------
|
|
|
|
For competitive ("duplicate") play, a previous letterset
|
|
may be duplicated by repeating the script's random seed,
|
|
command-line parameter \$1.
|
|
For example, "qky 7633" specifies the letterset
|
|
c a d i f r h u s k ...
|
|
INSTRUCTION3
|
|
|
|
echo; echo -n "Hit ENTER to begin game. "; read az1
|
|
|
|
echo -e "\033[0m" # Turn off red.
|
|
else clear
|
|
fi
|
|
|
|
clear
|
|
|
|
}
|
|
|
|
|
|
|
|
seed_random ()
|
|
{ # Seed random number generator.
|
|
if [ -n "$randseed" ] # Can specify random seed.
|
|
then #+ for play in competitive mode.
|
|
# RANDOM="$randseed"
|
|
echo "RANDOM seed set to "$randseed""
|
|
else
|
|
randseed="$$" # Or get random seed from process ID.
|
|
echo "RANDOM seed not specified, set to Process ID of script ($$)."
|
|
fi
|
|
|
|
RANDOM="$randseed"
|
|
|
|
echo
|
|
}
|
|
|
|
|
|
get_letset ()
|
|
{
|
|
element=0
|
|
echo -n "Letterset:"
|
|
|
|
for lset in $(seq $NVLET)
|
|
do # Pick random letters to fill out letterset.
|
|
LS[element]="${letters[$((RANDOM%numelements))]}"
|
|
((element++))
|
|
done
|
|
|
|
echo
|
|
echo "${LS[@]}"
|
|
|
|
}
|
|
|
|
|
|
add_word ()
|
|
{
|
|
wrd="$1"
|
|
local idx=0
|
|
|
|
Status[0]=""
|
|
Status[3]=""
|
|
Status[4]=""
|
|
|
|
while [ "${Words[idx]}" != '' ]
|
|
do
|
|
if [ "${Words[idx]}" = "$wrd" ]
|
|
then
|
|
Status[3]="Duplicate-word-PENALTY"
|
|
let "Score[0]= 0 - $PENALTY"
|
|
let "Score[1]-=$PENALTY"
|
|
return $E_DUP
|
|
fi
|
|
|
|
((idx++))
|
|
done
|
|
|
|
Words[idx]="$wrd"
|
|
get_score
|
|
|
|
}
|
|
|
|
get_score()
|
|
{
|
|
local wlen=0
|
|
local score=0
|
|
local bonus=0
|
|
local first_word=0
|
|
local add_word=0
|
|
local numwords=0
|
|
|
|
wlen=${#wrd}
|
|
numwords=${Score[wlen]}
|
|
Score[2]=0
|
|
Status[4]="" # Initialize "bonus" to 0.
|
|
|
|
case "$wlen" in
|
|
3) first_word=60
|
|
add_word=10;;
|
|
4) first_word=120
|
|
add_word=20;;
|
|
5) first_word=200
|
|
add_word=50;;
|
|
6) first_word=300
|
|
add_word=100;;
|
|
7) first_word=500
|
|
add_word=150;;
|
|
8) first_word=750
|
|
add_word=250;;
|
|
9) first_word=1000
|
|
add_word=500;;
|
|
10) first_word=2000
|
|
add_word=2000;; # This category modified from original rules!
|
|
esac
|
|
|
|
((Score[wlen]++))
|
|
if [ ${Score[wlen]} -eq $MAXCAT ]
|
|
then # Category completion bonus scoring simplified!
|
|
case $wlen in
|
|
3 ) bonus=100;;
|
|
4 ) bonus=200;;
|
|
5 ) bonus=400;;
|
|
6 ) bonus=800;;
|
|
7 ) bonus=2000;;
|
|
8 ) bonus=10000;;
|
|
esac # Needn't worry about 9's and 10's.
|
|
Status[4]="Category-$wlen-completion***BONUS***"
|
|
Score[2]=$bonus
|
|
else
|
|
Status[4]="" # Erase it.
|
|
fi
|
|
|
|
|
|
let "score = $first_word + $add_word * $numwords"
|
|
if [ "$numwords" -eq 0 ]
|
|
then
|
|
Score[0]=$score
|
|
else
|
|
Score[0]=$add_word
|
|
fi # All this to distinguish last-word score
|
|
#+ from total running score.
|
|
let "Score[1] += ${Score[0]}"
|
|
let "Score[1] += ${Score[2]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
get_word ()
|
|
{
|
|
local wrd=''
|
|
read -t $TIMEOUT wrd # Timed read.
|
|
echo $wrd
|
|
}
|
|
|
|
is_constructable ()
|
|
{ # This was the most complex and difficult-to-write function.
|
|
local -a local_LS=( "${LS[@]}" ) # Local copy of letter set.
|
|
local is_found=0
|
|
local idx=0
|
|
local pos
|
|
local strlen
|
|
local local_word=( "$1" )
|
|
strlen=${#local_word}
|
|
|
|
while [ "$idx" -lt "$strlen" ]
|
|
do
|
|
is_found=$(expr index "${local_LS[*]}" "${local_word:idx:1}")
|
|
if [ "$is_found" -eq "$NONCONS" ] # Not constructable!
|
|
then
|
|
echo "$FAILURE"; return
|
|
else
|
|
((pos = ($is_found - 1) / 2)) # Compensate for spaces betw. letters!
|
|
local_LS[pos]=$NULL # Zero out used letters.
|
|
((idx++)) # Bump index.
|
|
fi
|
|
done
|
|
|
|
echo "$SUCCESS"
|
|
return
|
|
}
|
|
|
|
is_valid ()
|
|
{ # Surprisingly easy to check if word in dictionary ...
|
|
fgrep -qw "$1" "$WLIST" # ... thanks to 'grep' ...
|
|
echo $?
|
|
}
|
|
|
|
check_word ()
|
|
{
|
|
if [ -z "$1" ]
|
|
then
|
|
return
|
|
fi
|
|
|
|
Status[1]=""
|
|
Status[2]=""
|
|
Status[3]=""
|
|
Status[4]=""
|
|
|
|
iscons=$(is_constructable "$1")
|
|
if [ "$iscons" ]
|
|
then
|
|
Status[1]="constructable"
|
|
v=$(is_valid "$1")
|
|
if [ "$v" -eq "$SUCCESS" ]
|
|
then
|
|
Status[2]="valid"
|
|
strlen=${#1}
|
|
|
|
if [ ${Score[strlen]} -eq "$MAXCAT" ] # Category full!
|
|
then
|
|
Status[3]="Category-$strlen-overflow-PENALTY"
|
|
return $NG
|
|
fi
|
|
|
|
case "$strlen" in
|
|
1 | 2 )
|
|
Status[3]="Two-letter-word-PENALTY"
|
|
return $NG;;
|
|
* )
|
|
Status[3]=""
|
|
return $SUCCESS;;
|
|
esac
|
|
else
|
|
Status[3]="Not-valid-PENALTY"
|
|
return $NG
|
|
fi
|
|
else
|
|
Status[3]="Not-constructable-PENALTY"
|
|
return $NG
|
|
fi
|
|
|
|
### FIXME: Streamline the above code.
|
|
|
|
}
|
|
|
|
|
|
display_words ()
|
|
{
|
|
local idx=0
|
|
local wlen0
|
|
|
|
clear
|
|
echo "Letterset: ${LS[@]}"
|
|
echo "Threes: Fours: Fives: Sixes: Sevens: Eights:"
|
|
echo "------------------------------------------------------------"
|
|
|
|
|
|
|
|
while [ "${Words[idx]}" != '' ]
|
|
do
|
|
wlen0=${#Words[idx]}
|
|
case "$wlen0" in
|
|
3) ;;
|
|
4) echo -n " " ;;
|
|
5) echo -n " " ;;
|
|
6) echo -n " " ;;
|
|
7) echo -n " " ;;
|
|
8) echo -n " " ;;
|
|
esac
|
|
echo "${Words[idx]}"
|
|
((idx++))
|
|
done
|
|
|
|
### FIXME: The word display is pretty crude.
|
|
}
|
|
|
|
|
|
play ()
|
|
{
|
|
word="Start game" # Dummy word, to start ...
|
|
|
|
while [ "$word" ] # If player just hits return (blank word),
|
|
do #+ then game ends.
|
|
echo "$word: "${Status[@]}""
|
|
echo -n "Last score: [${Score[0]}] TOTAL score: [${Score[1]}]: Next word: "
|
|
total=${Score[1]}
|
|
word=$(get_word)
|
|
check_word "$word"
|
|
|
|
if [ "$?" -eq "$SUCCESS" ]
|
|
then
|
|
add_word "$word"
|
|
else
|
|
let "Score[0]= 0 - $PENALTY"
|
|
let "Score[1]-=$PENALTY"
|
|
fi
|
|
|
|
display_words
|
|
done # Exit game.
|
|
|
|
### FIXME: The play () function calls too many other functions.
|
|
### This is perilously close to "spaghetti code" ...
|
|
}
|
|
|
|
end_of_game ()
|
|
{ # Save and display stats.
|
|
|
|
#######################Autosave##########################
|
|
savefile=qky.save.$$
|
|
# ^^ PID of script
|
|
echo `date` >> $savefile
|
|
echo "Letterset # $randseed (random seed) ">> $savefile
|
|
echo -n "Letterset: " >> $savefile
|
|
echo "${LS[@]}" >> $savefile
|
|
echo "---------" >> $savefile
|
|
echo "Words constructed:" >> $savefile
|
|
echo "${Words[@]}" >> $savefile
|
|
echo >> $savefile
|
|
echo "Score: $total" >> $savefile
|
|
|
|
echo "Statistics for this round saved in \""$savefile"\""
|
|
#########################################################
|
|
|
|
echo "Score for this round: $total"
|
|
echo "Words: ${Words[@]}"
|
|
}
|
|
|
|
# ---------#
|
|
instructions
|
|
seed_random
|
|
get_letset
|
|
play
|
|
end_of_game
|
|
# ---------#
|
|
|
|
exit $?
|
|
|
|
# TODO:
|
|
#
|
|
# 1) Clean up code!
|
|
# 2) Prettify the display_words () function (maybe with widgets?).
|
|
# 3) Improve the time-out ... maybe change to untimed entry,
|
|
#+ but with a time limit for the overall round.
|
|
# 4) An on-screen countdown timer would be nice.
|
|
# 5) Implement "vulnerable" mode of play.
|
|
# 6) Improve save-to-file capability (and maybe make it optional).
|
|
# 7) Fix bugs!!!
|