1 Replies - 3876 Views - Last Post: 08 November 2011 - 08:33 PM

#1 pbl   User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8379
  • View blog
  • Posts: 31,956
  • Joined: 06-March 08

Snake Game

Posted 27 July 2009 - 05:34 PM

Description: Many times we have in the forum questions on how to implement a Snake game. In this code snippet fully documented, you'll see how to use JLabels to display the snake and how to implement a thread to make the snake move.A small snake game using JLabels
import javax.swing.*;
import java.awt.*;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.*;

public class Snake extends JFrame implements Runnable, ActionListener {

	private static final long serialVersionUID = 123654L;   // just to avoid compile warning
	// number of squares in the grid, size of each of them
	private static final int SIZE = 25, WIDTH = 20;
	// the labels for displaying the snake and its foods
	private SnakeLabel[][] label = new SnakeLabel[SIZE][SIZE];
	// arrayList containing all of the snake position (Labels)
	private SnakeList snakeList = new SnakeList();
	// the label displaying if I am running or not and the size of the snake
	private JLabel statusLabel = new JLabel("Initializing");
	// random number generator to find a new spot for a new food when I eat one
	private Random random = new Random();
	// if I am in a pause state or not
	boolean pause = false;
	// the button to switch from pause to not pause
	private JButton pauseResume = new JButton("Pause");
	// which direction I am running and where is my head
	private int directionX = 0, directionY = -1, headX, headY;
	// the refresh rate
	private long refreshRate;

	Snake() {
		super("Snake Game");
		// to hold all the labels with a gap of 1 pixel
		JPanel panel = new JPanel(new GridLayout(SIZE, SIZE, 1, 1));
		// the same color as the Snake so the back will show a continuos snake
		panel.setBackground(Color.BLUE);
		panel.setBorder(BorderFactory.createLineBorder(Color.BLUE));
		// create all the JLabels
		initLabels(panel);
		
		// add the 4 foods
		initFood();
		// init the snake the length of a quarter of our size
		initSnake(SIZE / 4);
		add(panel, BorderLayout.CENTER);
		
		// label if I am running well and size of the snake
		statusLabel.setHorizontalAlignment(SwingConstants.CENTER);
		statusLabel.setOpaque(true);
		statusLabel.setBackground(Color.YELLOW);
		add(statusLabel, BorderLayout.NORTH);
		// listener so the pause button will work
		pauseResume.setForeground(Color.RED);
		pauseResume.addActionListener(this);
		add(pauseResume, BorderLayout.SOUTH);
		// general JFrame stuff its size and visible
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		setSize(SIZE * WIDTH - 5, SIZE * WIDTH - 5);
		setVisible(true);
		// start the thread that move the snakes
		Thread thread = new Thread(this);
		thread.start();
	}
	
	// to initialize the labels filling the grid
	// this method is called only once
	private void initLabels(JPanel p) {
		for(int j = 0; j < SIZE; j++) {
			for(int i = 0; i < SIZE; i++) {
				label[i][j] = new SnakeLabel(i, j);
				p.add(label[i][j]);
			}
		}
		
	}
	// to put the initial 4 food labels in 1/4 of the grid
	// this method will be called only once
	private void initFood() {
		// north/west at 1/4 of the grid
		int x = SIZE / 4;
		int y = x;
		label[x][y].setFood(true);
		// north east so x is the same delta from the end
		x = SIZE - x;
		label[x][y].setFood(true);
		// south/west
		y = x;
		x = SIZE / 4;
		label[x][y].setFood(true);
		// south east
		x = y;
		label[x][y].setFood(true);		
	}
	
	// to position the snake at the beginning of game
	private void initSnake(int length) {
		// the snake in the middle of everything initial size of length
		headX = SIZE / 2;
		headY = (SIZE / 2) - (length / 2);
		// declare these label as holding the snake
		// registering them in the arrayList
		for(int i = headY; i < headY + length; i++)
			snakeList.addLabel(label[headX][i]);
		// set the color for the first time
		snakeList.updateColor();
	}
		
