javascript - 蛇头出界
问题描述
我有一个基于用 JavaScript 和画布创建的蛇游戏。
问题是碰撞在块中起作用,但它仅在头部移动到最外面的单元格时才起作用。如何解决这个问题,以使这块头部不会落入框架中?我试图创建一个新头的空块,但它不起作用。
const playground = document.querySelector('canvas');
const ctx = playground.getContext('2d');
const scoreBox = document.querySelector('#scoreBox');
document.addEventListener("keydown", moveSnake);
playground.width = 500;
playground.height = 500;
const gridSize = 20;
const snakeColor = "darkslategray";
const foodColor = "indianred";
const spaceGrid = 2;
let tileCount = playground.width / gridSize;
let snakeVelocityX = 0;
let snakeVelocityY = 0;
let start = true;
let canDeath = false;
let drawSnakeTail = true;
const SnakeLen = [];
const gameFruits = [];
let snakeTail = 3;
function sleep(ms) {
ms += new Date().getTime();
while (new Date() < ms) {}
}
function getRandCoord() {
return Math.floor(Math.random() * tileCount);
}
let snakeX = playground.width / 2 - gridSize / 2;
let snakeY = playground.height / 2 - gridSize / 2;
console.log(snakeX);
function drawGameScene() {
ctx.fillStyle = "snow";
ctx.fillRect(0, 0, playground.width, playground.height);
}
function moveSnake(ev) {
const oldSnakeSpeedX = snakeVelocityX;
const oldSnakeSpeedY = snakeVelocityY;
switch (ev.keyCode) {
case 37:
snakeVelocityX = -1;
snakeVelocityY = 0;
break;
case 38:
snakeVelocityX = 0;
snakeVelocityY = -1;
break;
case 39:
snakeVelocityX = 1;
snakeVelocityY = 0;
break;
case 40:
snakeVelocityX = 0;
snakeVelocityY = 1;
break;
}
canDeath = true;
if ((snakeVelocityX !== 0 && oldSnakeSpeedX !== 0 && snakeVelocityX !== oldSnakeSpeedX) ||
(snakeVelocityY !== 0 && oldSnakeSpeedY !== 0 && snakeVelocityY !== oldSnakeSpeedY)) {
snakeVelocityX = oldSnakeSpeedX;
snakeVelocityY = oldSnakeSpeedY;
}
}
function drawFruits() {
for (let i = 0; i < gameFruits.length; i++) {
const fruit = gameFruits[i];
ctx.fillStyle = fruit.color;
ctx.fillRect(fruit.x * gridSize, fruit.y * gridSize, gridSize - 2, gridSize - 2);
}
}
function SnakeCollisionFoodHandler() {
for (let i = 0; i < gameFruits.length; i++) {
const fruit = gameFruits[i];
if (snakeX === fruit.x && snakeY === fruit.y) {
snakeTail += fruit.points;
gameFruits.splice(i, 1);
spawnFruitTile();
}
}
}
function containsObject(obj, list) {
var i;
for (i = 0; i < list.length; i++) {
if (list[i]["x"] == obj["x"] || list[i]["y"] == obj["y"]) {
return false;
}
}
return true;
}
function spawnFruitTile() {
let x = getRandCoord();
y = getRandCoord();
coors = {
x,
y
};
if (containsObject(coors, SnakeLen)) {
if (x === gridSize + 4 || y === gridSize + 4 || x === 0 || y === 0) {
spawnFruitTile();
} else {
gameFruits.push({
x: x,
y: y,
points: 1,
color: foodColor
});
}
} else {
spawnFruitTile();
}
}
function onGameOver() {
drawSnakeTail = false;
death_score = snakeTail - 3;
ctx.font = "20px Arial";
ctx.fillStyle = '#20201d';
window.pauseAll = true;
ctx.fillText("Game over\nScore:" + death_score, (playground.width - 200) / 2, playground.height / 2);
// alert();
// location.reload();
}
function drawSnake() {
console.log(SnakeLen);
if (drawSnakeTail == true) {
if (start == true) {
snakeVelocityY = 1;
snakeVelocityX = 1;
}
snakeX += snakeVelocityX;
snakeY += snakeVelocityY;
if (snakeX < 0) {
snakeX = tileCount - 1;
}
if (snakeX > tileCount - 1) {
snakeX = 0;
}
if (snakeY < 0) {
snakeY = tileCount - 1;
}
if (snakeY > tileCount - 1) {
snakeY = 0;
}
drawGameScene();
ctx.fillStyle = snakeColor;
for (let i = 0; i < SnakeLen.length; i++) {
const {
x,
y
} = SnakeLen[i];
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
if (start == true) {
snakeVelocityY = 0;
snakeVelocityX = 0;
setTimeout(start = false, 100);
}
if (start == false) {
if (canDeath == true) {
if ((x === snakeX && y === snakeY) && (snakeX !== 0 || snakeY !== 0)) {
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
setTimeout(onGameOver, 100);
}
} //playground.width
// console.log(snakeX , playground.width - tileCount, gridSize + spaceGrid, x);
if (snakeX === gridSize + 4 || snakeY === gridSize + 4 || snakeX === 0 || snakeY === 0) { //
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
setTimeout(onGameOver, 100);
}
}
}
}
SnakeLen.push({
x: snakeX,
y: snakeY
});
while (SnakeLen.length > snakeTail) {
SnakeLen.shift();
}
}
function drawBorder() {
ctx.globalCompositeOperation = "source-over";
ctx.lineWidth = 40;
ctx.strokeStyle = "#076826";
ctx.strokeRect(0, 0, playground.width, playground.height);
}
function drawScore() {
ctx.font = "20px Arial";
ctx.fillStyle = '#20201d';
ctx.fillText("Score:" + (snakeTail - 3), 10, 20);
}
function onGameFrame() {
drawSnake();
drawFruits();
SnakeCollisionFoodHandler();
drawBorder();
drawScore();
}
(function onGameInit() {
spawnFruitTile();
setInterval(onGameFrame, 100);
}());
canvas {
border: 0.5px solid black;
color: black;
display: flex;
position: absolute;
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%)
}
<div class="container">
<canvas width="500px" height="500px"></canvas>
</div>
解决方案
如果您在onGameOver
呼叫前后不设置超时,则游戏将在当前位置结束。我猜这是因为 javascript 能够运行另一个循环,因此在事件运行之前绘制了另一个帧:
if (snakeX === gridSize + 4 || snakeY === gridSize + 4 || snakeX === 0 || snakeY === 0) { //
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
onGameOver(); // <-- removed the setTimeout wrap
}
const playground = document.querySelector('canvas');
const ctx = playground.getContext('2d');
const scoreBox = document.querySelector('#scoreBox');
document.addEventListener("keydown", moveSnake);
playground.width = 500;
playground.height = 500;
const gridSize = 20;
const snakeColor = "darkslategray";
const foodColor = "indianred";
const spaceGrid = 2;
let tileCount = playground.width / gridSize;
let snakeVelocityX = 0;
let snakeVelocityY = 0;
let start = true;
let canDeath = false;
let drawSnakeTail = true;
const SnakeLen = [];
const gameFruits = [];
let snakeTail = 3;
function sleep(ms) {
ms += new Date().getTime();
while (new Date() < ms) {}
}
function getRandCoord() {
return Math.floor(Math.random() * tileCount);
}
let snakeX = playground.width / 2 - gridSize / 2;
let snakeY = playground.height / 2 - gridSize / 2;
console.log(snakeX);
function drawGameScene() {
ctx.fillStyle = "snow";
ctx.fillRect(0, 0, playground.width, playground.height);
}
function moveSnake(ev) {
const oldSnakeSpeedX = snakeVelocityX;
const oldSnakeSpeedY = snakeVelocityY;
switch (ev.keyCode) {
case 37:
snakeVelocityX = -1;
snakeVelocityY = 0;
break;
case 38:
snakeVelocityX = 0;
snakeVelocityY = -1;
break;
case 39:
snakeVelocityX = 1;
snakeVelocityY = 0;
break;
case 40:
snakeVelocityX = 0;
snakeVelocityY = 1;
break;
}
canDeath = true;
if ((snakeVelocityX !== 0 && oldSnakeSpeedX !== 0 && snakeVelocityX !== oldSnakeSpeedX) ||
(snakeVelocityY !== 0 && oldSnakeSpeedY !== 0 && snakeVelocityY !== oldSnakeSpeedY)) {
snakeVelocityX = oldSnakeSpeedX;
snakeVelocityY = oldSnakeSpeedY;
}
}
function drawFruits() {
for (let i = 0; i < gameFruits.length; i++) {
const fruit = gameFruits[i];
ctx.fillStyle = fruit.color;
ctx.fillRect(fruit.x * gridSize, fruit.y * gridSize, gridSize - 2, gridSize - 2);
}
}
function SnakeCollisionFoodHandler() {
for (let i = 0; i < gameFruits.length; i++) {
const fruit = gameFruits[i];
if (snakeX === fruit.x && snakeY === fruit.y) {
snakeTail += fruit.points;
gameFruits.splice(i, 1);
spawnFruitTile();
}
}
}
function containsObject(obj, list) {
var i;
for (i = 0; i < list.length; i++) {
if (list[i]["x"] == obj["x"] || list[i]["y"] == obj["y"]) {
return false;
}
}
return true;
}
function spawnFruitTile() {
let x = getRandCoord();
y = getRandCoord();
coors = {
x,
y
};
if (containsObject(coors, SnakeLen)) {
if (x === gridSize + 4 || y === gridSize + 4 || x === 0 || y === 0) {
spawnFruitTile();
} else {
gameFruits.push({
x: x,
y: y,
points: 1,
color: foodColor
});
}
} else {
spawnFruitTile();
}
}
function onGameOver() {
drawSnakeTail = false;
death_score = snakeTail - 3;
ctx.font = "20px Arial";
ctx.fillStyle = '#20201d';
window.pauseAll = true;
ctx.fillText("Game over\nScore:" + death_score, (playground.width - 200) / 2, playground.height / 2);
// alert();
// location.reload();
}
function drawSnake() {
console.log(SnakeLen);
if (drawSnakeTail == true) {
if (start == true) {
snakeVelocityY = 1;
snakeVelocityX = 1;
}
snakeX += snakeVelocityX;
snakeY += snakeVelocityY;
if (snakeX < 0) {
snakeX = tileCount - 1;
}
if (snakeX > tileCount - 1) {
snakeX = 0;
}
if (snakeY < 0) {
snakeY = tileCount - 1;
}
if (snakeY > tileCount - 1) {
snakeY = 0;
}
drawGameScene();
ctx.fillStyle = snakeColor;
for (let i = 0; i < SnakeLen.length; i++) {
const {
x,
y
} = SnakeLen[i];
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
if (start == true) {
snakeVelocityY = 0;
snakeVelocityX = 0;
setTimeout(start = false, 100);
}
if (start == false) {
if (canDeath == true) {
if ((x === snakeX && y === snakeY) && (snakeX !== 0 || snakeY !== 0)) {
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
setTimeout(onGameOver, 100);
}
} //playground.width
// console.log(snakeX , playground.width - tileCount, gridSize + spaceGrid, x);
if (snakeX === gridSize + 4 || snakeY === gridSize + 4 || snakeX === 0 || snakeY === 0) { //
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
onGameOver();
}
}
}
}
SnakeLen.push({
x: snakeX,
y: snakeY
});
while (SnakeLen.length > snakeTail) {
SnakeLen.shift();
}
}
function drawBorder() {
ctx.globalCompositeOperation = "source-over";
ctx.lineWidth = 40;
ctx.strokeStyle = "#076826";
ctx.strokeRect(0, 0, playground.width, playground.height);
}
function drawScore() {
ctx.font = "20px Arial";
ctx.fillStyle = '#20201d';
ctx.fillText("Score:" + (snakeTail - 3), 10, 20);
}
function onGameFrame() {
drawSnake();
drawFruits();
SnakeCollisionFoodHandler();
drawBorder();
drawScore();
}
(function onGameInit() {
spawnFruitTile();
setInterval(onGameFrame, 100);
}());
canvas {
border: 0.5px solid black;
color: black;
display: flex;
position: absolute;
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%)
}
<div class="container">
<canvas width="500px" height="500px"></canvas>
</div>
推荐阅读
- splunk - Splunk 查询根据同一索引中的其他事件过滤掉
- flutter - 如何解决飞镖的未来?
- jmeter - Jmeter停止当前的迭代问题
- python-3.x - 如何操作 CSV。Jupyter Notebook 上的文件
- reactjs - 如何使用 mobx-react-lite 使子组件对 mobx 中的状态变化做出反应
- java - Java:错误:在调用超类型构造函数之前无法引用 this
- ansible - Ansible:在虚拟环境中的目标主机上运行模块
- cypress - 赛普拉斯上传文件给出错误无法在“窗口”上执行“atob”
- powerbi - Power BI - 将字段名称合并为一个
- sql - SQL Contains() 未返回“The”的结果