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

基于 HTML5 的贪吃蛇小游戏实现

一、引言

在 Web 开发的世界中,利用 HTML、CSS 和 JavaScript 构建有趣的互动游戏是一项充满乐趣和挑战的任务。本文将详细介绍一个基于 HTML5 的贪吃蛇小游戏的实现过程,通过对代码的解析,带你了解如何打造一个经典的贪吃蛇游戏,并在网页上流畅运行。

二、游戏界面与基本样式

游戏的界面主要由一个表示游戏地图的div(类名为snake-map)、用于显示操作按钮的div(类名为operate)以及用于展示游戏说明的div(类名为desc)组成。

在 CSS 部分,snake-map被设置为相对定位,并通过flex布局使其内部元素水平垂直居中。游戏中的每个方块(蛇身、苹果和空白区域)都是一个绝对定位的div(类名为snake-item),通过设置不同的背景颜色来区分它们,如蛇身是绿色(类名为snake),苹果是深红色(类名为apple)。

操作按钮(类名为btn)同样使用flex布局,方便用户通过点击来控制蛇的移动方向。为了提升用户体验,当按钮被按下时,会通过active伪类改变背景颜色和文字颜色。

三、游戏逻辑代码解析

  1. 引入外部工具库:通过import语句从https://unpkg.com/@3r/tool引入了一些工具函数,如v2(可能用于表示二维向量)、Maths(数学相关操作)、Randoms(生成随机数)和cloneDeep(深度克隆对象)。
  2. 游戏参数初始化:定义了游戏地图的宽度width和高度height,并创建了一个二维数组map来表示游戏地图,初始值都为0(表示空白格)。同时,定义了一个枚举对象mapEnum,用于标识地图上不同元素的类型,如blank(空白格)、snake(蛇)和apple(苹果)。
  3. 蛇和移动方向:用一个数组snake来存储蛇的身体部分的位置,初始时蛇有两个身体部分。movingDirection变量用于记录蛇的移动方向,初始方向为向右。
  4. DOM 元素获取与变量声明:获取游戏地图的 DOM 元素snakeConDom、操作按钮的 DOM 元素集合operateDom,并创建一个对象snakeMapDom用于存储地图上每个方块对应的 DOM 元素。此外,还声明了一些变量用于记录地图的最大边界maxYmaxX,以及控制游戏循环的intervalTimeintervalId
  5. 初始化函数init:该函数负责创建游戏地图的 DOM 结构,根据map数组的大小动态生成每个方块的 DOM 元素,并设置它们的位置和初始样式。同时,将蛇的初始位置在地图上进行标记,设置操作按钮的点击事件监听器,获取地图的最大边界,并生成游戏中的第一个苹果。
  6. 渲染地图函数renderMap:遍历map数组,根据每个位置的元素类型,为对应的 DOM 元素添加或移除相应的类名,从而更新游戏地图的显示状态。
  7. 生成苹果函数generateApple:通过生成随机坐标,检查该位置是否为空白格,如果是则将其设置为苹果(在map数组中标记为mapEnum.apple),否则重新生成随机坐标,直到找到合适的位置。
  8. 移动函数moving:这是游戏的核心逻辑函数。首先判断当前的移动方向是否为0(即停止移动),如果是则直接返回。然后计算蛇头的新位置newHead,根据新位置的元素类型进行不同的处理:
    • 如果新位置是空白格,将蛇尾从数组中移除,并在新位置添加蛇头,同时更新地图上的元素标记。
    • 如果新位置是苹果,生成一个新的苹果,并在新位置添加蛇头,增加蛇的长度。
    • 如果新位置是蛇本身或超出地图边界,则游戏结束,清除游戏循环定时器,并在游戏地图上显示游戏结束的提示信息。
  9. 按键处理函数onKeyDown:根据用户按下的按键(向上、向下、向左、向右箭头键),计算对应的移动方向。在设置新的移动方向之前,会检查是否与当前禁止的移动方向(即蛇当前移动的反方向)相同,如果相同则不进行操作,避免蛇反向移动导致游戏逻辑错误。
  10. 事件监听器与游戏启动:通过document.addEventListener("keydown", (ev) => onKeyDown(ev.key))监听键盘按键事件,调用onKeyDown函数处理用户的按键操作。最后,调用init函数初始化游戏,并调用renderMap函数渲染初始地图,通过setInterval(moving, intervalTime)启动游戏循环,每隔intervalTime时间调用moving函数更新游戏状态。

 完整代码展示

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="./assets/global.css"><style>.snake-map {position: relative;display: flex;justify-content: center;align-items: center;}.snake-map .snake-item {background-color: saddlebrown;position: absolute;width: 20px;height: 20px;}.snake-map .snake-item.snake {background-color: green;}.snake-map .snake-item.apple {background-color: crimson;}.operate {display: flex;flex-wrap: wrap;margin: 0 20px;width: 120px;padding: 20px 0;}.operate .btn {width: 40px;height: 40px;display: flex;align-items: center;justify-content: center;user-select: none;}.operate .btn:active {background-color: saddlebrown;color: #fff;transition: all .4s ease-in-out;}.operate .flex {width: 100%;}.desc {margin: 20px;font-size: 12px;color: rgba(0, 0, 0, 0.7);}</style>
</head><body><div class="snake-map"> </div><div class="desc">玩法:通过上下左右键或屏幕上的上下左右将使其绿色线条(🐍)吃到红色方块(🍎)记录得分。</div><div class="operate"><div class="btn flex">上</div><div class="btn">左</div><div class="btn">下</div><div class="btn">右</div></div><script type="module">import { v2, Maths, Randoms, cloneDeep } from "https://unpkg.com/@3r/tool";let width = 20, height = 20;let map = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],]let mapEnum = {blank: 0, // 空白格snake: 1, // 蛇 🐍apple: 2, // 苹果 🍎}// 蛇数据let snake = [v2(5, 5), v2(5, 6)]// 移动方向let movingDirection = v2(1, 0)// 容器let snakeConDom = document.querySelector('.snake-map')// 操作元素let operateDom = document.querySelectorAll('.operate > .btn')// 地图let snakeMapDom = {}// 地图最大边界let maxY = 0;let maxX = 0;// 延迟let intervalTime = 500;let intervalId = 0;// 初始化function init() {let ylength = map.length, xlength = 0;for (let y = 0; y < map.length; y++) {const xmap = map[y];xlength = Math.max(xlength, xmap.length)for (let x = 0; x < xmap.length; x++) {let snakeItem = document.createElement('div')snakeItem.classList.add('snake-item')snakeItem.setAttribute(`style`, `left:${x * width}px;top:${y * height}px`)snakeConDom.appendChild(snakeItem)snakeMapDom[`${x},${y}`] = snakeItem}}for (let i = 0; i < snake.length; i++) {const snakeBody = snake[i];map[snakeBody.y][snakeBody.x] = mapEnum.snake;}snakeConDom.setAttribute(`style`, `width:${xlength * width}px;height:${ylength * height}px`)for (let i = 0; i < operateDom.length; i++) {const btn = operateDom.item(i);btn.addEventListener('click', function () {onKeyDown(['ArrowUp', 'ArrowLeft', 'ArrowDown', 'ArrowRight'][i])})}maxY = ylength;maxX = xlength;generateApple();}// 渲染地图function renderMap() {for (let y = 0; y < map.length; y++) {const xmap = map[y];for (let x = 0; x < xmap.length; x++) {let snakeItem = snakeMapDom[`${x},${y}`]if (xmap[x] == mapEnum.snake) snakeItem.classList.add('snake')else snakeItem.classList.remove('snake')if (xmap[x] == mapEnum.apple) snakeItem.classList.add('apple')else snakeItem.classList.remove('apple')}}}// 生成苹果function generateApple() {let apple = v2(Randoms.getRandomInt(0, maxX), Randoms.getRandomInt(0, maxY))if (map[apple.y][apple.x] != mapEnum.blank) return generateApple();map[apple.y][apple.x] = mapEnum.apple;return;}// 移动function moving() {// 运动为0时则停止运动if (Maths.equal(movingDirection, v2(0, 0))) return;// 头let head = cloneDeep(snake[0])// 新头let newHead = head.plus(movingDirection)// 当新头部是空白时if (map?.[newHead.y]?.[newHead.x] == mapEnum.blank) {// 尾巴let tail = snake.pop()map[tail.y][tail.x] = mapEnum.blank;map[newHead.y][newHead.x] = mapEnum.snake;snake.unshift(newHead)}// 再生成一个新🍎else if (map?.[newHead.y]?.[newHead.x] == mapEnum.apple) {generateApple()map[newHead.y][newHead.x] = mapEnum.snake;snake.unshift(newHead)}else {clearInterval(intervalId)snakeConDom.innerHTML = `游戏结束,得分为${snake.length - 2}`}renderMap()}function onKeyDown(key) {let direction = v2(0, 0)if (key == 'ArrowUp') direction = v2(0, -1)if (key == 'ArrowDown') direction = v2(0, 1)if (key == 'ArrowLeft') direction = v2(-1, 0)if (key == 'ArrowRight') direction = v2(1, 0)// 如果没有运动方向if (Maths.equal(movingDirection, direction)) return;let cannotMovingDirection = snake[1].subtract(snake[0]);// 如果移动方向是禁止的if (Maths.equal(cannotMovingDirection, direction)) return;movingDirection = direction;}document.addEventListener("keydown", (ev) => onKeyDown(ev.key))init();renderMap()intervalId = setInterval(moving, intervalTime)</script>
</body></html>

 

相关文章:

  • PE文件结构(导出表)
  • Linux 系统下VS Code python环境配置!
  • Cisco NDO - Nexus Dashboard Orchestrator
  • 六、shell脚本--正则表达式:玩转文本匹配的“万能钥匙”
  • Dify网页版 + vllm + Qwen
  • 论文报错4
  • Ubuntu安装编译环境
  • JookDB:一款国产的通用数据库开发工具
  • 网络传输中字节序
  • PostgreSQL 的 pg_current_wal_lsn 函数
  • Pinia状态管理工具速成
  • 【NLP】 28. 语言模型的评估方式:MRR, PERPLEXITY, BLEU, WER从困惑度到实际效果
  • C++ 类与对象(下)—— 进阶特性与底层机制解析(构造函数初始化,类型转换,static成员,友元,内部类,匿名对象)
  • torch.nn.Sequential() and torch.nn.ModuleList()
  • Linux 系统的指令详解介绍
  • 位运算的应用
  • 数据结构——算法复杂度
  • Linux系统安装PaddleDetection
  • 棋类游戏中的智能决策 ——蒙特卡洛树搜索(MCTS)算法解析
  • C# 反射
  • 媒体:不能让追求升学率,成为高中不双休的借口
  • 澎湃读报丨央媒头版五四青年节集中刊文:以青春之我,赴时代之约
  • AI世界的年轻人|与总书记交流过的“00后”,渴望紧握时代赋予的接力棒
  • 甘肃公布校园食品安全专项整治案例,有食堂涉腐败变质食物
  • 用小型核反应堆给数据中心供电,国内企业正在开展项目论证
  • 两部门发布“五一”假期全国森林草原火险形势预测