HTML实现2048小游戏

news2024/9/25 15:28:43

游戏概述

 

实现一个基本的2048游戏涉及综合运用HTML、CSS和JavaScript这三种关键技术。

 

HTML用于构建游戏的结构框架,包括游戏容器、网格布局以及可能的用户交互元素(如按钮或得分显示)。

 

CSS则负责美化游戏界面,通过样式表定义网格的样式、瓷砖的外观、动画效果以及整体布局的视觉呈现。

 

JavaScript则是游戏逻辑的核心,它处理用户的输入(如键盘事件监听),控制游戏状态的更新(如瓷砖的移动和合并),以及动态地更新DOM以反映游戏状态的变化。

 

通过这三者的紧密协作,可以创建出一个既美观又富有挑战性的2048游戏。

 

使用工具

 

本篇文章有用到GPT代码更正,国内可稳定使用,感兴趣的大佬可以试试。

 

传送门:ChatGPT-4o


 

实现步骤

 

 

HTML结构

代码部分:

<div id="gameContainer">
    <div id="score">Score: 0</div>
    <div id="gridContainer"></div>
    <div id="gameOverContainer">
        <div id="gameOver">Game Over!</div>
        <button id="restartButton">Restart</button>
    </div>
</div>

 

解释:

  • gameContainer: 包含整个游戏的容器。
  • score: 显示当前分数。
  • gridContainer: 游戏的主要区域,包含瓷砖。
  • gameOverContainer: 游戏结束时显示的消息和重启按钮。

 


 

CSS样式

 

代码部分:

body {
    text-align: center;
    margin-top: 50px;
    font-family: Arial, sans-serif;
    background-color: #f0f0f0;
}

#gridContainer {
    width: 360px;
    height: 360px;
    border: 1px solid #8b8b8b;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-gap: 5px;
    background-color: #dcdcdc;
    padding: 10px;
}

.tile {
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 36px;
    font-weight: bold;
    border-radius: 6px;
    width: 80px;
    height: 80px;
    color: #fff;
}

 

解释:

 

  • body: 设置页面的基本样式,包括字体、背景颜色和对齐方式。
  • gridContainer: 使用 CSS Grid 布局创建了一个 4x4 的网格,定义了网格的宽度、高度以及瓷砖之间的间距。
  • .tile: 定义了瓷砖的样式,包括大小、颜色和字体。

 


 

JavaScript逻辑

 

初始化

 

function init() {
    score = 0;
    gameOverFlag = false;
    updateScore();
    gameOverContainer.style.display = "none";
    resetGrid();
    tiles = Array(width * width).fill(null);
    addTile();
    addTile();
    drawTiles();
}

解释:

  • 初始化游戏状态:重置分数和游戏结束标志。
  • resetGrid(): 清空现有的网格。
  • addTile(): 添加两个初始瓷砖。
  • drawTiles(): 绘制当前的瓷砖状态。

 

 

 

添加瓷砖

 

function addTile() {
    const emptyTiles = tiles.map((tile, index) => tile === null ? index : null).filter(index => index !== null);

    if (emptyTiles.length > 0) {
        const randomIndex = emptyTiles[Math.floor(Math.random() * emptyTiles.length)];
        tiles[randomIndex] = Math.random() < 0.9 ? 2 : 4;
    }
}

 

解释:

  • 查找空位置:创建一个包含所有空位置索引的数组。
  • 随机添加:在空位置随机添加一个值为 2 或 4 的瓷砖。

 

 

绘制瓷砖

 

function drawTiles() {
    resetGrid();
    tiles.forEach(tile => {
        const tileElement = document.createElement("div");
        tileElement.classList.add("tile");
        if (tile !== null) {
            tileElement.textContent = tile;
            tileElement.classList.add(`tile-${tile}`);
        }
        gridContainer.appendChild(tileElement);
    });
}

解释:

 

  • 清空网格:每次重新绘制时清空网格。
  • 创建瓷砖元素:为每个瓷砖创建一个新的 div 元素并设置其样式和内容。

 

 

 

移动逻辑

 

