canvas实现贪食蛇的实践


Posted in Javascript onFebruary 15, 2022

贪食蛇

最近 dan 有在油管上直播,播放量最多的就是手写一个贪食蛇。
本来想学一下大佬写代码的姿势,看了几分钟就没了耐性,心想我为什么不能自己写一个呢。
一步一步跟着敲代码,我实践了一段时间但是收效甚微,因为中间少了自己的思考。
初期可能有些作用,可以学到一些技巧和规范。
但是自己实现一个东西带来的成就感,你不断的 debug 和查文档查资料留下的记忆和习惯,这大概就是这个玩意带给我最大的收获吧。
canvas实现贪食蛇的实践
我的 github 一直是单机模式,如果这篇文章对您有所帮助的话欢迎点个 star

数据结构及变量

const canvas = document.getElementById("canvas")
const ctx = canvas.getContext("2d")

const width = 400
const height = 400
const cellLength = 20

let foodPosition

let initSnake = [
  [0, 0],
  [1, 0],
  [2, 0],
]

let snake = [...initSnake]

let direction = "right"

let canChangeDirection = true

canvas 绘制页面

//  背景
function drawBackground() {
  ctx.strokeStyle = "#bfbfbf"
  for (let i = 0; i <= height / cellLength; i++) {
    ctx.beginPath()
    ctx.moveTo(0, cellLength * i)
    ctx.lineTo(width, cellLength * i)
    ctx.stroke()
  }

  for (let i = 0; i <= width / cellLength; i++) {
    ctx.beginPath()
    ctx.moveTo(cellLength * i, 0)
    ctx.lineTo(cellLength * i, height)
    ctx.stroke()
  }
}

// 蛇
function drawSnake() {
  let step = 100 / (snake.length - 1)
  for (let i = 0; i < snake.length; i++) {
    // 这里做了渐变色的蛇,添加动态色彩。尾部有个最小白色阀值,免得跟背景混为一体
    const percent = Math.min(100 - step * i, 90)
    ctx.fillStyle = `hsl(0,0%,${percent}%)`

    ctx.fillRect(
      snake[i][0] * cellLength,
      snake[i][1] * cellLength,
      cellLength,
      cellLength
    )
  }
}

// 绘制食物

// 随机生成食物的位置
function generateRandomFood() {
  // 如果没有位置可以生成
  if (snake.length > width * height) {
    return alert("you win")
  }
  const randomX = Math.floor(Math.random() * (width / cellLength))
  const randomY = Math.floor(Math.random() * (height / cellLength))
  // 生成的位置如果跟蛇体积碰撞,则重新生成。
  for (let i = 0; i < snake.length; i++) {
    if (snake[i][0] === randomX && snake[i][1] === randomY) {
      return generateRandomFood()
    }
  }
  foodPosition = [randomX, randomY]
}

// 绘制
function drawFood() {
  ctx.fillStyle = "#ff7875"
  ctx.fillRect(
    foodPosition[0] * cellLength,
    foodPosition[1] * cellLength,
    cellLength,
    cellLength
  )
}

蛇的移动

// 蛇的移动
// 确定下一次移动的位置,将这个点push到数组末尾(头的位置),
// 将数组第一项shift出来(尾的位置)

// 吃食物的逻辑
// 如果食物的位置跟下一次移动的位置相同,将这个点加入头部,不推出尾部

function snakeMove() {
  let next
  let last = snake[snake.length - 1]
  // 根据方向确定下一个蛇头的位置
  switch (direction) {
    case "up": {
      next = [last[0], last[1] - 1]
      break
    }
    case "down": {
      next = [last[0], last[1] + 1]
      break
    }
    case "left": {
      next = [last[0] - 1, last[1]]
      break
    }
    case "right": {
      next = [last[0] + 1, last[1]]
      break
    }
  }

  // 边缘碰撞
  const boundary =
    next[0] < 0 ||
    next[0] >= width / cellLength ||
    next[1] < 0 ||
    next[1] >= height / cellLength

  // 自身碰撞
  const selfCollision = snake.some(([x, y]) => next[0] === x && next[1] === y)

  // 碰撞重新开始游戏
  if (boundary || selfCollision) {
    return restart()
  }

  snake.push(next)

  // 如果下一个点是食物的位置,不推出头部
  if (next[0] === foodPosition[0] && next[1] === foodPosition[1]) {
    generateRandomFood()
    return
  }
  snake.shift()

  canChangeDirection = true
}

事件监听

document.addEventListener("keydown", (e) => {
  switch (e.key) {
    case "ArrowUp":
      if (direction === "down" || !canChangeDirection) return
      direction = "up"
      canChangeDirection = false
      break
    case "ArrowDown":
      if (direction === "up" || !canChangeDirection) return
      direction = "down"
      canChangeDirection = false
      break
    case "ArrowLeft":
      if (direction === "right" || !canChangeDirection) return
      direction = "left"
      canChangeDirection = false
      break
    case "ArrowRight":
      if (direction === "left" || !canChangeDirection) return
      direction = "right"
      canChangeDirection = false
      break
  }
})

requestAnimationFrame 实现动画

// 默认的requestAnimationFrame循环应该是60帧,对于这个游戏来说太快了。
// 所以做了限制,5次loop才渲染(蛇移动一格)一次
function animate() {
  let count = 0
  function loop() {
    if (++count > 5) {
      draw()
      count = 0
    }
    requestAnimationFrame(loop)
  }
  requestAnimationFrame(loop)
}

