Snake: Survival of the Fittest Edition (Final)

Introduction

Snake: Survival of the Fittest Edition adds a modern twist to the classic snake game you know and love. This game pits you, the player, against a computer-controlled snake in a battle. As you maneuver to collect food and grow in size, you’ll need to avoid obstacles, strategize your movements, and ensure that you don’t become the prey.

 

Code Overview

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let scl; // Grid scale (calculated dynamically)
let cols, rows;
let playerSnake, computerSnake;
let food;
let obstacles = [];
let obstacleTimer = 0; // Timer to track when to change obstacles
let obstacleInterval = 300; // Minimum frames before obstacles relocate
let help = "Press 'f' (possibly twice) to toggle fullscreen";
let gameState = 'idle'; // 'idle', 'playing', or 'gameOver' // NEW CODE
let hasEnded = false;
function setup() {
createCanvas(windowWidth, windowHeight);
frameRate(10); // Control the game speed
print(help);
updateGridDimensions(); // Calculate grid dimensions and scale
// Initialize snakes
playerSnake = new Snake(color(0, 0, 255)); // Blue snake for the player
computerSnake = new Snake(color(255, 0, 0)); // Red snake for the computer
computerSnake.chaseState = "food"; // Initial chase state
// Place food and obstacles
placeFood();
placeObstacles();
}
function draw() {
if (gameState === 'idle') {
drawStartScreen(); // NEW CODE
} else if (gameState === 'playing') {
runGame(); // NEW CODE
} else if (gameState === 'gameOver') {
drawGameOverScreen(); // NEW CODE
}
}
function runGame() {
background(220);
// Update obstacles at random intervals
if (frameCount > obstacleTimer + obstacleInterval) {
placeObstacles();
obstacleTimer = frameCount; // Reset the timer
}
// Draw the grid
drawGrid();
// Update the chase state
if (computerSnake.body.length > 0 && playerSnake.body.length > 0) {
if (computerSnake.len >= playerSnake.len * 3) {
computerSnake.chaseState = "user"; // Switch to chasing the user
} else {
computerSnake.chaseState = "food"; // Chase food
}
}
// Update and show the snakes
if (playerSnake.body.length > 0) {
playerSnake.update();
playerSnake.show();
}
if (computerSnake.body.length > 0) {
computerSnake.update();
computerSnake.show();
}
// Handle computer snake logic
if (computerSnake.body.length > 0 && computerSnake.chaseState === "food") {
computerSnake.chase(food); // Chase food
} else if (
computerSnake.body.length > 0 &&
playerSnake.body.length > 0 &&
computerSnake.chaseState === "user"
) {
computerSnake.chase(playerSnake.body[playerSnake.body.length - 1]); // Chase player's head
if (snakeCollidesWithSnake(computerSnake, playerSnake)) {
playerSnake.body.shift(); // Remove one cell from the player's snake
playerSnake.len--; // Decrease player's snake length
// NEW CODE: Check if player snake is engulfed completely
if (playerSnake.len <= 0) {
gameState = 'gameOver';
drawGameOverScreen();
fill(255, 255, 0);
textSize(32);
textAlign(CENTER, CENTER);
text("YOU WERE ENGULFED BY THE COMPUTER SNAKE!", width / 2, (2 * height) / 3);
return; // Exit the function immediately
}
}
}
// Check if the player snake eats the food
if (playerSnake.body.length > 0 && playerSnake.eat(food)) {
placeFood();
}
// Check if the computer snake eats the food
if (computerSnake.body.length > 0 && computerSnake.eat(food)) {
placeFood();
}
// Check for collisions with obstacles (only for the player snake)
if (playerSnake.body.length > 0 && snakeCollidesWithObstacles(playerSnake)) {
gameState = 'gameOver'; // NEW CODE: End the game
drawGameOverScreen();
return; // Exit the function immediately
}
// Draw the food
fill(0, 255, 0);
rect(food.x * scl, food.y * scl, scl, scl);
// Draw the obstacles
fill(100); // Gray obstacles
for (let obs of obstacles) {
rect(obs.x * scl, obs.y * scl, scl, scl);
}
// Check for game over conditions
if (playerSnake.body.length > 0 && playerSnake.isDead()) {
gameState = 'gameOver';
drawGameOverScreen();
return;
}
if (computerSnake.body.length > 0 && computerSnake.isDead()) {
gameState = 'gameOver';
drawGameOverScreen();
return;
}
}
function drawStartScreen() { // UPDATED FUNCTION
background(0);
fill(255, 255, 0); // Bright yellow color for arcade-like feel
textFont('monospace'); // Arcade-style font
textSize(48); // Large text size
textAlign(CENTER, CENTER);
text("SNAKE: SURVIVAL OF THE FITTEST", width / 2, height / 3);
fill(0, 255, 0); // Green color for instructions
textSize(24);
text("PRESS SPACE TO START", width / 2, height / 2);
fill(255, 0, 0); // Red color for help
textSize(18);
text(help, width / 2, (2 * height) / 3);
}
function drawGameOverScreen() { // UPDATED FUNCTION
background(0);
fill(255, 0, 0); // Red for game over message
textFont('monospace'); // Arcade-style font
textSize(48);
textAlign(CENTER, CENTER);
text("GAME OVER!", width / 2, height / 3);
fill(0, 255, 0); // Green for restart instructions
textSize(24);
text("PRESS SPACE TO RESTART", width / 2, height / 2);
noLoop(); // Stop the game loop
}
function updateGridDimensions() {
// Set desired number of columns and rows
cols = 40;
rows = 30;
// Calculate scale to fit the window size
scl = min(floor(windowWidth / cols), floor(windowHeight / rows));
// Adjust cols and rows to fill the screen exactly
cols = floor(windowWidth / scl);
rows = floor(windowHeight / scl);
// Resize the canvas to match the new grid
resizeCanvas(cols * scl, rows * scl);
}
function windowResized() {
// Update grid and canvas dimensions when the window is resized
updateGridDimensions();
// Reposition snakes, food, and obstacles to ensure they are within the new bounds
playerSnake.reposition();
computerSnake.reposition();
placeFood();
placeObstacles();
}
function resetGame() { // NEW FUNCTION
playerSnake = new Snake(color(0, 0, 255)); // Reset player snake
computerSnake = new Snake(color(255, 0, 0)); // Reset computer snake
computerSnake.chaseState = "food"; // Reset chase state
placeFood();
placeObstacles();
loop(); // Start the game loop // FIX
}
function keyTyped() {
if (key === 'f') {
toggleFullscreen(); // Toggle fullscreen mode
}
}
function toggleFullscreen() {
let fs = fullscreen();
fullscreen(!fs); // Flip fullscreen state
}
function keyPressed() {
if (gameState === 'idle' && key === ' ') { // NEW CODE
gameState = 'playing';
resetGame(); // NEW CODE
} else if (gameState === 'gameOver' && key === ' ') { // NEW CODE
gameState = 'playing';
resetGame(); // NEW CODE
} else if (gameState === 'playing') { // NEW CONDITION
switch (keyCode) {
case UP_ARROW:
if (playerSnake.ydir !== 1) playerSnake.setDir(0, -1);
break;
case DOWN_ARROW:
if (playerSnake.ydir !== -1) playerSnake.setDir(0, 1);
break;
case LEFT_ARROW:
if (playerSnake.xdir !== 1) playerSnake.setDir(-1, 0);
break;
case RIGHT_ARROW:
if (playerSnake.xdir !== -1) playerSnake.setDir(1, 0);
break;
}
}
}
function drawGrid() {
stroke(200);
for (let i = 0; i <= cols; i++) {
line(i * scl, 0, i * scl, rows * scl);
}
for (let j = 0; j <= rows; j++) {
line(0, j * scl, cols * scl, j * scl);
}
}
function placeFood() {
// Place food at a random position, avoiding obstacles and snakes
food = createVector(
floor(random(1, cols - 1)), // Avoid edges
floor(random(1, rows - 1))
);
// Ensure food does not overlap with obstacles or snakes
while (
obstacles.some(obs => obs.x === food.x && obs.y === food.y) ||
playerSnake.body.some(part => part.x === food.x && part.y === food.y) ||
computerSnake.body.some(part => part.x === food.x && part.y === food.y)
) {
food = createVector(
floor(random(1, cols - 1)),
floor(random(1, rows - 1))
);
}
}
function placeObstacles() {
// Place random obstacles
obstacles = []; // Clear existing obstacles
let numObstacles = floor(random(4, 9)); // Random number of obstacles between 4 and 8
for (let i = 0; i < numObstacles; i++) {
let obs = createVector(floor(random(cols)), floor(random(rows)));
// Ensure obstacles do not overlap with food or snakes
while (
(food && food.x === obs.x && food.y === obs.y) ||
playerSnake.body.some(part => part.x === obs.x && part.y === obs.y) ||
computerSnake.body.some(part => part.x === obs.x && part.y === obs.y)
) {
obs = createVector(floor(random(cols)), floor(random(rows)));
}
obstacles.push(obs);
}
}
function snakeCollidesWithObstacles(snake) {
// Check if the snake's head collides with any obstacle
if (snake === playerSnake) {
let head = snake.body[snake.body.length - 1];
return obstacles.some(obs => head.x === obs.x && head.y === obs.y);
}
return false; // Obstacles do not affect the computer snake
}
function snakeCollidesWithSnake(snake1, snake2) {
// Check if snake1's head collides with any part of snake2
let head1 = snake1.body[snake1.body.length - 1];
return snake2.body.some(part => part.x === head1.x && part.y === head1.y);
}
function endGame(message) {
// End the game and display a message
noLoop();
fill(0); // Black color for text
textSize(32);
textAlign(CENTER, CENTER);
text(message, width / 2, height / 2);
}
class Snake {
constructor(snakeColor) {
// Initialize snake properties
this.body = [createVector(floor(cols / 2), floor(rows / 2))];
this.xdir = 0;
this.ydir = 0;
this.len = 1;
this.dead = false;
this.snakeColor = snakeColor;
}
setDir(x, y) {
// Set snake's movement direction
this.xdir = x;
this.ydir = y;
}
update() {
// Update snake's position
let head = this.body[this.body.length - 1].copy();
head.x += this.xdir;
head.y += this.ydir;
// Check for wall collision
if (head.x < 0 || head.x >= cols || head.y < 0 || head.y >= rows) {
this.dead = true;
}
this.body.push(head);
// Remove the tail if the snake has not grown
if (this.body.length > this.len) {
this.body.shift();
}
}
eat(pos) {
// Check if the snake's head is at the same position as the food
let head = this.body[this.body.length - 1];
if (head.x === pos.x && head.y === pos.y) {
this.len++;
return true;
}
return false;
}
isDead() {
// Check if the snake runs into itself
let head = this.body[this.body.length - 1];
for (let i = 0; i < this.body.length - 1; i++) {
let part = this.body[i];
if (part.x === head.x && part.y === head.y) {
return true;
}
}
return this.dead;
}
show() {
// Display the snake on the canvas
fill(this.snakeColor);
for (let part of this.body) {
rect(part.x * scl, part.y * scl, scl, scl);
}
}
chase(target) {
// Chase a target (food or player's head)
let head = this.body[this.body.length - 1];
if (target.x > head.x && this.xdir !== -1) {
this.setDir(1, 0); // Move right
} else if (target.x < head.x && this.xdir !== 1) {
this.setDir(-1, 0); // Move left
} else if (target.y > head.y && this.ydir !== -1) {
this.setDir(0, 1); // Move down
} else if (target.y < head.y && this.ydir !== 1) {
this.setDir(0, -1); // Move up
}
}
reposition() {
// Reposition the snake if the window is resized
for (let part of this.body) {
part.x = constrain(part.x, 0, cols - 1);
part.y = constrain(part.y, 0, rows - 1);
}
}
}
let scl; // Grid scale (calculated dynamically) let cols, rows; let playerSnake, computerSnake; let food; let obstacles = []; let obstacleTimer = 0; // Timer to track when to change obstacles let obstacleInterval = 300; // Minimum frames before obstacles relocate let help = "Press 'f' (possibly twice) to toggle fullscreen"; let gameState = 'idle'; // 'idle', 'playing', or 'gameOver' // NEW CODE let hasEnded = false; function setup() { createCanvas(windowWidth, windowHeight); frameRate(10); // Control the game speed print(help); updateGridDimensions(); // Calculate grid dimensions and scale // Initialize snakes playerSnake = new Snake(color(0, 0, 255)); // Blue snake for the player computerSnake = new Snake(color(255, 0, 0)); // Red snake for the computer computerSnake.chaseState = "food"; // Initial chase state // Place food and obstacles placeFood(); placeObstacles(); } function draw() { if (gameState === 'idle') { drawStartScreen(); // NEW CODE } else if (gameState === 'playing') { runGame(); // NEW CODE } else if (gameState === 'gameOver') { drawGameOverScreen(); // NEW CODE } } function runGame() { background(220); // Update obstacles at random intervals if (frameCount > obstacleTimer + obstacleInterval) { placeObstacles(); obstacleTimer = frameCount; // Reset the timer } // Draw the grid drawGrid(); // Update the chase state if (computerSnake.body.length > 0 && playerSnake.body.length > 0) { if (computerSnake.len >= playerSnake.len * 3) { computerSnake.chaseState = "user"; // Switch to chasing the user } else { computerSnake.chaseState = "food"; // Chase food } } // Update and show the snakes if (playerSnake.body.length > 0) { playerSnake.update(); playerSnake.show(); } if (computerSnake.body.length > 0) { computerSnake.update(); computerSnake.show(); } // Handle computer snake logic if (computerSnake.body.length > 0 && computerSnake.chaseState === "food") { computerSnake.chase(food); // Chase food } else if ( computerSnake.body.length > 0 && playerSnake.body.length > 0 && computerSnake.chaseState === "user" ) { computerSnake.chase(playerSnake.body[playerSnake.body.length - 1]); // Chase player's head if (snakeCollidesWithSnake(computerSnake, playerSnake)) { playerSnake.body.shift(); // Remove one cell from the player's snake playerSnake.len--; // Decrease player's snake length // NEW CODE: Check if player snake is engulfed completely if (playerSnake.len <= 0) { gameState = 'gameOver'; drawGameOverScreen(); fill(255, 255, 0); textSize(32); textAlign(CENTER, CENTER); text("YOU WERE ENGULFED BY THE COMPUTER SNAKE!", width / 2, (2 * height) / 3); return; // Exit the function immediately } } } // Check if the player snake eats the food if (playerSnake.body.length > 0 && playerSnake.eat(food)) { placeFood(); } // Check if the computer snake eats the food if (computerSnake.body.length > 0 && computerSnake.eat(food)) { placeFood(); } // Check for collisions with obstacles (only for the player snake) if (playerSnake.body.length > 0 && snakeCollidesWithObstacles(playerSnake)) { gameState = 'gameOver'; // NEW CODE: End the game drawGameOverScreen(); return; // Exit the function immediately } // Draw the food fill(0, 255, 0); rect(food.x * scl, food.y * scl, scl, scl); // Draw the obstacles fill(100); // Gray obstacles for (let obs of obstacles) { rect(obs.x * scl, obs.y * scl, scl, scl); } // Check for game over conditions if (playerSnake.body.length > 0 && playerSnake.isDead()) { gameState = 'gameOver'; drawGameOverScreen(); return; } if (computerSnake.body.length > 0 && computerSnake.isDead()) { gameState = 'gameOver'; drawGameOverScreen(); return; } } function drawStartScreen() { // UPDATED FUNCTION background(0); fill(255, 255, 0); // Bright yellow color for arcade-like feel textFont('monospace'); // Arcade-style font textSize(48); // Large text size textAlign(CENTER, CENTER); text("SNAKE: SURVIVAL OF THE FITTEST", width / 2, height / 3); fill(0, 255, 0); // Green color for instructions textSize(24); text("PRESS SPACE TO START", width / 2, height / 2); fill(255, 0, 0); // Red color for help textSize(18); text(help, width / 2, (2 * height) / 3); } function drawGameOverScreen() { // UPDATED FUNCTION background(0); fill(255, 0, 0); // Red for game over message textFont('monospace'); // Arcade-style font textSize(48); textAlign(CENTER, CENTER); text("GAME OVER!", width / 2, height / 3); fill(0, 255, 0); // Green for restart instructions textSize(24); text("PRESS SPACE TO RESTART", width / 2, height / 2); noLoop(); // Stop the game loop } function updateGridDimensions() { // Set desired number of columns and rows cols = 40; rows = 30; // Calculate scale to fit the window size scl = min(floor(windowWidth / cols), floor(windowHeight / rows)); // Adjust cols and rows to fill the screen exactly cols = floor(windowWidth / scl); rows = floor(windowHeight / scl); // Resize the canvas to match the new grid resizeCanvas(cols * scl, rows * scl); } function windowResized() { // Update grid and canvas dimensions when the window is resized updateGridDimensions(); // Reposition snakes, food, and obstacles to ensure they are within the new bounds playerSnake.reposition(); computerSnake.reposition(); placeFood(); placeObstacles(); } function resetGame() { // NEW FUNCTION playerSnake = new Snake(color(0, 0, 255)); // Reset player snake computerSnake = new Snake(color(255, 0, 0)); // Reset computer snake computerSnake.chaseState = "food"; // Reset chase state placeFood(); placeObstacles(); loop(); // Start the game loop // FIX } function keyTyped() { if (key === 'f') { toggleFullscreen(); // Toggle fullscreen mode } } function toggleFullscreen() { let fs = fullscreen(); fullscreen(!fs); // Flip fullscreen state } function keyPressed() { if (gameState === 'idle' && key === ' ') { // NEW CODE gameState = 'playing'; resetGame(); // NEW CODE } else if (gameState === 'gameOver' && key === ' ') { // NEW CODE gameState = 'playing'; resetGame(); // NEW CODE } else if (gameState === 'playing') { // NEW CONDITION switch (keyCode) { case UP_ARROW: if (playerSnake.ydir !== 1) playerSnake.setDir(0, -1); break; case DOWN_ARROW: if (playerSnake.ydir !== -1) playerSnake.setDir(0, 1); break; case LEFT_ARROW: if (playerSnake.xdir !== 1) playerSnake.setDir(-1, 0); break; case RIGHT_ARROW: if (playerSnake.xdir !== -1) playerSnake.setDir(1, 0); break; } } } function drawGrid() { stroke(200); for (let i = 0; i <= cols; i++) { line(i * scl, 0, i * scl, rows * scl); } for (let j = 0; j <= rows; j++) { line(0, j * scl, cols * scl, j * scl); } } function placeFood() { // Place food at a random position, avoiding obstacles and snakes food = createVector( floor(random(1, cols - 1)), // Avoid edges floor(random(1, rows - 1)) ); // Ensure food does not overlap with obstacles or snakes while ( obstacles.some(obs => obs.x === food.x && obs.y === food.y) || playerSnake.body.some(part => part.x === food.x && part.y === food.y) || computerSnake.body.some(part => part.x === food.x && part.y === food.y) ) { food = createVector( floor(random(1, cols - 1)), floor(random(1, rows - 1)) ); } } function placeObstacles() { // Place random obstacles obstacles = []; // Clear existing obstacles let numObstacles = floor(random(4, 9)); // Random number of obstacles between 4 and 8 for (let i = 0; i < numObstacles; i++) { let obs = createVector(floor(random(cols)), floor(random(rows))); // Ensure obstacles do not overlap with food or snakes while ( (food && food.x === obs.x && food.y === obs.y) || playerSnake.body.some(part => part.x === obs.x && part.y === obs.y) || computerSnake.body.some(part => part.x === obs.x && part.y === obs.y) ) { obs = createVector(floor(random(cols)), floor(random(rows))); } obstacles.push(obs); } } function snakeCollidesWithObstacles(snake) { // Check if the snake's head collides with any obstacle if (snake === playerSnake) { let head = snake.body[snake.body.length - 1]; return obstacles.some(obs => head.x === obs.x && head.y === obs.y); } return false; // Obstacles do not affect the computer snake } function snakeCollidesWithSnake(snake1, snake2) { // Check if snake1's head collides with any part of snake2 let head1 = snake1.body[snake1.body.length - 1]; return snake2.body.some(part => part.x === head1.x && part.y === head1.y); } function endGame(message) { // End the game and display a message noLoop(); fill(0); // Black color for text textSize(32); textAlign(CENTER, CENTER); text(message, width / 2, height / 2); } class Snake { constructor(snakeColor) { // Initialize snake properties this.body = [createVector(floor(cols / 2), floor(rows / 2))]; this.xdir = 0; this.ydir = 0; this.len = 1; this.dead = false; this.snakeColor = snakeColor; } setDir(x, y) { // Set snake's movement direction this.xdir = x; this.ydir = y; } update() { // Update snake's position let head = this.body[this.body.length - 1].copy(); head.x += this.xdir; head.y += this.ydir; // Check for wall collision if (head.x < 0 || head.x >= cols || head.y < 0 || head.y >= rows) { this.dead = true; } this.body.push(head); // Remove the tail if the snake has not grown if (this.body.length > this.len) { this.body.shift(); } } eat(pos) { // Check if the snake's head is at the same position as the food let head = this.body[this.body.length - 1]; if (head.x === pos.x && head.y === pos.y) { this.len++; return true; } return false; } isDead() { // Check if the snake runs into itself let head = this.body[this.body.length - 1]; for (let i = 0; i < this.body.length - 1; i++) { let part = this.body[i]; if (part.x === head.x && part.y === head.y) { return true; } } return this.dead; } show() { // Display the snake on the canvas fill(this.snakeColor); for (let part of this.body) { rect(part.x * scl, part.y * scl, scl, scl); } } chase(target) { // Chase a target (food or player's head) let head = this.body[this.body.length - 1]; if (target.x > head.x && this.xdir !== -1) { this.setDir(1, 0); // Move right } else if (target.x < head.x && this.xdir !== 1) { this.setDir(-1, 0); // Move left } else if (target.y > head.y && this.ydir !== -1) { this.setDir(0, 1); // Move down } else if (target.y < head.y && this.ydir !== 1) { this.setDir(0, -1); // Move up } } reposition() { // Reposition the snake if the window is resized for (let part of this.body) { part.x = constrain(part.x, 0, cols - 1); part.y = constrain(part.y, 0, rows - 1); } } }
let scl; // Grid scale (calculated dynamically)
let cols, rows;
let playerSnake, computerSnake;
let food;
let obstacles = [];
let obstacleTimer = 0; // Timer to track when to change obstacles
let obstacleInterval = 300; // Minimum frames before obstacles relocate
let help = "Press 'f' (possibly twice) to toggle fullscreen";
let gameState = 'idle'; // 'idle', 'playing', or 'gameOver' // NEW CODE
let hasEnded = false;

