Shell 仿消灭星星游戏(2013-03-15)

本文通过Shell编写了一个类似消灭星星的小游戏,虽然没有使用二维数组,实现过程颇具挑战。作者分享了代码,并提供了下载链接以供详细查看。
摘要由CSDN通过智能技术生成
前言
做一个小游戏练习 shell脚本的语法什么的。入门一般都是俄罗斯方块,不过也都有了,推箱子有用C写过,很简单,网上也有了,就做一个网上还没有人用 shell写过的吧。模拟 IPAD 上一个“消灭星星”的游戏吧,就是消去几个连在一起的方块得分的游戏,看上去很简单。

正文

就放一个截图吧:



代码

放主程序吧,其它的放附件,方便下载查看。呃,csdn不能放附件啊?那就到 chinaunix 下载吧:http://blog.chinaunix.net/blog/downLoad/fileid/8112.html

#!/bin/bash
# block.sh
# 作者:亚丹
# 时间:2012-03-16
# http://seesea.blog.chinaunix.net
# http://blog.csdn.net/nicenight
# 功能:模拟 IPAD 的一个消方块的游戏
# 游戏规则:
#   1. 有两个以上的同色方块就可以消去,同时消去的方块越多,得分越多
#   2. 过关时,剩余的方块越少,奖励分数越多
#   3. 当总分低于过关目标分数时,游戏结束
#
# 写着写着发现控制和逻辑越来越混了,也罢,就是练习一下语法嘛

#-----------------------------------------------------------
# 加载通用文件
source colors.sh
source array2d.sh
source util.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 EFFECT_CURSOR=${BWHT}

# 当前位置同色的成片方块特殊效果,这里设置为亮黄色背景显示
declare -r EFFECT_CONNECTION=${BYEL}

# 游戏区域顶边界和左边界
declare -r MAP_AREA_TOP=9
declare -r MAP_AREA_LEFT=20

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

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

#-----------------------------------------------------------
# 常量
declare -r BLOCK_WIDTH=3        # 一个元素显示的宽度
declare -r BLOCK_HEIGHT=3       # 一个元素显示的高度

declare -r BONUS_STAGE=1000     # 过关奖励
declare -r BONUS_DECREASE=100   # 过关剩余方块扣分基数

#-----------------------------------------------------------
# 全局变量
declare pid                     # 主线程pid
declare sub_pid                 # 子线程pid
declare sign                    # 信号
declare stty_save               # 保存stty配置

declare score                   # 当前分数
declare score_top               # 最高分
declare score_shoot             # 当前区域消去可得分数
declare score_goal              # 当前关卡过关分数
declare stage_round             # 当前关卡数
declare bonus_stage             # 当前过关奖励分数

declare width                   # 屏宽
declare height                  # 屏高
declare limit_cursor_row        # 光标可移动范围的行数限制
declare limit_cursor_col        # 光标可移动范围的列数限制
declare paused                  # 是否暂停状态标志
declare exiting                 # 是否在退出状态
declare repaint                 # 是否需要重绘

declare map_area_width          # 游戏区域宽
declare map_area_height         # 游戏区域高

declare row_cur                 # 当前行
declare col_cur                 # 当前列
declare row_old                 # 旧行
declare col_old                 # 旧列

declare -a memory_page1         # 内存页一
declare -a memory_page2         # 内存页二

declare -a invalid_rect         # 需要重绘的脏矩形,格式:row0 col0 row1 col1

declare -a range_row_cur        # 光标所示同色方块相连的所有方块 行 位置
declare -a range_col_cur        # 光标所示同色方块相连的所有方块 列 位置

declare -a range_row_old        # 光标所示同色方块相连的所有方块 行 位置备份
declare -a range_col_old        # 光标所示同色方块相连的所有方块 列 位置备份

declare player_shoot=bc_click   # 函数指针,玩家击键后,调用游戏逻辑的处理函数

#-----------------------------------------------------------
# 函数定义

# 设备控制相关初始化
function Init()
{
    # ----------------------
    # 初始化游戏配置

    # 记录主线程pid
    pid=$$

    # 保存stty配置
    stty_save=$(stty -g)

    # 清屏
    clear

    # 获取显示信息
    width=$(tput cols)
    height=$(tput lines)

    # 关闭入出回显
    stty -echo

    # 关闭光标
    tput civis

    # 开启大小写case比较的开关
    shopt -s nocasematch
}

# 设置光标在地图中的相对位置
# 参数一:相对行
# 参数二:相对列
function Put_cursor_in_map()
{
    tput cup $(( MAP_AREA_TOP + $1 )) $(( MAP_AREA_LEFT + $2 ))
}

# 退出清理函数
function Exit_clear()
{
    # 清屏
    clear

    # 恢复输入回显
    # stty echo

    # 恢复大小写case比较的开关
    shopt -u nocasematch

    # 恢复stty配置
    stty $stty_save
}

# 帧函数
function Frame()
{
    :
}

# 刷新
function Refresh()
{
    # 显示提示信息
    Show_message

    # 刷新地图
    Refresh_map

    # 处理显示光标所在位置的连接在一起的同色区域
    Refresh_range

    # 显示光标
    Show_cursor

    # 处理光标所在块的显示
    # Show_cursor_block
}

