Java – Making a snake

As you probably know, I am an online student at the Software University in Bulgaria, Sofia.

This week, we had to submit a simple game in Java, developed by me and 4 other online students as a team. After some dispute, we all have agreed to make a snake. I will not go into details, why none of us was able to make it for the presentation of the game and I will simply present the code of the game. Considering the fact, that none of the code is written by me, as far as I am still a learner in Java (and I had some quite lame problems installing Eclipse), the main target of this article is to analyze the code line by line and to understand how it actually works.

If you just want to play the snake, I have used this manual here to create an exe file. The *.exe file is located here.

So, let’s start with the code. It would be quite challenging to try to understand how it works. Anyway, I will give it a try!

Mainly, we have 2 classes – 1 main class, called Snake.java and another one, concerning the panel in which the game is shown. So, we start with the main class:

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.Timer;

public class Snake implements ActionListener, KeyListener {

    public static Snake snake;

    public JFrame jframe;

    public RenderPanel renderPanel;

    public static final int UP = 0, DOWN = 1, LEFT = 2, RIGHT = 3, SCALE = 10, X = 59, Y = 36;

    public int ticks = 0, direction = DOWN, score, tailLength, time, delay, initialDelay = 100;

    public Timer timer = new Timer(initialDelay, this);

    public ArrayList snakeParts = new ArrayList(), stones = new ArrayList();

    public Point head, apple;

    public Random random;

    public boolean over = false, paused;

    public Dimension dim;

    public Snake() {
            dim = Toolkit.getDefaultToolkit().getScreenSize();
            jframe = new JFrame("Snake");
            jframe.setVisible(true);
            jframe.setSize(600, 500);
            jframe.setResizable(false);
            jframe.setLocation(dim.width / 2 - jframe.getWidth() / 2, dim.height
                            / 2 - jframe.getHeight() / 2);
            jframe.add(renderPanel = new RenderPanel());
            jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            jframe.addKeyListener(this);
            startGame();
    }

    public void startGame() {
            over = false;
            paused = false;
            time = 0;
            score = 0;
            tailLength = 1;
            timer.setDelay(initialDelay);
            delay = initialDelay;
            ticks = 0;
            direction = DOWN;
            head = new Point(0, -1);
            random = new Random();
            snakeParts.clear();
            stones.clear();
            apple = new Point(random.nextInt(X), random.nextInt(Y));
            //System.out.println(apple.x);
            //System.out.println(apple.y);
            timer.start();
    }

    @Override
    public void actionPerformed(ActionEvent arg0) {
            renderPanel.repaint();
            ticks++;
            if (ticks % 2 == 0 && head != null && !over && !paused) {
                    time++;
                    snakeParts.add(new Point(head.x, head.y));
                    if (direction == UP)
                            if (head.y - 1 >= 0 && noTailAt(head.x, head.y - 1))
                                    head = new Point(head.x, head.y - 1);
                            else
                                    over = true;
                    if (direction == DOWN)
                            if (head.y + 1 < 67 && noTailAt(head.x, head.y + 1))
                                    head = new Point(head.x, head.y + 1);
                            else
                                    over = true;
                    if (direction == LEFT)
                            if (head.x - 1 >= 0 && noTailAt(head.x - 1, head.y))
                                    head = new Point(head.x - 1, head.y);
                            else
                                    over = true;
                    if (direction == RIGHT)
                            if (head.x + 1 < 80 && noTailAt(head.x + 1, head.y))
                                    head = new Point(head.x + 1, head.y);
                            else
                                    over = true;
                    if (snakeParts.size() > tailLength)
                            snakeParts.remove(0);
                    if (apple != null) {
                            if (head.equals(apple)) {
                            		if (delay > 50) {
											delay -= 10;
									}
                            		timer.setDelay(delay);
                                    score += tailLength + (initialDelay - delay);
                                    tailLength++;
                                    apple.setLocation(random.nextInt(X), random.nextInt(Y));
                                    stones.add(generateStone());
                            }
                    }
                    for (Point stone : stones) {
                		if (head.equals(stone)) {
                            over = true;
                            break;
                        }
        			}
            }
    }

    public boolean noTailAt(int x, int y) {
            for (Point point : snakeParts) {
                    if (point.equals(new Point(x, y))) {
                            return false;
                    }
            }
            return true;
    }

    public Point generateStone() {
    	int x, y;
    	boolean overlapping;
    	Point stone = new Point();
    	do {
    		overlapping = false;
    		x = random.nextInt(X);
        	y = random.nextInt(Y);
        	stone.setLocation(x, y);
        	// for snake parts
        	for (Point point : snakeParts) {
                if (point.equals(stone)) {
                    overlapping = true;
                    break;
                }
        	}
        	// for snake head
        	if (head.equals(stone)) {
        		overlapping = true;
			}
        	// for the apple
        	if (apple.equals(stone)) {
        		overlapping = true;
			}
        	// for the other stones
        	for (Point point : stones) {
        		if (point.equals(stone)) {
                    overlapping = true;
                    break;
                }
			}
		} while (overlapping);
    	return stone;
    }

