Shell 飞机游戏(2013-03-15)

前言

试试比较多动态画面的飞机游戏是否可以用 Shell 来实现。

正文

试了一次性 echo 一个大字串,或者是分批 echo 小一点的字串,发现都很卡,最后还是保留最初的方案,有需要输出屏的时候就直接 echo。也弄过一个消息队列的结构,后来改着改着,发现消息队列都用不上了。

截图如下:


代码

颜色代码头文件 colors.sh

#!/bin/bash
# colors.sh
# 作者:亚丹
# http://seesea.blog.chinaunix.net
# http://blog.csdn.net/nicenight
# 一些输出控制常量定义,已经不只包括颜色常量了,对不起这个文件名呀

# 控制序列初始字符
ESC="\E"

# 前景色
BLK=$ESC"[30m"
RED=$ESC"[31m"
GRN=$ESC"[32m"
YEL=$ESC"[33m"
BLU=$ESC"[34m"
MAG=$ESC"[35m"
CYN=$ESC"[36m"
WHT=$ESC"[37m"

# 高亮前景色
HIR=$ESC"[1;31m"
HIG=$ESC"[1;32m"
HIY=$ESC"[1;33m"
HIB=$ESC"[1;34m"
HIM=$ESC"[1;35m"
HIC=$ESC"[1;36m"
HIW=$ESC"[1;37m"

# 背景色
BBLK=$ESC"[40m"
BRED=$ESC"[41m"
BGRN=$ESC"[42m"
BYEL=$ESC"[43m"
BBLU=$ESC"[44m"
BMAG=$ESC"[45m"
BCYN=$ESC"[46m"
BWHT=$ESC"[47m"

# 高亮背景色
BHIR=$ESC"[41;1m"
BHIG=$ESC"[42;1m"
BHIY=$ESC"[43;1m"
BHIB=$ESC"[44;1m"
BHIM=$ESC"[45;1m"
BHIC=$ESC"[46;1m"
BHIW=$ESC"[47;1m"

# 恢复默认显示
NOR=$ESC"[2;37;0m"

# 闪烁效果
BLINK=$ESC"[5m"

# 粗体效果
BOLD=$ESC"[1m"

# 反向显示
INV=$ESC"[7m"

主程序源文件 aircraft.sh

#!/bin/bash
# aircraft.sh
#
# 作者:亚丹
# 时间:2012-06-27
# seesea2517#gmail*com
# http://seesea.blog.chinaunix.net
# http://blog.csdn.net/nicenight
#
# 功能:飞机游戏的Demo
# 游戏规则:
# 1. 射击敌机,击中一架敌机得一分
# 2. 每十分升一级
# 3. 升级后,敌机的出现几率将会上升
# 4. 升级后,将增加可发射子弹的数量

source colors.sh

# ============================================================================
# 全局配置
# ============================================================================

# 响应的信号
declare -r SIG_UP=SIGRTMIN+1
declare -r SIG_DOWN=SIGRTMIN+2
declare -r SIG_LEFT=SIGRTMIN+3
declare -r SIG_RIGHT=SIGRTMIN+4
declare -r SIG_SHOOT=SIGRTMIN+5
declare -r SIG_PAUSE=SIGRTMIN+6
declare -r SIG_EXIT=SIGRTMIN+7

# 响应的按键(注意:使用大写配置)
declare -r KEY_UP="W"
declare -r KEY_DOWN="S"
declare -r KEY_LEFT="A"
declare -r KEY_RIGHT="D"
declare -r KEY_SHOOT="J"
declare -r KEY_PAUSE="P"
declare -r KEY_EXIT="Q"

# 游戏区域位置大小
declare -r GAME_AREA_TOP=10
declare -r GAME_AREA_LEFT=30
declare -r GAME_AREA_WIDTH=43
declare -r GAME_AREA_HEIGHT=33

# 标题位置
declare -r TITLE_POS_LEFT=22
declare -r TITLE_POS_TOP=2

# 信息显示位置
declare -r MSG_POS_TOP=$(( GAME_AREA_TOP + GAME_AREA_HEIGHT - 20 ))
declare -r MSG_POS_LEFT=$(( GAME_AREA_LEFT + GAME_AREA_WIDTH + 10 ))
declare -r MSG_SCORE_TOP=$(( MSG_POS_TOP + 1 ))
declare -r MSG_SCORE_LEFT=$(( MSG_POS_LEFT + 16 ))
declare -r MSG_LEVEL_TOP=$(( MSG_POS_TOP + 2 ))
declare -r MSG_LEVEL_LEFT=$MSG_SCORE_LEFT
declare -r MSG_BULLET_TOP=$(( MSG_POS_TOP + 3 ))
declare -r MSG_BULLET_LEFT=$MSG_SCORE_LEFT
declare -r MSG_TOP_SCORE_TOP=$(( MSG_POS_TOP + 4 ))
declare -r MSG_TOP_SCORE_LEFT=$MSG_SCORE_LEFT

