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><button
class="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');
// 棋盘大小为4x4
this.size = 4;
}
// 绘制棋盘背景
drawBackground() {
// 清空棋盘的内容
this.tileContainer.innerHTML = '';
// 遍历每一个格子
for (let i = 0; i < this.size * this.size; i++) {
// 创建div
const 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}`);
// 设置棋子id
tileDiv.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);
}
}
}
}
// 更新dom
updateDOM(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数组中所有棋子的id
const 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数组中所有棋子的id
const 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;
}
// 顺时针90
rotateRight90Deg(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();
//。。。操作dom
this.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();
// 提交标志位重置为false
this.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) {
// 如果两个数组是同一个引用,就返回true
if (arr1 === arr2) return true;
// 如果两个数组的长度不同,就返回false
if (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;
// 如果有可以合并的格子,就返回true
return 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中读取最高分,如果不存在,就默认为0
this.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() {
// 当前得分为0
this.score = 0;
// 更新页面显示的当前得分
this.scoreDiv.textContent = this.score;
}
}
主函数模块
// 创建实例,启动游戏
// 引入游戏控制类
import { GameController } from './GameController.js';
// 创建游戏实例
const gameInstance = new GameController();
如果你有更简单的方法,可以和我交流一下,因为是初学者,所以可能有写的不对的地方,请指正。