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 相关文章推荐
2010年最佳jQuery插件整理
Dec 06 Javascript
SyntaxHighlighter语法高亮插件使用说明
Aug 14 Javascript
JS和Jquery获取和修改label的值的示例代码
Jan 15 Javascript
JavaScript的21条基本知识点
Mar 04 Javascript
javascript模拟php函数in_array
Apr 27 Javascript
JS组件中bootstrap multiselect两大组件较量
Jan 26 Javascript
浅析Node.js实现HTTP文件下载
Aug 05 Javascript
Angular.js实现多个checkbox只能选择一个的方法示例
Feb 24 Javascript
微信小程序 自动登陆PHP源码实例(源码下载)
May 08 Javascript
BootStrap 表单控件之单选按钮水平排列
May 23 Javascript
AngularJS实现表单验证功能详解
Oct 12 Javascript
微信小程序利用云函数获取手机号码
Dec 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
如何实现给定日期的若干天以后的日期
2006/10/09 PHP
php 保留小数点
2009/04/21 PHP
ecshop 订单确认中显示省市地址信息的方法
2010/03/15 PHP
php与flash as3 socket通信传送文件实现代码
2014/08/16 PHP
destoon实现会员商铺中指定会员或会员组投放广告的方法
2014/08/21 PHP
Yii2实现跨mysql数据库关联查询排序功能代码
2017/02/10 PHP
浅谈PHP中pack、unpack的详细用法
2018/03/12 PHP
JS target与currentTarget区别说明
2011/08/28 Javascript
Javascript Throttle &amp; Debounce应用介绍
2013/03/19 Javascript
jquery 动态创建元素的方式介绍及应用
2013/04/21 Javascript
js控制容器隐藏出现防止样式变化的两种方法
2014/04/25 Javascript
浅谈javascript中for in 和 for each in的区别
2015/04/23 Javascript
简单谈谈javascript Date类型
2015/09/06 Javascript
Vue + Webpack + Vue-loader学习教程之功能介绍篇
2017/03/14 Javascript
详解webpack+angular2开发环境搭建
2017/06/28 Javascript
vue2中,根据list的id进入对应的详情页并修改title方法
2018/08/24 Javascript
React路由鉴权的实现方法
2019/09/05 Javascript
微信小程序语音同步智能识别的实现案例代码解析
2020/05/29 Javascript
[01:15:16]DOTA2-DPC中国联赛 正赛 Elephant vs Aster BO3 第一场 1月26日
2021/03/11 DOTA
python网络编程学习笔记(四):域名系统
2014/06/09 Python
python实现贪吃蛇小游戏
2020/03/21 Python
python实现随机漫步方法和原理
2019/06/10 Python
centos+nginx+uwsgi+Django实现IP+port访问服务器
2019/11/15 Python
python爬虫库scrapy简单使用实例详解
2020/02/10 Python
python百行代码自制电脑端网速悬浮窗的实现
2020/05/12 Python
python时间time模块处理大全
2020/10/25 Python
python实现文件分片上传的接口自动化
2020/11/19 Python
Sephora丝芙兰泰国官方网站:国际知名化妆品购物
2017/11/15 全球购物
校领导推荐信
2013/11/01 职场文书
采购类个人求职的自我评价
2014/02/18 职场文书
国旗下演讲稿
2014/05/08 职场文书
关于运动会的口号
2014/06/07 职场文书
领导班子在批评与自我批评座谈会上的发言
2014/09/28 职场文书
2014年质检工作总结
2014/11/26 职场文书
会议开幕致辞怎么写
2016/03/03 职场文书
探究Mysql模糊查询是否区分大小写
2021/06/11 MySQL