# 游戏边界显示字符(分横向和纵向两种字符)
declare -r BORDER_H="${BHIG} ${NOR}"
declare -r BORDER_V="${BHIG} ${NOR}"

# 游戏最高分存放文件
declare -r FILE_TOP_SCORE=".top_score"

# ============================================================================
# 全局常量
# ============================================================================

# 玩家图标   敌机图标
#   A         -+-
# -=#=-      -=#=-
#  -+-         V
declare -r player_width=5                                        # 玩家图标的宽
declare -r player_height=3                                       # 玩家图标的高
declare -r player_gun_offset_c=$(( (player_width - 1) / 2 - 1 )) # 玩家枪炮的相对于坐标的偏移
declare -r player_gun_offset_r=-2                                # 玩家枪炮的相对于坐标的偏移
declare -r enemy_width=5                                         # 敌机图标的宽
declare -r enemy_height=3                                        # 敌机图标的高

declare -r enemy_random_range_max=20                             # 每帧随机产生敌机的随机数范围 20 表示 1/20 的几率

# 各种不同风格的星星集合
declare -ar ar_star_style=( "${RED}.${NOR}" "${GRN}.${NOR}" "${YEL}.${NOR}" "${BLU}.${NOR}" "${MAG}.${NOR}" "${CYN}.${NOR}" "${WHT}.${NOR}" "${HIR}.${NOR}" "${HIG}.${NOR}" "${HIY}.${NOR}" "${HIB}.${NOR}" "${HIM}.${NOR}" "${HIC}.${NOR}" "${HIW}.${NOR}" )

# 敌机颜色列表
declare -ar ar_enemy_color=( "$HIR" "$HIG" "$HIY" "$HIB" "$HIM" "$HIC" "$HIW" )

# ============================================================================
# 全局变量
# ============================================================================

declare stty_save                 # 终端设置
declare game_paused=0             # 游戏暂停标志
declare game_overed=0             # 游戏中止标志
declare game_exit_confirmed=0     # 游戏确认退出标志

declare -a ar_pos_bullet          # 子弹列表
declare -a ar_old_pos_bullet      # 子弹旧坐标列表

declare -a ar_pos_enemy           # 敌机列表
declare -a ar_old_pos_enemy       # 敌机旧坐标列表

declare -a ar_pos_star            # 背景星星列表
declare -a ar_old_pos_star        # 背景星星旧坐标列表

declare pid_loop                  # 消息循环的进程pid

declare screen_width              # 屏宽
declare screen_height             # 屏高

declare width_max                 # 游戏区转换为屏幕坐标的最大位置:宽
declare height_max                # 游戏区转换为屏幕坐标的最大位置:高
declare width_min                 # 游戏区转换为屏幕坐标的最小位置:宽
declare height_min                # 游戏区转换为屏幕坐标的最小位置:高

declare range_player_r_min        # 玩家可移动位置的最小行
declare range_player_r_max        # 玩家可移动位置的最大行
declare range_player_c_min        # 玩家可移动位置的最小列
declare range_player_c_max        # 玩家可移动位置的最大列

declare range_enemy_r_min         # 敌机可移动位置的最小行
declare range_enemy_r_max         # 敌机可移动位置的最大行
declare range_enemy_c_min         # 敌机可移动位置的最小列
declare range_enemy_c_max         # 敌机可移动位置的最大列

declare pos_player_r              # 当前玩家坐标:行
declare pos_player_c              # 当前玩家坐标:列
declare old_player_r              # 先前玩家坐标:行
declare old_player_c              # 先前玩家坐标:列

declare score=100                   # 分数
declare score_top=0               # 最高分
declare level=10                   # 级别
declare bullet_num=$level         # 当前可发射的子弹数
declare bullet_num_max=$level     # 最大可发射的子弹数

# ============================================================================
# 函数定义
# ============================================================================

# ----------------------------------------------------------------------------
# 通用函数
# ----------------------------------------------------------------------------

# 随机函数
# 参数一:随机数的上限+1,缺省为 10
function random()
{
    echo $(( RANDOM % ${1:-10} ))
}

# ----------------------------------------------------------------------------
# 游戏框架函数
# ----------------------------------------------------------------------------

# 键盘输入响应函数
function Input()
{
    while true
    do
        read -s -n 1 -a key
        key="${key[@]: -1}"
        case $key in
            $KEY_UP)    sign=$SIG_UP    ;;
            $KEY_DOWN)  sign=$SIG_DOWN  ;;
            $KEY_LEFT)  sign=$SIG_LEFT  ;;
            $KEY_RIGHT) sign=$SIG_RIGHT ;;
            $KEY_SHOOT) sign=$SIG_SHOOT ;;
            $KEY_PAUSE) sign=$SIG_PAUSE ;;
            $KEY_EXIT)  sign=$SIG_EXIT  ;;
            *)          continue        ;;
        esac

        kill -s $sign $pid_loop

        # 若是退出按键,则根据游戏循环是否存在来判断是否确认退出
        if (( sign == SIG_EXIT ))
        then
            sleep 0.1
            if ! ps -p $pid_loop > /dev/null
            then
                break
            fi
        fi
    done
}

