Snake Game in Java (OOP design concepts)


In this article, we have explored how to design the classical Snake Game using Object Oriented Programming (OOP) concepts and implement it using Java.

Let us start immersing ourselves into it by fueling ourselves with some Nostalgia. Below is Snake Xenzia the Nokia.
snake_xenzia
The classic game that we are all acquainted with is actually surprisingly simple to implement and understand.

So lets get this straight, What do we need in our snake game?

  • A snake? Ofcourse!
  • Food for the snake!
  • A board.. where will the snake move afterall
  • Logic for the real game

Now that we're clear with what makes a Snake Game, lets look at it with an object oriented approach. What classes do we need?

Snake

A Snake Class for the snake. We want to
--> Know and maintain the size of the snake.
--> The ability to control movement of the Snake
--> Check whether it did not hit a Wall.

So it shall contain the following functions:

  • A constructor to initialise the snake with its head.
  • A function to grow the size of the snake.
  • A function for the snake to move.
  • A function to check if the snake has crashed.
  • A function that returns the snake
  • A function to set values in the snake.
  • A function that returns the head of the snake.
  • A function to set the head of the snake.

Cell

While food seemed intuitive to me while thinking, cell feels intuitive to me for working! Why so?
Our boards, consistes of various cells, A cell at random will be proclaimed as the cell containing food. The identify of food is random, but identity of cell is meaningful, so I would rather make a class Cell than food.
Cells are also important to kep track of position of the snake!

Each cell is thereby contains the information about
--> Its Row
--> Its Column
--> Whether it contains food or not?
--> Whether it is a part of the snake or not?

Hence its member functions include

  • A constructor to initialise it.
  • A function that specifies its type (contains food etc.)
  • A function that returns its type.
  • A function to return its row.
  • A function to return its column.

Board

It is the set of valid points for the game to function on, qualitatively a matrix of cells.
While the cells revert to the questions, being the collection and home of cells, the board needs to ensure the cells get satisfiable answers

Whether it contains food or not?
We need to assign food to some random cell as well. This is attributed to the board, as the cell doesn't choose whether it shall contain food, the board is acquainted with the cells it contains and hence should empowered to assign a cell to contain food.

Whether it is a part of the snake or not?
This is answered by the Snake class we described above. While food is an attribute of the board, movement is an attribute of the snake! Hence it is wise to let the Snake class contain method pertaining to the Snake's movement.

It will contain the following functions

  • A constructor to initialise te board with number of rows and columns.
  • A function to return its cells.
  • A function to set values in its cells.
  • A function to generate Food.

Game

This is the meaty part of the code, our primary logic, the game!
It needs an instance of

  • Board
  • Game
    Notice how Board already contains instances of Cell.

This also needs to contain the following
--> Ability and logic to use the classes we make and conduct a game!
--> Know when the game is over

So the following functions will be required:

  • A constructor to initialise the game with a snake and a board.
  • A function to return the snake.
  • A function to set the value of snake.
  • A function to return the board.
  • A function to set the value of board
  • A function to set the game to be over.
  • A function to check if the game is over.
  • A function to set the direction of snake's motion.
  • A function to return the direction of snake's motion.
  • A function to update the game according to snake's movement.
  • A function to keep track of the forthcoming cell.
    After this we will also define a main function to actually play the game.

Now that we are clear with the what and what not of the game, lets start coding!

Code

Cell
The most basic element, we only need private data members as discussed and public methods to set and retrieve them.

public class Cell { 

	private final int row, col; 
	private CellType cellType; 

	public Cell(int row, int col) 
	{ 
		this.row = row; 
		this.col = col; 
	} 

	public CellType getCellType() 
	{ 
		return cellType; 
	} 

	public void setCellType(CellType cellType) 
	{ 
		this.cellType = cellType; 
	} 

	public int getRow() 
	{ 
		return row; 
	} 

	public int getCol() 
	{ 
		return col; 
	} 
} 

Board
For the board, it must contain a 2-Dimensional array of cells, which we need to initialise in its constructor. Thereby it is meaningful to maintain the ROW_COUNT and COL_COUNT for a board, which shall be passed to the constructor.
We also need a method to randomly designate a cell as Food along with some methods to set and retrieve cells.

public class Board { 

	final int ROW_COUNT, COL_COUNT; 
	private Cell[][] cells; 

	public Board(int rowCount, int columnCount) 
	{ 
		ROW_COUNT = rowCount; 
		COL_COUNT = columnCount; 

		cells = new Cell[ROW_COUNT][COL_COUNT]; 
		for (int row = 0; row < ROW_COUNT; row++) { 
			for (int column = 0; column < COL_COUNT; column++) { 
				cells[row][column] = new Cell(row, column); 
			} 
		} 
	} 

	public Cell[][] getCells() 
	{ 
		return cells; 
	} 

	public void setCells(Cell[][] cells) 
	{ 
		this.cells = cells; 
	} 

	public void generateFood() 
	{ 
		System.out.println("Going to generate food"); 
		int row = (int)(Math.random() * ROW_COUNT); 
		int column = (int)(Math.random() * COL_COUNT); 

		cells[row][column].setCellType(CellType.FOOD); 
		System.out.println("Food is generated at: " + row + " " + column); 
	} 
} 

Snake
We will be representing the snake with a Linked List, that is because it has O(1) insertion, which helps us increase the size of the snake at the pace of the game!
We need to add elements at the tail and retrieve position with the head.
This is a FIFO approach, i.e. represented by a Queue that we are implementing with a Linked List.
We need to create methods for

  • Movement of snake i.e. incrementing the position in the direction of movements.
  • Check crashes i.e. if snake comes in contact with any prohibited cell, it shall declare a crash!
