Code viewer for World: Basic Snake
// 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++;
    }
}