Tetris

《俄羅斯方塊》(俄語:Тетрис,英語:Tetris),是1980年末期至1990年代初期風靡全世界的電腦遊戲,是落下型益智遊戲的始祖,為蘇聯首個在美國發佈的娛樂軟件。此遊戲最初由阿列克謝·帕基特諾夫在蘇聯設計和編寫,於1984年6月6日首次發佈,當時他正在蘇聯科學院電算中心工作。此遊戲的名稱是由希臘語數字「四」的前綴「tetra-」(因所有落下方塊皆由四塊組成)和帕基特諾夫最喜歡的運動網球(「tennis」)拼接而成,華語地區則因遊戲為俄羅斯人發明普遍稱為「俄羅斯方塊」。

Typical_Tetris_Game

0. 本章重點

  1. 熟習class的用法

  2. frameRate()外控制速度的方法

1. 準備畫面和class Spot用來裝起所有格

tetris.pyde:

gameBoard.py:

spot.py:

image-20230228131024107

新增兩個class,首先是 spot是用來裝起每一個格的變數,這個class包含ij是格的行列,xy是其顯示座標,wh則是格的大小。

另一個class GameBoard用來真正裝起整個game的畫面,之所以要用一個class去包起,而不直接在draw()中直接去畫,一來是為了簡潔,二來是為了擴展需要,寫好之後可以同一時間加多另一個gameboard,可以實玩雙打。

2. 俄羅斯方塊的元素

俄羅斯方塊共有7個元素,依次分別為TLJIZSO。而這此元素亦可以旋轉,所以一共19個可能性。

筆記 2023年2月17日

tetris.pyde:

spot.py:

gameBoard.py:

image-20230228132800137

在上一個版本中,gridsshow()是根據輸入的顏色,用來測試和效果。接著今次的版本,我們要根據上述俄羅斯方塊的元素去填顏色。

在class Spot中,

將之前的show()加入不同元素顯示不同顏色。

之後在主頁的中,

測試一下不同的元素是否有對應相對的顏色。

3. 新增鄰居

tetris.pyde:

spot.py:

gameBoard.py:

image-20230301113606901

在class Spot中,

加入addNeighbors()的函數,跟之前一樣,四個鄰居的次序分別是上、右、下、左順時針方向。

 

之後在GameBoardclass中,將10x20個spot都執行addNeighbors()

 

最後就可以在主程式中,執行addNeighbors(),為了測試是否正確加入鄰居,可以將四個鄰居都設定成不同的元素來測試看看顏色。

4. 新增Tetromino Block

筆記 2023年2月17日

tetris.pyde:

gameBoard.py:

spot.py:

block.py:

image-20230222112824465

在主程式 tetris.pyde,將之前用來debug的內容刪除。 spot.py沒有變化,貼出來只是方便大家對一對整個program,看看有否錯漏。

 

筆記 2023年2月17日

新增一個class叫Block,這個block是就是俄羅斯方塊的方塊,俄羅斯方塊的元素有7個,其有19個可能性,要實現的話,比較容易的方法是將所有block統一變成4x4的格,再在對應的格中增色,如上圖。

 

這個Blockclass有自己的座標,這個xy是指4x4格中的最左上角。type是block的元素,就是'I', 'Z', 'S', 'J', 'L', 'T', 'O'中的一個。gridgameBoard中的grids。由於shapeIndex = ['I', 'Z', 'S', 'J', 'L', 'T', 'O']是用文字去表達方便去閱讀,所以要將其變成數字,方便之後在shapes中查找對應的格,所以要用到index()指令,index()指令就是查找你輸入的內容,再輸出list中的位置索引。

 

shape()函數: 用來裝起shapelist (純粹是因為原名字太長太多self)。

rotate()函數: 每次call這個函數,self.rotation就加1。如此上面的shapelist中的self.shapes[self.index][self.rotation]後面的rotation就會變下一個,但因這個list中內容各有長短,所以要除以len(self.shapes[self.index])的餘數,這裡的len()是長度的意思。

setType()函數: _index = i*4+j就是重現上圖的0-15,如果這個數字是上圖shape中的內容,就將對應格的name設成這個block的type

 

最後在GameBoardclass中,加入self.block = Block(3,0,'T', self.grids)新的block,座標在(3, 0),元素是'T'。而在下面的show()中,在顯示之前,先將block的內容都設定好。

5. 令Tetromino Block向下

