使用JavaScript實現一個俄羅斯方塊
清明假期期間,閒的無聊,就做了一個小遊戲玩玩,目前遊戲邏輯上暫未發現bug,只不過樣子稍微醜了一些-.-
專案地址:https://github.com/Jiasm/tetris
線上Demo:http://blog.jiasm.org/tetris/?width=16&height=40 (修改URL引數可以調整難度)
整體分成三塊進行開發,使用物件導向式程式設計進行開發(其實我更喜歡用函數語言程式設計,但苦於遊戲的一些狀態用物件來儲存會更直觀一些):
-
Game
:- 負責生成新的方塊
- 負責方塊移動的處理
- 方塊觸底的判斷
- 移除滿足清除條件的行
-
Render
:- 負責用
Game
的資料來渲染整個遊戲介面
- 負責用
-
Controller
:- 負責接受使用者輸入(上下左右各種操作)並處理
- 向使用者反饋當前遊戲的狀態
這樣分層帶來了一個好處,我們遊戲的邏輯Game
模組並不依賴於當前程式執行的環境,而Render
可以是Canvas
、DOM
,甚至是控制檯輸出。我們要移植到其他平臺,只需要修改Render
即可。
專案結構
忽略了一些與遊戲沒有直接關係的結構
.
├── model
│ ├── Brick.js
│ ├── Game.js
│ └── index.js
├── utils
│ ├── buildEnum.js
│ ├── deepCopy.js
│ ├── getShape.js
│ ├── index.js
│ ├── lineIndex.js
│ ├── matrixString.js
│ └── rotateArray.js
├── enum
│ ├── gameType.js
│ ├── index.js
│ └── pointType.js
├── data
│ └── shapes.js
├── controller
│ └── index.js
└── view
├── RenderCanvas.js
└── index.js
各目錄下的index.js是為了方便同時引用多個檔案,大致長這個樣子:
export { default as model1 } from `./model1` export { default as model2 } from `./model2`
然後我們就可以在用到的地方寫:
import { model1, model2 } from `./XXX`
model
這裡是遊戲的核心邏輯所在位置。
像俄羅斯方塊這種的矩陣類遊戲,儲存資料最合適的方法就是一個二維陣列了。
為了更直觀一些,我們選擇了遊戲的高度作為第一層陣列的長度:
matrix = new Array(height).fill(new Array(width)) // width: 2 height: 4 [ [ 1, 1], [ 1, 1], [ 1, 1], [ 1, 1] ]
而且這樣選擇在一些邏輯處理上也會更方便一些:
- 下移操作時,我們只需改變元素的第一層下標
- 判斷是否觸底時,我們只需將當前下標 + 1 判斷是否有元素即可
我們對陣列中的元素進行了定義:
-
0
: 空,表示當前座標為空白 -
1
: 新的方塊,表示當前活動的方塊 -
2
: 老的方塊,已經觸底固定的方塊
接下來,我們就遇到了一個問題,如何處理方塊的放置。
我們知道,遊戲會不停的向棋盤中載入新的方塊。
如果我們每次處理下移的時候,都將當前二維陣列中對應的方塊元素移除,然後在塞入到新的位置,未免太過繁瑣了。
所以我們在初始化資料時,初始化兩個二維陣列。
當我們載入一個新的方塊後,將方塊對應的元素塞入其中的一個二維陣列。
然後等到我們有進行其他的操作時,比如左右移動,向下之類的。
我們直接使用第二個二維陣列覆蓋到當前的陣列中去,然後再將更改下標後的方塊塞入陣列。
這樣在資料上,我們就完成了方塊的移動。
class Game { init () { // 初始化兩個矩陣 this.matrix = [[], []] this.oldMatrix = [[], []] } move () { // 重置當前矩陣資料 this.matrix = deepCopy(this.oldMatrix) // 解除引用 // 載入方塊資料 this.matrix[y][x1] = 1 this.matrix[y][x2] = 1 } }
左右移動的處理
左右的移動不能像向下移動一樣,單純的下標+1。
我們需要判斷當前的操作是否有效。
比如右側如果遇到了障礙物或者到達邊緣,我們肯定是不能夠再進行移動的。
// blend 為活動磚塊的形狀描述 [[1, 1, 1], [0, 1, 0]] 類似這樣的結構 if ( x >= width - brickWidth || blend.some((row, rowIndex) => { let _pos = oldMatrix[y + rowIndex] return row && row[brickWidth - 1] && _pos && _pos[x + brickWidth] }) ) return // 右側有障礙物,無法移動
使用類似這樣的邏輯進行判斷,保證當前方塊向右移動後不會覆蓋之前的方塊。
快速向下的處理
我看有些遊戲實現的,貌似下降觸發只是加速下降而已(這種情況只需要改變定時下降的速度即可)-.-這裡的實現是,直接觸底
所以就會遇到一個問題,當前磚塊最多可以下降到什麼位置?
[1, 1, 1]
[0, 0, 0]
[0, 2, 0]
[2, 2, 2]
就像這樣的一個資料,0|2
這兩列都可以向下移動兩列,但是這樣就會導致中間一列的重疊。
我們一定要取出下降幅度最小的那個值。
所以我們就要算出最後一行1的下標以及第一行2的下標,將這兩個下標進行相減,最小值即為我們當前方塊可下降的距離。
旋轉方塊的處理
旋轉方塊應該是遊戲中比較複雜的一塊邏輯了。
絕不是僅僅簡單的將方塊的二維陣列由行改為列,在有些時候,我們還需要判斷方塊是否可以進行旋轉。
就像這樣的,中間的綠色長條是不能夠進行旋轉的。
所以我們要先拿到旋轉後的資料,來與當前遊戲中的資料進行比較,檢驗是否會出現重疊的情況,如果出現了,則表示不能夠進行旋轉。
觸底檢測
每完成一個移動的動作後,我們都需要進行方塊的觸底檢測。
也就是判斷當前方塊下,是否已經有元素佔位,如果有的話,則表示已經觸底了,當前元素就會被固定進矩陣陣列中。
同樣的,我們在判斷時,不需要將方塊所有的下標都檢查一遍,只需要檢查最底部一層的有效元素即可。
[1, 1],
[0, 1],
[0, 1],
像這樣的一個方塊,我們僅需要判斷第一列的第二行&第二列的第四行是否有元素即可完成檢查。
移除行
當某一行被填滿元素後,我們就要將它進行移除。
在觸底檢測觸發後,如果有方塊被固定進陣列,此時我們再進行移除行的操作。
因為如果沒有新的方塊進入,移除行的這步操作就不是必要的。
同時,得分的計數也應該在此處進行,我們將移除的行數進行記錄,獲取到的行數便是得分了。
至此,所有有關矩陣資料的操作就結束了。Game
物件只去維護這麼一個二維陣列,物件本身不包含任何遊戲相關的操作,只會在被呼叫時進行對應的處理。
然後生成新的二維陣列。
utils
這裡放置了一些比較通用的方法,用來提高開發效率使用。
比如獲取方塊最底部一層的下標之類的工具函式。
enum
存放了一些狀態的列舉,遊戲狀態以及方塊所對應的狀態,類似這樣的資料:
{ empty: 0, newBrick: 1, oldBrick: 2 }
data
存放了遊戲中各種使用到的方塊資訊。
正方形,梯形之類的方塊在二維陣列中所對應的描述。
controller
就是上邊我們所說的,用來與使用者互動的模組,由Controller
來獲取遊戲相關的資訊,並呼叫Render
進行渲染。
監聽鍵盤事件,在頁面中渲染一些控制按鈕。
以及定時觸發Game
的下落方法。
view
遊戲介面的渲染部分,目前選定的是使用canvas
,所以只寫了RenderCanvas
。
在渲染的這部分,稍微做了一些優化處理,將活動中的方塊與固定的方塊進行分開渲染。
這樣在使用者操作上下左右移動時,並不會重新渲染整個遊戲佈局,而只是渲染活動方塊的canvas
。
小記
兩天多的時間進行開發,其中有半天時間在修復FlowType
的Warning提示。。。
搞完了以後,覺得實現這個的主要難點就在於方塊旋轉&觸底的判斷這裡了。
能夠清晰的管理遊戲對應的二維陣列,這個遊戲開發起來就會很順暢。
介面還有待優化。
Tips
我的部落格即將搬運同步至騰訊雲+社群,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=1vas1z072yivn
相關文章
- canvas實現俄羅斯方塊Canvas
- 一個javascript指令碼寫的俄羅斯方塊 (轉)JavaScript指令碼
- JavaScript 寫遊戲 : 俄羅斯方塊 (轉)JavaScript遊戲
- 一個俄羅斯方塊的原始碼 (轉)原始碼
- 一個俄羅斯方塊遊戲源程式 (轉)遊戲
- 俄羅斯方塊聯機小遊戲的實現遊戲
- c#實現簡單的俄羅斯方塊C#
- Flutter Web 實戰 - 俄羅斯方塊FlutterWeb
- win32-c語言實現俄羅斯方塊Win32C語言
- 初學者——Java之實現簡易俄羅斯方塊Java
- Swift實現俄羅斯方塊詳細思路及原始碼Swift原始碼
- Tetris 俄羅斯方塊遊戲遊戲
- Python:遊戲:300行程式碼實現俄羅斯方塊Python遊戲行程
- 俄羅斯方塊的資料結構及實現 struct of a tetris資料結構Struct
- 【Java遊戲】java俄羅斯方塊!Java遊戲
- Win32俄羅斯方塊Win32
- 俄羅斯方塊(JS+CSS)JSCSS
- 我的俄羅斯方塊程式
- 最新《 java實戰開發俄羅斯方塊教程》Java
- Python 實戰開發俄羅斯方塊遊戲Python遊戲
- jQuery實現俄羅斯方塊中遇到的問題及解決方法jQuery
- 使用C#和MonoGame開發俄羅斯方塊遊戲C#MonoGAM遊戲
- java控制檯版本 俄羅斯方塊Java
- 鵬躍俄羅斯方塊遊戲 1.0遊戲
- 300行Python程式碼實現俄羅斯方塊,致敬逝去的童年Python
- 一個dos下tc編的俄羅斯方塊源程式(附註釋) (轉)
- 用kotlin來實現一個打方塊的小遊戲Kotlin遊戲
- 請大家談談‘俄羅斯方塊程式
- JS編寫的俄羅斯方塊 (轉)JS
- 使用Javascript實現小型區塊鏈JavaScript區塊鏈
- 【補檔STM32】STM32F103俄羅斯方塊遊戲實現遊戲
- Python3+pygame實現的俄羅斯方塊 程式碼完整 有演示效果PythonGAM
- 基於Flutter的俄羅斯方塊小遊戲Flutter遊戲
- 基於MonoGame重製《俄羅斯方塊》遊戲MonoGAM遊戲
- 用JS實現方塊碰撞JS
- javascript實現核取方塊全選和取消程式碼分析JavaScript
- 前端筆記之JavaScript物件導向(四)元件化開發&輪播圖|俄羅斯方塊實戰前端筆記JavaScript物件元件化
- HTML5 canvas遊戲開發實戰 6 : 俄羅斯方塊HTMLCanvas遊戲開發