如何运行
- 创建文件:在你的电脑上创建三个文件:
index.html、style.css和script.js。 - 复制代码:将下面提供的HTML、CSS和JavaScript代码分别复制到对应的文件中。
- 打开网页:用浏览器打开
index.html文件即可开始游戏。
HTML 代码 (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="scores-container">
<div class="score-container">
<div class="score-title">分数</div>
<span id="score">0</span>
</div>
<div class="best-container">
<div class="score-title">最高分</div>
<span id="best-score">0</span>
</div>
</div>
</header>
<div class="game-container">
<div class="game-message">
<p></p>
<div class="lower">
<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"></div>
</div>
<p class="game-explanation">
<strong>玩法说明:</strong> 使用 <strong>方向键</strong> 移动方块,当两个相同数字的方块碰撞时,它们会 <strong>合并成一个!</strong>
</p>
</div>
<script src="script.js"></script>
</body>
</html>
CSS 代码 (style.css)
这个文件负责游戏的视觉样式,包括布局、颜色和动画效果。
/* 基础样式 */
body {
font-family: Arial, sans-serif;
background-color: #faf8ef;
color: #776e65;
margin: 0;
padding: 0;
}
.container {
width: 500px;
margin: 0 auto;
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;
}
/* 分数样式 */
.scores-container {
display: flex;
gap: 10px;
}
.score-container, .best-container {
position: relative;
background: #bbada0;
padding: 15px 25px;
border-radius: 3px;
color: white;
text-align: center;
min-width: 60px;
}
.score-title {
font-size: 13px;
text-transform: uppercase;
}
#score, #best-score {
font-size: 25px;
font-weight: bold;
}
/* 游戏区域 */
.game-container {
position: relative;
background: #bbada0;
border-radius: 6px;
width: 500px;
height: 500px;
padding: 15px;
box-sizing: border-box;
}
.grid-container {
position: absolute;
z-index: 1;
}
.grid-row {
display: flex;
margin-bottom: 15px;
}
.grid-row:last-child {
margin-bottom: 0;
}
.grid-cell {
width: 106.25px;
height: 106.25px;
margin-right: 15px;
background: rgba(238, 228, 218, 0.35);
border-radius: 3px;
}
.grid-cell:last-child {
margin-right: 0;
}
/* 数字方块 */
.tile-container {
position: absolute;
z-index: 2;
}
.tile {
position: absolute;
width: 106.25px;
height: 106.25px;
font-size: 55px;
font-weight: bold;
line-height: 106.25px;
text-align: center;
border-radius: 3px;
transition: all 0.15s ease-in-out;
}
/* 不同数字的颜色 */
.tile-2 { background: #eee4da; color: #776e65; }
.tile-4 { background: #ede0c8; color: #776e65; }
.tile-8 { background: #f2b179; color: #f9f6f2; }
.tile-16 { background: #f59563; color: #f9f6f2; }
.tile-32 { background: #f67c5f; color: #f9f6f2; }
.tile-64 { background: #f65e3b; color: #f9f6f2; }
.tile-128 { background: #edcf72; color: #f9f6f2; font-size: 45px; }
.tile-256 { background: #edcc61; color: #f9f6f2; font-size: 45px; }
.tile-512 { background: #edc850; color: #f9f6f2; font-size: 45px; }
.tile-1024 { background: #edc53f; color: #f9f6f2; font-size: 35px; }
.tile-2048 { background: #edc22e; color: #f9f6f2; font-size: 35px; }
/* 新方块和合并动画 */
.tile-new {
animation: appear 0.2s ease-in-out;
}
.tile-merged {
animation: pop 0.2s ease-in-out;
z-index: 20;
}
@keyframes appear {
0% {
opacity: 0;
transform: scale(0);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes pop {
0% { transform: scale(1); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
/* 游戏消息(胜利/失败) */
.game-message {
display: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(238, 228, 218, 0.73);
z-index: 100;
text-align: center;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 6px;
}
.game-message p {
font-size: 60px;
font-weight: bold;
margin-bottom: 20px;
color: #776e65;
}
.game-message.game-won {
display: flex;
background: rgba(237, 194, 46, 0.5);
}
.game-message.game-over {
display: flex;
}
.retry-button {
display: inline-block;
background: #8f7a66;
border-radius: 3px;
padding: 0 20px;
text-decoration: none;
color: #f9f6f2;
height: 40px;
line-height: 42px;
font-weight: bold;
cursor: pointer;
}
/* 响应式设计 */
@media screen and (max-width: 520px) {
.container {
width: 320px;
padding: 10px;
}
h1 {
font-size: 50px;
}
.game-container, .grid-cell, .tile {
width: 70px;
height: 70px;
}
.grid-row {
margin-bottom: 10px;
}
.grid-cell {
width: 60px;
height: 60px;
margin-right: 10px;
}
.tile {
font-size: 35px;
line-height: 70px;
}
.tile-128, .tile-256, .tile-512 {
font-size: 30px;
}
.tile-1024, .tile-2048 {
font-size: 25px;
}
}
JavaScript 代码 (script.js)
这是游戏的核心逻辑,包含了所有功能:初始化、移动、合并、计分和判断游戏状态。
document.addEventListener('DOMContentLoaded', () => {
// 游戏状态
let grid = [];
let score = 0;
let bestScore = localStorage.getItem('bestScore') || 0;
let gameOver = false;
let gameWon = false;
// DOM 元素
const scoreDisplay = document.getElementById('score');
const bestScoreDisplay = document.getElementById('best-score');
const messageDisplay = document.querySelector('.game-message');
const retryButton = document.querySelector('.retry-button');
const tileContainer = document.querySelector('.tile-container');
// 初始化最高分显示
bestScoreDisplay.textContent = bestScore;
// 初始化游戏
function init() {
grid = Array(4).fill().map(() => Array(4).fill(0));
score = 0;
gameOver = false;
gameWon = false;
scoreDisplay.textContent = score;
messageDisplay.classList.remove('game-won', 'game-over');
messageDisplay.style.display = 'none';
clearTiles();
addNewTile();
addNewTile();
updateDisplay();
}
// 清除所有数字方块
function clearTiles() {
tileContainer.innerHTML = '';
}
// 在空白格子中随机添加一个新方块 (2或4)
function addNewTile() {
const emptyCells = [];
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; 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() {
clearTiles();
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; 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.transform = `translate(${c * 121}px, ${r * 121}px)`; // 106.25 + 15
tileContainer.appendChild(tile);
}
}
}
}
// 移动和合并逻辑
function move(direction) {
if (gameOver) return;
let moved = false;
const previousGrid = grid.map(row => [...row]);
if (direction === 'ArrowLeft' || direction === 'ArrowRight') {
for (let r = 0; r < 4; r++) {
let row = grid[r];
if (direction === 'ArrowRight') row.reverse();
row = slideAndMerge(row);
if (direction === 'ArrowRight') row.reverse();
grid[r] = row;
}
} else {
for (let c = 0; c < 4; c++) {
let column = [grid[0][c], grid[1][c], grid[2][c], grid[3][c]];
if (direction === 'ArrowDown') column.reverse();
column = slideAndMerge(column);
if (direction === 'ArrowDown') column.reverse();
for (let r = 0; r < 4; r++) {
grid[r][c] = column[r];
}
}
}
// 检查是否有移动发生
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
if (grid[r][c] !== previousGrid[r][c]) {
moved = true;
break;
}
}
}
if (moved) {
addNewTile();
updateDisplay();
checkGameState();
}
}
// 滑动和合并一行/列
function slideAndMerge(line) {
// 移除零
let newLine = line.filter(val => val !== 0);
// 合并相同的数字
for (let i = 0; i < newLine.length - 1; i++) {
if (newLine[i] === newLine[i + 1]) {
newLine[i] *= 2;
score += newLine[i];
scoreDisplay.textContent = score;
newLine.splice(i + 1, 1);
}
}
// 补充零
while (newLine.length < 4) {
newLine.push(0);
}
return newLine;
}
// 检查游戏状态
function checkGameState() {
// 检查是否获胜
if (!gameWon) {
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
if (grid[r][c] === 2048) {
gameWon = true;
messageDisplay.querySelector('p').textContent = '你赢了!';
messageDisplay.classList.add('game-won');
messageDisplay.style.display = 'flex';
return;
}
}
}
}
// 检查是否还有空格
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
if (grid[r][c] === 0) {
return; // 游戏继续
}
}
}
// 检查是否还能移动
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
const current = grid[r][c];
if (
(r < 3 && grid[r + 1][c] === current) ||
(c < 3 && grid[r][c + 1] === current)
) {
return; // 游戏继续
}
}
}
// 如果以上都不满足,则游戏结束
gameOver = true;
messageDisplay.querySelector('p').textContent = '游戏结束!';
messageDisplay.classList.add('game-over');
messageDisplay.style.display = 'flex';
// 更新最高分
if (score > bestScore) {
bestScore = score;
localStorage.setItem('bestScore', bestScore);
bestScoreDisplay.textContent = bestScore;
}
}
// 事件监听
document.addEventListener('keydown', (e) => {
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
e.preventDefault();
move(e.key);
}
});
retryButton.addEventListener('click', init);
// 启动游戏
init();
});
代码逻辑解析
-
HTML 结构:
- 一个主容器
container包含所有内容。 header显示游戏标题和分数。game-container是游戏的核心区域,包含一个静态的背景网格grid-container和一个动态的数字方块容器tile-container,数字方块会通过 JavaScript 动态添加到tile-container中。game-message用于显示“你赢了!”或“游戏结束!”的提示,默认是隐藏的。
- 一个主容器
-
CSS 样式:
(图片来源网络,侵删)- 使用 Flexbox 布局来排列标题和分数。
game-container设置为position: relative,内部的grid-container和tile-container都使用position: absolute并设置z-index,使数字方块可以显示在背景网格之上。- 为不同数值的方块定义了不同的背景颜色。
- 使用 CSS
transition和@keyframes动画,让新方块的出现和方块的合并过程更平滑。
-
JavaScript 逻辑:
- 初始化 (
init):- 创建一个 4x4 的二维数组
grid,初始值全为 0。 - 重置分数和游戏状态。
- 调用
addNewTile()两次,在空白位置随机生成两个新方块(90% 概率是 2,10% 概率是 4)。 - 调用
updateDisplay()将grid数组中的数据渲染到页面上。
- 创建一个 4x4 的二维数组
- 移动 (
move):- 根据按下的方向键(
ArrowLeft,ArrowRight,ArrowUp,ArrowDown)决定处理行还是列。 - 为了简化逻辑,
ArrowRight和ArrowDown会对行或列进行反转,然后统一当作向左或向上处理,处理完再反转回来。 - 对每一行/列调用
slideAndMerge()函数。 - 如果移动后有变化,则生成一个新方块并更新显示。
- 根据按下的方向键(
- 滑动与合并 (
slideAndMerge):- 这是核心算法,它接收一行或一列的数组。
- 第一步:过滤掉所有的 0,将非零数字靠左/靠上对齐。
- 第二步:从左到右/从上到下遍历,如果相邻的两个数字相等,就将它们合并成一个(值翻倍),并增加分数,然后移除被合并掉的数字。
- 第三步:再次过滤掉 0,确保合并后的数字紧挨着,然后在末尾补 0,使数组长度保持为 4。
- 游戏状态检查 (
checkGameState):- 检查是否出现了 2048 这个数字,如果出现了,玩家获胜。
- 检查是否还有 0,如果有,说明游戏还能继续。
- 如果没有 0,则检查是否还有相邻的相同数字,如果有,说明还能合并,游戏继续。
- 如果以上条件都不满足,则判定游戏结束。
- 事件监听:
- 监听键盘的
keydown事件,当按下方向键时触发move函数。 - 监听“新游戏”按钮的点击事件,触发
init函数重新开始。
- 监听键盘的
- 初始化 (
这个实现涵盖了2048游戏的所有基本规则和交互,代码结构良好,易于扩展。

(图片来源网络,侵删)
