一、游戏页面展示
开始游戏:
游戏结束:
二、游戏如何操作
通过监听键盘的操作,进行移动变化
键盘上下左右键控制页面中所有模块同时向键入的方向移动,如果有两块一样的方块,就进行合并,并且在键盘每操作一次的同时,会随机位置出现新的方块。
当所有的格子都有方块,并且没有可以合并的方块时,游戏结束
三、思路
游戏开始,启动计时器,创建棋盘数组,用于存放棋子,监听键盘操作,合并棋子的同时,随机生成新的棋子,将棋子的状态事实更新在数组中,当棋盘占满并且无棋子可以合并,游戏结束,将本局得分和游戏时间显示在排行榜中,点击按钮可以再来一局。
四、代码部分
我们主要研究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();
如果你有更简单的方法,可以和我交流一下,因为是初学者,所以可能有写的不对的地方,请指正。