很牛逼,shell编写的俄罗斯方块

本文详细介绍了Tetris游戏的实现过程,包括颜色、位置、大小、控制信号、方块旋转等关键要素,并针对在不同环境下可能出现的问题进行了优化。通过引入新的颜色编码和改进的信号处理机制,使得游戏在Ubuntu环境下能够正常运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


把注释加进去了,可能不是很详尽,
只希望能把问题表现的清楚一点

感谢各位兄弟的捧场,
属于旧瓶装新酒了,呵呵
如果你的终端可以显示出颜色,效果会好点

运行于GNU bash, version 2.05a.0(1)-release (i686-pc-linux-gnu)

---------------------------------------------------
果然在新的ubuntu下不能运行了,需要将类似\33的地方改成\033,
感谢网友thinux和guotao_buaa帮忙修改,已经将新的代码补入。
  1. #!/bin/bash

  2. # Tetris Game
  3. # 10.21.2003 xhchen<[email]xhchen@winbond.com.tw[/email]>

  4. #APP declaration
  5. APP_NAME="${0##*[\\/]}"
  6. APP_VERSION="1.0"


  7. #颜色定义
  8. cRed=1
  9. cGreen=2
  10. cYellow=3
  11. cBlue=4
  12. cFuchsia=5
  13. cCyan=6
  14. cWhite=7
  15. colorTable=($cRed $cGreen $cYellow $cBlue $cFuchsia $cCyan $cWhite)

  16. #位置和大小
  17. iLeft=3
  18. iTop=2
  19. ((iTrayLeft = iLeft + 2))
  20. ((iTrayTop = iTop + 1))
  21. ((iTrayWidth = 10))
  22. ((iTrayHeight = 15))

  23. #颜色设置
  24. cBorder=$cGreen
  25. cScore=$cFuchsia
  26. cScoreValue=$cCyan

  27. #控制信号
  28. #改游戏使用两个进程,一个用于接收输入,一个用于游戏流程和显示界面;
  29. #当前者接收到上下左右等按键时,通过向后者发送signal的方式通知后者。
  30. sigRotate=25
  31. sigLeft=26
  32. sigRight=27
  33. sigDown=28
  34. sigAllDown=29
  35. sigExit=30

  36. #七中不同的方块的定义
  37. #通过旋转,每种方块的显示的样式可能有几种
  38. box0=(0 0 0 1 1 0 1 1)
  39. box1=(0 2 1 2 2 2 3 2 1 0 1 1 1 2 1 3)
  40. box2=(0 0 0 1 1 1 1 2 0 1 1 0 1 1 2 0)
  41. box3=(0 1 0 2 1 0 1 1 0 0 1 0 1 1 2 1)
  42. box4=(0 1 0 2 1 1 2 1 1 0 1 1 1 2 2 2 0 1 1 1 2 0 2 1 0 0 1 0 1 1 1 2)
  43. box5=(0 1 1 1 2 1 2 2 1 0 1 1 1 2 2 0 0 0 0 1 1 1 2 1 0 2 1 0 1 1 1 2)
  44. box6=(0 1 1 1 1 2 2 1 1 0 1 1 1 2 2 1 0 1 1 0 1 1 2 1 0 1 1 0 1 1 1 2)
  45. #所有其中方块的定义都放到box变量中
  46. box=(${box0[@]} ${box1[@]} ${box2[@]} ${box3[@]} ${box4[@]} ${box5[@]} ${box6[@]})
  47. #各种方块旋转后可能的样式数目
  48. countBox=(1 2 2 2 4 4 4)
  49. #各种方块再box数组中的偏移
  50. offsetBox=(0 1 3 5 7 11 15)

  51. #每提高一个速度级需要积累的分数
  52. iScoreEachLevel=50 #be greater than 7

  53. #运行时数据
  54. sig=0 #接收到的signal
  55. iScore=0 #总分
  56. iLevel=0 #速度级
  57. boxNew=() #新下落的方块的位置定义
  58. cBoxNew=0 #新下落的方块的颜色
  59. iBoxNewType=0 #新下落的方块的种类
  60. iBoxNewRotate=0 #新下落的方块的旋转角度
  61. boxCur=() #当前方块的位置定义
  62. cBoxCur=0 #当前方块的颜色
  63. iBoxCurType=0 #当前方块的种类
  64. iBoxCurRotate=0 #当前方块的旋转角度
  65. boxCurX=-1 #当前方块的x坐标位置
  66. boxCurY=-1 #当前方块的y坐标位置
  67. iMap=() #背景方块图表

  68. #初始化所有背景方块为-1, 表示没有方块
  69. for ((i = 0; i < iTrayHeight * iTrayWidth; i++)); do iMap[$i]=-1; done


  70. #接收输入的进程的主函数
  71. function RunAsKeyReceiver()
  72. {
  73. local pidDisplayer key aKey sig cESC sTTY

  74. pidDisplayer=$1
  75. aKey=(0 0 0)

  76. cESC=`echo -ne "\033"`
  77. cSpace=`echo -ne "\040"`

  78. #保存终端属性。在read -s读取终端键时,终端的属性会被暂时改变。
  79. #如果在read -s时程序被不幸杀掉,可能会导致终端混乱,
  80. #需要在程序退出时恢复终端属性。
  81. sTTY=`stty -g`

  82. #捕捉退出信号
  83. trap "MyExit;" INT TERM
  84. trap "MyExitNoSub;" $sigExit

  85. #隐藏光标
  86. echo -ne "\033[?25l"


  87. while :
  88. do
  89. #读取输入。注-s不回显,-n读到一个字符立即返回
  90. read -s -n 1 key

  91. aKey[0]=${aKey[1]}
  92. aKey[1]=${aKey[2]}
  93. aKey[2]=$key
  94. sig=0

  95. #判断输入了何种键
  96. if [[ $key == $cESC && ${aKey[1]} == $cESC ]]
  97. then
  98. #ESC键
  99. MyExit
  100. elif [[ ${aKey[0]} == $cESC && ${aKey[1]} == "[" ]]
  101. then
  102. if [[ $key == "A" ]]; then sig=$sigRotate #<向上键>
  103. elif [[ $key == "B" ]]; then sig=$sigDown #<向下键>
  104. elif [[ $key == "D" ]]; then sig=$sigLeft #<向左键>
  105. elif [[ $key == "C" ]]; then sig=$sigRight #<向右键>
  106. fi
  107. elif [[ $key == "W" || $key == "w" ]]; then sig=$sigRotate #W, w
  108. elif [[ $key == "S" || $key == "s" ]]; then sig=$sigDown #S, s
  109. elif [[ $key == "A" || $key == "a" ]]; then sig=$sigLeft #A, a
  110. elif [[ $key == "D" || $key == "d" ]]; then sig=$sigRight #D, d
  111. elif [[ "[$key]" == "[]" ]]; then sig=$sigAllDown #空格键
  112. elif [[ $key == "Q" || $key == "q" ]] #Q, q
  113. then
  114. MyExit
  115. fi

  116. if [[ $sig != 0 ]]
  117. then
  118. #向另一进程发送消息
  119. kill -$sig $pidDisplayer
  120. fi
  121. done
  122. }

  123. #退出前的恢复
  124. function MyExitNoSub()
  125. {
  126. local y

  127. #恢复终端属性
  128. stty $sTTY
  129. ((y = iTop + iTrayHeight + 4))

  130. #显示光标
  131. echo -e "\033[?25h\033[${y};0H"
  132. exit
  133. }


  134. function MyExit()
  135. {
  136. #通知显示进程需要退出
  137. kill -$sigExit $pidDisplayer

  138. MyExitNoSub
  139. }


  140. #处理显示和游戏流程的主函数
  141. function RunAsDisplayer()
  142. {
  143. local sigThis
  144. InitDraw

  145. #挂载各种信号的处理函数
  146. trap "sig=$sigRotate;" $sigRotate
  147. trap "sig=$sigLeft;" $sigLeft
  148. trap "sig=$sigRight;" $sigRight
  149. trap "sig=$sigDown;" $sigDown
  150. trap "sig=$sigAllDown;" $sigAllDown
  151. trap "ShowExit;" $sigExit

  152. while :
  153. do
  154. #根据当前的速度级iLevel不同,设定相应的循环的次数
  155. for ((i = 0; i < 21 - iLevel; i++))
  156. do
  157. sleep 0.02
  158. sigThis=$sig
  159. sig=0

  160. #根据sig变量判断是否接受到相应的信号
  161. if ((sigThis == sigRotate)); then BoxRotate; #旋转
  162. elif ((sigThis == sigLeft)); then BoxLeft; #左移一列
  163. elif ((sigThis == sigRight)); then BoxRight; #右移一列
  164. elif ((sigThis == sigDown)); then BoxDown; #下落一行
  165. elif ((sigThis == sigAllDown)); then BoxAllDown; #下落到底
  166. fi
  167. done
  168. #kill -$sigDown $$
  169. BoxDown #下落一行
  170. done
  171. }


  172. #BoxMove(y, x), 测试是否可以把移动中的方块移到(x, y)的位置, 返回0则可以, 1不可以
  173. function BoxMove()
  174. {
  175. local j i x y xTest yTest
  176. yTest=$1
  177. xTest=$2
  178. for ((j = 0; j < 8; j += 2))
  179. do
  180. ((i = j + 1))
  181. ((y = ${boxCur[$j]} + yTest))
  182. ((x = ${boxCur[$i]} + xTest))
  183. if (( y < 0 || y >= iTrayHeight || x < 0 || x >= iTrayWidth))
  184. then
  185. #撞到墙壁了
  186. return 1
  187. fi
  188. if ((${iMap[y * iTrayWidth + x]} != -1 ))
  189. then
  190. #撞到其他已经存在的方块了
  191. return 1
  192. fi
  193. done
  194. return 0;
  195. }


  196. #将当前移动中的方块放到背景方块中去,
  197. #并计算新的分数和速度级。(即一次方块落到底部)
  198. function Box2Map()
  199. {
  200. local j i x y xp yp line

  201. #将当前移动中的方块放到背景方块中去
  202. for ((j = 0; j < 8; j += 2))
  203. do
  204. ((i = j + 1))
  205. ((y = ${boxCur[$j]} + boxCurY))
  206. ((x = ${boxCur[$i]} + boxCurX))
  207. ((i = y * iTrayWidth + x))
  208. iMap[$i]=$cBoxCur
  209. done

  210. #消去可被消去的行
  211. line=0
  212. for ((j = 0; j < iTrayWidth * iTrayHeight; j += iTrayWidth))
  213. do
  214. for ((i = j + iTrayWidth - 1; i >= j; i--))
  215. do
  216. if ((${iMap[$i]} == -1)); then break; fi
  217. done
  218. if ((i >= j)); then continue; fi

  219. ((line++))
  220. for ((i = j - 1; i >= 0; i--))
  221. do
  222. ((x = i + iTrayWidth))
  223. iMap[$x]=${iMap[$i]}
  224. done
  225. for ((i = 0; i < iTrayWidth; i++))
  226. do
  227. iMap[$i]=-1
  228. done
  229. done

  230. if ((line == 0)); then return; fi

  231. #根据消去的行数line计算分数和速度级
  232. ((x = iLeft + iTrayWidth * 2 + 7))
  233. ((y = iTop + 11))
  234. ((iScore += line * 2 - 1))
  235. #显示新的分数
  236. echo -ne "\033[1m\033[3${cScoreValue}m\033[${y};${x}H${iScore} "
  237. if ((iScore % iScoreEachLevel < line * 2 - 1))
  238. then
  239. if ((iLevel < 20))
  240. then
  241. ((iLevel++))
  242. ((y = iTop + 14))
  243. #显示新的速度级
  244. echo -ne "\033[3${cScoreValue}m\033[${y};${x}H${iLevel} "
  245. fi
  246. fi
  247. echo -ne "\033[0m"


  248. #重新显示背景方块
  249. for ((y = 0; y < iTrayHeight; y++))
  250. do
  251. ((yp = y + iTrayTop + 1))
  252. ((xp = iTrayLeft + 1))
  253. ((i = y * iTrayWidth))
  254. echo -ne "\033[${yp};${xp}H"
  255. for ((x = 0; x < iTrayWidth; x++))
  256. do
  257. ((j = i + x))
  258. if ((${iMap[$j]} == -1))
  259. then
  260. echo -ne " "
  261. else
  262. echo -ne "\033[1m\033[7m\033[3${iMap[$j]}m\033[4${iMap[$j]}m[]\033[0m"
  263. fi
  264. done
  265. done
  266. }


  267. #下落一行
  268. function BoxDown()
  269. {
  270. local y s
  271. ((y = boxCurY + 1)) #新的y坐标
  272. if BoxMove $y $boxCurX #测试是否可以下落一行
  273. then
  274. s="`DrawCurBox 0`" #将旧的方块抹去
  275. ((boxCurY = y))
  276. s="$s`DrawCurBox 1`" #显示新的下落后方块
  277. echo -ne $s
  278. else
  279. #走到这儿, 如果不能下落了
  280. Box2Map #将当前移动中的方块贴到背景方块中
  281. RandomBox #产生新的方块
  282. fi
  283. }

  284. #左移一列
  285. function BoxLeft()
  286. {
  287. local x s
  288. ((x = boxCurX - 1))
  289. if BoxMove $boxCurY $x
  290. then
  291. s=`DrawCurBox 0`
  292. ((boxCurX = x))
  293. s=$s`DrawCurBox 1`
  294. echo -ne $s
  295. fi
  296. }

  297. #右移一列
  298. function BoxRight()
  299. {
  300. local x s
  301. ((x = boxCurX + 1))
  302. if BoxMove $boxCurY $x
  303. then
  304. s=`DrawCurBox 0`
  305. ((boxCurX = x))
  306. s=$s`DrawCurBox 1`
  307. echo -ne $s
  308. fi
  309. }


  310. #下落到底
  311. function BoxAllDown()
  312. {
  313. local k j i x y iDown s
  314. iDown=$iTrayHeight

  315. #计算一共需要下落多少行
  316. for ((j = 0; j < 8; j += 2))
  317. do
  318. ((i = j + 1))
  319. ((y = ${boxCur[$j]} + boxCurY))
  320. ((x = ${boxCur[$i]} + boxCurX))
  321. for ((k = y + 1; k < iTrayHeight; k++))
  322. do
  323. ((i = k * iTrayWidth + x))
  324. if (( ${iMap[$i]} != -1)); then break; fi
  325. done
  326. ((k -= y + 1))
  327. if (( $iDown > $k )); then iDown=$k; fi
  328. done

  329. s=`DrawCurBox 0` #将旧的方块抹去
  330. ((boxCurY += iDown))
  331. s=$s`DrawCurBox 1` #显示新的下落后的方块
  332. echo -ne $s
  333. Box2Map #将当前移动中的方块贴到背景方块中
  334. RandomBox #产生新的方块
  335. }


  336. #旋转方块
  337. function BoxRotate()
  338. {
  339. local iCount iTestRotate boxTest j i s
  340. iCount=${countBox[$iBoxCurType]} #当前的方块经旋转可以产生的样式的数目

  341. #计算旋转后的新的样式
  342. ((iTestRotate = iBoxCurRotate + 1))
  343. if ((iTestRotate >= iCount))
  344. then
  345. ((iTestRotate = 0))
  346. fi

  347. #更新到新的样式, 保存老的样式(但不显示)
  348. for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
  349. do
  350. boxTest[$j]=${boxCur[$j]}
  351. boxCur[$j]=${box[$i]}
  352. done

  353. if BoxMove $boxCurY $boxCurX #测试旋转后是否有空间放的下
  354. then
  355. #抹去旧的方块
  356. for ((j = 0; j < 8; j++))
  357. do
  358. boxCur[$j]=${boxTest[$j]}
  359. done
  360. s=`DrawCurBox 0`

  361. #画上新的方块
  362. for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
  363. do
  364. boxCur[$j]=${box[$i]}
  365. done
  366. s=$s`DrawCurBox 1`
  367. echo -ne $s
  368. iBoxCurRotate=$iTestRotate
  369. else
  370. #不能旋转,还是继续使用老的样式
  371. for ((j = 0; j < 8; j++))
  372. do
  373. boxCur[$j]=${boxTest[$j]}
  374. done
  375. fi
  376. }


  377. #DrawCurBox(bDraw), 绘制当前移动中的方块, bDraw为1, 画上, bDraw为0, 抹去方块。
  378. function DrawCurBox()
  379. {
  380. local i j t bDraw sBox s
  381. bDraw=$1

  382. s=""
  383. if (( bDraw == 0 ))
  384. then
  385. sBox="\040\040"
  386. else
  387. sBox="[]"
  388. s=$s"\033[1m\033[7m\033[3${cBoxCur}m\033[4${cBoxCur}m"
  389. fi

  390. for ((j = 0; j < 8; j += 2))
  391. do
  392. ((i = iTrayTop + 1 + ${boxCur[$j]} + boxCurY))
  393. ((t = iTrayLeft + 1 + 2 * (boxCurX + ${boxCur[$j + 1]})))
  394. #\033[y;xH, 光标到(x, y)
  395. s=$s"\033[${i};${t}H${sBox}"
  396. done
  397. s=$s"\033[0m"
  398. echo -n $s
  399. }


  400. #更新新的方块
  401. function RandomBox()
  402. {
  403. local i j t

  404. #更新当前移动的方块
  405. iBoxCurType=${iBoxNewType}
  406. iBoxCurRotate=${iBoxNewRotate}
  407. cBoxCur=${cBoxNew}
  408. for ((j = 0; j < ${#boxNew[@]}; j++))
  409. do
  410. boxCur[$j]=${boxNew[$j]}
  411. done


  412. #显示当前移动的方块
  413. if (( ${#boxCur[@]} == 8 ))
  414. then
  415. #计算当前方块该从顶端哪一行"冒"出来
  416. for ((j = 0, t = 4; j < 8; j += 2))
  417. do
  418. if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
  419. done
  420. ((boxCurY = -t))
  421. for ((j = 1, i = -4, t = 20; j < 8; j += 2))
  422. do
  423. if ((${boxCur[$j]} > i)); then i=${boxCur[$j]}; fi
  424. if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
  425. done
  426. ((boxCurX = (iTrayWidth - 1 - i - t) / 2))

  427. #显示当前移动的方块
  428. echo -ne `DrawCurBox 1`

  429. #如果方块一出来就没处放,Game
  430. if ! BoxMove $boxCurY $boxCurX
  431. then
  432. kill -$sigExit ${PPID}
  433. ShowExit
  434. fi
  435. fi



  436. #清除右边预显示的方块
  437. for ((j = 0; j < 4; j++))
  438. do
  439. ((i = iTop + 1 + j))
  440. ((t = iLeft + 2 * iTrayWidth + 7))
  441. echo -ne "\033[${i};${t}H "
  442. done

  443. #随机产生新的方块
  444. ((iBoxNewType = RANDOM % ${#offsetBox[@]}))
  445. ((iBoxNewRotate = RANDOM % ${countBox[$iBoxNewType]}))
  446. for ((j = 0, i = (${offsetBox[$iBoxNewType]} + $iBoxNewRotate) * 8; j < 8; j++, i++))
  447. do
  448. boxNew[$j]=${box[$i]};
  449. done

  450. ((cBoxNew = ${colorTable[RANDOM % ${#colorTable[@]}]}))

  451. #显示右边预显示的方块
  452. echo -ne "\033[1m\033[7m\033[3${cBoxNew}m\033[4${cBoxNew}m"
  453. for ((j = 0; j < 8; j += 2))
  454. do
  455. ((i = iTop + 1 + ${boxNew[$j]}))
  456. ((t = iLeft + 2 * iTrayWidth + 7 + 2 * ${boxNew[$j + 1]}))
  457. echo -ne "\033[${i};${t}H[]"
  458. done
  459. echo -ne "\033[0m"
  460. }


  461. #初始绘制
  462. function InitDraw()
  463. {
  464. clear
  465. RandomBox #随机产生方块,这时右边预显示窗口中有方快了
  466. RandomBox #再随机产生方块,右边预显示窗口中的方块被更新,原先的方块将开始下落
  467. local i t1 t2 t3

  468. #显示边框
  469. echo -ne "\033[1m"
  470. echo -ne "\033[3${cBorder}m\033[4${cBorder}m"

  471. ((t2 = iLeft + 1))
  472. ((t3 = iLeft + iTrayWidth * 2 + 3))
  473. for ((i = 0; i < iTrayHeight; i++))
  474. do
  475. ((t1 = i + iTop + 2))
  476. echo -ne "\033[${t1};${t2}H||"
  477. echo -ne "\033[${t1};${t3}H||"
  478. done

  479. ((t2 = iTop + iTrayHeight + 2))
  480. for ((i = 0; i < iTrayWidth + 2; i++))
  481. do
  482. ((t1 = i * 2 + iLeft + 1))
  483. echo -ne "\033[${iTrayTop};${t1}H=="
  484. echo -ne "\033[${t2};${t1}H=="
  485. done
  486. echo -ne "\033[0m"


  487. #显示"Score""Level"字样
  488. echo -ne "\033[1m"
  489. ((t1 = iLeft + iTrayWidth * 2 + 7))
  490. ((t2 = iTop + 10))
  491. echo -ne "\033[3${cScore}m\033[${t2};${t1}HScore"
  492. ((t2 = iTop + 11))
  493. echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iScore}"
  494. ((t2 = iTop + 13))
  495. echo -ne "\033[3${cScore}m\033[${t2};${t1}HLevel"
  496. ((t2 = iTop + 14))
  497. echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iLevel}"
  498. echo -ne "\033[0m"
  499. }


  500. #
  501. function ShowExit()
  502. {
  503. local y
  504. ((y = iTrayHeight + iTrayTop + 3))
  505. echo -e "\033[${y};0HGameOver!\033[0m"
  506. exit
  507. }


  508. #显示用法.
  509. function Usage
  510. {
  511. cat << EOF
  512. Usage: $APP_NAME
  513. Start tetris game.

  514. -h, --help display this help and exit
  515. --version output version information and exit
  516. EOF
  517. }


  518. #游戏主程序在这儿开始.
  519. if [[ "$1" == "-h" || "$1" == "--help" ]]; then
  520. Usage
  521. elif [[ "$1" == "--version" ]]; then
  522. echo "$APP_NAME $APP_VERSION"
  523. elif [[ "$1" == "--show" ]]; then
  524. #当发现具有参数--show时,运行显示函数
  525. RunAsDisplayer
  526. else
  527. bash $0 --show& #以参数--show将本程序再运行一遍
  528. RunAsKeyReceiver $! #以上一行产生的进程的进程号作为参数
  529. fi

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值