gnaw.sh revision 3e14f97f673e8a630f076077de35afdd43dc1587
#
# 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
# 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
#
#
#
#
# gnaw - a simple ksh93 technology demo
#
# Note that this script has been written with the main idea to show
# as an example of efficient&&clean script code (much of the code
# could be done more efficient using compound variables, this script
# focus is the usage of associative arrays).
#
# Make sure all math stuff runs in the "C" locale to avoid problems
# with alternative # radix point representations (e.g. ',' instead of
# '.' in de_DE.*-locales). This needs to be set _before_ any
# floating-point constants are defined in this script).
if [[ "${LC_ALL}" != "" ]] ; then
export \
LC_MONETARY="${LC_ALL}" \
LC_MESSAGES="${LC_ALL}" \
LC_COLLATE="${LC_ALL}" \
LC_CTYPE="${LC_ALL}"
unset LC_ALL
fi
export LC_NUMERIC=C
function print_setcursorpos
{
print -n -- "${vtcode[cup_${1}_${2}]}"
}
function beep
{
}
function fatal_error
{
print -u2 "${progname}: $*"
exit 1
}
# Get terminal size and put values into a compound variable with the integer
# members "columns" and "lines"
function get_term_size
{
nameref rect=$1
return 0
}
function print_levelmap
{
integer screen_y_offset=$1
integer start_y_pos=$2 # start at this line in the map
integer max_numlines=$3 # maximum lines we're allowed to render
integer x
integer y
typeset line=""
line=""
done
print -- "${line} "
done
# print lines filled with spaces for each line not filled
# by the level map
print -- "${line} "
done
return 0
}
function level_completed
{
integer i
typeset dummy
print -n -- "${vtcode["clear"]}"
cat <<ENDOFTEXT
# ###### # # ###### #
# # # # # #
# ##### # # ##### #
# # # # # #
# # # # # #
###### ###### ## ###### ######
(Good job)
##### #### # # ######
# # # # ## # #
# # # # # # # #####
# # # # # # # #
# # # # # ## #
##### #### # # ######
ENDOFTEXT
)"
print -- "${render_buffer}${end_of_frame}"
# wait five seconds and swallow any user input
for (( i=0 ; i < 50 ; i++ )) ; do
read -r -t 0.1 -n 1 dummy
done
print "Press any key to continue...${end_of_frame}"
# wait five secs or for a key
read -r -t 5 -n 1 dummy
return 0
}
function game_over
{
typeset dummy
print -n -- "${vtcode["clear"]}"
cat <<ENDOFTEXT
#### ## # # ######
# # # # ## ## #
# # # # ## # #####
# ### ###### # # #
# # # # # # #
#### # # # # ######
(LOSER!)
#### # # ###### #####
# # # # # # #
# # # # ##### # #
# # # # # #####
# # # # # # #
#### ## ###### # #
ENDOFTEXT
)"
print -r -- "${render_buffer}${end_of_frame}"
# wait five seconds and swallow any user input
for (( i=0 ; i < 50 ; i++ )) ; do
read -r -t 0.1 -n 1 dummy
done
print "Press any key to continue...${end_of_frame}"
# wait five secs or for a key
read -r -t 5 -n 1 dummy
return 0
}
function run_logo
{
cat <<ENDOFTEXT
##### # # # # # ###
# # ## # # # # # # ###
# # # # # # # # # ###
# #### # # # # # # # # #
# # # # # ####### # # #
# # # ## # # # # # ###
##### # # # # ## ## ###
ENDOFTEXT
)"
print -- "${vtcode["clear"]}${render_buffer}"
# wait two seconds and swallow any user input
for (( i=0 ; i < 20 ; i++ )) ; do
read -r -t 0.1 -n 1 dummy
done
print "\n (The KornShell 93 maze game)"
return 0
}
function attract_mode
{
(
# Now present some info, line-by-line in an endless loop
# until the user presses a key (we turn the "magic" return
# code for that)
integer -r magic_return_code=69
typeset line
while true ; do
(
redirect 5<&0
(cat <<ENDOFTEXT
################
########################
############################
####### ###### #######
###### ###### ########
####### ###### #######
##############################
##############################
##############################
##############################
##############################
######### ######## #########
# #### #### #### #
Written by
Roland Mainz
(roland.mainz@nrubsig.org)
##############
########################
#################**############
################################
############################
######################
################
######################
############################
################################
##############################
########################
##############
High scores:
* 'chin' 8200 pt
* 'gisburn' 7900 pt
* 'tpenta' 5520 pt
* 'kupfer' 5510 pt
* 'noname' 5000 pt
* 'noname' 4000 pt
* 'livad' 3120 pt
* 'noname' 3000 pt
* 'noname' 2000 pt
* 'noname' 1000 pt
ENDOFTEXT
# clear screen, line-by-line
read -r -t 0.3 -n 1 c <&5
print -- "${line}"
done)
)
sleep 2
done
)
}
function run_menu
{
integer numlevels=0
integer selected_level=0
typeset l
# built list of available levels based on the "function levelmap_.*"
# built into this script
while read -r l ; do
numlevels+=1
done
# swallow any queued user input (e.g. drain stdin)
read -r -t 0.1 -n 100 dummy
while true ; do
# menu loop with timeout (which switches to "attract mode")
while true ; do
print -n -- "${vtcode["clear"]}"
cat <<ENDOFTEXT
>======================================\
> /-\ .--. |
> | OO| / _.-' .-. .-. .-. .-. |
> | | \ '-. '-' '-' '-' '-' |
> ^^^^^ '--' |
>======\ /================\ .-. |
> | | | '-' |
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ENDOFTEXT
print " GNAW - the ksh93 maze game"
print "\n\tMenu:"
print "\t - [L]evels:"
printf "\t %s %s \n" "$( (( i == selected_level )) && print -n "*" || print -n " ")" "${levellist[i]##levelmap_}"
done
print "\t - Rendering options:"
print "\t - [S]tart - [Q]uit"
# wait 30 secs (before we switch to "attract mode")
c="" ; read -r -t 30 -n 1 c
esac
print -- "${vtcode["vtreset"]}"
;;
# make sure we do not exit on a cursor key (e.g. <esc>[A,B,C,D)
read -r -t 0.01 -n 1 c
if [[ "$c" == "[" ]] ; then
# this was a cursor key sequence, just eat the 3rd charcater
read -r -t 0.01 -n 1 c
else
exit 0
fi
;;
"") break ;; # timeout, switch to attract mode
*) beep ;;
esac
done
print -n -- "${vtcode["clear"]}"
done
return 0
}
function levelmap_stripes
{
cat <<ENDOFLEVEL
###################################
#....... ............... P #
#########..#################..### #
#########..#################..### #
#....... .. ..............# #
############### ################ #
############### ################ #
#............. M ..............# #
##..##################### ###### #
##..##################### ###### #
#....... ........... .......# #
######## ############ ######### #
# #### ############ ######### #
# #.................. ......# #
# ############################### #
# #
###################################
ENDOFLEVEL
return 0
}
function levelmap_livad
{
cat <<ENDOFLEVEL
#####################################################
# #
# ############## ############### ################ #
# #............ P ..............# #
# .#############################################.# #
# #.#.......... ............#. #
# #.#.########## ############### ############.#.# #
# #...#........ ..........#...# #
# #...#.#####################################.#.#.# #
# #...#.#...... ........#...#.# #
# #.#.#...###### #########################.#.#.#.# #
# .#.....#.... M ......#...#.#.# #
# #.#.#...####################### ########.#.#.#.# #
# #...#.#...... ........#...#.# #
# #...#.######## ############### ##########.#.#.# #
# #...#........ ..........#...# #
# #.#.#########################################.#.# #
# #.#.......... ............#. #
# .############ ############### ##############.# #
# #............ ..............# #
# ################################################# #
# #
#####################################################
ENDOFLEVEL
return 0
}
function levelmap_classic1
{
cat <<ENDOFLEVEL
#########################
#.P.........#...........#
#.####.####.#.####.####.#
#.# #.# #.#.# #.# #.#
#.# #.# #.#.# #.# #.#
#.####.####.#.####.####.#
#.......................#
#.####.#.#######.#.####.#
#.# #.#.# #.#.# #.#
#.####.#.#######.#.####.#
#......#....#....#......#
######.####.#.####.######
###### # # ######
###### # ## ## # ######
###### # # # # ######
# # M # #
###### # ####### # ######
###### # # ######
###### # ####### # ######
###### # # # # ######
######.#.#######.#.######
#...........#...........#
#.###.###...#...###.###.#
#...#...............#...#
###.#....#######....#.###
# #.#..#.# #.#..#.# #
###....#.#######.#....###
#......#....#....#......#
#.#########.#.#########.#
#.......................#
#########################
ENDOFLEVEL
return 0
}
function levelmap_classic2
{
cat <<ENDOFLEVEL
#######################
#.P...#.........#.....#
#.###.#.#######.#.###.#
#.....................#
###.#.####.#.####.#.###
###.#......#......#.###
###.###.#######.###.###
###.................###
###.###.### ###.###.###
###.#...#M #...#.###
###.#.#.#######.#.#.###
#.....#.........#.....#
###.#####..#..#####.###
###........#........###
###.###.#######.###.###
#.....................#
#.###.####.#.####.###.#
#.###.#....#....#.###.#
#.###.#.#######.#.###.#
#.....................#
#######################
ENDOFLEVEL
return 0
}
function levelmap_easy
{
cat <<ENDOFLEVEL
##################
# .............. #
# . ###### #
# . # M # #
# . # # #
# . ### ## #
# . # #
# . ### #
# . #
# .......... #
# .......... P #
##################
ENDOFLEVEL
return 0
}
function levelmap_sunsolaristext
{
cat <<ENDOFLEVEL
################################################
# .#### . # #....# #
# # # # #....# #
# #### # # #.#..# M #
# # # # #..#.# #
# # # # # #...## #
# #### #### #....# #
# #
# #### #### # ## ##### # #### #
# # #. .# # # # #....# # # #
# #### # # # # P # #....# # #### #
# # # ### #.#### #.### # # #
# # .# #. .. # # #...# # # # #
# #### #### ###### . # ....# # ####. #
################################################
ENDOFLEVEL
return 0
}
function read_levelmap
{
integer y=0
integer x=0
integer maxx=0
integer numdots=0
typeset line
typeset c
while read -r line ; do
for (( x=0 ; x < ${#line} ; x++ )) ; do
c="${line:x:1}"
case $c in
"M")
# log start position of monsters
c=" "
;;
"P")
# log start position of player
c=" "
;;
esac
done
done <<<"${map}"
# consistency checks
if [[ "${levelmap["monsterstartpos_x"]}" == "" ]] ; then
fatal_error "read_levelmap: monsterstartpos_x is empty."
fi
if [[ "${levelmap["playerstartpos_x"]}" == "" ]] ; then
fatal_error "read_levelmap: playerstartpos_x is empty."
fi
return 0
}
function player.set
{
fi
;;
fi
;;
esac
return 0
}
function monster.set
{
*_pos_y)
# turn homing off when the monster hit a wall
fi
;;
*_pos_x)
# turn homing off when the monster hit a wall
fi
;;
esac
return 0
}
function render_game
{
# render_buffer is some kind of "background buffer" to "double buffer"
# all output and combine it in one write to reduce flickering in the
# terminal
integer screen_y_offset=1
integer start_y_pos=0
fi
#print -n -- "${vtcode["clear"]}"
# 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"]}"
# render player
print -n "@"
# render monsters
for currmonster in ${monsterlist} ; do
print_setcursorpos ${m_pos_x} ${m_pos_y}
print -n "x"
fi
done
# status block
emptyline=" "
)"
print -r -- "${render_buffer}${end_of_frame}"
# print "renderbuffersize=$(print "${render_buffer}" | wc -c) ${end_of_frame}"
return 0
}
function main_loop
{
float sleep_per_cycle=0.2
float seconds_before_read
integer num_cycles=0
float rs
print -n -- "${vtcode["clear"]}"
read_levelmap "$1"
# player init
monsterlist="maw claw jitterbug tentacle grendel"
for currmonster in ${monsterlist} ; do
done
# main game cycle loop
while true ; do
num_cycles+=1
if [[ "$c" != "" ]] ; then
# special case handling for cursor keys which are usually composed
# of three characters (e.g. "<ESC>[D"). If only <ESC> is hit we
# quicky exit
if [[ "$c" == $'\E' ]] ; then
read -r -t 0.1 -n 1 c
if [[ "$c" != "[" ]] ; then
return 0
fi
# we assume the user is using the cursor keys, this |read|
# should fetch the 3rd byte of the three-character sequence
# for the cursor keys
read -r -t 0.1 -n 1 c
fi
# if the user hit a key the "read" above was interrupted
# and didn't wait exactly |sleep_per_cycle| seconds.
# We wait here some moments (|rs|="remaining seconds") to
# avoid that the game gets "faster" when more user input
# is given.
q) return 0 ;;
esac
if [[ "${levelmap["${player["pos_x"]}_${player["pos_y"]}"]}" == "." ]] ; then
return 0
fi
fi
fi
# generic player status change
fi
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
fi
else
fi
fi
else
fi
fi
else
esac
fi
fi
(( monster[${currmonster}_"pos_x"]=monster[${currmonster}_"pos_x"]+monster[${currmonster}_"xstep"] ))
(( monster[${currmonster}_"pos_y"]=monster[${currmonster}_"pos_y"]+monster[${currmonster}_"ystep"] ))
# check if a monster hit the player
# if player was hit by a monster take one life and
# make him invulnerable for 10 cycles to avoid that
# the next cycle steals more lives
fi
fi
done
done
return 0
}
function map_filter
{
typeset ch_player ch_monster ch_wall var
if (( $1 == 1 )) ; then
else
ch_player=""
ch_monster=""
ch_wall=""
fi
if (( $2 == 1 )) ; then
# unicode map
else
# ascii map
ch_player+="@"
ch_monster+="x"
ch_wall+="#"
fi
# note that this filter currently defeats the "double-buffering"
var="${var//@/${ch_player}}"
var="${var//x/${ch_monster}}"
var="${var//#/${ch_wall}}"
print -r -- "${var}"
done
return 0
}
function exit_trap
{
# restore stty settings
stty ${saved_stty}
print "bye."
return 0
}
function usage
{
OPTIND=0
exit 2
}
# program start
# make sure we use the ksh93 "cat" builtin which supports the "-u" option
builtin basename
builtin cat
builtin wc
# terminal size rect
compound termsize=(
integer columns=-1
integer lines=-1
)
# global variables
typeset quiet=false
typeset -A levelmap
typeset -A player
typeset -A monster
# global rendering options
integer game_use_colors=0
integer game_use_unicode=0
typeset -r gnaw_usage=$'+
[-?\n@(#)\$Id: gnaw (Roland Mainz) 2009-05-09 \$\n]
[-author?Roland Mainz <roland.mainz@nrubsig.org>]
[+NAME?gnaw - maze game written in ksh93]
[+DESCRIPTION?\bgnaw\b is a maze game.
The player maneuvers a yellow "@" sign to navigate a maze while eating
small dots. A level is finished when all the dots are eaten. Five monsters
(maw, claw, jitterbug, tentacle and grendel) also wander the maze in an attempt
to catch the "@". Each level begins with all ghosts in their home, and "@" near
the bottom of the maze. The monsters are released from the home one by one at the
start of each level and start their rentless hunt after the player.]
[q:quiet?Disable use of terminal bell.]
[+SEE ALSO?\bksh93\b(1)]
'
while getopts -a "${progname}" "${gnaw_usage}" OPT ; do
# printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|"
q) quiet=true ;;
+q) quiet=false ;;
*) usage ;;
esac
done
# save stty values and register the exit trap which restores these values on exit
print "Loading..."
# set stty values, "-icanon min 1 time 0 -inpck" should improve input latency,
# "-echo" turns the terminal echo off
# prechecks
(( termsize.columns < 60 )) && fatal_error "Terminal width must be larger than 60 columns (currently ${termsize.columns})."
typeset -A vtcode
# color values taken from http://frexx.de/xterm-256-notes/, other
# codes from http://vt100.net/docs/vt100-tm/
vtcode=(
["bg_black"]="$(print -n "\E[40m")"
["fg_black"]="$(print -n "\E[30m")"
["fg_red"]="$(print -n "\E[31m")"
["fg_lightred"]="$(print -n "\E[1;31m")"
["fg_green"]="$(print -n "\E[32m")"
["fg_lightgreen"]="$(print -n "\E[1;32m")"
["fg_yellow"]="$(print -n "\E[33m")"
["fg_lightyellow"]="$(print -n "\E[1;33m")"
["fg_blue"]="$(print -n "\E[34m")"
["fg_lightblue"]="$(print -n "\E[1;34m")"
["fg_grey"]="$(print -n "\E[1;37m")"
["fg_white"]="$(print -n "\E[37m")"
# misc other vt stuff
)
# character used to as marker that a single frame ends at this point - this
# is used by the "double buffering" code to make sure the "read" builtin
# can read a whole "frame" instead of reading stuff line-by-line
typeset -r end_of_frame=$'\t'
# get terminal sequence to move cursor to position x,y
# (see http://vt100.net/docs/vt100-ug/chapter3.html#CPR)
-e 's/%[%id]*p1[%id]*/%2\\\$d/g' \
-e 's/%[%id]*p2[%id]*/%1\\\$d/g' \
-e 's/,$//')"
done
done
;;
*)
printf "# Unrecognised terminal type '%s', fetching %dx%d items from terminfo database, please wait...\n" "${TERM}" "${termsize.columns}" "${termsize.lines}"
done
done
;;
esac
print -- "${vtcode["vtreset"]}"
exit 0
# EOF.