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

1510阅读 0评论2013-08-11 樊聪5201314
分类:LINUX

前言


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

正文

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

截图如下:

代码

颜色代码头文件 colors.sh

点击(此处)折叠或打开

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

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

  9. # 前景色
  10. BLK=$ESC"[30m"
  11. RED=$ESC"[31m"
  12. GRN=$ESC"[32m"
  13. YEL=$ESC"[33m"
  14. BLU=$ESC"[34m"
  15. MAG=$ESC"[35m"
  16. CYN=$ESC"[36m"
  17. WHT=$ESC"[37m"

  18. # 高亮前景色
  19. HIR=$ESC"[1;31m"
  20. HIG=$ESC"[1;32m"
  21. HIY=$ESC"[1;33m"
  22. HIB=$ESC"[1;34m"
  23. HIM=$ESC"[1;35m"
  24. HIC=$ESC"[1;36m"
  25. HIW=$ESC"[1;37m"

  26. # 背景色
  27. BBLK=$ESC"[40m"
  28. BRED=$ESC"[41m"
  29. BGRN=$ESC"[42m"
  30. BYEL=$ESC"[43m"
  31. BBLU=$ESC"[44m"
  32. BMAG=$ESC"[45m"
  33. BCYN=$ESC"[46m"
  34. BWHT=$ESC"[47m"

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

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

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

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

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

主程序源文件 aircraft.sh

