【贪吃蛇】代码实现

2023-12-27 13:20:41

贪食蛇

最近 dan 有在油管上直播,播放量最多的就是手写一个贪食蛇。

本来想学一下大佬写代码的姿势,看了几分钟就没了耐性,心想我为什么不能自己写一个呢。

一步一步跟着敲代码,我实践了一段时间但是收效甚微,因为中间少了自己的思考。

初期可能有些作用,可以学到一些技巧和规范。

但是自己实现一个东西带来的成就感,你不断的 debug 和查文档查资料留下的记忆和习惯,这大概就是这个玩意带给我最大的收获吧。

在线演示icon-default.png?t=N7T8https://cyhfe.github.io/snake/

githubicon-default.png?t=N7T8https://github.com/cyhfe/snake

我的 github 一直是单机模式,如果这篇文章对您有所帮助的话欢迎点个 star

数据结构及变量

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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 绘制页面

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

//? 背景

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

??)

}

蛇的移动

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

// 蛇的移动

// 确定下一次移动的位置,将这个点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

}

事件监听

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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 实现动画

1

2

3

4

5

6

7

8

9

10

11

12

13

// 默认的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 变量,

当你改变方向之后,必须等待蛇移动了才能再次改变方向

1

2

3

4

5

6

// 事件回调

case "ArrowUp":

??if (direction === "down" |!canChangeDirection) return

??direction = "up"

??canChangeDirection = false

??break

到此这篇关于canvas实现贪食蛇的实践的文章就介绍到这了,更多相关 canvas贪食蛇内容请搜索脚本之家以前的文章或继续浏览下面的相关文章,希望大家以后多多支持

文章来源:https://blog.csdn.net/weixin_55639808/article/details/135236635
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。