js小游戏---2048(附源代码)

一、游戏页面展示

开始游戏:

游戏结束:

二、游戏如何操作

通过监听键盘的操作,进行移动变化

键盘上下左右键控制页面中所有模块同时向键入的方向移动,如果有两块一样的方块,就进行合并,并且在键盘每操作一次的同时,会随机位置出现新的方块。

当所有的格子都有方块,并且没有可以合并的方块时,游戏结束

三、思路

游戏开始,启动计时器,创建棋盘数组,用于存放棋子,监听键盘操作,合并棋子的同时,随机生成新的棋子,将棋子的状态事实更新在数组中,当棋盘占满并且无棋子可以合并,游戏结束,将本局得分和游戏时间显示在排行榜中,点击按钮可以再来一局。

四、代码部分

我们主要研究js部分,所以html和css部分就不展开解释了,js代码注释很全,希望能方便你理解,

代码背景比较浅,有些代码颜色也比较浅,我不知道怎么调,你可以试试用鼠标选中它,就能更清楚。代码如下:

html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="./css/index.css"></head>
<body><div class="container"><div class="directions"><h1>2048</h1><p><strong>HOW TO PLAY:</strong> Use your arrow keys to move the tiles. When two tiles slide into each other,they merge into one!</p></div><div class="scores"><div class="score-container best-score">best:<div class="score"><div id="bestScore">0</div></div></div><div class="score-container">score:<div class="score"><div id="score">0</div><div class="add" id="add"></div></div></div></div><div class="game"><div id="tile-container" class="tile-container"></div><div class="end" id="end">Game Over<div class="monkey">🙈</div><buttonclass="btn not-recommended__item js-restart-btn" id="try-again">Try Again</button></div></div><div class="not-recommended"><button class="btn not-recommended__item js-restart-btn" id="restart">Restart Game</button><span class="not-recommended__annotation"></span></div></div><script src="./js/index.js"></script>
</body>
</html>

css

