前言

// 呵呵, 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, SteelGrass 的特征为可以穿过, 无任何影响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 registerdocument.onkeydown = function (event) {game.onKeyPressed(event, canvasCtx)}// // auto directiongame.auto(canvasCtx, moveIntervalInMs/2);// update score panelsetInterval(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 = xthis.y = ythis.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 = 0for (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 = 0for (let idx in this.waterNodes) {let node = this.waterNodes[idx]if (nodeEquals(pos, node)) {counter++}}return counter}this.allNodeTouchedTimes = function (pos) {let counter = 0for (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 = nullwhile (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 = xthis.y = ythis.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 = xthis.y = ythis.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 = xthis.y = ythis.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 = xthis.y = ythis.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 = initLenthis.initX = initXthis.initY = initYthis.initDirection = initDirectionthis.direction = initDirectionthis.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 operationif ((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].directionif (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 = 0for (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 = xthis.y = ythis.type = typethis.direction = directionthis.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 = nullthis.gameOver = falsethis.gameOverInterval = nullthis.foodNode = nullthis.foodNodeInterval = nullthis.foodNodeCounter = 0this.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 = thisthis.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 = truethis.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 = nullclearInterval(this.foodNodeInterval)this.foodNodeInterval = null}this.stop = function (canvasCtx) {clearInterval(this.moveInterval)this.moveInterval = nullclearInterval(this.foodNodeInterval)this.foodNodeInterval = nulllet _this = thislet gameOverCounter = 0this.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 = nullwhile (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 = thissetInterval(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.directionstepDelta = 2if (_this.simulateNStepGameOver(stepDelta, newDirection)) {let dir1 = leftOfDirection(snakeHead.direction)let dir2 = reverseOfDirection(dir1)newDirection = (!_this.simulateNStepGameOver(stepDelta, dir1)) ? dir1 : dir2pressKey(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 = thisdocument.onkeydown = function (event) {_this.onKeyPressed(event, )}},methods: {init: function () {this.refArena = this.$refs.arenathis.refSnake = this.$refs.snakethis.refFood = this.$refs.foodthis.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 = thisthis.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 = truethis.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 = nullclearInterval(this.foodNodeInterval)this.foodNodeInterval = null},stop: function () {clearInterval(this.moveInterval)this.moveInterval = nullclearInterval(this.foodNodeInterval)this.foodNodeInterval = nulllet _this = thislet gameOverCounter = 0this.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 = nullwhile (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.headnodes[nodes.length - 1].type = constants.Type.tailthis.nodes = nodes},setDirection: function (newDirection) {// if direction is current direction or reverse of current direction, ignore operationif ((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.headnodes[nodes.length - 1].type = constants.Type.tail// if last node direction is single, update direction to nodes[nodes.length - 2].directionif (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 = 0for (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 = 0for (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 = 0for (let idx in this.waterNodes) {let node = this.waterNodes[idx]if (utils.nodeEquals(pos, node)) {counter++}}return counter},allNodeTouchedTimes: function (pos) {let counter = 0for (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 = nullwhile (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.xthis.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
}

展示效果

下载链接

参考代码下载

html + js 实现的贪吃蛇 实现了自动导航

vue 实现的贪吃蛇 实现了自动导航

