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 相关文章推荐
js自带函数备忘 数组
Dec 29 Javascript
javascript中不等于的代码是什么怎么写
Dec 29 Javascript
如何设置一定时间内只能发送一次请求
Feb 28 Javascript
js检测浏览器版本、核心、是否移动端示例
Apr 24 Javascript
jQuery实现长按按钮触发事件的方法
Feb 02 Javascript
JQuery复制DOM节点的方法
Jun 11 Javascript
jQuery实现的图文高亮滚动切换特效实例
Aug 10 Javascript
JavaScript驾驭网页-获取网页元素
Mar 24 Javascript
浅谈Angular的$q, defer, promise
Dec 20 Javascript
vue-cli构建项目使用 less的方法
Oct 04 Javascript
js实现Tab选项卡切换效果
Jul 17 Javascript
微信小程序吸底区域适配iPhoneX的实现
Apr 09 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
PHP fastcgi模式上传大文件(大约有300多K)报错
2014/09/28 PHP
js小技巧--自动隐藏红叉叉
2007/08/13 Javascript
JS创建自定义表格具体实现
2014/02/11 Javascript
调用DOM对象的focus使文本框获得焦点
2014/02/19 Javascript
jquery $.trim()方法使用介绍
2014/05/21 Javascript
情人节单身的我是如何在敲完代码之后收到12束玫瑰的(javascript)
2015/08/21 Javascript
学习javascript的闭包,原型,和匿名函数之旅
2015/10/18 Javascript
跟我学习javascript的prototype使用注意事项
2015/11/17 Javascript
给before和after伪元素设置js效果的方法
2015/12/04 Javascript
jquery zTree异步加载、模糊搜索简单实例分享
2016/03/24 Javascript
从重置input file标签中看jQuery的 .val() 和 .attr(“value”) 区别
2016/06/12 Javascript
js原生代码实现轮播图的实例讲解
2017/07/28 Javascript
30分钟快速入门掌握ES6/ES2015的核心内容(下)
2018/04/18 Javascript
vue 监听某个div垂直滚动条下拉到底部的方法
2018/09/15 Javascript
详解Vue SSR( Vue2 + Koa2 + Webpack4)配置指南
2018/11/13 Javascript
JS多个表单数据提交下的serialize()应用实例分析
2019/08/27 Javascript
微信小程序使用 vant Dialog组件的正确方式
2020/02/21 Javascript
[01:09:10]NB vs Liquid Supermajor小组赛 A组胜者组决赛 BO3 第一场 6.2
2018/06/04 DOTA
[03:17]DOTA2-DPC中国联赛1月29日Recap集锦
2021/03/11 DOTA
详解如何用OpenCV + Python 实现人脸识别
2017/10/20 Python
python实现类之间的方法互相调用
2018/04/29 Python
Python操作mongodb的9个步骤
2018/06/04 Python
Python自定义一个异常类的方法
2019/06/27 Python
如何将PySpark导入Python的放实现(2种)
2020/04/26 Python
pycharm 实现本地写代码,服务器运行的操作
2020/06/08 Python
深入CSS3 动画效果的总结详解
2013/05/09 HTML / CSS
纯CSS3实现鼠标滑过按钮动画第二节
2020/07/16 HTML / CSS
关于HTML5的22个初级技巧(图文教程)
2012/06/21 HTML / CSS
Belle Maison倍美丛官网:日本千趣会旗下邮购网站
2016/07/22 全球购物
日本索尼音乐商店:Sony Music Shop
2018/07/17 全球购物
华纳兄弟工作室的官方授权商店:WB Shop
2018/11/30 全球购物
日本著名化妆品零售网站:Cosme Land
2019/03/01 全球购物
init进程的作用
2015/08/20 面试题
2014年房产经纪人工作总结
2014/12/08 职场文书
2015年预备党员自我评价
2015/03/04 职场文书
SpringBoot全局异常处理方案分享
2022/05/25 Java/Android