	// run as a thread to move the snake
	public void run() {
		for(;;) {
			// wait according to snake length... determine an empiric way
			refreshRate = 5000L / (snakeList.size() / 2);
			// manage to have at least a minimum refresh rate
			if(refreshRate < 10L)
				refreshRate = 10L;
			
			try {
				Thread.sleep(refreshRate);
			}
			catch(Exception e) {}
			// if the "Pause" button was pushed go to next turn
			if(pause)
				continue;
			// update snake head position
			headX += directionX;
			headY += directionY;
			// check if we are going out of bound
			if(headX < 0 || headX >= SIZE || headY < 0 || headY >= SIZE) {
				statusLabel.setText("Out of bound... length: " + snakeList.size());
				return;
			}
			// check if we are eating ourself
			if(label[headX][headY].isSnake) {
				statusLabel.setText("You eat yourself... length: " + snakeList.size());
				return;
			}
			// if we hit food
			if(label[headX][headY].isFood) {
				snakeList.updateHead();     // without updating tail
				// found new place for food
				for(;;) {
					int x = random.nextInt(SIZE);
					int y = random.nextInt(SIZE);
					// start again if new random place is already in the snake or is already food
					if(label[x][y].isFood)
						continue;
					if(label[x][y].isSnake)
						continue;
					// ok good dpot not used yet/// declare it as food
					label[x][y].setFood(true);
					break;
				}
			}
			else {
				snakeList.updateTail();     // first if I pass on my back
				snakeList.updateHead();
			}
			statusLabel.setText("Running... snake length: " + snakeList.size() 
					                        + "    Refresh Rate: " + refreshRate + "/1000 sec");
		}
	}
	// called when the pause/resume button is hit
	public void actionPerformed(ActionEvent e) {
		// change the state
		pause = !pause;
		// updtae button text accordingly
		if(pause)
			pauseResume.setText("Resume");
		else
			pauseResume.setText("Pause");
	}
	// to run the whole damned thing
	public static void main(String[] args) {

		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				new Snake();
			}
		});
	}
	// array list that contains the position taken by the snake
	private class SnakeList extends ArrayList<SnakeLabel> {
		private static final long serialVersionUID = 654321L;  // to avoid compile warning
		// the 2 colours of the snake
		private Color[] snakeColor = {new Color(0, 0, 255), new Color(75, 75, 255)};
		// for initial build when we install the snake first positions
		private boolean addLabel(SnakeLabel sl) {
			// flag the SnakeLabel as being part of the Snake
			sl.setSnake(true);
			// call father's method to add an object into the arrayList
			return add(sl);
		}
		// remove (make free) the tail of the snake
		private void updateTail() {
			SnakeLabel sl = remove(size() - 1);
			sl.setSnake(false);
		}
		// to update the head to new position
		private void updateHead() {
			// declare it as part of the snake
			label[headX][headY].setSnake(true);
			// add it to the head of the snake
			add(0, label[headX][headY]);
			// switch colors
			updateColor();
		}
		
		// to put 2 colours in the snake
		private void updateColor() {
			int len = size();
			// the head in black
			get(0).setBackground(Color.BLACK);
			// variation of blue for the others
			for(int i = 1; i < len; i++) {
				get(i).setBackground(snakeColor[i % snakeColor.length]);
			}
		}
	} // end of inner class SnakeList
	
	// specialized JLabel to keep coordinate X and Y and have a mouseListener if cliked
	private class SnakeLabel extends JLabel implements MouseListener{
		private static final long serialVersionUID = 123456L;   // to avoid compile warning
		// my coordinates on the Grid so the mouseListener can know where I am situate
		private int x, y;
		// init not food and not snake
		boolean isFood = false;         // not a food case
		boolean isSnake = false;        // not part of the snake
		// constructor
		SnakeLabel(int x, int y) {
			super("");
			// save my coordinates
			this.x = x; this.y = y;
			// opaque because I use background to represent myself
			setOpaque(true);
			// so I will react if I am cleicked
			addMouseListener(this);
			// default color
			setBackground(Color.WHITE);
		}
		// declares me as a food case or not
		private void setFood(boolean state) {
			isFood = state;
			// change color accordingly
			if(state)
				setBackground(Color.RED);       // food color
			else
				setBackground(Color.WHITE);     // regular color
		}
		// declares me as part of the snake or not
		private void setSnake(boolean state) {
			isSnake = state;
			// change my color accordingly
			if(state) {
				setFood(false);      // make it available again as food slot
				setBackground(Color.BLUE);
			}
			else {
				setBackground(Color.WHITE);
			}
		}
		
		// ok click on me I just chek for the Pressed event to react faster
		// (important when the snake goes very fast)
		public void mousePressed(MouseEvent e) {
			// so am I going vertically or horitonaly
			if(directionX == 0) {
				// if clicked on my same X nothing to do
				if(x == headX)
					return;
				directionY = 0;				// stop updating Y
				// if clicked to my left
				if(x < headX) {
					// update the way I am going
					directionX = -1;
				}	
				else  {   // so to my rioght
					directionX = +1;				
				}
			}
			else {                  // OK I am moving horizontaly
				if(y == headY)
					return;
				directionX = 0;
				if(y < headY) {     // to my left
					directionY = -1;
				}
				else {     // to my right
					directionY = +1;
				}				
			}
		}
		// do not react to those but as I implement MouseListener have have to implement these
		public void mouseEntered(MouseEvent arg0) {}
		public void mouseExited(MouseEvent arg0) {}
		public void mouseClicked(MouseEvent arg0) {}
		public void mouseReleased(MouseEvent arg0) {}
		
	}  // end inner class SnakeLabel
}


