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 Mobile不显示讨厌loading界面的方法
Feb 19 Javascript
jQuery实现鼠标划过添加和删除class的方法
Jun 26 Javascript
详解react-webpack2-热模块替换[HMR]
Aug 03 Javascript
vue页面使用阿里oss上传功能的实例(二)
Aug 09 Javascript
js实现简易聊天对话框
Aug 17 Javascript
React Native验证码倒计时工具类分享
Oct 24 Javascript
JS实现留言板功能[楼层效果展示]
Dec 27 Javascript
使用javascript函数编写简单银行取钱存钱流程
May 26 Javascript
一份超级详细的Vue-cli3.0使用教程【推荐】
Nov 15 Javascript
jQuery事件绑定和解绑、事件冒泡与阻止事件冒泡及弹出应用示例
May 13 jQuery
解决vue+webpack项目接口跨域出现的问题
Aug 10 Javascript
vue选项卡切换的实现案例
Apr 11 Vue.js
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
图书管理程序(一)
2006/10/09 PHP
php笔记之:php数组相关函数的使用
2013/04/26 PHP
PHP使用curl模拟post上传及接收文件的方法
2016/03/04 PHP
Smarty分页实现方法完整实例
2016/05/11 PHP
Laravel中Facade的加载过程与原理详解
2017/09/22 PHP
主页面中的两个iframe实现鼠标拖动改变其大小
2013/04/16 Javascript
jQuery插件jQuery-JSONP开发ajax调用使用注意事项
2013/11/22 Javascript
JavaScript实现的石头剪刀布游戏源码分享
2014/08/22 Javascript
jQuery实现垂直半透明手风琴特效代码分享
2015/08/21 Javascript
JS中对Cookie的操作详解
2016/08/05 Javascript
概述如何实现一个简单的浏览器端js模块加载器
2016/12/07 Javascript
详解如何使用vue-cli脚手架搭建Vue.js项目
2017/05/19 Javascript
在 Node.js 中使用 async 函数的方法
2017/11/17 Javascript
JavaScript数据结构之双向链表和双向循环链表的实现
2017/11/28 Javascript
ionic grid(栅格)九宫格制作详解
2018/06/30 Javascript
小程序红包雨的实现示例
2019/02/19 Javascript
JavaScript 中判断变量是否为数字的示例代码
2020/10/22 Javascript
[36:05]完美世界DOTA2联赛循环赛 Forest vs DM 第一场 11.06
2020/11/06 DOTA
Python 错误和异常小结
2013/10/09 Python
Python中的赋值、浅拷贝、深拷贝介绍
2015/03/09 Python
利用标准库fractions模块让Python支持分数类型的方法详解
2017/08/11 Python
解决pip install的时候报错timed out的问题
2018/06/12 Python
python接口自动化(十六)--参数关联接口后传(详解)
2019/04/16 Python
python调用自定义函数的实例操作
2019/06/26 Python
Python缓存技术实现过程详解
2019/09/25 Python
Python Numpy库常见用法入门教程
2020/01/16 Python
PYQT5 vscode联合操作qtdesigner的方法
2020/03/24 Python
巴西备受欢迎的服装和生活方式品牌:FARM Rio
2020/02/04 全球购物
Aosom西班牙:家具在线商店
2020/06/11 全球购物
廉政教育心得体会
2014/01/01 职场文书
单位工程竣工验收方案
2014/03/16 职场文书
机关党员进社区活动总结
2014/07/05 职场文书
2014年行政后勤工作总结
2014/12/06 职场文书
贷款工资证明范本
2015/06/12 职场文书
朝花夕拾读书笔记
2015/06/29 职场文书
小学数学教学随笔
2015/08/14 职场文书