// To represent a snake 
import java.util.LinkedList; 

public class Snake { 

	private LinkedList<Cell> snakePartList = new LinkedList<>(); 
	private Cell head; 

	public Snake(Cell initPos) 
	{ 
		head = initPos; 
		snakePartList.add(head); 
	} 

	public void grow() 
	{ 
		snakePartList.add(head); 
	} 

	public void move(Cell nextCell) 
	{ 
		System.out.println("Snake is moving to " + 
			nextCell.getRow() + " " + nextCell.getCol()); 
		Cell tail = snakePartList.removeLast(); 
		tail.setCellType(CellType.EMPTY); 

		head = nextCell; 
		snakePartList.addFirst(head); 
	} 

	public boolean checkCrash(Cell nextCell) 
	{ 
		System.out.println("Going to check for Crash"); 
		for (Cell cell : snakePartList) { 
			if (cell == nextCell) { 
				return true; 
			} 
		} 

		return false; 
	} 

	public LinkedList<Cell> getSnakePartList() 
	{ 
		return snakePartList; 
	} 

	public void setSnakePartList(LinkedList<Cell> snakePartList) 
	{ 
		this.snakePartList = snakePartList; 
	} 

	public Cell getHead() 
	{ 
		return head; 
	} 

	public void setHead(Cell head) 
	{ 
		this.head = head; 
	} 
} 

Game
Credits to the classes we've made so far the forthcoming section is actually surprisingly easy!

We need a class Game, which contains data members to take care of the snake and the board.
We need a set of fixed directions for movement, and a direction member to determine movement.
Along with a boolean to check for the game to be over!

  • We need methods to initialise and receive the values.
  • We need to ensure that the game is updated as per the user, i.e. actually calling the functions for moving the snake, getting the food etc.

One characteristic is that, even though direction of movement is somewhat characteristic to the snake, the position is actually attributed to the cell, which is a part of the board. Since we have the snake as well as access to current values here, and have also added direction members here, it seems right to and modular to maintain directions here.

  • A function to update the direction here itself.
// To represent Snake Game 
public class Game { 

	public static final int DIRECTION_NONE = 0, DIRECTION_RIGHT = 1, 
		DIRECTION_LEFT = -1, DIRECTION_UP = 2, DIRECTION_DOWN = -2; 
	private Snake snake; 
	private Board board; 
	private int direction; 
	private boolean gameOver; 

	public Game(Snake snake, Board board) 
	{ 
		this.snake = snake; 
		this.board = board; 
	} 

	public Snake getSnake() 
	{ 
		return snake; 
	} 

	public void setSnake(Snake snake) 
	{ 
		this.snake = snake; 
	} 

	public Board getBoard() 
	{ 
		return board; 
	} 

	public void setBoard(Board board) 
	{ 
		this.board = board; 
	} 

	public boolean isGameOver() 
	{ 
		return gameOver; 
	} 

	public void setGameOver(boolean gameOver) 
	{ 
		this.gameOver = gameOver; 
	} 

	public int getDirection() 
	{ 
		return direction; 
	} 

	public void setDirection(int direction) 
	{ 
		this.direction = direction; 
	} 

	// We need to update the game at regular intervals, 
	// and accept user input from the Keyboard. 
	public void update() 
	{ 
		System.out.println("Going to update the game"); 
		if (!gameOver) { 
			if (direction != DIRECTION_NONE) { 
				Cell nextCell = getNextCell(snake.getHead()); 

				if (snake.checkCrash(nextCell)) { 
					setDirection(DIRECTION_NONE); 
					gameOver = true; 
				} 
				else { 
					snake.move(nextCell); 
					if (nextCell.getCellType() == CellType.FOOD) { 
						snake.grow(); 
						board.generateFood(); 
					} 
				} 
			} 
		} 
	} 

	private Cell getNextCell(Cell currentPosition) 
	{ 
		System.out.println("Going to find next cell"); 
		int row = currentPosition.getRow(); 
		int col = currentPosition.getCol(); 

		if (direction == DIRECTION_RIGHT) { 
			col++; 
		} 
		else if (direction == DIRECTION_LEFT) { 
			col--; 
		} 
		else if (direction == DIRECTION_UP) { 
			row--; 
		} 
		else if (direction == DIRECTION_DOWN) { 
			row++; 
		} 

		Cell nextCell = board.getCells()[row][col]; 

		return nextCell; 
	} 

	public static void main(String[] args) 
	{ 

		System.out.println("Going to start game"); 

		Cell initPos = new Cell(0, 0); 
		Snake initSnake = new Snake(initPos); 
		Board board = new Board(10, 10); 
		Game newGame = new Game(initSnake, board); 
		newGame.gameOver = false; 
		newGame.direction = DIRECTION_RIGHT; 

		// We need to update the game at regular intervals, 
		// and accept user input from the Keyboard. 

		// here I have just called the different methods 
		// to show the functionality 
		for (int i = 0; i < 5; i++) { 
			if (i == 2) 
				newGame.board.generateFood(); 
			newGame.update(); 
			if (i == 3) 
				newGame.direction = DIRECTION_RIGHT; 
			if (newGame.gameOver == true) 
				break; 
		} 
	} 
} 

You are welcome to compile and run them together, maybe even develop a friendly interface and actually play it!
You see, the way to solve a problem is logically breaking it into smaller problems. Now go ahead and maybe build another game, or break down your own game into smaller fragments and implement it!