# 刷新地图
function Refresh_map()
{
    local i
    local j
    local bit1
    local bit2

    # 得到最新显示页的数据
    memory_page1=( ${bc_map[@]} )

    # 关闭光标
    # tput civis

    # 逐位扫描
    for (( i = ${invalid_rect[0]}; i < ${invalid_rect[2]}; ++i ))
    do
        for (( j = ${invalid_rect[1]}; j < ${invalid_rect[3]}; ++j ))
        do
            bit1=$(array_get memory_page1 BC_COL $i $j)
            bit2=$(array_get memory_page2 BC_COL $i $j)

            # echo "refresh $i $j $bit1 $bit2" >> refresh.txt

            # 若两个位置数据一样,不需要更新显示
            if (( ! (bit1 ^ bit2) ))
            then
                continue
            fi

            # 根据位置更新该位的字符
            Print_block $i $j "${BC_IMAGE[$bit1]}"

            # echo "+ OK  ${BC_IMAGE[$bit1]}" >> refresh.txt
        done
    done

    # 开启光标
    # tput cnorm

    # 将当前显示页数据备份到备份页
    memory_page2=( ${memory_page1[@]} )

    # 脏矩形置0
    invalid_rect=( 0 0 0 0 )
}
#------------------------------------------------------------------
#------------------------------------------------------------------
# 刷新
# 尝试用内存图
# declare -a memory_map
# function Refresh2()
# {
#     local i
#     local j
#     local bit1
#     local bit2
#
#     # 显示提示信息
#     Show_message
#
#     # 得到最新显示页的数据
#     memory_page1=( ${bc_map[@]} )
#
#     # 关闭光标
#     # tput civis
#
#     # 逐位扫描
#     for (( i = ${invalid_rect[0]}; i < ${invalid_rect[2]}; ++i ))
#     do
#         for (( j = ${invalid_rect[1]}; j < ${invalid_rect[3]}; ++j ))
#         do
#             bit1=$(array_get memory_page1 BC_COL $i $j)
#             bit2=$(array_get memory_page2 BC_COL $i $j)
#
#             # 若两个位置数据一样,不需要更新显示
#             if (( ! (bit1 ^ bit2) ))
#             then
#                 continue
#             fi
#
#             # 根据位置更新该位的字符
#             Print_block_memory $i $j "\${BC_IMAGE[$bit1]}"
#         done
#
#         Print_line $i "$(array_get_row memory_map BC_COL $i)"
#     done
#
#     # 开启光标
#     # tput cnorm
#
#     # 将当前显示页数据备份到备份页
#     memory_page2=( ${memory_page1[@]} )
#
#     # 脏矩形置0
#     invalid_rect=( 0 0 0 0 )
#
#     # 处理显示光标所在位置的连接在一起的同色区域
#     Show_range
#
#     # 恢复坐标
#     Show_cursor
#
#     # 处理光标所在块的显示
#     Show_cursor_block
# }
#
# # 在内存图上打印
# function Print_block_memory()
# {
#     local i
#     local j
#     local x
#     local y
#
#     x=$(( BLOCK_HEIGHT * $1 ))
#     y=$(( BLOCK_WIDTH * $2 ))
#
#     for (( i = 0; i < BLOCK_HEIGHT; ++i ))
#     do
#         for (( j = 0; j < BLOCK_WIDTH; ++j ))
#         do
#             array_set memory_map BC_COL $(( y + $i )) $(( x + $j )) "$3"
#         done
#     done
#
#     # echo ${memory_map[@]} >> xx.txt
# }
#
# function Print_line()
# {
#     local i
#     local x
#
#     x=$(( BLOCK_HEIGHT * $1 ))
#
#     for (( i = 0; i < BLOCK_HEIGHT; ++i ))
#     do
#         Put_cursor_in_map $(( x + i )) 0
#         echo -ne "${2}"
#     done
# }

#------------------------------------------------------------------
#------------------------------------------------------------------

# 显示提示信息
function Show_title()
{
    local title_left
    local title_top

    title_top=1
    title_left=$(( MAP_AREA_LEFT - 5 ))

    tput cup $(( title_top++ )) $title_left
    echo -ne '_|_|_|    _|          _|_|      _|_|_|  _|    _|    _|_|_|'
    tput cup $(( title_top++ )) $title_left
    echo -ne '_|    _|  _|        _|    _|  _|        _|  _|    _|      '
    tput cup $(( title_top++ )) $title_left
    echo -ne '_|_|_|    _|        _|    _|  _|        _|_|        _|_|  '
    tput cup $(( title_top++ )) $title_left
    echo -ne '_|    _|  _|        _|    _|  _|        _|  _|          _|'
    tput cup $(( title_top++ )) $title_left
    echo -ne '_|_|_|    _|_|_|_|    _|_|      _|_|_|  _|    _|  _|_|_|  '


#    tput cup $(( title_top++ )) $title_left
#    echo -ne '@@@@@@@   @@@        @@@@@@    @@@@@@@  @@@  @@@   @@@@@@ '
#    tput cup $(( title_top++ )) $title_left
#    echo -ne '@@@@@@@@  @@@       @@@@@@@@  @@@@@@@@  @@@  @@@  @@@@@@@ '
#    tput cup $(( title_top++ )) $title_left
#    echo -ne '@@!  @@@  @@!       @@!  @@@  !@@       @@!  !@@  !@@     '
#    tput cup $(( title_top++ )) $title_left
#    echo -ne '!@   @!@  !@!       !@!  @!@  !@!       !@!  @!!  !@!     '
#    tput cup $(( title_top++ )) $title_left
#    echo -ne '@!@!@!@   @!!       @!@  !@!  !@!       @!@@!@!   !!@@!!  '
#    tput cup $(( title_top++ )) $title_left
#    echo -ne '!!!@!!!!  !!!       !@!  !!!  !!!       !!@!!!     !!@!!! '
#    tput cup $(( title_top++ )) $title_left
#    echo -ne '!!:  !!!  !!:       !!:  !!!  :!!       !!: :!!        !:!'
#    tput cup $(( title_top++ )) $title_left
#    echo -ne ':!:  !:!   :!:      :!:  !:!  :!:       :!:  !:!      !:! '
#    tput cup $(( title_top++ )) $title_left
#    echo -ne ' :: ::::   :: ::::  ::::: ::   ::: :::   ::  :::  :::: :: '
#    tput cup $(( title_top++ )) $title_left
#    echo -ne ':: : ::   : :: : :   : :  :    :: :: :   :   :::  :: : :  '
}

