Pixel Eight - Game Logic
This post is part of a series creating a snake game on the Pixel Kit using the pixel-eight library, the previous posts were an introduction and about adding interactivity.
Today we’re going to build out the logic of the game - to make it into an actual game.
The update
function is going to get quite a lot more logic, so we’ll do a bit of re-factoring, first we pull out the code to update the snake’s direction into a function:
const updateDirection = ({ dx, dy }, clicked) => {
if (clicked.up) {
dy = -1;
dx = 0;
}
if (clicked.down) {
dy = +1;
dx = 0;
}
if (clicked.left) {
dx = -1;
dy = 0;
}
if (clicked.right) {
dx = 1;
dy = 0;
}
return { dx, dy };
};
We can pull out the initial state into a variable that can be re-used:
const initialState = { dx: 0, dy: 0, x: 7, y: 3 };
start({
frameRate: 300,
init: () => {
return initialState;
},
update: (state, { clicked }) => {
const { dy, dx } = updateDirection(state, clicked);
const x = state.x + dx;
const y = state.y + dy;
// reset if we hit the edge of the screen
if (x < 0 || x >= 16 || y < 0 || y >= 8) {
return initialState;
}
return { ...state, x, y, dy, dx };
},
draw: (frame, { x, y }) => {
frame.cls();
frame.pset(x, y, color.yellow);
}
});
The food for the snake will be placed randomly around the screen, we can write a function to create it:
const createFood = () => {
return {
x: Math.floor(Math.random() * 16),
y: Math.floor(Math.random() * 8)
};
};
Since we will want to initially have some food in the game state we can pull the init out to a function:
const init = () => {
return { ...initialState, food: createFood() };
};
We can then modify our draw function to draw the food:
start({
frameRate: 300,
init,
update: (state, { clicked }) => {
...
},
draw: (frame, { x, y, food }) => {
frame.cls();
frame.pset(x, y, color.yellow);
frame.pset(food.x, food.y, color.green);
}
});
When the snake eats the food its length increases. Rather than representing the snake as a single point (x,y) we need to represent it as an array of points reflecting the snakes length.
const initialState = { dx: 0, dy: 0, snake: [{x: 7, y: 3}] };
...
start({
...
update: (state, { clicked }) => {
...
let food = state.food;
// add the new point to the head of the snake
const snake = [...state.snake, { x, y }];
if (x === food.x && y === food.y) {
// we hit the food so create a new one
food = createFood();
} else {
// we missed the food so remove the end of the tail
snake.shift();
}
return { ...state, snake, dy, dx, food };
},
draw: (frame, { snake, food }) => {
frame.cls();
// draw each part of the snake
snake.forEach(({ x, y }) => {
frame.pset(x, y, color.yellow);
});
frame.pset(food.x, food.y, color.green);
}
});
Finally we need to add collision detection so that the snake dies when it tries to eat itself.
const snakeHit = (snake, hx, hy) => {
return snake.reduce((hit, { x, y }) => {
return hit || (x === hx && y === hy);
}, false);
};
...
start({
update: (state, { clicked }) => {
...
// if the snake's moving check if it's eating itself
if ((dx !== 0 || dy !== 0) && snakeHit(state.snake, x, y)) {
return init();
}
...
},
...
});
In the next post will cover adding a splash screen and displaying the score.