当前位置: 首页 > ops >正文

动手用 Web 实现一个 2048 游戏

文章目录

    • 为什么选择 2048?
    • 关键技术点与算法详解
      • HTML 结构:搭建游戏界面
      • CSS 样式:美化游戏界面
      • JavaScript 核心逻辑:驱动游戏运行
        • 1)数据结构:二维数组表示游戏网格
        • 2)核心算法:添加随机方块
        • 3)核心算法:方块移动与合并
        • 4)事件监听与游戏流程
    • 最后

近期文章

  • 【前端练手必备】从零到一,教你用JS写出风靡全球的“贪吃蛇”!
  • Google Search Console 做SEO分析之“已发现未编入” 与 “已抓取未编入” 有什么区别?
  • 如何通过 noindex 阻止网页被搜索引擎编入索引?
  • 建站SEO优化之站点地图sitemap
  • 个人建站做SEO网站外链这一点需要注意,做错了可能受到Google惩罚
  • 一文搞懂SEO优化之站点robots.txt
  • Node.js中那些常用的进程通信方式
  • 实现篇:二叉树遍历收藏版
  • 实现篇:LRU算法的几种实现
  • 从底层视角看requestAnimationFrame的性能增强
  • Nginx Upstream了解一下
  • 一文搞懂 Markdown 文档规则

2048 游戏,这款曾经风靡全球的数字益智游戏,以其简洁的规则和深度的策略性吸引了无数玩家。它不仅是一款娱乐产品,更是许多初学者学习编程和前端开发的绝佳练手项目。今天,就来一起探索如何通过 Web 技术,一步步实现一个属于我们自己的 2048 游戏。

为什么选择 2048?

体验地址:2048游戏。2048 游戏看似简单,却蕴含了前端开发中的许多核心概念和技术:

  • DOM 操作: 游戏界面中方块的生成、移动、合并,都需要频繁地操作 HTML 元素。
  • 事件监听: 玩家通过键盘方向键控制方块移动,需要监听键盘事件。
  • 数据结构与算法: 游戏的核心逻辑,如方块的移动、合并、新方块的生成,都需要高效的数据结构(如二维数组)和相应的算法支持。
  • 响应式设计: 考虑到不同设备的屏幕尺寸,良好的响应式设计能提升用户体验。
  • 游戏逻辑与状态管理: 维护游戏得分、游戏结束判断等,需要清晰的逻辑和状态管理。

对于前端开发者来说,完成一个 2048 游戏项目,不仅能巩固基础知识,还能在实践中提升解决问题的能力,体验将创意变为现实的乐趣。

关键技术点与算法详解

HTML 结构:搭建游戏界面

游戏界面主要由一个网格容器和多个方块单元组成。

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>2048 游戏</title><link rel="stylesheet" href="style.css">
</head>
<body><div class="game-container"><h1>2048 游戏</h1><div class="score-container">分数: <span id="score">0</span></div><div id="game-grid"></div><div id="game-over-message" class="hidden">游戏结束!<button id="restart-button">重新开始</button></div></div><script src="script.js"></script>
</body>
</html>
  • game-grid:游戏网格的容器,我们将在这里动态创建方块。
  • score:显示当前得分。
  • game-over-message:游戏结束时显示的提示信息。

CSS 样式:美化游戏界面

CSS 主要负责方块的布局、颜色、动画效果等。

