文章目录

    • 为什么选择 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.pswp.cn/pingmian/86124.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/86124.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/86124.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

frp v0.62.1内网穿透搭建和使用

官网&#xff1a;https://gofrp.org/zh-cn/ Github&#xff1a;https://github.com/fatedier/frp 开源项目 frp frp 是一种快速反向代理&#xff0c;允许您将位于 NAT 或防火墙后面的本地服务器公开给 Internet。目前支持 TCP 和 UDP&#xff0c;以及 HTTP 和 HTTPS 协议&…

如何使用 USB 数据线将文件从 PC 传输到 iPhone

虽然用 USB 数据线将文件从 PC 传输到安卓设备非常容易&#xff0c;但对于 iPhone 用户来说&#xff0c;情况就不同了。不过&#xff0c;幸运的是&#xff0c;我们找到了三种可靠的方法&#xff0c;可以使用 USB 数据线将文件从 PC 传输到 iPhone&#xff0c;让您轻松完成这项任…

【C++高阶三】AVL树深度剖析

【C高阶三】AVL树深度剖析 1.什么是AVL树2.AVL树的实现2.1节点类和基本结构2.2插入2.3旋转处理2.3.1左单旋2.3.2右单旋2.3.3左右双旋2.3.4右左双旋 1.什么是AVL树 AVL树也叫二叉搜索平衡树 因为二叉搜索树如果插入顺序是有序的&#xff0c;那么这棵树的查找效率将会是O(N)&…

LangChain 文本分割器深度解析:从原理到落地应用(上)

食用指南 LangChain 作为大语言模型应用开发框架&#xff0c;文本分割器是其核心组件之一&#xff0c;本文以此作为切入点&#xff0c;详细介绍文本分割的作用、策略、以及常见的文本切割器应用。考虑到篇幅过长&#xff0c;故拆分为上、中、下三篇&#xff0c;后续会在中篇介…

【Java高频面试问题】高并发篇

【Java高频面试问题】高并发篇 Kafka原理核心组件高吞吐核心机制高可用设计 Kafka 如何保证消息不丢失如何解决Kafka重复消费一、生产者端&#xff1a;根源防重二、消费者端&#xff1a;精准控制三、业务层&#xff1a;幂等性设计&#xff08;核心方案&#xff09; 如何解决Kaf…

关于结构体,排序,递推的详细讲解(从属于GESP四级)

本章内容 排序算法基础 结构体 递推 简单双指针 一、排序算法基础三剑客 冒泡 Bubble、选择 Selection、插入 Insertion 1. 预备知识 1.1 排序算法评价指标 指标 含义 影响答题的典型问法 时间复杂度 算法在最坏、平均或最好情况下所需比较 / 交换次数 “写出此算法…

离线部署docker中的containerd服务

containerd 是一个行业标准的容器运行时&#xff0c;专注于简单、健壮的容器执行。它是从 Docker 中分离出来的项目&#xff0c;旨在作为一个底层的运行时接口&#xff0c;供更高层次的容器管理层使用。 containerd 负责镜像传输、存储、容器执行、网络配置等工作。它向上为 Do…

web布局15

CSS 网格布局除了提供定义网格和放置网格项目的相关属性之外&#xff0c;也提供了一些控制对齐方式的属性。这些控制对齐方式的属性&#xff0c;和 Flexbox 布局中的对齐属性 justify-* 、align-* 、*-items 、*-content 、 *-self 等是相似的&#xff1a; 在网格布局中可以用它…

leetcode 291. Word Pattern II和290. Word Pattern