tetris.pyde:

gameBoard.py:

spot.py:

block.py:

tetris3

spot.py沒有變化。

主程式tetris.pyde中,亦只有在draw()中加多了一句gameBoard.update(),其他都沒有變化。

 

先解說gameBoard.py:

GameBoard class中,加入函數update()if millis() - self.timer > 200:配合self.timer = millis()是一個常用的格式,在arduino或processing這類本身就預設有forever loop的程式中,要想同一時間執行幾個delay,就需要用到這個格式。

每隔200ms,先將全部沒有鎖起的格都清空,指著將block向下一格,如果block到了最底,就將這個block的內容鎖起來。isWall(2)中的2是指之前addNeighbors()中的次序,上,右,下,左分別是0,1,2,3

 

至於上面block的函數怎樣實現,就要看看Blockclass中怎樣做。

block的16個格都掃一次,如果是對應shape的內容,就看看方向,_direction == 2即下方,如果i+self.y>=19(即block的內容)到達19或以上,即已經到達最後一格,所以到底了,同理,其他左方(_direction == 3)和右方(_direction == 1)也是一樣。

isBlock()函數跟上面的isWall()函數十分相似,分別是我們之前在GameBoard class中已經做了addNeighbors(),所以直接查詢對應方向的鄰居是否已經鎖上,就知道有沒有其他的block阻擋了移動方向。

setLock(): 比較簡單就掃瞄16個格,將對應的格鎖上。

goDown(): 如果下方不是牆而且下方沒有阻擋,就將座標y1

6. 令Tetromino Block懂得堆疊

gameBoard.py:

tetris4

今次這個步驟比較簡單,只有gameBoard.py有變化,其他3個我就不再貼出來了。

主要更改的是update(),除了要檢視block是否已經到底之外,也要檢查block的下面是否有其他已鎖定的格阻擋了去路,如果有的話,就將block的內容設定成自己的type,鎖定後,再重新在座標(3, 0)開另一個block。

7. 令Tetromino Block左右移動

block.py:

tetris.pyde:

tetris5

只有block.py和主程式有變化,其他的我就不貼出來了。

block.py中,

除了之前已有的goDown()外,再加入goLeft()goRight(),內容跟goDown()十分相似,只是方向改變了。

 

返回主程式,在setup()draw()之外,加入keyPressed()函數,加入左、右方向鍵的控制。

8. Tetromino Block旋轉和快速到底

tetris6

在上面的基礎下,在keyPressed()中,加入上和下方向鍵的控制。

按上方向鍵就會清除沒有鎖定的內容,之後旋轉block。

下方向鍵就比較複雜,如果block未到最底下或block的下面沒有阻擋,就用while loop一直向下,相反while loop跳了出來即已經到底或有阻擋,就鎖定block,之後再將block變回座標(0,3)。

9. 每次新block都抽不同的元素

tetris.pyde:

gameBoard.py:

其他沒有變動的就不貼出來了。

tetris7

要每次都抽新的block其實很簡單,整個程式共有3處是重新更新block的,你可以搜尋gameBoard.block = Block(3,0,'T', gameBoard.grids),將這3處都變成gameBoard.block = Block(3,0,gameBoard.shapeIndex[int(random(7))], gameBoard.grids),就可以每次都抽新的block。

10. 考考你

  1. game over: 到這一步,這個game是沒有game over的,你可以在每次抽新的block之前,先檢查一下座標(0,3)的4x4=16格有沒有被佔用,如果被佔用了就game over。

  2. 遊戲得分:

    1. gameBoardclass中,加入lineClear()功能。顧名思義,就是每次block鎖定時,都檢查一下有沒有橫行是已經被填滿,而清理全行,上面已經鎖定了的格全部移向下一行。

    2. 之後加入一個line clear counter,和block counterblock counter用來紀錄遊戲開始後有多少block產生,line clear counter用來紀錄有多少行被清除,兩者都是用來為遊戲計分,你可以每一個block counter計10分,每個line clear counter計100分。

  3. (optional):

    1. 一次過清除一行,line counter加1,一次過清除二行,line counter加3(1+2=3),一次過清除3行,line counter加6(1+2+3=6),如此類推,一次過清除n行,line counter 就加i=1ni

    2. (optional)標準的tetris遊戲,除了現有的block供控制外,但有下一個block的內容在等候處,令玩家預先計劃的,你可以嘗試加入這個功能。