# 显示提示信息
function Show_message()
{
    local msg_left
    local msg_top

    msg_left=$(( MAP_AREA_LEFT + BLOCK_WIDTH * BC_COL + 5 ))
    msg_top=$((MAP_AREA_TOP - 1 ))

    # tput civis
    tput cup $(( msg_top++ )) $msg_left
    echo -n "Score       : $score      "
    tput cup $(( msg_top++ )) $msg_left
    echo -n "Top Score   : $score_top      "
    tput cup $(( msg_top++ )) $msg_left
    echo -n "Preview     : $score_shoot    "

    (( msg_top++ ))
    tput cup $(( msg_top++ )) $msg_left
    echo -n "Stage       : $stage_round  "
    tput cup $(( msg_top++ )) $msg_left
    echo -n "Stage bonus : $bonus_stage  "
    tput cup $(( msg_top++ )) $msg_left
    echo -n "Stage goal  : $score_goal   "

    (( msg_top++ ))
    tput cup $(( msg_top++ )) $msg_left
    echo -n "Operation"
    tput cup $(( msg_top++ )) $msg_left
    echo -n "Up          : $KEY_UP"
    tput cup $(( msg_top++ )) $msg_left
    echo -n "Down        : $KEY_DOWN"
    tput cup $(( msg_top++ )) $msg_left
    echo -n "Left        : $KEY_LEFT"
    tput cup $(( msg_top++ )) $msg_left
    echo -n "Right       : $KEY_RIGHT"
    tput cup $(( msg_top++ )) $msg_left
    echo -n "Shoot       : $KEY_SHOOT"
    tput cup $(( msg_top++ )) $msg_left
    echo -n "Quit        : $KEY_EXIT"

    # tput cnorm
}

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

    # Put_cursor_in_map $i -1
    border_h=""
    for (( i = 0; i < map_area_width + 2; ++i ))
    do
        border_h="$border_h$MAP_BORDER_H"
    done

    r=$(( MAP_AREA_TOP ))
    c=$(( MAP_AREA_LEFT ))
    echo -ne "${ESC}[${r};${c}H${border_h}"

    r=$(( MAP_AREA_TOP + map_area_height + 1 ))
    echo -ne "${ESC}[${r};${c}H${border_h}"

    c2=$(( MAP_AREA_LEFT + map_area_width + 1 ))
    for (( r = MAP_AREA_TOP + 1; r < MAP_AREA_TOP + map_area_height + 1; ++r ))
    do
        echo  -ne "${ESC}[${r};${c}H${MAP_BORDER_V}${ESC}[${r};${c2}H${MAP_BORDER_V}"
    done
}

# 方块消失动画
# 参数一:方块行
# 参数二:方块列
# 拟按顺时针方向一块一块地消失
function Animation_block_disappear()
{
    local block_row # 当前块所在的初始行(block_top)
    local block_col # 当前块所在的初始列(block_left)
    local char
    local i
    local r         # 当前处理行
    local c         # 当前处理列
    local top       # 旋转后的厚度:顶
    local bottom    # 旋转后的厚度:底
    local left      # 旋转后的厚度:左边界
    local right     # 旋转后的厚度:右边界
    local dir       # 方向:0 1 2 3 -> 右 下 左 上(顺时针)

    block_row=$(( BLOCK_HEIGHT * $1 ))
    block_col=$(( BLOCK_WIDTH * $2 ))
    char="${BC_IMAGE[0]}"

    dir=0
    top=0
    bottom=0
    left=0
    right=0
    c=0
    r=0
    i=$(( BLOCK_HEIGHT * BLOCK_WIDTH ))

    while true
    do
        echo "$i $dir $r $c $top $bottom $right $left" >> dir.txt
        Put_cursor_in_map $(( block_row + r )) $(( block_col + c ))
        echo -ne "$char"

        # 若所有块处理完毕,就结束循环
        if (( i <= 1 ))
        then
            break
        fi

        case $dir in
            0)  # 右行
                if (( c >= (BLOCK_WIDTH - right - 1) ))
                then
                    (( dir = (dir + 1) % 4 ))
                    (( ++top ))
                    continue
                fi

                (( ++c ))
            ;;

            1)  # 下行
                if (( r >= (BLOCK_HEIGHT - bottom - 1) ))
                then
                    (( dir = (dir + 1) % 4 ))
                    (( ++right ))
                    continue
                fi

                (( ++r ))
            ;;
            2)  # 左行
                if (( c <= left ))
                then
                    (( dir = (dir + 1) % 4 ))
                    (( ++bottom ))
                    continue
                fi

                (( --c ))
            ;;

            3)  # 上行
                if (( r <= top ))
                then
                    (( dir = (dir + 1) % 4 ))
                    (( ++left ))
                    continue
                fi

                (( --r ))
            ;;
        esac

        (( --i ))

        # 动画延时
        sleep 0.01
    done
}

