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 获取Mouse Position(鼠标坐标)的代码
Sep 21 Javascript
JavaScript在IE和Firefox浏览器下的7个差异兼容写法小结
Jun 18 Javascript
关于 文本框默认值 的操作js代码
Jan 12 Javascript
js实现倒计时(距离结束还有)示例代码
Jul 24 Javascript
js闭包的用途详解
Nov 09 Javascript
JavaScript实现标题栏文字轮播效果代码
Oct 24 Javascript
JSON与String互转的实现方法(Javascript)
Sep 27 Javascript
微信小程序 PHP后端form表单提交实例详解
Jan 12 Javascript
Bootstrap Table使用整理(四)之工具栏
Jun 09 Javascript
使用Vue开发一个实时性时间转换指令
Jan 17 Javascript
深入探讨JavaScript的最基本部分之执行上下文
Feb 12 Javascript
JavaScript实现五子棋游戏的方法详解
Jul 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
php下清空字符串中的HTML标签的代码
2010/09/06 PHP
php中的curl使用入门教程和常见用法实例
2014/04/10 PHP
如何解决PHP使用mysql_query查询超大结果集超内存问题
2016/03/14 PHP
JS应用之禁止抓屏、复制、打印
2008/02/21 Javascript
firefox和IE系列的相关区别整理 以备后用
2009/12/28 Javascript
javascript实现的DES加密示例
2013/10/30 Javascript
详解JS 比较两个Json对象的值是否相等的实例
2013/11/20 Javascript
jquery.fastLiveFilter.js实现输入自动过滤的方法
2015/08/11 Javascript
jQuery Validate初步体验(一)
2015/12/12 Javascript
10个JavaScript中易犯小错误
2016/02/14 Javascript
jQuery实现返回顶部功能
2016/02/23 Javascript
让DIV的滚动条自动滚动到最底部的3种方法(推荐)
2016/09/24 Javascript
JS+HTML5实现的前端购物车功能插件实例【附demo源码下载】
2016/10/17 Javascript
js简单正则验证汉字英文及下划线的方法
2016/11/28 Javascript
Javascript之面向对象--接口
2016/12/02 Javascript
JavaScript实现动态添加Form表单元素的方法示例
2017/08/14 Javascript
浅谈vue-cli加载不到dev-server.js的解决办法
2017/11/24 Javascript
详解layui中的树形关于取值传值问题
2018/01/16 Javascript
VUE重点问题总结
2018/03/19 Javascript
vue-cli的build的文件夹下没有dev-server.js文件配置mock数据的方法
2019/04/17 Javascript
基于vue与element实现创建试卷相关功能(实例代码)
2020/12/07 Vue.js
使用Django Form解决表单数据无法动态刷新的两种方法
2017/07/14 Python
python使用PIL和matplotlib获取图片像素点并合并解析
2019/09/10 Python
python excel和yaml文件的读取封装
2021/01/12 Python
详解Sticky Footer 绝对底部的两种套路
2017/11/03 HTML / CSS
小程序canvas中文字设置居中锚点
2019/04/16 HTML / CSS
会计师事务所审计实习自我鉴定
2013/09/20 职场文书
车工岗位职责
2013/11/26 职场文书
司仪主持词两篇
2014/03/22 职场文书
劳资协议书范本
2014/04/23 职场文书
纺织工程专业推荐信
2014/09/08 职场文书
领导干部作风建设自查报告
2014/10/23 职场文书
大学推普周活动总结
2015/05/07 职场文书
女人创业励志语录,句句蕴含能量,激发你的潜能
2019/08/20 职场文书
创业计划书之废品回收
2019/09/26 职场文书
Python数组变形的几种实现方法
2022/05/30 Python