当前位置: 首页 > article >正文

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();

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


http://www.kler.cn/a/517967.html

相关文章:

  • SAP新增公司间交易的配置点---SD部分内容
  • Go中的三种锁
  • 安装最小化的CentOS7后,执行yum命令报错Could not resolve host mirrorlist.centos.org; 未知的错误
  • Angular 2 表单深度解析
  • WebODM之python实现
  • 2025发文新方向:AI+量化 人工智能与金融完美融合!
  • 支持大功率输出高速频闪的图像处理用光源控制器
  • 亿坊软件前端命名规范
  • windows在命令行中切换盘符
  • springboot中DTO、VO、Entity相互转换
  • 低代码系统-产品架构案例介绍、得帆云(九)
  • 如何用VSCODE配置C++多文件编译
  • three.js+WebGL踩坑经验合集(2):3D场景被相机裁切后,被裁切的部分依然可以被鼠标碰撞检测得到(射线检测)
  • 豆包MarsCode:小C的类二进制拼图
  • ansible自动化运维实战--yaml的使用和配置(7)
  • http请求获取客户端ip
  • Flink(十一): DataStream API (八) Checkpointing
  • Arduino大师练成手册 -- 读取DS18B20
  • MacOS安装Docker battery-historian
  • 编译安装PaddleClas@openKylin(失败,安装好后报错缺scikit-learn)
  • 知识体系_统计学_03_描述性统计_概括性度量
  • 2025数学建模美赛|B题成品论文
  • GraphRAG 简介
  • 「全网最细 + 实战源码案例」设计模式——原型模式
  • 使用 Docker Compose 一键启动 Redis、MySQL 和 RabbitMQ
  • Linux 常用命令——软件篇(保姆级说明)