function setup() {
  createCanvas(windowWidth, windowHeight);
  frameRate(10); // Control the game speed
  print(help);

  updateGridDimensions(); // Calculate grid dimensions and scale

  // Initialize snakes
  playerSnake = new Snake(color(0, 0, 255)); // Blue snake for the player
  computerSnake = new Snake(color(255, 0, 0)); // Red snake for the computer
  computerSnake.chaseState = "food"; // Initial chase state

  // Place food and obstacles
  placeFood();
  placeObstacles();
}

function draw() {
  if (gameState === 'idle') {
    drawStartScreen(); // NEW CODE
  } else if (gameState === 'playing') {
    runGame(); // NEW CODE
  } else if (gameState === 'gameOver') {
    drawGameOverScreen(); // NEW CODE
  }
}

function runGame() {
  background(220);

  // Update obstacles at random intervals
  if (frameCount > obstacleTimer + obstacleInterval) {
    placeObstacles();
    obstacleTimer = frameCount; // Reset the timer
  }

  // Draw the grid
  drawGrid();

  // Update the chase state
  if (computerSnake.body.length > 0 && playerSnake.body.length > 0) {
    if (computerSnake.len >= playerSnake.len * 3) {
      computerSnake.chaseState = "user"; // Switch to chasing the user
    } else {
      computerSnake.chaseState = "food"; // Chase food
    }
  }

  // Update and show the snakes
  if (playerSnake.body.length > 0) {
    playerSnake.update();
    playerSnake.show();
  }

  if (computerSnake.body.length > 0) {
    computerSnake.update();
    computerSnake.show();
  }

  // Handle computer snake logic
  if (computerSnake.body.length > 0 && computerSnake.chaseState === "food") {
    computerSnake.chase(food); // Chase food
  } else if (
    computerSnake.body.length > 0 &&
    playerSnake.body.length > 0 &&
    computerSnake.chaseState === "user"
  ) {
    computerSnake.chase(playerSnake.body[playerSnake.body.length - 1]); // Chase player's head

    if (snakeCollidesWithSnake(computerSnake, playerSnake)) {
      playerSnake.body.shift(); // Remove one cell from the player's snake
      playerSnake.len--; // Decrease player's snake length

      // NEW CODE: Check if player snake is engulfed completely
      if (playerSnake.len <= 0) {
        gameState = 'gameOver';
        drawGameOverScreen();
        fill(255, 255, 0);
        textSize(32);
        textAlign(CENTER, CENTER);
        text("YOU WERE ENGULFED BY THE COMPUTER SNAKE!", width / 2, (2 * height) / 3);
        return; // Exit the function immediately
      }
    }
  }

  // Check if the player snake eats the food
  if (playerSnake.body.length > 0 && playerSnake.eat(food)) {
    placeFood();
  }

  // Check if the computer snake eats the food
  if (computerSnake.body.length > 0 && computerSnake.eat(food)) {
    placeFood();
  }

  // Check for collisions with obstacles (only for the player snake)
  if (playerSnake.body.length > 0 && snakeCollidesWithObstacles(playerSnake)) {
    gameState = 'gameOver'; // NEW CODE: End the game
    drawGameOverScreen();
    return; // Exit the function immediately
  }

  // Draw the food
  fill(0, 255, 0);
  rect(food.x * scl, food.y * scl, scl, scl);

  // Draw the obstacles
  fill(100); // Gray obstacles
  for (let obs of obstacles) {
    rect(obs.x * scl, obs.y * scl, scl, scl);
  }

  // Check for game over conditions
  if (playerSnake.body.length > 0 && playerSnake.isDead()) {
    gameState = 'gameOver';
    drawGameOverScreen();
    return;
  }

  if (computerSnake.body.length > 0 && computerSnake.isDead()) {
    gameState = 'gameOver';
    drawGameOverScreen();
    return;
  }
}


