gnaw revision da2e3ebdc1edfbc5028edf1354e7dd2fa69a7968
#
# 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
#
#
# 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
# as an example of efficient&&clean script code.
#
function print_setcursorpos
{
print -n "${vtcode[cup_${1}_${2}]}"
}
function beep
{
}
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=""
line=""
done
print "${line} "
done
# print lines filled with spaces for each line not filled
# by the level map
print "${line} "
done
}
function level_completed
{
print -n "${vtcode["clear"]}"
cat <<ENDOFTEXT
# ###### # # ###### #
# # # # # #
# ##### # # ##### #
# # # # # #
# # # # # #
###### ###### ## ###### ######
(Good job)
##### #### # # ######
# # # # ## # #
# # # # # # # #####
# # # # # # # #
# # # # # ## #
##### #### # # ######
ENDOFTEXT
)"
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
{
print -n "${vtcode["clear"]}"
cat <<ENDOFTEXT
#### ## # # ######
# # # # ## ## #
# # # # ## # #####
# ### ###### # # #
# # # # # # #
#### # # # # ######
(LOSER!)
#### # # ###### #####
# # # # # # #
# # # # ##### # #
# # # # # #####
# # # # # # #
#### ## ###### # #
ENDOFTEXT
)"
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
{
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)"
}
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)
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
read -t 0.3 -n 1 c <&5
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
while read l ; do
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:"
printf "\t %s %s \n" "$([ $i -eq $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 -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 -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
;;
"") break ;; # timeout, switch to attract mode
*) beep ;;
esac
done
print -n "${vtcode["clear"]}"
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
{
integer y=0
integer x=0
integer maxx=0
integer numdots=0
while read line ; do
x=0
while (( x < ${#line} )) ; do
c="${line:x:1}"
case $c in
"M")
c=" "
;;
"P")
c=" "
;;
esac
let x++
done
maxx=$x
let y++
done
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
}
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
}
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
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 "${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
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 -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.
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
# 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
}
# 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
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
fi
)
else
(
if [ $1 -eq 1 ] ; then
else
ch_player=""
ch_monster=""
ch_wall=""
fi
if [ $2 -eq 1 ] ; then
# unicode map
else
# ascii map
ch_player+="@"
ch_monster+="x"
ch_wall+="#"
fi
while read var ; do
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
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}|"
q) quiet=true ;;
*) usage ;;
esac
done
shift ${OPTIND}-1
# 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
;;
vt52)
;;
*)
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
)
# 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}" "${COLUMNS}" "${LINES}"
done
done
;;
esac
print "${vtcode["vtreset"]}"
# EOF.