#!/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.