function drawStartScreen() { // UPDATED FUNCTION
  background(0);
  fill(255, 255, 0); // Bright yellow color for arcade-like feel
  textFont('monospace'); // Arcade-style font
  textSize(48); // Large text size
  textAlign(CENTER, CENTER);
  text("SNAKE: SURVIVAL OF THE FITTEST", width / 2, height / 3);
  fill(0, 255, 0); // Green color for instructions
  textSize(24);
  text("PRESS SPACE TO START", width / 2, height / 2);
  fill(255, 0, 0); // Red color for help
  textSize(18);
  text(help, width / 2, (2 * height) / 3);
}

function drawGameOverScreen() { // UPDATED FUNCTION
  background(0);
  fill(255, 0, 0); // Red for game over message
  textFont('monospace'); // Arcade-style font
  textSize(48);
  textAlign(CENTER, CENTER);
  text("GAME OVER!", width / 2, height / 3);
  fill(0, 255, 0); // Green for restart instructions
  textSize(24);
  text("PRESS SPACE TO RESTART", width / 2, height / 2);
  noLoop(); // Stop the game loop
}



function updateGridDimensions() {
  // Set desired number of columns and rows
  cols = 40;
  rows = 30;

  // Calculate scale to fit the window size
  scl = min(floor(windowWidth / cols), floor(windowHeight / rows));

  // Adjust cols and rows to fill the screen exactly
  cols = floor(windowWidth / scl);
  rows = floor(windowHeight / scl);

  // Resize the canvas to match the new grid
  resizeCanvas(cols * scl, rows * scl);
}