/* style.css */
body {font-family: Arial, sans-serif;display: flex;justify-content: center;align-items: center;min-height: 100vh;background-color: #faf8ef;margin: 0;
}.game-container {background-color: #bbada0;padding: 20px;border-radius: 6px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);text-align: center;
}#game-grid {display: grid;grid-template-columns: repeat(4, 100px); /* 4x4 网格 */grid-template-rows: repeat(4, 100px);gap: 10px;margin-top: 20px;
}.tile {width: 100px;height: 100px;background-color: #cdc1b4;border-radius: 3px;display: flex;justify-content: center;align-items: center;font-size: 35px;font-weight: bold;color: #776e65;transition: transform 0.1s ease-in-out, background-color 0.1s ease-in-out;
}/* 不同数字方块的背景色和文字颜色 */
.tile-2 { background-color: #eee4da; color: #776e65; }
.tile-4 { background-color: #ede0c8; color: #776e65; }
.tile-8 { background-color: #f2b179; color: #f9f6f2; }
.tile-16 { background-color: #f59563; color: #f9f6f2; }
.tile-32 { background-color: #f67c5f; color: #f9f6f2; }
.tile-64 { background-color: #f65e3b; color: #f9f6f2; }
.tile-128 { background-color: #edcf72; color: #f9f6f2; }
.tile-256 { background-color: #edcc61; color: #f9f6f2; }
.tile-512 { background-color: #edc850; color: #f9f6f2; }
.tile-1024 { background-color: #edc53f; color: #f9f6f2; }
.tile-2048 { background-color: #edc22e; color: #f9f6f2; }.hidden {display: none;
}
  • grid 布局:轻松实现 4x4 的网格布局。
  • .tile:定义方块的基础样式。
  • .tile-X:根据方块的数值设置不同的背景色和文字颜色,增加视觉效果。
  • transition:为方块的移动和颜色变化添加平滑过渡动画。

JavaScript 核心逻辑:驱动游戏运行

JavaScript 是游戏的核心。

1)数据结构:二维数组表示游戏网格

我们使用一个 4x4 的二维数组来存储游戏网格中每个位置的数字。

// script.js
const GRID_SIZE = 4;
let gameGrid = []; // 存储游戏数据的二维数组
let score = 0;
let gameOver = false;const gameGridElement = document.getElementById('game-grid');
const scoreElement = document.getElementById('score');
const gameOverMessageElement = document.getElementById('game-over-message');
const restartButton = document.getElementById('restart-button');// 初始化游戏
function initializeGame() {gameGrid = Array(GRID_SIZE).fill(0).map(() => Array(GRID_SIZE).fill(0));score = 0;gameOver = false;scoreElement.textContent = score;gameOverMessageElement.classList.add('hidden');renderGrid();addRandomTile();addRandomTile();
}// 渲染游戏网格到 DOM
function renderGrid() {gameGridElement.innerHTML = ''; // 清空现有方块for (let r = 0; r < GRID_SIZE; r++) {for (let c = 0; c < GRID_SIZE; c++) {const tileValue = gameGrid[r][c];const tileElement = document.createElement('div');tileElement.classList.add('tile');if (tileValue > 0) {tileElement.textContent = tileValue;tileElement.classList.add(`tile-${tileValue}`);}gameGridElement.appendChild(tileElement);}}
}
  • gameGrid:一个二维数组,gameGrid[r][c] 表示 (r, c) 位置的方块数值,0 表示空。
  • initializeGame():初始化游戏状态,清空网格,重置分数,并生成两个初始方块。
  • renderGrid():根据 gameGrid 的数据,动态创建或更新 DOM 中的方块。
2)核心算法:添加随机方块

游戏开始和每次有效移动后,需要随机生成一个 2 或 4 的方块。

// 添加随机方块
function addRandomTile() {const emptyCells = [];for (let r = 0; r < GRID_SIZE; r++) {for (let c = 0; c < GRID_SIZE; c++) {if (gameGrid[r][c] === 0) {emptyCells.push({ r, c });}}}if (emptyCells.length > 0) {const randomIndex = Math.floor(Math.random() * emptyCells.length);const { r, c } = emptyCells[randomIndex];// 90% 的概率生成 2,10% 生成 4gameGrid[r][c] = Math.random() < 0.9 ? 2 : 4;}
}
  • emptyCells:找到所有空闲的格子。
  • 随机选择一个空闲格子,并为其赋值 2 或 4。
3)核心算法:方块移动与合并

这是 2048 游戏最核心的逻辑。我们将实现向上、下、左、右四个方向的移动。以向左移动为例:

  • 遍历每一行: 对每一行独立进行操作。
  • 过滤非零元素: 将当前行中所有非零的方块提取出来。
  • 合并相邻相同元素: 从左到右遍历提取出的方块,如果相邻的两个方块数值相同,则合并它们(前一个方块数值翻倍,后一个方块数值变为 0)。合并后,分数增加。
  • 填充新行: 将合并后的非零方块从左到右填充到新的一行中,其余位置用 0 填充。
// 移动方块的核心函数
function slideTiles(row) {// 过滤掉所有 0let filteredRow = row.filter(num => num !== 0);// 合并相邻相同的数字for (let i = 0; i < filteredRow.length - 1; i++) {if (filteredRow[i] === filteredRow[i + 1]) {filteredRow[i] *= 2;score += filteredRow[i];filteredRow[i + 1] = 0; // 被合并的方块清零}}// 再次过滤掉所有 0filteredRow = filteredRow.filter(num => num !== 0);// 填充 0 到末尾while (filteredRow.length < GRID_SIZE) {filteredRow.push(0);}return filteredRow;
}// 处理向上移动
function moveUp() {let moved = false;for (let c = 0; c < GRID_SIZE; c++) {// 提取列数据let column = [];for (let r = 0; r < GRID_SIZE; r++) {column.push(gameGrid[r][c]);}let oldColumn = [...column]; // 复制一份旧的列数据let newColumn = slideTiles(column); // 对列数据进行滑动合并// 更新列数据到 gameGridfor (let r = 0; r < GRID_SIZE; r++) {gameGrid[r][c] = newColumn[r];}// 检查是否有移动发生if (JSON.stringify(oldColumn) !== JSON.stringify(newColumn)) {moved = true;}}return moved;
}// 处理向下移动 (类似 moveUp,但数组需要反转)
function moveDown() {let moved = false;for (let c = 0; c < GRID_SIZE; c++) {let column = [];for (let r = GRID_SIZE - 1; r >= 0; r--) { // 从下往上提取column.push(gameGrid[r][c]);}let oldColumn = [...column];let newColumn = slideTiles(column);for (let r = 0; r < GRID_SIZE; r++) {gameGrid[GRID_SIZE - 1 - r][c] = newColumn[r]; // 从下往上填充}if (JSON.stringify(oldColumn) !== JSON.stringify(newColumn)) {moved = true;}}return moved;
}// 处理向左移动
function moveLeft() {let moved = false;for (let r = 0; r < GRID_SIZE; r++) {let oldRow = [...gameGrid[r]]; // 复制一份旧的行数据gameGrid[r] = slideTiles(gameGrid[r]); // 对行数据进行滑动合并if (JSON.stringify(oldRow) !== JSON.stringify(gameGrid[r])) {moved = true;}}return moved;
}// 处理向右移动 (类似 moveLeft,但数组需要反转)
function moveRight() {let moved = false;for (let r = 0; r < GRID_SIZE; r++) {let row = [...gameGrid[r]].reverse(); // 反转行数据let oldRow = [...row];let newRow = slideTiles(row);gameGrid[r] = newRow.reverse(); // 再次反转填充if (JSON.stringify(oldRow) !== JSON.stringify(newRow)) {moved = true;}}return moved;
}
  • slideTiles(row):这是核心的滑动合并逻辑,它接受一个一维数组(行或列),并返回处理后的新数组。
  • moveUp(), moveDown(), moveLeft(), moveRight():分别调用 slideTiles 对相应的行或列进行处理。需要注意的是,对于向上和向左移动,直接处理即可;对于向下和向右移动,需要先将行/列反转,处理后再反转回来。
  • moved 变量:用于判断本次移动是否实际改变了游戏盘面,以便决定是否生成新的方块。
4)事件监听与游戏流程

我们需要监听键盘的方向键事件,并根据按下的方向调用相应的移动函数。

// 键盘事件监听
document.addEventListener('keyup', handleKeyPress);function handleKeyPress(event) {if (gameOver) return;let moved = false;switch (event.key) {case 'ArrowUp':moved = moveUp();break;case 'ArrowDown':moved = moveDown();break;case 'ArrowLeft':moved = moveLeft();break;case 'ArrowRight':moved = moveRight();break;default:return; // 忽略其他按键}if (moved) {addRandomTile();renderGrid();updateScore();checkGameOver();}
}// 更新分数显示
function updateScore() {scoreElement.textContent = score;
}// 检查游戏是否结束
function checkGameOver() {// 检查是否有空位for (let r = 0; r < GRID_SIZE; r++) {for (let c = 0; c < GRID_SIZE; c++) {if (gameGrid[r][c] === 0) {return; // 还有空位,游戏未结束}}}// 检查是否还有可合并的方块for (let r = 0; r < GRID_SIZE; r++) {for (let c = 0; c < GRID_SIZE; c++) {const current = gameGrid[r][c];// 检查右侧if (c < GRID_SIZE - 1 && current === gameGrid[r][c + 1]) {return;}// 检查下方if (r < GRID_SIZE - 1 && current === gameGrid[r + 1][c]) {return;}}}gameOver = true;gameOverMessageElement.classList.remove('hidden');
}// 重新开始按钮
restartButton.addEventListener('click', initializeGame);// 游戏初始化
initializeGame();
  • handleKeyPress():根据按下的方向键调用不同的移动函数。
  • 在每次有效移动后:
    • addRandomTile():生成新的随机方块。
    • renderGrid():更新 UI。
    • updateScore():更新分数。
    • checkGameOver():判断游戏是否结束(没有空位且没有可合并的方块)。
  • restartButton:点击重新开始按钮可以重置游戏。

最后

通过 HTML 搭建结构,CSS 美化外观,JavaScript 实现核心逻辑,我们就成功地实现了一个基础的 2048 游戏。这个项目不仅是一个很好的前端入门实践,更可以在此基础上进行扩展:

  • 动画效果优化: 可以使用 CSS transformtransition 结合 JavaScript,实现更流畅的方块移动和合并动画。
  • 触摸事件支持: 适配移动端,实现滑动操作。
  • 保存/加载游戏: 使用 localStorage 将游戏进度保存到本地。
  • AI 玩家: 尝试实现一个简单的 AI 算法来玩 2048。
  • 计分板: 记录最高分数。

现在,你就可以动手尝试构建你自己的 2048 游戏了!亲自动手实现一遍还是大有益处。

  • 体验地址
  • 体验地址2
http://www.xdnf.cn/news/14575.html

相关文章:

  • 如何预防电磁铁损坏
  • Data Vault 初探(九) —— 定期装载_Kettle_附属表
  • Java性能优化权威指南-操作系统性能监控
  • HarmonyOS NEXT应用元服务布局优化ArkUI框架执行流程
  • 从java角度理解io多路复用和redis为什么使用io多路复用
  • PixPin:一个强大且免费的截图贴图工具
  • SpringBoot+Vue服装商城系统 附带详细运行指导视频
  • 群晖 NAS Docker 镜像加速配置详细教程
  • 开源 python 应用 开发(二)基于pyautogui、open cv 视觉识别的工具自动化
  • RSA加密原理及推导
  • Qt项目,记事本
  • 【JS-4.4-键盘常用事件】深入理解DOM键盘事件:提升用户交互体验的关键
  • 【unitrix】 4.0 类型级数值表示系统(types.rs)
  • Java的锁机制问题
  • Linux之网络的基础认识
  • KES数据库部署工具使用
  • 系统思考VS心智模式
  • CSP-S 模拟赛一总结(T1、T2)
  • AI大模型提示词工程研究报告:长度与效果的辩证分析
  • 数据结构转换与离散点生成
  • Python 爬虫案例(不定期更新)
  • XCVU47P-2FSVH2892E Xilinx Virtex UltraScale+ FPGA AMD
  • 2025年渗透测试面试题总结-2025年HW(护网面试) 03(题目+回答)
  • C++ 第一阶段项目一:实现简易计算器
  • 正则表达式与C++
  • RA4M2开发IOT(0)----安装e² studio
  • Linux第一个系统程序-进度条(14)
  • Redis 分布式锁、红锁分别是什么?红锁有什么问题?
  • 说说什么是幂等性?
  • 神经网络中的交叉熵(Cross-Entropy)损失函数详解