function move(direction) {
    if (gameOverFlag) return;
    let hasChanged = false;

    const moveTile = (currentIndex, newIndex) => {
        if (tiles[newIndex] === null) {
            tiles[newIndex] = tiles[currentIndex];
            tiles[currentIndex] = null;
            hasChanged = true;
        } else if (tiles[newIndex] === tiles[currentIndex]) {
            tiles[newIndex] *= 2;
            tiles[currentIndex] = null;
            score += tiles[newIndex];
            hasChanged = true;
        }
    };

    // Define moveUp, moveDown, moveLeft, moveRight

    switch (direction) {
        case "up":
            moveUp();
            break;
        case "down":
            moveDown();
            break;
        case "left":
            moveLeft();
            break;
        case "right":
            moveRight();
            break;
    }

    if (hasChanged) {
        addTile();
        drawTiles();
        updateScore();
        checkGameOver();
    }
}

解释:

 

  • 检查变动:检测移动是否改变了网格。
  • 移动函数:定义不同方向的移动逻辑,合并相同的瓷砖。

 

 

 

检查游戏结束

代码部分:

 

function checkGameOver() {
    if (!tiles.includes(null) && !checkMove()) {
        gameOverFlag = true;
        gameOverContainer.style.display = "block";
        restartButton.addEventListener("click", restart, { once: true });
    }
}

 

解释:

  • 游戏结束条件:当没有空格且不能移动时,游戏结束。
  • 显示游戏结束信息:并提供重启按钮。

 

 

重启游戏

function restart() {
    init();
}

重新初始化游戏:调用 init() 函数,重置游戏状态。

 

 

事件监听

document.addEventListener("keydown", event => {
    switch (event.key) {
        case "ArrowUp":
        case "w":
            move("up");
            break;
        case "ArrowDown":
        case "s":
            move("down");
            break;
        case "ArrowLeft":
        case "a":
            move("left");
            break;
        case "ArrowRight":
        case "d":
            move("right");
            break;
    }
});
  • 键盘事件:监听方向键和 WASD 键来移动瓷砖。

 


 

运行界面:

 