function windowResized() {
  // Update grid and canvas dimensions when the window is resized
  updateGridDimensions();

  // Reposition snakes, food, and obstacles to ensure they are within the new bounds
  playerSnake.reposition();
  computerSnake.reposition();
  placeFood();
  placeObstacles();
}

function resetGame() { // NEW FUNCTION
  playerSnake = new Snake(color(0, 0, 255)); // Reset player snake
  computerSnake = new Snake(color(255, 0, 0)); // Reset computer snake
  computerSnake.chaseState = "food"; // Reset chase state
  placeFood();
  placeObstacles();
  loop(); // Start the game loop // FIX
}

function keyTyped() {
  if (key === 'f') {
    toggleFullscreen(); // Toggle fullscreen mode
  }
}

function toggleFullscreen() {
  let fs = fullscreen();
  fullscreen(!fs); // Flip fullscreen state
}
function keyPressed() {
  if (gameState === 'idle' && key === ' ') { // NEW CODE
    gameState = 'playing';
    resetGame(); // NEW CODE
  } else if (gameState === 'gameOver' && key === ' ') { // NEW CODE
    gameState = 'playing';
    resetGame(); // NEW CODE
  } else if (gameState === 'playing') { // NEW CONDITION
    switch (keyCode) {
      case UP_ARROW:
        if (playerSnake.ydir !== 1) playerSnake.setDir(0, -1);
        break;
      case DOWN_ARROW:
        if (playerSnake.ydir !== -1) playerSnake.setDir(0, 1);
        break;
      case LEFT_ARROW:
        if (playerSnake.xdir !== 1) playerSnake.setDir(-1, 0);
        break;
      case RIGHT_ARROW:
        if (playerSnake.xdir !== -1) playerSnake.setDir(1, 0);
        break;
    }
  }
}


