- 项目结构与 HTML
- CSS 样式设计
- JavaScript 游戏逻辑
- 初始化游戏
- 渲染游戏盘面
- 移动和合并逻辑
- 键盘事件监听
- 游戏状态判断(胜利/失败)
- 分数计算
项目结构与 HTML
我们创建三个文件:index.html, style.css, 和 script.js。

(图片来源网络,侵删)
index.html
这是游戏的骨架,包含了游戏标题、分数显示、游戏盘面以及一个重新开始按钮。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">2048 游戏</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<header>
<h1>2048</h1>
<div class="score-container">
<div class="score-box">
<span class="score-label">分数</span>
<span id="score">0</span>
</div>
<div class="score-box">
<span class="score-label">最高分</span>
<span id="best-score">0</span>
</div>
</div>
</header>
<main>
<div class="game-container">
<div class="game-message">
<p></p>
<div class="lower">
<a class="keep-playing-button">继续游戏</a>
<a class="retry-button">再玩一次</a>
</div>
</div>
<div class="grid-container">
<!-- 4x4 的网格背景 -->
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
</div>
<!-- 游戏棋盘,用于放置数字方块 -->
<div class="tile-container" id="tile-container">
</div>
</div>
<p class="game-explanation">
<strong>游戏玩法:</strong> 使用 <strong>方向键</strong> 移动方块,当两个相同数字的方块碰撞时,它们会合并成一个!
</p>
<button class="new-game-button" id="new-game-button">新游戏</button>
</main>
</div>
<script src="script.js"></script>
</body>
</html>
CSS 样式设计
style.css
这部分负责美化游戏界面,使其看起来像真正的 2048。
/* 基础样式和变量 */
:root {
--grid-background-color: #bbada0;
--grid-cell-size: 100px;
--grid-cell-gap: 10px;
--tile-color-2: #eee4da;
--tile-color-4: #ede0c8;
--tile-color-8: #f2b179;
--tile-color-16: #f59563;
--tile-color-32: #f67c5f;
--tile-color-64: #f65e3b;
--tile-color-128: #edcf72;
--tile-color-256: #edcc61;
--tile-color-512: #edc850;
--tile-color-1024: #edc53f;
--tile-color-2048: #edc22e;
}
body {
font-family: "Clear Sans", "Helvetica Neue", Arial, sans-serif;
background-color: #faf8ef;
color: #776e65;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
.container {
text-align: center;
max-width: 500px;
width: 100%;
padding: 20px;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
h1 {
font-size: 80px;
font-weight: bold;
margin: 0;
color: #776e65;
}
.score-container {
display: flex;
gap: 10px;
}
.score-box {
background-color: #bbada0;
padding: 10px 20px;
border-radius: 3px;
color: white;
position: relative;
}
.score-label {
font-size: 13px;
text-transform: uppercase;
}
#score, #best-score {
font-size: 25px;
font-weight: bold;
}
/* 游戏区域 */
.game-container {
position: relative;
background-color: var(--grid-background-color);
border-radius: 6px;
padding: var(--grid-cell-gap);
width: calc(var(--grid-cell-size) * 4 + var(--grid-cell-gap) * 5);
margin: 0 auto;
}
.grid-container {
position: absolute;
z-index: 1;
}
.grid-row {
display: flex;
margin-bottom: var(--grid-cell-gap);
}
.grid-row:last-child {
margin-bottom: 0;
}
.grid-cell {
width: var(--grid-cell-size);
height: var(--grid-cell-size);
background-color: rgba(238, 228, 218, 0.35);
border-radius: 3px;
}
/* 数字方块 */
.tile-container {
position: absolute;
z-index: 2;
top: var(--grid-cell-gap);
left: var(--grid-cell-gap);
}
.tile {
position: absolute;
width: var(--grid-cell-size);
height: var(--grid-cell-size);
font-size: 55px;
font-weight: bold;
line-height: var(--grid-cell-size);
text-align: center;
border-radius: 3px;
transition: all 0.15s ease-in-out;
z-index: 10;
}
.tile-2 { background-color: var(--tile-color-2); color: #776e65; }
.tile-4 { background-color: var(--tile-color-4); color: #776e65; }
.tile-8 { background-color: var(--tile-color-8); color: #f9f6f2; }
.tile-16 { background-color: var(--tile-color-16); color: #f9f6f2; }
.tile-32 { background-color: var(--tile-color-32); color: #f9f6f2; }
.tile-64 { background-color: var(--tile-color-64); color: #f9f6f2; }
.tile-128 { background-color: var(--tile-color-128); color: #f9f6f2; font-size: 45px; }
.tile-256 { background-color: var(--tile-color-256); color: #f9f6f2; font-size: 45px; }
.tile-512 { background-color: var(--tile-color-512); color: #f9f6f2; font-size: 45px; }
.tile-1024 { background-color: var(--tile-color-1024); color: #f9f6f2; font-size: 35px; }
.tile-2048 { background-color: var(--tile-color-2048); color: #f9f6f2; font-size: 35px; }
/* 游戏结束消息 */
.game-message {
display: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.73);
z-index: 100;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 6px;
}
.game-message.game-won, .game-message-game-over {
display: flex;
}
.game-message p {
font-size: 60px;
font-weight: bold;
margin-bottom: 20px;
}
.game-won p { color: #f9f6f2; }
.game-over p { color: #776e65; }
.lower {
display: flex;
gap: 10px;
}
a.keep-playing-button, a.retry-button, button.new-game-button {
background: #8f7a66;
border-radius: 3px;
padding: 0 20px;
text-decoration: none;
color: #f9f6f2;
height: 40px;
line-height: 40px;
display: inline-block;
text-align: center;
font-weight: bold;
cursor: pointer;
border: none;
font-size: 18px;
}
a.keep-playing-button:hover, a.retry-button:hover, button.new-game-button:hover {
background: #9f8b77;
}
.game-explanation {
margin-top: 30px;
line-height: 1.5;
font-size: 16px;
}
JavaScript 游戏逻辑
这是游戏的核心部分,我们将在这里实现所有功能。
script.js

(图片来源网络,侵删)
document.addEventListener('DOMContentLoaded', () => {
// 游戏配置
const GRID_SIZE = 4;
const CELL_SIZE = 100;
const CELL_GAP = 10;
// DOM 元素
const tileContainer = document.getElementById('tile-container');
const scoreDisplay = document.getElementById('score');
const bestScoreDisplay = document.getElementById('best-score');
const newGameButton = document.getElementById('new-game-button');
const gameMessage = document.querySelector('.game-message');
const messageText = gameMessage.querySelector('p');
const retryButton = document.querySelector('.retry-button');
// 游戏状态
let grid = [];
let score = 0;
let bestScore = localStorage.getItem('bestScore') || 0;
bestScoreDisplay.textContent = bestScore;
// 初始化游戏
function initGame() {
grid = Array(GRID_SIZE).fill().map(() => Array(GRID_SIZE).fill(0));
score = 0;
scoreDisplay.textContent = score;
gameMessage.classList.remove('game-won', 'game-over');
tileContainer.innerHTML = '';
addNewTile();
addNewTile();
updateDisplay();
}
// 在空白格子中随机添加一个新方块 (2 or 4)
function addNewTile() {
const emptyCells = [];
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (grid[r][c] === 0) {
emptyCells.push({ r, c });
}
}
}
if (emptyCells.length > 0) {
const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
grid[randomCell.r][randomCell.c] = Math.random() < 0.9 ? 2 : 4;
}
}
// 更新整个游戏盘面的显示
function updateDisplay() {
tileContainer.innerHTML = '';
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (grid[r][c] !== 0) {
const tile = document.createElement('div');
tile.className = `tile tile-${grid[r][c]}`;
tile.textContent = grid[r][c];
tile.style.left = `${c * (CELL_SIZE + CELL_GAP)}px`;
tile.style.top = `${r * (CELL_SIZE + CELL_GAP)}px`;
tileContainer.appendChild(tile);
}
}
}
}
// 移动逻辑
function move(direction) {
let moved = false;
const previousGrid = grid.map(row => [...row]); // 深拷贝当前状态
if (direction === 'ArrowLeft') {
moved = moveLeft();
} else if (direction === 'ArrowRight') {
moved = moveRight();
} else if (direction === 'ArrowUp') {
moved = moveUp();
} else if (direction === 'ArrowDown') {
moved = moveDown();
}
if (moved) {
addNewTile();
updateDisplay();
checkGameState();
}
}
// 向左移动
function moveLeft() {
let moved = false;
for (let r = 0; r < GRID_SIZE; r++) {
const row = grid[r].filter(val => val !== 0);
for (let c = 0; c < row.length - 1; c++) {
if (row[c] === row[c + 1]) {
row[c] *= 2;
score += row[c];
scoreDisplay.textContent = score;
row.splice(c + 1, 1);
}
}
while (row.length < GRID_SIZE) {
row.push(0);
}
if (JSON.stringify(grid[r]) !== JSON.stringify(row)) {
moved = true;
}
grid[r] = row;
}
return moved;
}
// 向右移动
function moveRight() {
let moved = false;
for (let r = 0; r < GRID_SIZE; r++) {
const row = grid[r].filter(val => val !== 0);
for (let c = row.length - 1; c > 0; c--) {
if (row[c] === row[c - 1]) {
row[c] *= 2;
score += row[c];
scoreDisplay.textContent = score;
row.splice(c - 1, 1);
c--; // 因为删除了一个元素,索引需要回退
}
}
while (row.length < GRID_SIZE) {
row.unshift(0);
}
if (JSON.stringify(grid[r]) !== JSON.stringify(row)) {
moved = true;
}
grid[r] = row;
}
return moved;
}
// 向上移动
function moveUp() {
let moved = false;
for (let c = 0; c < GRID_SIZE; c++) {
const column = [];
for (let r = 0; r < GRID_SIZE; r++) {
if (grid[r][c] !== 0) {
column.push(grid[r][c]);
}
}
for (let r = 0; r < column.length - 1; r++) {
if (column[r] === column[r + 1]) {
column[r] *= 2;
score += column[r];
scoreDisplay.textContent = score;
column.splice(r + 1, 1);
}
}
while (column.length < GRID_SIZE) {
column.push(0);
}
for (let r = 0; r < GRID_SIZE; r++) {
if (grid[r][c] !== column[r]) {
moved = true;
}
grid[r][c] = column[r];
}
}
return moved;
}
// 向下移动
function moveDown() {
let moved = false;
for (let c = 0; c < GRID_SIZE; c++) {
const column = [];
for (let r = 0; r < GRID_SIZE; r++) {
if (grid[r][c] !== 0) {
column.push(grid[r][c]);
}
}
for (let r = column.length - 1; r > 0; r--) {
if (column[r] === column[r - 1]) {
column[r] *= 2;
score += column[r];
scoreDisplay.textContent = score;
column.splice(r - 1, 1);
r--;
}
}
while (column.length < GRID_SIZE) {
column.unshift(0);
}
for (let r = 0; r < GRID_SIZE; r++) {
if (grid[r][c] !== column[r]) {
moved = true;
}
grid[r][c] = column[r];
}
}
return moved;
}
// 检查游戏状态 (胜利或失败)
function checkGameState() {
// 检查是否获胜 (出现 2048)
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (grid[r][c] === 2048) {
gameMessage.classList.add('game-won');
messageText.textContent = '你赢了!';
return;
}
}
}
// 检查是否还有空格
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (grid[r][c] === 0) {
return; // 游戏继续
}
}
}
// 检查是否还能移动
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
const current = grid[r][c];
// 检查右边
if (c < GRID_SIZE - 1 && grid[r][c + 1] === current) {
return; // 可以合并,游戏继续
}
// 检查下边
if (r < GRID_SIZE - 1 && grid[r + 1][c] === current) {
return; // 可以合并,游戏继续
}
}
}
// 如果以上条件都不满足,则游戏结束
gameMessage.classList.add('game-over');
messageText.textContent = '游戏结束!';
// 更新最高分
if (score > bestScore) {
bestScore = score;
bestScoreDisplay.textContent = bestScore;
localStorage.setItem('bestScore', bestScore);
}
}
// 事件监听
document.addEventListener('keydown', (event) => {
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
event.preventDefault();
move(event.key);
}
});
newGameButton.addEventListener('click', initGame);
retryButton.addEventListener('click', initGame);
// 启动游戏
initGame();
});
如何运行
- 将以上三段代码分别保存为
index.html,style.css, 和script.js。 - 确保这三个文件在同一个文件夹中。
- 用浏览器打开
index.html文件。
你应该能看到一个可以玩的 2048 游戏了!你可以使用键盘方向键来控制方块移动和合并,祝你玩得开心!

(图片来源网络,侵删)
