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 相关文章推荐
Jquery下的26个实用小技巧(jQuery tips, tricks &amp; solutions)
Mar 01 Javascript
jquery中ajax学习笔记3
Oct 16 Javascript
放弃用你的InnerHTML来输出HTML吧 jQuery Tmpl不详细讲解
Apr 20 Javascript
将字符串中由空格隔开的每个单词首字母大写
Apr 06 Javascript
利用JS生成博文目录及CSS定制博客
Feb 10 Javascript
JS使用eval()动态创建变量的方法
Jun 03 Javascript
onmouseover事件和onmouseout事件全面理解
Aug 15 Javascript
Vue非父子组件通信详解
Jun 12 Javascript
图片加载完成再执行事件的实例
Nov 16 Javascript
vue项目中用cdn优化的方法
Jan 03 Javascript
详解Webstorm 下的Angular2.0开发之路(图文)
Dec 06 Javascript
npm 常用命令详解(小结)
Jan 17 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 面向对象程序设计(oop)学习笔记(三) - 单例模式和工厂模式
2014/06/12 PHP
php使用cookie保存登录用户名的方法
2015/01/26 PHP
php 静态属性和静态方法区别详解
2017/04/09 PHP
php+ajax实现异步上传文件或图片功能
2017/07/18 PHP
PHP连接SQL Server的方法分析【基于thinkPHP5.1框架】
2019/05/06 PHP
ThinkPHP框架结合Ajax实现用户名校验功能示例
2019/07/03 PHP
PHP正则之正向预查与反向预查讲解与实例
2020/04/06 PHP
用正则xmlHttp实现的偷(转)
2007/01/22 Javascript
超清晰的document对象详解
2007/02/27 Javascript
JS随即打乱数组实现代码
2012/12/03 Javascript
在Ubuntu上安装最新版本的Node.js
2014/07/14 Javascript
基于jQuery实现表单提交验证
2014/11/24 Javascript
JavaScript检测浏览器cookie是否已经启动的方法
2015/02/27 Javascript
javascript实现鼠标拖动改变层大小的方法
2015/04/30 Javascript
JS使用eval解析JSON的注意事项分析
2015/11/14 Javascript
使用D3.js创建物流地图的示例代码
2018/01/27 Javascript
JS高级技巧(简洁版)
2018/07/29 Javascript
实用Javascript调试技巧分享(小结)
2019/06/18 Javascript
vue+vuex+axios从后台获取数据存入vuex,组件之间共享数据操作
2020/07/31 Javascript
工作中常用js功能汇总
2020/11/07 Javascript
玩转python爬虫之URLError异常处理
2016/02/17 Python
详解Python中 __get__和__getattr__和__getattribute__的区别
2016/06/16 Python
python获取指定字符串中重复模式最高的字符串方法
2018/06/29 Python
对python numpy.array插入一行或一列的方法详解
2019/01/29 Python
python PyQt5/Pyside2 按钮右击菜单实例代码
2019/08/17 Python
Python CSV文件模块的使用案例分析
2019/12/21 Python
华为慧通面试题
2012/09/11 面试题
自我评价范文分享
2014/01/04 职场文书
同学会邀请书大全
2014/01/12 职场文书
水利学院求职自荐书
2014/02/01 职场文书
勤俭节约演讲稿
2014/05/08 职场文书
花坛标语大全
2014/06/30 职场文书
科级干部群众路线教育实践活动个人对照检查材料
2014/09/19 职场文书
慰问信格式规范
2015/03/23 职场文书
python析构函数用法及注意事项
2021/06/22 Python
python字符串的一些常见实用操作
2022/04/06 Python