function drawGrid() {
  stroke(200);
  for (let i = 0; i <= cols; i++) {
    line(i * scl, 0, i * scl, rows * scl);
  }
  for (let j = 0; j <= rows; j++) {
    line(0, j * scl, cols * scl, j * scl);
  }
}

function placeFood() {
  // Place food at a random position, avoiding obstacles and snakes
  food = createVector(
    floor(random(1, cols - 1)), // Avoid edges
    floor(random(1, rows - 1))
  );

  // Ensure food does not overlap with obstacles or snakes
  while (
    obstacles.some(obs => obs.x === food.x && obs.y === food.y) ||
    playerSnake.body.some(part => part.x === food.x && part.y === food.y) ||
    computerSnake.body.some(part => part.x === food.x && part.y === food.y)
  ) {
    food = createVector(
      floor(random(1, cols - 1)),
      floor(random(1, rows - 1))
    );
  }
}

function placeObstacles() {
  // Place random obstacles
  obstacles = []; // Clear existing obstacles
  let numObstacles = floor(random(4, 9)); // Random number of obstacles between 4 and 8

  for (let i = 0; i < numObstacles; i++) {
    let obs = createVector(floor(random(cols)), floor(random(rows)));

    // Ensure obstacles do not overlap with food or snakes
    while (
      (food && food.x === obs.x && food.y === obs.y) ||
      playerSnake.body.some(part => part.x === obs.x && part.y === obs.y) ||
      computerSnake.body.some(part => part.x === obs.x && part.y === obs.y)
    ) {
      obs = createVector(floor(random(cols)), floor(random(rows)));
    }
    obstacles.push(obs);
  }
}