# 输入动作响应函数
# 输入参数一:键盘消息
function Action()
{
    sign=$1

    # 若游戏暂停,则只响应暂停信号和退出信号
    if (( game_paused && sign != SIG_PAUSE && sign != SIG_EXIT ))
    then
        return
    fi

    # 输入线程的暂停处理
    if (( game_overed && sign == SIG_PAUSE ))
    then
        return
    fi

    case $sign in
        $SIG_UP)    OnPressPlayerMove "U" ;;
        $SIG_DOWN)  OnPressPlayerMove "D" ;;
        $SIG_LEFT)  OnPressPlayerMove "L" ;;
        $SIG_RIGHT) OnPressPlayerMove "R" ;;
        $SIG_SHOOT) OnPressShoot          ;;
        $SIG_PAUSE) OnPressGamePause      ;;
        $SIG_EXIT)  OnPressGameExit       ;;
    esac
}

# 系统初始化
function Init()
{
    width_max=$(( GAME_AREA_LEFT + GAME_AREA_WIDTH ))
    height_max=$(( GAME_AREA_TOP + GAME_AREA_HEIGHT ))
    width_min=$GAME_AREA_LEFT
    height_min=$GAME_AREA_TOP

    screen_width=$(tput cols)
    screen_height=$(tput lines)

    if [ $screen_width -lt $width_max -o $screen_height -lt $height_max ]
    then
        echo "Screen size too small (width = $screen_width, height = $screen_height), should be width = $width_max and height = $height_max at least."
        exit 1
    fi

    range_player_r_min=$height_min
    range_player_r_max=$(( height_max - player_height ))
    range_player_c_min=$width_min
    range_player_c_max=$(( width_max - player_width ))

    range_enemy_r_min=$height_min
    range_enemy_r_max=$(( height_max - enemy_height ))
    range_enemy_c_min=$width_min
    range_enemy_c_max=$(( width_max - enemy_width ))

    game_paused=0
    game_overed=0

    # 终端设置
    stty_save=$(stty -g)    # 保存stty配置
    stty -echo              # 关闭输入回显
    tput civis              # 关闭光标
    shopt -s nocasematch    # 开启大小写case比较的开关
    clear

    # 有时候echo会提示中断的函数调用,目前没啥解决办法,就把错误提示屏蔽了先
    exec 2> /dev/null
}

# 判断一个矩形物件是否在游戏区域内
# 参数:行、列、高、宽
function IsInGameArea()
{
    local r c h w
    local r_min r_max c_min c_max
    r=$1
    c=$2
    h=$3
    w=$4
    r_min=$height_min
    r_max=$(( height_max - h ))
    c_min=$width_min
    c_max=$(( width_max - w ))

    if [ $r -le $r_min -o $r -ge $r_max -o $c -le $c_min -o $c -ge $c_max ]
    then
        return 1
    fi

    return 0
}

# 游戏初始化
function GameInit()
{
    # 设定输入响应函数
    # trap Action $SIG_UP $SIG_DOWN $SIG_LEFT $SIG_RIGHT $SIG_PAUSE $SIG_SHOOT $SIG_EXIT
    trap "Action $SIG_UP"    $SIG_UP
    trap "Action $SIG_DOWN"  $SIG_DOWN
    trap "Action $SIG_LEFT"  $SIG_LEFT
    trap "Action $SIG_RIGHT" $SIG_RIGHT
    trap "Action $SIG_PAUSE" $SIG_PAUSE
    trap "Action $SIG_SHOOT" $SIG_SHOOT
    trap "Action $SIG_EXIT"  $SIG_EXIT

    pos_player_r=$(( range_player_r_max ))
    pos_player_c=$(( (GAME_AREA_WIDTH - $player_width) / 2 + GAME_AREA_LEFT ))

    old_player_r=$pos_player_r
    old_player_c=$pos_player_c

    game_paused=0
    game_overed=0

    TopScoreRead
    TitleShow
    DrawBorder
    MessageShow
    PlayerMove      # 不带参数,就会根据当前位置刷新一下玩家
}

# 游戏循环
function GameLoop()
{
    GameInit

    while true
    do
        if (( game_exit_confirmed ))
        then
            break
        fi

        FrameAction
        sleep 0.04                   # 每秒25帧,sleep 0.04
    done
}

# 游戏的暂停切换
# 切换后的状态为暂停则返回真(0),非暂停返回假(1)
function GamePauseSwitch()
{
    game_paused=$(( ! game_paused ))

    return $(( ! game_paused ))
}

# 启动游戏
function GameStart()
{
    GameLoop &
    pid_loop=$!
}

