#!/usr/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 2008 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

#
# 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 (much of the code
# could be done more efficient using compound variables, this script
# focus is the usage of associative arrays).
#

# Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant
export PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin

# 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
{
    ${quiet} || print -n -- "${vtcode["bel"]}"
}

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
    
	rect.columns=${ tput cols ; } || return 1
	rect.lines=${ tput lines ; }  || return 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=""

    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
    return 0
}

function level_completed
{
    integer i
    typeset dummy
    typeset 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}${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
    typeset render_buffer="$(
    print -n -- "${vtcode["clear"]}"
    cat <<ENDOFTEXT

  ####     ##    #    #  ######
 #    #   #  #   ##  ##  #
 #       #    #  # ## #  #####
 #  ###  ######  #    #  #
 #    #  #    #  #    #  #
  ####   #    #  #    #  ######

            (LOSER!)

  ####   #    #  ######  #####
 #    #  #    #  #       #    #
 #    #  #    #  #####   #    #
 #    #  #    #  #       #####
 #    #   #  #   #       #   #
  ####     ##    ######  #    #

ENDOFTEXT

    printf "\n    SCORE: --> %s <--\n" "${player["score"]}"
    )"
    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
{
    typeset 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 -r -t 0.1 -n 1 dummy
    done

    print "\n   (The KornShell 93 maze game)"
    
    attract_mode
    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
    IFS='' ; # Make sure we do not swallow whitespaces

    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
        for (( i=0 ; i < termsize.lines ; i++ )) ; do print "" ; done
        ) | (while read -r line ; do
                read -r -t 0.3 -n 1 c <&5
                [[ "$c" != "" ]] && exit ${magic_return_code}
                print -- "${line}"
            done)
        (( $? == magic_return_code )) && exit ${magic_return_code}
        )
        (( $? == magic_return_code )) && return 0
        
        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
    typeset -f | egrep "^function.*levelmap_.*" | sed 's/^function //' |
    while read -r l ; do
        levellist[numlevels]="$l"
        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:"
            for (( i=0 ; i < numlevels ; i++ )) ; do
                printf "\t    %s %s \n" "$( (( i == selected_level )) && print -n "*" || print -n " ")" "${levellist[i]##levelmap_}"
            done

            print  "\t - Rendering options:"
            printf "\t    [%s] Use [U]nicode\n" "$( (( game_use_unicode == 1 )) && print -n "x" || print -n "_" )"
            printf "\t    [%s] Use [C]olors\n"  "$( (( game_use_colors  == 1 )) && print -n "x" || print -n "_" )"

            print "\t - [S]tart - [Q]uit"

            # wait 30 secs (before we switch to "attract mode")
            c="" ; read -r -t 30 -n 1 c
            case "$c" in
                'l') (( selected_level=(selected_level+numlevels+1) % numlevels )) ;;
                'L') (( selected_level=(selected_level+numlevels-1) % numlevels )) ;;
                ~(Fi)s)
                    (( game_use_colors == 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"]}"
                    ;;
                ~(Fi)q | $'\E')
                    # 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
                    ;;           
                ~(Fi)u) (( game_use_unicode=(game_use_unicode+2+1) % 2)) ;;
                ~(Fi)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
    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
{
    typeset map="$( $1 )"

    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
                ".") numdots+=1 ;;
                "M")
		    # log start position of monsters
                    levelmap["monsterstartpos_x"]="$x"
                    levelmap["monsterstartpos_y"]="$y"
                    c=" "
                    ;;
                "P")
		    # log start position of player
                    levelmap["playerstartpos_x"]="$x"
                    levelmap["playerstartpos_y"]="$y"
                    c=" "
                    ;;
            esac

            levelmap["${x}_${y}"]="$c"
        done
        (( maxx=x , y++ ))
    done <<<"${map}"

    levelmap["max_x"]=${maxx}
    levelmap["max_y"]=${y}
    levelmap["numdots"]=${numdots}
    
    # 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
{    
    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
    return 0
}

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
    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
    typeset render_buffer="$(
        integer screen_y_offset=1
        integer start_y_pos=0
        integer render_num_lines=${levelmap["max_y"]}

        if (( (termsize.lines-3) < levelmap["max_y"] )) ; then
            (( start_y_pos=player["pos_y"] / 2))
            (( render_num_lines=termsize.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
            (( m_pos_x=monster[${currmonster}_"pos_x"] ))
            (( 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 -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
    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 -r -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 -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.
            (( rs=sleep_per_cycle-(SECONDS-seconds_before_read) ))
            (( rs > 0.001 )) && sleep ${rs}

            player["message"]=""

            case "$c" in
                j|D|4) (( player["pos_x"]-=1 )) ;;
                k|C|6) (( player["pos_x"]+=1 )) ;;
                i|A|8) (( player["pos_y"]-=1 )) ;;
                m|B|2) (( player["pos_y"]+=1 )) ;;

                q) return 0 ;;
            esac

            if [[ "${levelmap["${player["pos_x"]}_${player["pos_y"]}"]}" == "." ]] ; then
                levelmap["${player["pos_x"]}_${player["pos_y"]}"]=" "
                (( levelmap["numdots"]-=1 ))

                (( player["score"]+=10 ))
                player["message"]='GNAW!!'

                if (( levelmap["numdots"] <= 0 )) ; then
                    level_completed
                    return 0
                fi
            fi
        fi

        # generic player status change
        if (( player["invulnerable"] > 0 )) ; then
            (( player["invulnerable"]-=1 ))
        fi
        if (( player["lives"] <= 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"] > 0 )) ; then
                (( (num_cycles%2) > 0 )) && continue
            fi
                       
            if [[ ${monster[${currmonster}_"pos_x"]} == ${player["pos_x"]} ]] ; then
                if (( (monster[${currmonster}_"pos_y"]-player["pos_y"]) > 0 )) ; then
                    (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=-1 ))
                else
                    (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=+1 ))
                fi
                monster[${currmonster}_"homing"]=1
                if (( player["invulnerable"] <= 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"]) > 0 )) ; then
                    (( monster[${currmonster}_"xstep"]=-1 , monster[${currmonster}_"ystep"]=-0 ))
                else
                    (( monster[${currmonster}_"xstep"]=+1 , monster[${currmonster}_"ystep"]=+0 ))
                fi           
                monster[${currmonster}_"homing"]=1
                if (( player["invulnerable"] <= 0 )) ; then
                    player["message"]="Attention: ${currmonster} is chasing you"
                fi
            else
                if (( monster[${currmonster}_"homing"] == 0 )) ; then
                    case $((SECONDS % 6 + RANDOM % 4)) in
                        0) (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=+0 )) ;;
                        2) (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=+1 )) ;;
                        3) (( monster[${currmonster}_"xstep"]=+1 , monster[${currmonster}_"ystep"]=+0 )) ;;
                        5) (( monster[${currmonster}_"xstep"]=+0 , monster[${currmonster}_"ystep"]=-1 )) ;;
                        6) (( monster[${currmonster}_"xstep"]=-1 , monster[${currmonster}_"ystep"]=+0 )) ;;
                    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["invulnerable"] <= 0 )) ; then
                if (( monster[${currmonster}_"pos_x"] == player["pos_x"] && \
                      monster[${currmonster}_"pos_y"] == 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
                     (( player["lives"]-=1 ))

                     beep ; beep ; sleep 0.2 ; beep ; beep
                fi
            fi 
        done    

        render_game
    done
    return 0
}

function map_filter
{
    typeset ch_player ch_monster ch_wall var

    if (( $1 == 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 == 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

    # note that this filter currently defeats the "double-buffering"
    while IFS='' read -r -d "${end_of_frame}" 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 -r -- "${var}"
    done
    return 0
}

function exit_trap
{
    # restore stty settings
    stty ${saved_stty}
    
    print "bye."
    return 0
}

function usage
{
    OPTIND=0
    getopts -a "${progname}" "${gnaw_usage}" OPT '-?'
    exit 2
}

# program start
# make sure we use the ksh93 "cat" builtin which supports the "-u" option
builtin basename
builtin cat
builtin wc

typeset progname="${ basename "${0}" ; }"

# terminal size rect
typeset -C 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) 2008-11-04 \$\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}|"
    case ${OPT} in
        q)    quiet=true  ;;
        +q)   quiet=false ;;
        *)    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

get_term_size termsize || fatal_error "Could not get terminal size."

# 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
    ["vtreset"]="$(tput reset)"
    ["clear"]="$(tput clear)"
    ["bel"]="$(tput bel)"
    ["spaceline"]="$(for (( i=0 ; i < termsize.columns ; i++ )) ; do print -n " " ; done)"
)

# 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)
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 < termsize.columns ; x++ )) ; do
            for (( y=0 ; y < termsize.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}" "${termsize.columns}" "${termsize.lines}"
        for (( x=0 ; x < termsize.columns ; x++ )) ; do
            for (( y=0 ; y < termsize.lines ; y++ )) ; do
                vtcode[cup_${x}_${y}]="$(tput cup ${y} ${x})"
            done
        done
        ;;
esac

print -- "${vtcode["vtreset"]}"

run_logo
run_menu

exit 0
# EOF.