前言
做一个小游戏练习 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

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

Shell 仿消灭星星游戏(2013-03-15)相关推荐

  1. 使用C语言+EasyX完成消灭星星游戏(2)

    使用C语言+EasyX完成消灭星星游戏(2) 上一篇简单介绍一下项目和创建游戏界面 本篇介绍如何达到消除方块的功能.具体思路,代码都有详细注释. 下一篇消除同色方块后其他方块的下落. #include ...

  2. 使用C语言+EasyX完成消灭星星游戏(1)

    使用C语言+EasyX完成消灭星星游戏(1) 给大家介绍一个自己做的消灭星星小游戏项目,主要是基于C语言+EasyX实现,我使用的是vs2017编写.项目实现登陆,注册,游戏基本的玩法等功能. 项目展 ...

  3. 使用C语言+EasyX完成消灭星星游戏(3)

    使用C语言+EasyX完成消灭星星游戏(3) 本篇介绍方块消除后,方块下落移动. #include<stdio.h> #include<graphics.h> #include ...

  4. PopStar(消灭星星)游戏源代码下载、分析及跨平台移植---第一篇(界面)

    背景: 来自星星的你电视剧很火,消灭星星游戏也很火,好像星星都很火,笔者就以星星为主题开始这篇博文.消除类的游戏挺受欢迎的,从2013年度app store最赚钱的游戏--粉碎糖果传奇,到总是可以在游 ...

  5. 纯js 消灭星星游戏,js 消灭星星游戏实现原理,有道具的消灭星星

    消灭星星游戏的几个核心逻辑 用10*10的数组nums保存星星,1-5表示有星星,0表示已经消去 1.初始化,5种颜色的星星分配. 1-5 这个最容易,随机分配就好,产生1-100的随机数num,nu ...

  6. PopStar(消灭星星)游戏源代码下载、分析及跨平台移植—第一篇(界面)

    背景: 来自星星的你电视剧很火,消灭星星游戏也很火,好像星星都很火,笔者就以星星为主题开始这篇博文.消除类的游戏挺受欢迎的,从2013年度app store最赚钱的游戏–粉碎糖果传奇,到总是可以在游戏 ...

  7. PopStar(消灭星星)游戏源代码下载、分析及跨平台移植---第三篇(分数)

    背景: 经过消灭星星第二篇算法,最高的山峰已经过去了,剩下的都是小沟小河,没什么难度了.这一节笔者继续完成消灭星星的分数篇,这节主要包括:触摸提示得分 比如4 blocks 80 points,然后产 ...

  8. PopStar(消灭星星)游戏源代码下载、分析及跨平台移植---第四篇(关卡)

    背景: 本来打算把第三篇和第四篇合并都一起,但以前计划分开,就还是分来吧:一般的游戏涉及到关卡的话,一般都会建立一个数组来存放各种定义参数,消灭星星关卡比较容易,不需要建立数组,只有两个参数level ...

  9. PopStar(消灭星星)游戏源代码下载、分析及跨平台移植---第二篇(算法)

    背景: 上一节,我们已经把消灭星星的界面搭建好了,流程也跑通了. 这一篇涉及到程序的算法,也许是最难的部分了,理解起来需要多花点时间,而且我提供的算法未必就是最好的,如果读者有更优更好的算法,希望分享 ...

最新文章

  1. Openresty最佳案例 | 第1篇:Nginx介绍
  2. Java中的单例模式
  3. 使用SIFT特征提取和K-Means方法对图片进行分类
  4. 题目: javaweb前端素材管理系统(附免费下载源码链接)
  5. 网络知识:DNS 访问原理详解
  6. 好好的活,简简单单过!
  7. wordcloud python3.7_[原创]win7/64位系统+python3.7.2下安装wordcloud库失败之解决——一个莫名其妙的方法...
  8. PHP AJAXFORM提交图片上传并显示图片源代码
  9. JS基础--子类型重写超类型方法原型链图解
  10. 旅游网站进行邮件订阅的七大步骤讲解
  11. java ing印版_Java TCP实现高仿版QQ聊天(二)
  12. asp.net 通过IHttpHandler开发接口
  13. Java基础篇:如何使用圆括号
  14. linux alias命令
  15. java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversal
  16. Atitit atitit 编程语言之道补充 s22 attilax 艾龙 著 1. 程序设计是个什么概念呢?历史发展 1 1.1. 连接电缆 2 1.2. 程序内置纸带打点 2 1.3. FORT
  17. VS Code 取色器 插件 颜色选取
  18. sql插入多条记录_如何在SQL中插入多条记录
  19. 机器学习——LBP特征
  20. c语言专业面试问题,c语言面试问题(共3篇).doc

热门文章

  1. windows xp/2003 自动登陆
  2. SpringBoot 学习笔记
  3. ng2 体验报告、总结
  4. 深度神经网络模型训练中的 tricks(原理与代码汇总)
  5. How to access SMTP/POP server using telnet - 用 Telnet 如何访问邮件服务器
  6. HelloCode:少儿编程可以和其他学科相融合吗?
  7. python去除空格trim,Python去除字符串前后空格的几种方法
  8. 无题(2011.9.28)
  9. PnP算法详解(超详细公式推导)
  10. 汽修学徒一般学多久能出师