蛇游戏:如何检查头部是否与自己的头部相撞body

Snake game: how to check if the head collides with its own body

你可能玩过贪吃蛇,这是一款你必须吃食物才能成长的游戏,如果你撞到蛇的 body 或某些障碍物,你就会失败。前半部分很简单,后半部分好像做不到。

我尝试进行 for 循环检查我的蛇数组的最后一个元素是否与其其他部分发生冲突。我的情况是这样的:如果数组中最后一项的 x 位置大于任何数组项的 x 位置,并且小于它们的 x 位置加上它们的宽度, 等等。那没有用。

这是我的代码:

<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="myCanvas" width="200px" height="200px" style="border:1px solid black"/>
<script>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var yPos = 20;
var width = 15;
var variable = 1;
var currentDir = 1;
//var xPos = (width+5)*variable;
var xPos = 20;
var myArr = [{myX:xPos,myY:yPos},{myX:xPos,myY:yPos},{myX:xPos,myY:yPos}];
var downPressed = false;
var upPressed = false;
var leftPressed = false;
var rightPressed = false;
var first = [0,20,40,60,80,100,120,140,160,180];
var firstX = Math.floor(Math.random()*10);
var firstY = Math.floor(Math.random()*10);
var okayed = first[firstX];
var notOkayed = first[firstY];
var maths = myArr[myArr.length-1];
function drawFood() {
ctx.beginPath();
ctx.rect(okayed,notOkayed,15,15);
ctx.fillStyle = "red";
ctx.fill();
ctx.closePath();    
}

function drawRectangle() {

    ctx.clearRect(0,0,200,200);
    drawFood();

    for(var i = 0;i<myArr.length;i++) {
    ctx.beginPath();
    ctx.rect(myArr[i].myX,myArr[i].myY,width,15);
    ctx.fillStyle = "blue";
    ctx.fill();
    ctx.closePath();
    }

    requestAnimationFrame(drawRectangle);
}


setInterval("calledin()",100);
function calledin() {
    var secondX = Math.floor(Math.random()*10);
    var secondY = Math.floor(Math.random()*10);
    var newobj = {myX:myArr[myArr.length-1].myX+20,myY:myArr[myArr.length-1].myY};
    var newobjTwo = {myX:myArr[myArr.length-1].myX,myY:myArr[myArr.length-1].myY+20};
    var newobjLeft = {myX:myArr[myArr.length-1].myX-20,myY:myArr[myArr.length-1].myY};
    var newobjUp = {myX:myArr[myArr.length-1].myX,myY:myArr[myArr.length-1].myY-20};
    var okayNewObj = {myX:myArr[1].myX - 20,myY:myArr[1].myY};


if(myArr[myArr.length-1].myX > 180 || myArr[myArr.length-1].myX < 0 || myArr[myArr.length-1].myY > 180 || myArr[myArr.length-1].myY < 0)
        {alert("Game Over");window.location.reload();}

    if(myArr[myArr.length-1].myX > okayed-5 && myArr[myArr.length-1].myX < okayed+20 && myArr[myArr.length-1].myY < notOkayed+20 &&
    myArr[myArr.length-1].myY > notOkayed-5) {
        okayed = first[secondX];
        notOkayed = first[secondY];
        myArr.unshift(okayNewObj);
    }

    if(currentDir == 1) {
    myArr.push(newobj);
    myArr.shift();}

    if(currentDir == 2) {
        myArr.push(newobjTwo);
        myArr.shift();
    }

    if(currentDir == 4) {
        myArr.push(newobjLeft);
        myArr.shift();
    }


    if(currentDir == 3) {
        myArr.push(newobjUp);
        myArr.shift();
    }

     for(var i = 0;i<myArr.length-2;i++) {
     if(myArr[myArr.length-1].myX > myArr[i].myX &&
     myArr[myArr.length-1].myX < myArr[i].myX + 15 && myArr[myArr.length-1].myY > myArr[i].myY && myArr[myArr.length-1].myY > myArr[i].myY + 15)
     {alert("Game over");window.location.reload();} 
 }

}


function downed(e) {
    if(e.keyCode==40) {if(currentDir != 3) {currentDir = 2;}}
    if(e.keyCode==38) {if(currentDir != 2) {currentDir = 3;}}
    if(e.keyCode==39) {if(currentDir != 4) {currentDir = 1;}}
    if(e.keyCode==37) {if(currentDir != 1) {currentDir = 4;}}

}

function upped(e) {
    if(e.keyCode == 40) {downPressed = false;}
}
document.addEventListener("keydown",downed,false);
document.addEventListener("keyup",upped,false);

drawRectangle();
</script>
</body>
</html>

假设蛇由一个名为 snake 的数组表示,其中头部位于索引 snake.length - 1 处。我们必须将头部的位置与索引 0snake.length - 2.

处的 body 段的位置进行比较

如果蛇头与 body 段发生碰撞,以下代码将 okay 设置为 false。否则,okay 仍然是 true.

var head = snake[snake.length - 1],
    x = head.x,
    y = head.y,
    okay = true;           
for (var i = snake.length - 2; i >= 0; --i) {
  if (snake[i].x == x && snake[i].y == y) {
    okay = false;
    break;
  }
}

下面是一个片段,我在其中修改了您的代码以阐明游戏逻辑并简化许多计算。

我没有直接使用 canvas 坐标,而是使用虚拟网格单元格的列索引 x 和行索引 y 表示每个位置。这让我们可以通过将 1 或 -1 添加到 xy 来计算相邻的网格位置。当需要绘制 canvas 时,我们将虚拟坐标乘以单元格大小。