11 贪吃蛇小游戏 js版本 + vue版本相关推荐

  1. JS贪吃蛇小游戏(DOM (html+css+js))

    游戏截图: html部分: <!DOCTYPE html> <html lang="en"> <head><meta charset=&q ...

  2. Snake贪吃蛇小游戏纯js代码

    Snake贪吃蛇小游戏纯js代码 先给个效果图,一样的简单而又纯朴,回归贪吃蛇最原始的状态 还是先分析一下,使用面向对象编程真的降低了编程的难度(只要前期把思路想好,各个类,属性,方法抽象出来),就真 ...

  3. JavaScript 进阶教程(2)---面向对象实战之贪吃蛇小游戏

    目录 1 引言 2 游戏地图 3 游戏对象 3.1 食物对象 3.2 小蛇对象 3.3 游戏对象 4 游戏的逻辑 4.1小蛇的移动 4.2 让蛇自己动起来 4.2.1 自动移动 4.2.2 自调用函数 ...

  4. python 贪吃蛇小游戏代码_10分钟再用Python编写贪吃蛇小游戏

    Python编写贪吃蛇 前不久我们公众号发布了一篇C++编写贪吃蛇小游戏的推文,反响空前.看来大家对这类简单易上手小游戏还是很喜爱的. 恰逢2018年IEEE Spectrum编程语言排行榜新鲜出炉, ...

  5. 再来一次的C语言贪吃蛇小游戏(三)

    8.游戏的不同界面 为了便于实现主要功能,之前我们所有的状态控制都是放在游戏中,但实际上我们应该把这些状态控制抽离出来,通过菜单来控制,以便在不同游戏界面间切换. 菜单界面 游戏界面 排行榜 游戏结束 ...

  6. 基于C语言Ncurse库和链表的简单贪吃蛇小游戏

    参考:基于C语言Ncurse库和链表的简单贪吃蛇小游戏 作者:三速何时sub20 发布时间:2020-09-29 10:23:51 网址:https://blog.csdn.net/weixin_44 ...

  7. 用 typescript 做一个贪吃蛇小游戏

    typescript 做一个贪吃蛇小游戏 搭建环境 创建 tscofig.json 文件 配置如下 {"compilerOptions": {"target": ...

  8. 控制台版贪吃蛇小游戏 C语言

    写在前面 最近我们C语言的课设快开始了,开始前刚好有时间就写了一下C语言的贪吃蛇小游戏(单链表实现),包含了经典模式和无边界模式 ,网上查了设置颜色 和 改变光标位置 还有 用方向键控制 的函数,第一 ...

  9. GUI编程---贪吃蛇小游戏开发

    学习链接:狂神说Java–1小时开发贪吃蛇小游戏 ①初识理论 帧:时间片足够小=>就是动画,1秒30帧.连起来是动画,拆开就是静态的图片! 键盘监听 定时器Timer 游戏图片素材:GUI之贪吃 ...

最新文章

  1. YOLOv4-5D:一种高效的自动驾驶物体检测器
  2. ldd查看可执行程序的依赖库
  3. css中vertical-align生效
  4. php dos命令用不了,windows下如何使用DOS命令强制复制文件
  5. 11个小技巧,玩转Spring!
  6. eclipse中使用Lombok(转)
  7. 【Vue】v-if与v-show的区别
  8. 干货 | 利用SPSS进行高级统计分析第二期
  9. DatagramPacket.getData()与DatagramPacket.getLength()的误区
  10. C3D行为识别(一):UCF101视频数据集预处理
  11. struct termios结构体详解
  12. 使用Qt Designer来设计界面
  13. 3种方法解决word文档无法编辑
  14. 电子实验记录本促进科研诚信建设
  15. 物理机通过Xshell连接不上虚拟机的解决方案
  16. pmp练习题及其答案
  17. PM第1步:产品需求文档
  18. 第一阶段:2014年10月13日-12月14日,36天完成。每周5天,8周完成。
  19. git: Couldn‘t find remote ref
  20. 解决银河麒麟系统开机后桌面无图标,只有下方开始菜单和任务栏可操作问题

热门文章

  1. 迅雷软件一直出现崩溃问题的解决方法
  2. 【操作系统基础】文件管理系统(二)
  3. crmeb知识付费系统直播列表管理
  4. 前端上传大文件怎么处理
  5. Eclipse @override报错
  6. APQP(advanced product quality planning先期产品质量策划)
  7. kali linux 安装lxde_【kaliLinux】安装
  8. char byte java_java byte与char互转原理-转 | 学步园
  9. 原始套接字透析之ICMP拒绝服务攻击
  10. 修改本地Git用户名、密码