Create the Classic Snake Game With Processing Library and Java

Here’s a hands-on tutorial to help you create your own version of the classic snake game with Processing and Java.

Published on Jul. 12, 2022
Image: Shutterstock / Built In
Image: Shutterstock / Built In
Brand Studio Logo

Do you know the basics of Java or Processing and want to start developing your own games or maybe just recreate one of the classics? This article describes the process of building the classic Snake game using Processing.   

How to Create the Classic Snake Game With Processing Library and Java

Processing is a graphical library and a development environment (IDE). It provides a graphical interface that can be used to draw different shapes and text. setup(), draw(), and keyPressed() are built-in Processing methods used to handle initialization, continuous behavior, and keypress events. ArrayList can be used to create a dynamically sized list and PVector specifies a vector, and provides methods to calculate the distance between vectors. The movement of the snake is created by looping the snakePositions in reversed order and assigning the position of each element to the position of the element before reaching the snake’s head which is moved towards the direction specified in snakeDirection. 

More From Our Software Engineering ExpertsCreate React App and TypeScript — A Quick How-To

 

1. What Is Processing?

Processing is a graphical library and integrated development environment (IDE). The library is built on Java and provides additional classes and simplifications. You can download and install Processing; make sure to download the installable matching your preferred platform.

 

Application Flow

Processing provides many methods to control the flow of an application, for example: control key events, initialization or continuous behavior. Two important methods to know to get started with Processing are the methods setup() and draw(). Another built-in method I’ll introduce later is keyPressed(). The behavior of the three methods are:

 

Graphical Interface

Processing also provides a graphical interface where the point (x, y) = (0, 0) is at the left top corner (see figure one below). Drawing shapes within Processing is very simple because the library provides primitive two-dimensional methods such as rect(x, y, w, h), circle(x, y, r) or line(x1, y1, x2, y2).

classic-snake-game-java
Figure 1: Display the direction of the horizontal axis (x) and vertical axis (y) within Processing’s graphical interface. | Image: Author

More on Gaming From Built InWhat Does the Future of Gaming Look Like?

 

2. Prepare to Create the Classic Snake Game

As with every project, it can be a great idea to establish a couple of requirements to set the direction and outline an overview of necessary features. Here are the requirements of the game, followed by a description of the code further below. 

 

Requirements

The requirements for the game are:

  1. The snake and objective are drawn using rectangles.
  2. The first rectangle of the snake represents the head of the snake and it is only the head that can trigger collision events with the objective and tail.
  3. When the snake reaches the screen’s border, it is teleported to the opposite position.
  4. The objective gets a new random position when it collides with the snake’s head.
  5. The game ends if the snake’s head collides with its tail.
  6. The score increases whenever the snake collides with the objective.
Java Snake Game | Bro Code

 

3. How to Code the Classic Snake the Game

To keep the article simple, the following section is divided into different steps of coding the game.

 

Property Data Types

Here is a short overview of the data types used to add properties to the game. Boolean and float are primitive data types; boolean refers to a value that can be either true or false, and float refers to a number that, unlike an integer, can specify a whole number and a decimal part. 

ArrayList<T> and PVector are composite data types. ArrayList<T> refers to a list of other data types that can be dynamically resized and PVector refers to a two or three-dimensional vector.

More For Game DevelopersHow to Built A Career in the Gaming Industry

 

Add Properties

The first step is to add global properties that can store information about size, positions, etc. The first two properties to add are gameover and s (see code below). We use the boolean variable gameover to check if the player has lost the game and the float variable s specifies the size of the rectangles representing the objective and the snake.

Display the first two properties of the game:

// A boolean used to check if the game is ended
boolean gameover = false; 

// A general size used for the objective
// and snake's head and tail
float s = 5;

When the general properties are in place, it’s time to add the properties for the snake. The snake will require a property specifying the position of each rectangle representing the snake’s tail and head. The snake will also require a property specifying the direction of the snake. To store the positions of the snake’s rectangles, add a property called snakePositions of the type ArrayList<PVector>. To store information about the snake’s direction, add a property called snakeDirection of the type PVector.

This code shows the snake’s properties:

// ...

// All the positions of the snake's head and tail
// snakePositions.get(0) => head
// snakePositions.get(n > 0) => tail
ArrayList<PVector> snakePositions = new ArrayList<PVector>();

// The direction of the snake
PVector snakeDirection = new PVector(1, 0);

The last property the game requires is a variable specifying the positions of the objective. Add a property called objPosition of the type PVector

This code shows the property of the objective:

// ...

// The position of the objective
PVector objPosition = new PVector(0, 0);

 

Add Custom Helper Methods

The next section covers a couple of helper methods used to help simplify the explanation of the game. The first method is used to check if the position of two points overlap.

We can define the method’s procedure as follows:

  1. Expect two vectors specifying the top left corner of a rectangle.
  2. Find the center position of the rectangle using its size (s).
  3. Return true if the distance between the center positions is less than the size.

This code shows an implementation of a method that returns true if two points overlap.

