// Quick and messy implementation of the 1997 game Snake
// Created as a little exercise for learning JavaScript and p5.js
// Probably incomplete and I moved onto other stuff
var player;
var score = 0;
var food;
function setup() {
createCanvas(840, 600);
frameRate(10);
gridSize = 20;
player = new Snake();
food = new Food();
}
function draw() {
background(51);
food.show();
player.update();
player.show();
fill('white');
textSize(16);
text("Score: " + score, 10, 10, 100, 20);
}
function reset() {
score = 0;
player = new Snake();
food = new Food();
}
function keyPressed() {
switch (keyCode) {
case UP_ARROW:
player.changeDirection(0, -1);
break;
case DOWN_ARROW:
player.changeDirection(0, 1);
break;
case RIGHT_ARROW:
player.changeDirection(1, 0);
break;
case LEFT_ARROW:
player.changeDirection(-1, 0);
break;
}
}
function Snake() {
this.pos = createVector(240, 280);
this.direction = createVector(1, 0);
this.lastPos = [this.pos];
this.changeDirection = function(x, y) {
if (x !== 0 && this.direction.y === 0) {
// Horizontal movement is not allowed if the snake isn't currently travelling vertically
// This is to prevent the snake from turning around on itself and eating itself (buggy game over)
return;
}
if (y !== 0 && this.direction.x === 0) {
// Same logic as above but for vertical movement
// This doesn't solve all cases of the player eating themselves (mainly if they input fast)
// A solution like queuing up inputs to parse through one at a time may solve this
return;
}
this.direction = createVector(x, y);
}
this.update = function() {
this.pos.x += this.direction.x * gridSize;
this.pos.y += this.direction.y * gridSize;
let distance = p5.Vector.dist(this.pos, food.pos);
if (distance <= gridSize/2) {
food.eaten();
}
if (this.lastPos.length > score) {
this.lastPos.splice(0, 1);
}
// Modulo operator to screen wrap the snake once it hits the edges of the screen
// Looks strange because of how JS handles negative modulo https://dustinpfister.github.io/2017/09/02/js-whats-wrong-with-modulo/
this.pos.x = (this.pos.x % width + width) % width;
this.pos.y = (this.pos.y % height + height) % height;
// Check if overlapping with own body
for (let i = 0; i < this.lastPos.length; i++) {
let distance = p5.Vector.dist(this.pos, this.lastPos[i]);
if (distance <= gridSize / 2) {
// Rough game over state
reset();
}
// Also check if the food is inside any of the body parts while we're here
let foodDistance = p5.Vector.dist(food.pos, this.lastPos[i]);
if(foodDistance <= gridSize / 2)
{
// Should prevent the food from ever appearing inside an occupied space in the first place, but this will do for now
food.changePosition();
}
}
this.lastPos.push(this.pos.copy());
}
this.show = function() {
fill(0, 188, 227);
stroke(0, 130, 180);
strokeWeight(5);
for (let i = 0; i < this.lastPos.length; i++) {
rect(this.lastPos[i].x, this.lastPos[i].y, gridSize, gridSize);
}
noStroke();
}
}
function Food() {
this.pos = createVector(600, 280);
this.show = function() {
stroke(51);
strokeWeight(gridSize / 2);
ellipseMode(CORNER);
fill('red')
rect(this.pos.x, this.pos.y, gridSize, gridSize);
noStroke();
}
this.changePosition = function() {
newX = floor(random(0, width) / gridSize) * gridSize;
newY = floor(random(0, height) / gridSize) * gridSize;
this.pos = createVector(newX, newY);
}
this.eaten = function() {
this.changePosition();
score++;
}
}