#!/bin/bash # horserace.sh: Very simple horserace simulation. # Author: Stefano Palmeri # Used with permission. ################################################################ # Goals of the script: # playing with escape sequences and terminal colors. # # Exercise: # Edit the script to make it run less randomly, #+ set up a fake betting shop . . . # Um . . . um . . . it's starting to remind me of a movie . . . # # The script gives each horse a random handicap. # The odds are calculated upon horse handicap #+ and are expressed in European(?) style. # E.g., odds=3.75 means that if you bet $1 and win, #+ you receive $3.75. # # The script has been tested with a GNU/Linux OS, #+ using xterm and rxvt, and konsole. # On a machine with an AMD 900 MHz processor, #+ the average race time is 75 seconds. # On faster computers the race time would be lower. # So, if you want more suspense, reset the USLEEP_ARG variable. # # Script by Stefano Palmeri. ################################################################ E_RUNERR=65 # Check if md5sum and bc are installed. if ! which bc &> /dev/null; then echo bc is not installed. echo "Can\'t run . . . " exit $E_RUNERR fi if ! which md5sum &> /dev/null; then echo md5sum is not installed. echo "Can\'t run . . . " exit $E_RUNERR fi # Set the following variable to slow down script execution. # It will be passed as the argument for usleep (man usleep) #+ and is expressed in microseconds (500000 = half a second). USLEEP_ARG=0 # Clean up the temp directory, restore terminal cursor and #+ terminal colors -- if script interrupted by Ctl-C. trap 'echo -en "\E[?25h"; echo -en "\E[0m"; stty echo;\ tput cup 20 0; rm -fr $HORSE_RACE_TMP_DIR' TERM EXIT # See the chapter on debugging for an explanation of 'trap.' # Set a unique (paranoid) name for the temp directory the script needs. HORSE_RACE_TMP_DIR=$HOME/.horserace-`date +%s`-`head -c10 /dev/urandom \ | md5sum | head -c30` # Create the temp directory and move right in. mkdir $HORSE_RACE_TMP_DIR cd $HORSE_RACE_TMP_DIR # This function moves the cursor to line $1 column $2 and then prints $3. # E.g.: "move_and_echo 5 10 linux" is equivalent to #+ "tput cup 4 9; echo linux", but with one command instead of two. # Note: "tput cup" defines 0 0 the upper left angle of the terminal, #+ echo defines 1 1 the upper left angle of the terminal. move_and_echo() { echo -ne "\E[${1};${2}H""$3" } # Function to generate a pseudo-random number between 1 and 9. random_1_9 () { head -c10 /dev/urandom | md5sum | tr -d [a-z] | tr -d 0 | cut -c1 } # Two functions that simulate "movement," when drawing the horses. draw_horse_one() { echo -n " "//$MOVE_HORSE// } draw_horse_two(){ echo -n " "\\\\$MOVE_HORSE\\\\ } # Define current terminal dimension. N_COLS=`tput cols` N_LINES=`tput lines` # Need at least a 20-LINES X 80-COLUMNS terminal. Check it. if [ $N_COLS -lt 80 ] || [ $N_LINES -lt 20 ]; then echo "`basename $0` needs a 80-cols X 20-lines terminal." echo "Your terminal is ${N_COLS}-cols X ${N_LINES}-lines." exit $E_RUNERR fi # Start drawing the race field. # Need a string of 80 chars. See below. BLANK80=`seq -s "" 100 | head -c80` clear # Set foreground and background colors to white. echo -ne '\E[37;47m' # Move the cursor on the upper left angle of the terminal. tput cup 0 0 # Draw six white lines. for n in `seq 5`; do echo $BLANK80 # Use the 80 chars string to colorize the terminal. done # Sets foreground color to black. echo -ne '\E[30m' move_and_echo 3 1 "START 1" move_and_echo 3 75 FINISH move_and_echo 1 5 "|" move_and_echo 1 80 "|" move_and_echo 2 5 "|" move_and_echo 2 80 "|" move_and_echo 4 5 "| 2" move_and_echo 4 80 "|" move_and_echo 5 5 "V 3" move_and_echo 5 80 "V" # Set foreground color to red. echo -ne '\E[31m' # Some ASCII art. move_and_echo 1 8 "..@@@..@@@@@...@@@@@.@...@..@@@@..." move_and_echo 2 8 ".@...@...@.......@...@...@.@......." move_and_echo 3 8 ".@@@@@...@.......@...@@@@@.@@@@...." move_and_echo 4 8 ".@...@...@.......@...@...@.@......." move_and_echo 5 8 ".@...@...@.......@...@...@..@@@@..." move_and_echo 1 43 "@@@@...@@@...@@@@..@@@@..@@@@." move_and_echo 2 43 "@...@.@...@.@.....@.....@....." move_and_echo 3 43 "@@@@..@@@@@.@.....@@@@...@@@.." move_and_echo 4 43 "@..@..@...@.@.....@.........@." move_and_echo 5 43 "@...@.@...@..@@@@..@@@@.@@@@.." # Set foreground and background colors to green. echo -ne '\E[32;42m' # Draw eleven green lines. tput cup 5 0 for n in `seq 11`; do echo $BLANK80 done # Set foreground color to black. echo -ne '\E[30m' tput cup 5 0 # Draw the fences. echo "++++++++++++++++++++++++++++++++++++++\ ++++++++++++++++++++++++++++++++++++++++++" tput cup 15 0 echo "++++++++++++++++++++++++++++++++++++++\ ++++++++++++++++++++++++++++++++++++++++++" # Set foreground and background colors to white. echo -ne '\E[37;47m' # Draw three white lines. for n in `seq 3`; do echo $BLANK80 done # Set foreground color to black. echo -ne '\E[30m' # Create 9 files to stores handicaps. for n in `seq 10 7 68`; do touch $n done # Set the first type of "horse" the script will draw. HORSE_TYPE=2 # Create position-file and odds-file for every "horse". #+ In these files, store the current position of the horse, #+ the type and the odds. for HN in `seq 9`; do touch horse_${HN}_position touch odds_${HN} echo \-1 > horse_${HN}_position echo $HORSE_TYPE >> horse_${HN}_position # Define a random handicap for horse. HANDICAP=`random_1_9` # Check if the random_1_9 function returned a good value. while ! echo $HANDICAP | grep [1-9] &> /dev/null; do HANDICAP=`random_1_9` done # Define last handicap position for horse. LHP=`expr $HANDICAP \* 7 + 3` for FILE in `seq 10 7 $LHP`; do echo $HN >> $FILE done # Calculate odds. case $HANDICAP in 1) ODDS=`echo $HANDICAP \* 0.25 + 1.25 | bc` echo $ODDS > odds_${HN} ;; 2 | 3) ODDS=`echo $HANDICAP \* 0.40 + 1.25 | bc` echo $ODDS > odds_${HN} ;; 4 | 5 | 6) ODDS=`echo $HANDICAP \* 0.55 + 1.25 | bc` echo $ODDS > odds_${HN} ;; 7 | 8) ODDS=`echo $HANDICAP \* 0.75 + 1.25 | bc` echo $ODDS > odds_${HN} ;; 9) ODDS=`echo $HANDICAP \* 0.90 + 1.25 | bc` echo $ODDS > odds_${HN} esac done # Print odds. print_odds() { tput cup 6 0 echo -ne '\E[30;42m' for HN in `seq 9`; do echo "#$HN odds->" `cat odds_${HN}` done } # Draw the horses at starting line. draw_horses() { tput cup 6 0 echo -ne '\E[30;42m' for HN in `seq 9`; do echo /\\$HN/\\" " done } print_odds echo -ne '\E[47m' # Wait for a enter key press to start the race. # The escape sequence '\E[?25l' disables the cursor. tput cup 17 0 echo -e '\E[?25l'Press [enter] key to start the race... read -s # Disable normal echoing in the terminal. # This avoids key presses that might "contaminate" the screen #+ during the race. stty -echo # -------------------------------------------------------- # Start the race. draw_horses echo -ne '\E[37;47m' move_and_echo 18 1 $BLANK80 echo -ne '\E[30m' move_and_echo 18 1 Starting... sleep 1 # Set the column of the finish line. WINNING_POS=74 # Define the time the race started. START_TIME=`date +%s` # COL variable needed by following "while" construct. COL=0 while [ $COL -lt $WINNING_POS ]; do MOVE_HORSE=0 # Check if the random_1_9 function has returned a good value. while ! echo $MOVE_HORSE | grep [1-9] &> /dev/null; do MOVE_HORSE=`random_1_9` done # Define old type and position of the "randomized horse". HORSE_TYPE=`cat horse_${MOVE_HORSE}_position | tail -n 1` COL=$(expr `cat horse_${MOVE_HORSE}_position | head -n 1`) ADD_POS=1 # Check if the current position is an handicap position. if seq 10 7 68 | grep -w $COL &> /dev/null; then if grep -w $MOVE_HORSE $COL &> /dev/null; then ADD_POS=0 grep -v -w $MOVE_HORSE $COL > ${COL}_new rm -f $COL mv -f ${COL}_new $COL else ADD_POS=1 fi else ADD_POS=1 fi COL=`expr $COL + $ADD_POS` echo $COL > horse_${MOVE_HORSE}_position # Store new position. # Choose the type of horse to draw. case $HORSE_TYPE in 1) HORSE_TYPE=2; DRAW_HORSE=draw_horse_two ;; 2) HORSE_TYPE=1; DRAW_HORSE=draw_horse_one esac echo $HORSE_TYPE >> horse_${MOVE_HORSE}_position # Store current type. # Set foreground color to black and background to green. echo -ne '\E[30;42m' # Move the cursor to new horse position. tput cup `expr $MOVE_HORSE + 5` \ `cat horse_${MOVE_HORSE}_position | head -n 1` # Draw the horse. $DRAW_HORSE usleep $USLEEP_ARG # When all horses have gone beyond field line 15, reprint odds. touch fieldline15 if [ $COL = 15 ]; then echo $MOVE_HORSE >> fieldline15 fi if [ `wc -l fieldline15 | cut -f1 -d " "` = 9 ]; then print_odds : > fieldline15 fi # Define the leading horse. HIGHEST_POS=`cat *position | sort -n | tail -1` # Set background color to white. echo -ne '\E[47m' tput cup 17 0 echo -n Current leader: `grep -w $HIGHEST_POS *position | cut -c7`\ " " done # Define the time the race finished. FINISH_TIME=`date +%s` # Set background color to green and enable blinking text. echo -ne '\E[30;42m' echo -en '\E[5m' # Make the winning horse blink. tput cup `expr $MOVE_HORSE + 5` \ `cat horse_${MOVE_HORSE}_position | head -n 1` $DRAW_HORSE # Disable blinking text. echo -en '\E[25m' # Set foreground and background color to white. echo -ne '\E[37;47m' move_and_echo 18 1 $BLANK80 # Set foreground color to black. echo -ne '\E[30m' # Make winner blink. tput cup 17 0 echo -e "\E[5mWINNER: $MOVE_HORSE\E[25m"" Odds: `cat odds_${MOVE_HORSE}`"\ " Race time: `expr $FINISH_TIME - $START_TIME` secs" # Restore cursor and old colors. echo -en "\E[?25h" echo -en "\E[0m" # Restore echoing. stty echo # Remove race temp directory. rm -rf $HORSE_RACE_TMP_DIR tput cup 19 0 exit 0