mirror of https://github.com/tLDP/LDP
276 lines
5.7 KiB
Bash
276 lines
5.7 KiB
Bash
#!/bin/bash
|
|
# nim.sh: Game of Nim
|
|
|
|
# Author: Mendel Cooper
|
|
# Reldate: 15 July 2008
|
|
# License: GPL3
|
|
|
|
ROWS=5 # Five rows of pegs (or matchsticks).
|
|
WON=91 # Exit codes to keep track of wins/losses.
|
|
LOST=92 # Possibly useful if running in batch mode.
|
|
QUIT=99
|
|
peg_msg= # Peg/Pegs?
|
|
Rows=( 0 5 4 3 2 1 ) # Array holding play info.
|
|
# ${Rows[0]} holds total number of pegs, updated after each turn.
|
|
# Other array elements hold number of pegs in corresponding row.
|
|
|
|
instructions ()
|
|
{
|
|
clear
|
|
tput bold
|
|
echo "Welcome to the game of Nim."; echo
|
|
echo -n "Do you need instructions? (y/n) "; read ans
|
|
|
|
if [ "$ans" = "y" -o "$ans" = "Y" ]; then
|
|
clear
|
|
echo -e '\E[33;41m' # Yellow fg., over red bg.; bold.
|
|
cat <<INSTRUCTIONS
|
|
|
|
Nim is a game with roots in the distant past.
|
|
This particular variant starts with five rows of pegs.
|
|
|
|
1: | | | | |
|
|
2: | | | |
|
|
3: | | |
|
|
4: | |
|
|
5: |
|
|
|
|
The number at the left identifies the row.
|
|
|
|
The human player moves first, and alternates turns with the bot.
|
|
A turn consists of removing at least one peg from a single row.
|
|
It is permissable to remove ALL the pegs from a row.
|
|
For example, in row 2, above, the player can remove 1, 2, 3, or 4 pegs.
|
|
The player who removes the last peg loses.
|
|
|
|
The strategy consists of trying to be the one who removes
|
|
the next-to-last peg(s), leaving the loser with the final peg.
|
|
|
|
To exit the game early, hit ENTER during your turn.
|
|
INSTRUCTIONS
|
|
|
|
echo; echo -n "Hit ENTER to begin game. "; read azx
|
|
|
|
echo -e "\033[0m" # Restore display.
|
|
else tput sgr0; clear
|
|
fi
|
|
|
|
clear
|
|
|
|
}
|
|
|
|
|
|
tally_up ()
|
|
{
|
|
let "Rows[0] = ${Rows[1]} + ${Rows[2]} + ${Rows[3]} + ${Rows[4]} + \
|
|
${Rows[5]}" # Add up how many pegs remaining.
|
|
}
|
|
|
|
|
|
display ()
|
|
{
|
|
index=1 # Start with top row.
|
|
echo
|
|
|
|
while [ "$index" -le "$ROWS" ]
|
|
do
|
|
p=${Rows[index]}
|
|
echo -n "$index: " # Show row number.
|
|
|
|
# ------------------------------------------------
|
|
# Two concurrent inner loops.
|
|
|
|
indent=$index
|
|
while [ "$indent" -gt 0 ]
|
|
do
|
|
echo -n " " # Staggered rows.
|
|
((indent--)) # Spacing between pegs.
|
|
done
|
|
|
|
while [ "$p" -gt 0 ]
|
|
do
|
|
echo -n "| "
|
|
((p--))
|
|
done
|
|
# -----------------------------------------------
|
|
|
|
echo
|
|
((index++))
|
|
done
|
|
|
|
tally_up
|
|
|
|
rp=${Rows[0]}
|
|
|
|
if [ "$rp" -eq 1 ]
|
|
then
|
|
peg_msg=peg
|
|
final_msg="Game over."
|
|
else # Game not yet over . . .
|
|
peg_msg=pegs
|
|
final_msg="" # . . . So "final message" is blank.
|
|
fi
|
|
|
|
echo " $rp $peg_msg remaining."
|
|
echo " "$final_msg""
|
|
|
|
|
|
echo
|
|
}
|
|
|
|
player_move ()
|
|
{
|
|
|
|
echo "Your move:"
|
|
|
|
echo -n "Which row? "
|
|
while read idx
|
|
do # Validity check, etc.
|
|
|
|
if [ -z "$idx" ] # Hitting return quits.
|
|
then
|
|
echo "Premature exit."; echo
|
|
tput sgr0 # Restore display.
|
|
exit $QUIT
|
|
fi
|
|
|
|
if [ "$idx" -gt "$ROWS" -o "$idx" -lt 1 ] # Bounds check.
|
|
then
|
|
echo "Invalid row number!"
|
|
echo -n "Which row? "
|
|
else
|
|
break
|
|
fi
|
|
# TODO:
|
|
# Add check for non-numeric input.
|
|
# Also, script crashes on input outside of range of long double.
|
|
# Fix this.
|
|
|
|
done
|
|
|
|
echo -n "Remove how many? "
|
|
while read num
|
|
do # Validity check.
|
|
|
|
if [ -z "$num" ]
|
|
then
|
|
echo "Premature exit."; echo
|
|
tput sgr0 # Restore display.
|
|
exit $QUIT
|
|
fi
|
|
|
|
if [ "$num" -gt ${Rows[idx]} -o "$num" -lt 1 ]
|
|
then
|
|
echo "Cannot remove $num!"
|
|
echo -n "Remove how many? "
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
# TODO:
|
|
# Add check for non-numeric input.
|
|
# Also, script crashes on input outside of range of long double.
|
|
# Fix this.
|
|
|
|
let "Rows[idx] -= $num"
|
|
|
|
display
|
|
tally_up
|
|
|
|
if [ ${Rows[0]} -eq 1 ]
|
|
then
|
|
echo " Human wins!"
|
|
echo " Congratulations!"
|
|
tput sgr0 # Restore display.
|
|
echo
|
|
exit $WON
|
|
fi
|
|
|
|
if [ ${Rows[0]} -eq 0 ]
|
|
then # Snatching defeat from the jaws of victory . . .
|
|
echo " Fool!"
|
|
echo " You just removed the last peg!"
|
|
echo " Bot wins!"
|
|
tput sgr0 # Restore display.
|
|
echo
|
|
exit $LOST
|
|
fi
|
|
}
|
|
|
|
|
|
bot_move ()
|
|
{
|
|
|
|
row_b=0
|
|
while [[ $row_b -eq 0 || ${Rows[row_b]} -eq 0 ]]
|
|
do
|
|
row_b=$RANDOM # Choose random row.
|
|
let "row_b %= $ROWS"
|
|
done
|
|
|
|
|
|
num_b=0
|
|
r0=${Rows[row_b]}
|
|
|
|
if [ "$r0" -eq 1 ]
|
|
then
|
|
num_b=1
|
|
else
|
|
let "num_b = $r0 - 1"
|
|
# Leave only a single peg in the row.
|
|
fi # Not a very strong strategy,
|
|
#+ but probably a bit better than totally random.
|
|
|
|
let "Rows[row_b] -= $num_b"
|
|
echo -n "Bot: "
|
|
echo "Removing from row $row_b ... "
|
|
|
|
if [ "$num_b" -eq 1 ]
|
|
then
|
|
peg_msg=peg
|
|
else
|
|
peg_msg=pegs
|
|
fi
|
|
|
|
echo " $num_b $peg_msg."
|
|
|
|
display
|
|
tally_up
|
|
|
|
if [ ${Rows[0]} -eq 1 ]
|
|
then
|
|
echo " Bot wins!"
|
|
tput sgr0 # Restore display.
|
|
exit $WON
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
# ================================================== #
|
|
instructions # If human player needs them . . .
|
|
tput bold # Bold characters for easier viewing.
|
|
display # Show game board.
|
|
|
|
while [ true ] # Main loop.
|
|
do # Alternate human and bot turns.
|
|
player_move
|
|
bot_move
|
|
done
|
|
# ================================================== #
|
|
|
|
# Exercise:
|
|
# --------
|
|
# Improve the bot's strategy.
|
|
# There is, in fact, a Nim strategy that can force a win.
|
|
# See the Wikipedia article on Nim: http://en.wikipedia.org/wiki/Nim
|
|
# Recode the bot to use this strategy (rather difficult).
|
|
|
|
# Curiosities:
|
|
# -----------
|
|
# Nim played a prominent role in Alain Resnais' 1961 New Wave film,
|
|
#+ Last Year at Marienbad.
|
|
#
|
|
# In 1978, Leo Christopherson wrote an animated version of Nim,
|
|
#+ Android Nim, for the TRS-80 Model I.
|