Is This A Good Question/Topic? 0
  • +

Replies To: Snake Game

#2 pbl   User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8379
  • View blog
  • Posts: 31,956
  • Joined: 06-March 08

Re: Snake Game

Posted 27 July 2009 - 05:34 PM

Description: Many times we have in the forum questions on how to implement a Snake game. In this code snippet fully documented, you'll see how to use JLabels to display the snake and how to implement a thread to make the snake move.A small snake game using JLabels and SwingTimer
import javax.swing.*;
import java.awt.*;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Random;
import java.util.ArrayList;

public class Snake extends JFrame implements ActionListener {

	private static final long serialVersionUID = 123654L;   // just to avoid compile warning
	// number of squares in the grid, size of each of them
	private static final int SIZE = 25, WIDTH = 20;
	// the labels for displaying the snake and its foods
	private SnakeLabel[][] label = new SnakeLabel[SIZE][SIZE];
	// arrayList containing all of the snake position (Labels)
	private SnakeList snakeList = new SnakeList();
	// the label displaying if I am running or not and the size of the snake
	private JLabel statusLabel = new JLabel("Initializing");
	// random number generator to find a new spot for a new food when I eat one
	private Random random = new Random();
	// if I am in a pause state or not
	boolean pause = false;
	// the button to switch from pause to not pause
	private JButton pauseResume = new JButton("Pause");
	// which direction I am running and where is my head
	private int directionX = 0, directionY = -1, headX, headY;
	// the refresh rate
	private int refreshRate;
	// The Timer that moves the Snake
	private Timer timer;

	Snake() {
		super("Snake Game");
		// to hold all the labels with a gap of 1 pixel
		JPanel panel = new JPanel(new GridLayout(SIZE, SIZE, 1, 1));
		// the same color as the Snake so the back will show a continuos snake
		panel.setBackground(Color.BLUE);
		panel.setBorder(BorderFactory.createLineBorder(Color.BLUE));
		// create all the JLabels
		initLabels(panel);

		// add the 4 foods
		initFood();
		// init the snake the length of a quarter of our size
		initSnake(SIZE / 4);
		add(panel, BorderLayout.CENTER);

		// label if I am running well and size of the snake
		statusLabel.setHorizontalAlignment(SwingConstants.CENTER);
		statusLabel.setOpaque(true);
		statusLabel.setBackground(Color.YELLOW);
		add(statusLabel, BorderLayout.NORTH);
		// listener so the pause button will work
		pauseResume.setForeground(Color.RED);
		pauseResume.addActionListener(this);
		add(pauseResume, BorderLayout.SOUTH);
		// general JFrame stuff its size and visible
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		setSize(SIZE * WIDTH - 5, SIZE * WIDTH - 5);
		setVisible(true);
		// start the thread that move the snakes
		refreshRate = 5000 / (snakeList.size() / 2);
		timer = new Timer(refreshRate, this);
		timer.start();
	}

