使用JavaScript實現一個俄羅斯方塊

来源:https://www.cnblogs.com/jiasm/archive/2018/04/07/8733356.html
-Advertisement-
Play Games

清明假期期間,閑的無聊,就做了一個小游戲玩玩,目前游戲邏輯上暫未發現bug,只不過樣子稍微醜了一些-.-項目地址:https://github.com/Jiasm/tetris線上Demo:http://blog.jiasm.org/tetris/?width=16&height=40 (修改URL ...


 

清明假期期間,閑的無聊,就做了一個小游戲玩玩,目前游戲邏輯上暫未發現bug,只不過樣子稍微醜了一些-.-
項目地址:https://github.com/Jiasm/tetris
線上Demo:http://blog.jiasm.org/tetris/?width=16&height=40 (修改URL參數可以調整難度)

整體分成三塊進行開發,使用面向對象式編程進行開發(其實我更喜歡用函數式編程,但苦於游戲的一些狀態用對象來存儲會更直觀一些):

  1. Game
    1. 負責生成新的方塊
    2. 負責方塊移動的處理
    3. 方塊觸底的判斷
    4. 移除滿足清除條件的行
  2. Render
    1. 負責用Game的數據來渲染整個游戲界面
  3. Controller
    1. 負責接受用戶輸入(上下左右各種操作)並處理
    2. 向用戶反饋當前游戲的狀態

這樣分層帶來了一個好處,我們游戲的邏輯Game模塊並不依賴於當前程式運行的環境,而Render可以是CanvasDOM,甚至是控制台輸出。我們要移植到其他平臺,只需要修改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. 下移操作時,我們只需改變元素的第一層下標
  2. 判斷是否觸底時,我們只需將當前下標 + 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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 知乎在手機瀏覽器打開,會有個 App 內打開的按鈕,點擊直接打開且跳轉到該詳情頁,是不是有點神奇,是如何做到的呢? 效果預覽 Uri Scheme 配置 intent filter AndroidManifest.xml 測試網頁 main 下新建 assets 文件,寫了簡單的 Html 網 ...
  • 純命令行界面指沒有安裝Android studio。 下載sdk-tools 可以根據實際需要下載,不需要翻牆(2018-04-07) 下載後只有一個tools目錄。 安裝需要的package 查看可用的package .\tools\bin\sdkmanager --list 有些摺疊了可以加上- ...
  • 參考 "https://blog.csdn.net/yin767833376/article/details/51656402" "https://developer.mozilla.org/en US/docs/Web/API/Console Usage" Alert 優點 1. 阻塞執行 缺點 ...
  • 最近項目有個需求:用戶之間發送消息時,如果發送者輸入的信息中含有網址文本,要在接受者界面中顯示網址鏈接,點擊該鏈接直接跳轉到網頁。 這個功能和 QQ 發送網址文本的效果非常像,可以說是一模一樣的。 思路:首先,要判斷文本中是否含有網址文本,其次,將網址文本轉換為可點擊的鏈接文本,即將網址文本通... ...
  • 把昨天添加文章和編輯文章都是彈出添加文章的對話框,這個bug給解決了。 原因是函數名寫混了…… 本來是editPost,寫成addPost了…… 今天又多了新的bug,不知道哪來代碼改動了,現在編輯文章,提交的時候總是報錯。 能看到提交的數據啊,只是多加了個 “1” 而已。 具體哪來的問題也不知道, ...
  • 瞭解攝影活著美圖秀秀之類美圖軟體的同學對濾鏡肯定不陌生,CSS3對各種濾鏡效果有了支持,可以做出很多好玩兒效果,走馬觀花瞭解一下 語法 很很多CSS3屬性一樣,監獄支持情況需要使用瀏覽器首碼,CSS濾鏡支持的方法有 效果 拿圖片做例子,看看效果 原圖 以下效果都不是截圖,Chrome上看 模糊 灰度 ...
  • 代碼即教程:HTML5初識Canvas ...
  • 回調函數,或簡稱回調,是指通過函數參數傳遞到其它代碼的,某一塊可執行代碼的引用。這一設計允許了底層代碼調用在高層定義的子程式。 咋一看回調函數的概念,可能並不能立即理解什麼是回調函數。通俗的講,回調函數就是以函數作為參數傳給另一個函數執行。比如:有一個函數A,函數B, 將A函數作為B函數的參數,然... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...