    public static void main(String[] args) {
            snake = new Snake();
    }

    @Override
    public void keyPressed(KeyEvent e) {
            int i = e.getKeyCode();
            if ((i == KeyEvent.VK_A || i == KeyEvent.VK_LEFT)  && direction != RIGHT)
                    direction = LEFT;
            if ((i == KeyEvent.VK_D || i == KeyEvent.VK_RIGHT) && direction != LEFT)
                    direction = RIGHT;
            if ((i == KeyEvent.VK_W || i == KeyEvent.VK_UP)  && direction != DOWN)
                    direction = UP;
            if ((i == KeyEvent.VK_S || i == KeyEvent.VK_DOWN) && direction != UP)
                    direction = DOWN;
            if (i == KeyEvent.VK_SPACE)
                    if (over)
                            startGame();
                    else
                            paused = !paused;
    }

    @Override
    public void keyReleased(KeyEvent e) {
    }

    @Override
    public void keyTyped(KeyEvent e) {
    }

}

The second class is RenderPanel.Java:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;

import javax.swing.JPanel;

@SuppressWarnings("serial")
public class RenderPanel extends JPanel {

    @Override
    protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.BLACK);
            g.fillRect(0, 0, 600, 500);
            Snake snake = Snake.snake;
            g.setColor(Color.GREEN);
            for (Point point : snake.snakeParts) {
                    g.fillRect(point.x * Snake.SCALE, point.y * Snake.SCALE,
                                    Snake.SCALE, Snake.SCALE);
            }
            g.fillRect(snake.head.x * Snake.SCALE, snake.head.y * Snake.SCALE,
                            Snake.SCALE, Snake.SCALE);
            g.setColor(Color.RED);
            g.fillRect(snake.apple.x * Snake.SCALE, snake.apple.y * Snake.SCALE,
                            Snake.SCALE, Snake.SCALE);
            g.setColor(Color.WHITE);
            for (Point point : snake.stones) {
                g.fillRect(point.x * Snake.SCALE, point.y * Snake.SCALE,
                                Snake.SCALE, Snake.SCALE);
            }
            String string = "Score: " + snake.score + ", Length: "
                            + snake.tailLength + ", Time: " + snake.time / 20;
            g.setColor(Color.white);
            g.drawString(string, (int) (getWidth() / 2 - string.length() * 2.5f),
                            10);
            string = "Game Over!";
            if (snake.over)
                    g.drawString(string,
                                    (int) (getWidth() / 2 - string.length() * 2.5f),
                                    (int) snake.dim.getHeight() / 4);
            string = "Paused!";
            if (snake.paused && !snake.over)
                    g.drawString(string,
                                    (int) (getWidth() / 2 - string.length() * 2.5f),
                                    (int) snake.dim.getHeight() / 4);
    }
}

So, how does it work?

The first thing, which is executed is the main method in Snake.java:

    public static void main(String[] args) {
            snake = new Snake();
    }

It instantiates a new object Snake. The new object is created with the following characteristics:

    public Snake() {
            dim = Toolkit.getDefaultToolkit().getScreenSize();
            jframe = new JFrame("Snake");
            jframe.setVisible(true);
            jframe.setSize(600, 500);
            jframe.setResizable(false);
            jframe.setLocation(dim.width / 2 - jframe.getWidth() / 2, dim.height
                            / 2 - jframe.getHeight() / 2);
            jframe.add(renderPanel = new RenderPanel());
            jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            jframe.addKeyListener(this);
            startGame();
    }

Once, the object is created, we see the magic happening. It generates a new object jframe, setting default visibility, size and location. Then it adds and instantiates a new render panel with the line:

            jframe.add(renderPanel = new RenderPanel());

Then it adds keyListener (with this we manage to navigate the snake) and it starts the game with the startGame() line. The start Game method looks like this:

    public void startGame() {
            over = false;
            paused = false;
            time = 0;
            score = 0;
            tailLength = 1;
            timer.setDelay(initialDelay);
            delay = initialDelay;
            ticks = 0;
            direction = DOWN;
            head = new Point(0, -1);
            random = new Random();
            snakeParts.clear();
            stones.clear();
            apple = new Point(random.nextInt(X), random.nextInt(Y));
            //System.out.println(apple.x);
            //System.out.println(apple.y);
            timer.start();
    }

In start game we see some initiation of objects – head, random and apple are initiated. So, where is the magic, moving the snake? It is in the public snake, where we create a instantiate a new RenderPanel. Once, we instantiate it, we go to the RenderPanel.java class, where we paint it with given colors.

So far so good! The game is outstanding, enjoy it!