点击(此处)折叠或打开

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

  16. source colors.sh

  17. # ============================================================================
  18. # 全局配置
  19. # ============================================================================

  20. # 响应的信号
  21. declare -r SIG_UP=SIGRTMIN+1
  22. declare -r SIG_DOWN=SIGRTMIN+2
  23. declare -r SIG_LEFT=SIGRTMIN+3
  24. declare -r SIG_RIGHT=SIGRTMIN+4
  25. declare -r SIG_SHOOT=SIGRTMIN+5
  26. declare -r SIG_PAUSE=SIGRTMIN+6
  27. declare -r SIG_EXIT=SIGRTMIN+7

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

  36. # 游戏区域位置大小
  37. declare -r GAME_AREA_TOP=10
  38. declare -r GAME_AREA_LEFT=30
  39. declare -r GAME_AREA_WIDTH=43
  40. declare -r GAME_AREA_HEIGHT=33

  41. # 标题位置
  42. declare -r TITLE_POS_LEFT=22
  43. declare -r TITLE_POS_TOP=2

  44. # 信息显示位置
  45. declare -r MSG_POS_TOP=$(( GAME_AREA_TOP + GAME_AREA_HEIGHT - 20 ))
  46. declare -r MSG_POS_LEFT=$(( GAME_AREA_LEFT + GAME_AREA_WIDTH + 10 ))
  47. declare -r MSG_SCORE_TOP=$(( MSG_POS_TOP + 1 ))
  48. declare -r MSG_SCORE_LEFT=$(( MSG_POS_LEFT + 16 ))
  49. declare -r MSG_LEVEL_TOP=$(( MSG_POS_TOP + 2 ))
  50. declare -r MSG_LEVEL_LEFT=$MSG_SCORE_LEFT
  51. declare -r MSG_BULLET_TOP=$(( MSG_POS_TOP + 3 ))
  52. declare -r MSG_BULLET_LEFT=$MSG_SCORE_LEFT
  53. declare -r MSG_TOP_SCORE_TOP=$(( MSG_POS_TOP + 4 ))
  54. declare -r MSG_TOP_SCORE_LEFT=$MSG_SCORE_LEFT

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

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

  60. # ============================================================================
  61. # 全局常量
  62. # ============================================================================

  63. # 玩家图标 敌机图标
  64. # A -+-
  65. # -=#=- -=#=-
  66. # -+- V
  67. declare -r player_width=5 # 玩家图标的宽
  68. declare -r player_height=3 # 玩家图标的高
  69. declare -r player_gun_offset_c=$(( (player_width - 1) / 2 - 1 )) # 玩家枪炮的相对于坐标的偏移
  70. declare -r player_gun_offset_r=-2 # 玩家枪炮的相对于坐标的偏移
  71. declare -r enemy_width=5 # 敌机图标的宽
  72. declare -r enemy_height=3 # 敌机图标的高

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

  74. # 各种不同风格的星星集合
  75. 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}" )

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

  78. # ============================================================================
  79. # 全局变量
  80. # ============================================================================

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

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

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

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

  91. declare pid_loop # 消息循环的进程pid

  92. declare screen_width # 屏宽
  93. declare screen_height # 屏高

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

  98. declare range_player_r_min # 玩家可移动位置的最小行
  99. declare range_player_r_max # 玩家可移动位置的最大行
  100. declare range_player_c_min # 玩家可移动位置的最小列
  101. declare range_player_c_max # 玩家可移动位置的最大列

  102. declare range_enemy_r_min # 敌机可移动位置的最小行
  103. declare range_enemy_r_max # 敌机可移动位置的最大行
  104. declare range_enemy_c_min # 敌机可移动位置的最小列
  105. declare range_enemy_c_max # 敌机可移动位置的最大列

  106. declare pos_player_r # 当前玩家坐标:行
  107. declare pos_player_c # 当前玩家坐标:列
  108. declare old_player_r # 先前玩家坐标:行
  109. declare old_player_c # 先前玩家坐标:列

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

  115. # ============================================================================
  116. # 函数定义
  117. # ============================================================================

  118. # ----------------------------------------------------------------------------
  119. # 通用函数
  120. # ----------------------------------------------------------------------------

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

  127. # ----------------------------------------------------------------------------
  128. # 游戏框架函数
  129. # ----------------------------------------------------------------------------

  130. # 键盘输入响应函数
  131. function Input()
  132. {
  133.     while true
  134.     do
  135.         read -s -n 1 -a key
  136.         key="${key[@]: -1}"
  137.         case $key in
  138.             $KEY_UP) sign=$SIG_UP ;;
  139.             $KEY_DOWN) sign=$SIG_DOWN ;;
  140.             $KEY_LEFT) sign=$SIG_LEFT ;;
  141.             $KEY_RIGHT) sign=$SIG_RIGHT ;;
  142.             $KEY_SHOOT) sign=$SIG_SHOOT ;;
  143.             $KEY_PAUSE) sign=$SIG_PAUSE ;;
  144.             $KEY_EXIT) sign=$SIG_EXIT ;;
  145.             *) continue ;;
  146.         esac

  147.         kill -s $sign $pid_loop

  148.         # 若是退出按键,则根据游戏循环是否存在来判断是否确认退出
  149.         if (( sign == SIG_EXIT ))
  150.         then
  151.             sleep 0.1
  152.             if ! ps -p $pid_loop > /dev/null
  153.             then
  154.                 break
  155.             fi
  156.         fi
  157.     done
  158. }

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

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

  169.     # 输入线程的暂停处理
  170.     if (( game_overed && sign == SIG_PAUSE ))
  171.     then
  172.         return
  173.     fi

  174.     case $sign in
  175.         $SIG_UP) OnPressPlayerMove "U" ;;
  176.         $SIG_DOWN) OnPressPlayerMove "D" ;;
  177.         $SIG_LEFT) OnPressPlayerMove "L" ;;
  178.         $SIG_RIGHT) OnPressPlayerMove "R" ;;
  179.         $SIG_SHOOT) OnPressShoot ;;
  180.         $SIG_PAUSE) OnPressGamePause ;;
  181.         $SIG_EXIT) OnPressGameExit ;;
  182.     esac
  183. }

  184. # 系统初始化
  185. function Init()
  186. {
  187.     width_max=$(( GAME_AREA_LEFT + GAME_AREA_WIDTH ))
  188.     height_max=$(( GAME_AREA_TOP + GAME_AREA_HEIGHT ))
  189.     width_min=$GAME_AREA_LEFT
  190.     height_min=$GAME_AREA_TOP

  191.     screen_width=$(tput cols)
  192.     screen_height=$(tput lines)

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

  198.     range_player_r_min=$height_min
  199.     range_player_r_max=$(( height_max - player_height ))
  200.     range_player_c_min=$width_min
  201.     range_player_c_max=$(( width_max - player_width ))

  202.     range_enemy_r_min=$height_min
  203.     range_enemy_r_max=$(( height_max - enemy_height ))
  204.     range_enemy_c_min=$width_min
  205.     range_enemy_c_max=$(( width_max - enemy_width ))

  206.     game_paused=0
  207.     game_overed=0

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

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

  217. # 判断一个矩形物件是否在游戏区域内
  218. # 参数:行、列、高、宽
  219. function IsInGameArea()
  220. {
  221.     local r c h w
  222.     local r_min r_max c_min c_max
  223.     r=$1
  224.     c=$2
  225.     h=$3
  226.     w=$4
  227.     r_min=$height_min
  228.     r_max=$(( height_max - h ))
  229.     c_min=$width_min
  230.     c_max=$(( width_max - w ))

  231.     if [ $r -le $r_min -o $r -ge $r_max -o $c -le $c_min -o $c -ge $c_max ]
  232.     then
  233.         return 1
  234.     fi

  235.     return 0
  236. }

  237. # 游戏初始化
  238. function GameInit()
  239. {
  240.     # 设定输入响应函数
  241.     # trap Action $SIG_UP $SIG_DOWN $SIG_LEFT $SIG_RIGHT $SIG_PAUSE $SIG_SHOOT $SIG_EXIT
  242.     trap "Action $SIG_UP" $SIG_UP
  243.     trap "Action $SIG_DOWN" $SIG_DOWN
  244.     trap "Action $SIG_LEFT" $SIG_LEFT
  245.     trap "Action $SIG_RIGHT" $SIG_RIGHT
  246.     trap "Action $SIG_PAUSE" $SIG_PAUSE
  247.     trap "Action $SIG_SHOOT" $SIG_SHOOT
  248.     trap "Action $SIG_EXIT" $SIG_EXIT

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

  251.     old_player_r=$pos_player_r
  252.     old_player_c=$pos_player_c

  253.     game_paused=0
  254.     game_overed=0

  255.     TopScoreRead
  256.     TitleShow
  257.     DrawBorder
  258.     MessageShow
  259.     PlayerMove # 不带参数,就会根据当前位置刷新一下玩家
  260. }

  261. # 游戏循环
  262. function GameLoop()
  263. {
  264.     GameInit

  265.     while true
  266.     do
  267.         if (( game_exit_confirmed ))
  268.         then
  269.             break
  270.         fi

  271.         FrameAction
  272.         sleep 0.04 # 每秒25帧,sleep 0.04
  273.     done
  274. }

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

  280.     return $(( ! game_paused ))
  281. }

  282. # 启动游戏
  283. function GameStart()
  284. {
  285.     GameLoop &
  286.     pid_loop=$!
  287. }

  288. # 游戏结束
  289. function GameOver()
  290. {
  291.     game_paused=1
  292.     game_overed=1
  293.     TipShow "Game Over! Press $KEY_EXIT to exit."
  294. }

  295. # 退出游戏
  296. function GameExit()
  297. {
  298.     game_exit_confirmed=1
  299. }

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

  305.     # 恢复stty配置
  306.     stty $stty_save
  307.     tput cnorm

  308.     clear
  309. }

  310. # 绘制边界
  311. function DrawBorder()
  312. {
  313.     local i
  314.     local border_h
  315.     local border_v
  316.     local r
  317.     local c
  318.     local c2

  319.     border_h=""
  320.     for (( i = 0; i < GAME_AREA_WIDTH + 1; ++i ))
  321.     do
  322.         border_h="$border_h$BORDER_H"
  323.     done

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

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

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

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

  347.     ar=$1
  348.     ar_old=$2
  349.     h=${3:-1}
  350.     w=${4:-1}

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

  353.     # 更新当前位置
  354.     flag=0
  355.     eval "count=\${#$ar[@]}"
  356.     for (( i = 0; i < count; ++i ))
  357.     do
  358.         eval "pos=( \${$ar[$i]} )"
  359.         r=$(( ${pos[0]} + ${pos[2]} ))
  360.         c=$(( ${pos[1]} + ${pos[3]} ))

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

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

  377. # 读取最高分数
  378. function TopScoreRead()
  379. {
  380.     # 若没有最高分数记录则最高分为0
  381.     if [ ! -f "$FILE_TOP_SCORE" ]
  382.     then
  383.         score_top=0
  384.         return
  385.     fi

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

  393. # 保存最高分数
  394. function TopScoreSave()
  395. {
  396.     echo $score_top > "$FILE_TOP_SCORE"
  397. }

  398. # 更新最高分
  399. # 最高分更改了返回真,没有更改返回假
  400. function TopScoreUpdate()
  401. {
  402.     if (( score < score_top ))
  403.     then
  404.         return 1
  405.     fi

  406.     score_top=$score
  407.     return 0
  408. }

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

  414. # 标题显示
  415. function TitleShow()
  416. {
  417.     local r c
  418.     r=$TITLE_POS_TOP
  419.     c=$TITLE_POS_LEFT

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

  426. # 显示游戏信息
  427. function MessageShow()
  428. {
  429.     local r c

  430.     r=$MSG_POS_TOP
  431.     c=$MSG_POS_LEFT

  432.     echo -ne "${ESC}[${r};${c}HInfomation" ; (( ++r ))
  433.     echo -ne "${ESC}[${r};${c}H Score : $score " ; (( ++r ))
  434.     echo -ne "${ESC}[${r};${c}H Level : $level " ; (( ++r ))
  435.     echo -ne "${ESC}[${r};${c}H Bullet : $bullet_num " ; (( ++r ))
  436.     echo -ne "${ESC}[${r};${c}H Top Score : $score_top " ; (( ++r ))
  437.     (( ++r ))
  438.     (( ++r ))
  439.     echo -ne "${ESC}[${r};${c}HOperation" ; (( ++r ))
  440.     echo -ne "${ESC}[${r};${c}H Up : $KEY_UP" ; (( ++r ))
  441.     echo -ne "${ESC}[${r};${c}H Down : $KEY_DOWN" ; (( ++r ))
  442.     echo -ne "${ESC}[${r};${c}H Left : $KEY_LEFT" ; (( ++r ))
  443.     echo -ne "${ESC}[${r};${c}H Right : $KEY_RIGHT" ; (( ++r ))
  444.     echo -ne "${ESC}[${r};${c}H Shoot : $KEY_SHOOT" ; (( ++r ))
  445.     echo -ne "${ESC}[${r};${c}H Pause : $KEY_PAUSE" ; (( ++r ))
  446.     echo -ne "${ESC}[${r};${c}H Quit : $KEY_EXIT" ; (( ++r ))
  447.     (( ++r ))
  448.     (( ++r ))
  449.     echo -ne "${ESC}[${r};${c}HIntroduction" ; (( ++r ))
  450.     echo -ne "${ESC}[${r};${c}H 1. 1 point score per enemy" ; (( ++r ))
  451.     echo -ne "${ESC}[${r};${c}H 2. Level up every 10 points score" ; (( ++r ))
  452.     echo -ne "${ESC}[${r};${c}H 3. Level up to increase bullet number"
  453. }

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

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

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

  469.     echo -ne "${ESC}[${r};${c}H$border_h" ; (( ++r ))
  470.     echo -ne "${ESC}[${r};${c}H$msg" ; (( ++r ))
  471.     echo -ne "${ESC}[${r};${c}H$border_h"
  472. }

  473. # 清除提示
  474. function TipClear()
  475. {
  476.     local r c
  477.     local empty_line
  478.     local len
  479.     local tip_lines=3

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

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

  484.     echo -ne "${ESC}[${r};${c}H$empty_line" ; (( ++r ))
  485.     echo -ne "${ESC}[${r};${c}H$empty_line" ; (( ++r ))
  486.     echo -ne "${ESC}[${r};${c}H$empty_line"
  487. }

  488. # ----------------------------------------------------------------------------
  489. # 子弹处理
  490. # ----------------------------------------------------------------------------

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

  501.     # 若弹药用光了,则不能发射
  502.     if IsBulletUsedUp
  503.     then
  504.         return
  505.     fi

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

  509. # 判断弹药是否用光
  510. function IsBulletUsedUp()
  511. {
  512.     if [ $bullet_num -le 0 ]
  513.     then
  514.         return 0
  515.     fi

  516.     return 1
  517. }

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

  523. # 子弹刷新
  524. function BulletRefresh()
  525. {
  526.     BulletMove
  527.     BulletDisplay

  528.     BulletNumUpdate
  529.     BulletNumRefresh
  530. }

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

  536.     # 以下为原内容,想想要做成一个通用的函数来替换才行,于是有了 ListMove
  537.     # local i flag pos
  538.     # local r c
  539.     #
  540.     # # 记录旧位置
  541.     # ar_old_pos_bullet=( "${ar_pos_bullet[@]}" )
  542.     #
  543.     # # 更新当前位置
  544.     # flag=0
  545.     # count=${#ar_pos_bullet[@]}
  546.     # for (( i = 0; i < count; ++i ))
  547.     # do
  548.     # pos=( ${ar_pos_bullet[$i]} )
  549.     # r=$(( ${pos[0]} + ${pos[2]} ))
  550.     # c=$(( ${pos[1]} + ${pos[3]} ))
  551.     #
  552.     # if [ $r -le $height_min -o $r -ge $height_max -o $c -le $width_min -o $c -ge $width_max ]
  553.     # then
  554.     # unset ar_pos_bullet[$i]
  555.     # flag=1
  556.     # else
  557.     # ar_pos_bullet[$i]="$r $c ${pos[2]} ${pos[3]}"
  558.     # fi
  559.     # done
  560.     #
  561.     # # 如果有删除元素,则要重组数组,以便下标连续
  562.     # if [ $flag -eq 1 ]
  563.     # then
  564.     # ar_pos_bullet=( "${ar_pos_bullet[@]}" )
  565.     # fi
  566. }

  567. # 显示所有的子弹
  568. function BulletDisplay()
  569. {
  570.     local pos c r

  571.     for pos in "${ar_old_pos_bullet[@]}"
  572.     do
  573.         pos=( ${pos[@]} )
  574.         r=${pos[0]}
  575.         c=${pos[1]}
  576.         BulletClear $r $c
  577.     done

  578.     for pos in "${ar_pos_bullet[@]}"
  579.     do
  580.         pos=( ${pos[@]} )
  581.         r=${pos[0]}
  582.         c=${pos[1]}
  583.         BulletPut $r $c
  584.     done
  585. }

  586. # 显示一个子弹
  587. function BulletPut()
  588. {
  589.     tput cup $1 $2
  590.     echo -n "!"
  591. }

  592. # 清除一个子弹
  593. function BulletClear()
  594. {
  595.     tput cup $1 $2
  596.     echo -n " "
  597. }

  598. # ----------------------------------------------------------------------------
  599. # 玩家处理
  600. # ----------------------------------------------------------------------------

  601. # 玩家移动
  602. # $1: U D L R = up down left right
  603. function PlayerMove()
  604. {
  605.     case "$1" in
  606.         'U') (( pos_player_r = (pos_player_r - 1 <= range_player_r_min ? range_player_r_min : pos_player_r - 1) )) ;;
  607.         'D') (( pos_player_r = (pos_player_r + 1 >= range_player_r_max ? range_player_r_max : pos_player_r + 1) )) ;;
  608.         'L') (( pos_player_c = (pos_player_c - 1 <= range_player_c_min ? range_player_c_min : pos_player_c - 1) )) ;;
  609.         'R') (( pos_player_c = (pos_player_c + 1 >= range_player_c_max ? range_player_c_max : pos_player_c + 1) )) ;;
  610.     esac

  611.     PlayerPosUpdate $pos_player_r $pos_player_c
  612. }

  613. # 玩家位置数据更新
  614. function PlayerPosUpdate()
  615. {
  616.     pos_player_r=$1
  617.     pos_player_c=$2
  618. }

  619. # 玩家图像刷新
  620. function PlayerDraw()
  621. {
  622.     PlayerClear $old_player_r $old_player_c
  623.     PlayerPut $pos_player_r $pos_player_c

  624.     old_player_r=$pos_player_r
  625.     old_player_c=$pos_player_c
  626. }

  627. # 传入坐标,放置玩家
  628. function PlayerPut()
  629. {
  630.     local r c
  631.     r=$1
  632.     c=$2

  633.     echo -ne "${ESC}[${r};${c}H A${NOR}"
  634.     (( ++r ))
  635.     echo -ne "${ESC}[${r};${c}H-=#=-${NOR}"
  636.     (( ++r ))
  637.     echo -ne "${ESC}[${r};${c}H -+-${NOR}"
  638. }

  639. # 传入坐标,清除玩家
  640. function PlayerClear()
  641. {
  642.     local r c
  643.     r=$1
  644.     c=$2

  645.     echo -ne "${ESC}[${r};${c}H "
  646.     (( ++r ))
  647.     echo -ne "${ESC}[${r};${c}H "
  648.     (( ++r ))
  649.     echo -ne "${ESC}[${r};${c}H "
  650. }

  651. # ----------------------------------------------------------------------------
  652. # 敌机处理
  653. # ----------------------------------------------------------------------------

  654. # 敌机随机生成
  655. function EnemyRandomGen()
  656. {
  657.     local r c
  658.     local speed_v speed_h
  659.     local rand_range
  660.     local color_index color_num

  661.     # 最小 5% 几率增加敌机,等级升高一级增加 5% 几率
  662.     if (( level < enemy_random_range_max ))
  663.     then
  664.         rand_range=$(( enemy_random_range_max - level ))
  665.     else
  666.         rand_range=1
  667.     fi

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

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

  679.     EnemyAdd $r $c $speed_v $speed_h $color_index
  680. }

  681. # 敌机列表中加入一个敌机
  682. function EnemyAdd()
  683. {
  684.     # 只在游戏区域范围内的才加入,不在的就不处理
  685.     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 ]
  686.     then
  687.         return
  688.     fi

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

  692. # 敌机刷新
  693. function EnemyRefresh()
  694. {
  695.     EnemyMove
  696.     EnemyDisplay
  697. }

  698. # 移动所有的敌机
  699. function EnemyMove()
  700. {
  701.     ListMove ar_pos_enemy ar_old_pos_enemy $enemy_height $enemy_width
  702. }

  703. # 显示所有的敌机
  704. function EnemyDisplay()
  705. {
  706.     local pos c r color_index
  707.     for pos in "${ar_old_pos_enemy[@]}"
  708.     do
  709.         pos=( ${pos[@]} )
  710.         r=${pos[0]}
  711.         c=${pos[1]}
  712.         EnemyClear $r $c
  713.     done

  714.     for pos in "${ar_pos_enemy[@]}"
  715.     do
  716.         pos=( ${pos[@]} )
  717.         r=${pos[0]}
  718.         c=${pos[1]}
  719.         color_index=${pos[4]}
  720.         EnemyPut $r $c $color_index
  721.     done
  722. }

  723. # 传入坐标,放置敌机
  724. function EnemyPut()
  725. {
  726.     local r c color
  727.     r=$1
  728.     c=$2
  729.     color=${ar_enemy_color[$3]}

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

  734. # 传入坐标,清除敌机
  735. function EnemyClear()
  736. {
  737.     local r c
  738.     r=$1
  739.     c=$2

  740.     echo -ne "${ESC}[${r};${c}H " ; (( ++r ))
  741.     echo -ne "${ESC}[${r};${c}H " ; (( ++r ))
  742.     echo -ne "${ESC}[${r};${c}H "
  743. }

  744. # ----------------------------------------------------------------------------
  745. # 背景处理
  746. # ----------------------------------------------------------------------------

  747. # 背景星星处理线程
  748. function StarRandomGen()
  749. {
  750.     local r c
  751.     local speed_v speed_h
  752.     local style style_num

  753.     # 80% 机率增加星星
  754.     if [ $(random 5) -ne 0 ]
  755.     then
  756.         return
  757.     fi

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

  764.     StarAdd $r $c $speed_v $speed_h $style
  765. }

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

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

  778. # 星星刷新
  779. function StarRefresh()
  780. {
  781.     StarMove
  782.     StarDisplay
  783. }

  784. # 移动所有的星星
  785. function StarMove()
  786. {
  787.     ListMove ar_pos_star ar_old_pos_star
  788. }

  789. # 显示所有的敌机
  790. function StarDisplay()
  791. {
  792.     local pos c r
  793.     for pos in "${ar_old_pos_star[@]}"
  794.     do
  795.         pos=( ${pos[@]} )
  796.         r=${pos[0]}
  797.         c=${pos[1]}
  798.         StarClear $r $c
  799.     done

  800.     for pos in "${ar_pos_star[@]}"
  801.     do
  802.         pos=( ${pos[@]} )
  803.         r=${pos[0]}
  804.         c=${pos[1]}
  805.         StarPut $r $c "${pos[4]}"
  806.     done
  807. }

  808. # 传入坐标和风格,绘置星星
  809. function StarPut()
  810. {
  811.     local r c star
  812.     r=$1
  813.     c=$2
  814.     star=${ar_star_style[$3]}

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

  817. # 传入坐标,清除敌机
  818. function StarClear()
  819. {
  820.     local r c
  821.     r=$1
  822.     c=$2

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

  825. # ----------------------------------------------------------------------------
  826. # 计分与升级处理
  827. # ----------------------------------------------------------------------------

  828. # 增加分数
  829. # 参数一:增加的分数值,缺省为 1
  830. function ScoreIncrease()
  831. {
  832.     (( score += ${1:-1} ))
  833.     ScoreRefresh

  834.     if (( score / 10 + 1 > level ))
  835.     then
  836.         LevelUp
  837.     fi

  838.     if TopScoreUpdate
  839.     then
  840.         TopScoreSave
  841.         TopScoreRefresh
  842.     fi
  843. }

  844. # 分数刷新
  845. function ScoreRefresh()
  846. {
  847.     echo -ne "${ESC}[${MSG_SCORE_TOP};${MSG_SCORE_LEFT}H$score "
  848. }

  849. # 升级
  850. # 参数一:升级数值,缺省为 1
  851. function LevelUp()
  852. {
  853.     (( level += ${1:-1} ))
  854.     LevelRefresh

  855.     # 升级增加子弹最大数量
  856.     bullet_num_max=$level
  857.     BulletNumUpdate
  858.     BulletNumRefresh
  859. }

  860. # 等级刷新
  861. function LevelRefresh()
  862. {
  863.     echo -ne "${ESC}[${MSG_LEVEL_TOP};${MSG_LEVEL_LEFT}H$level "
  864. }

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

  870. # ----------------------------------------------------------------------------
  871. # 帧处理
  872. # ----------------------------------------------------------------------------

  873. # 帧动作
  874. declare frame_count=0 # 全局变量代替静态变量的作用
  875. function FrameAction()
  876. {
  877.     # 若游戏暂停,则不进行帧动作
  878.     if (( game_paused ))
  879.     then
  880.         return
  881.     fi

  882.     # 碰撞检测
  883.     HitTest
  884.     if (( game_overed ))
  885.     then
  886.         return
  887.     fi

  888.     # 每四帧刷新背景
  889.     if (( frame_count % 4 == 0 ))
  890.     then
  891.         StarRefresh
  892.     fi

  893.     # 每两帧刷新敌机
  894.     if (( frame_count % 2 == 0 ))
  895.     then
  896.         EnemyRefresh
  897.     fi

  898.     # 每帧刷新子弹
  899.     BulletRefresh

  900.     # 每帧刷新角色
  901.     PlayerDraw


  902.     # 每帧随机生成敌机
  903.     EnemyRandomGen

  904.     # 每帧随机生成星星
  905.     StarRandomGen

  906.     (( ++frame_count ))
  907.     if (( frame_count > 10000 ))
  908.     then
  909.         frame_count=0
  910.     fi
  911. }

  912. # ----------------------------------------------------------------------------
  913. # 碰撞处理
  914. # ----------------------------------------------------------------------------

  915. # 碰撞检测
  916. function HitTest()
  917. {
  918.     # 敌机与子弹的碰撞
  919.     HitTestBulletEnemy

  920.     # 敌机与玩家的碰撞
  921.     HitTestPlayEnemy
  922. }

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

  930.     r1=$1
  931.     c1=$2
  932.     h1=$3
  933.     w1=$4
  934.     r2=$5
  935.     c2=$6
  936.     h2=$7
  937.     w2=$8

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

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

  948.     # 碰撞
  949.     return 0
  950. }

  951. # 敌机与子弹的碰撞
  952. function HitTestBulletEnemy()
  953. {
  954.     local pos1 pos2
  955.     local r1 c1 h1 w1 r2 c2 h2 w2
  956.     local i j
  957.     local flag_reset

  958.     h1=1
  959.     w1=1
  960.     h2=$enemy_height
  961.     w2=$enemy_width

  962.     flag_reset=0
  963.     i=0
  964.     for pos1 in "${ar_pos_bullet[@]}"
  965.     do
  966.         pos1=( ${pos1[@]} )
  967.         r1=${pos1[0]}
  968.         c1=${pos1[1]}

  969.         j=0
  970.         for pos2 in "${ar_pos_enemy[@]}"
  971.         do
  972.             pos2=( ${pos2[@]} )
  973.             r2=${pos2[0]}
  974.             c2=${pos2[1]}

  975.             if IsHit $r1 $c1 $h1 $w1 $r2 $c2 $h2 $w2
  976.             then
  977.                 unset ar_pos_bullet[$i]
  978.                 unset ar_pos_enemy[$j]

  979.                 BulletClear $r1 $c1
  980.                 EnemyClear $r2 $c2

  981.                 ScoreIncrease

  982.                 flag_reset=1
  983.             fi

  984.             (( ++j ))
  985.         done

  986.         (( ++i ))
  987.     done

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

  993.         return 0
  994.     fi

  995.     return 1
  996. }

  997. # 敌机与玩家的碰撞
  998. function HitTestPlayEnemy()
  999. {
  1000.     local pos r c

  1001.     for pos in "${ar_pos_enemy[@]}"
  1002.     do
  1003.         pos=( ${pos[@]} )
  1004.         r=${pos[0]}
  1005.         c=${pos[1]}

  1006.         # 若敌机与玩家碰撞,则游戏结束
  1007.         if IsHit $r $c $enemy_width $enemy_width $pos_player_r $pos_player_c $player_height $player_width
  1008.         then
  1009.             GameOver
  1010.             return
  1011.         fi
  1012.     done
  1013. }

  1014. # ----------------------------------------------------------------------------
  1015. # 按键响应
  1016. # ----------------------------------------------------------------------------

  1017. # 按键响应:退出游戏
  1018. function OnPressGameExit()
  1019. {
  1020.     # 游戏结束、暂停情况下,直接退出
  1021.     if (( game_overed || game_paused ))
  1022.     then
  1023.         GameExit
  1024.         return
  1025.     fi

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

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

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

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

  1045.     if GamePauseSwitch
  1046.     then
  1047.         TipShow "$msg_paused"
  1048.     else
  1049.         TipClear
  1050.         game_exit_confirmed=0
  1051.     fi
  1052. }

  1053. # ----------------------------------------------------------------------------
  1054. # 主函数
  1055. # ----------------------------------------------------------------------------

  1056. # 主函数
  1057. function Main()
  1058. {
  1059.     Init

  1060.     GameStart
  1061.     Input
  1062.     ExitClear
  1063. }

  1064. Main
后记
  1. 截图发现背景星星变得很不明显了。
  2. 原来代码选择python可以保留格式。

上一篇:没有了
下一篇:图像缩放算法