BUG 解决

游戏规则中,蛇是不能反向移动的。
但是在事件回调中,如果改变方向过快,(5 次 loop 才执行一次重绘),就会出现方向变了,但是蛇的位置没变(比如蛇向右移动,我们先按上再按左),他就和自身碰撞了
解决方案:
我加了一个 canChangeDirection 变量,
当你改变方向之后,必须等待蛇移动了才能再次改变方向
// 事件回调
case "ArrowUp":
  if (direction === "down" |!canChangeDirection) return
  direction = "up"
  canChangeDirection = false
  break

到此这篇关于canvas实现贪食蛇的实践的文章就介绍到这了,更多相关 canvas贪食蛇内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章,希望大家以后多多支持三水点靠木!

 
Javascript 相关文章推荐
JavaScript基本概念初级讲解论坛贴的学习记录
Feb 22 Javascript
基于jquery的多彩百分比 动态进度条 投票效果显示效果实现代码
Aug 28 Javascript
JS重要知识点小结
Nov 06 Javascript
JS实现下拉框的动态添加(附效果)
Apr 03 Javascript
javascript窗口宽高,鼠标位置,滚动高度(详细解析)
Nov 18 Javascript
JS小功能(列表页面隔行变色)简单实现
Nov 28 Javascript
jQuery实现简单二级下拉菜单
Apr 12 Javascript
jQuery grep()方法详解及实例代码
Oct 30 Javascript
layui加载数据显示loading加载完成loading消失的实例代码
Sep 23 Javascript
微信小程序实现多选框全选与反全选及购物车中删除选中的商品功能
Dec 17 Javascript
JS面向对象编程实现的拖拽功能案例详解
Mar 03 Javascript
JS使用setInterval计时器实现挑战10秒
Nov 08 Javascript
Vue自定义铃声提示音组件的实现
Jan 22 #Vue.js
JavaScript实例 ODO List分析
Jan 22 #Javascript
JavaScript ES6的函数拓展
Jan 18 #Javascript
Vue提供的三种调试方式你知道吗
Jan 18 #Vue.js
详解Vue项目的打包方式(生成dist文件)
Jan 18 #Vue.js
html5调用摄像头截图功能
Jan 18 #Javascript
在 HTML 页面中使用 React的场景分析
Jan 18 #Javascript
You might like
dedecms 制作模板中使用的全局标记图文教程
2007/03/11 PHP
php xml常用函数的集合(比较详细)
2013/06/06 PHP
php网站被挂木马后的修复方法总结
2014/11/06 PHP
详解yii2实现分库分表的方案与思路
2017/02/03 PHP
动态表单验证的操作方法和TP框架里面的ajax表单验证
2017/07/19 PHP
Thinkphp框架使用list_to_tree 实现无限级分类列出所有节点示例
2020/04/04 PHP
List the Codec Files on a Computer
2007/06/11 Javascript
ext form 表单提交数据的方法小结
2008/08/08 Javascript
jQuery数据显示插件整合实现代码
2011/10/24 Javascript
jquery focus(fn),blur(fn)方法实例代码
2011/12/16 Javascript
基于jquery的网站幻灯片切换效果焦点图代码
2013/09/15 Javascript
Jquery实现仿腾讯微博发表广播
2014/11/17 Javascript
javascript实现一个简单的弹出窗
2016/02/22 Javascript
第六篇Bootstrap表格样式介绍
2016/06/21 Javascript
Jquery Easyui自定义下拉框组件使用详解(21)
2020/12/31 Javascript
javascript 中null和undefined区分和比较
2017/04/19 Javascript
Vue.js简易安装和快速入门(第二课)
2017/10/17 Javascript
深入学习TypeScript 、React、 Redux和Ant-Design的最佳实践
2019/06/17 Javascript
async/await让异步操作同步执行的方法详解
2019/11/01 Javascript
微信小程序整个页面的自动适应布局的实现
2020/07/12 Javascript
Django中模版的子目录与include标签的使用方法
2015/07/16 Python
PyQT实现菜单中的复制,全选和清空的功能的方法
2019/06/17 Python
详解Selenium+PhantomJS+python简单实现爬虫的功能
2019/07/14 Python
PyTorch 随机数生成占用 CPU 过高的解决方法
2020/01/13 Python
Spring Boot中使用IntelliJ IDEA插件EasyCode一键生成代码详细方法
2020/03/20 Python
django数据模型中null和blank的区别说明
2020/09/02 Python
html5 冒号分隔符对齐的实现
2019/07/31 HTML / CSS
Square Off美国/加拿大:世界上最聪明的国际象棋棋盘
2018/12/06 全球购物
捐书活动总结
2014/05/04 职场文书
节能环保标语
2014/06/12 职场文书
中秋节国旗下演讲稿
2014/09/13 职场文书
处级领导干部四风问题自我剖析材料
2014/09/29 职场文书
学校总务处领导干部个人对照检查材料思想汇报
2014/10/06 职场文书
Python如何导出导入所有依赖包详解
2021/06/08 Python
python 离散点图画法的实现
2022/04/01 Python
SQL试题 使用窗口函数选出连续3天登录的用户
2022/04/24 Oracle