diff options
author | April Chin <April.Chin@Sun.COM> | 2008-12-27 14:59:38 -0800 |
---|---|---|
committer | April Chin <April.Chin@Sun.COM> | 2008-12-27 14:59:38 -0800 |
commit | 7c2fbfb345896881c631598ee3852ce9ce33fb07 (patch) | |
tree | 4b173b5657508562dfc0aa05f7d056d1e9add505 /usr/src/lib/libshell/common/scripts/gnaw.sh | |
parent | 6071ac1de68fed78e1e10052045bbb5f1732a263 (diff) | |
download | illumos-joyent-7c2fbfb345896881c631598ee3852ce9ce33fb07.tar.gz |
PSARC/2008/094 ksh93 Update 1
PSARC/2008/344 ksh93 Integration Update 1 Amendments 1
PSARC/2008/589 Remove /usr/bin/printf from PSARC case 2008 094
6619428 *ksh93* RFE: Update ksh93 in Solaris to ast-ksh.2008-11-04
6788659 RFE: Update libpp in Solaris to ast-open.2008-07-25
6561901 RFE: Add "shcomp" (shell script compiler) + kernel module to exec binary sh code
6599668 RFE: Move consumers of alias.sh over to ksh93
6595183 *ksh93* RFE: Update ksh93-integration demo code
6775901 *ksh93* no C message catalogs are generated for ksh93
6451262 *sleep* RFE: /usr/bin/sleep should support floating-point values
6687139 *ksh93* command substitution, exec, and stdout redirection cause allocation loop
6703761 *ksh93* crashes in script containing uncommon output redirections
6715496 *ksh93* SEGVs on array reinitialization
6713682 *ksh93* Creating a compound variable in a subshell "bleeds through" to the calling subshell
6672350 *ksh93* causes parent shell to die when child shell is suspended
6745015 *ksh93* VARIABLE=`command substitution` assignment is not reliable on OpenSolaris
6710205 *ksh93* problem with command substitution (within back quotes) containing \$'
6737600 *ksh93* exits debugger when user presses ctrl-c
6748645 *ksh93* fc -l -e - is mis-parsed, outputs wrong error message "-e - requires single argument"
6754020 *ksh93* does weird '[' expansion
6753538 *ksh93* umask modification leaks out of a ksh93 subshell
6766246 *ksh93* bug in pattern matching
6763594 *ksh93* executes command after "command" builtin twice on failure
6762665 *ksh93* Difficult-to-reproduce SIGSEGV in ksh93
Diffstat (limited to 'usr/src/lib/libshell/common/scripts/gnaw.sh')
-rw-r--r-- | usr/src/lib/libshell/common/scripts/gnaw.sh | 1045 |
1 files changed, 1045 insertions, 0 deletions
diff --git a/usr/src/lib/libshell/common/scripts/gnaw.sh b/usr/src/lib/libshell/common/scripts/gnaw.sh new file mode 100644 index 0000000000..880e18118b --- /dev/null +++ b/usr/src/lib/libshell/common/scripts/gnaw.sh @@ -0,0 +1,1045 @@ +#!/usr/bin/ksh93 + +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# gnaw - a simple ksh93 technology demo +# +# Note that this script has been written with the main idea to show +# many of ksh93's new features (comparing to ksh88/bash) and not +# as an example of efficient&&clean script code (much of the code +# could be done more efficient using compound variables, this script +# focus is the usage of associative arrays). +# + +# Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant +export PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin + +# Make sure all math stuff runs in the "C" locale to avoid problems +# with alternative # radix point representations (e.g. ',' instead of +# '.' in de_DE.*-locales). This needs to be set _before_ any +# floating-point constants are defined in this script). +if [[ "${LC_ALL}" != "" ]] ; then + export \ + LC_MONETARY="${LC_ALL}" \ + LC_MESSAGES="${LC_ALL}" \ + LC_COLLATE="${LC_ALL}" \ + LC_CTYPE="${LC_ALL}" + unset LC_ALL +fi +export LC_NUMERIC=C + +function print_setcursorpos +{ + print -n -- "${vtcode[cup_${1}_${2}]}" +} + +function beep +{ + ${quiet} || print -n -- "${vtcode["bel"]}" +} + +function fatal_error +{ + print -u2 "${progname}: $*" + exit 1 +} + +# Get terminal size and put values into a compound variable with the integer +# members "columns" and "lines" +function get_term_size +{ + nameref rect=$1 + + rect.columns=${ tput cols ; } || return 1 + rect.lines=${ tput lines ; } || return 1 + + return 0 +} + +function print_levelmap +{ + integer screen_y_offset=$1 + integer start_y_pos=$2 # start at this line in the map + integer max_numlines=$3 # maximum lines we're allowed to render + integer x + integer y + typeset line="" + + print_setcursorpos 0 ${screen_y_offset} + + for (( y=start_y_pos; (y-start_y_pos) < max_numlines && y < levelmap["max_y"] ; y++ )) ; do + line="" + for (( x=0 ; x < levelmap["max_x"] ; x++ )) ; do + line+="${levelmap["${x}_${y}"]}" + done + + print -- "${line} " + done + + # print lines filled with spaces for each line not filled + # by the level map + line="${vtcode["spaceline"]:0:${levelmap["max_x"]}}" + for (( ; (y-start_y_pos) < max_numlines ; y++ )) ; do + print -- "${line} " + done + return 0 +} + +function level_completed +{ + integer i + typeset dummy + typeset render_buffer="$( + print -n -- "${vtcode["clear"]}" + cat <<ENDOFTEXT + + # ###### # # ###### # + # # # # # # + # ##### # # ##### # + # # # # # # + # # # # # # + ###### ###### ## ###### ###### + + (Good job) + + ##### #### # # ###### + # # # # ## # # + # # # # # # # ##### + # # # # # # # # + # # # # # ## # + ##### #### # # ###### + + +ENDOFTEXT + + printf " SCORE: --> %s <--\n" "${player["score"]}" + printf " LIVES: --> %s <--\n" "${player["lives"]}" + )" + print -- "${render_buffer}${end_of_frame}" + + # wait five seconds and swallow any user input + for (( i=0 ; i < 50 ; i++ )) ; do + read -r -t 0.1 -n 1 dummy + done + + print "Press any key to continue...${end_of_frame}" + # wait five secs or for a key + read -r -t 5 -n 1 dummy + return 0 +} + +function game_over +{ + typeset dummy + typeset render_buffer="$( + print -n -- "${vtcode["clear"]}" + cat <<ENDOFTEXT + + #### ## # # ###### + # # # # ## ## # + # # # # ## # ##### + # ### ###### # # # + # # # # # # # + #### # # # # ###### + + (LOSER!) + + #### # # ###### ##### + # # # # # # # + # # # # ##### # # + # # # # # ##### + # # # # # # # + #### ## ###### # # + +ENDOFTEXT + + printf "\n SCORE: --> %s <--\n" "${player["score"]}" + )" + print -r -- "${render_buffer}${end_of_frame}" + + # wait five seconds and swallow any user input + for (( i=0 ; i < 50 ; i++ )) ; do + read -r -t 0.1 -n 1 dummy + done + + print "Press any key to continue...${end_of_frame}" + # wait five secs or for a key + read -r -t 5 -n 1 dummy + return 0 +} + +function run_logo +{ + typeset render_buffer="$( + cat <<ENDOFTEXT + + ##### # # # # # ### +# # ## # # # # # # ### +# # # # # # # # # ### +# #### # # # # # # # # # +# # # # # ####### # # # +# # # ## # # # # # ### + ##### # # # # ## ## ### +ENDOFTEXT + )" + print -- "${vtcode["clear"]}${render_buffer}" + + # wait two seconds and swallow any user input + for (( i=0 ; i < 20 ; i++ )) ; do + read -r -t 0.1 -n 1 dummy + done + + print "\n (The KornShell 93 maze game)" + + attract_mode + return 0 +} + +function attract_mode +{ +( + # Now present some info, line-by-line in an endless loop + # until the user presses a key (we turn the "magic" return + # code for that) + integer -r magic_return_code=69 + typeset line + IFS='' ; # Make sure we do not swallow whitespaces + + while true ; do + ( + redirect 5<&0 + + (cat <<ENDOFTEXT + + + + + + ################ + ######################## + ############################ + ####### ###### ####### + ###### ###### ######## + ####### ###### ####### + ############################## + ############################## + ############################## + ############################## + ############################## + ######### ######## ######### + # #### #### #### # + + + + + + + Written by + + Roland Mainz + (roland.mainz@nrubsig.org) + + + + + + + ############## + ######################## + #################**############ + ################################ + ############################ + ###################### + ################ + ###################### + ############################ + ################################ + ############################## + ######################## + ############## + + + + + + + + High scores: + + * 'chin' 8200 pt + * 'gisburn' 7900 pt + * 'tpenta' 5520 pt + * 'kupfer' 5510 pt + * 'noname' 5000 pt + * 'noname' 4000 pt + * 'livad' 3120 pt + * 'noname' 3000 pt + * 'noname' 2000 pt + * 'noname' 1000 pt + +ENDOFTEXT + + # clear screen, line-by-line + for (( i=0 ; i < termsize.lines ; i++ )) ; do print "" ; done + ) | (while read -r line ; do + read -r -t 0.3 -n 1 c <&5 + [[ "$c" != "" ]] && exit ${magic_return_code} + print -- "${line}" + done) + (( $? == magic_return_code )) && exit ${magic_return_code} + ) + (( $? == magic_return_code )) && return 0 + + sleep 2 + done +) +} + +function run_menu +{ + integer numlevels=0 + integer selected_level=0 + typeset l + + # built list of available levels based on the "function levelmap_.*" + # built into this script + typeset -f | egrep "^function.*levelmap_.*" | sed 's/^function //' | + while read -r l ; do + levellist[numlevels]="$l" + numlevels+=1 + done + + # swallow any queued user input (e.g. drain stdin) + read -r -t 0.1 -n 100 dummy + + while true ; do + # menu loop with timeout (which switches to "attract mode") + while true ; do + print -n -- "${vtcode["clear"]}" + + cat <<ENDOFTEXT +>======================================\ +> /-\ .--. | +> | OO| / _.-' .-. .-. .-. .-. | +> | | \ '-. '-' '-' '-' '-' | +> ^^^^^ '--' | +>======\ /================\ .-. | +> | | | '-' | + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +ENDOFTEXT + print " GNAW - the ksh93 maze game" + print "\n\tMenu:" + + print "\t - [L]evels:" + for (( i=0 ; i < numlevels ; i++ )) ; do + printf "\t %s %s \n" "$( (( i == selected_level )) && print -n "*" || print -n " ")" "${levellist[i]##levelmap_}" + done + + print "\t - Rendering options:" + printf "\t [%s] Use [U]nicode\n" "$( (( game_use_unicode == 1 )) && print -n "x" || print -n "_" )" + printf "\t [%s] Use [C]olors\n" "$( (( game_use_colors == 1 )) && print -n "x" || print -n "_" )" + + print "\t - [S]tart - [Q]uit" + + # wait 30 secs (before we switch to "attract mode") + c="" ; read -r -t 30 -n 1 c + case "$c" in + 'l') (( selected_level=(selected_level+numlevels+1) % numlevels )) ;; + 'L') (( selected_level=(selected_level+numlevels-1) % numlevels )) ;; + ~(Fi)s) + (( game_use_colors == 1 )) && print -- "${vtcode["bg_black"]}" + case "${game_use_colors}${game_use_unicode}" in + "00") main_loop "${levellist[selected_level]}" ;; + "01") main_loop "${levellist[selected_level]}" | map_filter 0 1 ;; + "10") main_loop "${levellist[selected_level]}" | map_filter 1 0 ;; + "11") main_loop "${levellist[selected_level]}" | map_filter 1 1 ;; + esac + print -- "${vtcode["vtreset"]}" + ;; + ~(Fi)q | $'\E') + # make sure we do not exit on a cursor key (e.g. <esc>[A,B,C,D) + read -r -t 0.01 -n 1 c + if [[ "$c" == "[" ]] ; then + # this was a cursor key sequence, just eat the 3rd charcater + read -r -t 0.01 -n 1 c + else + exit 0 + fi + ;; + ~(Fi)u) (( game_use_unicode=(game_use_unicode+2+1) % 2)) ;; + ~(Fi)c) (( game_use_colors=(game_use_colors+2+1) % 2)) ;; + "") break ;; # timeout, switch to attract mode + *) beep ;; + esac + done + + print -n -- "${vtcode["clear"]}" + attract_mode + done + return 0 +} + +function levelmap_stripes +{ +cat <<ENDOFLEVEL +################################### +#....... ............... P # +#########..#################..### # +#########..#################..### # +#....... .. ..............# # +############### ################ # +############### ################ # +#............. M ..............# # +##..##################### ###### # +##..##################### ###### # +#....... ........... .......# # +######## ############ ######### # +# #### ############ ######### # +# #.................. ......# # +# ############################### # +# # +################################### +ENDOFLEVEL + return 0 +} + +function levelmap_livad +{ +cat <<ENDOFLEVEL +##################################################### +# # +# ############## ############### ################ # +# #............ P ..............# # +# .#############################################.# # +# #.#.......... ............#. # +# #.#.########## ############### ############.#.# # +# #...#........ ..........#...# # +# #...#.#####################################.#.#.# # +# #...#.#...... ........#...#.# # +# #.#.#...###### #########################.#.#.#.# # +# .#.....#.... M ......#...#.#.# # +# #.#.#...####################### ########.#.#.#.# # +# #...#.#...... ........#...#.# # +# #...#.######## ############### ##########.#.#.# # +# #...#........ ..........#...# # +# #.#.#########################################.#.# # +# #.#.......... ............#. # +# .############ ############### ##############.# # +# #............ ..............# # +# ################################################# # +# # +##################################################### +ENDOFLEVEL + return 0 +} + +function levelmap_classic1 +{ +cat <<ENDOFLEVEL +######################### +#.P.........#...........# +#.####.####.#.####.####.# +#.# #.# #.#.# #.# #.# +#.# #.# #.#.# #.# #.# +#.####.####.#.####.####.# +#.......................# +#.####.#.#######.#.####.# +#.# #.#.# #.#.# #.# +#.####.#.#######.#.####.# +#......#....#....#......# +######.####.#.####.###### +###### # # ###### +###### # ## ## # ###### +###### # # # # ###### +# # M # # +###### # ####### # ###### +###### # # ###### +###### # ####### # ###### +###### # # # # ###### +######.#.#######.#.###### +#...........#...........# +#.###.###...#...###.###.# +#...#...............#...# +###.#....#######....#.### +# #.#..#.# #.#..#.# # +###....#.#######.#....### +#......#....#....#......# +#.#########.#.#########.# +#.......................# +######################### +ENDOFLEVEL + return 0 +} + +function levelmap_classic2 +{ +cat <<ENDOFLEVEL +####################### +#.P...#.........#.....# +#.###.#.#######.#.###.# +#.....................# +###.#.####.#.####.#.### +###.#......#......#.### +###.###.#######.###.### +###.................### +###.###.### ###.###.### +###.#...#M #...#.### +###.#.#.#######.#.#.### +#.....#.........#.....# +###.#####..#..#####.### +###........#........### +###.###.#######.###.### +#.....................# +#.###.####.#.####.###.# +#.###.#....#....#.###.# +#.###.#.#######.#.###.# +#.....................# +####################### +ENDOFLEVEL + return 0 +} + +function levelmap_easy +{ +cat <<ENDOFLEVEL +################## +# .............. # +# . ###### # +# . # M # # +# . # # # +# . ### ## # +# . # # +# . ### # +# . # +# .......... # +# .......... P # +################## +ENDOFLEVEL + return 0 +} + +function levelmap_sunsolaristext +{ +cat <<ENDOFLEVEL +################################################ +# .#### . # #....# # +# # # # #....# # +# #### # # #.#..# M # +# # # # #..#.# # +# # # # # #...## # +# #### #### #....# # +# # +# #### #### # ## ##### # #### # +# # #. .# # # # #....# # # # +# #### # # # # P # #....# # #### # +# # # ### #.#### #.### # # # +# # .# #. .. # # #...# # # # # +# #### #### ###### . # ....# # ####. # +################################################ +ENDOFLEVEL + return 0 +} + +function read_levelmap +{ + typeset map="$( $1 )" + + integer y=0 + integer x=0 + integer maxx=0 + integer numdots=0 + typeset line + typeset c + + while read -r line ; do + for (( x=0 ; x < ${#line} ; x++ )) ; do + c="${line:x:1}" + + case $c in + ".") numdots+=1 ;; + "M") + # log start position of monsters + levelmap["monsterstartpos_x"]="$x" + levelmap["monsterstartpos_y"]="$y" + c=" " + ;; + "P") + # log start position of player + levelmap["playerstartpos_x"]="$x" + levelmap["playerstartpos_y"]="$y" + c=" " + ;; + esac + + levelmap["${x}_${y}"]="$c" + done + (( maxx=x , y++ )) + done <<<"${map}" + + levelmap["max_x"]=${maxx} + levelmap["max_y"]=${y} + levelmap["numdots"]=${numdots} + + # consistency checks + if [[ "${levelmap["monsterstartpos_x"]}" == "" ]] ; then + fatal_error "read_levelmap: monsterstartpos_x is empty." + fi + if [[ "${levelmap["playerstartpos_x"]}" == "" ]] ; then + fatal_error "read_levelmap: playerstartpos_x is empty." + fi + + return 0 +} + +function player.set +{ + case "${.sh.subscript}" in + pos_y) + if [[ "${levelmap["${player["pos_x"]}_${.sh.value}"]}" == "#" ]] ; then + .sh.value=${player["pos_y"]} + beep + fi + ;; + + pos_x) + if [[ "${levelmap["${.sh.value}_${player["pos_y"]}"]}" == "#" ]] ; then + .sh.value=${player["pos_x"]} + beep + fi + ;; + esac + return 0 +} + +function monster.set +{ + case "${.sh.subscript}" in + *_pos_y) + if [[ "${levelmap["${monster[${currmonster}_"pos_x"]}_${.sh.value}"]}" == "#" ]] ; then + .sh.value=${monster[${currmonster}_"pos_y"]} + # turn homing off when the monster hit a wall + monster[${currmonster}_"homing"]=0 + fi + ;; + + *_pos_x) + if [[ "${levelmap["${.sh.value}_${monster[${currmonster}_"pos_y"]}"]}" == "#" ]] ; then + .sh.value=${monster[${currmonster}_"pos_x"]} + # turn homing off when the monster hit a wall + monster[${currmonster}_"homing"]=0 + fi + ;; + esac + return 0 +} + +function render_game +{ + # render_buffer is some kind of "background buffer" to "double buffer" + # all output and combine it in one write to reduce flickering in the + # terminal + typeset render_buffer="$( + integer screen_y_offset=1 + integer start_y_pos=0 + integer render_num_lines=${levelmap["max_y"]} + + if (( (termsize.lines-3) < levelmap["max_y"] )) ; then + (( start_y_pos=player["pos_y"] / 2)) + (( render_num_lines=termsize.lines-5)) + fi + + #print -n -- "${vtcode["clear"]}" + print_setcursorpos 0 0 + + # print score (note the " " around "%d" are neccesary to clean up cruft + # when we overwrite the level + printf "SCORE: %05d DOTS: %.3d LIVES: %2.d " "${player["score"]}" "${levelmap["numdots"]}" "${player["lives"]}" + print_levelmap ${screen_y_offset} ${start_y_pos} ${render_num_lines} + + # render player + print_setcursorpos ${player["pos_x"]} $((player["pos_y"]+screen_y_offset-start_y_pos)) + print -n "@" + + # render monsters + for currmonster in ${monsterlist} ; do + (( m_pos_x=monster[${currmonster}_"pos_x"] )) + (( m_pos_y=monster[${currmonster}_"pos_y"]+screen_y_offset-start_y_pos )) + + if (( m_pos_y >= screen_y_offset && m_pos_y < render_num_lines )) ; then + print_setcursorpos ${m_pos_x} ${m_pos_y} + print -n "x" + fi + done + + # status block + print_setcursorpos 0 $((render_num_lines+screen_y_offset)) + emptyline=" " + print -n " >> ${player["message"]} <<${emptyline:0:${#emptyline}-${#player["message"]}}" + )" + print -r -- "${render_buffer}${end_of_frame}" +# print "renderbuffersize=$(print "${render_buffer}" | wc -c) ${end_of_frame}" + return 0 +} + +function main_loop +{ + float sleep_per_cycle=0.2 + float seconds_before_read + integer num_cycles=0 + float rs + + print -n -- "${vtcode["clear"]}" + + read_levelmap "$1" + + # player init + player["pos_x"]=${levelmap["playerstartpos_x"]} + player["pos_y"]=${levelmap["playerstartpos_y"]} + player["score"]=0 # player score + player["lives"]=5 # number of lives + player["invulnerable"]=10 # cycles how long the player remains invulnerable + player["message"]="Go..." + + monsterlist="maw claw jitterbug tentacle grendel" + + for currmonster in ${monsterlist} ; do + monster[${currmonster}_"pos_x"]=${levelmap["monsterstartpos_x"]} + monster[${currmonster}_"pos_y"]=${levelmap["monsterstartpos_y"]} + monster[${currmonster}_"xstep"]=0 + monster[${currmonster}_"ystep"]=0 + monster[${currmonster}_"homing"]=0 + done + + # main game cycle loop + while true ; do + num_cycles+=1 + seconds_before_read=${SECONDS} + c="" ; read -r -t ${sleep_per_cycle} -n 1 c + + if [[ "$c" != "" ]] ; then + # special case handling for cursor keys which are usually composed + # of three characters (e.g. "<ESC>[D"). If only <ESC> is hit we + # quicky exit + if [[ "$c" == $'\E' ]] ; then + read -r -t 0.1 -n 1 c + if [[ "$c" != "[" ]] ; then + return 0 + fi + + # we assume the user is using the cursor keys, this |read| + # should fetch the 3rd byte of the three-character sequence + # for the cursor keys + read -r -t 0.1 -n 1 c + fi + + # if the user hit a key the "read" above was interrupted + # and didn't wait exactly |sleep_per_cycle| seconds. + # We wait here some moments (|rs|="remaining seconds") to + # avoid that the game gets "faster" when more user input + # is given. + (( rs=sleep_per_cycle-(SECONDS-seconds_before_read) )) + (( rs > 0.001 )) && sleep ${rs} + + player["message"]="" + + case "$c" in + j|D|4) (( player["pos_x"]-=1 )) ;; + k|C|6) (( player["pos_x"]+=1 )) ;; + i|A|8) (( player["pos_y"]-=1 )) ;; + m|B|2) (( player["pos_y"]+=1 )) ;; + + q) return 0 ;; + esac + + if [[ "${levelmap["${player["pos_x"]}_${player["pos_y"]}"]}" == "." ]] ; then + levelmap["${player["pos_x"]}_${player["pos_y"]}"]=" " + (( levelmap["numdots"]-=1 )) + + (( player["score"]+=10 )) + player["message"]='GNAW!!' + + if (( levelmap["numdots"] <= 0 )) ; then + level_completed + return 0 + fi + fi + fi + + # generic player status change + if (( player["invulnerable"] > 0 )) ; then + (( player["invulnerable"]-=1 )) + fi + if (( player["lives"] <= 0 )) ; then + game_over + return 0 + fi + + # move monsters + for currmonster in ${monsterlist} ; do + # make monster as half as slow then the others when it is following the user + if (( monster[${currmonster}_"homing"] > 0 )) ; then + (( (num_cycles%2) > 0 )) && continue + fi + + if [[ ${monster[${currmonster}_"pos_x"]} == ${player["pos_x"]} ]] ; then + if (( (monster[${currmonster}_"pos_y"]-player["pos_y"]) > 0 )) ; then + (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=-1 )) + else + (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=+1 )) + fi + monster[${currmonster}_"homing"]=1 + if (( player["invulnerable"] <= 0 )) ; then + player["message"]="Attention: ${currmonster} is chasing you" + fi + elif (( monster[${currmonster}_"pos_y"] == player["pos_y"] )) ; then + if (( (monster[${currmonster}_"pos_x"]-player["pos_x"]) > 0 )) ; then + (( monster[${currmonster}_"xstep"]=-1 , monster[${currmonster}_"ystep"]=-0 )) + else + (( monster[${currmonster}_"xstep"]=+1 , monster[${currmonster}_"ystep"]=+0 )) + fi + monster[${currmonster}_"homing"]=1 + if (( player["invulnerable"] <= 0 )) ; then + player["message"]="Attention: ${currmonster} is chasing you" + fi + else + if (( monster[${currmonster}_"homing"] == 0 )) ; then + case $((SECONDS % 6 + RANDOM % 4)) in + 0) (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=+0 )) ;; + 2) (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=+1 )) ;; + 3) (( monster[${currmonster}_"xstep"]=+1 , monster[${currmonster}_"ystep"]=+0 )) ;; + 5) (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=-1 )) ;; + 6) (( monster[${currmonster}_"xstep"]=-1 , monster[${currmonster}_"ystep"]=+0 )) ;; + esac + fi + fi + + (( monster[${currmonster}_"pos_x"]=monster[${currmonster}_"pos_x"]+monster[${currmonster}_"xstep"] )) + (( monster[${currmonster}_"pos_y"]=monster[${currmonster}_"pos_y"]+monster[${currmonster}_"ystep"] )) + + # check if a monster hit the player + if (( player["invulnerable"] <= 0 )) ; then + if (( monster[${currmonster}_"pos_x"] == player["pos_x"] && \ + monster[${currmonster}_"pos_y"] == player["pos_y"] )) ; then + # if player was hit by a monster take one life and + # make him invulnerable for 10 cycles to avoid that + # the next cycle steals more lives + player["message"]="Ouuuchhhh" + player["invulnerable"]=10 + (( player["lives"]-=1 )) + + beep ; beep ; sleep 0.2 ; beep ; beep + fi + fi + done + + render_game + done + return 0 +} + +function map_filter +{ + typeset ch_player ch_monster ch_wall var + + if (( $1 == 1 )) ; then + ch_player="${vtcode["fg_yellow"]}" + ch_monster="${vtcode["fg_red"]}" + ch_wall="${vtcode["fg_blue"]}" + else + ch_player="" + ch_monster="" + ch_wall="" + fi + + if (( $2 == 1 )) ; then + # unicode map + ch_player+="$(printf '\u[24d2]')" + ch_monster+="$(printf '\u[2605]')" + ch_wall+="$(printf '\u[25a6]')" + else + # ascii map + ch_player+="@" + ch_monster+="x" + ch_wall+="#" + fi + + # note that this filter currently defeats the "double-buffering" + while IFS='' read -r -d "${end_of_frame}" var ; do + var="${var// /${vtcode["fg_grey"]} }" + var="${var//\./${vtcode["fg_lightred"]}.}" + var="${var//@/${ch_player}}" + var="${var//x/${ch_monster}}" + var="${var//#/${ch_wall}}" + + print -r -- "${var}" + done + return 0 +} + +function exit_trap +{ + # restore stty settings + stty ${saved_stty} + + print "bye." + return 0 +} + +function usage +{ + OPTIND=0 + getopts -a "${progname}" "${gnaw_usage}" OPT '-?' + exit 2 +} + +# program start +# make sure we use the ksh93 "cat" builtin which supports the "-u" option +builtin basename +builtin cat +builtin wc + +typeset progname="${ basename "${0}" ; }" + +# terminal size rect +typeset -C termsize=( + integer columns=-1 + integer lines=-1 +) + +# global variables +typeset quiet=false + +typeset -A levelmap +typeset -A player +typeset -A monster +# global rendering options +integer game_use_colors=0 +integer game_use_unicode=0 + +typeset -r gnaw_usage=$'+ +[-?\n@(#)\$Id: gnaw (Roland Mainz) 2008-11-04 \$\n] +[-author?Roland Mainz <roland.mainz@nrubsig.org>] +[+NAME?gnaw - maze game written in ksh93] +[+DESCRIPTION?\bgnaw\b is a maze game. + The player maneuvers a yellow "@" sign to navigate a maze while eating + small dots. A level is finished when all the dots are eaten. Five monsters + (maw, claw, jitterbug, tentacle and grendel) also wander the maze in an attempt + to catch the "@". Each level begins with all ghosts in their home, and "@" near + the bottom of the maze. The monsters are released from the home one by one at the + start of each level and start their rentless hunt after the player.] +[q:quiet?Disable use of terminal bell.] +[+SEE ALSO?\bksh93\b(1)] +' + +while getopts -a "${progname}" "${gnaw_usage}" OPT ; do +# printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|" + case ${OPT} in + q) quiet=true ;; + +q) quiet=false ;; + *) usage ;; + esac +done +shift $((OPTIND-1)) + +# save stty values and register the exit trap which restores these values on exit +saved_stty="$(stty -g)" +trap exit_trap EXIT + +print "Loading..." + +# set stty values, "-icanon min 1 time 0 -inpck" should improve input latency, +# "-echo" turns the terminal echo off +stty -icanon min 1 time 0 -inpck -echo + +get_term_size termsize || fatal_error "Could not get terminal size." + +# prechecks +(( termsize.columns < 60 )) && fatal_error "Terminal width must be larger than 60 columns (currently ${termsize.columns})." + +typeset -A vtcode +# color values taken from http://frexx.de/xterm-256-notes/, other +# codes from http://vt100.net/docs/vt100-tm/ +vtcode=( + ["bg_black"]="$(print -n "\E[40m")" + ["fg_black"]="$(print -n "\E[30m")" + ["fg_red"]="$(print -n "\E[31m")" + ["fg_lightred"]="$(print -n "\E[1;31m")" + ["fg_green"]="$(print -n "\E[32m")" + ["fg_lightgreen"]="$(print -n "\E[1;32m")" + ["fg_yellow"]="$(print -n "\E[33m")" + ["fg_lightyellow"]="$(print -n "\E[1;33m")" + ["fg_blue"]="$(print -n "\E[34m")" + ["fg_lightblue"]="$(print -n "\E[1;34m")" + ["fg_grey"]="$(print -n "\E[1;37m")" + ["fg_white"]="$(print -n "\E[37m")" + + # misc other vt stuff + ["vtreset"]="$(tput reset)" + ["clear"]="$(tput clear)" + ["bel"]="$(tput bel)" + ["spaceline"]="$(for (( i=0 ; i < termsize.columns ; i++ )) ; do print -n " " ; done)" +) + +# character used to as marker that a single frame ends at this point - this +# is used by the "double buffering" code to make sure the "read" builtin +# can read a whole "frame" instead of reading stuff line-by-line +typeset -r end_of_frame=$'\t' + +# get terminal sequence to move cursor to position x,y +# (see http://vt100.net/docs/vt100-ug/chapter3.html#CPR) +case ${TERM} in + xterm | xterm-color | vt100 | vt220 | dtterm | sun | sun-color) + cup="$(infocmp -1 | \ + egrep '^[[:space:]]*cup=' | \ + sed -e 's/.*cup=//' \ + -e 's/%[%id]*p1[%id]*/%2\\\$d/g' \ + -e 's/%[%id]*p2[%id]*/%1\\\$d/g' \ + -e 's/,$//')" + for (( x=0 ; x < termsize.columns ; x++ )) ; do + for (( y=0 ; y < termsize.lines ; y++ )) ; do + vtcode[cup_${x}_${y}]="$(printf "${cup}" $((x + 1)) $((y + 1)) )" + done + done + ;; + *) + printf "# Unrecognised terminal type '%s', fetching %dx%d items from terminfo database, please wait...\n" "${TERM}" "${termsize.columns}" "${termsize.lines}" + for (( x=0 ; x < termsize.columns ; x++ )) ; do + for (( y=0 ; y < termsize.lines ; y++ )) ; do + vtcode[cup_${x}_${y}]="$(tput cup ${y} ${x})" + done + done + ;; +esac + +print -- "${vtcode["vtreset"]}" + +run_logo +run_menu + +exit 0 +# EOF. |