d7a5f6905ea24856b904f350387c249c.png

 

 

 以下是完整代码:

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>2048 Game</title>
    <style>
        body {
            text-align: center;
            margin-top: 50px;
            font-family: Arial, sans-serif;
            background-color: #f0f0f0;
        }

        h1 {
            font-size: 50px;
            color: #333;
        }

        #gameContainer {
            margin-top: 20px;
            display: inline-block;
        }

        #score {
            font-size: 24px;
            margin-bottom: 10px;
            color: #333;
        }

        #gridContainer {
            width: 360px;
            height: 360px;
            border: 1px solid #8b8b8b;
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            grid-gap: 5px;
            background-color: #dcdcdc;
            padding: 10px;
        }

        .tile {
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 36px;
            font-weight: bold;
            border-radius: 6px;
            width: 80px;
            height: 80px;
            color: #fff;
        }

        .tile-2 { background-color: #f78c6b; }
        .tile-4 { background-color: #f7b26b; }
        .tile-8 { background-color: #f7d06b; }
        .tile-16 { background-color: #f4f76b; }
        .tile-32 { background-color: #a3f76b; }
        .tile-64 { background-color: #6bf7c4; }
        .tile-128 { background-color: #6baff7; }
        .tile-256 { background-color: #a26bf7; }
        .tile-512 { background-color: #f76bf7; }
        .tile-1024 { background-color: #f76b9f; }
        .tile-2048 { background-color: #f76b6b; }

        #gameOverContainer {
            display: none;
            margin-top: 20px;
        }

        #gameOver {
            font-size: 36px;
            color: #333;
            margin-bottom: 10px;
        }

        #restartButton {
            font-size: 20px;
            padding: 8px 20px;
            border-radius: 6px;
            background-color: #444;
            color: #fff;
            border: none;
            cursor: pointer;
            transition: background-color 0.3s;
        }

        #restartButton:hover {
            background-color: #666;
        }
    </style>
</head>
<body>
    <h1>2048</h1>

    <div id="gameContainer">
        <div id="score">Score: 0</div>
        <div id="gridContainer"></div>
        <div id="gameOverContainer">
            <div id="gameOver">Game Over!</div>
            <button id="restartButton">Restart</button>
        </div>
    </div>

    <script>
        document.addEventListener("DOMContentLoaded", () => {
            const gridContainer = document.getElementById("gridContainer");
            const scoreDisplay = document.getElementById("score");
            const gameOverContainer = document.getElementById("gameOverContainer");
            const restartButton = document.getElementById("restartButton");

            const width = 4;
            let tiles = [];
            let score = 0;
            let gameOverFlag = false;

            function init() {
                score = 0;
                gameOverFlag = false;
                updateScore();
                gameOverContainer.style.display = "none";
                resetGrid();
                tiles = Array(width * width).fill(null);
                addTile();
                addTile();
                drawTiles();
            }

            function resetGrid() {
                gridContainer.innerHTML = "";
            }

            function addTile() {
                const emptyTiles = tiles.map((tile, index) => tile === null ? index : null).filter(index => index !== null);

                if (emptyTiles.length > 0) {
                    const randomIndex = emptyTiles[Math.floor(Math.random() * emptyTiles.length)];
                    tiles[randomIndex] = Math.random() < 0.9 ? 2 : 4;
                }
            }

            function drawTiles() {
                resetGrid();
                tiles.forEach(tile => {
                    const tileElement = document.createElement("div");
                    tileElement.classList.add("tile");
                    if (tile !== null) {
                        tileElement.textContent = tile;
                        tileElement.classList.add(`tile-${tile}`);
                    }
                    gridContainer.appendChild(tileElement);
                });
            }

            function updateScore() {
                scoreDisplay.textContent = `Score: ${score}`;
            }

            function move(direction) {
                if (gameOverFlag) return;
                let hasChanged = false;

                const moveTile = (currentIndex, newIndex) => {
                    if (tiles[newIndex] === null) {
                        tiles[newIndex] = tiles[currentIndex];
                        tiles[currentIndex] = null;
                        hasChanged = true;
                    } else if (tiles[newIndex] === tiles[currentIndex]) {
                        tiles[newIndex] *= 2;
                        tiles[currentIndex] = null;
                        score += tiles[newIndex];
                        hasChanged = true;
                    }
                };

                const moveUp = () => {
                    for (let col = 0; col < width; col++) {
                        for (let row = 1; row < width; row++) {
                            let currentIndex = col + row * width;
                            while (currentIndex >= width && tiles[currentIndex] !== null) {
                                moveTile(currentIndex, currentIndex - width);
                                currentIndex -= width;
                            }
                        }
                    }
                };

                const moveDown = () => {
                    for (let col = 0; col < width; col++) {
                        for (let row = width - 2; row >= 0; row--) {
                            let currentIndex = col + row * width;
                            while (currentIndex + width < width * width && tiles[currentIndex] !== null) {
                                moveTile(currentIndex, currentIndex + width);
                                currentIndex += width;
                            }
                        }
                    }
                };

                const moveLeft = () => {
                    for (let row = 0; row < width; row++) {
                        for (let col = 1; col < width; col++) {
                            let currentIndex = row * width + col;
                            while (currentIndex % width !== 0 && tiles[currentIndex] !== null) {
                                moveTile(currentIndex, currentIndex - 1);
                                currentIndex--;
                            }
                        }
                    }
                };

                const moveRight = () => {
                    for (let row = 0; row < width; row++) {
                        for (let col = width - 2; col >= 0; col--) {
                            let currentIndex = row * width + col;
                            while ((currentIndex + 1) % width !== 0 && tiles[currentIndex] !== null) {
                                moveTile(currentIndex, currentIndex + 1);
                                currentIndex++;
                            }
                        }
                    }
                };

                switch (direction) {
                    case "up":
                        moveUp();
                        break;
                    case "down":
                        moveDown();
                        break;
                    case "left":
                        moveLeft();
                        break;
                    case "right":
                        moveRight();
                        break;
                }

                if (hasChanged) {
                    addTile();
                    drawTiles();
                    updateScore();
                    checkGameOver();
                }
            }

            function checkGameOver() {
                if (!tiles.includes(null) && !checkMove()) {
                    gameOverFlag = true;
                    gameOverContainer.style.display = "block";
                    restartButton.addEventListener("click", restart, { once: true });
                }
            }

            function checkMove() {
                for (let i = 0; i < tiles.length; i++) {
                    if (tiles[i] === null) return true;
                    if (i % width !== width - 1 && tiles[i] === tiles[i + 1]) return true;
                    if (i < tiles.length - width && tiles[i] === tiles[i + width]) return true;
                }
                return false;
            }

            function restart() {
                init();
            }

            document.addEventListener("keydown", event => {
                switch (event.key) {
                    case "ArrowUp":
                    case "w":
                        move("up");
                        break;
                    case "ArrowDown":
                    case "s":
                        move("down");
                        break;
                    case "ArrowLeft":
                    case "a":
                        move("left");
                        break;
                    case "ArrowRight":
                    case "d":
                        move("right");
                        break;
                }
            });

            init();
        });
    </script>
</body>
</html>

 

通过这篇文章,你可以了解如何从头开始构建一个简单的 2048 游戏,包括 HTML、CSS 和 JavaScript 的使用。

希望你能从中学习到有用的知识!

 

感谢阅读!!!

 

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

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

相关文章

培训班和科班出生有什么区别

IT业的萌新来啦 每到毕业季 总有大量萌新走进职场 IT圈子的程序员 有的是科班出生 比如 计算机科学与技术、软件工程、大数据技术 有的是相关专业出生的 比如 信息安全、网络工程、物联网工程等等 除此之外 还有各种其他专业转行过来的 文理不限、专业不限 科班出生…

mydump 文件拆分 mysqldumpsplitter

mydump 文件拆分 如何将mysqldump的输出拆分为较小的文件&#xff1f; 要将mysqldump的输出拆分为较小的文件&#xff0c;可以使用–max-allowed-packet和–single-transaction选项。 使用–max-allowed-packet选项设置每个查询允许的最大数据包大小。这将确保mysqldump在执行…

精通 Stable Diffusion 调优秘籍

一、Stable Diffusion 调优概览 Stable Diffusion 作为一款强大的 AI 绘画工具&#xff0c;其调优具有至关重要的意义。通过合理的调优&#xff0c;可以显著提升图像生成的质量、速度和准确性&#xff0c;满足用户更加多样化和精细化的需求。 调优主要涵盖了多个方面。首先是模…

鸿蒙(API 12 Beta3版)【使用Image完成图片解码】图片开发指导依赖JS对象

图片解码指将所支持格式的存档图片解码成统一的[PixelMap]&#xff0c;以便在应用或系统中进行图片显示或[图片处理]。当前支持的存档图片格式包括JPEG、PNG、GIF、WebP、BMP、SVG、ICO、DNG。 开发步骤 添加依赖 在进行应用开发之前&#xff0c;开发者需要打开native工程的…

YOLOv5改进 | 融合改进 | C3 融合Efficient Multi-Scale Conv提升检测效果

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录&#xff1a; 《YOLOv5入门 改…

博立的个人代表作品集锦

1. 比赛同创 国一_2023年全国大学生电子设计竞赛 【信号分离装置】 国二_2023年全国大学生集成电路创新创业大赛半决赛作品&#xff08;robei赛道&#xff09; 【基于robei EDA的可重构无线控制小车设计】 省一_2022年重庆市大学生电子设计竞赛 【混沌信号发生装置】 国…

Oracle RAC 集群启动顺序

大家好&#xff0c;这里是 Lucifer三思而后行&#xff0c;专注于提升数据库运维效率。 目录 前言Oracle 11GR2 RAC 集群启动顺序官方文档11GR212CR218C19C21C23ai 往期精彩文章推荐 前言 前几天使用脚本在 RockyLinux 9.4 安装 Oracle 11GR2 RAC&#xff0c;安装完之后发现集群…

Adobe Premiere Pro (PR2024)win/mac 视频编辑软件安装下载(附安装包)

一、软件概述 1.1 Premiere Pro 简介 Adobe Premiere Pro (简称PR) 是一款专业的视频编辑软件&#xff0c;广泛应用于电影、电视、广告、网络视频等多种视频制作领域。它提供了强大的编辑工具、丰富的特效和灵活的工作流程&#xff0c;帮助用户高效地完成从素材整理到最终输出…

[Linux#41][线程] 线程的特性 | 分离线程 | 并发的问题

1.线程的特性 进程和线程的关系如下图: 关于进程线程的问题 • 如何看待之前学习的单进程&#xff1f;具有一个线程执行流的进程 线程 ID 及进程地址空间布局 pthread_ create 函数会产生一个线程 ID&#xff0c;存放在第一个参数指向的地址中。 该线程 ID 和前面说的线程 ID …

持久化SSE对象

SpringBoot整合SSE&#xff0c;实现后端主动推送DEMO 前些日子写了整合SSE得demo。但是SSE对象是存储在ConcurrentHashMap<String, SseEmitter>中。在正式环境明显就不行了&#xff0c;服务重启一下的话都没有了。 那么要持久化&#xff0c;第一选择放redis 1、写了一个…

When Do We Not Need Larger Vision Models?

总结 传统观点挑战&#xff1a;传统上&#xff0c;扩大视觉模型的大小一直被认为是提升视觉表示能力和下游任务性能的关键途径。然而&#xff0c;本文重新审视了这一观点&#xff0c;提出了通过在不同图像尺度上运行较小的预训练视觉模型&#xff08;如ViT-B或ViT-L&#xff0…

Linux入门——11 线程

线程的概念&#xff0c;线程的控制&#xff0c;线程的同步和互斥&#xff0c;队列结构&#xff0c;线程池&#xff0c;锁 1.预备知识 1.1可重入函数 1.1.1链表的头插 main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的时候,因为硬件中断…

续——网络通信编程

一、网络通信 1、编程 &#xff08;1&#xff09;基于UDP c/s通信模型 -------server——服务端——被动角色------- socket 全双工的&#xff08;可读可写&#xff09;。同上篇。 bind int bind(int sockfd , struct sockaddr *my_addr&#xff08;所绑定的地址信息&…

Linux的进程详解(进程创建函数fork和vfork的区别,资源回收函数wait,进程的状态(孤儿进程,僵尸进程),加载进程函数popen)

目录 什么是进程 Linux下操作进程的相关命令 进程的状态&#xff08;生老病死&#xff09; 创建进程系统api介绍&#xff1a; fork() 父进程和子进程的区别 vfork() 进程的状态补充&#xff1a; 孤儿进程 僵尸进程 回收进程资源api介绍&#xff1a; wait() waitpid…

编译运行 llama.cpp (vulkan, Intel GPU SYCL)

llama.cpp 是一个运行 AI (神经网络) 语言大模型的推理程序, 支持多种 后端 (backend), 也就是不同的具体的运行方式, 比如 CPU 运行, GPU 运行等. 但是编译运行 llama.cpp 并不是那么容易的, 特别是对于 SYCL 后端 (用于 Intel GPU), 坑那是一大堆. 只有特定版本的 llama.cpp…

【代码随想录训练营第42期 Day38打卡 - 动态规划Part6 - LeetCode 322. 零钱兑换 279.完全平方数 139.单词拆分

目录 一、做题心得 二、题目与题解 题目一&#xff1a;322. 零钱兑换 题目链接 题解&#xff1a;动态规划--完全背包 题目二&#xff1a; 279.完全平方数 题目链接 题解&#xff1a;动态规划--完全背包 题目三&#xff1a;139.单词拆分 题目链接 题解&#xff1a;动…

blender骨骼绑定(让物体动起来)

园哥摸索了两天了&#xff0c;骨骼做好就是不能带动物体&#xff0c;点击时候要选中那个骨骼点圆圈&#xff0c;点中间骨骼没用。终于动起来了。虽然有点奇怪。 点击图二那个点&#xff0c;貌似我的骨骼生长反了。做游戏是真麻烦。本来想搞个简单的2d游戏&#xff0c;结果那个瓦…

一起学Java(4)-[起步篇]教你掌握本协作项目中的Gralde相关配置文件(上)

将思绪拉回java-all-in-one项目&#xff0c;如果你fork并下载了代码&#xff0c;你会看到在项目中除了HelloWorldMain代码外&#xff0c;还存在很多文件。如果你并不了解他们的作用并有足够的好奇心&#xff0c;那你应该想要知道他们的作用。带着好奇&#xff0c;今天我也来研究…

网络抓包测试

利用fgets遇到\n停止的特性&#xff0c;给流数据直接加间隔&#xff0c;fgets读的时候会把soket缓冲区里面的数据全部放到fgets的缓冲区内&#xff0c;再读的时候就不能从套接字fd描述符读而是从fgets的fq里面读 行为1. 读取行为&#xff1a;•fgets 读取字符直到遇到换行符 \n…

下载ncurses操作步骤

https://invisible-island.net/ncurses/announce.htmlncurses-6.5.官网下载链接 选择下载版本