# 游戏结束
function GameOver()
{
    game_paused=1
    game_overed=1
    TipShow "Game Over! Press $KEY_EXIT to exit."
}

# 退出游戏
function GameExit()
{
    game_exit_confirmed=1
}

# 退出游戏清理操作
function ExitClear()
{
    # 恢复大小写case比较的开关
    shopt -u nocasematch

    # 恢复stty配置
    stty $stty_save
    tput cnorm

    clear
}

# 绘制边界
function DrawBorder()
{
    local i
    local border_h
    local border_v
    local r
    local c
    local c2

    border_h=""
    for (( i = 0; i < GAME_AREA_WIDTH + 1; ++i ))
    do
        border_h="$border_h$BORDER_H"
    done

    # 画顶边
    r=$(( GAME_AREA_TOP - 1 ))
    c=$(( GAME_AREA_LEFT - 1 ))
    echo -ne "${ESC}[${r};${c}H${border_h}"

    # 画底边
    r=$(( GAME_AREA_TOP + GAME_AREA_HEIGHT ))
    echo -ne "${ESC}[${r};${c}H${border_h}"

    c2=$(( GAME_AREA_LEFT - 1 + GAME_AREA_WIDTH + 1 ))
    for (( r = GAME_AREA_TOP - 1; r < GAME_AREA_TOP + GAME_AREA_HEIGHT + 1; ++r ))
    do
        echo -ne "${ESC}[${r};${c}H${BORDER_V}${ESC}[${r};${c2}H${BORDER_V}"
    done
}

# 处理列表中的物件坐标
# 仅是改坐标,显示丢给显示函数处理
# 参数一为当前位置列表
# 参数二为旧位置列表
# 参数三四为物件高宽,缺省为 1
function ListMove()
{
    local i flag pos
    local r c h w
    local ar ar_old

    ar=$1
    ar_old=$2
    h=${3:-1}
    w=${4:-1}

    # 记录旧位置
    eval "$ar_old=( \"\${$ar[@]}\" )"

    # 更新当前位置
    flag=0
    eval "count=\${#$ar[@]}"
    for (( i = 0; i < count; ++i ))
    do
        eval "pos=( \${$ar[$i]} )"
        r=$(( ${pos[0]} + ${pos[2]} ))
        c=$(( ${pos[1]} + ${pos[3]} ))

        if ! IsInGameArea $r $c $h $w
        then
            # 超出游戏区域的删除
            eval "unset $ar[$i]"
            flag=1
        else
            # 在游戏区域内的更新位置
            eval "$ar[$i]=\"$r $c ${pos[2]} ${pos[3]} ${pos[4]}\""
        fi
    done

    # 如果有删除元素,则要重组数组,以便下标连续
    if [ $flag -eq 1 ]
    then
        eval "$ar=( \"\${$ar[@]}\" )"
    fi
}

# 读取最高分数
function TopScoreRead()
{
    # 若没有最高分数记录则最高分为0
    if [ ! -f "$FILE_TOP_SCORE" ]
    then
        score_top=0
        return
    fi

    # 读取文件内容,若不是有效数字则设置最高分为0
    score_top=$(cat "$FILE_TOP_SCORE")
    if [ "${score_top//[[:digit:]]}" != "" ]
    then
        score_top=0
    fi
}

# 保存最高分数
function TopScoreSave()
{
    echo $score_top > "$FILE_TOP_SCORE"
}

# 更新最高分
# 最高分更改了返回真,没有更改返回假
function TopScoreUpdate()
{
    if (( score < score_top ))
    then
        return 1
    fi

    score_top=$score
    return 0
}

# 刷新最高分的屏幕显示
function TopScoreRefresh()
{
    echo -ne "${ESC}[${MSG_TOP_SCORE_TOP};${MSG_TOP_SCORE_LEFT}H$score_top   "
}

# 标题显示
function TitleShow()
{
    local r c
    r=$TITLE_POS_TOP
    c=$TITLE_POS_LEFT

    echo -ne "${ESC}[${r};${c}H______________________________________________________________" ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H___    |___  _/__  __ \_  ____/__  __ \__    |__  ____/__  __/" ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H__  /| |__  / __  /_/ /  /    __  /_/ /_  /| |_  /_   __  /   " ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H_  ___ |_/ /  _  _, _// /___  _  _, _/_  ___ |  __/   _  /    " ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H/_/  |_/___/  /_/ |_| \____/  /_/ |_| /_/  |_/_/      /_/     " ;
}

