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.rhkb.cn/news/9729.html

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

相关文章

ASP.NET代码审计 SQL注入篇(简单记录)

sql注入&#xff0c;全局搜索 Request QueryString ToString() select select * aspx是设计页面&#xff0c;而aspx.cs是类页面&#xff0c;也就是说设计页面用到的类信息在这个页面里面&#xff0c;其实就是把设计和实现分离开来。 源码 using System; using System.Collect…

【Rust自学】14.6. 安装二进制crate

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 14.6.1. 从cratea.io安装二进制crate 通过cargo_install命令可以从crates.io安装二进制crate。 这并不是为了替换系统包&#xff0c;它应…

C++中的类与对象(中)

在上一节中&#xff0c;我们初步了解了一下&#xff0c;C中的类&#xff0c;这一概念&#xff0c;这一节让我们进一步深入了解一下。 文章目录 目录 前言 一、类中的默认成员函数 1.1 构造函数 构造函数的特点&#xff1a; 1.2 析构函数 析构函数的特点&#xff1a; 1.3 …

机器人抓取与操作概述(深蓝)——1

工业机器人&#xff1a;① “臂”的形态 ② “手”的形态 ③ 视觉&#xff0c;力和触觉 1 机器人的不同形态 “臂”的形态 “手”的形态 2 常见的操作任务 操作&#xff1a;插入、推和滑 抓取&#xff1a;两指&#xff08;平行夹爪&#xff09;抓取、灵巧手抓取 落地-产…

指针的介绍3后

1.函数指针变量 1.1函数的地址 void test(int (*arr)[2]) {printf("zl_dfq\n"); } int main() {printf("%p\n", test);printf("%p\n", &test);return 0; } 由上面的程序运行可知&#xff1a; 函数名就是函数的地址 &函数名也可以拿到函…

基于Springboot的智能学习平台系统【附源码】

基于Springboot的智能学习平台系统 效果如下&#xff1a; 系统登陆页面 系统主页面 课程详情页面 论坛页面 考试页面 试卷管理页面 考试记录页面 错题本页面 研究背景 随着互联网技术的普及&#xff0c;人们探索知识的方式逐渐转向数字化平台。传统的教学方法通常局限于固定…

shiro学习五:使用springboot整合shiro。在前面学习四的基础上,增加shiro的缓存机制,源码讲解:认证缓存、授权缓存。

文章目录 前言1. 直接上代码最后在讲解1.1 新增的pom依赖1.2 RedisCache.java1.3 RedisCacheManager.java1.4 jwt的三个类1.5 ShiroConfig.java新增Bean 2. 源码讲解。2.1 shiro 缓存的代码流程。2.2 缓存流程2.2.1 认证和授权简述2.2.2 AuthenticatingRealm.getAuthentication…

网关登录校验

网关登录校验 单体架构时我们只需要完成一次用户登录、身份校验&#xff0c;就可以在所有业务中获取到用户信息。而微服务拆分后&#xff0c;每个微服务都独立部署&#xff0c;不再共享数据。也就意味着每个微服务都需要做登录校验&#xff0c;这显然不可取。 鉴权思路分析 …

【单细胞第二节:单细胞示例数据分析-GSE218208】

GSE218208 1.创建Seurat对象 #untar(“GSE218208_RAW.tar”) rm(list ls()) a data.table::fread("GSM6736629_10x-PBMC-1_ds0.1974_CountMatrix.tsv.gz",data.table F) a[1:4,1:4] library(tidyverse) a$alias:gene str_split(a$alias:gene,":",si…

【已解决】黑马点评项目Redis版本替换过程的数据迁移

黑马点评项目Redis版本替换过程的数据迁移 【哭哭哭】附近商户中需要用到的GEO功能只在Redis 6.2以上版本生效 如果用的是老版本&#xff0c;美食/KTV的主页能正常返回&#xff0c;但无法显示内容 上次好不容易升到了5.0以上版本&#xff0c;现在又用不了了 Redis 6.2的windo…

本地部署deepseek模型步骤

文章目录 0.deepseek简介1.安装ollama软件2.配置合适的deepseek模型3.安装chatbox可视化 0.deepseek简介 DeepSeek 是一家专注于人工智能技术研发的公司&#xff0c;致力于打造高性能、低成本的 AI 模型&#xff0c;其目标是让 AI 技术更加普惠&#xff0c;让更多人能够用上强…

[论文总结] 深度学习在农业领域应用论文笔记14

当下&#xff0c;深度学习在农业领域的研究热度持续攀升&#xff0c;相关论文发表量呈现出迅猛增长的态势。但繁荣背后&#xff0c;质量却不尽人意。相当一部分论文内容空洞无物&#xff0c;缺乏能够落地转化的实际价值&#xff0c;“凑数” 的痕迹十分明显。在农业信息化领域的…

快速分析LabVIEW主要特征进行判断

在LabVIEW中&#xff0c;快速分析程序特征进行判断是提升开发效率和减少调试时间的重要技巧。本文将介绍如何高效地识别和分析程序的关键特征&#xff0c;从而帮助开发者在编写和优化程序时做出及时的判断&#xff0c;避免不必要的错误。 ​ 数据流和并行性分析 LabVIEW的图形…

展示统计信息收集情况

看看最近是否收集失败 SET LINES 200 PAGES 0 SET LONG 100000 longc 100000 COLUMN REPORT FORMAT A200VARIABLE stat_report CLOB; BEGIN:stat_report : DBMS_STATS.REPORT_STATS_OPERATIONS (since > SYSDATE-3 , until > SYSDATE , detail_lev…

STM32 TIM输入捕获 测量频率

输入捕获简介&#xff1a; IC&#xff08;Input Capture&#xff09;输入捕获 输入捕获模式下&#xff0c;当通道输入引脚出现指定电平跳变时&#xff0c;当前CNT的值将被锁存到CCR中&#xff0c;可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数 每个高级定时器…

如何将 Windows 上的文件传递到 Mac 上

文章目录 效果需求Windows 上设置共享磁盘【可选】新建一个带有密码的账户查看 Windows 的 IP 地址Mac 上链接 Windows 共享的磁盘 效果 需求 Windows 上有一个有密码的账户 Windows 上设置共享磁盘 windows 这边需要用 Administrator 权限的账号&#xff0c;把要共享的磁盘设…

NLP模型大对比:Transformer > RNN > n-gram

结论 Transformer 大于 RNN 大于 传统的n-gram n-gram VS Transformer 我们可以用一个 图书馆查询 的类比来解释它们的差异&#xff1a; 一、核心差异对比 维度n-gram 模型Transformer工作方式固定窗口的"近视观察员"全局关联的"侦探"依赖距离只能看前…

ODP(OBProxy)路由初探

OBProxy路由策略 Primary Zone 路由 官方声明默认情况&#xff0c;会将租户请求发送到租户的 primary zone 所在的机器上&#xff0c;通过 Primary Zone 路由可以尽量发往主副本&#xff0c;方便快速寻找 Leader 副本。另外&#xff0c;设置primary zone 也会在一定成都上减少…

Python NumPy(7):连接数组、分割数组、数组元素的添加与删除

1 连接数组 函数描述concatenate连接沿现有轴的数组序列stack沿着新的轴加入一系列数组。hstack水平堆叠序列中的数组&#xff08;列方向&#xff09;vstack竖直堆叠序列中的数组&#xff08;行方向&#xff09; 1.1 numpy.concatenate numpy.concatenate 函数用于沿指定轴连…

在线课堂小程序设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…