8354 sync regcomp(3C) with upstream (fix make catalog)
[unleashed/tickless.git] / usr / src / lib / libshell / common / scripts / gnaw.sh
blob25445e7a4d27b61f04a670ad1bf0f7e54add5df5
1 #!/usr/bin/ksh93
4 # CDDL HEADER START
6 # The contents of this file are subject to the terms of the
7 # Common Development and Distribution License (the "License").
8 # You may not use this file except in compliance with the License.
10 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11 # or http://www.opensolaris.org/os/licensing.
12 # See the License for the specific language governing permissions
13 # and limitations under the License.
15 # When distributing Covered Code, include this CDDL HEADER in each
16 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17 # If applicable, add the following below this CDDL HEADER, with the
18 # fields enclosed by brackets "[]" replaced with your own identifying
19 # information: Portions Copyright [yyyy] [name of copyright owner]
21 # CDDL HEADER END
25 # Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
29 # gnaw - a simple ksh93 technology demo
31 # Note that this script has been written with the main idea to show
32 # many of ksh93's new features (comparing to ksh88/bash) and not
33 # as an example of efficient&&clean script code (much of the code
34 # could be done more efficient using compound variables, this script
35 # focus is the usage of associative arrays).
38 # Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant
39 export PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin
41 # Make sure all math stuff runs in the "C" locale to avoid problems
42 # with alternative # radix point representations (e.g. ',' instead of
43 # '.' in de_DE.*-locales). This needs to be set _before_ any
44 # floating-point constants are defined in this script).
45 if [[ "${LC_ALL}" != "" ]] ; then
46 export \
47 LC_MONETARY="${LC_ALL}" \
48 LC_MESSAGES="${LC_ALL}" \
49 LC_COLLATE="${LC_ALL}" \
50 LC_CTYPE="${LC_ALL}"
51 unset LC_ALL
53 export LC_NUMERIC=C
55 function print_setcursorpos
57 print -n -- "${vtcode[cup_${1}_${2}]}"
60 function beep
62 ${quiet} || print -n -- "${vtcode["bel"]}"
65 function fatal_error
67 print -u2 "${progname}: $*"
68 exit 1
71 # Get terminal size and put values into a compound variable with the integer
72 # members "columns" and "lines"
73 function get_term_size
75 nameref rect=$1
77 rect.columns=${ tput cols ; } || return 1
78 rect.lines=${ tput lines ; } || return 1
80 return 0
83 function print_levelmap
85 integer screen_y_offset=$1
86 integer start_y_pos=$2 # start at this line in the map
87 integer max_numlines=$3 # maximum lines we're allowed to render
88 integer x
89 integer y
90 typeset line=""
92 print_setcursorpos 0 ${screen_y_offset}
94 for (( y=start_y_pos; (y-start_y_pos) < max_numlines && y < levelmap["max_y"] ; y++ )) ; do
95 line=""
96 for (( x=0 ; x < levelmap["max_x"] ; x++ )) ; do
97 line+="${levelmap["${x}_${y}"]}"
98 done
100 print -- "${line} "
101 done
103 # print lines filled with spaces for each line not filled
104 # by the level map
105 line="${vtcode["spaceline"]:0:${levelmap["max_x"]}}"
106 for (( ; (y-start_y_pos) < max_numlines ; y++ )) ; do
107 print -- "${line} "
108 done
109 return 0
112 function level_completed
114 integer i
115 typeset dummy
116 typeset render_buffer="$(
117 print -n -- "${vtcode["clear"]}"
118 cat <<ENDOFTEXT
120 # ###### # # ###### #
121 # # # # # #
122 # ##### # # ##### #
123 # # # # # #
124 # # # # # #
125 ###### ###### ## ###### ######
127 (Good job)
129 ##### #### # # ######
130 # # # # ## # #
131 # # # # # # # #####
132 # # # # # # # #
133 # # # # # ## #
134 ##### #### # # ######
137 ENDOFTEXT
139 printf " SCORE: --> %s <--\n" "${player["score"]}"
140 printf " LIVES: --> %s <--\n" "${player["lives"]}"
142 print -- "${render_buffer}${end_of_frame}"
144 # wait five seconds and swallow any user input
145 for (( i=0 ; i < 50 ; i++ )) ; do
146 read -r -t 0.1 -n 1 dummy
147 done
149 print "Press any key to continue...${end_of_frame}"
150 # wait five secs or for a key
151 read -r -t 5 -n 1 dummy
152 return 0
155 function game_over
157 typeset dummy
158 typeset render_buffer="$(
159 print -n -- "${vtcode["clear"]}"
160 cat <<ENDOFTEXT
162 #### ## # # ######
163 # # # # ## ## #
164 # # # # ## # #####
165 # ### ###### # # #
166 # # # # # # #
167 #### # # # # ######
169 (LOSER!)
171 #### # # ###### #####
172 # # # # # # #
173 # # # # ##### # #
174 # # # # # #####
175 # # # # # # #
176 #### ## ###### # #
178 ENDOFTEXT
180 printf "\n SCORE: --> %s <--\n" "${player["score"]}"
182 print -r -- "${render_buffer}${end_of_frame}"
184 # wait five seconds and swallow any user input
185 for (( i=0 ; i < 50 ; i++ )) ; do
186 read -r -t 0.1 -n 1 dummy
187 done
189 print "Press any key to continue...${end_of_frame}"
190 # wait five secs or for a key
191 read -r -t 5 -n 1 dummy
192 return 0
195 function run_logo
197 typeset render_buffer="$(
198 cat <<ENDOFTEXT
200 ##### # # # # # ###
201 # # ## # # # # # # ###
202 # # # # # # # # # ###
203 # #### # # # # # # # # #
204 # # # # # ####### # # #
205 # # # ## # # # # # ###
206 ##### # # # # ## ## ###
207 ENDOFTEXT
209 print -- "${vtcode["clear"]}${render_buffer}"
211 # wait two seconds and swallow any user input
212 for (( i=0 ; i < 20 ; i++ )) ; do
213 read -r -t 0.1 -n 1 dummy
214 done
216 print "\n (The KornShell 93 maze game)"
218 attract_mode
219 return 0
222 function attract_mode
225 # Now present some info, line-by-line in an endless loop
226 # until the user presses a key (we turn the "magic" return
227 # code for that)
228 integer -r magic_return_code=69
229 typeset line
230 IFS='' ; # Make sure we do not swallow whitespaces
232 while true ; do
234 redirect 5<&0
236 (cat <<ENDOFTEXT
242 ################
243 ########################
244 ############################
245 ####### ###### #######
246 ###### ###### ########
247 ####### ###### #######
248 ##############################
249 ##############################
250 ##############################
251 ##############################
252 ##############################
253 ######### ######## #########
254 # #### #### #### #
261 Written by
263 Roland Mainz
264 (roland.mainz@nrubsig.org)
271 ##############
272 ########################
273 #################**############
274 ################################
275 ############################
276 ######################
277 ################
278 ######################
279 ############################
280 ################################
281 ##############################
282 ########################
283 ##############
291 High scores:
293 * 'chin' 8200 pt
294 * 'gisburn' 7900 pt
295 * 'tpenta' 5520 pt
296 * 'kupfer' 5510 pt
297 * 'noname' 5000 pt
298 * 'noname' 4000 pt
299 * 'livad' 3120 pt
300 * 'noname' 3000 pt
301 * 'noname' 2000 pt
302 * 'noname' 1000 pt
304 ENDOFTEXT
306 # clear screen, line-by-line
307 for (( i=0 ; i < termsize.lines ; i++ )) ; do print "" ; done
308 ) | (while read -r line ; do
309 read -r -t 0.3 -n 1 c <&5
310 [[ "$c" != "" ]] && exit ${magic_return_code}
311 print -- "${line}"
312 done)
313 (( $? == magic_return_code )) && exit ${magic_return_code}
315 (( $? == magic_return_code )) && return 0
317 sleep 2
318 done
322 function run_menu
324 integer numlevels=0
325 integer selected_level=0
326 typeset l
328 # built list of available levels based on the "function levelmap_.*"
329 # built into this script
330 typeset -f | egrep "^function.*levelmap_.*" | sed 's/^function //' |
331 while read -r l ; do
332 levellist[numlevels]="$l"
333 numlevels+=1
334 done
336 # swallow any queued user input (e.g. drain stdin)
337 read -r -t 0.1 -n 100 dummy
339 while true ; do
340 # menu loop with timeout (which switches to "attract mode")
341 while true ; do
342 print -n -- "${vtcode["clear"]}"
344 cat <<ENDOFTEXT
345 >======================================\
346 > /-\ .--. |
347 > | OO| / _.-' .-. .-. .-. .-. |
348 > | | \ '-. '-' '-' '-' '-' |
349 > ^^^^^ '--' |
350 >======\ /================\ .-. |
351 > | | | '-' |
352 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
353 ENDOFTEXT
354 print " GNAW - the ksh93 maze game"
355 print "\n\tMenu:"
357 print "\t - [L]evels:"
358 for (( i=0 ; i < numlevels ; i++ )) ; do
359 printf "\t %s %s \n" "$( (( i == selected_level )) && print -n "*" || print -n " ")" "${levellist[i]##levelmap_}"
360 done
362 print "\t - Rendering options:"
363 printf "\t [%s] Use [U]nicode\n" "$( (( game_use_unicode == 1 )) && print -n "x" || print -n "_" )"
364 printf "\t [%s] Use [C]olors\n" "$( (( game_use_colors == 1 )) && print -n "x" || print -n "_" )"
366 print "\t - [S]tart - [Q]uit"
368 # wait 30 secs (before we switch to "attract mode")
369 c="" ; read -r -t 30 -n 1 c
370 case "$c" in
371 'l') (( selected_level=(selected_level+numlevels+1) % numlevels )) ;;
372 'L') (( selected_level=(selected_level+numlevels-1) % numlevels )) ;;
373 ~(Fi)s)
374 (( game_use_colors == 1 )) && print -- "${vtcode["bg_black"]}"
375 case "${game_use_colors}${game_use_unicode}" in
376 "00") main_loop "${levellist[selected_level]}" ;;
377 "01") main_loop "${levellist[selected_level]}" | map_filter 0 1 ;;
378 "10") main_loop "${levellist[selected_level]}" | map_filter 1 0 ;;
379 "11") main_loop "${levellist[selected_level]}" | map_filter 1 1 ;;
380 esac
381 print -- "${vtcode["vtreset"]}"
383 ~(Fi)q | $'\E')
384 # make sure we do not exit on a cursor key (e.g. <esc>[A,B,C,D)
385 read -r -t 0.01 -n 1 c
386 if [[ "$c" == "[" ]] ; then
387 # this was a cursor key sequence, just eat the 3rd charcater
388 read -r -t 0.01 -n 1 c
389 else
390 exit 0
393 ~(Fi)u) (( game_use_unicode=(game_use_unicode+2+1) % 2)) ;;
394 ~(Fi)c) (( game_use_colors=(game_use_colors+2+1) % 2)) ;;
395 "") break ;; # timeout, switch to attract mode
396 *) beep ;;
397 esac
398 done
400 print -n -- "${vtcode["clear"]}"
401 attract_mode
402 done
403 return 0
406 function levelmap_stripes
408 cat <<ENDOFLEVEL
409 ###################################
410 #....... ............... P #
411 #########..#################..### #
412 #########..#################..### #
413 #....... .. ..............# #
414 ############### ################ #
415 ############### ################ #
416 #............. M ..............# #
417 ##..##################### ###### #
418 ##..##################### ###### #
419 #....... ........... .......# #
420 ######## ############ ######### #
421 # #### ############ ######### #
422 # #.................. ......# #
423 # ############################### #
425 ###################################
426 ENDOFLEVEL
427 return 0
430 function levelmap_livad
432 cat <<ENDOFLEVEL
433 #####################################################
435 # ############## ############### ################ #
436 # #............ P ..............# #
437 # .#############################################.# #
438 # #.#.......... ............#. #
439 # #.#.########## ############### ############.#.# #
440 # #...#........ ..........#...# #
441 # #...#.#####################################.#.#.# #
442 # #...#.#...... ........#...#.# #
443 # #.#.#...###### #########################.#.#.#.# #
444 # .#.....#.... M ......#...#.#.# #
445 # #.#.#...####################### ########.#.#.#.# #
446 # #...#.#...... ........#...#.# #
447 # #...#.######## ############### ##########.#.#.# #
448 # #...#........ ..........#...# #
449 # #.#.#########################################.#.# #
450 # #.#.......... ............#. #
451 # .############ ############### ##############.# #
452 # #............ ..............# #
453 # ################################################# #
455 #####################################################
456 ENDOFLEVEL
457 return 0
460 function levelmap_classic1
462 cat <<ENDOFLEVEL
463 #########################
464 #.P.........#...........#
465 #.####.####.#.####.####.#
466 #.# #.# #.#.# #.# #.#
467 #.# #.# #.#.# #.# #.#
468 #.####.####.#.####.####.#
469 #.......................#
470 #.####.#.#######.#.####.#
471 #.# #.#.# #.#.# #.#
472 #.####.#.#######.#.####.#
473 #......#....#....#......#
474 ######.####.#.####.######
475 ###### # # ######
476 ###### # ## ## # ######
477 ###### # # # # ######
478 # # M # #
479 ###### # ####### # ######
480 ###### # # ######
481 ###### # ####### # ######
482 ###### # # # # ######
483 ######.#.#######.#.######
484 #...........#...........#
485 #.###.###...#...###.###.#
486 #...#...............#...#
487 ###.#....#######....#.###
488 # #.#..#.# #.#..#.# #
489 ###....#.#######.#....###
490 #......#....#....#......#
491 #.#########.#.#########.#
492 #.......................#
493 #########################
494 ENDOFLEVEL
495 return 0
498 function levelmap_classic2
500 cat <<ENDOFLEVEL
501 #######################
502 #.P...#.........#.....#
503 #.###.#.#######.#.###.#
504 #.....................#
505 ###.#.####.#.####.#.###
506 ###.#......#......#.###
507 ###.###.#######.###.###
508 ###.................###
509 ###.###.### ###.###.###
510 ###.#...#M #...#.###
511 ###.#.#.#######.#.#.###
512 #.....#.........#.....#
513 ###.#####..#..#####.###
514 ###........#........###
515 ###.###.#######.###.###
516 #.....................#
517 #.###.####.#.####.###.#
518 #.###.#....#....#.###.#
519 #.###.#.#######.#.###.#
520 #.....................#
521 #######################
522 ENDOFLEVEL
523 return 0
526 function levelmap_easy
528 cat <<ENDOFLEVEL
529 ##################
530 # .............. #
531 # . ###### #
532 # . # M # #
533 # . # # #
534 # . ### ## #
535 # . # #
536 # . ### #
537 # . #
538 # .......... #
539 # .......... P #
540 ##################
541 ENDOFLEVEL
542 return 0
545 function levelmap_sunsolaristext
547 cat <<ENDOFLEVEL
548 ################################################
549 # .#### . # #....# #
550 # # # # #....# #
551 # #### # # #.#..# M #
552 # # # # #..#.# #
553 # # # # # #...## #
554 # #### #### #....# #
556 # #### #### # ## ##### # #### #
557 # # #. .# # # # #....# # # #
558 # #### # # # # P # #....# # #### #
559 # # # ### #.#### #.### # # #
560 # # .# #. .. # # #...# # # # #
561 # #### #### ###### . # ....# # ####. #
562 ################################################
563 ENDOFLEVEL
564 return 0
567 function read_levelmap
569 typeset map="$( $1 )"
571 integer y=0
572 integer x=0
573 integer maxx=0
574 integer numdots=0
575 typeset line
576 typeset c
578 while read -r line ; do
579 for (( x=0 ; x < ${#line} ; x++ )) ; do
580 c="${line:x:1}"
582 case $c in
583 ".") numdots+=1 ;;
584 "M")
585 # log start position of monsters
586 levelmap["monsterstartpos_x"]="$x"
587 levelmap["monsterstartpos_y"]="$y"
588 c=" "
590 "P")
591 # log start position of player
592 levelmap["playerstartpos_x"]="$x"
593 levelmap["playerstartpos_y"]="$y"
594 c=" "
596 esac
598 levelmap["${x}_${y}"]="$c"
599 done
600 (( maxx=x , y++ ))
601 done <<<"${map}"
603 levelmap["max_x"]=${maxx}
604 levelmap["max_y"]=${y}
605 levelmap["numdots"]=${numdots}
607 # consistency checks
608 if [[ "${levelmap["monsterstartpos_x"]}" == "" ]] ; then
609 fatal_error "read_levelmap: monsterstartpos_x is empty."
611 if [[ "${levelmap["playerstartpos_x"]}" == "" ]] ; then
612 fatal_error "read_levelmap: playerstartpos_x is empty."
615 return 0
618 function player.set
620 case "${.sh.subscript}" in
621 pos_y)
622 if [[ "${levelmap["${player["pos_x"]}_${.sh.value}"]}" == "#" ]] ; then
623 .sh.value=${player["pos_y"]}
624 beep
628 pos_x)
629 if [[ "${levelmap["${.sh.value}_${player["pos_y"]}"]}" == "#" ]] ; then
630 .sh.value=${player["pos_x"]}
631 beep
634 esac
635 return 0
638 function monster.set
640 case "${.sh.subscript}" in
641 *_pos_y)
642 if [[ "${levelmap["${monster[${currmonster}_"pos_x"]}_${.sh.value}"]}" == "#" ]] ; then
643 .sh.value=${monster[${currmonster}_"pos_y"]}
644 # turn homing off when the monster hit a wall
645 monster[${currmonster}_"homing"]=0
649 *_pos_x)
650 if [[ "${levelmap["${.sh.value}_${monster[${currmonster}_"pos_y"]}"]}" == "#" ]] ; then
651 .sh.value=${monster[${currmonster}_"pos_x"]}
652 # turn homing off when the monster hit a wall
653 monster[${currmonster}_"homing"]=0
656 esac
657 return 0
660 function render_game
662 # render_buffer is some kind of "background buffer" to "double buffer"
663 # all output and combine it in one write to reduce flickering in the
664 # terminal
665 typeset render_buffer="$(
666 integer screen_y_offset=1
667 integer start_y_pos=0
668 integer render_num_lines=${levelmap["max_y"]}
670 if (( (termsize.lines-3) < levelmap["max_y"] )) ; then
671 (( start_y_pos=player["pos_y"] / 2))
672 (( render_num_lines=termsize.lines-5))
675 #print -n -- "${vtcode["clear"]}"
676 print_setcursorpos 0 0
678 # print score (note the " " around "%d" are neccesary to clean up cruft
679 # when we overwrite the level
680 printf "SCORE: %05d DOTS: %.3d LIVES: %2.d " "${player["score"]}" "${levelmap["numdots"]}" "${player["lives"]}"
681 print_levelmap ${screen_y_offset} ${start_y_pos} ${render_num_lines}
683 # render player
684 print_setcursorpos ${player["pos_x"]} $((player["pos_y"]+screen_y_offset-start_y_pos))
685 print -n "@"
687 # render monsters
688 for currmonster in ${monsterlist} ; do
689 (( m_pos_x=monster[${currmonster}_"pos_x"] ))
690 (( m_pos_y=monster[${currmonster}_"pos_y"]+screen_y_offset-start_y_pos ))
692 if (( m_pos_y >= screen_y_offset && m_pos_y < render_num_lines )) ; then
693 print_setcursorpos ${m_pos_x} ${m_pos_y}
694 print -n "x"
696 done
698 # status block
699 print_setcursorpos 0 $((render_num_lines+screen_y_offset))
700 emptyline=" "
701 print -n " >> ${player["message"]} <<${emptyline:0:${#emptyline}-${#player["message"]}}"
703 print -r -- "${render_buffer}${end_of_frame}"
704 # print "renderbuffersize=$(print "${render_buffer}" | wc -c) ${end_of_frame}"
705 return 0
708 function main_loop
710 float sleep_per_cycle=0.2
711 float seconds_before_read
712 integer num_cycles=0
713 float rs
715 print -n -- "${vtcode["clear"]}"
717 read_levelmap "$1"
719 # player init
720 player["pos_x"]=${levelmap["playerstartpos_x"]}
721 player["pos_y"]=${levelmap["playerstartpos_y"]}
722 player["score"]=0 # player score
723 player["lives"]=5 # number of lives
724 player["invulnerable"]=10 # cycles how long the player remains invulnerable
725 player["message"]="Go..."
727 monsterlist="maw claw jitterbug tentacle grendel"
729 for currmonster in ${monsterlist} ; do
730 monster[${currmonster}_"pos_x"]=${levelmap["monsterstartpos_x"]}
731 monster[${currmonster}_"pos_y"]=${levelmap["monsterstartpos_y"]}
732 monster[${currmonster}_"xstep"]=0
733 monster[${currmonster}_"ystep"]=0
734 monster[${currmonster}_"homing"]=0
735 done
737 # main game cycle loop
738 while true ; do
739 num_cycles+=1
740 seconds_before_read=${SECONDS}
741 c="" ; read -r -t ${sleep_per_cycle} -n 1 c
743 if [[ "$c" != "" ]] ; then
744 # special case handling for cursor keys which are usually composed
745 # of three characters (e.g. "<ESC>[D"). If only <ESC> is hit we
746 # quicky exit
747 if [[ "$c" == $'\E' ]] ; then
748 read -r -t 0.1 -n 1 c
749 if [[ "$c" != "[" ]] ; then
750 return 0
753 # we assume the user is using the cursor keys, this |read|
754 # should fetch the 3rd byte of the three-character sequence
755 # for the cursor keys
756 read -r -t 0.1 -n 1 c
759 # if the user hit a key the "read" above was interrupted
760 # and didn't wait exactly |sleep_per_cycle| seconds.
761 # We wait here some moments (|rs|="remaining seconds") to
762 # avoid that the game gets "faster" when more user input
763 # is given.
764 (( rs=sleep_per_cycle-(SECONDS-seconds_before_read) ))
765 (( rs > 0.001 )) && sleep ${rs}
767 player["message"]=""
769 case "$c" in
770 j|D|4) (( player["pos_x"]-=1 )) ;;
771 k|C|6) (( player["pos_x"]+=1 )) ;;
772 i|A|8) (( player["pos_y"]-=1 )) ;;
773 m|B|2) (( player["pos_y"]+=1 )) ;;
775 q) return 0 ;;
776 esac
778 if [[ "${levelmap["${player["pos_x"]}_${player["pos_y"]}"]}" == "." ]] ; then
779 levelmap["${player["pos_x"]}_${player["pos_y"]}"]=" "
780 (( levelmap["numdots"]-=1 ))
782 (( player["score"]+=10 ))
783 player["message"]='GNAW!!'
785 if (( levelmap["numdots"] <= 0 )) ; then
786 level_completed
787 return 0
792 # generic player status change
793 if (( player["invulnerable"] > 0 )) ; then
794 (( player["invulnerable"]-=1 ))
796 if (( player["lives"] <= 0 )) ; then
797 game_over
798 return 0
801 # move monsters
802 for currmonster in ${monsterlist} ; do
803 # make monster as half as slow then the others when it is following the user
804 if (( monster[${currmonster}_"homing"] > 0 )) ; then
805 (( (num_cycles%2) > 0 )) && continue
808 if [[ ${monster[${currmonster}_"pos_x"]} == ${player["pos_x"]} ]] ; then
809 if (( (monster[${currmonster}_"pos_y"]-player["pos_y"]) > 0 )) ; then
810 (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=-1 ))
811 else
812 (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=+1 ))
814 monster[${currmonster}_"homing"]=1
815 if (( player["invulnerable"] <= 0 )) ; then
816 player["message"]="Attention: ${currmonster} is chasing you"
818 elif (( monster[${currmonster}_"pos_y"] == player["pos_y"] )) ; then
819 if (( (monster[${currmonster}_"pos_x"]-player["pos_x"]) > 0 )) ; then
820 (( monster[${currmonster}_"xstep"]=-1 , monster[${currmonster}_"ystep"]=-0 ))
821 else
822 (( monster[${currmonster}_"xstep"]=+1 , monster[${currmonster}_"ystep"]=+0 ))
824 monster[${currmonster}_"homing"]=1
825 if (( player["invulnerable"] <= 0 )) ; then
826 player["message"]="Attention: ${currmonster} is chasing you"
828 else
829 if (( monster[${currmonster}_"homing"] == 0 )) ; then
830 case $((SECONDS % 6 + RANDOM % 4)) in
831 0) (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=+0 )) ;;
832 2) (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=+1 )) ;;
833 3) (( monster[${currmonster}_"xstep"]=+1 , monster[${currmonster}_"ystep"]=+0 )) ;;
834 5) (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=-1 )) ;;
835 6) (( monster[${currmonster}_"xstep"]=-1 , monster[${currmonster}_"ystep"]=+0 )) ;;
836 esac
840 (( monster[${currmonster}_"pos_x"]=monster[${currmonster}_"pos_x"]+monster[${currmonster}_"xstep"] ))
841 (( monster[${currmonster}_"pos_y"]=monster[${currmonster}_"pos_y"]+monster[${currmonster}_"ystep"] ))
843 # check if a monster hit the player
844 if (( player["invulnerable"] <= 0 )) ; then
845 if (( monster[${currmonster}_"pos_x"] == player["pos_x"] && \
846 monster[${currmonster}_"pos_y"] == player["pos_y"] )) ; then
847 # if player was hit by a monster take one life and
848 # make him invulnerable for 10 cycles to avoid that
849 # the next cycle steals more lives
850 player["message"]="Ouuuchhhh"
851 player["invulnerable"]=10
852 (( player["lives"]-=1 ))
854 beep ; beep ; sleep 0.2 ; beep ; beep
857 done
859 render_game
860 done
861 return 0
864 function map_filter
866 typeset ch_player ch_monster ch_wall var
868 if (( $1 == 1 )) ; then
869 ch_player="${vtcode["fg_yellow"]}"
870 ch_monster="${vtcode["fg_red"]}"
871 ch_wall="${vtcode["fg_blue"]}"
872 else
873 ch_player=""
874 ch_monster=""
875 ch_wall=""
878 if (( $2 == 1 )) ; then
879 # unicode map
880 ch_player+="$(printf '\u[24d2]')"
881 ch_monster+="$(printf '\u[2605]')"
882 ch_wall+="$(printf '\u[25a6]')"
883 else
884 # ascii map
885 ch_player+="@"
886 ch_monster+="x"
887 ch_wall+="#"
890 # note that this filter currently defeats the "double-buffering"
891 while IFS='' read -r -d "${end_of_frame}" var ; do
892 var="${var// /${vtcode["fg_grey"]} }"
893 var="${var//\./${vtcode["fg_lightred"]}.}"
894 var="${var//@/${ch_player}}"
895 var="${var//x/${ch_monster}}"
896 var="${var//#/${ch_wall}}"
898 print -r -- "${var}"
899 done
900 return 0
903 function exit_trap
905 # restore stty settings
906 stty ${saved_stty}
908 print "bye."
909 return 0
912 function usage
914 OPTIND=0
915 getopts -a "${progname}" "${gnaw_usage}" OPT '-?'
916 exit 2
919 # program start
920 # make sure we use the ksh93 "cat" builtin which supports the "-u" option
921 builtin basename
922 builtin cat
923 builtin wc
925 typeset progname="${ basename "${0}" ; }"
927 # terminal size rect
928 compound termsize=(
929 integer columns=-1
930 integer lines=-1
933 # global variables
934 typeset quiet=false
936 typeset -A levelmap
937 typeset -A player
938 typeset -A monster
939 # global rendering options
940 integer game_use_colors=0
941 integer game_use_unicode=0
943 typeset -r gnaw_usage=$'+
944 [-?\n@(#)\$Id: gnaw (Roland Mainz) 2009-05-09 \$\n]
945 [-author?Roland Mainz <roland.mainz@nrubsig.org>]
946 [+NAME?gnaw - maze game written in ksh93]
947 [+DESCRIPTION?\bgnaw\b is a maze game.
948 The player maneuvers a yellow "@" sign to navigate a maze while eating
949 small dots. A level is finished when all the dots are eaten. Five monsters
950 (maw, claw, jitterbug, tentacle and grendel) also wander the maze in an attempt
951 to catch the "@". Each level begins with all ghosts in their home, and "@" near
952 the bottom of the maze. The monsters are released from the home one by one at the
953 start of each level and start their rentless hunt after the player.]
954 [q:quiet?Disable use of terminal bell.]
955 [+SEE ALSO?\bksh93\b(1)]
958 while getopts -a "${progname}" "${gnaw_usage}" OPT ; do
959 # printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|"
960 case ${OPT} in
961 q) quiet=true ;;
962 +q) quiet=false ;;
963 *) usage ;;
964 esac
965 done
966 shift $((OPTIND-1))
968 # save stty values and register the exit trap which restores these values on exit
969 saved_stty="$(stty -g)"
970 trap exit_trap EXIT
972 print "Loading..."
974 # set stty values, "-icanon min 1 time 0 -inpck" should improve input latency,
975 # "-echo" turns the terminal echo off
976 stty -icanon min 1 time 0 -inpck -echo
978 get_term_size termsize || fatal_error "Could not get terminal size."
980 # prechecks
981 (( termsize.columns < 60 )) && fatal_error "Terminal width must be larger than 60 columns (currently ${termsize.columns})."
983 typeset -A vtcode
984 # color values taken from http://frexx.de/xterm-256-notes/, other
985 # codes from http://vt100.net/docs/vt100-tm/
986 vtcode=(
987 ["bg_black"]="$(print -n "\E[40m")"
988 ["fg_black"]="$(print -n "\E[30m")"
989 ["fg_red"]="$(print -n "\E[31m")"
990 ["fg_lightred"]="$(print -n "\E[1;31m")"
991 ["fg_green"]="$(print -n "\E[32m")"
992 ["fg_lightgreen"]="$(print -n "\E[1;32m")"
993 ["fg_yellow"]="$(print -n "\E[33m")"
994 ["fg_lightyellow"]="$(print -n "\E[1;33m")"
995 ["fg_blue"]="$(print -n "\E[34m")"
996 ["fg_lightblue"]="$(print -n "\E[1;34m")"
997 ["fg_grey"]="$(print -n "\E[1;37m")"
998 ["fg_white"]="$(print -n "\E[37m")"
1000 # misc other vt stuff
1001 ["vtreset"]="$(tput reset)"
1002 ["clear"]="$(tput clear)"
1003 ["bel"]="$(tput bel)"
1004 ["spaceline"]="$(for (( i=0 ; i < termsize.columns ; i++ )) ; do print -n " " ; done)"
1007 # character used to as marker that a single frame ends at this point - this
1008 # is used by the "double buffering" code to make sure the "read" builtin
1009 # can read a whole "frame" instead of reading stuff line-by-line
1010 typeset -r end_of_frame=$'\t'
1012 # get terminal sequence to move cursor to position x,y
1013 # (see http://vt100.net/docs/vt100-ug/chapter3.html#CPR)
1014 case ${TERM} in
1015 xterm | xterm-color | vt100 | vt220 | dtterm | sun | sun-color)
1016 cup="$(infocmp -1 | \
1017 egrep '^[[:space:]]*cup=' | \
1018 sed -e 's/.*cup=//' \
1019 -e 's/%[%id]*p1[%id]*/%2\\\$d/g' \
1020 -e 's/%[%id]*p2[%id]*/%1\\\$d/g' \
1021 -e 's/,$//')"
1022 for (( x=0 ; x < termsize.columns ; x++ )) ; do
1023 for (( y=0 ; y < termsize.lines ; y++ )) ; do
1024 vtcode[cup_${x}_${y}]="$(printf "${cup}" $((x + 1)) $((y + 1)) )"
1025 done
1026 done
1029 printf "# Unrecognised terminal type '%s', fetching %dx%d items from terminfo database, please wait...\n" "${TERM}" "${termsize.columns}" "${termsize.lines}"
1030 for (( x=0 ; x < termsize.columns ; x++ )) ; do
1031 for (( y=0 ; y < termsize.lines ; y++ )) ; do
1032 vtcode[cup_${x}_${y}]="$(tput cup ${y} ${x})"
1033 done
1034 done
1036 esac
1038 print -- "${vtcode["vtreset"]}"
1040 run_logo
1041 run_menu
1043 exit 0
1044 # EOF.