我已经用变量替换了你的大部分文字值。例如,我们可以这样做,而不是将 canvas 尺寸设置为 200 x 200:

canvas.width = numCols * cellSize;
canvas.height = numRows * cellSize;

这让我们可以在一处更改 numColsnumRows 以调整整个游戏网格的大小。所有的计算都成功了,因为它们评估变量而不是使用文字。

我更改了 key-event 处理以识别 W-A-S-D 键的键码以及箭头键。当游戏嵌入到一个长网页中时,就像这里一样,您可能需要使用 W-A-S-D 键,这样在您玩游戏时页面就不会上下滚动。

var canvas,
    ctx,
    currentDir,
    startX = 1,
    startY = 1,
    startSnakeLength = 3,
    snake,
    cellSize = 18,
    cellGap = 1,
    foodColor = '#a2302a',
    snakeBodyColor = '#2255a2',
    snakeHeadColor = '#0f266b',
    numRows = 10,
    numCols = 10,
    canvasWidth = numCols * cellSize,
    canvasHeight = numRows * cellSize;

var food = {};

function placeFood() {
  // Find a random location that isn't occupied by the snake.
  var okay = false;
  while (!okay) {
    food.x = Math.floor(Math.random() * numCols);
    food.y = Math.floor(Math.random() * numRows);
    okay = true;
    for (var i = 0; i < snake.length; ++i) {
      if (snake[i].x == food.x && snake[i].y == food.y) {
        okay = false;
        break;
      }
    }
  }
}

function paintCell(x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x * cellSize + cellGap,
               y * cellSize + cellGap,
               cellSize - cellGap,
               cellSize - cellGap);
}

function paintCanvas() {
  ctx.clearRect(0, 0, canvasWidth, canvasHeight);
  paintCell(food.x, food.y, foodColor);
  var head = snake[snake.length - 1];
  paintCell(head.x, head.y, snakeHeadColor);
  for (var i = snake.length - 2; i >= 0; --i) {
    paintCell(snake[i].x, snake[i].y, snakeBodyColor);
  }
}
  
function updateGame() {
  var head = snake[snake.length - 1],
      x = head.x,
      y = head.y;

  // Move the snake.
  var tail = snake.shift();
  switch (currentDir) {
    case 'up': 
      snake.push(head = { x: x, y: y - 1 });
      break;
    case 'right': 
      snake.push(head = { x: x + 1, y: y });
      break;
    case 'down': 
      snake.push(head = { x: x, y: y + 1 });
      break;
    case 'left': 
      snake.push(head = { x: x - 1, y: y });
      break;
  }
  paintCanvas();
  x = head.x;
  y = head.y;

  // Check for wall collision.
  if (x < 0 || x >= numCols || y < 0 || y >= numRows) {
    stopGame('wall collision');
    return;
  }

  // Check for snake head colliding with snake body.
  for (var i = snake.length - 2; i >= 0; --i) {
    if (snake[i].x == x && snake[i].y == y) {
      stopGame('self-collision');
      return;
    }
  }

  // Check for food.
  if (x == food.x && y == food.y) {
    placeFood();
    snake.unshift(tail);
    setMessage(snake.length + ' segments');
  }
}

var dirToKeyCode = {  // Codes for arrow keys and W-A-S-D.
      up: [38, 87],
      right: [39, 68],
      down: [40, 83],
      left: [37, 65]
    },
    keyCodeToDir = {};  // Fill this from dirToKeyCode on page load.

function keyDownHandler(e) {
  var keyCode = e.keyCode;
  if (keyCode in keyCodeToDir) {
    currentDir = keyCodeToDir[keyCode];
  }
}

function setMessage(s) {
  document.getElementById('messageBox').innerHTML = s;
}

function startGame() {
  currentDir = 'right';
  snake = new Array(startSnakeLength);
  snake[snake.length - 1] = { x: startX, y: startY };
  for (var i = snake.length - 2; i >= 0; --i) {
    snake[i] = { x: snake[i + 1].x, y: snake[i + 1].y + 1 };
  }
  placeFood();
  paintCanvas();
  setMessage('');
  gameInterval = setInterval(updateGame, 200);
  startGameButton.disabled = true;
}

function stopGame(message) {
  setMessage(message + '<br> ended with ' + snake.length + ' segments');
  clearInterval(gameInterval);
  startGameButton.disabled = false;
}

var gameInterval,
    startGameButton;

window.onload = function () {
  canvas = document.getElementById('gameCanvas'),
  ctx = canvas.getContext('2d');
  canvas.width = numCols * cellSize;
  canvas.height = numRows * cellSize;
  Object.keys(dirToKeyCode).forEach(function (dir) {
    dirToKeyCode[dir].forEach(function (keyCode) {
      keyCodeToDir[keyCode] = dir;
    })
  });
  document.addEventListener("keydown", keyDownHandler, false);
  startGameButton = document.getElementById('startGameButton');
  startGameButton.onclick = startGame;
}
body {
  font-family: sans-serif;
}
#gameCanvas {
  border: 1px solid #000;
  float: left;
  margin-right: 15px;
}
#startGameButton, #messageBox {
  font-size: 16px;
  margin-top: 15px;
}
#messageBox {
  line-height: 24px;
}
<canvas id="gameCanvas"></canvas>

<button id="startGameButton">Start game</button>

<div id="messageBox"></div>