# 旧区域消失动画
function Animation_old_range_disappear()
{
    local i

    # 除最后一个块外,以后台形式播放消失动画

    for (( i = 0; i < ${#range_row_old[@]}; ++i ))
    do
        Animation_block_disappear ${range_row_old[$i]} ${range_col_old[$i]}
    done

    # Animation_block_disappear ${range_row_old[$i]} ${range_col_old[$i]}
}

# 显示指定位置的一个方块
# 参数一:方块 行 位置
# 参数二:方块 列 位置
# 参数三:方块填充字符
function Print_block()
{
    local i
    local block_row
    local block_col
    local block_line=""

    block_row=$(( BLOCK_HEIGHT * $1 ))
    block_col=$(( BLOCK_WIDTH * $2 ))

    for (( i = 0; i < BLOCK_WIDTH; ++i ))
    do
        block_line="$block_line$3"
    done

    for (( i = 0; i < BLOCK_HEIGHT; ++i ))
    do
        Put_cursor_in_map $(( block_row + i )) $block_col
        echo -ne "$block_line"
    done
}

# 将光标所在位置的方块特殊显示
function Show_cursor_block()
{
    local char

    # 恢复旧位置
    char="${BC_IMAGE[$(array_get memory_page1 BC_COL $row_old $col_old)]}"
    Print_block $row_old $col_old "$char"

    # 显示新位置
    char="${EFFECT_CURSOR}${BC_IMAGE[$(array_get memory_page1 BC_COL $row_cur $col_cur)]}"
    Print_block $row_cur $col_cur "$char"

    # 记录新位置为旧位置
    # row_old=$row_cur
    # col_old=$col_cur
}

# 判断光标是否在旧的特殊显示区域中
# 只需要判断前后两个位置的值是不是一样即可
# 是则返回 0
# 否则返回 1
function Is_cursor_in_old_range()
{
    local i
    local old_val
    local new_val

    new_val=$(array_get memory_page1 BC_COL $row_cur $col_cur)
    if [ $new_val -eq 0 ]
    then
        return 1
    fi

    old_val=$(array_get memory_page1 BC_COL $row_old $col_old)
    if [ $new_val -ne $old_val ]
    then
        return 1
    fi

    return 0

    # 原函数
    # if [ ${#range_row_old[@]} -eq 0 ]
    # then
    #     return 1
    # fi
    #
    # if [ $(array_get memory_page1 BC_COL $row_cur $col_cur) -eq 0 ]
    # then
    #     return 1
    # fi
    #
    # for ((i = 0; i < ${#range_row_old[@]}; ++i ))
    # do
    #     if [ $row_cur -eq ${range_row_old[$i]} -a $col_cur -eq ${range_col_old[i]} ]
    #     then
    #         return 0
    #     fi
    # done
    #
    # return 1
}

# 判断光标是否在特殊显示区域中
# 是则返回 0
# 否则返回 1
# function Is_cursor_in_range()
# {
#     local i
#
#     if [ ${#range_row_cur[@]} -eq 0 ]
#     then
#         return 1
#     fi
#
#     if [ $(array_get memory_page1 BC_COL $row_cur $col_cur) -eq 0 ]
#     then
#         return 1
#     fi
#
#     for ((i = 0; i < ${#range_row_cur[@]}; ++i ))
#     do
#         if [ $row_cur -eq ${range_row_cur[$i]} -a $col_cur -eq ${range_col_cur[i]} ]
#         then
#             return 0
#         fi
#     done
#
#     return 1
# }

# 更新区域信息
function Refresh_range_info()
{
    # 重新计算
    bc_calcualate_range $row_cur $col_cur

    range_row_old=( ${range_row_cur[@]} )
    range_col_old=( ${range_col_cur[@]} )
    range_row_cur=( ${bc_range_row[@]} )
    range_col_cur=( ${bc_range_col[@]} )
}

# 清除特殊显示光标所示方块相连的同色方块
function Clear_range()
{
    local char
    local i

    # 若区域数据为空,则不需要处理
    if [ ${#range_row_old[@]} -le 0 ]
    then
        return
    fi

    # 恢复旧块
    for (( i = 0; i < ${#range_row_old[@]}; ++i ))
    do
        char="${BC_IMAGE[$(array_get memory_page1 BC_COL ${range_row_old[$i]} ${range_col_old[$i]})]}"
        Print_block ${range_row_old[$i]} ${range_col_old[$i]} "$char"
    done
}

# 特殊显示光标所示方块相连的同色方块
function Show_range()
{
    local char
    local i

    # 显示新块
    char="${EFFECT_CONNECTION}${BC_IMAGE[$(array_get memory_page1 BC_COL $row_cur $col_cur)]}"
    for (( i = 0; i < ${#range_row_cur[@]}; ++i ))
    do
        Print_block ${range_row_cur[$i]} ${range_col_cur[$i]} "$char"
    done
}

# 更新相连区域的显示
# 参数一:是否强制刷新
#         1: 强制刷新,不判断光标位置
#         0: 判断光标位置变动情况来刷新
function Refresh_range()
{
    local force=1

    # 不传入参数,默认为 false
    if [ -z "$1" ] || [ $1 -eq 0 ]
    then
        force=0
    fi

    # 若当前光标移动后还在本区域内
    # 则不需要擦除旧块
    if [ $force -eq 1 ] || ! Is_cursor_in_old_range
    then
        Clear_range
    fi

    # 显示
    Show_range
}

# 键盘输入响应函数
function Input()
{
    while true
    do
        read -s -n 1 key

        case $key in
            $KEY_UP)     Player_up         ;;
            $KEY_DOWN)   Player_down       ;;
            $KEY_LEFT)   Player_left       ;;
            $KEY_RIGHT)  Player_right      ;;
            $KEY_SHOOT)  Player_shoot      ;;
            $KEY_PAUSE)  Game_pause_switch ;;
            $KEY_EXIT)   Game_exit         ;;
        esac

        if (( exiting == 1 ))
        then
            break
        fi
    done
}

# 游戏坐标:
#   +-----> x
#   |
#   |
#   |
#   v
#   y

# 玩家上移
function Player_up()
{
    if (( paused == 1 ))
    then
        return
    fi

    if (( row_cur <= 0 ))
    then
        return
    fi

    row_old=$row_cur
    col_old=$col_cur
    (( --row_cur ))
    Move_cursor
}

# 玩家下移
function Player_down()
{
    if (( paused == 1 ))
    then
        return
    fi

    if (( row_cur >= limit_cursor_row ))
    then
        return
    fi

    row_old=$row_cur
    col_old=$col_cur
    (( ++row_cur ))
    Move_cursor
}

# 玩家左移
function Player_left()
{
    if (( paused == 1 ))
    then
        return
    fi

    if (( col_cur <= 0 ))
    then
        return
    fi

    row_old=$row_cur
    col_old=$col_cur
    (( --col_cur ))
    Move_cursor
}

# 玩家右移
function Player_right()
{
    if (( paused == 1 ))
    then
        return
    fi

    if (( col_cur >= limit_cursor_col ))
    then
        return
    fi

    row_old=$row_cur
    col_old=$col_cur
    (( ++col_cur ))
    Move_cursor
}

# 光标显示函数
function Show_cursor()
{
    # tput cup $(( row_cur * BLOCK_WIDTH )) $(( col_cur * BLOCK_HEIGHT ))

    local char

    # 显示新位置
    char="${EFFECT_CURSOR}${BC_IMAGE[$(array_get memory_page1 BC_COL $row_cur $col_cur)]}"
    Print_block $row_cur $col_cur "$char"
}

# 光标移除函数
function Clear_cursor()
{
    local char

    # 恢复旧位置
    char="${BC_IMAGE[$(array_get memory_page1 BC_COL $row_old $col_old)]}"
    Print_block $row_old $col_old "$char"
}

# 移动光标操作处理函数
function Move_cursor()
{
    # 若光标不在新生成的区域内则需要刷新获得信息
    if ! Is_cursor_in_old_range
    then
        Refresh_range_info
    fi

    Clear_cursor
    Refresh_range
    Show_cursor

    Refresh_shoot_score
}

# 玩家开火
function Player_shoot()
{
    # 若当前区域只有一个方块,不允许消除
    if [ ${#range_row_cur[@]} -le 1 ]
    then
        return
    fi

    # 调用游戏逻辑处理
    $player_shoot $row_cur $col_cur

    # 增加分数
    Increase_score

#    # 需要重绘的区域为 顶行,最小列,最大行,最大列
#    # 判断作为容错处理(本不应该出现的情况,但可能脚本运行速度慢,并发的情况下出现)
#    # echo "range_row_cur: ${range_row_cur[@]}" >> debug.txt
#    # echo "range_col_cur: ${range_col_cur[@]}" >> debug.txt
#    if [ -z "${range_row_cur[@]}" ] || [ -z "${range_col_cur[@]}" ]
#    then
#        invalid_rect=( 0 0 $BC_ROW $BC_COL )
#    else
#        invalid_rect=( 0 $(min ${range_col_cur[@]}) $(( $(max ${range_row_cur[@]}) + 1 )) $(( $(max ${range_col_cur[@]}) + 1 )) )
#    fi

    # 在有空列需要移动的时候,需要重绘的区域为
    # 顶行,最小列,地图最大行,地图最大列
    invalid_rect=( 0 $(min ${range_col_cur[@]}) $BC_ROW $BC_COL )
    # invalid_rect=( 0 0 $BC_ROW $BC_COL )

    # 刷新区域信息
    Refresh_range_info

    # 插入旧区域消失动画
    Animation_old_range_disappear

    Refresh

    Refresh_shoot_score

    # 检测是否结束当前关卡
    if ! Is_stage_over
    then
        return
    fi

    # 清算关卡
    Clear_stage

    # 在结束当前关卡的情况下,检测是否游戏结束
    # 若未结束,则开启新关卡
    # 否则,结束游戏
    if ! Is_game_over
    then
        # 新关卡
        New_stage
    else
        # 结束游戏
        Game_over
    fi
}

# 计算当前消除方块能得到的分数
function Calcualate_score_shoot()
{
    local block_number

    block_number=${#range_row_cur[@]}

    case $block_number in
        1)      score_shoot=0 ;;
        [2-4])  score_shoot=$(( block_number * 20 )) ;;
        *)      score_shoot=$(( (block_number - 4 ) * 25 + 4 * 20 )) ;;
    esac
}

# 刷新预览分数
function Refresh_shoot_score()
{
    Calcualate_score_shoot
    Show_message
}

# 刷新分数,并根据当前分数来确认是否更新最高分
function Increase_score()
{
    (( score += score_shoot ))

    if (( score > score_top ))
    then
        score_top=$score
        Save_top_score
    fi
}

# 测试当前轮次是否结束
function Is_stage_over()
{
    bc_check_stage_over

    return $?
}

# 测试游戏是否结束
# 是则返回 0
# 否则返回 1
function Is_game_over()
{
    if (( score < score_goal ))
    then
        return 0
    fi

    return 1
}

# 计算过关分数
function Calcualate_score_goal()
{
    # 这个过关分数需要委认真地计算呀,这里就只是简单做着玩,就不那么认真了

    # 计算规则:按一个方块 20 分计算,一局可得 20 * BC_ROW * BC_COL 分
    # 目标就是总分的 1/4
    (( score_goal = stage_round * BC_ROW * BC_COL * 20 / 4 ))
}

# 开启新关卡
function New_stage()
{
    while true
    do
        bc_init

        # 万一初始化后就是无法操作的地图,得重新生成
        # 否则就可以继续后续操作了
        if ! Is_stage_over
        then
            break
        fi
    done

    memory_page1=( ${bc_map[@]} )
    memory_page2=( ${bc_map[@]/*/0} )

    Refresh_range_info

    (( ++stage_round ))
    Calcualate_score_goal
    Calcualate_score_shoot

    bonus_stage=$BONUS_STAGE

    invalid_rect=( 0 0 $BC_ROW $BC_COL )
    Refresh
}

# 清算关卡
# 过关奖励 1000 分
# 剩余一个方块扣除 10 分,直至 0 分
function Clear_stage()
{
    local r
    local c

    # 从上到下,从右到左地进行清算
    for (( c = BC_ROW - 1; --c; c >= 0 ))
    do
        for (( r = BC_COL - 1; --r; r >= 0 ))
        do
            # echo "---> array_get memory_page1 BC_COL $r $c)"
            if [ $(array_get memory_page1 BC_COL $r $c) -eq 0 ]
            then
                continue
            fi

            Animation_block_disappear $r $c
            (( bonus_stage -= BONUS_DECREASE ))
            (( bonus_stage = (bonus_stage < 0) ? 0 : bonus_stage ))

            Show_message
            sleep 0.1
        done
    done
}

# 读取最高分数
function Read_top_score()
{
    # 若没有最高分数记录则最高分为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 Save_top_score()
{
    echo $score_top > "$FILE_TOP_SCORE"
}

# 获取整个游戏宽度(游戏区+信息区)
function Game_width()
{
    # 估计信息区最长信息长度
    local estimate=0

    echo $(( MAP_AREA_LEFT + BLOCK_WIDTH * BC_COL + 5 + estimate ))
}

# 获取整个游戏高度(游戏区+信息区)
function Game_height()
{
    # 认为信息区高度不超过游戏区高度
    echo $(( MAP_AREA_TOP + BLOCK_HEIGHT * BC_ROW ))
}

# 于游戏区中央显示信息
# 参数一:信息内容
function Game_tip()
{
    local msg=$1
    # local len=${#msg}
    local top
    local left
    local h_border

    hborder="+--$(echo $msg | sed 's/./-/g')--+"
    msg="|  $msg  |"

    top=$(( $(Game_height) / 2 + 2 ))
    left=$(( $(Game_width) / 2 ))

    tput cup $(( top++ )) $left
    echo -ne "$hborder"
    tput cup $(( top++ )) $left
    echo -ne "$msg"
    tput cup $(( top++ )) $left
    echo -ne "$hborder"
}

# 切换游戏暂停状态
function Game_pause_switch()
{
    # 不过这个游戏不需要暂停,所以不允许用户操作
    return

    (( paused = paused == 1 ? 0 : 1 ))
}

# 开始游戏
function Game_start()
{
    Game_init
    sub_pid=$!
}

# 游戏结束
function Game_over()
{
    # 停止响应用户的游戏输入
    paused=1

    # 于游戏区中央显示游戏结束,按退出键返回
    Game_tip "Game Over! Press '$KEY_EXIT' to quit."
}

# 退出游戏
function Game_exit()
{
    exiting=1
}

# 游戏初始化
function Game_init()
{
    row_cur=0       # 当前行
    col_cur=0       # 当前列
    row_old=0       # 旧行
    col_old=0       # 旧列

    paused=0        # 设置非暂停状态
    exiting=0       # 设置非退出状态

    stage_round=0   # 关卡数初始化

    score=0         # 当前游戏分数
    Read_top_score  # 读取最高分

    limit_cursor_row=$(( BC_ROW - 1 ))              # 光标最大限制行数
    limit_cursor_col=$(( BC_COL - 1 ))              # 光标最大限制列数

    map_area_width=$(( BLOCK_WIDTH * BC_COL ))      # 游戏区域宽
    map_area_height=$(( BLOCK_HEIGHT * BC_ROW ))    # 游戏区域高

    # 如果屏幕大小不够,则提示退出(不考虑信息显示区,因为没有信息提示也一样可以玩)
    if [ $width -lt $(( MAP_AREA_LEFT + map_area_width )) -o $height -lt $(( MAP_AREA_TOP + map_area_height )) ]
    then
        # 停止响应用户的游戏输入
        paused=1

        # 提示错误信息,按退出键返回
        echo "Screen size too small! Press '$KEY_EXIT' to quit."

        return
    fi

    Show_title      # 显示游戏标题
    Show_border     # 显示边框
    New_stage       # 开启新关卡
}

# 主函数
function Main()
{
    Init
    Game_start
    Input
    Exit_clear
}

#-----------------------------------------------------------
# 游戏逻辑部分
# 游戏名:block_clear(消方块)
# 游戏逻辑函数、变量均以 bc 为前缀

# 游戏 map,拟二维数组
declare -a bc_map

# map 的行列
declare -r BC_ROW=8
declare -r BC_COL=8

# map 中的方块类型种类数量,及数字对应的显示字符
declare -r  BC_TYPE_NUM=6
declare -ar BC_IMAGE=(" ${NOR}" "${HIG}O${NOR}" "${HIY}#${NOR}" "${HIR}H${NOR}" "${HIM}M${NOR}" "${HIC}@${NOR}" "${HIW}X${NOR}")
# declare -ar BC_IMAGE=(" ${NOR}" "${BHG} ${NOR}" "${BHIY} ${NOR}" "${BHIR} ${NOR}" "${BHIM} ${NOR}" "${BHIC} ${NOR}" "${BHIW}.${NOR}")
# 测试用的显示字符declare -ar BC_IMAGE=(" ${NOR}" "${HIG}1${NOR}" "${HIY}2${NOR}" "${HIR}3${NOR}" "${HIM}4${NOR}" "${HIC}5${NOR}" "${HIW}6${NOR}")

# 用于存放相连方块的坐标数组
declare -a bc_range_row
declare -a bc_range_col

# bc 初始化
function bc_init()
{
    local i
    local j

    array_init bc_map $BC_ROW $BC_COL
    for (( i = 0; i < BC_ROW; ++i ))
    do
        for (( j = 0; j < BC_COL; ++j ))
        do
            array_set bc_map BC_COL $i $j $(( $(random $BC_TYPE_NUM) + 1 ))
        done
    done
}

# bc 帧逻辑
function bc_frame()
{
    :
}

# bc 数字地图输出
function bc_show_map()
{
    local map=""
    local i

    for (( i = 0; i < BC_ROW; ++i ))
    do
        map="$map\n$(array_get_row bc_map $BC_COL $i)"
    done

    # map=$(echo "$map" | tr $(seq -s "" 0 $BC_TYPE_NUM) "$BC_IMAGE")
    echo "${map}"
}

# 将 bc 数字图渲染为文字图
function bc_rander_map()
{
    local map=""
    local i

    for (( i = 0; i < BC_ROW; ++i ))
    do
        map="$map\n$(array_get_row bc_map $BC_COL $i)"
    done

    # xxx map=$(echo "$map" | tr $(seq -s "" 0 $BC_TYPE_NUM) "${BC_IMAGE[@]}")
    echo "${map}"
}

# 点击事件响应函数
# 参数一:行
# 参数二:列
function bc_click()
{
    local i
    local xxstr
    # array_set bc_map $BC_COL $1 $2 0

    for (( i = 0; i < ${#bc_range_row[@]}; ++i ))
    do
        array_set bc_map $BC_COL ${bc_range_row[$i]} ${bc_range_col[$i]} 0
    done

    # 调整地图
    bc_adjust_map

    # 重新计算区域
    # bc_calcualate_range $1 $2
}

# 调整地图
#   空格上方的方块下落
#   空列右方的方块左移
#   需要调整的区域为根据当前区域计算的 顶行,最小列,顶行,最大列
function bc_adjust_map()
{
    local i
    local j
    local k
    local value
    local bottom_zero_row
    local empty_cols
    local flag_moved

    # echo "bc_range_col: ${bc_range_col[@]}" >> debug.txt
    # 容错判断(本不应该出现的情况,但可能脚本运行速度慢,并发的情况下出现)
    # if [ -z "${bc_range_col[*]}" ]
    # then
    #     return
    # fi

    # 对每一列处理下落
    for (( j = $(min ${bc_range_col[@]}); j <= $(max ${bc_range_col[@]}); ++j ))
    do
        # 从底层往上走,并记录最下一个 0 元素的位置
        # 若发现非 0 元素,则移动到最下一个 0 位置上,并更新最下一个 0 位置的指针
        bottom_zero_row=$(( BC_ROW - 1 ))
        for (( i = $bottom_zero_row; i >= 0; --i ))
        do
            value=$(array_get bc_map BC_COL $i $j)

            # 若此位置为 0,则不处理
            if [ $value -eq 0 ]
            then
                continue
            fi

            # 非 0 元素
            # 判断下方是否有空位置
            #   若无则调整最下非 0 位置指针
            if [ $bottom_zero_row -eq $i ]
            then
                (( --bottom_zero_row ))
                continue
            fi

            # 非 0 元素需要下移
            array_set bc_map BC_COL $bottom_zero_row $j $value
            array_set bc_map BC_COL $i $j 0

            # 非 0 元素
            (( --bottom_zero_row ))
        done
    done

    # 测试是否有空列,若无则返回
    empty_cols=( $(bc_get_empty_col) )
    if [ ${#empty_cols[@]} -eq 0 ]
    then
        return
    fi

    # 有空列,需要移动

    # 空列最后一个元素设置为最大列,用于简化控制循环
    empty_cols=( ${empty_cols[@]} $BC_COL )

    # 得到当前空列
    k=0
    cur_zero_col=${empty_cols[$k]}
    (( ++k ))

    flag_moved=0

    # 处理每一列
    for (( j = cur_zero_col + 1; j < BC_COL; ++j ))
    do
        # 若 j 大于行于下一个 0 列,说明当前处理的列也是空列
        # 则移动 k,跳过当前列处理
        # 上面给 empty_cols 加上最后一个元素就用于这里简化控制,不需要判断 k 是否到达末尾了
        if [ $j -ge ${empty_cols[$k]} ]
        then
            (( ++k ))
            continue
        fi

        # 处理每一行,进行移动
        # 由下自上倒着处理,当遇到 0 时,这一列可以提前结束处理
        for (( i = BC_ROW - 1; i >= 0; --i ))
        do
            # 得到需要移动的数据,若为0,则提前结束
            value=$(array_get bc_map BC_COL $i $j)
            if [ $value -eq 0 ]
            then
                break
            fi

            # 移动
            array_set bc_map BC_COL $i $cur_zero_col $value

            # 当前处理列本来需要设置为 0 的,在最后的统一处理,这里略过
        done

        # 此列处理完后,下一列就做为当前 0 列
        (( ++ cur_zero_col ))

        # 标记有移动过
        flag_moved=1
    done

    # 若没有移动过,则不需要做置 0 处理
    if [ $flag_moved -ne 1 ]
    then
        return
    fi

    # 对当前 0 列之后的列做置 0 处理
    for (( j = cur_zero_col; j < BC_COL; ++j ))
    do
        # 处理每一行
        # 由下自上倒着处理,当遇到 0 时,这一列可以提前结束处理
        for (( i = BC_ROW - 1; i >= 0; --i ))
        do
            # 得到需要移动的数据,若为0,则提前结束
            value=$(array_get bc_map BC_COL $i $j)
            if [ $value -eq 0 ]
            then
                break
            fi

            # 移动
            array_set bc_map BC_COL $i $j 0
        done
    done
}

# 测试是否需要移动操作
# 返回所有的空列的列号组成的字串,以空格分隔,可以使用 var=( $(function) ) 方式得到结果
# 判断结果长度来确定是否有空列及是否需要移动
function bc_get_empty_col()
{
    # 在此,只需要判断最底列是否有 0 值即可,并不需要真的判断空列

    local bottom_row
    local j
    local ret

    ret=""
    (( bottom_row = BC_ROW - 1 ))
    for (( j = 0; j < BC_COL; ++j ))
    do
        if [ $(array_get bc_map BC_COL $bottom_row $j) -ne 0 ]
        then
            continue
        fi

        ret="$ret $j"
    done

    echo "$ret"
}

# 计算输入位置所示广场相连的方块坐标,结果保存于数组中备用
# 参数一:当前行
# 参数二:当前列
# local_map_temp 为本函数内部临时使用的一个数组
declare -a local_map_temp
function bc_calcualate_range()
{
    local cur_row=$1
    local cur_col=$2
    local queue_tail
    local queue_head
    local i
    local j
    local value=$(array_get bc_map BC_COL $cur_row $cur_col)

    # bc_range_row=( )
    # bc_range_col=( )
    #
    # bc_range_row[0]=$cur_row
    # bc_range_col[0]=$cur_col
    # 换成这样
    bc_range_row=( $cur_row )
    bc_range_col=( $cur_col )
    queue_tail=0
    queue_head=0

    if [ $value -eq 0 ]
    then
        return
    fi

    local_map_temp=( ${bc_map[@]} )
    array_set local_map_temp BC_COL $cur_row $cur_col 0

    while true
    do
        if [ $queue_head -gt $queue_tail ]
        then
            break
        fi

        cur_row=${bc_range_row[$queue_head]}
        cur_col=${bc_range_col[$queue_head]}
        # array_set local_map_temp BC_COL $cur_row $cur_col $(( BC_TYPE_NUM + 1 ))
        # array_set local_map_temp BC_COL $cur_row $cur_col 0
        (( ++queue_head ))

        # 朝上找
        if [ $(( cur_row - 1 )) -ge 0       ] && [ $value -eq $(array_get local_map_temp BC_COL $(( cur_row - 1 )) $cur_col) ]
        then
            (( ++queue_tail ))
            bc_range_row[queue_tail]=$(( cur_row - 1 ))
            bc_range_col[queue_tail]=$cur_col

            # 标记访问过
            array_set local_map_temp BC_COL ${bc_range_row[queue_tail]} ${bc_range_col[queue_tail]} 0
        fi

        # 朝下找
        if [ $(( cur_row + 1 )) -lt $BC_ROW ] && [ $value -eq $(array_get local_map_temp BC_COL $(( cur_row + 1 )) $cur_col) ]
        then
            (( ++queue_tail ))
            bc_range_row[queue_tail]=$(( cur_row + 1 ))
            bc_range_col[queue_tail]=$cur_col

            # 标记访问过
            array_set local_map_temp BC_COL ${bc_range_row[queue_tail]} ${bc_range_col[queue_tail]} 0
        fi

        # 朝左找
        if [ $(( cur_col - 1 )) -ge 0       ] && [ $value -eq $(array_get local_map_temp BC_COL $cur_row $(( cur_col - 1 )) ) ]
        then
            (( ++queue_tail ))
            bc_range_row[queue_tail]=$cur_row
            bc_range_col[queue_tail]=$(( cur_col - 1 ))

            # 标记访问过
            array_set local_map_temp BC_COL ${bc_range_row[queue_tail]} ${bc_range_col[queue_tail]} 0
        fi

        # 朝右找
        if [ $(( cur_col + 1 )) -lt $BC_COL ] && [ $value -eq $(array_get local_map_temp BC_COL $cur_row $(( cur_col + 1 )) ) ]
        then
            (( ++queue_tail ))
            bc_range_row[queue_tail]=$cur_row
            bc_range_col[queue_tail]=$(( cur_col + 1 ))

            # 标记访问过
            array_set local_map_temp BC_COL ${bc_range_row[queue_tail]} ${bc_range_col[queue_tail]} 0
        fi
    done
}

# 判断是否结束一轮
# 返回 0 说明已结束   (true)
# 返回 1 说明还未结束 (false)
function bc_check_stage_over()
{
    local i
    local j
    local range_row_bak
    local range_col_bak

    # 备份以备后续恢复
    range_row_bak=( ${bc_range_row[@]} )
    range_col_bak=( ${bc_range_col[@]} )

    for (( i = 0; i < BC_ROW; ++i ))
    do
        for (( j = 0; j < BC_COL; ++j ))
        do
            bc_calcualate_range $i $j

            # 若某一个块的连接区域大于等于 2 块,说明还可以进行游戏
            if [ ${#bc_range_row[@]} -ge 2 ]
            then
                bc_range_row=( ${range_row_bak[@]} )
                bc_range_col=( ${range_col_bak[@]} )

                return 1
            fi
        done
    done

    bc_range_row=( ${range_row_bak[@]} )
    bc_range_col=( ${range_col_bak[@]} )

    return 0
}

#-----------------------------------------------------------
# 执行

# Main 2>> err.txt
Main

后记
这没有二维数组,做这种小游戏真是一种折磨啊。写着写着这脚本就写的乱了,大家见笑。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值