HTML+JavaScript 贪吃蛇游戏实现与详解

在网页开发的领域中,利用 HTML 和 JavaScript 能够创造出各种引人入胜的互动游戏。其中,贪吃蛇作为一款经典之作,以其简单易玩的特性和紧张刺激的挑战,一直深受玩家的喜爱。本文将详细阐述如何运用 HTML 和 JavaScript 来打造一个具有美观度的贪吃蛇游戏,并深入剖析其代码。

一、游戏介绍

贪吃蛇是一款广为人知的游戏。玩家的任务是通过方向键控制蛇的移动来吞食食物。随着蛇不断吃到食物,其身体会逐渐变长。然而,如果蛇头撞击到边界或者蛇身自身,游戏就会宣告结束。

二、效果图

三、代码分析

  1. HTML 结构

    • 页面主要包含一个<canvas>元素,用于绘制游戏画面。此外,还有一个<div>元素,用于放置开始按钮和显示当前分数。
  2. JavaScript 部分

    • SnakeGame类是游戏的核心所在。在构造函数中,初始化了游戏所需的各种属性,包括蛇的初始位置、移动方向、食物位置以及得分等。同时,还添加了事件监听器,分别用于响应开始按钮的点击和键盘按下事件。
    • generateFood方法负责生成随机的食物位置,确保食物在画布范围内随机出现。
    • draw方法承担着绘制游戏画面的重任,它会遍历蛇的身体部分进行绘制,包括蛇头、眼睛、嘴以及蛇身,同时还会绘制红色的食物方块。
    • update方法用于更新游戏状态,包括移动蛇的位置、检查是否吃到食物以及判断是否发生碰撞。如果蛇头与食物位置重合,蛇会吃到食物,身体变长且得分增加;如果发生碰撞,游戏结束并进行重置。
    • checkCollision方法主要检查蛇是否撞到边界或者自身身体。如果蛇头超出画布边界或者与蛇身其他部分重合,就会返回碰撞状态。
    • handleKeyDown方法处理键盘按下事件,根据按下的方向键改变蛇的移动方向,但不能直接反向移动。
    • startGame方法启动游戏,设置游戏运行标志为true,重置上次更新时间,并开始游戏循环。
    • gameLoop方法是游戏的循环核心,它根据时间间隔更新游戏状态并绘制游戏画面,通过不断请求动画帧来保持游戏的持续运行。

四、代码实现

