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 处理 URL 的两个函数代码
Aug 13 Javascript
LazyForm jQuery plugin 定制您的CheckBox Radio和Select
Oct 24 Javascript
获得Javascript对象属性个数的示例代码
Nov 21 Javascript
变量声明时命名与变量作为对象属性时命名的区别解析
Dec 06 Javascript
js实现Form栏显示全格式时间时钟效果代码
Aug 19 Javascript
简介alert()与console.log()的不同
Aug 26 Javascript
15个常用的jquery代码片段
Dec 19 Javascript
javascript实现鼠标点击页面 移动DIV
Dec 02 Javascript
vue 属性拦截实现双向绑定的实例代码
Oct 24 Javascript
详解webpack打包时排除其中一个css、js文件或单独打包一个css、js文件(两种方法)
Oct 26 Javascript
IE浏览器下JS脚本提交表单后,不能自动提示问题解决方法
Jun 04 Javascript
ng-alain的sf如何自定义部件的流程
Jun 12 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
Terran历史背景
2020/03/14 星际争霸
一个php作的文本留言本的例子(五)
2006/10/09 PHP
php读取txt文件组成SQL并插入数据库的代码(原创自Zjmainstay)
2012/07/31 PHP
php循环table实现一行两列显示的方法
2015/06/04 PHP
PHP网站建设的流程与步骤分享
2015/09/25 PHP
PHP 实现文件压缩解压操作的方法
2019/06/14 PHP
jquery 插件之仿“卓越亚马逊”首页弹出菜单效果
2008/12/25 Javascript
判断客户端浏览器是否安装了Flash插件的多种方法
2010/08/11 Javascript
用jquery实现点击栏目背景色改变
2012/12/10 Javascript
jquery时间下拉框小例子
2013/04/15 Javascript
javascript在网页中实现读取剪贴板粘贴截图功能
2014/06/07 Javascript
JS实现的通用表单验证插件完整实例
2015/08/20 Javascript
jquery mobile界面数据刷新的实现方法
2016/05/28 Javascript
关于在mongoose中填充外键的方法详解
2017/08/14 Javascript
vue组件watch属性实例讲解
2017/11/07 Javascript
利用SpringMVC过滤器解决vue跨域请求的问题
2018/02/10 Javascript
纯JS实现可用于页码更换的飞页特效示例
2018/05/21 Javascript
微信小程序框架的页面布局代码
2019/08/17 Javascript
JavaScript实现简单计算器
2020/03/19 Javascript
javascript实现固定侧边栏
2021/02/09 Javascript
使用Python编写一个在Linux下实现截图分享的脚本的教程
2015/04/24 Python
实例探究Python以并发方式编写高性能端口扫描器的方法
2016/06/14 Python
python中如何使用朴素贝叶斯算法
2017/04/06 Python
对python numpy.array插入一行或一列的方法详解
2019/01/29 Python
Django后台admin的使用详解
2019/07/08 Python
html5 Canvas画图教程(6)—canvas里画曲线之arcTo方法
2013/01/09 HTML / CSS
如何将整数int转换成字串String
2014/03/21 面试题
面试后感谢信
2014/02/01 职场文书
网络技术专业求职信
2014/02/18 职场文书
《荷花》教学反思
2014/04/16 职场文书
迎新晚会策划方案
2014/06/13 职场文书
机械设计制造及其自动化专业求职信
2014/06/17 职场文书
群众路线剖析材料范文
2014/10/09 职场文书
2014光棍节单身联谊活动策划书
2014/10/10 职场文书
安全先进个人材料
2014/12/29 职场文书
html中相对位置与绝对位置的具体使用
2022/05/15 HTML / CSS