目录 291. Word Pattern II 290. Word Pattern 291. Word Pattern II 回溯法哈希表 class Solution {unordered_map<char,string> hashmap;unordered_set<string> wordset; public:bool wordPatternMatch(string pattern, string s) {return backtrack(pattern,…

大模型的开发应用(十三):基于RAG的法律助手项目(上):总体流程简易实现

RAG法律助手项目&#xff08;上&#xff09;&#xff1a;总体流程简易实现 1 项目介绍1.1 方案选型1.2 知识文档 2 文档解析3 知识库构建3.1 构建知识节点3.2 嵌入向量初始化3.2 向量存储 4 查询4.1 初始化大模型4.2 模型响应4.2 本文程序存在的问题 完整代码 1 项目介绍 本项…

覆盖迁移工具选型、增量同步策略与数据一致性校验

1 引言 在当今数据驱动的时代&#xff0c;数据迁移已成为系统迭代、数据库升级、云迁移和架构演进中的关键环节。根据Gartner的调研&#xff0c;超过70%的企业级数据迁移项目因工具选择不当或同步策略缺陷而延期或失败。数据迁移不仅仅是简单的数据搬运&#xff0c;而是涉及数…

`docker run -it --rm` 笔记250624

docker run -it --rm 笔记250624 docker run -it --rm 是一个强大且常用的 Docker 命令组合&#xff0c;特别适合交互式开发和调试场景。以下是详细解析和使用指南&#xff1a; 参数解析 参数作用典型场景-i保持 STDIN 打开&#xff08;交互模式&#xff09;需要输入命令的交…

解锁阿里云AnalyticDB:数据仓库的革新利器

AnalyticDB&#xff1a;云数据仓库新势力 在数字化浪潮中&#xff0c;数据已成为企业的核心资产&#xff0c;而云数据仓库作为数据管理与分析的关键基础设施&#xff0c;正扮演着愈发重要的角色。阿里云 AnalyticDB 作为云数据仓库领域的佼佼者&#xff0c;以其卓越的性能、创…

【PX30 Qt 5.15 交叉编译环境搭建完整指南】

PX30 Qt 5.15 交叉编译环境搭建完整指南 (Ubuntu 20.04 → PX30 aarch64) &#x1f3af; 项目概览 本指南详细记录了在Ubuntu 20.04上搭建针对Rockchip PX30的Qt 5.15.2交叉编译环境的完整过程&#xff0c;包括实际操作步骤、遇到的问题及解决方案。 目标平台: Rockchip PX3…

深入理解读写锁 ReadWriteLock

在高性能并发编程中&#xff0c;如何有效地管理共享资源的访问是核心挑战之一。传统的排他锁&#xff08;如ReentrantLock&#xff09;在读多写少的场景下&#xff0c;性能瓶颈尤为突出&#xff0c;因为它不允许并发读取。Java并发包&#xff08;java.util.concurrent.locks&am…

Unity Addressable使用之检测更新流程

补充知识 关键文件说明 Addressable打包后会生成多种文件&#xff0c;主要包括 .hash、.json 和 .bundle 文件&#xff0c;它们各自有不同的作用。 .hash 文件&#xff08;哈希文件&#xff09; 作用&#xff1a; 用于 版本对比&#xff0c;检查资源是否有更新。存储的是 资…

Elasticsearch 中实现推荐搜索(方案设想)

1. 存储商品数据的数据类型 为了支持推荐搜索&#xff0c;商品数据通常需要包含以下字段&#xff1a; 商品索引结构 PUT /products {"mappings": {"properties": {"product_id": {"type": "keyword" // 商品 ID},"…

Aerotech系列(4)Aerotech.A3200名空间

IconTypeDescriptionAxisMask Represents a selection of axes Controller Represents a controller Allows configuring and c

React Router 是怎么实现灵活导航的?

&#x1f399; 欢迎来到《前端达人 React播客书单》第 21 期。 视频版&#xff08;播客风格更精彩&#xff09; 今天我们不讲 Hook&#xff0c;来拆解前端开发中另一个高频组件&#xff1a;React Router 的进阶导航模式。 你可能用过 <Link> 或 <Route>&#xff0…

Modbus TCP转Profibus DP网关与JF - 600MT 称重变送器轻松实现数据互换

Modbus TCP转Profibus DP网关与JF - 600MT 称重变送器轻松实现数据互换 在工业自动化领域&#xff0c;不同设备之间的通信与数据交互至关重要。Modbus TCP转Profibus DP网关作为连接不同协议设备的关键桥梁&#xff0c;发挥着不可或缺的作用。本文将以JF - 600MT称重变送器与3…