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

展示效果

下载链接

参考代码下载

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

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

以上是关于11 贪吃蛇小游戏 js版本 + vue版本的主要内容,如果未能解决你的问题,请参考以下文章

13行js写贪吃蛇游戏

贪吃蛇easyx版本

结对-开发贪吃蛇游戏-开发环境搭建过程

结对-开发贪吃蛇游戏-开发环境搭建过程

40行贪吃蛇 原生js

简单贪吃蛇游戏js