// ...

// A method used to check if two vectors overlaps
public boolean overlaps(PVector p1, PVector p2) {
  PVector c1 = new PVector(p1.x + s/2, p1.y + s/2);
  PVector c2 = new PVector(p2.x + s/2, p2.y + s/2);
  return PVector.dist(c1, c2) < s;
}

The next method is used to give a vector a random position.

The method’s procedure is as follows:

  1. Set the horizontal property (x) to a random float within a and b.
  2. Set the vertical property (y) to a random float within a and b.

This code shows a method that gives a vector a random position within the given boundaries.

// ...

// A method used to randomize the position
// of a vector within the boundaries
public void randomize(PVector position, float a, float b) {
  position.x = random(a, b);
  position.y = random(a, b);
}

The last two helper methods are reset() and endgame(). reset() is used to set the properties of the game to the same state as at the start of the application and endgame() is used to start the end game functionality.

The procedure of endgame() is:

  1. Set the gameover property to true to signal the game is ended.
  2. Override the current content with a black background.
  3. Reset the fill color to set the text color to white.
  4. Display a text saying “Gameover.”

The procedure of reset() is:

  1. Set the gameover property to true to signal the game is NOT ended.
  2. Remove all saved positions from the snakePositions property.
  3. Define a vector with the position of the screen’s center.
  4. Add the vector to the snakePositions property to define the snake’s head.
  5. Set the objective’s position to a random position within the screen’s borders.

This code shows the implementation of a method used to end the game and another used to reset the properties of the game.

// ...

// A method used to end the game
public void endgame() {
  gameover = true;
  
  // Show a black screen
  background(0);
  // Set text color
  fill(255);
  // Show game over text
  text("Gameover", 10, 20); 
}

// A method used to reset the game
public void reset() {
  gameover = false;
  
  // Clear the positions
  snakePositions.clear();
  
  // Get screen center position
  PVector c = new PVector(width/2, height/2);
  
  // Add the snake's head
  snakePositions.add(c);
  
  // Randomize the position of the obj.
  randomize(objPosition, 0, width-s); 
}

More Tech Tutorials on Built InWhat Is the @ Symbol in Python and How Do I Use It?

 

Add the Built-In Method: Setup

The next method, the game requires, is the built-in setup method which is called once when the application starts.

The method’s procedure is as follows:

  1. Set the screen size to 250 x 250 pixels.
  2. Set the frame rate to 25 frames per second.
  3. Reset the properties of the game using the helper method: reset

This code shows the implementation of the built-in method: setup.

// ...

void setup() {
  // Set the screen size.
  size(250, 250);
  // Set the application's frame rate
  // Lower frame rate = slower movement
  // Higher frame rame = faster movement
  frameRate(25); 
  
  // Reset the game's properties.
  reset();
}

More How-TosHow to Find Variance Using Python

 

Add the Built-In Method: Draw

The built-in method draw() is called continuously until the application is stopped. The code within the draw() method is a bit more complex than other methods presented in the article and the code is, therefore, divided into several parts in the following section.

We can describe the procedure of the code presented below as follows:

  1. Set the screen’s background to black.
  2. If the property gameover is true, which means the game is ended, wait five seconds and reset the game.
  3. Set the fill color to white.
  4. Display a text saying “Score: {x}” at (x, y) = (10, 20).
  5. Set the fill color to black and stroke color to white.
  6. Draw the objective.
  7. Loop the snakePositions property in reversed order.
  8. Get the current position.
  9. Check if the current index is equal to the head’s index to define behavior for the snake’s head and add an else statement used to define the behavior for the snake’s tail.
  10. Draw a rectangle at the current position representing the snake’s tail or head.

This code displays the implementation of the built-in method: draw.

void draw() {
  // Set background color
  background(0);
  
  // If the game ended
  if (gameover) {
    // Wait 5 seconds
    delay(5000);
    // Reset the game    
    reset();
    return;
  }
  
  // Draw score
  fill(255);
  text("Score: " + (snakePositions.size()-1), 10, 20);
  
  // Set snake and objective 
  // fill and stroke color 
  fill(0);
  stroke(255);
  
  // Draw the objective
  rect(objPosition.x, objPosition.y, s, s);
  
  // Draw the snake
  for (int i = snakePositions.size() - 1; i > -1; i--) {

      // Get the current position
      PVector position = snakePositions.get(i);
      
      // If the position is the head
      if (i == 0) {
        // ...
      } else {
        // ...      
      }
      
      // Display the point
      rect(position.x, position.y, s, s);
   }
}

Why loop the snakePositions from the end to the start? The reason is related to the snake’s movement. Whenever the head of the snake moves one time in some direction, each rectangle of the tail also has to be moved one time.

The code is, therefore, designed as follows:

  1. Start by selecting the last element and set its position to the second last element’s position.
  2. Select the second last element and set its position to the third last element’s position.
  3. Continue to select an element and set its position to the position of the element before it, until the first element is reached.
  4. When the first element is reached, move the position of that element one time towards the direction specified in the property called snakeDirection.