以下是用 HTML 和 JavaScript 实现的贪吃蛇游戏代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>贪吃蛇游戏</title>
    <style>
        body {
            display: flex;
            flex-direction: column;
            align-items: center;
            background-color: #e8e8e8;
            font-family: 'Open Sans', sans-serif;
        }

        #gameCanvas {
            border: 4px solid #2e86c1;
            border-radius: 12px;
            box-shadow: 0 0 20px rgba(46, 134, 193, 0.4);
            margin: 40px auto;
        }

     .controls {
            background-color: #fff;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
            width: 420px;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        button {
            padding: 15px 30px;
            background-color: #2e86c1;
            color: #fff;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-size: 18px;
            margin-top: 20px;
            transition: background-color 0.3s ease;
        }

        button:hover {
            background-color: #226699;
        }

        p {
            font-size: 20px;
            margin-top: 15px;
        }
    </style>
</head>

<body>
    <canvas id="gameCanvas" width="400" height="400"></canvas>
    <div class="controls">
        <button id="startButton">开始游戏</button>
        <p>当前分数:<span id="scoreDisplay">0</span></p>
    </div>
    <script>
        // 定义贪吃蛇游戏类
        class SnakeGame {
            constructor() {
                // 获取canvas元素,用于绘制游戏画面
                this.canvas = document.getElementById('gameCanvas');
                // 获取canvas的绘图上下文,用于在canvas上进行绘图操作
                this.ctx = this.canvas.getContext('2d');
                // 每个方块的大小
                this.gridSize = 20;
                // canvas的宽度包含的方块数量
                this.widthInGrids = this.canvas.width / this.gridSize;
                // canvas的高度包含的方块数量
                this.heightInGrids = this.canvas.height / this.gridSize;
                // 初始化蛇的位置为一个包含一个对象的数组,对象表示蛇头的初始位置
                this.snake = [{ x: 10, y: 10 }];
                // 初始化蛇的移动方向为向右
                this.direction = 'right';
                // 生成食物的初始位置
                this.food = this.generateFood();
                // 游戏是否正在运行的标志,初始为false
                this.isRunning = false;
                // 得分,初始为0
                this.score = 0;
                // 给开始按钮添加点击事件监听器,点击时调用startGame方法
                document.getElementById('startButton').addEventListener('click', this.startGame.bind(this));
                // 给文档添加键盘按下事件监听器,按下键盘时调用handleKeyDown方法
                document.addEventListener('keydown', this.handleKeyDown.bind(this));
            }

            // 生成食物位置的方法
            generateFood() {
                // 返回一个随机的食物位置对象,x和y坐标在canvas范围内随机生成
                return {
                    x: Math.floor(Math.random() * this.widthInGrids),
                    y: Math.floor(Math.random() * this.heightInGrids),
                };
            }

            // 绘制游戏画面的方法
            draw() {
                // 清空canvas,为绘制新的一帧画面做准备
                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                // 遍历蛇的身体部分进行绘制
                this.snake.forEach((segment, index) => {
                    if (index === 0) {
                        // 绘制蛇头
                        this.ctx.beginPath();
                        this.ctx.arc(segment.x * this.gridSize + this.gridSize / 2, segment.y * this.gridSize + this.gridSize / 2, this.gridSize / 2, 0, 2 * Math.PI);
                        this.ctx.fillStyle = '#006400';
                        this.ctx.fill();
                        // 绘制眼睛
                        this.ctx.beginPath();
                        this.ctx.arc(segment.x * this.gridSize + this.gridSize / 4, segment.y * this.gridSize + this.gridSize / 4, 2, 0, 2 * Math.PI);
                        this.ctx.fillStyle = 'white';
                        this.ctx.fill();
                        this.ctx.beginPath();
                        this.ctx.arc(segment.x * this.gridSize + this.gridSize / 4, segment.y * this.gridSize + this.gridSize / 4, 1, 0, 2 * Math.PI);
                        this.ctx.fillStyle = 'black';
                        this.ctx.fill();
                        this.ctx.beginPath();
                        this.ctx.arc(segment.x * this.gridSize + this.gridSize * 3 / 4, segment.y * this.gridSize + this.gridSize / 4, 2, 0, 2 * Math.PI);
                        this.ctx.fillStyle = 'white';
                        this.ctx.fill();
                        this.ctx.beginPath();
                        this.ctx.arc(segment.x * this.gridSize + this.gridSize * 3 / 4, segment.y * this.gridSize + this.gridSize / 4, 1, 0, 2 * Math.PI);
                        this.ctx.fillStyle = 'black';
                        this.ctx.fill();
                        // 绘制嘴
                        this.ctx.beginPath();
                        this.ctx.moveTo(segment.x * this.gridSize + this.gridSize / 2, segment.y * this.gridSize + this.gridSize * 3 / 4);
                        this.ctx.lineTo(segment.x * this.gridSize + this.gridSize / 4, segment.y * this.gridSize + this.gridSize * 2 / 3);
                        this.ctx.lineTo(segment.x * this.gridSize + this.gridSize * 3 / 4, segment.y * this.gridSize + this.gridSize * 2 / 3);
                        this.ctx.fillStyle = '#003300';
                        this.ctx.fill();
                    } else {
                        // 绘制蛇身,使用较浅的绿色
                        this.ctx.fillStyle = '#008000';
                        this.ctx.fillRect(segment.x * this.gridSize, segment.y * this.gridSize, this.gridSize, this.gridSize);
                    }
                });
                // 绘制红色方块表示食物
                this.ctx.fillStyle = '#ff0000';
                this.ctx.fillRect(this.food.x * this.gridSize, this.food.y * this.gridSize, this.gridSize, this.gridSize);
            }

            // 更新游戏状态的方法
            update() {
                // 如果游戏没有在运行,直接返回,不进行更新
                if (!this.isRunning) return;
                // 获取蛇头的位置
                const head = { x: this.snake[0].x, y: this.snake[0].y };
                // 根据当前方向更新蛇头位置
                switch (this.direction) {
                    case 'right':
                        head.x++;
                        break;
                    case 'left':
                        head.x--;
                        break;
                    case 'up':
                        head.y--;
                        break;
                    case 'down':
                        head.y++;
                        break;
                }
                // 将新的蛇头位置添加到蛇身数组的开头
                this.snake.unshift(head);
                // 如果蛇头位置与食物位置相同,表示吃到食物
                if (head.x === this.food.x && head.y === this.food.y) {
                    // 重新生成食物位置
                    this.food = this.generateFood();
                    // 得分加一
                    this.score++;
                    // 更新页面上显示的得分
                    document.getElementById('scoreDisplay').textContent = this.score;
                } else {
                    // 如果没有吃到食物,将蛇尾部分移除
                    this.snake.pop();
                }
                // 检查是否发生碰撞
                if (this.checkCollision()) {
                    // 如果发生碰撞,弹出提示框显示得分并重置游戏状态
                    alert(`游戏结束!你的得分为:${this.score}`);
                    this.snake = [{ x: 10, y: 10 }];
                    this.direction = 'right';
                    this.food = this.generateFood();
                    this.score = 0;
                    document.getElementById('scoreDisplay').textContent = this.score;
                    this.isRunning = false;
                }
            }

            // 检查碰撞的方法
            checkCollision() {
                const head = this.snake[0];
                // 检查蛇头是否超出canvas边界
                if (head.x < 0 || head.x >= this.widthInGrids || head.y < 0 || head.y >= this.heightInGrids) {
                    return true;
                }
                // 检查蛇头是否与蛇身其他部分碰撞
                for (let i = 1; i < this.snake.length; i++) {
                    if (head.x === this.snake[i].x && head.y === this.snake[i].y) {
                        return true;
                    }
                }
                // 如果没有发生碰撞,返回false
                return false;
            }

            // 处理键盘按下事件的方法
            handleKeyDown(event) {
                // 如果游戏没有在运行,直接返回,不处理按键事件
                if (!this.isRunning) return;
                // 根据按下的方向键改变蛇的移动方向,但不能直接反向移动
                switch (event.key) {
                    case 'ArrowRight':
                        if (this.direction!== 'left') this.direction = 'right';
                        break;
                    case 'ArrowLeft':
                        if (this.direction!== 'right') this.direction = 'left';
                        break;
                    case 'ArrowUp':
                        if (this.direction!== 'down') this.direction = 'up';
                        break;
                    case 'ArrowDown':
                        if (this.direction!== 'up') this.direction = 'down';
                        break;
                }
            }

            // 开始游戏的方法
            startGame() {
                // 设置游戏正在运行标志为true
                this.isRunning = true;
                // 将上次更新时间重置为0
                this.lastTime = 0;
                // 开始游戏循环
                requestAnimationFrame(this.gameLoop.bind(this));
            }

            // 游戏循环方法
            gameLoop(timestamp) {
                // 如果游戏没有在运行,直接返回
                if (!this.isRunning) return;
                // 如果自上次更新时间超过100毫秒
                if (timestamp - this.lastTime > 100) {
                    // 更新游戏状态
                    this.update();
                    // 绘制游戏画面
                    this.draw();
                    // 更新上次更新时间为当前时间戳
                    this.lastTime = timestamp;
                }
                // 请求下一次动画帧,继续游戏循环
                requestAnimationFrame(this.gameLoop.bind(this));
            }
        }

        // 游戏规则说明:
        // - 玩家通过方向键控制蛇的移动。
        // - 蛇会不断地朝着当前方向移动,吃到食物后身体会变长,得分增加。
        // - 如果蛇头撞到边界或者自己的身体,游戏结束。

        // 创建一个新的游戏实例,启动游戏
        new SnakeGame();
    </script>
</body>

</html>

五、总结

通过本文,我们成功地实现了一个简单的贪吃蛇游戏,并对其代码进行了详细的分析。这个游戏展示了 HTML5 的canvas元素和 JavaScript 的强大组合能力,为网页游戏开发提供了一个良好的示例。你可以根据自己的需求进一步扩展和优化这个游戏,比如添加不同的游戏模式、音效效果等,使其更加丰富多彩,为玩家带来更好的游戏体验。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OPPO携手比亚迪共同探索手机与汽车互融新时代

10月23日&#xff0c;OPPO与比亚迪宣布签订战略合作协议&#xff0c;双方将共同推进手机与汽车的互融合作&#xff0c;这一合作也标志着两大行业巨头在技术创新和产业融合上迈出了重要一步&#xff0c;为手机与汽车的深度融合探索新的可能。 OPPO创始人兼首席执行官陈明永、OP…

鸿蒙网络编程系列3-TCP客户端通讯示例

1. TCP简介 TCP协议是传输层最重要的协议&#xff0c;提供了可靠、有序的数据传输&#xff0c;是多个广泛使用的表示层协议的运行基础&#xff0c;相对于UDP来说&#xff0c;TCP需要经过三次握手后才能建立连接&#xff0c;建立连接后才能进行数据传输&#xff0c;所以效率差了…

【PDF文件】默认被某种软件打开,如何进行修改?

当有时下载某种软件后&#xff0c;电脑中的PDF文件就默认由该种软件打开&#xff0c;每次需要右键选择打开方式才能选择需要的其他软件打开。如下图所示。 修改方法&#xff1a; &#xff08;1&#xff09;点击电脑的“设置”&#xff0c;选择应用 &#xff08;2&#xff09;…

Java一站式校园社区外卖系统小程序源码

一站式校园社区外卖系统&#xff0c;让校园生活更便捷&#xff01; &#x1f3eb; 开篇&#xff1a;校园生活的“新宠儿” 嘿&#xff0c;小伙伴们&#xff01;今天要和大家分享一个超级实用的校园生活神器——“一站式校园社区外卖系统”&#xff01;在这个快节奏的时代&…

一个开源可私有化部署的没有任何广告的网易云

优点 ✅ 使用 Vue.js 全家桶开发&#x1f534; 网易云账号登录&#xff08;扫码/手机/邮箱登录&#xff09;&#x1f4fa; 支持 MV 播放&#x1f4c3; 支持歌词显示&#x1f4fb; 支持私人 FM / 每日推荐歌曲&#x1f6ab;&#x1f91d; 无任何社交功能&#x1f30e;️ 海外用…

歌手如何建立抖音百科?塑造个人形象!

在抖音这个充满无限可能的舞台上&#xff0c;明星们以独特的魅力吸引着亿万粉丝的目光。而抖音百科&#xff0c;作为明星们展示自我、塑造形象的又一重要窗口&#xff0c;正逐渐成为连接明星与粉丝的桥梁。 创建明星人物抖音百科&#xff0c;不仅是对明星过往成就的总结与回顾&…

WRB Hidden Gap,WRB隐藏缺口,MetaTrader 免费公式!(指标教程)

WRB Hidden Gap MetaTrader 指标用于检测和标记宽范围的柱体&#xff08;非常长的柱体&#xff09;或宽范围的烛身&#xff08;具有非常长实体的阴阳烛&#xff09;。此指标可以识别WRB中的隐藏跳空&#xff0c;并区分显示已填补和未填补的隐藏跳空&#xff0c;方便用户一眼识别…

uniapp移动端优惠券! 附源码!!!!

本文为常见的移动端uniapp优惠券&#xff0c;共有6种优惠券样式&#xff08;参考了常见的优惠券&#xff09;&#xff0c;文本内容仅为示例&#xff0c;您可在此基础上调整为你想要的文本 预览效果 通过模拟数据&#xff0c;实现点击使用优惠券让其变为灰色的效果&#xff08;模…

使用Dask在多块AMD GPU上加速XGBoost

Accelerating XGBoost with Dask using multiple AMD GPUs — ROCm Blogs 2024年1月26日 由Clint Greene撰写。 XGBoost 是一个用于分布式梯度提升的优化库。它已经成为解决回归和分类问题的领先机器学习库。如果您想深入了解梯度提升的工作原理&#xff0c;推荐阅读 Introduc…

Maven入门到实践:从安装到项目构建与IDEA集成

目录 1. Maven的概念 1.1 什么是Maven 1.2 什么是依赖管理 1.3 什么是项目构建 1.4 Maven的应用场景 1.5 为什么使用Maven 1.6 Maven模型 2.初识Maven 2.1 Maven安装 2.1.1 安装准备 2.1.2 Maven安装目录分析 2.1.3 Maven的环境变量 2.2 Maven的第一个项目 2.2.1…

深度学习Pytorch-Tensor函数

深度学习Pytorch-Tensor函数 Tensor的三角函数Tensor中其他的数学函数Tensor中统计学相关的函数&#xff08;维度&#xff0c;对于二维数据&#xff1a;dim0 按列&#xff0c;dim1 按行&#xff0c;默认 dim1&#xff09;Tensor的torch.distributions(分布函数)Tensor中的随机抽…

图论day62|拓扑排序理论基础、117.软件构建(卡码网)、最短路径之dijkstra理论基、47.参加科学大会(卡码网 第六期模拟笔试)

图论day62|拓扑排序理论基础、117.软件构建&#xff08;卡码网&#xff09;、最短路径之dijkstra理论基、47.参加科学大会&#xff08;卡码网 第六期模拟笔试&#xff09; 拓扑排序理论基础117.软件构建&#xff08;卡码网&#xff09;最短路径之dijkstra理论基础47.参加科学大…

IDEA 安装热部署 JRebel -新版-亲测有效

由于采用直接从idea 下载的插件会出现版本不适配&#xff0c;激活不成功 下载地址&#xff1a;https://note.youdao.com/web/#/file/recent/note/WEB0e3010b4015162dc6a11d6c0ab11f750/ 导入刚才下载的插件 其中&#xff0c;Team URL可以使用在线GUID地址在线生成GUID 拿到GUID…

Node.js 模块化

1. 介绍 1.1 什么是模块化与模块 ? 将一个复杂的程序文件依据一定规则&#xff08;规范&#xff09;拆分成多个文件的过程称之为 模块化其中拆分出的 每个文件就是一个模块 &#xff0c;模块的内部数据是私有的&#xff0c;不过模块可以暴露内部数据以便其他模块使用 1.2 什…

蓝桥杯注意事项

蓝桥杯注意事项 比赛注意事项 能暴力枚举就暴力枚举&#xff0c;能用简单的思路做就尽量用简单的思路做。认真审核题目的题意和输入输出的要求&#xff0c;避免因为误解题意而导致题目错误。对于提供多组测试样例或者需要对一个过程重复进行循环的代码&#xff0c;要时刻记住…

第四范式发布AI Data Foundry,加速大模型训练及应用

产品上新 Product Release 今日&#xff0c;第四范式发布AI Data Foundry&#xff0c;提供基于AI技术&#xff0c;融合人类专家反馈的高质量、丰富可扩展、多样化的数据集&#xff0c;大幅提升模型效果。同时&#xff0c;通过模型评估系统及工具&#xff0c;对模型效果进行有效…

w外链如何跳转微信小程序

要创建外链跳转微信小程序&#xff0c;主要有以下几种方法&#xff1a; 使用第三方工具生成跳转链接&#xff1a; 注册并登录第三方外链平台&#xff1a;例如 “W外链” 等工具。前往该平台的官方网站&#xff0c;使用手机号、邮箱等方式进行注册并登录账号。选择创建小程序外…

windows SVN 忘记账号密码

一、本地登录过且记录未清空 1、打开C:\Users\用户名\AppData\Roaming\Subversion\auth\svn.simple目录 2、下载SvnPwd.exe文件 链接地址&#xff1a;TortoiseSVN Password Decrypter 复制SvnPwd.exe到 C:\Users\用户名\AppData\Roaming\Subversion\auth\svn.simple目录下 3、运…

Web组态-仪器间的相互通信(WebSocket技术)

Web组态&#xff0c;通过Vue3TypeScriptWebSocket技术实现平台仪器间的相互通信&#xff0c;用于设计工业化虚拟仿真。 界面图如下&#xff08;之前文章有详细教学&#xff09; 如下是通信设备虚拟仿真的三个仪器&#xff0c;设计初衷是想三个仪器能够数据互通&#xff0c;实现…

【Thymeleaf】spring boot模板引擎thymeleaf用法详解

快速入门Thymeleaf 1️⃣ 什么是Thymeleaf&#xff1f;1️⃣ 模板入门2️⃣ 创建测试工程2️⃣ 配置文件2️⃣ 创建controller2️⃣ 写一个html页面2️⃣ 启动测试 1️⃣ Thymeleaf基础2️⃣ 实体类2️⃣ 增加接口2️⃣ $符号使用2️⃣ *符号的使用2️⃣ 符号的使用2️⃣ #符号…