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 (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. 26# 27 28# 29# gnaw - a simple ksh93 technology demo 30# 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). 36# 37 38# Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant 39export PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin 40 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). 45if [[ "${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 52fi 53export LC_NUMERIC=C 54 55function print_setcursorpos 56{ 57 print -n -- "${vtcode[cup_${1}_${2}]}" 58} 59 60function beep 61{ 62 ${quiet} || print -n -- "${vtcode["bel"]}" 63} 64 65function fatal_error 66{ 67 print -u2 "${progname}: $*" 68 exit 1 69} 70 71# Get terminal size and put values into a compound variable with the integer 72# members "columns" and "lines" 73function get_term_size 74{ 75 nameref rect=$1 76 77 rect.columns=${ tput cols ; } || return 1 78 rect.lines=${ tput lines ; } || return 1 79 80 return 0 81} 82 83function print_levelmap 84{ 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="" 91 92 print_setcursorpos 0 ${screen_y_offset} 93 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 99 100 print -- "${line} " 101 done 102 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 110} 111 112function level_completed 113{ 114 integer i 115 typeset dummy 116 typeset render_buffer="$( 117 print -n -- "${vtcode["clear"]}" 118 cat <<ENDOFTEXT 119 120 # ###### # # ###### # 121 # # # # # # 122 # ##### # # ##### # 123 # # # # # # 124 # # # # # # 125 ###### ###### ## ###### ###### 126 127 (Good job) 128 129 ##### #### # # ###### 130 # # # # ## # # 131 # # # # # # # ##### 132 # # # # # # # # 133 # # # # # ## # 134 ##### #### # # ###### 135 136 137ENDOFTEXT 138 139 printf " SCORE: --> %s <--\n" "${player["score"]}" 140 printf " LIVES: --> %s <--\n" "${player["lives"]}" 141 )" 142 print -- "${render_buffer}${end_of_frame}" 143 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 148 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 153} 154 155function game_over 156{ 157 typeset dummy 158 typeset render_buffer="$( 159 print -n -- "${vtcode["clear"]}" 160 cat <<ENDOFTEXT 161 162 #### ## # # ###### 163 # # # # ## ## # 164 # # # # ## # ##### 165 # ### ###### # # # 166 # # # # # # # 167 #### # # # # ###### 168 169 (LOSER!) 170 171 #### # # ###### ##### 172 # # # # # # # 173 # # # # ##### # # 174 # # # # # ##### 175 # # # # # # # 176 #### ## ###### # # 177 178ENDOFTEXT 179 180 printf "\n SCORE: --> %s <--\n" "${player["score"]}" 181 )" 182 print -r -- "${render_buffer}${end_of_frame}" 183 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 188 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 193} 194 195function run_logo 196{ 197 typeset render_buffer="$( 198 cat <<ENDOFTEXT 199 200 ##### # # # # # ### 201# # ## # # # # # # ### 202# # # # # # # # # ### 203# #### # # # # # # # # # 204# # # # # ####### # # # 205# # # ## # # # # # ### 206 ##### # # # # ## ## ### 207ENDOFTEXT 208 )" 209 print -- "${vtcode["clear"]}${render_buffer}" 210 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 215 216 print "\n (The KornShell 93 maze game)" 217 218 attract_mode 219 return 0 220} 221 222function attract_mode 223{ 224( 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 231 232 while true ; do 233 ( 234 redirect 5<&0 235 236 (cat <<ENDOFTEXT 237 238 239 240 241 242 ################ 243 ######################## 244 ############################ 245 ####### ###### ####### 246 ###### ###### ######## 247 ####### ###### ####### 248 ############################## 249 ############################## 250 ############################## 251 ############################## 252 ############################## 253 ######### ######## ######### 254 # #### #### #### # 255 256 257 258 259 260 261 Written by 262 263 Roland Mainz 264 (roland.mainz@nrubsig.org) 265 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 High scores: 292 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 303 304ENDOFTEXT 305 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} 314 ) 315 (( $? == magic_return_code )) && return 0 316 317 sleep 2 318 done 319) 320} 321 322function run_menu 323{ 324 integer numlevels=0 325 integer selected_level=0 326 typeset l 327 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 335 336 # swallow any queued user input (e.g. drain stdin) 337 read -r -t 0.1 -n 100 dummy 338 339 while true ; do 340 # menu loop with timeout (which switches to "attract mode") 341 while true ; do 342 print -n -- "${vtcode["clear"]}" 343 344 cat <<ENDOFTEXT 345>======================================\ 346> /-\ .--. | 347> | OO| / _.-' .-. .-. .-. .-. | 348> | | \ '-. '-' '-' '-' '-' | 349> ^^^^^ '--' | 350>======\ /================\ .-. | 351> | | | '-' | 352 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 353ENDOFTEXT 354 print " GNAW - the ksh93 maze game" 355 print "\n\tMenu:" 356 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 361 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 "_" )" 365 366 print "\t - [S]tart - [Q]uit" 367 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"]}" 382 ;; 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 391 fi 392 ;; 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 399 400 print -n -- "${vtcode["clear"]}" 401 attract_mode 402 done 403 return 0 404} 405 406function levelmap_stripes 407{ 408cat <<ENDOFLEVEL 409################################### 410#....... ............... P # 411#########..#################..### # 412#########..#################..### # 413#....... .. ..............# # 414############### ################ # 415############### ################ # 416#............. M ..............# # 417##..##################### ###### # 418##..##################### ###### # 419#....... ........... .......# # 420######## ############ ######### # 421# #### ############ ######### # 422# #.................. ......# # 423# ############################### # 424# # 425################################### 426ENDOFLEVEL 427 return 0 428} 429 430function levelmap_livad 431{ 432cat <<ENDOFLEVEL 433##################################################### 434# # 435# ############## ############### ################ # 436# #............ P ..............# # 437# .#############################################.# # 438# #.#.......... ............#. # 439# #.#.########## ############### ############.#.# # 440# #...#........ ..........#...# # 441# #...#.#####################################.#.#.# # 442# #...#.#...... ........#...#.# # 443# #.#.#...###### #########################.#.#.#.# # 444# .#.....#.... M ......#...#.#.# # 445# #.#.#...####################### ########.#.#.#.# # 446# #...#.#...... ........#...#.# # 447# #...#.######## ############### ##########.#.#.# # 448# #...#........ ..........#...# # 449# #.#.#########################################.#.# # 450# #.#.......... ............#. # 451# .############ ############### ##############.# # 452# #............ ..............# # 453# ################################################# # 454# # 455##################################################### 456ENDOFLEVEL 457 return 0 458} 459 460function levelmap_classic1 461{ 462cat <<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######################### 494ENDOFLEVEL 495 return 0 496} 497 498function levelmap_classic2 499{ 500cat <<ENDOFLEVEL 501####################### 502#.P...#.........#.....# 503#.###.#.#######.#.###.# 504#.....................# 505###.#.####.#.####.#.### 506###.#......#......#.### 507###.###.#######.###.### 508###.................### 509###.###.### ###.###.### 510###.#...#M #...#.### 511###.#.#.#######.#.#.### 512#.....#.........#.....# 513###.#####..#..#####.### 514###........#........### 515###.###.#######.###.### 516#.....................# 517#.###.####.#.####.###.# 518#.###.#....#....#.###.# 519#.###.#.#######.#.###.# 520#.....................# 521####################### 522ENDOFLEVEL 523 return 0 524} 525 526function levelmap_easy 527{ 528cat <<ENDOFLEVEL 529################## 530# .............. # 531# . ###### # 532# . # M # # 533# . # # # 534# . ### ## # 535# . # # 536# . ### # 537# . # 538# .......... # 539# .......... P # 540################## 541ENDOFLEVEL 542 return 0 543} 544 545function levelmap_sunsolaristext 546{ 547cat <<ENDOFLEVEL 548################################################ 549# .#### . # #....# # 550# # # # #....# # 551# #### # # #.#..# M # 552# # # # #..#.# # 553# # # # # #...## # 554# #### #### #....# # 555# # 556# #### #### # ## ##### # #### # 557# # #. .# # # # #....# # # # 558# #### # # # # P # #....# # #### # 559# # # ### #.#### #.### # # # 560# # .# #. .. # # #...# # # # # 561# #### #### ###### . # ....# # ####. # 562################################################ 563ENDOFLEVEL 564 return 0 565} 566 567function read_levelmap 568{ 569 typeset map="$( $1 )" 570 571 integer y=0 572 integer x=0 573 integer maxx=0 574 integer numdots=0 575 typeset line 576 typeset c 577 578 while read -r line ; do 579 for (( x=0 ; x < ${#line} ; x++ )) ; do 580 c="${line:x:1}" 581 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=" " 589 ;; 590 "P") 591 # log start position of player 592 levelmap["playerstartpos_x"]="$x" 593 levelmap["playerstartpos_y"]="$y" 594 c=" " 595 ;; 596 esac 597 598 levelmap["${x}_${y}"]="$c" 599 done 600 (( maxx=x , y++ )) 601 done <<<"${map}" 602 603 levelmap["max_x"]=${maxx} 604 levelmap["max_y"]=${y} 605 levelmap["numdots"]=${numdots} 606 607 # consistency checks 608 if [[ "${levelmap["monsterstartpos_x"]}" == "" ]] ; then 609 fatal_error "read_levelmap: monsterstartpos_x is empty." 610 fi 611 if [[ "${levelmap["playerstartpos_x"]}" == "" ]] ; then 612 fatal_error "read_levelmap: playerstartpos_x is empty." 613 fi 614 615 return 0 616} 617 618function player.set 619{ 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 625 fi 626 ;; 627 628 pos_x) 629 if [[ "${levelmap["${.sh.value}_${player["pos_y"]}"]}" == "#" ]] ; then 630 .sh.value=${player["pos_x"]} 631 beep 632 fi 633 ;; 634 esac 635 return 0 636} 637 638function monster.set 639{ 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 646 fi 647 ;; 648 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 654 fi 655 ;; 656 esac 657 return 0 658} 659 660function render_game 661{ 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"]} 669 670 if (( (termsize.lines-3) < levelmap["max_y"] )) ; then 671 (( start_y_pos=player["pos_y"] / 2)) 672 (( render_num_lines=termsize.lines-5)) 673 fi 674 675 #print -n -- "${vtcode["clear"]}" 676 print_setcursorpos 0 0 677 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} 682 683 # render player 684 print_setcursorpos ${player["pos_x"]} $((player["pos_y"]+screen_y_offset-start_y_pos)) 685 print -n "@" 686 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 )) 691 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" 695 fi 696 done 697 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"]}}" 702 )" 703 print -r -- "${render_buffer}${end_of_frame}" 704# print "renderbuffersize=$(print "${render_buffer}" | wc -c) ${end_of_frame}" 705 return 0 706} 707 708function main_loop 709{ 710 float sleep_per_cycle=0.2 711 float seconds_before_read 712 integer num_cycles=0 713 float rs 714 715 print -n -- "${vtcode["clear"]}" 716 717 read_levelmap "$1" 718 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..." 726 727 monsterlist="maw claw jitterbug tentacle grendel" 728 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 736 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 742 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 751 fi 752 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 757 fi 758 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} 766 767 player["message"]="" 768 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 )) ;; 774 775 q) return 0 ;; 776 esac 777 778 if [[ "${levelmap["${player["pos_x"]}_${player["pos_y"]}"]}" == "." ]] ; then 779 levelmap["${player["pos_x"]}_${player["pos_y"]}"]=" " 780 (( levelmap["numdots"]-=1 )) 781 782 (( player["score"]+=10 )) 783 player["message"]='GNAW!!' 784 785 if (( levelmap["numdots"] <= 0 )) ; then 786 level_completed 787 return 0 788 fi 789 fi 790 fi 791 792 # generic player status change 793 if (( player["invulnerable"] > 0 )) ; then 794 (( player["invulnerable"]-=1 )) 795 fi 796 if (( player["lives"] <= 0 )) ; then 797 game_over 798 return 0 799 fi 800 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 806 fi 807 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 )) 813 fi 814 monster[${currmonster}_"homing"]=1 815 if (( player["invulnerable"] <= 0 )) ; then 816 player["message"]="Attention: ${currmonster} is chasing you" 817 fi 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 )) 823 fi 824 monster[${currmonster}_"homing"]=1 825 if (( player["invulnerable"] <= 0 )) ; then 826 player["message"]="Attention: ${currmonster} is chasing you" 827 fi 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 837 fi 838 fi 839 840 (( monster[${currmonster}_"pos_x"]=monster[${currmonster}_"pos_x"]+monster[${currmonster}_"xstep"] )) 841 (( monster[${currmonster}_"pos_y"]=monster[${currmonster}_"pos_y"]+monster[${currmonster}_"ystep"] )) 842 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 )) 853 854 beep ; beep ; sleep 0.2 ; beep ; beep 855 fi 856 fi 857 done 858 859 render_game 860 done 861 return 0 862} 863 864function map_filter 865{ 866 typeset ch_player ch_monster ch_wall var 867 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="" 876 fi 877 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+="#" 888 fi 889 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}}" 897 898 print -r -- "${var}" 899 done 900 return 0 901} 902 903function exit_trap 904{ 905 # restore stty settings 906 stty ${saved_stty} 907 908 print "bye." 909 return 0 910} 911 912function usage 913{ 914 OPTIND=0 915 getopts -a "${progname}" "${gnaw_usage}" OPT '-?' 916 exit 2 917} 918 919# program start 920# make sure we use the ksh93 "cat" builtin which supports the "-u" option 921builtin basename 922builtin cat 923builtin wc 924 925typeset progname="${ basename "${0}" ; }" 926 927# terminal size rect 928compound termsize=( 929 integer columns=-1 930 integer lines=-1 931) 932 933# global variables 934typeset quiet=false 935 936typeset -A levelmap 937typeset -A player 938typeset -A monster 939# global rendering options 940integer game_use_colors=0 941integer game_use_unicode=0 942 943typeset -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)] 956' 957 958while 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 965done 966shift $((OPTIND-1)) 967 968# save stty values and register the exit trap which restores these values on exit 969saved_stty="$(stty -g)" 970trap exit_trap EXIT 971 972print "Loading..." 973 974# set stty values, "-icanon min 1 time 0 -inpck" should improve input latency, 975# "-echo" turns the terminal echo off 976stty -icanon min 1 time 0 -inpck -echo 977 978get_term_size termsize || fatal_error "Could not get terminal size." 979 980# prechecks 981(( termsize.columns < 60 )) && fatal_error "Terminal width must be larger than 60 columns (currently ${termsize.columns})." 982 983typeset -A vtcode 984# color values taken from http://frexx.de/xterm-256-notes/, other 985# codes from http://vt100.net/docs/vt100-tm/ 986vtcode=( 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")" 999 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)" 1005) 1006 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 1010typeset -r end_of_frame=$'\t' 1011 1012# get terminal sequence to move cursor to position x,y 1013# (see http://vt100.net/docs/vt100-ug/chapter3.html#CPR) 1014case ${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 1027 ;; 1028 *) 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 1035 ;; 1036esac 1037 1038print -- "${vtcode["vtreset"]}" 1039 1040run_logo 1041run_menu 1042 1043exit 0 1044# EOF. 1045