summaryrefslogtreecommitdiff
path: root/usr/src/lib/libshell/common/scripts/gnaw.sh
diff options
context:
space:
mode:
authorApril Chin <April.Chin@Sun.COM>2008-12-27 14:59:38 -0800
committerApril Chin <April.Chin@Sun.COM>2008-12-27 14:59:38 -0800
commit7c2fbfb345896881c631598ee3852ce9ce33fb07 (patch)
tree4b173b5657508562dfc0aa05f7d056d1e9add505 /usr/src/lib/libshell/common/scripts/gnaw.sh
parent6071ac1de68fed78e1e10052045bbb5f1732a263 (diff)
downloadillumos-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.sh1045
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.