/* 字体引入 */
@import url('https://fonts.googleapis.com/css?family=Arvo');/* 伪类,表示文档树的根元素 */
/* 定义一些全局的变量 */
:root {/* 网格背景 */--gridBg: #8ca5a0;/* 格子颜色 */--color1: #c2bf1b;--color2: #EF476F;--color3: #0c6148;--color4: #b9543b;/* 背景颜色 *//* --placeholder-tile: #F8FFE5; */--placeholder-tile: #bce4e6;/* 字体颜色 */--font-color: #389989;/* 边框宽度 */--border-width: 7px;
}* {/* c3盒模型 */box-sizing: border-box;
}body,
html {/* 子绝父相 */position: relative;width: 100%;height: 100%;display: flex;/* 纵向排列 */flex-direction: column;font-family: "Arvo", Helvetica, sans-serif;font-size: 12px;color: var(--font-color);background: var(--placeholder-tile);/* 不想显示滚动条 */overflow: hidden;
}/* 文字部分 */
.directions {padding: 2rem;border-top: 1px solid var(--gridBg);border-bottom: 1px solid var(--gridBg);
}
/* 整个容器 */
.container {margin: 0 auto;flex: 1;width: 100%;max-width: 550px;text-align: center;
}
/* 2048 */
.directions h1 {margin-top: -20px;
}
/* 游玩方法 */
.directions p {margin-top: -5px;
}
/* 得分部分的大盒子 */
.scores {display: flex;justify-content: center;
}
/* 得分部分的2个小盒子 */
.score-container {display: flex;justify-content: center;align-items: center;margin: 1.8rem;font-size: 1.2rem;line-height: 1;color: var(--font-color);
}
/* 最高得分 */
.score-container.best-score {color: var(--gridBg);
}.score {margin-left: 1rem;position: relative;font-weight: bold;font-size: 1.5rem;vertical-align: middle;text-align: right;
}
/* 游戏部分 */
.game {position: relative;margin: 0 auto;background: var(--gridBg);padding: var(--border-width);display: inline-block;border-radius: 3px;box-sizing: border-box;
}.tile-container {border-radius: 6px;position: relative;width: 400px;height: 400px;
}
/*  */
.tile,
.background {display: block;color: var(--placeholder-tile);position: absolute;width: 100px;height: 100px;box-sizing: border-box;text-align: center;
}
/*  */
.background {z-index: 1;text-align: center;border: var(--border-width) solid var(--gridBg);background-color: var(--placeholder-tile);
}.tile {opacity: 0;z-index: 2;background: var(--color1);color: #F8FFE5;display: flex;align-items: center;justify-content: center;font-size: 1.8rem;align-items: center;transition: 110ms ease-in-out;border-radius: 3px;border: var(--border-width) solid var(--gridBg);box-sizing: border-box;
}
/* 4分格子 */
.tile.tile--4 {background: var(--color2);color: #F8FFE5;
}
/* 8分格子 */
.tile.tile--8 {background: var(--color3);color: #F8FFE5;}
/* 16分格子 */
.tile.tile--16 {background: var(--color4);color: #F8FFE5;}
/* 32分格子 */
.tile.tile--32 {background: #FF6B81;color: #F8FFE5;}
/* 64分格子 */
.tile.tile--64 {background: #24B0BC;color: #F8FFE5;
}
/* 128分格子 */.tile.tile--128 {background: #11E2AE;color: #F8FFE5;
}
/* 256分格子 */
.tile.tile--256 {background: #FFD047;color: #F8FFE5;}
/* 512分格子 */
.tile.tile--512 {background: #E53B5A;color: #F8FFE5;
}
/* 1024分格子 */.tile.tile--1024 {background: #147B8B;color: #F8FFE5;
}
/* 2048分格子 */
.tile.tile--2048 {background: #05B48A;color: #F8FFE5;
}
/* 增加盒子 */
.tile.tile--pop {animation: pop 0.3s ease-in-out;animation-fill-mode: forwards;
}
/* 减少盒子 */
.tile.tile--shrink {animation: shrink 0.5s ease-in-out;animation-fill-mode: forwards;
}
/* 计算积分 */
.add {position: absolute;opacity: 0;left: 120%;top: 0;font-size: 1rem;color: var(--color3);
}
/* 计算积分的运动 */
.add.active {animation: add 0.8s ease-in-out;
}
/* 算积分动画 */
@keyframes add {0% {opacity: 1;top: 0;}100% {opacity: 0;top: -100%;}
}
/* 减少盒子动画 */
@keyframes pop {0% {transform: scale(0.5);opacity: 0;}90% {transform: scale(1.1);opacity: 1;}100% {transform: scale(1);opacity: 1;}
}
/* 减少盒子动画 */
@keyframes shrink {0% {transform: scale(1);opacity: 1;}100% {transform: scale(0.9);opacity: 0.9;}
}
/* 结束页面 */
.end {opacity: 0;position: absolute;top: 0;left: 0;width: 100%;height: 100%;/* 默认不显示 */z-index: -1;display: flex;flex-direction: column;justify-content: center;align-items: center;background: rgba(85, 85, 85, 0.9);color: white;font-size: 2rem;transition: opacity 0.3s ease-in-out;
}
/* 再来一局按钮 */
.end btn {margin-top: 1rem;
}
/* 结束页面出现 */
.end.active {opacity: 1;z-index: 1000;
}
/* 猴子图标 */
.monkey {font-size: 3rem;margin: 1rem 0;
}
/* 开始按钮 */
.btn {font-family: inherit;font-size: 1rem;border: none;background: var(--color3);letter-spacing: 1px;color: white;font-weight: 300;padding: 0.9em 1.5em;border-radius: 3px;border: 1px solid transparent;cursor: pointer;
}
/* 按钮悬浮时 */
.btn:hover {background-color: #137a8b;}
/* 按钮点击时 */
.btn:active {background-color: #0e5f6e;}
/* 按钮获得焦点时 */
.btn:focus {box-shadow: 0 0 10px #0e5f6e inset;outline: none;
}
/* 底部 */
.not-recommended {display: flex;justify-content: center;align-items: center;margin-top: 3rem;
}
/* 表情位置 */
.not-recommended__annotation {margin-left: 10px;
}
/* 四种状态表情 */
.not-recommended__item+.not-recommended__annotation:before {font-size: 30px;content: "😐";
}.not-recommended__item:hover+.not-recommended__annotation:before {content: "😟";
}.not-recommended__item:focus+.not-recommended__annotation:before {content: "😄";
}.not-recommended__item:active+.not-recommended__annotation:before {content: "😨";
}

js

这部分,我们按照功能的不同来划分模块

页面更新和绘制模块

// 页面更新和绘制
// 引出类
export class DOMManager {// 构造函数constructor() {// 显示棋盘的容器this.tileContainer = document.getElementById('tile-container');// 游戏结束画面this.endDiv = document.getElementById('end');// 棋盘大小为4x4this.size = 4;}
// 绘制棋盘背景drawBackground() {// 清空棋盘的内容this.tileContainer.innerHTML = '';// 遍历每一个格子for (let i = 0; i < this.size * this.size; i++) {// 创建divconst tileDiv = document.createElement('div');// 将一维索引i转换为二维坐标const [x, y] = [i % this.size, Math.floor(i / this.size)];// 创建的div的大小tileDiv.style.top = `${y * 100}px`;tileDiv.style.left = `${x * 100}px`;// div设置背景,添加背景类tileDiv.classList.add("background");// 将格子元素添加到棋盘容器中this.tileContainer.appendChild(tileDiv);}}
// 定位棋子positionTile(tile, elm) {// 将棋子的索引转换为二维坐标const [x, y] = [tile.index % this.size, Math.floor(tile.index / this.size)];// 根据坐标,设置棋子的位置elm.style.top = `${y * 100}px`;elm.style.left = `${x * 100}px`;}
// 画棋子drawGame(tiles, isNew) {// 遍历棋子数组for (const tile of tiles) {// 如果棋子存在if (tile) {// 如果是新棋子if (isNew) {// 创建一个新的div元素const tileDiv = document.createElement('div');// 调用positiontile方法设置棋子位置this.positionTile(tile, tileDiv);// 棋子添加类,设置样式tileDiv.classList.add('tile', `tile--${tile.value}`);// 设置棋子idtileDiv.id = tile.id;// 添加动画,实现按钮弹出效果setTimeout(() => {tileDiv.classList.add("tile--pop");}, tile.mergedIds ? 1 : 150);// 设置棋子的值tileDiv.innerHTML = `<p>${tile.value}</p>`;// 将棋子元素添加到棋盘容器中this.tileContainer.appendChild(tileDiv);} else {//如果棋子已经存在,获取棋子对应dom元素const currentElement = document.getElementById(tile.id);// 调用方法,更新棋子位置this.positionTile(tile, currentElement);}}}}
// 更新domupdateDOM(before, after) {// 获取新生成的棋子const newElements = this.getNewElementsDOM(before, after);// 获取已经存在的棋子const existingElements = this.getExistingElementsDOM(before, after);// 获取合并后的棋子const mergedTiles = this.getMergedTiles(after);// 移除合并后的旧棋子this.removeElements(mergedTiles);// 绘制新棋子this.drawGame(newElements, true);// 更新已存在的棋子的位置this.drawGame(existingElements);}
// 移除合并的棋子removeElements(mergedTiles) {// 遍历合并后的棋子for (const tile of mergedTiles) {// 遍历合并后的棋子的id列表for (const id of tile.mergedIds) {// 获取对应dom元素const currentElm = document.getElementById(id);// 更新棋子的位置this.positionTile(tile, currentElm);// 添加缩小动画类currentElm.classList.add('tile--shrink');// 延时移除棋子元素setTimeout(() => {currentElm.remove();}, 100);}}}
// 获取合并后的棋子getMergedTiles(after) {// 过滤出after数组中包含mergedIds的棋子,表示这些棋子是通过合并生成的return after.filter(tile => tile && tile.mergedIds);}
// 获取新生成的棋子getNewElementsDOM(before, after) {// 获取before数组中所有棋子的idconst beforeIds = before.filter(tile => tile).map(tile => tile.id);// 过滤出after数组不再beforeIds中的棋子,表示新生成的棋子const newElements = after.filter(tile => tile && !beforeIds.includes(tile.id));// 返回新棋子数组return newElements;}
// 获取已经存在的棋子getExistingElementsDOM(before, after) {// 获取before数组中所有棋子的idconst beforeIds = before.filter(tile => tile).map(tile => tile.id);// 过滤出 after 数组中在 beforeIds 中的棋子,表示已存在的棋子const existingElements = after.filter(tile => tile && beforeIds.includes(tile.id));// 返回已存在的棋子数组return existingElements;}
// 游戏结束画面showGameOver() {// 延时800毫秒之后,为游戏结束画面的dom元素添加active类,显示游戏结束画面setTimeout(() => {this.endDiv.classList.add('active');}, 800);}
}

游戏棋盘状态模块

// 游戏棋盘状态
// 导出类
export class GameBoard {// 构造函数constructor(size = 4) {this.size = size;// 创建一个长度为size*size的一维数组,初始值为null,表示空格this.game = Array.from({ length: size * size }, () => null);// 初始化一个id计数器,用于为新生成的数字分配唯一标识符this.nextId = 1;}
// 初始化游戏棋盘initGame() {// 重新初始化棋盘状态,将所有格子设置为null,表示清空棋盘this.game = Array.from({ length: this.size * this.size }, () => null);}
// 获取空格的索引getEmptyCells() {return this.game.map((_, index) => index).filter(index => this.game[index] === null);}
// 随机生成数字addRandomNumber() {// 获取所有空格的索引const emptyCells = this.getEmptyCells();// 如果没有空格,直接返回if (emptyCells.length === 0) return;// 随机选择一个空格的索引const newPos = emptyCells[Math.floor(Math.random() * emptyCells.length)];// 创建一个新对象,包含,唯一标识符id,新数字的位置,新数字的值const newObj = {id: this.nextId++,index: newPos,value: this.generateNewNumber()};// 将新数字放置到棋盘的指定位置this.game[newPos] = newObj;}
// 生成新数字的值generateNewNumber() {return Math.random() * 100 <= 90 ? 2 : 4;}
// 根据坐标获取索引getIndexForPoint(x, y) {return y * this.size + x;}// 水平翻转棋盘reflectGrid(grid) {let reflectedGame = Array.from({ length: this.size * this.size }, () => 0);for (let row = 0; row < this.size; row++) {for (let col = 0; col < this.size; col++) {const index1 = this.getIndexForPoint(col, row);const index2 = this.getIndexForPoint(this.size - col - 1, row);reflectedGame[index1] = grid[index2];}}return reflectedGame;}
// 逆时针旋转棋盘90度rotateLeft90Deg(grid) {let rotatedGame = Array.from({ length: this.size * this.size }, () => 0);for (let row = 0; row < this.size; row++) {for (let col = 0; col < this.size; col++) {const index1 = this.getIndexForPoint(col, row);const index2 = this.getIndexForPoint(this.size - 1 - row, col);rotatedGame[index1] = grid[index2];}}return rotatedGame;}
// 顺时针90rotateRight90Deg(grid) {let rotatedGame = Array.from({ length: this.size * this.size }, () => 0);for (let row = 0; row < this.size; row++) {for (let col = 0; col < this.size; col++) {const index1 = this.getIndexForPoint(col, row);const index2 = this.getIndexForPoint(row, this.size - 1 - col);rotatedGame[index1] = grid[index2];}}return rotatedGame;}
}

游戏控制模块

// 游戏控制模块
import { GameBoard } from './GameBoard.js';
import { ScoreManager } from './ScoreManager.js';
import { DOMManager } from './DOMManager.js';
import { LeaderboardManager } from './LeaderboardManager.js';
// 导出游戏控制类
export class GameController {// 构造函数constructor() {// 创建实例// 创建一个gameboard实例,管理游戏棋盘this.gameBoard = new GameBoard();// 。。。管理游戏分数this.scoreManager = new ScoreManager();//。。。操作domthis.domManager = new DOMManager();// 。。。管理排行榜this.leaderboardManager = new LeaderboardManager();// 获取页面中显示时间的元素this.timerElement = document.getElementById('timer');//游戏开始时间this.startTime = null;this.timerInterval = null;// 初始化一个标志位,用来标记游戏结果是否已经提交到排行榜,防止一条数据因为键盘多按了几下就重复提交this.resultSubmitted = false;// 调用排行榜管理器的renderleaderboard方法,渲染排行榜this.leaderboardManager.renderLeaderboard();// 初始化游戏方法this.initGame();// 事件监听器this.addEventListeners();}
// 初始化游戏方法initGame() {// 初始化棋盘状态this.gameBoard.initGame();// 重置当前分数this.scoreManager.resetScore();// 初始化最高分this.scoreManager.initBestScore();// 绘制游戏背景this.domManager.drawBackground();// 复制当前状态到previousgame,用于后续比较------?const previousGame = [...this.gameBoard.game];// 随机生成两个数字this.gameBoard.addRandomNumber();this.gameBoard.addRandomNumber();// 更新棋盘显示this.domManager.updateDOM(previousGame, this.gameBoard.game);// 启动计时器this.startTimer();// 提交标志位重置为falsethis.resultSubmitted = false;}
// 启动计时器startTimer() {// 记录当前时间(毫秒)this.startTime = Date.now();// 定时器每秒计算一下现在的时间距离开始时间过了多久this.timerInterval = setInterval(() => {const elapsedTime = Date.now() - this.startTime;const seconds = Math.floor(elapsedTime / 1000);this.timerElement.textContent = `时长: ${seconds} 秒`;}, 1000);}// 停止计时stopTimer() {// 如果正在计时if (this.timerInterval) {// 清除计时器clearInterval(this.timerInterval);// 计时器设值为null,计时器已经停止this.timerInterval = null;}}
// 添加事件监听器addEventListeners() {// 两个按钮const buttons = document.querySelectorAll(".js-restart-btn");// 两个按钮都执行下面的方法// 添加点击监听buttons.forEach(button => {// 点击按钮,开启新游戏button.addEventListener("click", () => this.newGameStart());});// bind绑定this,传入的第一个数就是this的值,其他的是函数参数document.addEventListener("keydown", this.handleKeypress.bind(this));}// 处理键盘事件handleKeypress(evt) {// 特殊键const modifiers = evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey;// 如果不是特殊键,就执行这个方法if (!modifiers) {// 仅处理上下左右键const validKeys = [37, 38, 39, 40];// 如果数组中不包含事件的键码,就退出当前的执行if (!validKeys.includes(evt.which)) {return;}// 阻止事件的默认行为evt.preventDefault();// 复制当前棋盘状态,用于后续比较const prevGame = [...this.gameBoard.game];// 判断按下的是哪个键switch (evt.which) {// 左case 37:// 调用滑动方法// ----?this.gameBoard.game = this.shiftGameLeft(this.gameBoard.game);break;// 上case 38:this.gameBoard.game = this.shiftGameUp(this.gameBoard.game);break;// 右case 39:this.gameBoard.game = this.shiftGameRight(this.gameBoard.game);break;// 下case 40:this.gameBoard.game = this.shiftGameDown(this.gameBoard.game);break;}// 更新棋盘状态,为每个数字添加索引this.gameBoard.game = this.gameBoard.game.map((tile, index) => tile ? { ...tile, index } : null);// 如果棋盘状态未改变,则直接返回if (this.arrayEqual(prevGame, this.gameBoard.game)) return;// 在棋盘上随机生成一个新的数字this.gameBoard.addRandomNumber();// 更新dom显式this.domManager.updateDOM(prevGame, this.gameBoard.game);// 检查游戏是否结束if (this.gameOver()) {// 显示结束画面this.domManager.showGameOver();// 计时停止this.stopTimer();// 结果提交到排行榜this.submitGameResult();return;}}}
// 查看棋盘状态有没有变化,通过比较数组是否相等来判断arrayEqual(arr1, arr2) {// 如果两个数组是同一个引用,就返回trueif (arr1 === arr2) return true;// 如果两个数组的长度不同,就返回falseif (arr1.length !== arr2.length) return false;// 遍历数组的每个元素// ---?for (let i = 0; i < arr1.length; i++) {if (Array.isArray(arr1[i]) && Array.isArray(arr2[i])) {if (!this.arrayEqual(arr1[i], arr2[i])) return false;} else if (arr1[i] !== arr2[i]) {return false;}}return true;}
// 重新开始游戏newGameStart() {// 清空棋盘的内容this.domManager.tileContainer.innerHTML = '';// 去掉游戏结束的画面this.domManager.endDiv.classList.remove('active');// 停止计时器---?this.stopTimer();// 重新初始化游戏状态this.initGame();}
// 检查游戏是否结束gameOver() {// 如果棋盘上还有空格if (this.gameBoard.getEmptyCells().length === 0) {// 检查是否存在相邻的格子可以合并const sameNeighbors = this.gameBoard.game.find((tile, i) => {// 检查右侧是否有相同数字// -----?const isRightSame = this.gameBoard.game[i + 1] && (i + 1) % 4 !== 0 ? tile.value === this.gameBoard.game[i + 1].value : false;// 检测下方是否有相同数字const isDownSame = this.gameBoard.game[i + 4] ? tile.value === this.gameBoard.game[i + 4].value : false;// 如果有可以合并的格子,就返回truereturn isRightSame || isDownSame;});// 没有可以合并的数字,返回true,游戏结束return !sameNeighbors;}return false;}
// 格子右移shiftGameRight(gameGrid) {// 将棋盘水平翻转let reflectedGame = this.gameBoard.reflectGrid(gameGrid);reflectedGame = this.shiftGameLeft(reflectedGame);// 再次翻转,恢复到原来return this.gameBoard.reflectGrid(reflectedGame);}
// 格子左移shiftGameLeft(gameGrid) {let newGameState = [];let totalAdd = 0;for (let i = 0; i < this.gameBoard.size; i++) {const firstPos = 4 * i;const lastPos = (this.gameBoard.size) + 4 * i;const currentRow = gameGrid.slice(firstPos, lastPos);const filteredRow = currentRow.filter(row => row);for (const row of filteredRow) {delete row.mergedIds;}for (let j = 0; j < filteredRow.length - 1; j++) {if (filteredRow[j].value === filteredRow[j + 1].value) {const sum = filteredRow[j].value * 2;filteredRow[j] = {id: this.gameBoard.nextId++,mergedIds: [filteredRow[j].id, filteredRow[j + 1].id],value: sum};filteredRow.splice(j + 1, 1);totalAdd += sum;}}while (filteredRow.length < this.gameBoard.size) {filteredRow.push(null);}newGameState = [...newGameState, ...filteredRow];}if (totalAdd > 0) {this.scoreManager.updateScore(totalAdd);}return newGameState;}
// 格子上移shiftGameUp(gameGrid) {let rotatedGame = this.gameBoard.rotateLeft90Deg(gameGrid);rotatedGame = this.shiftGameLeft(rotatedGame);return this.gameBoard.rotateRight90Deg(rotatedGame);}
// 格子下移shiftGameDown(gameGrid) {let rotatedGame = this.gameBoard.rotateRight90Deg(gameGrid);rotatedGame = this.shiftGameLeft(rotatedGame);return this.gameBoard.rotateLeft90Deg(rotatedGame);}
// 提交结果submitGameResult() {// 检查结果是否已提交// 如果没有提交if (!this.resultSubmitted) {// 获取页面显示的时间文本const timeText = this.timerElement.textContent;
// 从时间文本中提取秒数const elapsedTime = parseInt(timeText.match(/\d+/)[0], 10);// 获取当前分数const score = this.scoreManager.score;// 将分数和时间提交到排行榜this.leaderboardManager.addScore(score, elapsedTime);// 重新渲染排行榜this.leaderboardManager.renderLeaderboard();// 标记结果已提交this.resultSubmitted = true;}}
}

排行榜模块

//排行榜
// 导出类
export class LeaderboardManager {// 构造函数constructor() {// 定义一个键名,用于在localStorage中存储排行榜数据this.leaderboardKey = 'game2048Leaderboard';// 调用getLeaderboard方法,从localStorage中读取排行榜数据,并将其存储在实例的leaderboard中this.leaderboard = this.getLeaderboard();}
// 从localstorage中读取数据getLeaderboard() {// 从 localStorage 中获取存储的排行榜数据const leaderboardData = localStorage.getItem(this.leaderboardKey);// 如果数据存在,则将其从 JSON 格式解析为 JavaScript 对象,如果数据不存在,则返回一个空数组,表示排行榜为空return leaderboardData ? JSON.parse(leaderboardData) : [];}
// 保存排行榜数据到saveLeaderboard() {// 将当前的排行榜数据(this.leaderboard)转换为 JSON 格式,并存储到 localStorage 中localStorage.setItem(this.leaderboardKey, JSON.stringify(this.leaderboard));}
// 添加新的分数到排行榜addScore(score, time) {// 检查新分数是否已经存在于排行榜const isScoreExists = this.leaderboard.some(entry => entry.score === score && entry.time === time);// 如果记录不存在if (!isScoreExists) {// 将新的分数和时间记录添加到排行榜数组中this.leaderboard.push({ score, time });// 根据分数从高到低对排行榜进行排序this.leaderboard.sort((a, b) => b.score - a.score);// 将更新后的排行榜保存到Localstorage中this.saveLeaderboard();}}
// 渲染排行榜到页面renderLeaderboard() {// 获取排行榜元素const leaderboardElement = document.getElementById('leaderboard');// 清空排行榜容器的内容leaderboardElement.innerHTML = '';// 创建一个表格行,用于显示排行榜的表头const headerRow = document.createElement('tr');// 创建一个表头单元格,显示排名const rankHeader = document.createElement('th');rankHeader.textContent = '排名';// 创建一个表头单元格,显示分数const scoreHeader = document.createElement('th');scoreHeader.textContent = '分数';// 创建一个表头单元格,显示时长const timeHeader = document.createElement('th');timeHeader.textContent = '时长';// 将表头单元格添加到表头行中headerRow.appendChild(rankHeader);headerRow.appendChild(scoreHeader);headerRow.appendChild(timeHeader);// 将表头行添加到排行榜容器中leaderboardElement.appendChild(headerRow);
// 遍历排行榜数组,为每一项都创建一个表格行this.leaderboard.forEach((entry, index) => {const row = document.createElement('tr');const rankCell = document.createElement('td');rankCell.textContent = index + 1;const scoreCell = document.createElement('td');scoreCell.textContent = entry.score;const timeCell = document.createElement('td');timeCell.textContent = `${entry.time} 秒`;
// 将单元格添加到表格中row.appendChild(rankCell);row.appendChild(scoreCell);row.appendChild(timeCell);// 将表格行添加到排行榜容器中leaderboardElement.appendChild(row);});}
}

得分模块

// 本局得分和最高分
export class ScoreManager {// 构造函数constructor() {// 初始化当前得分this.score = 0;// 从localstorage中读取最高分,如果不存在,就默认为0// this.bestScore = localStorage.getItem('bestScore') || 0;// 获取页面中用于显示当前得分的元素this.scoreDiv = document.getElementById('score');// 获取页面中用于显示最高得分的元素this.bestScoreDiv = document.getElementById('bestScore');// 获取页面中用于显示加分动画的元素this.addDiv = document.getElementById('add');}
// 初始化最高分initBestScore() {// 从localstorage中读取最高分,如果不存在,就默认为0this.bestScore = localStorage.getItem('bestScore') || 0;// 将最高分显示在页面上this.bestScoreDiv.textContent = this.bestScore;}
// 更新得分updateScore(totalAdd) {// 将新增分数加到当前得分上this.score += totalAdd;// 更新页面上显示的当前得分this.scoreDiv.textContent = this.score;// 在加分动画中显示新增分数this.addDiv.textContent = `+${totalAdd}`;// 为加分动画添加active类,触发css动画this.addDiv.classList.add('active');// 延时800毫秒后移除active,动画结束setTimeout(() => {this.addDiv.classList.remove("active");}, 800);// 如果当前得分超过最高得分,将当前得分保存为最高得分if (this.score > this.bestScore) {localStorage.setItem('bestScore', this.score);// 更新页面上显示的最高得分this.initBestScore();}}
// 重置得分resetScore() {// 当前得分为0this.score = 0;// 更新页面显示的当前得分this.scoreDiv.textContent = this.score;}
}

主函数模块

// 创建实例,启动游戏
// 引入游戏控制类
import { GameController } from './GameController.js';
// 创建游戏实例
const gameInstance = new GameController();

如果你有更简单的方法,可以和我交流一下,因为是初学者,所以可能有写的不对的地方,请指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/893793.shtml

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

相关文章

美格智能AIMO智能体+DeepSeek-R1模型,AI应用的iPhone时刻来了

导语&#xff1a; 当AI大模型从云端下沉至终端设备&#xff0c;一场关于效率、隐私与智能化的革命悄然展开。作为全球领先的无线通信模组及解决方案提供商&#xff0c;美格智能凭借其高算力AI模组矩阵与端侧大模型部署经验&#xff0c;结合最新发布的AIMO智能体产品&#xff0…

C语言的灵魂——指针(1)

指针是C语言的灵魂&#xff0c;有了指针C语言才能完成一些复杂的程序&#xff1b;没了指针就相当于C语言最精髓的部分被去掉了&#xff0c;可见指针是多么重要。废话不多讲我们直接开始。 指针 一&#xff0c;内存和地址二&#xff0c;编址三&#xff0c;指针变量和地址1&#…

物业巡更系统助推社区管理智能化与服务模式创新的研究与应用

内容概要 在现代社区管理中&#xff0c;物业巡更系统扮演着至关重要的角色。首先&#xff0c;我们先来了解一下这个系统的概念与发展背景。物业巡更系统&#xff0c;顾名思义&#xff0c;是一个用来提升物业管理效率与服务质量的智能化工具。随着科技的发展&#xff0c;传统的…

每日一题 427. 建立四叉树

427. 建立四叉树 class Solution { public:Node* construct(vector<vector<int>>& grid) {int n grid.size();return dfs(grid,0,0,n);}bool allSame(vector<vector<int>>& grid, int x,int y,int len){int val grid[x][y];for(int i0;i<…

关于CAN(FD)转以太网详细介绍

一、功能描述 CANFD 完全向下兼容 CAN &#xff0c;以下统称 CAN(FD) 。 SG-CAN(FD)NET-210 是一款用来把 CANFD 总线数据转为网口数据的设备。 网口支持 TCP Sever 、 TCP Client 、 UDP Sever 、 UDP Client 四种模式。 可以通过软件配置和 Web 网页配置。 两路…

用Python和PyQt5打造一个股票涨幅统计工具

在当今的金融市场中&#xff0c;股票数据的实时获取和分析是投资者和金融从业者的核心需求之一。无论是个人投资者还是专业机构&#xff0c;都需要一个高效的工具来帮助他们快速获取股票数据并进行分析。本文将带你一步步用Python和PyQt5打造一个股票涨幅统计工具&#xff0c;不…

Centos类型服务器等保测评整/etc/pam.d/system-auth

修改服务器配置文件/etc/pam.d/system-auth&#xff0c;但是&#xff0c;把一下配置放在password的配置第一行才会生效 执行命令&#xff1a;配置口令要求&#xff1a;大小写字母、数字、特殊字符组合、至少8位&#xff0c;包括强制设置root口令&#xff01; sed -i 14a pas…

At coder beginner contest 290AB

A12435 思路&#xff1a;只有4中情况:A1,A2翻转&#xff0c;其他正常&#xff0c;A2A3翻转其他正常.....为了下标与数字对应我开了6个空间&#xff0c;然后从1开始循环&#xff0c;到4截止&#xff0c;因为循环中有i1害怕数组越界&#xff0c;如果索引出的数与下标不相等了&…

【ElasticSearch】 Java API Client 7.17文档

​ 本文章内容根据 Elastic Search Java API Client 7.17 版本官方文档 内容翻译而来&#xff0c;用于方便后续学习翻阅 序言 这是适用于 Elasticsearch 的官方 Java API Client 文档。该客户端针对所有 Elasticsearch API 提供强类型的请求和响应。 功能 所有 Elasticsearc…

Vue3 + TS 实现批量拖拽 文件夹和文件 组件封装

一、html 代码&#xff1a; 代码中的表格引入了 vxe-table 插件 <Tag /> 是自己封装的说明组件 表格列表这块我使用了插槽来增加扩展性&#xff0c;可根据自己需求&#xff0c;在组件外部做调整 <template><div class"dragUpload"><el-dial…

STM32完全学习——RT-thread在STM32F407上移植

一、写在前面 关于源码的下载&#xff0c;以及在KEIL工程里面添加操作系统的源代码&#xff0c;这里就不再赘述了。需要注意的是RT-thread默认里面是会使用串口的&#xff0c;因此需要额外的进行串口的初始化&#xff0c;有些人可能会问&#xff0c;为什么不直接使用CubMAX直接…

JVM深入学习(一)

目录 一.JVM概述 1.1 为什么要学jvm&#xff1f; 1.2 jvm的作用 1.3 jvm内部构造 二.JVM类加载 2.1类加载过程 2.2类加载器 2.3类加载器的分类 2.4双亲委派机制 三.运行时数据区 堆空间区域划分&#xff08;堆&#xff09; 为什么分区(代)&#xff1f;&#xff08…

Spring Boot(6)解决ruoyi框架连续快速发送post请求时,弹出“数据正在处理,请勿重复提交”提醒的问题

一、整个前言 在基于 Ruoyi 框架进行系统开发的过程中&#xff0c;我们常常会遇到各种有趣且具有挑战性的问题。今天&#xff0c;我们就来深入探讨一个在实际开发中较为常见的问题&#xff1a;当连续快速发送 Post 请求时&#xff0c;前端会弹出 “数据正在处理&#xff0c;请…

002-SpringBoot整合AI(Alibaba)

SpringBoot整合AI 一、引入依赖二、配置application.yml三、获取 api-key四、编写 controller五、起服务调用 一、引入依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><vers…

安宝特方案 | 智能培训:安宝特AR如何提升企业技能培训的效率与互动性

随着企业不断推进数字化转型&#xff0c;传统培训方式已无法满足现代企业对高效、灵活培训的需求。尤其在技术更新频繁、工艺流程复杂、员工流动性大的环境中&#xff0c;传统培训模式的局限性愈加明显。为了提升培训质量、降低培训成本&#xff0c;并帮助员工迅速掌握新技能&a…

【阅读笔记】基于整数+分数微分的清晰度评价算子

本文介绍的是一种新的清晰度评价算子&#xff0c;整数微分算子分数微分算子 一、概述 目前在数字图像清晰度评价函数中常用的评价函数包括三类&#xff1a;灰度梯度评价函数、频域函数和统计学函数&#xff0c;其中灰度梯度评价函数具有计算简单&#xff0c;评价效果好等优点…

Windows 下本地 Docker RAGFlow 部署指南

Windows 下本地 Docker RAGFlow 部署指南 环境要求部署步骤1. 克隆代码仓库2. 配置 Docker 镜像加速(可选)3. 修改端口配置(可选)4. 启动服务5. 验证服务状态6. 访问服务7. 登录系统8. 配置模型8.1 使用 Ollama 本地模型8.2 使用在线 API 服务9. 开始使用10. 常见问题处理端…

【学习笔记】计算机网络(二)

第2章 物理层 文章目录 第2章 物理层2.1物理层的基本概念2.2 数据通信的基础知识2.2.1 数据通信系统的模型2.2.2 有关信道的几个基本概念2.2.3 信道的极限容量 2.3物理层下面的传输媒体2.3.1 导引型传输媒体2.3.2 非导引型传输媒体 2.4 信道复用技术2.4.1 频分复用、时分复用和…

linux设置mysql远程连接

首先保证服务器开放了mysql的端口 然后输入 mysql -u root -p 输入密码后即可进入mysql 然后再 use mysql; select user,host from user; update user set host"%" where user"root"; flush privileges; 再执行 select user,host from user; 即可看到变…

Midscene.js:重新定义UI自动化的新时代工具

前言 Midscene.js 是一个创新的、面向开发者的 UI 自动化解决方案&#xff0c;并通过人工智能技术简化自动化脚本的编写与维护。 它提供了三种核心方法——交互&#xff08;.ai, .aiAction&#xff09;、提取&#xff08;.aiQuery&#xff09;和断言&#xff08;.aiAssert&am…