# 显示游戏信息
function MessageShow()
{
    local r c

    r=$MSG_POS_TOP
    c=$MSG_POS_LEFT

    echo -ne "${ESC}[${r};${c}HInfomation"                     ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    Score     : $score   "      ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    Level     : $level   "      ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    Bullet    : $bullet_num   " ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    Top Score : $score_top   "  ; (( ++r ))
    (( ++r ))
    (( ++r ))
    echo -ne "${ESC}[${r};${c}HOperation"                      ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    Up        : $KEY_UP"        ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    Down      : $KEY_DOWN"      ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    Left      : $KEY_LEFT"      ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    Right     : $KEY_RIGHT"     ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    Shoot     : $KEY_SHOOT"     ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    Pause     : $KEY_PAUSE"     ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    Quit      : $KEY_EXIT"      ; (( ++r ))
    (( ++r ))
    (( ++r ))
    echo -ne "${ESC}[${r};${c}HIntroduction"                          ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    1. 1 point score per enemy"        ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    2. Level up every 10 points score" ; (( ++r ))
    echo -ne "${ESC}[${r};${c}H    3. Level up to increase bullet number"
}

# 显示提示
# 参数一:提示信息的内容
# 简单起见,只做一行的提示
declare length_msg      # 信息长度,用于清除提示使用
function TipShow()
{
    local msg=${1:- }
    local r c
    local border_h
    local tip_lines=3

    length_msg=${#msg}
    border_h="+--$(echo $msg | sed 's/./-/g')--+"
    msg="|  ${BLINK}$msg${NOR}  |"

    r=$(( GAME_AREA_TOP + GAME_AREA_HEIGHT / 2 - tip_lines ))
    c=$(( (GAME_AREA_WIDTH - ${#border_h}) / 2 + GAME_AREA_LEFT ))

    echo -ne "${ESC}[${r};${c}H$border_h"  ;  (( ++r ))
    echo -ne "${ESC}[${r};${c}H$msg"      ;  (( ++r ))
    echo -ne "${ESC}[${r};${c}H$border_h"
}

# 清除提示
function TipClear()
{
    local r c
    local empty_line
    local len
    local tip_lines=3

    len=$(( length_msg + 6 ))
    empty_line=$(printf "%${len}s")

    r=$(( GAME_AREA_TOP + GAME_AREA_HEIGHT / 2 - tip_lines ))
    c=$(( (GAME_AREA_WIDTH - ${#empty_line}) / 2 + GAME_AREA_LEFT ))

    echo -ne "${ESC}[${r};${c}H$empty_line"  ;  (( ++r ))
    echo -ne "${ESC}[${r};${c}H$empty_line"  ;  (( ++r ))
    echo -ne "${ESC}[${r};${c}H$empty_line"
}

# ----------------------------------------------------------------------------
# 子弹处理
# ----------------------------------------------------------------------------

# 向子弹链表里加入一个子弹坐标即可
# 增加立即显示的操作
# 参数:row col row_speed col_speed
function BulletAdd()
{
    # 只在游戏区域范围内的才加入,不在的就不处理
    if [ $1 -le $height_min -o $1 -ge $height_max -o $2 -le $width_min -o $2 -ge $width_max ]
    then
        return
    fi

    # 若弹药用光了,则不能发射
    if IsBulletUsedUp
    then
        return
    fi

    ar_pos_bullet=( "${ar_pos_bullet[@]}" "$1 $2 $3 $4" )
    BulletPut $1 $2
}

# 判断弹药是否用光
function IsBulletUsedUp()
{
    if [ $bullet_num -le 0 ]
    then
        return 0
    fi

    return 1
}

# 子弹库存数量更新
function BulletNumUpdate()
{
    (( bullet_num = bullet_num_max - ${#ar_pos_bullet[@]} ))
}

# 子弹刷新
function BulletRefresh()
{
    BulletMove
    BulletDisplay

    BulletNumUpdate
    BulletNumRefresh
}

# 移动所有的子弹
# 仅是改坐标,显示丢给显示函数处理
function BulletMove()
{
    ListMove ar_pos_bullet ar_old_pos_bullet

    #   以下为原内容,想想要做成一个通用的函数来替换才行,于是有了 ListMove
    #   local i flag pos
    #   local r c
    #
    #   # 记录旧位置
    #   ar_old_pos_bullet=( "${ar_pos_bullet[@]}" )
    #
    #   # 更新当前位置
    #   flag=0
    #   count=${#ar_pos_bullet[@]}
    #   for (( i = 0; i < count; ++i ))
    #   do
    #       pos=( ${ar_pos_bullet[$i]} )
    #       r=$(( ${pos[0]} + ${pos[2]} ))
    #       c=$(( ${pos[1]} + ${pos[3]} ))
    #
    #       if [ $r -le $height_min -o $r -ge $height_max -o $c -le $width_min -o $c -ge $width_max ]
    #       then
    #           unset ar_pos_bullet[$i]
    #           flag=1
    #       else
    #           ar_pos_bullet[$i]="$r $c ${pos[2]} ${pos[3]}"
    #       fi
    #   done
    #
    #   # 如果有删除元素,则要重组数组,以便下标连续
    #   if [ $flag -eq 1 ]
    #   then
    #       ar_pos_bullet=( "${ar_pos_bullet[@]}" )
    #   fi
}

# 显示所有的子弹
function BulletDisplay()
{
    local pos c r

    for pos in "${ar_old_pos_bullet[@]}"
    do
        pos=( ${pos[@]} )
        r=${pos[0]}
        c=${pos[1]}
        BulletClear $r $c
    done

    for pos in "${ar_pos_bullet[@]}"
    do
        pos=( ${pos[@]} )
        r=${pos[0]}
        c=${pos[1]}
        BulletPut $r $c
    done
}

# 显示一个子弹
function BulletPut()
{
    tput cup $1 $2
    echo -n "!"
}

# 清除一个子弹
function BulletClear()
{
    tput cup $1 $2
    echo -n " "
}

# ----------------------------------------------------------------------------
# 玩家处理
# ----------------------------------------------------------------------------

# 玩家移动
# $1: U D L R = up down left right
function PlayerMove()
{
    case "$1" in
        'U') (( pos_player_r = (pos_player_r - 1 <= range_player_r_min ? range_player_r_min : pos_player_r - 1)  )) ;;
        'D') (( pos_player_r = (pos_player_r + 1 >= range_player_r_max ? range_player_r_max : pos_player_r + 1)  )) ;;
        'L') (( pos_player_c = (pos_player_c - 1 <= range_player_c_min ? range_player_c_min : pos_player_c - 1)  )) ;;
        'R') (( pos_player_c = (pos_player_c + 1 >= range_player_c_max ? range_player_c_max : pos_player_c + 1)  )) ;;
    esac

    PlayerPosUpdate $pos_player_r $pos_player_c
}

# 玩家位置数据更新
function PlayerPosUpdate()
{
    pos_player_r=$1
    pos_player_c=$2
}

# 玩家图像刷新
function PlayerDraw()
{
    PlayerClear $old_player_r $old_player_c
    PlayerPut $pos_player_r $pos_player_c

    old_player_r=$pos_player_r
    old_player_c=$pos_player_c
}

# 传入坐标,放置玩家
function PlayerPut()
{
    local r c
    r=$1
    c=$2

    echo -ne "${ESC}[${r};${c}H  A${NOR}"
    (( ++r ))
    echo -ne "${ESC}[${r};${c}H-=#=-${NOR}"
    (( ++r ))
    echo -ne "${ESC}[${r};${c}H -+-${NOR}"
}

# 传入坐标,清除玩家
function PlayerClear()
{
    local r c
    r=$1
    c=$2

    echo -ne "${ESC}[${r};${c}H   "
    (( ++r ))
    echo -ne "${ESC}[${r};${c}H     "
    (( ++r ))
    echo -ne "${ESC}[${r};${c}H    "
}

# ----------------------------------------------------------------------------
# 敌机处理
# ----------------------------------------------------------------------------

# 敌机随机生成
function EnemyRandomGen()
{
    local r c
    local speed_v speed_h
    local rand_range
    local color_index color_num

    # 最小 5% 几率增加敌机,等级升高一级增加 5% 几率
    if (( level < enemy_random_range_max ))
    then
        rand_range=$(( enemy_random_range_max - level ))
    else
        rand_range=1
    fi

    # 根据几率随机确定是否需要加入敌机
    if [ $(random $rand_range) -ne 0 ]
    then
        return
    fi

    color_num=${#ar_enemy_color}
    (( r = 1 + range_enemy_r_min ))                                  # 行坐标固定为第一行
    (( c = $(random range_enemy_c_max) + width_min ))                # 随机列坐标
    (( speed_v = $(random 3) + 1 ))                                  # 纵向速度为 1  -> 3
    (( speed_h = $(random 3) - 1 ))                                  # 横向速度为 -1 -> 1
    color_index=$(random $color_num)

    EnemyAdd $r $c $speed_v $speed_h $color_index
}

# 敌机列表中加入一个敌机
function EnemyAdd()
{
    # 只在游戏区域范围内的才加入,不在的就不处理
    if [ $1 -le $range_enemy_r_min -o $1 -ge $range_enemy_r_max -o $2 -le $range_enemy_c_min -o $2 -ge $range_enemy_c_max ]
    then
        return
    fi

    ar_pos_enemy=( "${ar_pos_enemy[@]}" "$1 $2 $3 $4 $5" )
    EnemyPut $1 $2 $5
}

# 敌机刷新
function EnemyRefresh()
{
    EnemyMove
    EnemyDisplay
}

# 移动所有的敌机
function EnemyMove()
{
    ListMove ar_pos_enemy ar_old_pos_enemy $enemy_height $enemy_width
}

# 显示所有的敌机
function EnemyDisplay()
{
    local pos c r color_index
    for pos in "${ar_old_pos_enemy[@]}"
    do
        pos=( ${pos[@]} )
        r=${pos[0]}
        c=${pos[1]}
        EnemyClear $r $c
    done

    for pos in "${ar_pos_enemy[@]}"
    do
        pos=( ${pos[@]} )
        r=${pos[0]}
        c=${pos[1]}
        color_index=${pos[4]}
        EnemyPut $r $c $color_index
    done
}

# 传入坐标,放置敌机
function EnemyPut()
{
    local r c color
    r=$1
    c=$2
    color=${ar_enemy_color[$3]}

    echo -ne "${ESC}[${r};${c}H${color} -+-${NOR}"      ;    (( ++r ))
    echo -ne "${ESC}[${r};${c}H${color}-=#=-${NOR}"     ;    (( ++r ))
    echo -ne "${ESC}[${r};${c}H${color}  V${NOR}"
}

# 传入坐标,清除敌机
function EnemyClear()
{
    local r c
    r=$1
    c=$2

    echo -ne "${ESC}[${r};${c}H    "    ;    (( ++r ))
    echo -ne "${ESC}[${r};${c}H     "   ;    (( ++r ))
    echo -ne "${ESC}[${r};${c}H    "
}

# ----------------------------------------------------------------------------
# 背景处理
# ----------------------------------------------------------------------------

# 背景星星处理线程
function StarRandomGen()
{
    local r c
    local speed_v speed_h
    local style style_num

    # 80% 机率增加星星
    if [ $(random 5) -ne 0 ]
    then
        return
    fi

    style_num=${#ar_star_style[@]}
    (( r = 1 + height_min ))
    (( c = $(random $(( width_max - 1 )) ) + width_min ))           # 随机列坐标
    (( speed_v = $(random 3) + 1 ))                                 # 纵向速度为 1  -> 3
    (( speed_h = 0 ))                                               # 横向速度为 0
    style=$(random $style_num)

    StarAdd $r $c $speed_v $speed_h $style
}

# 星星列表中加入一个星星
# 第五个参数为星星的风格
function StarAdd()
{
    # 只在游戏区域范围内的才加入,不在的就不处理
    if [ $1 -le $height_min -o $1 -ge $height_max -o $2 -le $width_min -o $2 -ge $width_max ]
    then
        return
    fi

    ar_pos_star=( "${ar_pos_star[@]}" "$1 $2 $3 $4 $5" )
    StarPut $1 $2 $5
}

# 星星刷新
function StarRefresh()
{
    StarMove
    StarDisplay
}

# 移动所有的星星
function StarMove()
{
    ListMove ar_pos_star ar_old_pos_star
}

# 显示所有的敌机
function StarDisplay()
{
    local pos c r
    for pos in "${ar_old_pos_star[@]}"
    do
        pos=( ${pos[@]} )
        r=${pos[0]}
        c=${pos[1]}
        StarClear $r $c
    done

    for pos in "${ar_pos_star[@]}"
    do
        pos=( ${pos[@]} )
        r=${pos[0]}
        c=${pos[1]}
        StarPut $r $c "${pos[4]}"
    done
}

# 传入坐标和风格,绘置星星
function StarPut()
{
    local r c star
    r=$1
    c=$2
    star=${ar_star_style[$3]}

    echo -ne "${ESC}[${r};${c}H${star}"
}

# 传入坐标,清除敌机
function StarClear()
{
    local r c
    r=$1
    c=$2

    echo -ne "${ESC}[${r};${c}H "
}

# ----------------------------------------------------------------------------
# 计分与升级处理
# ----------------------------------------------------------------------------

# 增加分数
# 参数一:增加的分数值,缺省为 1
function ScoreIncrease()
{
    (( score += ${1:-1} ))
    ScoreRefresh

    if (( score / 10 + 1 > level ))
    then
        LevelUp
    fi

    if TopScoreUpdate
    then
        TopScoreSave
        TopScoreRefresh
    fi
}

# 分数刷新
function ScoreRefresh()
{
    echo -ne "${ESC}[${MSG_SCORE_TOP};${MSG_SCORE_LEFT}H$score   "
}

# 升级
# 参数一:升级数值,缺省为 1
function LevelUp()
{
    (( level += ${1:-1} ))
    LevelRefresh

    # 升级增加子弹最大数量
    bullet_num_max=$level
    BulletNumUpdate
    BulletNumRefresh
}

# 等级刷新
function LevelRefresh()
{
    echo -ne "${ESC}[${MSG_LEVEL_TOP};${MSG_LEVEL_LEFT}H$level   "
}

# 子弹发射数刷新
function BulletNumRefresh()
{
    echo -ne "${ESC}[${MSG_BULLET_TOP};${MSG_BULLET_LEFT}H$bullet_num/$bullet_num_max      "
}

# ----------------------------------------------------------------------------
# 帧处理
# ----------------------------------------------------------------------------

# 帧动作
declare frame_count=0       # 全局变量代替静态变量的作用
function FrameAction()
{
    # 若游戏暂停,则不进行帧动作
    if (( game_paused ))
    then
        return
    fi

    # 碰撞检测
    HitTest
    if (( game_overed ))
    then
        return
    fi

    # 每四帧刷新背景
    if (( frame_count % 4 == 0 ))
    then
        StarRefresh
    fi

    # 每两帧刷新敌机
    if (( frame_count % 2 == 0 ))
    then
        EnemyRefresh
    fi

    # 每帧刷新子弹
    BulletRefresh

    # 每帧刷新角色
    PlayerDraw


    # 每帧随机生成敌机
    EnemyRandomGen

    # 每帧随机生成星星
    StarRandomGen

    (( ++frame_count ))
    if (( frame_count > 10000 ))
    then
        frame_count=0
    fi
}

# ----------------------------------------------------------------------------
# 碰撞处理
# ----------------------------------------------------------------------------

# 碰撞检测
function HitTest()
{
    # 敌机与子弹的碰撞
    HitTestBulletEnemy

    # 敌机与玩家的碰撞
    HitTestPlayEnemy
}

# 碰撞判断
# 参数:
# 1 - 4:物件 1 的行、列、高、宽
# 5 - 8:物件 2 的行、列、高、宽
function IsHit()
{
    local r1 c1 h1 w1 r2 c2 h2 w2

    r1=$1
    c1=$2
    h1=$3
    w1=$4
    r2=$5
    c2=$6
    h2=$7
    w2=$8

    # 横向无交叉,未碰撞
    if (( (r1 <= r2 && (r1 + h1) <= r2) || (r1 >= r2 && (r2 + h2) <= r1) ))
    then
        return 1
    fi

    # 纵向无交叉,未碰撞
    if (( (c1 <= c2 && (c1 + w1) <= c2) || (c1 >= c2 && (c2 + w2) <= c1) ))
    then
        return 1
    fi

    # 碰撞
    return 0
}

# 敌机与子弹的碰撞
function HitTestBulletEnemy()
{
    local pos1 pos2
    local r1 c1 h1 w1 r2 c2 h2 w2
    local i j
    local flag_reset

    h1=1
    w1=1
    h2=$enemy_height
    w2=$enemy_width

    flag_reset=0
    i=0
    for pos1 in "${ar_pos_bullet[@]}"
    do
        pos1=( ${pos1[@]} )
        r1=${pos1[0]}
        c1=${pos1[1]}

        j=0
        for pos2 in "${ar_pos_enemy[@]}"
        do
            pos2=( ${pos2[@]} )
            r2=${pos2[0]}
            c2=${pos2[1]}

            if IsHit $r1 $c1 $h1 $w1 $r2 $c2 $h2 $w2
            then
                unset ar_pos_bullet[$i]
                unset ar_pos_enemy[$j]

                BulletClear $r1 $c1
                EnemyClear $r2 $c2

                ScoreIncrease

                flag_reset=1
            fi

            (( ++j ))
        done

        (( ++i ))
    done

    # 有元素删除,重新设置数组以使得下标连续
    if [ $flag_reset -eq 1 ]
    then
        ar_pos_bullet=( "${ar_pos_bullet[@]}" )
        ar_pos_enemy=( "${ar_pos_enemy[@]}" )

        return 0
    fi

    return 1
}

# 敌机与玩家的碰撞
function HitTestPlayEnemy()
{
    local pos r c

    for pos in "${ar_pos_enemy[@]}"
    do
        pos=( ${pos[@]} )
        r=${pos[0]}
        c=${pos[1]}

        # 若敌机与玩家碰撞,则游戏结束
        if IsHit $r $c $enemy_width $enemy_width $pos_player_r $pos_player_c $player_height $player_width
        then
            GameOver
            return
        fi
    done
}

# ----------------------------------------------------------------------------
# 按键响应
# ----------------------------------------------------------------------------

# 按键响应:退出游戏
function OnPressGameExit()
{
    # 游戏结束、暂停情况下,直接退出
    if (( game_overed || game_paused ))
    then
        GameExit
        return
    fi

    # 游戏中按下退出的话,先暂停并提示确认退出
    game_paused=1
    TipShow "Press $KEY_EXIT to exit, $KEY_PAUSE to continue."
}

# 按键响应:发射子弹
function OnPressShoot()
{
    BulletAdd $(( pos_player_r + player_gun_offset_r )) $(( pos_player_c + player_gun_offset_c )) -1 0
}

# 按键响应:玩家动作
# $1: U D L R = up down left right
function OnPressPlayerMove()
{
    PlayerMove $1
}

# 按键响应:游戏暂停
function OnPressGamePause()
{
    local msg_paused="Game paused."

    if GamePauseSwitch
    then
        TipShow "$msg_paused"
    else
        TipClear
        game_exit_confirmed=0
    fi
}

# ----------------------------------------------------------------------------
# 主函数
# ----------------------------------------------------------------------------

# 主函数
function Main()
{
    Init

    GameStart
    Input
    ExitClear
}

Main


你可能感兴趣的:(shell,游戏)