function snakeCollidesWithObstacles(snake) {
  // Check if the snake's head collides with any obstacle
  if (snake === playerSnake) {
    let head = snake.body[snake.body.length - 1];
    return obstacles.some(obs => head.x === obs.x && head.y === obs.y);
  }
  return false; // Obstacles do not affect the computer snake
}

function snakeCollidesWithSnake(snake1, snake2) {
  // Check if snake1's head collides with any part of snake2
  let head1 = snake1.body[snake1.body.length - 1];
  return snake2.body.some(part => part.x === head1.x && part.y === head1.y);
}

function endGame(message) {
  // End the game and display a message
  noLoop();
  fill(0); // Black color for text
  textSize(32);
  textAlign(CENTER, CENTER);
  text(message, width / 2, height / 2);
}

class Snake {
  constructor(snakeColor) {
    // Initialize snake properties
    this.body = [createVector(floor(cols / 2), floor(rows / 2))];
    this.xdir = 0;
    this.ydir = 0;
    this.len = 1;
    this.dead = false;
    this.snakeColor = snakeColor;
  }

  setDir(x, y) {
    // Set snake's movement direction
    this.xdir = x;
    this.ydir = y;
  }

  update() {
    // Update snake's position
    let head = this.body[this.body.length - 1].copy();
    head.x += this.xdir;
    head.y += this.ydir;

    // Check for wall collision
    if (head.x < 0 || head.x >= cols || head.y < 0 || head.y >= rows) {
      this.dead = true;
    }

    this.body.push(head);

    // Remove the tail if the snake has not grown
    if (this.body.length > this.len) {
      this.body.shift();
    }
  }

