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