11 贪吃蛇小游戏 js版本 + vue版本
Posted 蓝风9
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了11 贪吃蛇小游戏 js版本 + vue版本相关的知识,希望对你有一定的参考价值。
前言
// 呵呵, 1024 发一波 基础知识 的库存
缘于一些 小的需求
因为一些原因 可能到这里就不会再往后面设计了
主要分为了两个版本, html + js 的版本 和 vue 的版本
核心的意义是 面向对象的程序设计
不过是 基于了这种常见的小游戏 展现出来了
关于设计
关于 面向对象的程序设计 内容很多很杂
这里 也不想 一一文字描述, 需要自己去领悟了
具体的 代码实现的设计, 这里 也不多说了, 文字描述有限
整个流程大概如下, 其中第一点至关重要
1. 设计各个对象 Game, Snake, SnakeNode, Arena, WallNode, FoodNode 的各个属性, 以及方法, 方法可以留空, 定义好方法名字就行
2. 实现 Arena 以及 墙体节点, 并绘制 Arena 的整个墙体
3. 实现 食物节点 并绘制食物, 需要按照频率闪烁
4. 实现按键操作 贪吃蛇 的方向, 按空格键暂停游戏
5. 实现 贪吃蛇 触碰到食物之后, 吃掉食物, 贪吃蛇 的长度+1
6. 实现 FoodNode 的随机初始化, 以及吃掉食物之后随机初始化 [需要避开墙体 和 贪吃蛇 占用的节点]
7. 实现贪吃蛇 撞到墙 或者 自己 暂停游戏, 整条蛇闪烁, 标志着游戏结束
8. 拆分整个 H5DrawImage.html 按照对象拆分到各自的 js
9. 增加元素 Grass, Water, Steel
Grass 的特征为可以穿过, 无任何影响
Water 的特征为到 Water 之后不在向前走, 只能左右调整方向
Steel 的特征和墙一样, 碰到之后游戏结束
10. 增加分数的显示, 更新
11. 写一个简单的自动导航贪吃蛇的 "外挂"
12. 将一些配置提取出来, 比如 墙体的长宽, 所有元素节点的宽高, 贪吃蛇的移动频率, 外部导入 "地图" 等等
另外还有一些想法, 因为种种原因 算了, 不想继续再做了
增加道具, 比如 坦克大战的船, 获得船之后, 再规定的时间内可以穿过水
增加 坦克大战的星星, 获得两个之后, 可以撞开 Steel
html + js 版本
主要包含了一个 html 来驱动, 各个对象 js 来处理核心业务
H5DrawImage.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>H5DrawImage.html</title>
</head>
<body>
<canvas id="canvas" width="1500" height="1000"></canvas>
<div id="scoreDiv" style="position: absolute; left: 1000px; top:50px" >分数 : 12</div>
</body>
<script src="./js/jquery.min.js" type="text/javascript"></script>
<script src="./js/constants.js" type="text/javascript"></script>
<script src="./js/utils.js" type="text/javascript"></script>
<script src="./js/food.js" type="text/javascript"></script>
<script src="./js/arena.js" type="text/javascript"></script>
<script src="./js/snake.js" type="text/javascript"></script>
<script src="./js/game.js" type="text/javascript"></script>
<script>
// $('html').css('font-size', $(window).width() / 10)
// --------------------------- game initiator ---------------------------
let game = new Game()
game.init(canvasCtx)
game.draw(canvasCtx)
game.run(canvasCtx)
// key event register
document.onkeydown = function (event)
game.onKeyPressed(event, canvasCtx)
// // auto direction
game.auto(canvasCtx, moveIntervalInMs/2);
// update score panel
setInterval(function()
let score = game.score()
document.getElementById("scoreDiv").innerText = `分数 : $score`
, moveIntervalInMs)
</script>
</html>
constants.js
// --------------------------- global vars ---------------------------
let canvas = $('#canvas')[0]
// 画布 api
let canvasCtx = canvas.getContext('2d')
// 各个单元格的边长, 墙体节点/贪吃蛇节点/食物节点
let defaultCellLength = 30
// 默认的墙体宽由多少个 墙体节点 组成
let defaultWallWidth = 40
// 默认的墙体高由多少个 墙体节点 组成
let defaultWallHeight = 20
// 贪吃蛇 的自动移动频率, 毫秒计算
let moveIntervalInMs = 100
// 游戏结束的 闪烁频率
let gameOverIntervalInMs = 100
// 食物 闪烁频率
let foodIntervalInMs = 100
// 贪吃蛇节点类型, 头部/身体/尾部
let Type =
head: 'head',
body: 'body',
tail: 'tail',
// 贪吃蛇节点方向, 上下左右
let Direction =
up: 'up',
left: 'left',
down: 'down',
right: 'right',
// 各个按键代码, W/A/S/D 上/下/左/右 空格
let KeyEvent =
W: 87,
A: 65,
S: 83,
D: 68,
UP: 38,
LEFT: 37,
DOWN: 40,
RIGHT: 39,
SPACE: 32,
// 方向 -> 按键
let Direction2KeyEvent =
'up': KeyEvent.UP,
'left': KeyEvent.LEFT,
'down': KeyEvent.DOWN,
'right': KeyEvent.RIGHT,
utils.js
// --------------------------- assist methods ---------------------------
/**
* nodeMove
*/
function nodeMove(pos, direction)
if (direction === Direction.up)
return x: pos.x, y: pos.y - 1
else if (direction === Direction.left)
return x: pos.x - 1, y: pos.y
else if (direction === Direction.down)
return x: pos.x, y: pos.y + 1
else if (direction === Direction.right)
return x: pos.x + 1, y: pos.y
function nodeEquals(pos1, pos2)
if (pos1.x === pos2.x && pos1.y === pos2.y)
return true
return false
function nodeStepDelta(pos1, pos2)
return Math.abs(pos1.x - pos2.x) + Math.abs(pos1.y - pos2.y)
function nodeStepDeltaByDirection(pos1, pos2, direction)
if (direction === Direction.left || direction === Direction.right)
return Math.abs(pos1.x - pos2.x)
if (direction === Direction.up || direction === Direction.down)
return Math.abs(pos1.y - pos2.y)
/**
* reverseOfDirection
*/
function reverseOfDirection(direction)
if (direction === Direction.up)
return Direction.down
else if (direction === Direction.left)
return Direction.right
else if (direction === Direction.down)
return Direction.up
else if (direction === Direction.right)
return Direction.left
function leftOfDirection(direction)
if (direction === Direction.up)
return Direction.left
else if (direction === Direction.left)
return Direction.down
else if (direction === Direction.down)
return Direction.right
else if (direction === Direction.right)
return Direction.up
/**
* draw image 2 canvas
*/
function drawImage(canvasCtx, imgUrl, x, y, width, height)
let img = new Image();
img.src = imgUrl;
img.onload = function ()
canvasCtx.drawImage(img, x, y, width, height);
// canvasCtx.drawImage(img, 0, 0, 20, 20, x, y, width, height);
/**
* simulate press key
*/
function pressKey(key)
let e = new KeyboardEvent('keydown', 'keyCode': key, 'which': key);
document.dispatchEvent(e);
food.js
// --------------------------- food ---------------------------
function FoodNode(x, y)
this.x = x
this.y = y
this.draw = function (canvasCtx)
drawImage(canvasCtx, "./food.png", x * defaultCellLength, y * defaultCellLength, defaultCellLength, defaultCellLength)
this.clear = function (canvasCtx)
canvasCtx.clearRect(x * defaultCellLength, y * defaultCellLength, defaultCellLength, defaultCellLength)
this.toString = `[$this.x, $this.y]`
arena.js
// --------------------------- arena ---------------------------
function Arena()
this.wallNodes = []
this.steelNodes = []
this.grassNodes = []
this.waterNodes = []
this.init = function ()
for (let i = 0; i <= defaultWallWidth; i++)
this.wallNodes.push(new WallNode(i, 0))
this.wallNodes.push(new WallNode(i, defaultWallHeight))
for (let i = 0; i <= defaultWallHeight; i++)
this.wallNodes.push(new WallNode(0, i))
this.wallNodes.push(new WallNode(defaultWallWidth, i))
for (let i = 0; i < 10; i++)
let nextPos = this.nextRandomNode()
this.steelNodes.push(new SteelNode(nextPos.x, nextPos.y))
nextPos = this.nextRandomNode()
this.grassNodes.push(new GrassNode(nextPos.x, nextPos.y))
nextPos = this.nextRandomNode()
this.waterNodes.push(new WaterNode(nextPos.x, nextPos.y))
this.touchedTimes = function (pos)
let counter = 0
for (let idx in this.wallNodes)
let node = this.wallNodes[idx]
if (nodeEquals(pos, node))
counter++
for (let idx in this.steelNodes)
let node = this.steelNodes[idx]
if (nodeEquals(pos, node))
counter++
return counter
this.blockedNodeTouchedTimes = function (pos)
let counter = 0
for (let idx in this.waterNodes)
let node = this.waterNodes[idx]
if (nodeEquals(pos, node))
counter++
return counter
this.allNodeTouchedTimes = function (pos)
let counter = 0
for (let idx in this.wallNodes)
let node = this.wallNodes[idx]
if (nodeEquals(pos, node))
counter++
for (let idx in this.steelNodes)
let node = this.steelNodes[idx]
if (nodeEquals(pos, node))
counter++
for (let idx in this.grassNodes)
let node = this.grassNodes[idx]
if (nodeEquals(pos, node))
counter++
for (let idx in this.waterNodes)
let node = this.waterNodes[idx]
if (nodeEquals(pos, node))
counter++
return counter
this.draw = function (canvasCtx)
for (let idx in this.wallNodes)
let node = this.wallNodes[idx]
node.draw(canvasCtx)
for (let idx in this.steelNodes)
let node = this.steelNodes[idx]
node.draw(canvasCtx)
for (let idx in this.grassNodes)
let node = this.grassNodes[idx]
node.draw(canvasCtx)
for (let idx in this.waterNodes)
let node = this.waterNodes[idx]
node.draw(canvasCtx)
this.clear = function (canvasCtx)
for (let idx in this.wallNodes)
let node = this.wallNodes[idx]
node.clear(canvasCtx)
for (let idx in this.steelNodes)
let node = this.steelNodes[idx]
node.clear(canvasCtx)
for (let idx in this.grassNodes)
let node = this.grassNodes[idx]
node.clear(canvasCtx)
for (let idx in this.waterNodes)
let node = this.waterNodes[idx]
node.clear(canvasCtx)
this.nextRandomNode = function ()
let nextPos = null
while (true)
let x = Math.floor(Math.random() * defaultWallWidth + 1)
let y = Math.floor(Math.random() * defaultWallHeight + 1)
nextPos = x: x, y: y
if (this.allNodeTouchedTimes(nextPos) === 0)
break
return nextPos
function WallNode(x, y)
this.x = x
this.y = y
this.draw = function (canvasCtx)
drawImage(canvasCtx, "./wall.png", x * defaultCellLength, y * defaultCellLength, defaultCellLength, defaultCellLength)
this.clear = function (canvasCtx)
canvasCtx.clearRect(x * defaultCellLength, y * defaultCellLength, defaultCellLength, defaultCellLength)
this.toString = `[$this.x, $this.y]`
function SteelNode(x, y)
this.x = x
this.y = y
this.draw = function (canvasCtx)
drawImage(canvasCtx, "./steel.png", x * defaultCellLength, y * defaultCellLength, defaultCellLength, defaultCellLength)
this.clear = function (canvasCtx)
canvasCtx.clearRect(x * defaultCellLength, y * defaultCellLength, defaultCellLength, defaultCellLength)
this.toString = `[$this.x, $this.y]`
function WaterNode(x, y)
this.x = x
this.y = y
this.draw = function (canvasCtx)
drawImage(canvasCtx, "./water.png", x * defaultCellLength, y * defaultCellLength, defaultCellLength, defaultCellLength)
this.clear = function (canvasCtx)
canvasCtx.clearRect(x * defaultCellLength, y * defaultCellLength, defaultCellLength, defaultCellLength)
this.toString = `[$this.x, $this.y]`
function GrassNode(x, y)
this.x = x
this.y = y
this.draw = function (canvasCtx)
drawImage(canvasCtx, "./grass.png", x * defaultCellLength, y * defaultCellLength, defaultCellLength, defaultCellLength)
this.clear = function (canvasCtx)
canvasCtx.clearRect(x * defaultCellLength, y * defaultCellLength, defaultCellLength, defaultCellLength)
this.toString = `[$this.x, $this.y]`
snake.js
// --------------------------- snake ---------------------------
function Snake(initLen, initX, initY, initDirection)
this.initLen = initLen
this.initX = initX
this.initY = initY
this.initDirection = initDirection
this.direction = initDirection
this.nodes = []
this.toClearNodes = []
this.init = function ()
let revDirection = reverseOfDirection(this.direction)
let nodes = []
let pos = x: this.initX, y: this.initY
for (let i = 0; i < this.initLen; i++)
nodes.push(new SnakeNode(pos.x, pos.y, Type.body, this.direction))
pos = nodeMove(pos, revDirection)
nodes[0].setType(Type.head)
nodes[nodes.length - 1].setType(Type.tail)
this.nodes = nodes
this.setDirection = function (newDirection)
// if direction is current direction or reverse of current direction, ignore operation
if ((newDirection === this.direction) || (newDirection === reverseOfDirection(this.direction)))
return
this.direction = newDirection
this.move = function (canvasCtx)
let nodes = []
let pos = x: this.nodes[0].x, y: this.nodes[0].y
pos = nodeMove(pos, this.direction)
nodes.push(new SnakeNode(pos.x, pos.y, Type.body, this.direction))
for (let i = 0; i < this.nodes.length - 1; i++)
let oldNode = this.nodes[i]
nodes.push(new SnakeNode(oldNode.x, oldNode.y, Type.body, oldNode.direction))
nodes[0].setType(Type.head)
nodes[nodes.length - 1].setType(Type.tail)
// if last node direction is single, update direction to nodes[nodes.length - 2].direction
if (nodes[nodes.length - 1].direction !== nodes[nodes.length - 2].direction)
nodes[nodes.length - 1].direction = nodes[nodes.length - 2].direction
let lastNode = this.nodes[this.nodes.length - 1]
this.toClearNodes.push(lastNode)
this.nodes = nodes
this.getHead = function ()
return this.nodes[0]
this.getNextHead = function ()
let snakeHead = this.getHead()
return nodeMove(snakeHead, this.direction)
this.addTailNode = function ()
let lastNode = this.nodes[this.nodes.length - 1]
let revDirection = reverseOfDirection(lastNode.direction)
let newTailNodePos = nodeMove(lastNode, revDirection)
let newTailNode = new SnakeNode(newTailNodePos.x, newTailNodePos.y, Type.tail, lastNode.direction)
this.nodes.push(newTailNode)
this.touchedTimes = function (pos)
let counter = 0
for (let idx in this.nodes)
let node = this.nodes[idx]
if (nodeEquals(pos, node))
counter++
return counter
this.copy = function ()
let copied = new Snake(initLen, initX, initY, this.direction)
copied.nodes = []
this.nodes.forEach(ele => copied.nodes.push(ele))
return copied
this.draw = function (canvasCtx)
for (let idx in this.toClearNodes)
let node = this.toClearNodes[idx]
node.clear(canvasCtx)
this.toClearNodes = []
for (let idx in this.nodes)
let node = this.nodes[idx]
node.draw(canvasCtx)
this.clear = function (canvasCtx)
for (let idx in this.toClearNodes)
let node = this.toClearNodes[idx]
node.clear(canvasCtx)
this.toClearNodes = []
for (let idx in this.nodes)
let node = this.nodes[idx]
node.clear(canvasCtx)
function SnakeNode(x, y, type, direction)
this.x = x
this.y = y
this.type = type
this.direction = direction
this.setType = function (newType)
this.type = newType
this.setDirection = function (newDirection)
this.direction = newDirection
this.draw = function (canvasCtx)
drawImage(canvasCtx, `./$this.type_$this.direction.png`, x * defaultCellLength, y * defaultCellLength, defaultCellLength, defaultCellLength)
this.clear = function (canvasCtx)
canvasCtx.clearRect(x * defaultCellLength, y * defaultCellLength, defaultCellLength, defaultCellLength)
this.toString = `[$this.x, $this.y]`
game.js
// --------------------------- game ---------------------------
function Game()
this.arena = new Arena()
this.snake = new Snake(5, 30, 5, Direction.left)
this.moveInterval = null
this.gameOver = false
this.gameOverInterval = null
this.foodNode = null
this.foodNodeInterval = null
this.foodNodeCounter = 0
this.init = function (canvasCtx)
this.arena.init()
this.snake.init()
this.foodNode = this.nextFoodNode()
this.run = function (canvasCtx)
if (this.gameOver)
alert('game over')
return
if (this.moveInterval)
return
let _this = this
this.moveInterval = setInterval(function ()
_this.move0(canvasCtx)
, moveIntervalInMs)
this.foodNodeInterval = setInterval(function ()
_this.foodMove0(canvasCtx)
, foodIntervalInMs)
this.move0 = function (canvasCtx)
if (this.gameOver)
return
// this.draw(canvasCtx)
let snakeNextHead = this.snake.getNextHead()
if (this.arena.blockedNodeTouchedTimes(snakeNextHead) > 0)
return
this.snake.move(canvasCtx)
this.snake.draw(canvasCtx)
if ((this.arena.touchedTimes(snakeNextHead) > 0)
|| (this.snake.touchedTimes(snakeNextHead) > 1))
this.gameOver = true
this.stop(canvasCtx)
else if (nodeEquals(snakeNextHead, this.foodNode))
this.snake.addTailNode()
this.foodNode = this.nextFoodNode()
this.foodMove0 = function (canvasCtx)
this.foodNodeCounter++
if (this.foodNodeCounter % 2 === 0)
this.foodNode.draw(canvasCtx)
else
this.foodNode.clear(canvasCtx)
this.pause = function (canvasCtx)
if (this.gameOver)
alert('game over')
return
if (!this.moveInterval)
this.run(canvasCtx)
return
clearInterval(this.moveInterval)
this.moveInterval = null
clearInterval(this.foodNodeInterval)
this.foodNodeInterval = null
this.stop = function (canvasCtx)
clearInterval(this.moveInterval)
this.moveInterval = null
clearInterval(this.foodNodeInterval)
this.foodNodeInterval = null
let _this = this
let gameOverCounter = 0
this.gameOverInterval = setInterval(function ()
gameOverCounter++
if (gameOverCounter % 2 === 0)
_this.snake.draw(canvasCtx)
_this.foodNode.draw(canvasCtx)
else
_this.snake.clear(canvasCtx)
_this.foodNode.clear(canvasCtx)
, gameOverIntervalInMs)
this.score = function ()
return (this.snake.nodes.length - this.snake.initLen) * 5
this.draw = function (canvasCtx)
this.arena.draw(canvasCtx)
this.snake.draw(canvasCtx)
this.clear = function (canvasCtx)
this.arena.clear(canvasCtx)
this.snake.clear(canvasCtx)
this.onKeyPressed = function (event, canvasCtx)
let e = event || window.event || arguments.callee.caller.arguments[0]
if (!e)
return
if (e && e.keyCode === KeyEvent.LEFT || e.keyCode === KeyEvent.A)
this.snake.setDirection(Direction.left)
else if (e.keyCode === KeyEvent.UP || e.keyCode === KeyEvent.W)
this.snake.setDirection(Direction.up)
else if (e.keyCode === KeyEvent.RIGHT || e.keyCode === KeyEvent.D)
this.snake.setDirection(Direction.right)
else if (e.keyCode === KeyEvent.DOWN || e.keyCode === KeyEvent.S)
this.snake.setDirection(Direction.down)
else if (e.keyCode === KeyEvent.SPACE)
this.pause(canvasCtx)
this.nextFoodNode = function ()
let nextFoodPos = null
while (true)
let x = Math.floor(Math.random() * defaultWallWidth + 1)
let y = Math.floor(Math.random() * defaultWallHeight + 1)
nextFoodPos = x: x, y: y
if ((this.snake.touchedTimes(nextFoodPos) === 0)
&& (this.arena.touchedTimes(nextFoodPos) === 0))
break
return new FoodNode(nextFoodPos.x, nextFoodPos.y)
this.auto = function (canvasCtx, autoIntervalInMs)
let _this = this
setInterval(function ()
let snakeHead = _this.snake.getHead()
let newDirection = _this.deductDirection()
if (newDirection === snakeHead.direction)
return
if (newDirection === reverseOfDirection(snakeHead.direction))
newDirection = leftOfDirection(snakeHead.direction)
let stepDelta = nodeStepDeltaByDirection(snakeHead, _this.foodNode, newDirection)
if (!_this.simulateNStepGameOver(stepDelta, newDirection))
pressKey(Direction2KeyEvent[newDirection])
return
let stepDelta = nodeStepDeltaByDirection(snakeHead, _this.foodNode, newDirection)
if (!_this.simulateNStepGameOver(stepDelta, newDirection))
pressKey(Direction2KeyEvent[newDirection])
return
newDirection = snakeHead.direction
stepDelta = 2
if (_this.simulateNStepGameOver(stepDelta, newDirection))
let dir1 = leftOfDirection(snakeHead.direction)
let dir2 = reverseOfDirection(dir1)
newDirection = (!_this.simulateNStepGameOver(stepDelta, dir1)) ? dir1 : dir2
pressKey(Direction2KeyEvent[newDirection])
, autoIntervalInMs)
this.simulateNStepGameOver = function (stepDelta, newDirection)
let snakeCopy = this.snake.copy()
snakeCopy.setDirection(newDirection)
for (let i = 0; i < stepDelta; i++)
snakeCopy.move(canvasCtx)
let snakeHead = snakeCopy.getHead()
if ((this.arena.touchedTimes(snakeHead) > 0)
|| (snakeCopy.touchedTimes(snakeHead) > 1))
return true
return false
this.deductDirection = function ()
let snakeHead = this.snake.getHead()
if (snakeHead.x < this.foodNode.x)
return Direction.right
if (snakeHead.x > this.foodNode.x)
return Direction.left
if (snakeHead.y < this.foodNode.y)
return Direction.down
if (snakeHead.y > this.foodNode.y)
return Direction.up
展示效果
vue 版本
game.vue
<template>
<div>
<snake ref="snake" :init-x="30" :init-y="5" :init-len="6" init-direction="left"></snake>
<arena ref="arena"></arena>
<food ref="food"></food>
</div>
</template>
<script>
// import snake from '/src/components/snake/Snake';
import snake from "./Snake";
import arena from "./Arena";
import food from "./Food";
import constants from './js/constants'
import utils from './js/utils'
export default
name: 'Game',
components:
snake,
arena,
food,
,
data()
return
refArena: null,
refSnake: null,
refFood: null,
moveInterval: null,
gameOver: false,
gameOverInterval: null,
foodNodeInterval: null,
foodNodeCounter: 0
,
mounted()
this.init()
this.draw()
this.run()
let _this = this
document.onkeydown = function (event)
_this.onKeyPressed(event, )
,
methods:
init: function ()
this.refArena = this.$refs.arena
this.refSnake = this.$refs.snake
this.refFood = this.$refs.food
this.refArena.init()
this.refSnake.init()
this.refFood.refresh(this.nextFoodNode())
,
run: function ()
if (this.gameOver)
alert('game over')
return
if (this.moveInterval)
return
let _this = this
this.moveInterval = setInterval(function ()
_this.move0()
, constants.moveIntervalInMs)
this.foodNodeInterval = setInterval(function ()
_this.foodMove0()
, constants.foodIntervalInMs)
,
move0: function ()
if (this.gameOver)
return
// this.draw()
let snakeNextHead = this.refSnake.getNextHead()
if (this.refArena.blockedNodeTouchedTimes(snakeNextHead) > 0)
return
this.refSnake.move()
this.refSnake.draw()
if ((this.refArena.touchedTimes(snakeNextHead) > 0)
|| (this.refSnake.touchedTimes(snakeNextHead) > 1))
this.gameOver = true
this.stop()
else if (utils.nodeEquals(snakeNextHead, this.refFood.getNode()))
this.refSnake.addTailNode()
this.refFood.refresh(this.nextFoodNode())
,
foodMove0: function ()
this.foodNodeCounter++
if (this.foodNodeCounter % 2 === 0)
this.refFood.draw()
else
this.refFood.clear()
,
pause: function ()
if (this.gameOver)
alert('game over')
return
if (!this.moveInterval)
this.run()
return
clearInterval(this.moveInterval)
this.moveInterval = null
clearInterval(this.foodNodeInterval)
this.foodNodeInterval = null
,
stop: function ()
clearInterval(this.moveInterval)
this.moveInterval = null
clearInterval(this.foodNodeInterval)
this.foodNodeInterval = null
let _this = this
let gameOverCounter = 0
this.gameOverInterval = setInterval(function ()
gameOverCounter++
if (gameOverCounter % 2 === 0)
_this.refSnake.draw()
_this.refFood.draw()
else
_this.refSnake.clear()
_this.refFood.clear()
, constants.gameOverIntervalInMs)
,
draw: function ()
this.refArena.draw()
this.refSnake.draw()
,
clear: function ()
this.refArena.clear()
this.refSnake.clear()
,
onKeyPressed: function (event, )
let e = event || window.event || arguments.callee.caller.arguments[0]
if (!e)
return
if (e && e.keyCode === constants.KeyEvent.LEFT || e.keyCode === constants.KeyEvent.A)
this.refSnake.setDirection(constants.Direction.left)
else if (e.keyCode === constants.KeyEvent.UP || e.keyCode === constants.KeyEvent.W)
this.refSnake.setDirection(constants.Direction.up)
else if (e.keyCode === constants.KeyEvent.RIGHT || e.keyCode === constants.KeyEvent.D)
this.refSnake.setDirection(constants.Direction.right)
else if (e.keyCode === constants.KeyEvent.DOWN || e.keyCode === constants.KeyEvent.S)
this.refSnake.setDirection(constants.Direction.down)
else if (e.keyCode === constants.KeyEvent.SPACE)
this.pause()
,
nextFoodNode: function ()
let nextFoodPos = null
while (true)
let x = Math.floor(Math.random() * constants.defaultWallWidth + 1)
let y = Math.floor(Math.random() * constants.defaultWallHeight + 1)
nextFoodPos = x: x, y: y
if ((this.refSnake.touchedTimes(nextFoodPos) === 0)
&& (this.refArena.touchedTimes(nextFoodPos) === 0))
break
return x: nextFoodPos.x, y: nextFoodPos.y
,
</script>
<style scoped>
</style>
snake.vue
<template>
<div>
<div v-if="doDraw">
<div v-for="node in finalNodes" :style="node.style"></div>
</div>
</div>
</template>
<script>
import constants from './js/constants'
import utils from './js/utils'
export default
name: 'snake',
computed:
finalNodes: function ()
let result = []
for (let idx in this.nodes)
let node = this.nodes[idx]
let finalNode =
finalNode.style = utils.wrapStyleInfo(node.x, node.y, node.type + "_" + node.direction + ".png")
result.push(finalNode)
return result
,
props:
initLen:
type: Number,
required: false,
default: '5'
,
initX:
type: Number,
required: false,
default: '30'
,
initY:
type: Number,
required: false,
default: '5'
,
initDirection:
type: String,
required: false,
default: constants.Direction.left
,
data()
return
doDraw: true,
direction: 'left',
nodes: [
type: constants.Type.head,
direction: constants.Direction.left,
x: 30,
y: 5,
,
type: constants.Type.body,
direction: constants.Direction.left,
x: 31,
y: 5,
,
type: constants.Type.tail,
direction: constants.Direction.left,
x: 32,
y: 5,
]
,
mounted()
,
methods:
init: function ()
let revDirection = utils.reverseOfDirection(this.direction)
let nodes = []
let pos = x: this.initX, y: this.initY
for (let i = 0; i < this.initLen; i++)
nodes.push(x: pos.x, y: pos.y, type: constants.Type.body, direction: this.direction)
pos = utils.nodeMove(pos, revDirection)
nodes[0].type = constants.Type.head
nodes[nodes.length - 1].type = constants.Type.tail
this.nodes = nodes
,
setDirection: function (newDirection)
// if direction is current direction or reverse of current direction, ignore operation
if ((newDirection === this.direction) || (newDirection === utils.reverseOfDirection(this.direction)))
return
this.direction = newDirection
,
move: function ()
let nodes = []
let pos = x: this.nodes[0].x, y: this.nodes[0].y
pos = utils.nodeMove(pos, this.direction)
nodes.push(x: pos.x, y: pos.y, type: constants.Type.body, direction: this.direction)
for (let i = 0; i < this.nodes.length - 1; i++)
let oldNode = this.nodes[i]
nodes.push(x: oldNode.x, y: oldNode.y, type: constants.Type.body, direction: oldNode.direction)
nodes[0].type = constants.Type.head
nodes[nodes.length - 1].type = constants.Type.tail
// if last node direction is single, update direction to nodes[nodes.length - 2].direction
if (nodes[nodes.length - 1].direction !== nodes[nodes.length - 2].direction)
nodes[nodes.length - 1].direction = nodes[nodes.length - 2].direction
this.nodes = nodes
,
getHead: function ()
return this.nodes[0]
,
getNextHead: function ()
let snakeHead = this.getHead()
return utils.nodeMove(snakeHead, this.direction)
,
addTailNode: function ()
let lastNode = this.nodes[this.nodes.length - 1]
let revDirection = utils.reverseOfDirection(lastNode.direction)
let newTailNodePos = utils.nodeMove(lastNode, revDirection)
let newTailNode = x: newTailNodePos.x, y: newTailNodePos.y, type: constants.Type.tail, direction: lastNode.direction
this.nodes.push(newTailNode)
,
touchedTimes: function (pos)
let counter = 0
for (let idx in this.nodes)
let node = this.nodes[idx]
if (utils.nodeEquals(pos, node))
counter++
return counter
,
draw: function ()
this.doDraw = true
,
clear: function ()
this.doDraw = false
</script>
<style scoped>
</style>
arena.vue
<template>
<div>
<div v-if="doDraw">
<div v-for="node in finalWallNodes" :style="node.style"></div>
<div v-for="node in finalSteelNodes" :style="node.style"></div>
<div v-for="node in finalGrassNodes" :style="node.style"></div>
<div v-for="node in finalWaterNodes" :style="node.style"></div>
</div>
</div>
</template>
<script>
import utils from './js/utils'
import constants from "./js/constants";
export default
name: 'arena',
computed:
finalWallNodes: function ()
let result = []
for (let idx in this.wallNodes)
let node = this.wallNodes[idx]
let finalNode =
finalNode.style = utils.wrapStyleInfo(node.x, node.y, "wall.png")
result.push(finalNode)
return result
,
finalSteelNodes: function ()
let result = []
for (let idx in this.steelNodes)
let node = this.steelNodes[idx]
let finalNode =
finalNode.style = utils.wrapStyleInfo(node.x, node.y, "steel.png")
result.push(finalNode)
return result
,
finalGrassNodes: function ()
let result = []
for (let idx in this.grassNodes)
let node = this.grassNodes[idx]
let finalNode =
finalNode.style = utils.wrapStyleInfo(node.x, node.y, "grass.png")
result.push(finalNode)
return result
,
finalWaterNodes: function ()
let result = []
for (let idx in this.waterNodes)
let node = this.waterNodes[idx]
let finalNode =
finalNode.style = utils.wrapStyleInfo(node.x, node.y, "water.png")
result.push(finalNode)
return result
,
data()
return
doDraw: true,
wallNodes: [],
steelNodes: [],
grassNodes: [],
waterNodes: []
,
mounted()
,
methods:
init: function ()
for (let i = 0; i <= constants.defaultWallWidth; i++)
this.wallNodes.push(x: i, y: 0)
this.wallNodes.push(x: i, y: constants.defaultWallHeight)
for (let i = 0; i <= constants.defaultWallHeight; i++)
this.wallNodes.push(x: 0, y: i)
this.wallNodes.push(x: constants.defaultWallWidth, y: i)
// for (let i = 0; i < 10; i++)
// let nextPos = this.nextRandomNode()
// this.steelNodes.push(x: nextPos.x, y: nextPos.y)
// nextPos = this.nextRandomNode()
// this.grassNodes.push(x: nextPos.x, y: nextPos.y)
// nextPos = this.nextRandomNode()
// this.waterNodes.push(x: nextPos.x, y: nextPos.y)
//
,
touchedTimes: function (pos)
let counter = 0
for (let idx in this.wallNodes)
let node = this.wallNodes[idx]
if (utils.nodeEquals(pos, node))
counter++
for (let idx in this.steelNodes)
let node = this.steelNodes[idx]
if (utils.nodeEquals(pos, node))
counter++
return counter
,
blockedNodeTouchedTimes: function (pos)
let counter = 0
for (let idx in this.waterNodes)
let node = this.waterNodes[idx]
if (utils.nodeEquals(pos, node))
counter++
return counter
,
allNodeTouchedTimes: function (pos)
let counter = 0
for (let idx in this.wallNodes)
let node = this.wallNodes[idx]
if (utils.nodeEquals(pos, node))
counter++
for (let idx in this.steelNodes)
let node = this.steelNodes[idx]
if (utils.nodeEquals(pos, node))
counter++
for (let idx in this.grassNodes)
let node = this.grassNodes[idx]
if (utils.nodeEquals(pos, node))
counter++
for (let idx in this.waterNodes)
let node = this.waterNodes[idx]
if (utils.nodeEquals(pos, node))
counter++
return counter
,
draw: function ()
this.doDraw = true
,
clear: function ()
this.doDraw = false
,
nextRandomNode: function ()
let nextPos = null
while (true)
let x = Math.floor(Math.random() * constants.defaultWallWidth + 1)
let y = Math.floor(Math.random() * constants.defaultWallHeight + 1)
nextPos = x: x, y: y
if (this.allNodeTouchedTimes(nextPos) === 0)
break
return nextPos
</script>
<style scoped>
</style>
food.js
<template>
<div>
<div v-if="doDraw">
<div v-for="node in finalNodes" :style="node.style"></div>
</div>
</div>
</template>
<script>
import utils from './js/utils'
export default
name: 'food',
computed:
finalNodes: function ()
let result = []
for (let idx in this.nodes)
let node = this.nodes[idx]
let finalNode =
finalNode.style = utils.wrapStyleInfo(node.x, node.y, "food.png")
result.push(finalNode)
return result
,
data()
return
doDraw: true,
nodes: [
x: 0,
y: 0,
]
,
mounted()
,
methods:
draw: function ()
this.doDraw = true
,
clear: function ()
this.doDraw = false
,
refresh: function(pos)
this.nodes[0].x = pos.x
this.nodes[0].y = pos.y
,
getNode : function()
return this.nodes[0]
</script>
<style scoped>
</style>
constants.js
// --------------------------- global vars ---------------------------
// let canvas = $('#canvas')[0]
画布 api
// let canvasCtx = canvas.getContext('2d')
// 各个单元格的边长, 墙体节点/贪吃蛇节点/食物节点
let defaultCellLength = 20
// 默认的墙体宽由多少个 墙体节点 组成
let defaultWallWidth = 40
// 默认的墙体高由多少个 墙体节点 组成
let defaultWallHeight = 20
// 贪吃蛇 的自动移动频率, 毫秒计算
let moveIntervalInMs = 100
// 游戏结束的 闪烁频率
let gameOverIntervalInMs = 100
// 食物 闪烁频率
let foodIntervalInMs = 100
// 贪吃蛇节点类型, 头部/身体/尾部
let Type =
head: 'head',
body: 'body',
tail: 'tail',
// 贪吃蛇节点方向, 上下左右
let Direction =
up: 'up',
left: 'left',
down: 'down',
right: 'right',
// 各个按键代码, W/A/S/D 上/下/左/右 空格
let KeyEvent =
W: 87,
A: 65,
S: 83,
D: 68,
UP: 38,
LEFT: 37,
DOWN: 40,
RIGHT: 39,
SPACE: 32,
// 方向 -> 按键
let Direction2KeyEvent =
'up': KeyEvent.UP,
'left': KeyEvent.LEFT,
'down': KeyEvent.DOWN,
'right': KeyEvent.RIGHT,
export default
defaultCellLength,
defaultWallWidth,
defaultWallHeight,
moveIntervalInMs,
gameOverIntervalInMs,
foodIntervalInMs,
Type,
Direction,
KeyEvent,
Direction2KeyEvent,
utils.js
// --------------------------- assist methods ---------------------------
import constants from './constants'
/**
* nodeMove
*/
function nodeMove(pos, direction)
if (direction === constants.Direction.up)
return x: pos.x, y: pos.y - 1
else if (direction === constants.Direction.left)
return x: pos.x - 1, y: pos.y
else if (direction === constants.Direction.down)
return x: pos.x, y: pos.y + 1
else if (direction === constants.Direction.right)
return x: pos.x + 1, y: pos.y
function nodeEquals(pos1, pos2)
if (pos1.x === pos2.x && pos1.y === pos2.y)
return true
return false
function nodeStepDelta(pos1, pos2)
return Math.abs(pos1.x - pos2.x) + Math.abs(pos1.y - pos2.y)
function nodeStepDeltaByDirection(pos1, pos2, direction)
if (direction === constants.Direction.left || direction === constants.Direction.right)
return Math.abs(pos1.x - pos2.x)
if (direction === constants.Direction.up || direction === constants.Direction.down)
return Math.abs(pos1.y - pos2.y)
/**
* reverseOfDirection
*/
function reverseOfDirection(direction)
if (direction === constants.Direction.up)
return constants.Direction.down
else if (direction === constants.Direction.left)
return constants.Direction.right
else if (direction === constants.Direction.down)
return constants.Direction.up
else if (direction === constants.Direction.right)
return constants.Direction.left
function leftOfDirection(direction)
if (direction === constants.Direction.up)
return constants.Direction.left
else if (direction === constants.Direction.left)
return constants.Direction.down
else if (direction === constants.Direction.down)
return constants.Direction.right
else if (direction === constants.Direction.right)
return constants.Direction.up
function wrapStyleInfo(x, y, imageName)
let styleInfo =
position: "absolute",
left: (x * constants.defaultCellLength) + "px",
top: (y * constants.defaultCellLength) + "px",
width: "20px",
height: "20px",
backgroundImage: "url(" + require("@/assets/snake/" + imageName) + ")",
return styleInfo
/**
* draw image 2 canvas
*/
function drawImage(canvasCtx, imgUrl, x, y, width, height)
let img = new Image();
img.src = imgUrl;
img.onload = function ()
canvasCtx.drawImage(img, x, y, width, height);
// canvasCtx.drawImage(img, 0, 0, 20, 20, x, y, width, height);
/**
* simulate press key
*/
function pressKey(key)
let e = new KeyboardEvent('keydown', 'keyCode': key, 'which': key);
document.dispatchEvent(e);
export default
nodeMove,
nodeEquals,
nodeStepDelta,
nodeStepDeltaByDirection,
reverseOfDirection,
leftOfDirection,
wrapStyleInfo,
drawImage,
pressKey
展示效果
下载链接
参考代码下载
完
以上是关于11 贪吃蛇小游戏 js版本 + vue版本的主要内容,如果未能解决你的问题,请参考以下文章