fork download
  1. #!/bin/bash
  2. # Tetris Game
  3. # 10.21.2003 xhchen<xhchen@winbond.com.tw>
  4. #颜色定义
  5. cRed=1
  6. cGreen=2
  7. cYellow=3
  8. cBlue=4
  9. cFuchsia=5
  10. cCyan=6
  11. cWhite=7
  12. colorTable=($cRed $cGreen $cYellow $cBlue $cFuchsia $cCyan $cWhite)
  13. #位置和大小
  14. iLeft=3
  15. iTop=2
  16. ((iTrayLeft = iLeft + 2))
  17. ((iTrayTop = iTop + 1))
  18. ((iTrayWidth = 10))
  19. ((iTrayHeight = 15))
  20. #颜色设置
  21. cBorder=$cGreen
  22. cScore=$cFuchsia
  23. cScoreValue=$cCyan
  24. #控制信号
  25. #改游戏使用两个进程,一个用于接收输入,一个用于游戏流程和显示界面;
  26. #当前者接收到上下左右等按键时,通过向后者发送signal的方式通知后者。
  27. sigRotate=25
  28. sigLeft=26
  29. sigRight=27
  30. sigDown=28
  31. sigAllDown=29
  32. sigExit=30
  33. #七中不同的方块的定义
  34. #通过旋转,每种方块的显示的样式可能有几种
  35. box0=(0 0 0 1 1 0 1 1)
  36. box1=(0 2 1 2 2 2 3 2 1 0 1 1 1 2 1 3)
  37. box2=(0 0 0 1 1 1 1 2 0 1 1 0 1 1 2 0)
  38. box3=(0 1 0 2 1 0 1 1 0 0 1 0 1 1 2 1)
  39. 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)
  40. 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)
  41. 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)
  42. #所有其中方块的定义都放到box变量中
  43. box=(${box0[@]} ${box1[@]} ${box2[@]} ${box3[@]} ${box4[@]} ${box5[@]} ${box6[@]})
  44. #各种方块旋转后可能的样式数目
  45. countBox=(1 2 2 2 4 4 4)
  46. #各种方块再box数组中的偏移
  47. offsetBox=(0 1 3 5 7 11 15)
  48. #每提高一个速度级需要积累的分数
  49. iScoreEachLevel=50 #be greater than 7
  50. #运行时数据
  51. sig=0 #接收到的signal
  52. iScore=0 #总分
  53. iLevel=0 #速度级
  54. boxNew=() #新下落的方块的位置定义
  55. cBoxNew=0 #新下落的方块的颜色
  56. iBoxNewType=0 #新下落的方块的种类
  57. iBoxNewRotate=0 #新下落的方块的旋转角度
  58. boxCur=() #当前方块的位置定义
  59. cBoxCur=0 #当前方块的颜色
  60. iBoxCurType=0 #当前方块的种类
  61. iBoxCurRotate=0 #当前方块的旋转角度
  62. boxCurX=-1 #当前方块的x坐标位置
  63. boxCurY=-1 #当前方块的y坐标位置
  64. iMap=() #背景方块图表
  65. #初始化所有背景方块为-1, 表示没有方块
  66. for ((i = 0; i < iTrayHeight * iTrayWidth; i++)); do iMap[$i]=-1; done
  67.  
  68. #接收输入的进程的主函数
  69. function RunAsKeyReceiver()
  70. {
  71. local pidDisplayer key aKey sig cESC sTTY
  72. pidDisplayer=$1
  73. aKey=(0 0 0)
  74. cESC=`echo -ne "\33"`
  75. cSpace=`echo -ne "\40"`
  76. #保存终端属性。在read -s读取终端键时,终端的属性会被暂时改变。
  77. #如果在read -s时程序被不幸杀掉,可能会导致终端混乱,
  78. #需要在程序退出时恢复终端属性。
  79. sTTY=`stty -g`
  80.  
  81. #捕捉退出信号
  82. trap "MyExit;" INT TERM
  83. trap "MyExitNoSub;" $sigExit
  84.  
  85. #隐藏光标
  86. echo -ne "\33[?25l"
  87.  
  88. while (( 1 ))
  89. do
  90. #读取输入。注-s不回显,-n读到一个字符立即返回
  91. read -s -n 1 key
  92.  
  93. aKey[0]=${aKey[1]}
  94. aKey[1]=${aKey[2]}
  95. aKey[2]=$key
  96. sig=0
  97. #判断输入了何种键
  98. if [[ $key == $cESC && ${aKey[1]} == $cESC ]]
  99. then
  100. #ESC键
  101. MyExit
  102. elif [[ ${aKey[0]} == $cESC && ${aKey[1]} == "[" ]]
  103. then
  104. if [[ $key == "A" ]]; then sig=$sigRotate #<向上键>
  105. elif [[ $key == "B" ]]; then sig=$sigDown #<向下键>
  106. elif [[ $key == "D" ]]; then sig=$sigLeft #<向左键>
  107. elif [[ $key == "C" ]]; then sig=$sigRight #<向右键>
  108. fi
  109. elif [[ $key == "W" || $key == "w" ]]; then sig=$sigRotate #W, w
  110. elif [[ $key == "S" || $key == "s" ]]; then sig=$sigDown #S, s
  111. elif [[ $key == "A" || $key == "a" ]]; then sig=$sigLeft #A, a
  112. elif [[ $key == "D" || $key == "d" ]]; then sig=$sigRight #D, d
  113. elif [[ "[$key]" == "[]" ]]; then sig=$sigAllDown #空格键
  114. elif [[ $key == "Q" || $key == "q" ]] #Q, q
  115. then
  116. MyExit
  117. fi
  118. if [[ $sig != 0 ]]
  119. then
  120. #向另一进程发送消息
  121. kill -$sig $pidDisplayer
  122. fi
  123. done
  124. }
  125. #退出前的恢复
  126. function MyExitNoSub()
  127. {
  128. local y
  129.  
  130. #恢复终端属性
  131. stty $sTTY
  132. ((y = iTop + iTrayHeight + 4))
  133. #显示光标
  134. echo -e "\33[?25h\33[${y};0H"
  135. exit
  136. }
  137.  
  138. function MyExit()
  139. {
  140. #通知显示进程需要退出
  141. kill -$sigExit $pidDisplayer
  142.  
  143. MyExitNoSub
  144. }
  145.  
  146. #处理显示和游戏流程的主函数
  147. function RunAsDisplayer()
  148. {
  149. local sigThis
  150. InitDraw
  151. #挂载各种信号的处理函数
  152. trap "sig=$sigRotate;" $sigRotate
  153. trap "sig=$sigLeft;" $sigLeft
  154. trap "sig=$sigRight;" $sigRight
  155. trap "sig=$sigDown;" $sigDown
  156. trap "sig=$sigAllDown;" $sigAllDown
  157. trap "ShowExit;" $sigExit
  158. while (( 1 ))
  159. do
  160. #根据当前的速度级iLevel不同,设定相应的循环的次数
  161. for ((i = 0; i < 21 - iLevel; i++))
  162. do
  163. sleep 0.02
  164. sigThis=$sig
  165. sig=0
  166. #根据sig变量判断是否接受到相应的信号
  167. if ((sigThis == sigRotate)); then BoxRotate; #旋转
  168. elif ((sigThis == sigLeft)); then BoxLeft; #左移一列
  169. elif ((sigThis == sigRight)); then BoxRight; #右移一列
  170. elif ((sigThis == sigDown)); then BoxDown; #下落一行
  171. elif ((sigThis == sigAllDown)); then BoxAllDown; #下落到底
  172. fi
  173. done
  174. #kill -$sigDown $$
  175. BoxDown #下落一行
  176. done
  177. }
  178.  
  179. #BoxMove(y, x), 测试是否可以把移动中的方块移到(x, y)的位置, 返回0则可以, 1不可以
  180. function BoxMove()
  181. {
  182. local j i x y xTest yTest
  183. yTest=$1
  184. xTest=$2
  185. for ((j = 0; j < 8; j += 2))
  186. do
  187. ((i = j + 1))
  188. ((y = ${boxCur[$j]} + yTest))
  189. ((x = ${boxCur[$i]} + xTest))
  190. if (( y < 0 || y >= iTrayHeight || x < 0 || x >= iTrayWidth))
  191. then
  192. #撞到墙壁了
  193. return 1
  194. fi
  195. if ((${iMap[y * iTrayWidth + x]} != -1 ))
  196. then
  197. #撞到其他已经存在的方块了
  198. return 1
  199. fi
  200. done
  201. return 0;
  202. }
  203.  
  204. #将当前移动中的方块放到背景方块中去,
  205. #并计算新的分数和速度级。(即一次方块落到底部)
  206. function Box2Map()
  207. {
  208. local j i x y xp yp line
  209. #将当前移动中的方块放到背景方块中去
  210. for ((j = 0; j < 8; j += 2))
  211. do
  212. ((i = j + 1))
  213. ((y = ${boxCur[$j]} + boxCurY))
  214. ((x = ${boxCur[$i]} + boxCurX))
  215. ((i = y * iTrayWidth + x))
  216. iMap[$i]=$cBoxCur
  217. done
  218.  
  219. #消去可被消去的行
  220. line=0
  221. for ((j = 0; j < iTrayWidth * iTrayHeight; j += iTrayWidth))
  222. do
  223. for ((i = j + iTrayWidth - 1; i >= j; i--))
  224. do
  225. if ((${iMap[$i]} == -1)); then break; fi
  226. done
  227. if ((i >= j)); then continue; fi
  228.  
  229. ((line++))
  230. for ((i = j - 1; i >= 0; i--))
  231. do
  232. ((x = i + iTrayWidth))
  233. iMap[$x]=${iMap[$i]}
  234. done
  235. for ((i = 0; i < iTrayWidth; i++))
  236. do
  237. iMap[$i]=-1
  238. done
  239. done
  240.  
  241. if ((line == 0)); then return; fi
  242. #根据消去的行数line计算分数和速度级
  243. ((x = iLeft + iTrayWidth * 2 + 7))
  244. ((y = iTop + 11))
  245. ((iScore += line * 2 - 1))
  246. #显示新的分数
  247. echo -ne "\33[1m\33[3${cScoreValue}m\33[${y};${x}H${iScore} "
  248. if ((iScore % iScoreEachLevel < line * 2 - 1))
  249. then
  250. if ((iLevel < 20))
  251. then
  252. ((iLevel++))
  253. ((y = iTop + 14))
  254. #显示新的速度级
  255. echo -ne "\33[3${cScoreValue}m\33[${y};${x}H${iLevel} "
  256. fi
  257. fi
  258. echo -ne "\33[0m"
  259.  
  260. #重新显示背景方块
  261. for ((y = 0; y < iTrayHeight; y++))
  262. do
  263. ((yp = y + iTrayTop + 1))
  264. ((xp = iTrayLeft + 1))
  265. ((i = y * iTrayWidth))
  266. echo -ne "\33[${yp};${xp}H"
  267. for ((x = 0; x < iTrayWidth; x++))
  268. do
  269. ((j = i + x))
  270. if ((${iMap[$j]} == -1))
  271. then
  272. echo -ne " "
  273. else
  274. echo -ne "\33[1m\33[7m\33[3${iMap[$j]}m\33[4${iMap[$j]}m[]\33[0m"
  275. fi
  276. done
  277. done
  278. }
  279.  
  280. #下落一行
  281. function BoxDown()
  282. {
  283. local y s
  284. ((y = boxCurY + 1)) #新的y坐标
  285. if BoxMove $y $boxCurX #测试是否可以下落一行
  286. then
  287. s="`DrawCurBox 0`" #将旧的方块抹去
  288. ((boxCurY = y))
  289. s="$s`DrawCurBox 1`" #显示新的下落后方块
  290. echo -ne $s
  291. else
  292. #走到这儿, 如果不能下落了
  293. Box2Map #将当前移动中的方块贴到背景方块中
  294. RandomBox #产生新的方块
  295. fi
  296. }
  297. #左移一列
  298. function BoxLeft()
  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 BoxRight()
  312. {
  313. local x s
  314. ((x = boxCurX + 1))
  315. if BoxMove $boxCurY $x
  316. then
  317. s=`DrawCurBox 0`
  318. ((boxCurX = x))
  319. s=$s`DrawCurBox 1`
  320. echo -ne $s
  321. fi
  322. }
  323.  
  324. #下落到底
  325. function BoxAllDown()
  326. {
  327. local k j i x y iDown s
  328. iDown=$iTrayHeight
  329. #计算一共需要下落多少行
  330. for ((j = 0; j < 8; j += 2))
  331. do
  332. ((i = j + 1))
  333. ((y = ${boxCur[$j]} + boxCurY))
  334. ((x = ${boxCur[$i]} + boxCurX))
  335. for ((k = y + 1; k < iTrayHeight; k++))
  336. do
  337. ((i = k * iTrayWidth + x))
  338. if (( ${iMap[$i]} != -1)); then break; fi
  339. done
  340. ((k -= y + 1))
  341. if (( $iDown > $k )); then iDown=$k; fi
  342. done
  343.  
  344. s=`DrawCurBox 0` #将旧的方块抹去
  345. ((boxCurY += iDown))
  346. s=$s`DrawCurBox 1` #显示新的下落后的方块
  347. echo -ne $s
  348. Box2Map #将当前移动中的方块贴到背景方块中
  349. RandomBox #产生新的方块
  350. }
  351.  
  352. #旋转方块
  353. function BoxRotate()
  354. {
  355. local iCount iTestRotate boxTest j i s
  356. iCount=${countBox[$iBoxCurType]} #当前的方块经旋转可以产生的样式的数目
  357. #计算旋转后的新的样式
  358. ((iTestRotate = iBoxCurRotate + 1))
  359. if ((iTestRotate >= iCount))
  360. then
  361. ((iTestRotate = 0))
  362. fi
  363. #更新到新的样式, 保存老的样式(但不显示)
  364. for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
  365. do
  366. boxTest[$j]=${boxCur[$j]}
  367. boxCur[$j]=${box[$i]}
  368. done
  369. if BoxMove $boxCurY $boxCurX #测试旋转后是否有空间放的下
  370. then
  371. #抹去旧的方块
  372. for ((j = 0; j < 8; j++))
  373. do
  374. boxCur[$j]=${boxTest[$j]}
  375. done
  376. s=`DrawCurBox 0`
  377. #画上新的方块
  378. for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
  379. do
  380. boxCur[$j]=${box[$i]}
  381. done
  382. s=$s`DrawCurBox 1`
  383. echo -ne $s
  384. iBoxCurRotate=$iTestRotate
  385. else
  386. #不能旋转,还是继续使用老的样式
  387. for ((j = 0; j < 8; j++))
  388. do
  389. boxCur[$j]=${boxTest[$j]}
  390. done
  391. fi
  392. }
  393.  
  394. #DrawCurBox(bDraw), 绘制当前移动中的方块, bDraw为1, 画上, bDraw为0, 抹去方块。
  395. function DrawCurBox()
  396. {
  397. local i j t bDraw sBox s
  398. bDraw=$1
  399. s=""
  400. if (( bDraw == 0 ))
  401. then
  402. sBox="\40\40"
  403. else
  404. sBox="[]"
  405. s=$s"\33[1m\33[7m\33[3${cBoxCur}m\33[4${cBoxCur}m"
  406. fi
  407.  
  408. for ((j = 0; j < 8; j += 2))
  409. do
  410. ((i = iTrayTop + 1 + ${boxCur[$j]} + boxCurY))
  411. ((t = iTrayLeft + 1 + 2 * (boxCurX + ${boxCur[$j + 1]})))
  412. #\33[y;xH, 光标到(x, y)处
  413. s=$s"\33[${i};${t}H${sBox}"
  414. done
  415. s=$s"\33[0m"
  416. echo -n $s
  417. }
  418.  
  419. #更新新的方块
  420. function RandomBox()
  421. {
  422. local i j t
  423. #更新当前移动的方块
  424. iBoxCurType=${iBoxNewType}
  425. iBoxCurRotate=${iBoxNewRotate}
  426. cBoxCur=${cBoxNew}
  427. for ((j = 0; j < ${#boxNew[@]}; j++))
  428. do
  429. boxCur[$j]=${boxNew[$j]}
  430. done
  431.  
  432. #显示当前移动的方块
  433. if (( ${#boxCur[@]} == 8 ))
  434. then
  435. #计算当前方块该从顶端哪一行"冒"出来
  436. for ((j = 0, t = 4; j < 8; j += 2))
  437. do
  438. if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
  439. done
  440. ((boxCurY = -t))
  441. for ((j = 1, i = -4, t = 20; j < 8; j += 2))
  442. do
  443. if ((${boxCur[$j]} > i)); then i=${boxCur[$j]}; fi
  444. if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
  445. done
  446. ((boxCurX = (iTrayWidth - 1 - i - t) / 2))
  447. #显示当前移动的方块
  448. echo -ne `DrawCurBox 1`
  449. #如果方块一出来就没处放,Game over!
  450. if ! BoxMove $boxCurY $boxCurX
  451. then
  452. kill -$sigExit ${PPID}
  453. ShowExit
  454. fi
  455. fi
  456.  
  457.  
  458. #清除右边预显示的方块
  459. for ((j = 0; j < 4; j++))
  460. do
  461. ((i = iTop + 1 + j))
  462. ((t = iLeft + 2 * iTrayWidth + 7))
  463. echo -ne "\33[${i};${t}H "
  464. done
  465. #随机产生新的方块
  466. ((iBoxNewType = RANDOM % ${#offsetBox[@]}))
  467. ((iBoxNewRotate = RANDOM % ${countBox[$iBoxNewType]}))
  468. for ((j = 0, i = (${offsetBox[$iBoxNewType]} + $iBoxNewRotate) * 8; j < 8; j++, i++))
  469. do
  470. boxNew[$j]=${box[$i]};
  471. done
  472. ((cBoxNew = ${colorTable[RANDOM % ${#colorTable[@]}]}))
  473.  
  474. #显示右边预显示的方块
  475. echo -ne "\33[1m\33[7m\33[3${cBoxNew}m\33[4${cBoxNew}m"
  476. for ((j = 0; j < 8; j += 2))
  477. do
  478. ((i = iTop + 1 + ${boxNew[$j]}))
  479. ((t = iLeft + 2 * iTrayWidth + 7 + 2 * ${boxNew[$j + 1]}))
  480. echo -ne "\33[${i};${t}H[]"
  481. done
  482. echo -ne "\33[0m"
  483. }
  484.  
  485. #初始绘制
  486. function InitDraw()
  487. {
  488. clear
  489. RandomBox #随机产生方块,这时右边预显示窗口中有方快了
  490. RandomBox #再随机产生方块,右边预显示窗口中的方块被更新,原先的方块将开始下落
  491. local i t1 t2 t3
  492. #显示边框
  493. echo -ne "\33[1m"
  494. echo -ne "\33[3${cBorder}m\33[4${cBorder}m"
  495.  
  496. ((t2 = iLeft + 1))
  497. ((t3 = iLeft + iTrayWidth * 2 + 3))
  498. for ((i = 0; i < iTrayHeight; i++))
  499. do
  500. ((t1 = i + iTop + 2))
  501. echo -ne "\33[${t1};${t2}H||"
  502. echo -ne "\33[${t1};${t3}H||"
  503. done
  504.  
  505. ((t2 = iTop + iTrayHeight + 2))
  506. for ((i = 0; i < iTrayWidth + 2; i++))
  507. do
  508. ((t1 = i * 2 + iLeft + 1))
  509. echo -ne "\33[${iTrayTop};${t1}H=="
  510. echo -ne "\33[${t2};${t1}H=="
  511. done
  512. echo -ne "\33[0m"
  513.  
  514. #显示"Score"和"Level"字样
  515. echo -ne "\33[1m"
  516. ((t1 = iLeft + iTrayWidth * 2 + 7))
  517. ((t2 = iTop + 10))
  518. echo -ne "\33[3${cScore}m\33[${t2};${t1}HScore"
  519. ((t2 = iTop + 11))
  520. echo -ne "\33[3${cScoreValue}m\33[${t2};${t1}H${iScore}"
  521. ((t2 = iTop + 13))
  522. echo -ne "\33[3${cScore}m\33[${t2};${t1}HLevel"
  523. ((t2 = iTop + 14))
  524. echo -ne "\33[3${cScoreValue}m\33[${t2};${t1}H${iLevel}"
  525. echo -ne "\33[0m"
  526. }
  527.  
  528. #退出时显示GameOVer!
  529. function ShowExit()
  530. {
  531. local y
  532. ((y = iTrayHeight + iTrayTop + 3))
  533. echo -e "\33[${y};0HGameOver!\33[0m"
  534. exit
  535. }
  536.  
  537. #游戏主程序在这儿开始.
  538. if [[ $1 != "--show" ]]
  539. then
  540. bash $0 --show& #以参数--show将本程序再运行一遍
  541. RunAsKeyReceiver $! #以上一行产生的进程的进程号作为参数
  542. exit
  543. else
  544. #当发现具有参数--show时,运行显示函数
  545. RunAsDisplayer
  546. exit
  547. fi
Runtime error #stdin #stdout 0.22s 5316KB
stdin
Standard input is empty
stdout
\33[?25l