Reputation: 966
In an attempt to begin learning Javascript, I decided to implement the well-known snake game. I have been trying to follow as little as possible in regards to tutorials to try and best understand what I am doing.
So far I have gotten my snake to move, eat food, and "grow", however upon growing the cells do not follow the main snake cell. I have tried troubleshooting this for a while now, and some possible solutions I came up with include: starting with multiple cells rather than 1, storing the position of the prior location of the main cell and placing the new cell there, or even changing the implementation completely.
I am unsure what route to take, and I feel as if I am close to getting it to work, but just not quite. Currently, the cells are created but relocated onto the parent cell and follow that same cell.
const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");
const HEIGHT = 400;
const WIDTH = 400;
const SCALE = 20;
window.addEventListener("keydown", (event) => {
const direction = event.key.replace("Arrow", "");
snake.update(direction);
});
function setup() {
canvas.height = HEIGHT;
canvas.width = WIDTH;
food.createFood();
window.setInterval(() => {
context.clearRect(0, 0, WIDTH, HEIGHT);
snake.move(snake.xSpeed, snake.ySpeed);
food.drawFood();
snake.draw();
snake.shiftCells();
}, 200);
}
let snake = {
cells: [{ x: 200, y: 200 }],
position: {
x: 200,
y: 200,
},
xSpeed: SCALE,
ySpeed: 0,
length: 1,
draw: () => {
for (cell of snake.cells) {
context.fillStyle = "#C2F970";
context.fillRect(cell.x, cell.y, SCALE, SCALE);
}
},
update: (direction) => {
switch (direction) {
case "Up":
snake.ySpeed = -1 * SCALE;
snake.xSpeed = 0;
break;
case "Down":
snake.ySpeed = 1 * SCALE;
snake.xSpeed = 0;
break;
case "Left":
snake.xSpeed = -1 * SCALE;
snake.ySpeed = 0;
break;
case "Right":
snake.xSpeed = 1 * SCALE;
snake.ySpeed = 0;
break;
}
},
move: (xDist, yDist) => {
if (snake.cells[0].x == food.position.x && snake.cells[0].y == food.position.y) {
snake.eat();
}
// x-position
if (snake.cells[0].x + xDist < 0) {
snake.cells[0].x = WIDTH;
} else if (snake.cells[0].x + xDist > WIDTH) {
snake.cells[0].x = 0;
} else {
snake.cells[0].x += xDist;
}
// y-position
if (snake.cells[0].y + yDist < 0) {
snake.cells[0].y = HEIGHT;
} else if (snake.cells[0].y + yDist > HEIGHT) {
snake.cells[0].y = 0;
} else {
snake.cells[0].y += yDist;
}
},
shiftCells: () => {
for (let i = length; i > 1; i--) {
snake.cells[i] = snake.cells[i - 1];
}
},
eat: () => {
snake.grow();
food.createFood();
},
grow: () => {
snake.cells.push({
x: snake.cells[snake.length - 1].x,
y: snake.cells[snake.length - 1].y,
});
snake.cells[0].x += snake.xSpeed;
snake.cells[0].y += snake.ySpeed;
length++;
},
};
let food = {
position: {
x: 0,
y: 0,
},
createFood: () => {
food.position.x = SCALE * Math.floor(Math.random() * (WIDTH / SCALE));
food.position.y = SCALE * Math.floor(Math.random() * (HEIGHT / SCALE));
},
drawFood: () => {
context.fillStyle = "#D3FCD5";
context.fillRect(food.position.x, food.position.y, SCALE, SCALE);
},
};
setup();
Any feedback on how to get the cells to follow properly and even any tips on how to more properly structure my code would be much appreciated (eg. should snake be implemented in a different file? In a class?).
Upvotes: 0
Views: 423
Reputation: 6648
First, great stuff on the neat way you've laid out your code. You've clearly got a lot of potential at this, but there are nevertheless few things that you can improve.
The mechanism to get the snake's growth to work is a bit subtle. I've put some working code below but you'll probably want to ignore it at first and try and get it to work given the following observations:
length
not snake.length
length - 1
to 1 inclusiveshiftCells
before you move the first cell of the snake otherwise you are placing the second cell in the new position of the first cell instead of its one before the snake movesgrow
methodgrow
method you increment the first cell based on the speed. This doesn't seem the right place for this given you have other code dedicated to moving the first cell and dealing with the wrapping problem at the same timeSome more (incomplete) observations on the code are:
length
property as this information is contained in the length of the cells
array. That's duplicating information and that means there's a risk it becomes inconsistentWorking code:
const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");
const HEIGHT = 400;
const WIDTH = 400;
const SCALE = 20;
window.addEventListener("keydown", (event) => {
const direction = event.key.replace("Arrow", "");
snake.update(direction);
});
function setup() {
canvas.height = HEIGHT;
canvas.width = WIDTH;
food.createFood();
window.setInterval(() => {
context.clearRect(0, 0, WIDTH, HEIGHT);
snake.move(snake.xSpeed, snake.ySpeed);
food.drawFood();
snake.draw();
}, 200);
}
let snake = {
cells: [{ x: 200, y: 200 }],
position: {
x: 200,
y: 200,
},
xSpeed: SCALE,
ySpeed: 0,
length: 1,
draw: () => {
snake.cells.forEach( cell => {
context.fillStyle = "#C2F970";
context.fillRect( cell.x, cell.y, SCALE, SCALE );
})
},
update: (direction) => {
switch (direction) {
case "Up":
snake.ySpeed = -1 * SCALE;
snake.xSpeed = 0;
break;
case "Down":
snake.ySpeed = 1 * SCALE;
snake.xSpeed = 0;
break;
case "Left":
snake.xSpeed = -1 * SCALE;
snake.ySpeed = 0;
break;
case "Right":
snake.xSpeed = 1 * SCALE;
snake.ySpeed = 0;
break;
}
},
move: (xDist, yDist) => {
if (snake.cells[0].x == food.position.x && snake.cells[0].y == food.position.y) {
snake.eat();
snake.shiftCells( snake.length - 1 )
snake.moveFirstCell( xDist, yDist )
}
else {
snake.shiftCells( snake.length )
snake.moveFirstCell( xDist, yDist )
}
},
moveFirstCell: ( xDist, yDist ) => {
// x-position
if (snake.cells[0].x + xDist < 0) {
snake.cells[0].x = WIDTH;
} else if (snake.cells[0].x + xDist > WIDTH) {
snake.cells[0].x = 0;
} else {
snake.cells[0].x += xDist;
}
// y-position
if (snake.cells[0].y + yDist < 0) {
snake.cells[0].y = HEIGHT;
} else if (snake.cells[0].y + yDist > HEIGHT) {
snake.cells[0].y = 0;
} else {
snake.cells[0].y += yDist;
}
},
shiftCells: length => {
for (let i = length - 1; i >= 1; i--) {
snake.cells[i].x = snake.cells[i - 1].x;
snake.cells[i].y = snake.cells[i - 1].y;
}
},
eat: () => {
snake.grow();
food.createFood();
},
grow: () => {
snake.cells.push({
x: snake.cells[snake.length - 1].x,
y: snake.cells[snake.length - 1].y,
});
snake.length++;
},
};
let food = {
position: {
x: 0,
y: 0,
},
createFood: () => {
food.position.x = SCALE * Math.floor(Math.random() * (WIDTH / SCALE));
food.position.y = SCALE * Math.floor(Math.random() * (HEIGHT / SCALE));
},
drawFood: () => {
context.fillStyle = "#D3FCD5";
context.fillRect(food.position.x, food.position.y, SCALE, SCALE);
},
};
setup();
Upvotes: 1