前言
這是一款完全使用 HTML、CSS 和 JavaScript 打造的經典俄羅斯方塊遊戲。它不僅包含了完整的遊戲邏輯,還加入了暫停、中英切換等現代化功能。本頁面將帶您深入了解其背後的核心技術與設計思路。
1. 遊戲的基礎:網格系統
遊戲的核心是一個二維陣列(Array of Arrays),我們稱之為 board。這個網格代表了整個遊戲區域。我們透過幾個常數來定義這個網格的大小以及每個方塊在畫面上的像素尺寸。
// 遊戲常數設定
const COLS = 10; // 遊戲區塊寬度 (10個方塊寬)
const ROWS = 20; // 遊戲區塊高度 (20個方塊高)
const BLOCK_SIZE = 30; // 每個方塊的大小 (30x30 像素)
// 建立遊戲板,一個充滿 0 的二維陣列
// 0 代表空格,其他數字代表不同顏色的方塊
let board = createBoard(COLS, ROWS);
2. 方塊的形狀與顏色
遊戲中的七種方塊(Tetrominoes)是怎麼來的?我們使用兩個陣列來儲存它們的資訊:COLORS 陣列定義顏色,而 SHAPES 是一個更複雜的四維陣列,用來定義每種方塊的四種旋轉形態。
// 方塊形狀 (以 T 形方塊為例)
// 陣列的第一層:方塊種類 (T 是第 6 種)
// 陣列的第二層:旋轉狀態 (0, 1, 2, 3)
// 陣列的第三、四層:方塊本身的 2D 形狀
const SHAPES = [
// ... 其他方塊
[ // T 形方塊
[[0,6,0],[6,6,6]], // 旋轉 0 度
[[6,0],[6,6],[6,0]], // 旋轉 90 度
[[6,6,6],[0,6,0]], // 旋轉 180 度
[[0,6],[6,6],[0,6]] // 旋轉 270 度
],
// ...
];
透過存取 SHAPES[shapeId][rotation],我們就能輕易獲得任意方塊在任意旋轉角度下的形狀。
3. 遊戲主循環 (The Game Loop)
遊戲的動畫是透過 requestAnimationFrame 實現的,這比傳統的 setInterval 更高效、更流暢。在每一幀(frame),遊戲都會:
- 計算時間差,以固定的間隔讓方塊自動下落。
- 重新繪製整個遊戲版圖和當前的方塊。
- 檢查玩家的輸入(如移動、旋轉)。
let lastTime = 0;
let dropCounter = 0;
let dropInterval = 1000; // 每 1000 毫秒 (1秒) 下落一次
function gameLoop(time = 0) {
if (isPaused || !gameRunning) return;
const deltaTime = time - lastTime;
lastTime = time;
dropCounter += deltaTime;
if (dropCounter > dropInterval) {
// ... 讓方塊下落的邏輯 ...
dropCounter = 0;
}
drawBoard(); // 繪製背景
drawPiece(); // 繪製正在移動的方塊
requestAnimationFrame(gameLoop);
}
4. 核心邏輯:碰撞檢測
遊戲的靈魂在於判斷「可不可以這樣動?」。isColliding 函式負責這個重任。在方塊的每一次移動或旋轉前,它都會模擬執行後的結果,並檢查是否滿足以下任一條件:
- 方塊的任何一部分是否超出了遊戲邊界(左、右、下)。
- 方塊的任何一部分是否與已經固定的方塊重疊。
如果檢測到碰撞,該次操作(移動或旋轉)就會被取消。如果下落時發生碰撞,則方塊會被固定在當前位置。
5. 消除與計分
當一個方塊被固定後,遊戲會從下到上檢查每一行是否被完全填滿。
function clearLines() {
let linesCleared = 0;
// 從下往上遍歷每一行
for (let y = ROWS - 1; y >= 0; y--) {
// 檢查該行是否所有格子都不是 0
if (board[y].every(value => value > 0)) {
linesCleared++;
// 從 board 中移除這一行
board.splice(y, 1);
// 在 board 頂部加入一個新的空行
board.unshift(Array(COLS).fill(0));
// 因為 y 被移除了,所以要重新檢查同一個 y
y++;
}
}
// ... 根據 linesCleared 計算分數 ...
}
6. 額外功能:中英互換
為了實現多國語言,我們利用了 HTML 的 data-* 屬性。首先,定義一個包含所有翻譯文字的物件。
const translations = {
en: { score: 'Score', startGame: 'Start Game', ... },
zh: { score: '分數', startGame: '開始遊戲', ... }
};
接著,在 HTML 元素上加上 data-lang-key 標籤。
<h2 data-lang-key="score">分數</h2>
<button id="start-button" data-lang-key="startGame">開始遊戲</button>
最後,一個簡單的函式就能遍歷所有帶有此標籤的元素,並根據當前選擇的語言更新它們的文字內容,實現動態切換。
7. 遊戲架構圖
為了更清晰地理解各個模組之間的協作關係,以下是一個簡化的遊戲架構流程圖。它展示了從使用者輸入到畫面更新的完整流程。
(鍵盤 / 觸控)
遊戲主循環 (Game Loop)
requestAnimationFrame
遊戲狀態管理
更新分數、行數、等級
固定方塊、產生新方塊
畫面渲染
使用 Canvas API 繪製
board 和 currentPiece