This code displays the implementation of the snake’s movement:

void draw() {
  // ...
  
  // Draw the snake
  for (int i = snakePositions.size() - 1; i > -1; i--) {

      // Get the current position
      PVector position = snakePositions.get(i);
      
      // If the position is the head
      if (i == 0) {
        // Move towards the direction
        position.x += snakeDirection.x * s;
        position.y += snakeDirection.y * s;
        
        // Check if the position is outside
        // the screen's borders
        // ...
          
        // Check if the head overlaps with
        // the objective
        // ...    
        
      } else {
        // Check if the tail overlaps if the head,
        // if the tail is not directly attached to the head
        // ...       
        // Get the position before
        PVector before = snakePositions.get(i - 1);         
        // Set the current element's position to the position before
        position.x = before.x;
        position.y = before.y;
        // ...    
      }
      
      // Display the point
      rect(position.x, position.y, s, s);
   }
}

The draw() method currently moves the position of the snake, checks if the game is ended, and draws the snake and the objective. But it still needs some functionality handling what should happen when the snake reaches the screen’s borders and some other functionality handling when the snake’s head overlaps with the objective or its own tail.

The procedure for when the snake reaches the screen’s borders (L 14–18):

  1. If the horizontal property (x) of the snake is less than zero, set the property to the screen’s width.
  2. If the horizontal property (x) of the snake is greater than the screen’s width, set the property to zero.
  3. If the vertical property (y) of the snake is less than zero, set the property to the screen’s height.
  4. If the vertical property (y) of the snake is greater than the screen’s height, set the property to zero.

The procedure for when the snake’s head overlaps with the objective (L 22–29):

  1. If the position of the snake’s head overlaps with the position of the objective.
  2. Set the position of the objective to a new random position.
  3. Get the position of the snake’s last tail element.
  4. Define a position behind the last tail element.
  5. Add that position as the new last tail element.

The procedure for when the snake’s head overlaps with one of its tail elements (L 34–39):

  1. If the tail element is not directly attached to the head and overlaps with the head.
  2. End the game.
  3. Stop the loop.
  4. If the tail element doesn’t overlap, set the position of the tail element.

This code displays the implementation of the functionality handling whenever the snake reaches the screen’s borders and handling overlaps between the snake’s head and tail.

void draw() {
  // ...
  
  for (int i = snakePositions.size() - 1; i > -1; i--) {
      // ...
      
      // If the position is the head
      if (i == 0) {
        // ...
        
        // Check if the position is outside
        // the screen's borders
        // Horizontal
        if (position.x < 0) position.x = width;
        else if (position.x > width) position.x = 0;
        // Vertical
        if (position.y < 0) position.y = height;
        else if (position.y > height) position.y = 0;  
          
        // Check if the head overlaps with
        // the objective
        if (overlaps(position, objPosition)) {
          // Randomize the position of the obj.
          randomize(objPosition, 0, width-s);
          // Increase the snake's tail
          PVector last = snakePositions.get(snakePositions.size() - 1);
          PVector next = new PVector(last.x-(snakeDirection.x*s), last.y-(snakeDirection.x*s));
          snakePositions.add(next);
        }        
        
      } else {
        // Check if the tail overlaps with the head
        // Unless it's the point attached directly to the head
        if (i > 1 && overlaps(position, snakePositions.get(0))) {          
          endgame();
          break;
        } else {        
          // ... (set tail position)          
        }        
      }
      
      // ...
   }
}

 

Add the Built-In Method: keyPressed

The last functionality the game requires is something handling key events.

The directions the snake can move can be defined as follows:

  • Left :(x, y) = (-1, 0)
  • Right : (x, y) = (1, 0)
  • Up : (x, y) = (0, -1)
  • Down : (x, y) = (0, 1)

The procedure for the keyPressed() method is:

  1. If the ‘a’-key is pressed and if the snake is not moving right, set the direction to left.
  2. If the ‘d’-key is pressed and if the snake is not moving left, set the direction to right.
  3. If the ‘s’-key is pressed and if the snake is not moving up, set the direction to down.
  4. If the ‘w’-key is pressed and if the snake is not moving down, set the direction to up.

This code displays the implementation of the method handling keypress.

// ...

void keyPressed() {
  if (key == 'a' && snakeDirection.x != 1) {
    snakeDirection.x = -1;
    snakeDirection.y = 0;
  }
  else if (key == 'd' && snakeDirection.x != -1) {
    snakeDirection.x = 1;
    snakeDirection.y = 0;
  }
  else if (key == 's' && snakeDirection.y != -1) {
    snakeDirection.x = 0;
    snakeDirection.y = 1;
  }
  else if (key == 'w' && snakeDirection.y != 1) {
    snakeDirection.x = 0;
    snakeDirection.y = -1;
  }
}

A GIF displaying the Snake game from the article.

Now you’ve got your very own classic snake game.

You can find a full example of the code in my GitHub Gist.

Explore Job Matches.