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——表单应用范例
Feb 20 Javascript
jquery下利用jsonp跨域访问实现方法
Jul 29 Javascript
JS在IE下缺少标识符的错误
Jul 23 Javascript
jQuery Ajax使用实例
Apr 16 Javascript
js变形金刚文字特效代码分享
Aug 20 Javascript
AngularJS中的Directive自定义一个表格
Jan 25 Javascript
简单实现jQuery进度条轮播实例代码
Jun 20 Javascript
使用jquery/js获取iframe父子级、同级获取元素的方法
Aug 05 Javascript
AngularJS实现自定义指令与控制器数据交互的方法示例
Jun 19 Javascript
canvas绘制爱心的几种方法总结(推荐)
Oct 31 Javascript
Jquery如何使用animation动画效果改变背景色的代码
Jul 20 jQuery
antd的select下拉框因为数据量太大造成卡顿的解决方式
Oct 31 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
BBS(php &amp; mysql)完整版(三)
2006/10/09 PHP
PHP语言中global和$GLOBALS[]的分析 之二
2012/02/02 PHP
php共享内存段示例分享
2014/01/20 PHP
PHP日期函数date格式化UNIX时间的方法
2015/03/19 PHP
PHP模拟asp中response类实现方法
2015/08/08 PHP
在Mac OS上自行编译安装Apache服务器和PHP解释器
2015/12/24 PHP
CI框架中数据库操作函数$this-&gt;db-&gt;where()相关用法总结
2016/05/17 PHP
解决Yii2邮件发送结果返回成功,但接收不到邮件的问题
2017/05/23 PHP
PHP中使用jQuery+Ajax实现分页查询多功能操作(示例讲解)
2017/09/17 PHP
laravel 查询数据库获取结果实现判断是否为空
2019/10/24 PHP
js如何设置在iframe框架中指定div不显示
2013/12/04 Javascript
浅谈javascript中createElement事件
2014/12/05 Javascript
简介JavaScript中Boolean.toSource()方法的使用
2015/06/05 Javascript
js 求时间差的实现代码
2016/04/26 Javascript
微信小程序 loading(加载中提示框)实例
2016/10/28 Javascript
Javascript Event(事件)的传播与冒泡
2017/01/23 Javascript
angularjs自定义过滤器demo示例
2019/08/24 Javascript
解决antd 下拉框 input [defaultValue] 的值的问题
2020/10/31 Javascript
Javascript实现单选框效果
2020/12/09 Javascript
初步理解Python进程的信号通讯
2015/04/09 Python
pyenv命令管理多个Python版本
2017/03/26 Python
Python的装饰器使用详解
2017/06/26 Python
Django中Forms的使用代码解析
2018/02/10 Python
flask框架使用orm连接数据库的方法示例
2018/07/16 Python
Python分支语句与循环语句应用实例分析
2019/05/07 Python
Python 、Pycharm、Anaconda三者的区别与联系、安装过程及注意事项
2019/10/11 Python
canvas 下载二维码和图片加水印的方法
2018/03/21 HTML / CSS
Ralph Lauren英国官方网站:Ralph Lauren UK
2018/04/03 全球购物
英国PC组件和在线电脑商店:SCAN
2019/04/18 全球购物
世界经理人咨询有限公司面试
2014/09/23 面试题
经管应届生求职信范文
2014/05/18 职场文书
药剂专业求职信
2014/06/20 职场文书
小学运动会演讲稿
2014/08/25 职场文书
《水浒传》读后感3篇(范文)
2019/09/19 职场文书
MySql开发之自动同步表结构
2021/05/28 MySQL
Redis实现一个账号只能登录一个设备
2022/04/19 Redis