gnaw revision da2e3ebdc1edfbc5028edf1354e7dd2fa69a7968
#!/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 2007 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
# ident "%Z%%M% %I% %E% SMI"
#
#
# 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.
#
# Solaris needs /usr/xpg4/bin/ because the tools in /usr/bin are not POSIX-conformant
export PATH=/usr/xpg4/bin:/bin:/usr/bin
function print_setcursorpos
{
print -n "${vtcode[cup_${1}_${2}]}"
}
function beep
{
${quiet} || print -n "${vtcode["bel"]}"
}
function fatal_error
{
print -u 2 "${progname}: $@"
exit 1
}
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
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
}
function level_completed
{
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}"
# wait five seconds and swallow any user input
for (( i=0 ; i < 50 ; i++ )) ; do
read -t 0.1 -n 1 dummy
done
print "Press any key to continue..."
# wait five secs or for a key
read -t 5 -n 1 dummy
}
function game_over
{
render_buffer="$(
print -n "${vtcode["clear"]}"
cat <<ENDOFTEXT
#### ## # # ######
# # # # ## ## #
# # # # ## # #####
# ### ###### # # #
# # # # # # #
#### # # # # ######
(LOSER!)
#### # # ###### #####
# # # # # # #
# # # # ##### # #
# # # # # #####
# # # # # # #
#### ## ###### # #
ENDOFTEXT
printf "\n SCORE: --> %s <--\n" "${player["score"]}"
)"
print "${render_buffer}"
# wait five seconds and swallow any user input
for (( i=0 ; i < 50 ; i++ )) ; do
read -t 0.1 -n 1 dummy
done
print "Press any key to continue..."
# wait five secs or for a key
read -t 5 -n 1 dummy
}
function run_logo
{
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 -t 0.1 -n 1 dummy
done
print "\n (The KornShell 93 maze game)"
attract_mode
}
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)
magic_return_code=69
IFS="|" ; # Make sure we do not swallow whitespaces
while true ; do
(
exec 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 < LINES ; i++ )) ; do print "" ; done
) | (while read line ; do
read -t 0.3 -n 1 c <&5
[ "$c" != "" ] && exit ${magic_return_code}
print "${line}"
done)
[ $? -eq ${magic_return_code} ] && exit ${magic_return_code}
)
[ $? -eq ${magic_return_code} ] && return 0
sleep 2
done
)
}
function run_menu
{
integer numlevels=0
integer selected_level=0
# built list of available levels based on the "function levelmap_.*"
# built into this script
typeset -f | egrep "^function.*levelmap_.*" | sed 's/^function //' |
while read l ; do
levellist[numlevels]="$l"
numlevels+=1
done
# swallow any queued user input (e.g. drain stdin)
read -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 -eq $selected_level ] && print -n "*" || print -n " ")" "${levellist[i]##levelmap_}"
done
print "\t - Rendering options:"
printf "\t [%s] Use [U]nicode\n" "$([ $game_use_unicode -eq 1 ] && print -n "x" || print -n "_")"
printf "\t [%s] Use [C]olors\n" "$([ $game_use_colors -eq 1 ] && print -n "x" || print -n "_")"
print "\t - [S]tart - [Q]uit"
# wait 30 secs (before we switch to "attract mode")
c="" ; read -t 30 -n 1 c
case "$c" in
'l') selected_level=$(((selected_level+numlevels+1) % numlevels)) ;;
'L') selected_level=$(((selected_level+numlevels-1) % numlevels)) ;;
~(Ei)s)
[ ${game_use_colors} -eq 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"]}"
;;
~(Ei)q|$'\E')
# make sure we do not exit on a cursor key (e.g. <esc>[A,B,C,D)
read -t 0.01 -n 1 c
if [ "$c" = "[" ] ; then
# this was a cursor key sequence, just eat the 3rd charcater
read -t 0.01 -n 1 c
else
exit 0
fi
;;
~(Ei)u) game_use_unicode=$(((game_use_unicode+2+1) % 2)) ;;
~(Ei)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
}
function levelmap_stripes
{
cat <<ENDOFLEVEL
###################################
#....... ............... P #
#########..#################..### #
#########..#################..### #
#....... .. ..............# #
############### ################ #
############### ################ #
#............. M ..............# #
##..##################### ###### #
##..##################### ###### #
#....... ........... .......# #
######## ############ ######### #
# #### ############ ######### #
# #.................. ......# #
# ############################### #
# #
###################################
ENDOFLEVEL
}
function levelmap_livad
{
cat <<ENDOFLEVEL
#####################################################
# #
# ############## ############### ################ #
# #............ P ..............# #
# .#############################################.# #
# #.#.......... ............#. #
# #.#.########## ############### ############.#.# #
# #...#........ ..........#...# #
# #...#.#####################################.#.#.# #
# #...#.#...... ........#...#.# #
# #.#.#...###### #########################.#.#.#.# #
# .#.....#.... M ......#...#.#.# #
# #.#.#...####################### ########.#.#.#.# #
# #...#.#...... ........#...#.# #
# #...#.######## ############### ##########.#.#.# #
# #...#........ ..........#...# #
# #.#.#########################################.#.# #
# #.#.......... ............#. #
# .############ ############### ##############.# #
# #............ ..............# #
# ################################################# #
# #
#####################################################
ENDOFLEVEL
}
function levelmap_classic1
{
cat <<ENDOFLEVEL
#########################
#.P.........#...........#
#.####.####.#.####.####.#
#.# #.# #.#.# #.# #.#
#.# #.# #.#.# #.# #.#
#.####.####.#.####.####.#
#.......................#
#.####.#.#######.#.####.#
#.# #.#.# #.#.# #.#
#.####.#.#######.#.####.#
#......#....#....#......#
######.####.#.####.######
###### # # ######
###### # ## ## # ######
###### # # # # ######
# # M # #
###### # ####### # ######
###### # # ######
###### # ####### # ######
###### # # # # ######
######.#.#######.#.######
#...........#...........#
#.###.###...#...###.###.#
#...#...............#...#
###.#....#######....#.###
# #.#..#.# #.#..#.# #
###....#.#######.#....###
#......#....#....#......#
#.#########.#.#########.#
#.......................#
#########################
ENDOFLEVEL
}
function levelmap_classic2
{
cat <<ENDOFLEVEL
#######################
#.P...#.........#.....#
#.###.#.#######.#.###.#
#.....................#
###.#.####.#.####.#.###
###.#......#......#.###
###.###.#######.###.###
###.................###
###.###.### ###.###.###
###.#...#M #...#.###
###.#.#.#######.#.#.###
#.....#.........#.....#
###.#####..#..#####.###
###........#........###
###.###.#######.###.###
#.....................#
#.###.####.#.####.###.#
#.###.#....#....#.###.#
#.###.#.#######.#.###.#
#.....................#
#######################
ENDOFLEVEL
}
function levelmap_easy
{
cat <<ENDOFLEVEL
##################
# .............. #
# . ###### #
# . # M # #
# . # # #
# . ### ## #
# . # #
# . ### #
# . #
# .......... #
# .......... P #
##################
ENDOFLEVEL
}
function levelmap_sunsolaristext
{
cat <<ENDOFLEVEL
################################################
# .#### . # #....# #
# # # # #....# #
# #### # # #.#..# M #
# # # # #..#.# #
# # # # # #...## #
# #### #### #....# #
# #
# #### #### # ## ##### # #### #
# # #. .# # # # #....# # # #
# #### # # # # P # #....# # #### #
# # # ### #.#### #.### # # #
# # .# #. .. # # #...# # # # #
# #### #### ###### . # ....# # ####. #
################################################
ENDOFLEVEL
}
function read_levelmap
{
map="$( $1 )"
integer y=0
integer x=0
integer maxx=0
integer numdots=0
print "$map" |
while read line ; do
x=0
while (( x < ${#line} )) ; do
c="${line:x:1}"
case $c in
".") numdots+=1 ;;
"M")
levelmap["monsterstartpos_x"]="$x"
levelmap["monsterstartpos_y"]="$y"
c=" "
;;
"P")
levelmap["playerstartpos_x"]="$x"
levelmap["playerstartpos_y"]="$y"
c=" "
;;
esac
levelmap["${x}_${y}"]="$c"
let x++
done
maxx=$x
let y++
done
levelmap["max_x"]=${maxx}
levelmap["max_y"]=${y}
levelmap["numdots"]=${numdots}
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
}
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
}
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
render_buffer="$(
screen_y_offset=1
start_y_pos=0
render_num_lines=${levelmap["max_y"]}
if (( (LINES-3) < levelmap["max_y"] )) ; then
start_y_pos=$((player["pos_y"] / 2))
render_num_lines=$((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
let m_pos_x=monster[${currmonster}_"pos_x"]
let 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 "${render_buffer}"
# print "renderbuffersize=$(print "${render_buffer}" | wc -c) "
}
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 -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 -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 -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) let player["pos_x"]-=1 ;;
k|C|6) let player["pos_x"]+=1 ;;
i|A|8) let player["pos_y"]-=1 ;;
m|B|2) let player["pos_y"]+=1 ;;
q) return 0 ;;
esac
if [ "${levelmap["${player["pos_x"]}_${player["pos_y"]}"]}" = "." ] ; then
levelmap["${player["pos_x"]}_${player["pos_y"]}"]=" "
let levelmap["numdots"]-=1
let player["score"]+=10
player["message"]='GNAW!!'
if [ ${levelmap["numdots"]} -le 0 ] ; then
level_completed
return 0
fi
fi
fi
# generic player status change
if [ ${player["invulnerable"]} -gt 0 ] ; then
let player["invulnerable"]-=1
fi
if [ ${player["lives"]} -le 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"]} -gt 0 ] ; then
[ $((num_cycles % 2)) -gt 0 ] && continue
fi
if [ ${monster[${currmonster}_"pos_x"]} = ${player["pos_x"]} ] ; then
if [ $((monster[${currmonster}_"pos_y"]-player["pos_y"])) -gt 0 ] ; then
let monster[${currmonster}_"xstep"]=+0 monster[${currmonster}_"ystep"]=-1
else
let monster[${currmonster}_"xstep"]=+0 monster[${currmonster}_"ystep"]=+1
fi
monster[${currmonster}_"homing"]=1
if [ ${player["invulnerable"]} -le 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"])) -gt 0 ] ; then
let monster[${currmonster}_"xstep"]=-1 monster[${currmonster}_"ystep"]=-0
else
let monster[${currmonster}_"xstep"]=+1 monster[${currmonster}_"ystep"]=+0
fi
monster[${currmonster}_"homing"]=1
if [ ${player["invulnerable"]} -le 0 ] ; then
player["message"]="Attention: ${currmonster} is chasing you"
fi
else
if [ ${monster[${currmonster}_"homing"]} -eq 0 ] ; then
case $((SECONDS % 6 + RANDOM % 4)) in
0) let monster[${currmonster}_"xstep"]=+0 monster[${currmonster}_"ystep"]=+0 ;;
2) let monster[${currmonster}_"xstep"]=+0 monster[${currmonster}_"ystep"]=+1 ;;
3) let monster[${currmonster}_"xstep"]=+1 monster[${currmonster}_"ystep"]=+0 ;;
5) let monster[${currmonster}_"xstep"]=+0 monster[${currmonster}_"ystep"]=-1 ;;
6) let monster[${currmonster}_"xstep"]=-1 monster[${currmonster}_"ystep"]=+0 ;;
esac
fi
fi
let monster[${currmonster}_"pos_x"]=monster[${currmonster}_"pos_x"]+monster[${currmonster}_"xstep"]
let monster[${currmonster}_"pos_y"]=monster[${currmonster}_"pos_y"]+monster[${currmonster}_"ystep"]
# check if a monster hit the player
if [ ${player["invulnerable"]} -le 0 ] ; then
if [ ${monster[${currmonster}_"pos_x"]} -eq ${player["pos_x"]} -a \
${monster[${currmonster}_"pos_y"]} -eq ${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
let player["lives"]-=1
beep ; beep ; sleep 0.3 ; beep ; beep
fi
fi
done
render_game
done
}
# program start
function map_filter
{
# Choose between the old "sed"-based codepath and the new ksh93-native one
# The old codepath no longer used except for the unicode mode because
# we do not have control over the point where "sed" flushes it's buffer
# which completely defeats the doube-buffering code. Unfortunately the new
# codepath has problems in UTF-8 mode (bug in ksh93 ?) which forces us to
# use the old codepath in this case.
if [ $2 -eq 1 ] ; then
(
filter1=""
filter2=""
# should we add the color map ?
if [ $1 -eq 1 ] ; then
filter1="s/#/${vtcode["fg_blue"]}#/g;\
s/x/${vtcode["fg_red"]}x/g;\
s/@/${vtcode["fg_yellow"]}@/g;\
s/ /${vtcode["fg_grey"]} /g;\
s/\./${vtcode["fg_lightred"]}./g;"
fi
# should we add the unicode map ?
if [ $2 -eq 1 ] ; then
filter2="s/@/$(printf '\u[24d2]')/g;s/x/$(printf '\u[2605]')/g;s/#/$(printf '\u[25a6]')/g"
fi
sed -e "${filter1}" -e "${filter2}"
)
else
(
if [ $1 -eq 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 -eq 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
IFS="|" # make sure we don't swallow spaces/tabs
while read 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 "${var}"
done
)
fi
}
function exit_trap
{
# restore stty settings
stty ${SAVED_STTY}
print "bye."
}
function usage
{
OPTIND=0
getopts -a "${progname}" "${USAGE}" OPT '-?'
exit 2
}
# program start
progname="${0}"
quiet=false
# make sure we use the ksh93 "cat" builtin which supports the "-u" option
builtin cat
builtin wc
builtin printf # we need this for positional parameters ('printf "%2\$s %1\$s" hello world' = "world hello")
builtin sleep
# global variables
typeset -A levelmap
typeset -A player
typeset -A monster
# global rendering options
integer game_use_colors=0
integer game_use_unicode=0
USAGE=$'
[-?
@(#)\$Id: gnaw (Roland Mainz) 2007-06-05 \$
]
[+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}" "${USAGE}" OPT ; do
# printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|"
case ${OPT} in
q) quiet=true ;;
*) 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
# "resize" cannot fetch the terminal width/height for some terminals
case ${TERM} in
sun | sun-color)
export COLUMNS=80 LINES=25
;;
vt52)
export COLUMNS=80 LINES=24
;;
*)
# get width/height from current terminal
[ -x "/usr/X11/bin/resize" ] && eval "$(/usr/X11/bin/resize -u)" ||
[ -x "/usr/X11R6/bin/resize" ] && eval "$(/usr/X11R6/bin/resize -u)" ||
[ -x "/usr/openwin/bin/resize" ] && eval "$(/usr/openwin/bin/resize -u)" ||
fatal_error "resize not found."
;;
esac
# prechecks
(( COLUMNS < 60 )) && fatal_error "Terminal width must be larger than 60 columns (currently ${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 < COLUMNS ; i++ )) ; do print -n " " ; done)"
)
# 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 < COLUMNS ; x++ )) ; do
for (( y=0 ; y < 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}" "${COLUMNS}" "${LINES}"
for (( x=0 ; x < COLUMNS ; x++ )) ; do
for (( y=0 ; y < LINES ; y++ )) ; do
vtcode[cup_${x}_${y}]="$(tput cup ${y} ${x})"
done
done
;;
esac
print "${vtcode["vtreset"]}"
run_logo
run_menu
# EOF.