I am making a snake game using linked lists and such for practice, and I want to increase the speed of the snake using Timers in Java every time the snake grows in length. So far I have the basic game set up, with multiple food pellets and pellets that can kill the snake. But I am stuck trying to get the snake to go faster every time it grows.
import java.awt.Color;
import java.awt.Graphics;
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.LinkedList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.Timer;
public class SnakeGame extends JFrame {
// A snake is just a list of coordinates (java.util.LinkedList, not our LinkedList)
private LinkedList<Coordinate> snake = new LinkedList<Coordinate>();
// The snake grows when it eats food
//private Coordinate food = new Coordinate();
private List<Coordinate> objects = new ArrayList<>();
// The game is on or over
private static enum Game { ON, OVER};
private Game status = Game.ON;
// Repeatedly moves the snake
private Timer timer;
// The snake can move in one of 4 directions
public static enum Direction { UP, DOWN, LEFT, RIGHT };
// The snake's current direction (heading). Default: moving right
private Direction heading = Direction.RIGHT;
/**
* An (x,y) coordinate in a 64 by 48 grid
*/
public static class Coordinate {
public final int x;
public final int y;
public boolean food = false;
// By default, construct a random coordinate not too far from the wall
Coordinate() {
this.x = new Random().nextInt(60) + 2;
this.y = new Random().nextInt(40) + 2;
}
Coordinate(int x, int y) {
this.x = x;
this.y = y;
}
}
// The snake can't switch to the opposite direction
public boolean oppositeDirection(Direction newHeading) {
return (heading == Direction.UP && newHeading == Direction.DOWN) ||
(heading == Direction.DOWN && newHeading == Direction.UP) ||
(heading == Direction.LEFT && newHeading == Direction.RIGHT) ||
(heading == Direction.RIGHT && newHeading == Direction.LEFT);
}
// Update the heading based on the new heading
public void changeHeading(Direction newHeading) {
if (!oppositeDirection(newHeading)) {
heading = newHeading;
}
}
/**
* Handle keyboard input (arrows change the snake's heading)
*/
private class KeyControl implements KeyListener {
@Override
public void keyTyped(KeyEvent e) {}
@Override
public void keyPressed(KeyEvent e) {
Direction newHeading = heading;
switch(e.getKeyCode()) {
case KeyEvent.VK_LEFT: case KeyEvent.VK_KP_LEFT:
newHeading = Direction.LEFT; break;
case KeyEvent.VK_RIGHT: case KeyEvent.VK_KP_RIGHT:
newHeading = Direction.RIGHT; break;
case KeyEvent.VK_UP: case KeyEvent.VK_KP_UP:
newHeading = Direction.UP; break;
case KeyEvent.VK_DOWN: case KeyEvent.VK_KP_DOWN:
newHeading = Direction.DOWN; break;
}
changeHeading(newHeading);
}
@Override
public void keyReleased(KeyEvent e) {
}
}
@Override
// This view renders the snake and food
// Each snake coordinate is a 10x10 pixel square
// This method is called from repaint() from AWT
public void paint(Graphics g) {
g.clearRect(0, 0, 640, 480);
Color green = new Color(0,128,0);
g.setColor(green);
for (Coordinate c : snake) {
g.fillRect(c.x*10, c.y*10, 10, 10);
}
for (Coordinate object : objects) {
if(object.food){
g.setColor(Color.BLUE);
}else{
g.setColor(Color.RED);
}
g.fillOval(object.x*10, object.y*10, 10, 10);
}
}
// When the snake moves, it can hit the wall, hit the food, poison (not implemented) or itself (not implemented)
public void move() {
Coordinate newHead = newHead();
if (hitTheWall(newHead)) {
status = Game.OVER;
return; // will return back to where this method is called
}
snake.addFirst(newHead); // java.util.LinkedList.addFirst()
if (hitTheFood(newHead)) {
// new object should always be added when the snake eats one
Coordinate object = new Coordinate();
object.food = true;
objects.add(object);
} else {
snake.removeLast();
}
}
// The snake's heading determines its new head coordinate
private Coordinate newHead() {
Coordinate head, newHead;
head = snake.getFirst(); // java.util.LinkedList.getFirst()
switch (heading) {
case DOWN: newHead = new Coordinate(head.x, head.y + 1); break;
case LEFT: newHead = new Coordinate(head.x - 1, head.y); break;
case RIGHT: newHead = new Coordinate(head.x + 1, head.y); break;
case UP: newHead = new Coordinate(head.x, head.y - 1); break;
// The default case is never reached because we have only 4 events.
default: newHead = new Coordinate(); break;
}
return newHead;
}
private boolean hitTheFood(Coordinate newHead) {
for (Coordinate obj : objects) {
boolean hit = newHead.x == obj.x && newHead.y == obj.y && obj.food;
if(hit){
objects.remove(obj);
return true;
}
}
return false;
}
public boolean hitTheWall(Coordinate head) {
for (Coordinate obj : objects){
if(head.x == obj.x && head.y == obj.y && !obj.food)
return true;
}
for (Coordinate obj : snake){
if(head.x == obj.x && head.y == obj.y && !obj.food)
return true;
}
if (head.x == 64 || head.y == 48 || head.x == 0 || head.y == 0)
return true;
return false;
}
/**
* The timer moves the snake using this class.
*/
private class SnakeMover implements ActionListener {
@Override
// Listening Action (in this case Timer - every certain millisecond) and execute this method
public void actionPerformed(ActionEvent e) {
move();
repaint(); // from AWT library. It will call paint() automatically
if (status == Game.OVER) {
playAgain("The snake's dead");
}
}
}
// Ask the player what to do when the game is over
private void playAgain(String message) {
String[] options = new String[] {"Play again","Quit"};
int choice = JOptionPane.showOptionDialog(null, message, "Game over", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE,null,options,options[0]);
if (choice == 0) {
initialize();
} else {
System.exit(0);
}
}
// Initialize game (snake, food, etc)
private void initialize() {
status = Game.ON;
// Make a small snake with 1 node (a 10x10 pixel coordinate)
snake.clear(); // remove all of the elements of the LinkedList
snake.add(new Coordinate()); // append the new element to the end of the LinkedList
// creating some foods
for (int i = 0; i < 10 ; i++) {
Coordinate object = new Coordinate();
object.food= true;
objects.add(object);
}
// creating some poisons
for (int i = 0; i < 5 ; i++) {
Coordinate object = new Coordinate();
objects.add(object);
}
}
public SnakeGame() {
setSize(640, 480); // Window size - pixel
setTitle("Snake Game");
setVisible(true);
// Update the snake's direction using keyboard arrows
// Event Handler: addKeyListener is from AWT library. This is how to "register" event
addKeyListener(new KeyControl());
// Make the snake move every 150 milliseconds
timer = new Timer(150, new SnakeMover());
timer.start();
// Initialize game (snake, food)
initialize();
}
public static void main(String[] args) {
new SnakeGame();
}
}