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 相关文章推荐
不用锚点也可以平滑滚动到页面的指定位置实现代码
May 08 Javascript
js右下角弹出提示框示例代码
Jan 12 Javascript
Bootstrap安装环境配置教程分享
May 27 Javascript
JQuery validate 验证一个单独的表单元素实例
Feb 17 Javascript
Vue.js如何优雅的进行form validation
Apr 07 Javascript
Vue resource中的GET与POST请求的实例代码
Jul 21 Javascript
js使用highlight.js高亮你的代码
Aug 18 Javascript
vue与TypeScript集成配置最简教程(推荐)
Oct 17 Javascript
jQuery实现checkbox的简单操作
Nov 18 jQuery
10个经典的网页鼠标特效代码
Jan 09 Javascript
vue采用EventBus实现跨组件通信及注意事项小结
Jun 14 Javascript
js实现登录时记住密码的方法分析
Apr 05 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批量检测并去除文件BOM头代码实例
2014/05/08 PHP
PHP的反射类ReflectionClass、ReflectionMethod使用实例
2014/08/05 PHP
PHP基于redis计数器类定义与用法示例
2018/02/08 PHP
PHP实现一个按钮点击上传多个图片操作示例
2020/01/23 PHP
JavaScript 在线压缩和格式化收藏
2009/01/16 Javascript
深入理解JavaScript系列(7) S.O.L.I.D五大原则之开闭原则OCP
2012/01/15 Javascript
JS实现一个列表中包含上移下移删除等功能
2014/09/24 Javascript
javascript常见数字进制转换实例分析
2016/04/21 Javascript
Boostrap入门准备之border box
2016/05/09 Javascript
微信小程序 vidao实现视频播放和弹幕的功能
2016/11/02 Javascript
Angular4学习笔记之准备和环境搭建项目
2017/08/01 Javascript
Vue实例中生命周期created和mounted的区别详解
2017/08/25 Javascript
在一个页面实现两个zTree联动的方法
2017/12/20 Javascript
分享5个好用的javascript文件上传插件
2018/09/16 Javascript
JS扁平化输出数组的2种方法解析
2019/09/17 Javascript
JS数据类型(基本数据类型、引用数据类型)及堆和栈的区别分析
2020/03/04 Javascript
python实现list元素按关键字相加减的方法示例
2017/06/09 Python
python实现list由于numpy array的转换
2018/04/04 Python
python实现剪切功能
2019/01/23 Python
numpy.transpose()实现数组的转置例子
2019/12/02 Python
基于python-pptx库中文文档及使用详解
2020/02/14 Python
美国生日蛋糕店:Bake Me A Wish!
2017/02/08 全球购物
Kangol帽子官网:坎戈尔袋鼠
2018/09/26 全球购物
Lacoste澳大利亚官网:服装、鞋类及配饰
2018/11/14 全球购物
阿迪达斯希腊官方网上商店:adidas希腊
2019/04/06 全球购物
财务会计专业毕业生自荐信
2013/10/19 职场文书
机电一体化大学生求职信
2013/11/08 职场文书
销售主管的自我评价分享
2014/01/03 职场文书
婚礼主持词
2014/03/13 职场文书
创业培训计划书
2014/05/03 职场文书
先进学校事迹材料
2014/12/30 职场文书
检讨书范文500字
2015/01/28 职场文书
CSS3 制作的书本翻页特效
2021/04/13 HTML / CSS
python中print格式化输出的问题
2021/04/16 Python
Django+Celery实现定时任务的示例
2021/06/23 Python
Redis数据同步之redis shake的实现方法
2022/04/21 Redis