fork(1) download
  1. #!/bin/bash
  2.  
  3. # Tetris Game
  4. # 10.21.2003 xhchen<[email]xhchen@winbond.com.tw[/email]>
  5.  
  6. #APP declaration
  7. APP_NAME="${0##*[\\/]}"
  8. APP_VERSION="1.0"
  9.  
  10.  
  11. #顏色定義
  12. cRed=1
  13. cGreen=2
  14. cYellow=3
  15. cBlue=4
  16. cFuchsia=5
  17. cCyan=6
  18. cWhite=7
  19. colorTable=($cRed $cGreen $cYellow $cBlue $cFuchsia $cCyan $cWhite)
  20.  
  21. #位置和大小
  22. iLeft=3
  23. iTop=2
  24. ((iTrayLeft = iLeft + 2))
  25. ((iTrayTop = iTop + 1))
  26. ((iTrayWidth = 10))
  27. ((iTrayHeight = 15))
  28.  
  29. #顏色設置
  30. cBorder=$cGreen
  31. cScore=$cFuchsia
  32. cScoreValue=$cCyan
  33.  
  34. #控制信號
  35. #改遊戲使用兩個進程,一個用於接收輸入,一個用於遊戲流程和顯示界面;
  36. #當前者接收到上下左右等按鍵時,通過向後者發送signal的方式通知後者。
  37. sigRotate=25
  38. sigLeft=26
  39. sigRight=27
  40. sigDown=28
  41. sigAllDown=29
  42. sigExit=30
  43.  
  44. #七中不同的方塊的定義
  45. #通過旋轉,每種方塊的顯示的樣式可能有幾種
  46. box0=(0 0 0 1 1 0 1 1)
  47. box1=(0 2 1 2 2 2 3 2 1 0 1 1 1 2 1 3)
  48. box2=(0 0 0 1 1 1 1 2 0 1 1 0 1 1 2 0)
  49. box3=(0 1 0 2 1 0 1 1 0 0 1 0 1 1 2 1)
  50. 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)
  51. 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)
  52. 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)
  53. #所有其中方塊的定義都放到box變量中
  54. box=(${box0[@]} ${box1[@]} ${box2[@]} ${box3[@]} ${box4[@]} ${box5[@]} ${box6[@]})
  55. #各種方塊旋轉後可能的樣式數目
  56. countBox=(1 2 2 2 4 4 4)
  57. #各種方塊再box數組中的偏移
  58. offsetBox=(0 1 3 5 7 11 15)
  59.  
  60. #每提高一個速度級需要積累的分數
  61. iScoreEachLevel=50 #be greater than 7
  62.  
  63. #運行時數據
  64. sig=0 #接收到的signal
  65. iScore=0 #總分
  66. iLevel=0 #速度級
  67. boxNew=() #新下落的方塊的位置定義
  68. cBoxNew=0 #新下落的方塊的顏色
  69. iBoxNewType=0 #新下落的方塊的種類
  70. iBoxNewRotate=0 #新下落的方塊的旋轉角度
  71. boxCur=() #當前方塊的位置定義
  72. cBoxCur=0 #當前方塊的顏色
  73. iBoxCurType=0 #當前方塊的種類
  74. iBoxCurRotate=0 #當前方塊的旋轉角度
  75. boxCurX=-1 #當前方塊的x坐標位置
  76. boxCurY=-1 #當前方塊的y坐標位置
  77. iMap=() #背景方塊圖表
  78.  
  79. #初始化所有背景方塊為-1, 表示沒有方塊
  80. for ((i = 0; i < iTrayHeight * iTrayWidth; i++)); do iMap[$i]=-1; done
  81.  
  82.  
  83. #接收輸入的進程的主函數
  84. function RunAsKeyReceiver()
  85. {
  86. local pidDisplayer key aKey sig cESC sTTY
  87.  
  88. pidDisplayer=$1
  89. aKey=(0 0 0)
  90.  
  91. cESC=`echo -ne "\033"`
  92. cSpace=`echo -ne "\040"`
  93.  
  94. #保存終端屬性。在read -s讀取終端鍵時,終端的屬性會被暫時改變。
  95. #如果在read -s時程序被不幸殺掉,可能會導致終端混亂,
  96. #需要在程序退出時恢復終端屬性。
  97. sTTY=`stty -g`
  98.  
  99. #捕捉退出信號
  100. trap "MyExit;" INT TERM
  101. trap "MyExitNoSub;" $sigExit
  102.  
  103. #隱藏光標
  104. echo -ne "\033[?25l"
  105.  
  106.  
  107. while :
  108. do
  109. #讀取輸入。注-s不回顯,-n讀到一個字符立即返回
  110. read -s -n 1 key
  111.  
  112. aKey[0]=${aKey[1]}
  113. aKey[1]=${aKey[2]}
  114. aKey[2]=$key
  115. sig=0
  116.  
  117. #判斷輸入了何種鍵
  118. if [[ $key == $cESC && ${aKey[1]} == $cESC ]]
  119. then
  120. #ESC鍵
  121. MyExit
  122. elif [[ ${aKey[0]} == $cESC && ${aKey[1]} == "[" ]]
  123. then
  124. if [[ $key == "A" ]]; then sig=$sigRotate #<向上鍵>
  125. elif [[ $key == "B" ]]; then sig=$sigDown #<向下鍵>
  126. elif [[ $key == "D" ]]; then sig=$sigLeft #<向左鍵>
  127. elif [[ $key == "C" ]]; then sig=$sigRight #<向右鍵>
  128. fi
  129. elif [[ $key == "W" || $key == "w" ]]; then sig=$sigRotate #W, w
  130. elif [[ $key == "S" || $key == "s" ]]; then sig=$sigDown #S, s
  131. elif [[ $key == "A" || $key == "a" ]]; then sig=$sigLeft #A, a
  132. elif [[ $key == "D" || $key == "d" ]]; then sig=$sigRight #D, d
  133. elif [[ "[$key]" == "[]" ]]; then sig=$sigAllDown #空格鍵
  134. elif [[ $key == "Q" || $key == "q" ]] #Q, q
  135. then
  136. MyExit
  137. fi
  138.  
  139. if [[ $sig != 0 ]]
  140. then
  141. #向另一進程發送消息
  142. kill -$sig $pidDisplayer
  143. fi
  144. done
  145. }
  146.  
  147. #退出前的恢復
  148. function MyExitNoSub()
  149. {
  150. local y
  151.  
  152. #恢復終端屬性
  153. stty $sTTY
  154. ((y = iTop + iTrayHeight + 4))
  155.  
  156. #顯示光標
  157. echo -e "\033[?25h\033[${y};0H"
  158. exit
  159. }
  160.  
  161.  
  162. function MyExit()
  163. {
  164. #通知顯示進程需要退出
  165. kill -$sigExit $pidDisplayer
  166.  
  167. MyExitNoSub
  168. }
  169.  
  170.  
  171. #處理顯示和遊戲流程的主函數
  172. function RunAsDisplayer()
  173. {
  174. local sigThis
  175. InitDraw
  176.  
  177. #掛載各種信號的處理函數
  178. trap "sig=$sigRotate;" $sigRotate
  179. trap "sig=$sigLeft;" $sigLeft
  180. trap "sig=$sigRight;" $sigRight
  181. trap "sig=$sigDown;" $sigDown
  182. trap "sig=$sigAllDown;" $sigAllDown
  183. trap "ShowExit;" $sigExit
  184.  
  185. while :
  186. do
  187. #根據當前的速度級iLevel不同,設定相應的循環的次數
  188. for ((i = 0; i < 21 - iLevel; i++))
  189. do
  190. sleep 0.02
  191. sigThis=$sig
  192. sig=0
  193.  
  194. #根據sig變量判斷是否接受到相應的信號
  195. if ((sigThis == sigRotate)); then BoxRotate; #旋轉
  196. elif ((sigThis == sigLeft)); then BoxLeft; #左移一列
  197. elif ((sigThis == sigRight)); then BoxRight; #右移一列
  198. elif ((sigThis == sigDown)); then BoxDown; #下落一行
  199. elif ((sigThis == sigAllDown)); then BoxAllDown; #下落到底
  200. fi
  201. done
  202. #kill -$sigDown $$
  203. BoxDown #下落一行
  204. done
  205. }
  206.  
  207.  
  208. #BoxMove(y, x), 測試是否可以把移動中的方塊移到(x, y)的位置, 返回0則可以, 1不可以
  209. function BoxMove()
  210. {
  211. local j i x y xTest yTest
  212. yTest=$1
  213. xTest=$2
  214. for ((j = 0; j < 8; j += 2))
  215. do
  216. ((i = j + 1))
  217. ((y = ${boxCur[$j]} + yTest))
  218. ((x = ${boxCur[$i]} + xTest))
  219. if (( y < 0 || y >= iTrayHeight || x < 0 || x >= iTrayWidth))
  220. then
  221. #撞到牆壁了
  222. return 1
  223. fi
  224. if ((${iMap[y * iTrayWidth + x]} != -1 ))
  225. then
  226. #撞到其他已經存在的方塊了
  227. return 1
  228. fi
  229. done
  230. return 0;
  231. }
  232.  
  233.  
  234. #將當前移動中的方塊放到背景方塊中去,
  235. #並計算新的分數和速度級。(即一次方塊落到底部)
  236. function Box2Map()
  237. {
  238. local j i x y xp yp line
  239.  
  240. #將當前移動中的方塊放到背景方塊中去
  241. for ((j = 0; j < 8; j += 2))
  242. do
  243. ((i = j + 1))
  244. ((y = ${boxCur[$j]} + boxCurY))
  245. ((x = ${boxCur[$i]} + boxCurX))
  246. ((i = y * iTrayWidth + x))
  247. iMap[$i]=$cBoxCur
  248. done
  249.  
  250. #消去可被消去的行
  251. line=0
  252. for ((j = 0; j < iTrayWidth * iTrayHeight; j += iTrayWidth))
  253. do
  254. for ((i = j + iTrayWidth - 1; i >= j; i--))
  255. do
  256. if ((${iMap[$i]} == -1)); then break; fi
  257. done
  258. if ((i >= j)); then continue; fi
  259.  
  260. ((line++))
  261. for ((i = j - 1; i >= 0; i--))
  262. do
  263. ((x = i + iTrayWidth))
  264. iMap[$x]=${iMap[$i]}
  265. done
  266. for ((i = 0; i < iTrayWidth; i++))
  267. do
  268. iMap[$i]=-1
  269. done
  270. done
  271.  
  272. if ((line == 0)); then return; fi
  273.  
  274. #根據消去的行數line計算分數和速度級
  275. ((x = iLeft + iTrayWidth * 2 + 7))
  276. ((y = iTop + 11))
  277. ((iScore += line * 2 - 1))
  278. #顯示新的分數
  279. echo -ne "\033[1m\033[3${cScoreValue}m\033[${y};${x}H${iScore} "
  280. if ((iScore % iScoreEachLevel < line * 2 - 1))
  281. then
  282. if ((iLevel < 20))
  283. then
  284. ((iLevel++))
  285. ((y = iTop + 14))
  286. #顯示新的速度級
  287. echo -ne "\033[3${cScoreValue}m\033[${y};${x}H${iLevel} "
  288. fi
  289. fi
  290. echo -ne "\033[0m"
  291.  
  292.  
  293. #重新顯示背景方塊
  294. for ((y = 0; y < iTrayHeight; y++))
  295. do
  296. ((yp = y + iTrayTop + 1))
  297. ((xp = iTrayLeft + 1))
  298. ((i = y * iTrayWidth))
  299. echo -ne "\033[${yp};${xp}H"
  300. for ((x = 0; x < iTrayWidth; x++))
  301. do
  302. ((j = i + x))
  303. if ((${iMap[$j]} == -1))
  304. then
  305. echo -ne " "
  306. else
  307. echo -ne "\033[1m\033[7m\033[3${iMap[$j]}m\033[4${iMap[$j]}m[]\033[0m"
  308. fi
  309. done
  310. done
  311. }
  312.  
  313.  
  314. #下落一行
  315. function BoxDown()
  316. {
  317. local y s
  318. ((y = boxCurY + 1)) #新的y坐標
  319. if BoxMove $y $boxCurX #測試是否可以下落一行
  320. then
  321. s="`DrawCurBox 0`" #將舊的方塊抹去
  322. ((boxCurY = y))
  323. s="$s`DrawCurBox 1`" #顯示新的下落後方塊
  324. echo -ne $s
  325. else
  326. #走到這兒, 如果不能下落了
  327. Box2Map #將當前移動中的方塊貼到背景方塊中
  328. RandomBox #產生新的方塊
  329. fi
  330. }
  331.  
  332. #左移一列
  333. function BoxLeft()
  334. {
  335. local x s
  336. ((x = boxCurX - 1))
  337. if BoxMove $boxCurY $x
  338. then
  339. s=`DrawCurBox 0`
  340. ((boxCurX = x))
  341. s=$s`DrawCurBox 1`
  342. echo -ne $s
  343. fi
  344. }
  345.  
  346. #右移一列
  347. function BoxRight()
  348. {
  349. local x s
  350. ((x = boxCurX + 1))
  351. if BoxMove $boxCurY $x
  352. then
  353. s=`DrawCurBox 0`
  354. ((boxCurX = x))
  355. s=$s`DrawCurBox 1`
  356. echo -ne $s
  357. fi
  358. }
  359.  
  360.  
  361. #下落到底
  362. function BoxAllDown()
  363. {
  364. local k j i x y iDown s
  365. iDown=$iTrayHeight
  366.  
  367. #計算一共需要下落多少行
  368. for ((j = 0; j < 8; j += 2))
  369. do
  370. ((i = j + 1))
  371. ((y = ${boxCur[$j]} + boxCurY))
  372. ((x = ${boxCur[$i]} + boxCurX))
  373. for ((k = y + 1; k < iTrayHeight; k++))
  374. do
  375. ((i = k * iTrayWidth + x))
  376. if (( ${iMap[$i]} != -1)); then break; fi
  377. done
  378. ((k -= y + 1))
  379. if (( $iDown > $k )); then iDown=$k; fi
  380. done
  381.  
  382. s=`DrawCurBox 0` #將舊的方塊抹去
  383. ((boxCurY += iDown))
  384. s=$s`DrawCurBox 1` #顯示新的下落後的方塊
  385. echo -ne $s
  386. Box2Map #將當前移動中的方塊貼到背景方塊中
  387. RandomBox #產生新的方塊
  388. }
  389.  
  390.  
  391. #旋轉方塊
  392. function BoxRotate()
  393. {
  394. local iCount iTestRotate boxTest j i s
  395. iCount=${countBox[$iBoxCurType]} #當前的方塊經旋轉可以產生的樣式的數目
  396.  
  397. #計算旋轉後的新的樣式
  398. ((iTestRotate = iBoxCurRotate + 1))
  399. if ((iTestRotate >= iCount))
  400. then
  401. ((iTestRotate = 0))
  402. fi
  403.  
  404. #更新到新的樣式, 保存老的樣式(但不顯示)
  405. for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
  406. do
  407. boxTest[$j]=${boxCur[$j]}
  408. boxCur[$j]=${box[$i]}
  409. done
  410.  
  411. if BoxMove $boxCurY $boxCurX #測試旋轉後是否有空間放的下
  412. then
  413. #抹去舊的方塊
  414. for ((j = 0; j < 8; j++))
  415. do
  416. boxCur[$j]=${boxTest[$j]}
  417. done
  418. s=`DrawCurBox 0`
  419.  
  420. #畫上新的方塊
  421. for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
  422. do
  423. boxCur[$j]=${box[$i]}
  424. done
  425. s=$s`DrawCurBox 1`
  426. echo -ne $s
  427. iBoxCurRotate=$iTestRotate
  428. else
  429. #不能旋轉,還是繼續使用老的樣式
  430. for ((j = 0; j < 8; j++))
  431. do
  432. boxCur[$j]=${boxTest[$j]}
  433. done
  434. fi
  435. }
  436.  
  437.  
  438. #DrawCurBox(bDraw), 繪製當前移動中的方塊, bDraw為1, 畫上, bDraw為0, 抹去方塊。
  439. function DrawCurBox()
  440. {
  441. local i j t bDraw sBox s
  442. bDraw=$1
  443.  
  444. s=""
  445. if (( bDraw == 0 ))
  446. then
  447. sBox="\040\040"
  448. else
  449. sBox="[]"
  450. s=$s"\033[1m\033[7m\033[3${cBoxCur}m\033[4${cBoxCur}m"
  451. fi
  452.  
  453. for ((j = 0; j < 8; j += 2))
  454. do
  455. ((i = iTrayTop + 1 + ${boxCur[$j]} + boxCurY))
  456. ((t = iTrayLeft + 1 + 2 * (boxCurX + ${boxCur[$j + 1]})))
  457. #\033[y;xH, 光標到(x, y)處
  458. s=$s"\033[${i};${t}H${sBox}"
  459. done
  460. s=$s"\033[0m"
  461. echo -n $s
  462. }
  463.  
  464.  
  465. #更新新的方塊
  466. function RandomBox()
  467. {
  468. local i j t
  469.  
  470. #更新當前移動的方塊
  471. iBoxCurType=${iBoxNewType}
  472. iBoxCurRotate=${iBoxNewRotate}
  473. cBoxCur=${cBoxNew}
  474. for ((j = 0; j < ${#boxNew[@]}; j++))
  475. do
  476. boxCur[$j]=${boxNew[$j]}
  477. done
  478.  
  479.  
  480. #顯示當前移動的方塊
  481. if (( ${#boxCur[@]} == 8 ))
  482. then
  483. #計算當前方塊該從頂端哪一行"冒"出來
  484. for ((j = 0, t = 4; j < 8; j += 2))
  485. do
  486. if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
  487. done
  488. ((boxCurY = -t))
  489. for ((j = 1, i = -4, t = 20; j < 8; j += 2))
  490. do
  491. if ((${boxCur[$j]} > i)); then i=${boxCur[$j]}; fi
  492. if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
  493. done
  494. ((boxCurX = (iTrayWidth - 1 - i - t) / 2))
  495.  
  496. #顯示當前移動的方塊
  497. echo -ne `DrawCurBox 1`
  498.  
  499. #如果方塊一出來就沒處放,Game over!
  500. if ! BoxMove $boxCurY $boxCurX
  501. then
  502. kill -$sigExit ${PPID}
  503. ShowExit
  504. fi
  505. fi
  506.  
  507.  
  508.  
  509. #清除右邊預顯示的方塊
  510. for ((j = 0; j < 4; j++))
  511. do
  512. ((i = iTop + 1 + j))
  513. ((t = iLeft + 2 * iTrayWidth + 7))
  514. echo -ne "\033[${i};${t}H "
  515. done
  516.  
  517. #隨機產生新的方塊
  518. ((iBoxNewType = RANDOM % ${#offsetBox[@]}))
  519. ((iBoxNewRotate = RANDOM % ${countBox[$iBoxNewType]}))
  520. for ((j = 0, i = (${offsetBox[$iBoxNewType]} + $iBoxNewRotate) * 8; j < 8; j++, i++))
  521. do
  522. boxNew[$j]=${box[$i]};
  523. done
  524.  
  525. ((cBoxNew = ${colorTable[RANDOM % ${#colorTable[@]}]}))
  526.  
  527. #顯示右邊預顯示的方塊
  528. echo -ne "\033[1m\033[7m\033[3${cBoxNew}m\033[4${cBoxNew}m"
  529. for ((j = 0; j < 8; j += 2))
  530. do
  531. ((i = iTop + 1 + ${boxNew[$j]}))
  532. ((t = iLeft + 2 * iTrayWidth + 7 + 2 * ${boxNew[$j + 1]}))
  533. echo -ne "\033[${i};${t}H[]"
  534. done
  535. echo -ne "\033[0m"
  536. }
  537.  
  538.  
  539. #初始繪製
  540. function InitDraw()
  541. {
  542. clear
  543. RandomBox #隨機產生方塊,這時右邊預顯示窗口中有方快了
  544. RandomBox #再隨機產生方塊,右邊預顯示窗口中的方塊被更新,原先的方塊將開始下落
  545. local i t1 t2 t3
  546.  
  547. #顯示邊框
  548. echo -ne "\033[1m"
  549. echo -ne "\033[3${cBorder}m\033[4${cBorder}m"
  550.  
  551. ((t2 = iLeft + 1))
  552. ((t3 = iLeft + iTrayWidth * 2 + 3))
  553. for ((i = 0; i < iTrayHeight; i++))
  554. do
  555. ((t1 = i + iTop + 2))
  556. echo -ne "\033[${t1};${t2}H||"
  557. echo -ne "\033[${t1};${t3}H||"
  558. done
  559.  
  560. ((t2 = iTop + iTrayHeight + 2))
  561. for ((i = 0; i < iTrayWidth + 2; i++))
  562. do
  563. ((t1 = i * 2 + iLeft + 1))
  564. echo -ne "\033[${iTrayTop};${t1}H=="
  565. echo -ne "\033[${t2};${t1}H=="
  566. done
  567. echo -ne "\033[0m"
  568.  
  569.  
  570. #顯示"Score"和"Level"字樣
  571. echo -ne "\033[1m"
  572. ((t1 = iLeft + iTrayWidth * 2 + 7))
  573. ((t2 = iTop + 10))
  574. echo -ne "\033[3${cScore}m\033[${t2};${t1}HScore"
  575. ((t2 = iTop + 11))
  576. echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iScore}"
  577. ((t2 = iTop + 13))
  578. echo -ne "\033[3${cScore}m\033[${t2};${t1}HLevel"
  579. ((t2 = iTop + 14))
  580. echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iLevel}"
  581. echo -ne "\033[0m"
  582. }
  583.  
  584.  
  585. #退出時顯示GameOVer!
  586. function ShowExit()
  587. {
  588. local y
  589. ((y = iTrayHeight + iTrayTop + 3))
  590. echo -e "\033[${y};0HGameOver!\033[0m"
  591. exit
  592. }
  593.  
  594.  
  595. #顯示用法.
  596. function Usage
  597. {
  598. cat << EOF
  599. Usage: $APP_NAME
  600. Start tetris game.
  601.  
  602.   -h, --help display this help and exit
  603.   --version output version information and exit
  604. EOF
  605. }
  606.  
  607.  
  608. #遊戲主程序在這兒開始.
  609. if [[ "$1" == "-h" || "$1" == "--help" ]]; then
  610. Usage
  611. elif [[ "$1" == "--version" ]]; then
  612. echo "$APP_NAME $APP_VERSION"
  613. elif [[ "$1" == "--show" ]]; then
  614. #當發現具有參數--show時,運行顯示函數
  615. RunAsDisplayer
  616. else
  617. bash $0 --show& #以參數--show將本程序再運行一遍
  618. RunAsKeyReceiver $! #以上一行產生的進程的進程號作為參數
  619. fi
  620.  
  621.  
Not running #stdin #stdout 0s 0KB
stdin
Standard input is empty
stdout
Standard output is empty