  eat(pos) {
    // Check if the snake's head is at the same position as the food
    let head = this.body[this.body.length - 1];
    if (head.x === pos.x && head.y === pos.y) {
      this.len++;
      return true;
    }
    return false;
  }

  isDead() {
    // Check if the snake runs into itself
    let head = this.body[this.body.length - 1];
    for (let i = 0; i < this.body.length - 1; i++) {
      let part = this.body[i];
      if (part.x === head.x && part.y === head.y) {
        return true;
      }
    }
    return this.dead;
  }

  show() {
    // Display the snake on the canvas
    fill(this.snakeColor);
    for (let part of this.body) {
      rect(part.x * scl, part.y * scl, scl, scl);
    }
  }

  chase(target) {
    // Chase a target (food or player's head)
    let head = this.body[this.body.length - 1];
    if (target.x > head.x && this.xdir !== -1) {
      this.setDir(1, 0); // Move right
    } else if (target.x < head.x && this.xdir !== 1) {
      this.setDir(-1, 0); // Move left
    } else if (target.y > head.y && this.ydir !== -1) {
      this.setDir(0, 1); // Move down
    } else if (target.y < head.y && this.ydir !== 1) {
      this.setDir(0, -1); // Move up
    }
  }

  reposition() {
    // Reposition the snake if the window is resized
    for (let part of this.body) {
      part.x = constrain(part.x, 0, cols - 1);
      part.y = constrain(part.y, 0, rows - 1);
    }
  }
}

 

