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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
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:
1 2 3 |
public static void main(String[] args) { snake = new Snake(); } |
It instantiates a new object Snake. The new object is created with the following characteristics:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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:
1 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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!