	// to initialize the labels filling the grid
	// this method is called only once
	private void initLabels(JPanel p) {
		for(int j = 0; j < SIZE; j++) {
			for(int i = 0; i < SIZE; i++) {
				label[i][j] = new SnakeLabel(i, j);
				p.add(label[i][j]);
			}
		}

	}
	// to put the initial 4 food labels in 1/4 of the grid
	// this method will be called only once
	private void initFood() {
		// north/west at 1/4 of the grid
		int x = SIZE / 4;
		int y = x;
		label[x][y].setFood(true);
		// north east so x is the same delta from the end
		x = SIZE - x;
		label[x][y].setFood(true);
		// south/west
		y = x;
		x = SIZE / 4;
		label[x][y].setFood(true);
		// south east
		x = y;
		label[x][y].setFood(true);		
	}

	// to position the snake at the beginning of game
	private void initSnake(int length) {
		// the snake in the middle of everything initial size of length
		headX = SIZE / 2;
		headY = (SIZE / 2) - (length / 2);
		// declare these label as holding the snake
		// registering them in the arrayList
		for(int i = headY; i < headY + length; i++)
			snakeList.addLabel(label[headX][i]);
		// set the color for the first time
		snakeList.updateColor();
	}

	// run 
	public void runSnake() {

		// wait according to snake length... determine an empiric way
		int oldRefreshRate = refreshRate;
		refreshRate = 5000 / (snakeList.size() / 2);
		// manage to have at least a minimum refresh rate
		if(refreshRate < 10)
			refreshRate = 10;
        // if refresh rate changed, update Timer delay
		if(oldRefreshRate != refreshRate)
			timer.setDelay(refreshRate);
		
		// if the "Pause" button was pushed go to next turn
		if(pause)
			return;
		// update snake head position
		headX += directionX;
		headY += directionY;
		// check if we are going out of bound
		if(headX < 0 || headX >= SIZE || headY < 0 || headY >= SIZE) {
			statusLabel.setText("Out of bound... length: " + snakeList.size());
			timer.stop();
			pauseResume.setEnabled(false);
			return;
		}
		// check if we are eating ourself
		if(label[headX][headY].isSnake) {
			statusLabel.setText("You eat yourself... length: " + snakeList.size());
			timer.stop();
			pauseResume.setEnabled(false);
			return;
		}
		// if we hit food
		if(label[headX][headY].isFood) {
			snakeList.updateHead();     // without updating tail
			// found new place for food
			for(;;) {
				int x = random.nextInt(SIZE);
				int y = random.nextInt(SIZE);
				// start again if new random place is already in the snake or is already food
				if(label[x][y].isFood)
					continue;
				if(label[x][y].isSnake)
					continue;
				// ok good dpot not used yet/// declare it as food
				label[x][y].setFood(true);
				break;
			}
		}
		else {
			snakeList.updateTail();     // first if I pass on my back
			snakeList.updateHead();
		}
		statusLabel.setText("Running... snake length: " + snakeList.size() 
				+ "    Refresh Rate: " + refreshRate + "/1000 sec");

	}
	// called when the pause/resume button is hit
	public void actionPerformed(ActionEvent e) {
		Object o = e.getSource();
		if(o == pauseResume) {
			// change the state
			pause = !pause;
			// update button text accordingly
			if(pause) {
				pauseResume.setText("Resume");
				timer.stop();
			}
			else {
				pauseResume.setText("Pause");
				timer.restart();
			}
			return;
		}
		else {    // it is the Timer if it is not the pauseResume button
			runSnake();
		}
	}
	// to run the whole damned thing
	public static void main(String[] args) {

		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				new Snake();
			}
		});
	}
	// array list that contains the position taken by the snake
	private class SnakeList extends ArrayList<SnakeLabel> {
		private static final long serialVersionUID = 654321L;  // to avoid compile warning
		// the 2 colours of the snake
		private Color[] snakeColor = {new Color(0, 0, 255), new Color(75, 75, 255)};
		// for initial build when we install the snake first positions
		private boolean addLabel(SnakeLabel sl) {
			// flag the SnakeLabel as being part of the Snake
			sl.setSnake(true);
			// call father's method to add an object into the arrayList
			return add(sl);
		}
		// remove (make free) the tail of the snake
		private void updateTail() {
			SnakeLabel sl = remove(size() - 1);
			sl.setSnake(false);
		}
		// to update the head to new position
		private void updateHead() {
			// declare it as part of the snake
			label[headX][headY].setSnake(true);
			// add it to the head of the snake
			add(0, label[headX][headY]);
			// switch colors
			updateColor();
		}

		// to put 2 colours in the snake
		private void updateColor() {
			int len = size();
			// the head in black
			get(0).setBackground(Color.BLACK);
			// variation of blue for the others
			for(int i = 1; i < len; i++) {
				get(i).setBackground(snakeColor[i % snakeColor.length]);
			}
		}
	} // end of inner class SnakeList

	// specialized JLabel to keep coordinate X and Y and have a mouseListener if cliked
	private class SnakeLabel extends JLabel implements MouseListener{
		private static final long serialVersionUID = 123456L;   // to avoid compile warning
		// my coordinates on the Grid so the mouseListener can know where I am situate
		private int x, y;
		// init not food and not snake
		boolean isFood = false;         // not a food case
		boolean isSnake = false;        // not part of the snake
		// constructor
		SnakeLabel(int x, int y) {
			super("");
			// save my coordinates
			this.x = x; this.y = y;
			// opaque because I use background to represent myself
			setOpaque(true);
			// so I will react if I am cleicked
			addMouseListener(this);
			// default color
			setBackground(Color.WHITE);
		}
		// declares me as a food case or not
		private void setFood(boolean state) {
			isFood = state;
			// change color accordingly
			if(state)
				setBackground(Color.RED);       // food color
			else
				setBackground(Color.WHITE);     // regular color
		}
		// declares me as part of the snake or not
		private void setSnake(boolean state) {
			isSnake = state;
			// change my color accordingly
			if(state) {
				setFood(false);      // make it available again as food slot
				setBackground(Color.BLUE);
			}
			else {
				setBackground(Color.WHITE);
			}
		}

		// ok click on me I just chek for the Pressed event to react faster
		// (important when the snake goes very fast)
		public void mousePressed(MouseEvent e) {
			// so am I going vertically or horitonaly
			if(directionX == 0) {
				// if clicked on my same X nothing to do
				if(x == headX)
					return;
				directionY = 0;				// stop updating Y
				// if clicked to my left
				if(x < headX) {
					// update the way I am going
					directionX = -1;
				}	
				else  {   // so to my rioght
					directionX = +1;				
				}
			}
			else {                  // OK I am moving horizontaly
				if(y == headY)
					return;
				directionX = 0;
				if(y < headY) {     // to my left
					directionY = -1;
				}
				else {     // to my right
					directionY = +1;
				}				
			}
		}
		// do not react to those but as I implement MouseListener have have to implement these
		public void mouseEntered(MouseEvent arg0) {}
		public void mouseExited(MouseEvent arg0) {}
		public void mouseClicked(MouseEvent arg0) {}
		public void mouseReleased(MouseEvent arg0) {}

	}  // end inner class SnakeLabel
}


Was This Post Helpful? 0
  • +
  • -

#3 pbl   User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8379
  • View blog
  • Posts: 31,956
  • Joined: 06-March 08

Re: Snake Game

Posted 08 November 2011 - 08:33 PM

Modified to use Swing.Timer rather than Thread
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1