'Svelte snake game not deleting snakes properly
I've been trying to make a snake game in svelte and I can't figure out why it doesn't properly remember previous snake positions and delete them, instead it deletes the current one.
<script lang="ts">
import { onMount } from 'svelte';
// variable declarations
let gridSize = 20;
type Cell = 'empty' | 'snake' | 'food';
let grid : Cell[][] = [...Array(gridSize)].map(() => [...Array(gridSize)].map(() => "empty"));
let snakePos: Array<[number, number]> = [[12, 13]];
grid[5][10] = 'food';
let snakes = [];
let length = 1;
let xy = 0;
let dir = 1;
// functions and onmount
function handleKeydown(e : any) {
switch(e.keyCode){
case 87:
xy = 0;
dir = -1;
break;
case 65:
xy = 1;
dir = -1;
break;
case 83:
xy = 0;
dir = 1;
break;
case 68:
xy = 1;
dir = 1;
break;
default:
break;
}
}
onMount(() => {
setInterval(() => {
snakes.unshift(snakePos[0]);
snakePos[0][xy] += dir;
grid[snakePos[0][0]][snakePos[0][1]] = 'snake';
if(snakes.length > length+1)
{
let delSnake = snakes.pop();
grid[delSnake[0]][delSnake[1]] = 'empty';
}
}, 500);
});
</script>
<svelte:window on:keydown={handleKeydown}/>
<main>
{#each grid as row, i}
<div class="row">
{#each row as cell, k}
<div class={`square ${cell}`} />
{/each}
</div>
{/each}
</main>
I've tried adding snakePos to the snakes array, but it turns out that even after changing the snakePos:
snakePos[0][xy] += dir;
when I check, snakePos is the same as before(checked with if(===)).
If I remove the line where I set the last snake to empty, the snake moves normally but with infinite length. Also, I haven't yet added the ability to eat food or lose, I'll add it after I figure out how to limit the length of the snake(this bug).
Solution 1:[1]
It looks like you've got your positions array kind of mixed up.
This is an array of array:
let snakePos: Array<[number, number]> = [[12, 13]];
I suppose you want a "tupple" of X, Y position instead, said otherwise an array of length 2:
let snakePos: [number, number] = [12, 13];
Following this change, in your interval function:
// snakePos[0][xy] += dir;
// =>
snakePos[xy] += dir;
// grid[snakePos[0][0]][snakePos[0][1]] = 'snake';
// =>
grid[snakePos[0]][snakePos[1]] = 'snake';
This will make your snake move.
I'm not sure what you're trying to do in the lines that follow. ¯\_(?)_/¯
if (snakes.length > length + 1) {
let delSnake = snakes.pop();
grid[delSnake[0]][delSnake[1]] = 'empty';
}
The assignment seems pretty regular, but you should probably ensure that delSnake is not undefined.
A last word, you should use the cleanup function of onMount to clear your interval:
onMount(() => {
const interval = setInterval(() => {
...
}, 500)
return () => {
clearInterval(interval)
}
})
Short of that, the interval will continue to fire after the component is destroyed, generally with disastrous effects.
If you think you don't care about destroying the component for now (but you should care), do it for HMR. HMR destroys and recreates the components on changes... If you leak a forever running interval each time, it will become a mess!
Answering your comment...
What I tried to do with that if statement is, keep previous snake positions and the length of the snake, and remove the snake from the board if the array length(snakes) is more than how long the snake is supposed to be.
Oh, I see. Then I think you were right with your array of positions for the whole snake. What seems to be the source of confusion is the snakePos variable that you don't need because all this information is in the whole snake.
So, rather than my first advice, I suggest:
// get rid of this
// let snakePos: Array<[number, number]> = [[12, 13]];
// keep only this one -- whole snake
let snakes: Array<[number, number]> = [[12, 13]];
The rest can be much more clearly done with local variables in the update function:
const interval = setInterval(() => {
const lastPos = snakes[0];
// clone lastPos somehow, or the line after will mutate lastPos,
// that is snakes[0]
const nextPos = [...lastPos] as [number, number];
nextPos[xy] += dir;
snakes.unshift(nextPos);
grid[nextPos[0]][nextPos[1]] = 'snake';
if (snakes.length > length + 1) {
let delSnake = snakes.pop();
grid[delSnake[0]][delSnake[1]] = 'empty';
}
}, 500);
Note that Svelte does not see the changes to the snakes array (i.e. snakes.unshift(nextPos) and snakes.pop()), because they are not assignment.
The above code works and the view gets updated because there are actual assignments to the grid variable (e.g. grid[nextPos[0]][nextPos[1]] = 'snake') that Svelte does track, and that's what is used in your current markup.
If you ever need the view to react to this kind of changes to the snakes variable, you must also signal to Svelte that it has changed with an actual self assignment, like this:
snakes.pop()
snakes = snakes
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