Here’s a high-level breakdown of code:

1. Game Structure

The game is divided into three primary states:

  1. Idle State: Displays the start screen with instructions and the game title.
  2. Playing State: Runs the game loop where the snakes move, food is consumed, and collisions are handled.
  3. Game Over State: Displays a game-over message and prompts the user to restart.

This is achieved using a game state variable, which transitions between ‘idle’, ‘playing’, and ‘gameover’.

2. Grid System

The game grid dynamically adjusts to fit the browser window. The grid is divided into cells, and all game elements (snakes, food, obstacles) are positioned on this grid. The scale of the grid (scl) is calculated based on the window size to ensure the game looks consistent on any screen.

3. Snakes

Both the player and computer-controlled snakes are objects created using the snake class. Each snake:

  • Has a body represented as an array of segments (each segment is a grid cell).
  • Moves in a specified direction and grows when it consumes food.
  • Can “die” if it collides with itself, obstacles, or the grid boundaries.

The computer snake is programmed to switch between two behaviors:

  • Chasing Food: Moves toward food on the grid.
  • Chasing the Player: Pursues the player’s snake when it is three times larger, adding a competitive element.
4. Game Logic

The core gameplay logic is handled in the runGame function, which:

  • Updates the positions of the snakes.
  • Checks for collisions (e.g., snake colliding with obstacles or itself).
  • Manages interactions, such as consuming food or one snake engulfing the other.
  • Adjusts the state of the game based on player or computer actions.
5. Visuals

The game’s visuals, including the grid, snakes, food, and obstacles, are drawn dynamically using p5.js functions like rect and line. To create an arcade-like feel:

  • The title screen and game-over messages use bold, vibrant colors and pixelated fonts.
  • The game background, snakes, and food are kept simple yet visually distinct for clarity.
6. Interaction

The player controls their snake using the arrow keys. Pressing the space bar transitions the game between the start screen and gameplay or restarts the game after it ends. Additionally, pressing ‘f’ toggles fullscreen mode for an immersive experience.

7. Obstacles

Randomly placed obstacles add complexity to the game. They are repositioned periodically to ensure dynamic gameplay. The player’s snake must avoid these obstacles, while the computer snake is immune to them, giving it an advantage.

User Testing

I got feedback to include an info text about the game details and also the user suggested to reduce the speed of the computer’s snake.

IMG_0104

Future Considerations

While the game is already engaging, there are plenty of opportunities for enhancement:

  1. Improved AI:
    • Introduce smarter pathfinding for the computer snake, like using the AI
  2. Multiplayer Support:
    • Allow two players to compete head-to-head on the same grid.
  3. Power-Ups:
    • Include items that provide temporary benefits, such as invincibility, speed boosts, or traps for the computer snake.
  4. Dynamic Environments:
    • Add levels with different grid layouts, moving obstacles, or shrinking safe zones.
  5. Scoring System and Leaderboards:
    • Introduce a scoring mechanism based on time survived or food consumed.
    • Display high scores for competitive play.
  6. Sound Effects and Music:
    • Add classic arcade sound effects for eating food, colliding, and game-over events.
    • Include background music that changes tempo based on the intensity of gameplay.

 

Leave a Reply

Your email address will not be published. Required fields are marked *