Page 1 of 1

Java Game Programming – Part 1: Tic Tac Toe

#1 Dogstopper  Icon User is offline

  • The Ninjaducky
  • member icon



Reputation: 2873
  • View blog
  • Posts: 11,032
  • Joined: 15-July 08

Posted 06 December 2009 - 11:47 AM

*
POPULAR

Java Game Programming – Part 1: Tic Tac Toe
Written by Stephen Schwahn on 12/6/09

Many people, relatively often degrade Java for its game-making abilities. Some say its too slow, others say that it is too slow, and that a low level language like C++ is better. Well, sometimes these clams can be true, and other times, these claims can be sorely mistaken. Regardless, we are about to embark on a game making adventure with something quite simple, Tic Tac Toe.

The Array Issues I Faced
One very important issue to talk about before we start is the complexity of multidimensional arrays. In Java, there are arrays of arrays, which can often be used like a matrix or a grid in this game's case. I just would like to go over these arrays really quickly before we begin.
int[][] array = new int[2][3];
array[1][2] = 6;



In this example, we set the value 6 to the array at row 1 (actually row 2 because array indexes begin at 0) and column number 2 (actually 3). Sometimes, this concept can be difficult to understand and can be confusing, but try to bear with me as I go through this tutorial and just try to understand how it all works. Well, thats all, so let's begin the first installment of game programming in Java. Let us first look at the framework for this program.

The Framework
This program will consist of 3 classes: TTTFrame which is the root window class, TTTPanel is the Tic Tac Toe playing board, and MouseHandler is an internal class that handles all of the input. The benefit of these nested classes allows for the usage of private variables and methods, but still maintaining a good object-oriented structure, and being able to separate methods from one another. In any game more advanced than this, I would separate the game pieces into their own separate classes, but this program uses a simple numeric array to manage the game pieces instead.

When the user clicks a certain spot on the screen, we find which part of the grid was clicked and add the players “number” (either 1 or 2) to the corresponding part of the game array. Then, the screen is repainted, and in that method, the grid is cycled through and if there is a player's number in that spot in the array, then we paint their game piece in that corresponding part of the game's main panel. This will make far more sense when you see it in code. Finally, if there is a winner, then we halt user input (because there are empty cells), then we tell which player won and wait for the game to be reset. Alright, enough planning, let's dive in.

The Code
Well, now that I've taken care of that monster, lets begin our experience by setting up out main object, the Tic Tac Toe panel where all the drawing occurs.

Let's start with the instance variables.
class TTTPanel extends JPanel {

	// Constants to help with pixel to grid conversions
	public static final int PANELWIDTH  = 600;
	public static final int PANELHEIGHT = 600;
	public static final int GRIDWIDTH   = PANELWIDTH/3;
	public static final int GRIDHEIGHT  = PANELHEIGHT/3;

	// This is the game array
	// 0's mean blank, 1's mean “X”, and 2's mean “O”
	int[][] game;

	// Control the current player, player 1 is “X”, player 2 is “O”
	int player;

	// Establish whether game is done or not. This halts user input
	// until the game is reset
	boolean gameWon;

	// “X” and “Y” images
	Image xImage;
	Image yImage;



Right, I hope the comments explain all of that. Note that our class extends javax.swing.JPanel so that it is easier to paint onto it. Now without further ado, let's examine the constructor.
public TTTPanel() {
		setSize(PANELWIDTH, PANELHEIGHT);
		
		// Set up a new game and load it with blanks
		game = new int[3][3];
		for (int i = 0; i < game.length; i++)
			for (int j = 0; j < game[i].length; j++)
				game[i][j] = 0;

		// Set the game to not won, so that it will accept user input
		gameWon = false;

		// Set the first player to player 1
		player = 1;

		// Load the images from file
		xImage = Toolkit.getDefaultToolkit().createImage("x.png");
		yImage = Toolkit.getDefaultToolkit().createImage("o.png");
		
		// Prepares the images for painting
		prepareImage(xImage, this);
		prepareImage(yImage, this);

		// Set up the user's input
		// MouseHandler is another class that handles all of the		
		// mouse input and updates the game array.
		addMouseListener(new MouseHandler(this));
	}



The images that I used (which are dreadful crap, no joke) are included at the end of this tutorial; feel free to use your own and name them the same and stick them in the current directory and your all set.

Notice the use of the prepareImage() method. I ran into a problem by not including this – every first click of every different image was not rendered, instead it was spent preparing the image. Not so anymore. Now let's look at the next three methods. They involve repainting the screen appropriately.
	public void paintComponent(Graphics g) {
		// Make sure that the images are loaded, or kill the program
		if (xImage == null || yImage == null)
			System.exit(1);
		paintGrid(g);
		paintGame(g);
	}

	private void paintGrid(Graphics g) {
		// Horizontal lines
		g.drawLine(5, GRIDHEIGHT, PANELWIDTH-5, GRIDHEIGHT);
		g.drawLine(5, 2*GRIDHEIGHT, PANELWIDTH-5, 2*GRIDHEIGHT);
		
		// Vertical lines
		g.drawLine(GRIDWIDTH, 5, GRIDWIDTH, PANELHEIGHT-5);
		g.drawLine(2*GRIDWIDTH, 5, 2*GRIDWIDTH, PANELHEIGHT-5);
	}

	private void paintGame(Graphics g) {
		g.setColor(Color.lightGray); // The background color.

		for (int i = 0; i < game.length; i++) {
			for (int j = 0; j< game[i].length; j++) {
				
				// If there is an “x” then draw it in that spot
				if (game[i][j] == 1) {
					g.drawImage(xImage, i*GRIDWIDTH, 
								   j*GRIDHEIGHT,null);
				}
				// Otherwise, draw an “O”
				else if (game[i][j] == 2) {
					g.drawImage(yImage, i*GRIDWIDTH, 
								   j*GRIDHEIGHT, null);
				}
				// If that spot in the grid has none, then clear
				// That grid spot. Notice the 1's? That is to prevent
				// it from accidentally painting over the grid lines
				else
					g.fillRect(i*GRIDWIDTH+1, j*GRIDHEIGHT+1,
								   GRIDWIDTH-1, GRIDHEIGHT-1);
			}
		}
	}



I hope you understood that. The paintComponent method is subclassed from JPanel and is called whenever the repaint method or whenever it feels like it needs to update. The constants that are in use here help to draw the image at the row location, but because the screen is 600 pixels, it needs to be inflated with these constants. If you don't understand, try taking them out to see what happens.

Alright, time to get to the meat of the program. The MouseHandler class is what is know as an inner class, meaning it is actually located inside of another (in this case, TTTPanel). Make sure to include it right after the code I just gave above and not outside of the TTPanel class. Inner classes have complete access to public, private, and protected methods and variables of the class encasing it. Now, let's take a look!
class MouseHandler implements MouseListener {

		// The reference to the panel so that you can repaint it.
		TTTPanel pane;

		// Sets the TTTPanel object to the pane.
		public MouseHandler(TTTPanel panel) {
			pane = panel;
		}

		// This handles all the input in th entire game! Are you ready
		// for this?!
		public void mouseClicked(MouseEvent e) {
			if (!gameWon) { // If the game is still accepting input.

				// Take the pixel location and change to the grid's
				// value. e.g: a 0, 1, or 2
				int column = e.getX()/GRIDWIDTH;
				int row = e.getY()/GRIDHEIGHT;

				// Only if that cell is empty!
				if (game[column][row] == 0) {
					// Mark that the current player has that cell
					game[column][row] = player;

					// Check to see if anybody has won or a cat's game
					checkWin();

					// Switch up the players
					if (player == 1)
						player = 2;
					else if (player == 2)
						player = 1;
				}
				pane.repaint();
			
			}
		}
		// These methods do nothing, but are required by the
		// MouseListener interface
		public void mousePressed(MouseEvent e) {}
		public void mouseReleased(MouseEvent e) {}
		public void mouseEntered(MouseEvent e) {}
		public void mouseExited(MouseEvent e) {}

		// Here is the real pain in the butt, but this code checks
		// horizontally, vertically, and diagonally. This code IS 
		// difficult to understand but bear with it, and try a few
		// experiments on your own to figure out how exactly it's	
		// working. Often, that's the best way to learn algorithms
		private void checkWin() {

			// Check horizontal
			for (int i = 0; i < game.length; i++) {
				boolean rowWin = true;
				for (int j = 0; j < game[i].length; j++) {
					if (game[j][i] != player)
						rowWin = false;
				}
				if (rowWin == true) {
					displayWin();
					return;
				}
			}

			// Check vertical
			for (int i = 0; i < game.length; i++) {
				boolean rowWin = true;
				for (int j = 0; j < game[i].length; j++) {
					if (game[i][j] != player)
						rowWin = false;
				}
				if (rowWin == true) {
					displayWin();
					return;
				}
			}

			// Check diagonal
			boolean topWin = true;
			boolean bottomWin = true;
			for (int i = 0; i < game.length; i++) {
				if (game[i][i] != player)
					topWin = false;
				if (game[(game.length-(i+1))][i] != player)
					bottomWin = false;
			}
			if (topWin == true || bottomWin == true) {
				displayWin();
				return;
			}

			// If the array is full and there is no winner, it's a 
			//cat's game
			boolean isFull = true;
			for (int i = 0; i < game.length; i++)
				for (int j = 0; j < game[i].length; j++)
					if (game[i][j] == 0)
						isFull = false;
			if (isFull) {
				pane.repaint();
				// Notify the user that it was a tie game.
				JOptionPane.showMessageDialog
					(pane, "Cat's game!",
					"Tie", JOptionPane.INFORMATION_MESSAGE);
				return;
			}

		}

		// Tell the user who won
		private void displayWin() {
			// Since not all the spaces are taken, end user input.
			gameWon = true; 
			pane.repaint(); // Paint screen before you notify winner		 
							// (for appearance purpose only)
			JOptionPane.showMessageDialog
					(pane, "Player " + player + " wins!",
					"Winner", JOptionPane.INFORMATION_MESSAGE);
			return;
		}

	} // End of MouseHandler class
} // End of TTTPanel class



Alright, my comments explained that monster. Now there are two more things that must happen: add a reset method to start over again and to add this to a JFrame. Lets start with the latter. Add the following code to the TTTPanel class.
	public void reset() {
		gameWon = false;
		player = 1;
		for (int i = 0; i < game.length; i++)
			for (int j = 0; j < game[i].length; j++)
				game[i][j] = 0;
		repaint();
	}



And now, finally set up a simple JFrame class to take care of it all:
public class TTTFrame extends JFrame {

	TTTPanel ticTacToe;

	public TTTFrame() {

		// Set up the simple GUI elements
		super("Tic Tac Toe");
		setMinimumSize(new Dimension(610,670)); // Give a little space
		setSize(600,670);
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

		// Set up the Tic Tac Toe panel
		ticTacToe = new TTTPanel();
		add(ticTacToe, BorderLayout.CENTER);

		// Set up the bottom button with a reset button
		JPanel bottom = new JPanel();
		bottom.setBackground(Color.darkGray);
		JButton resetButton = new JButton("Reset");
		bottom.add(resetButton);
		add(bottom, BorderLayout.SOUTH);

		// Make the button reset the panel
		resetButton.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				ticTacToe.reset();
				ticTacToe.requestFocus();
			}

		});

		ticTacToe.requestFocus(); // Start the focus on TTTPanel
		
		// Finally, make the frame visible!
		setVisible(true);
	}
	
	public static void main(String[] args) {
		new TTTFrame();
	}

}



OH! And for those of you that didn't quite keep up, here is the code for both files (including package imports):
TTTFrame.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class TTTFrame extends JFrame {

	TTTPanel ticTacToe;

	public TTTFrame() {

		// Set up the simple GUI elements
		super("Tic Tac Toe");
		setMinimumSize(new Dimension(610,670));
		setSize(600,670);
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

		// Set up the Tic Tac Toe panel
		ticTacToe = new TTTPanel();
		add(ticTacToe, BorderLayout.CENTER);

		// Set up the bottom button
		JPanel bottom = new JPanel();
		bottom.setBackground(Color.darkGray);
		JButton resetButton = new JButton("Reset");
		bottom.add(resetButton);
		add(bottom, BorderLayout.SOUTH);

		// Make the button reset the panel
		resetButton.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				ticTacToe.reset();
				ticTacToe.requestFocus();
			}

		});

		ticTacToe.requestFocus();
		
		// Finally, make the frame visible!
		setVisible(true);
	}
	
	public static void main(String[] args) {
		new TTTFrame();
	}

}



And here is TTTPanel.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class TTTPanel extends JPanel {

	// Panel handlers for math
	public static final int PANELWIDTH  = 600;
	public static final int PANELHEIGHT = 600;
	public static final int GRIDWIDTH   = PANELWIDTH/3;
	public static final int GRIDHEIGHT  = PANELHEIGHT/3;

	// This is the game array
	int[][] game;

	// Control the current player
	int player;

	// Establish whether game is done or not.
	boolean gameWon;

	// X and Y images
	Image xImage;
	Image yImage;

	public TTTPanel() {
		setSize(PANELWIDTH, PANELHEIGHT);
		
		// Set up a new game and load it with blanks
		game = new int[3][3];
		for (int i = 0; i < game.length; i++)
			for (int j = 0; j < game[i].length; j++)
				game[i][j] = 0;

		gameWon = false;

		// Set the first player to 1
		player = 1;

		// Load the images
		xImage = Toolkit.getDefaultToolkit().createImage("x.png");
		yImage = Toolkit.getDefaultToolkit().createImage("o.png");

		// Prepares them so that they can be instantly painted
		prepareImage(xImage, this);
		prepareImage(yImage, null);

		// Set up the user's input
		addMouseListener(new MouseHandler(this));
	}
	
	public void reset() {
		gameWon = false;
		player = 1;
		for (int i = 0; i < game.length; i++)
			for (int j = 0; j < game[i].length; j++)
				game[i][j] = 0;
		repaint();
	}
	
	@Override
	public void paintComponent(Graphics g) {
		if (xImage == null || yImage == null)
			System.exit(1);
		paintGrid(g);
		paintGame(g);
	}

	private void paintGrid(Graphics g) {
		// Horizontal
		g.drawLine(5, GRIDHEIGHT, PANELWIDTH-5, GRIDHEIGHT);
		g.drawLine(5, 2*GRIDHEIGHT, PANELWIDTH-5, 2*GRIDHEIGHT);
		
		// Vertical
		g.drawLine(GRIDWIDTH, 5, GRIDWIDTH, PANELHEIGHT-5);
		g.drawLine(2*GRIDWIDTH, 5, 2*GRIDWIDTH, PANELHEIGHT-5);
	}

	private void paintGame(Graphics g) {
		g.setColor(Color.lightGray);

		for (int i = 0; i < game.length; i++) {
			for (int j = 0; j< game[i].length; j++) {
				
				if (game[i][j] == 1) {
					g.drawImage(xImage, i*GRIDWIDTH, j*GRIDHEIGHT, null);
				}
				else if (game[i][j] == 2) {
					g.drawImage(yImage, i*GRIDWIDTH, j*GRIDHEIGHT, null);
				}
				else
					g.fillRect(i*GRIDWIDTH+1, j*GRIDHEIGHT+1, GRIDWIDTH-1, GRIDHEIGHT-1);
			}
		}
	}

	class MouseHandler implements MouseListener {

		TTTPanel pane;

		public MouseHandler(TTTPanel panel) {
			pane = panel;
		}

		public void mouseClicked(MouseEvent e) {
			if (!gameWon) {

				int column = e.getX()/GRIDWIDTH;
				int row = e.getY()/GRIDHEIGHT;

				if (game[column][row] == 0) {
					game[column][row] = player;

					checkWin();

					// Switch up the players
					if (player == 1)
						player = 2;
					else if (player == 2)
						player = 1;
				}
				pane.repaint();
			
			}
		}
		public void mousePressed(MouseEvent e) {}
		public void mouseReleased(MouseEvent e) {}
		public void mouseEntered(MouseEvent e) {}
		public void mouseExited(MouseEvent e) {}

		private void checkWin() {

			// Check horizontal
			for (int i = 0; i < game.length; i++) {
				boolean rowWin = true;
				for (int j = 0; j < game[i].length; j++) {
					if (game[j][i] != player)
						rowWin = false;
				}
				if (rowWin == true) {
					displayWin();
					return;
				}
			}

			// Check vertical
			for (int i = 0; i < game.length; i++) {
				boolean rowWin = true;
				for (int j = 0; j < game[i].length; j++) {
					if (game[i][j] != player)
						rowWin = false;
				}
				if (rowWin == true) {
					displayWin();
					return;
				}
			}

			// Check diagonal
			boolean topWin = true;
			boolean bottomWin = true;
			for (int i = 0; i < game.length; i++) {
				if (game[i][i] != player)
					topWin = false;
				if (game[(game.length-(i+1))][i] != player)
					bottomWin = false;
			}
			if (topWin == true || bottomWin == true) {
				displayWin();
				return;
			}

			// If the array is full and there is no winner, it's a cat's game
			boolean isFull = true;
			for (int i = 0; i < game.length; i++)
				for (int j = 0; j < game[i].length; j++)
					if (game[i][j] == 0)
						isFull = false;
			if (isFull) {
				pane.repaint();
				JOptionPane.showMessageDialog
					(pane, "Cat's game!",
					"Tie", JOptionPane.INFORMATION_MESSAGE);
				return;
			}

		}

		private void displayWin() {
			gameWon = true;
			pane.repaint();
			JOptionPane.showMessageDialog
					(pane, "Player " + player + " wins!",
					"Winner", JOptionPane.INFORMATION_MESSAGE);
			return;
		}

	}
}



Oh yah! The crappy graphics! Better off making them yourself... (make sure that the background is transparent, as this program does not set a single color transparent as of yet.)



See you all later!



Is This A Good Question/Topic? 5
  • +

Replies To: Java Game Programming – Part 1: Tic Tac Toe

#2 Dogstopper  Icon User is offline

  • The Ninjaducky
  • member icon



Reputation: 2873
  • View blog
  • Posts: 11,032
  • Joined: 15-July 08

Posted 12 December 2009 - 09:31 PM

Oh...here are the graphics.
Attached Image
Attached Image
Was This Post Helpful? 0
  • +
  • -

#3 Guest_ahmed*


Reputation:

Posted 08 February 2010 - 08:37 AM

i am very sory for bad language....
i donot want from you to send me the code i just ask
because i begin the code using polygon for the area of the game i want to know it is good or there is better than...
i am beginner in the java and in the programming
thank you....
Was This Post Helpful? 0

#4 Guest_Aaron*


Reputation:

Posted 29 October 2010 - 10:07 PM

Somehow after a mouse click, the image does not show. Sometimes, instead, the reset button appears in the top left corner.
The dark gray background shows along with the lines.
I copied the same code as shown and went over it a couple of times.
I created my images in illustrator and saved them as png (193x193).

It would be appreciated if you can tell me why this is so?

Thanks in advance.

Note: I will try to attach the images I made.

Attached image(s)

  • Attached Image
  • Attached Image

Was This Post Helpful? 0

#5 v0rtex  Icon User is offline

  • Caffeine: db "Never Enough!"
  • member icon

Reputation: 223
  • View blog
  • Posts: 773
  • Joined: 02-June 10

Posted 27 April 2011 - 01:02 PM

Really nice tutorial, helped a lot :) + rep :P
Was This Post Helpful? 0
  • +
  • -

#6 DivideByZero  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 238
  • View blog
  • Posts: 551
  • Joined: 02-December 10

Posted 24 May 2011 - 03:51 AM

Great tutorial :)
I'm trying out Java again now that my second year at uni is over.
I have one question, in XNA you add any images to the content folder and add a line of code to store it into a texture2D variable for use.
How do I do this in Java? I'm using Eclipse.
If I can get this done I can finish the tutorial :)

Thanks for any help.

This post has been edited by DivideByZero: 24 May 2011 - 11:00 AM

Was This Post Helpful? 0
  • +
  • -

#7 Dogstopper  Icon User is offline

  • The Ninjaducky
  • member icon



Reputation: 2873
  • View blog
  • Posts: 11,032
  • Joined: 15-July 08

Posted 24 May 2011 - 12:31 PM

In Eclipse, you would you File...Import...File System. From there, it's easy enough. Just stick them in a directory or sub-directory and refer to the project root as /
Was This Post Helpful? 1
  • +
  • -

#8 DivideByZero  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 238
  • View blog
  • Posts: 551
  • Joined: 02-December 10

Posted 28 May 2011 - 11:57 AM

View PostAaron, on 29 October 2010 - 10:07 PM, said:

Somehow after a mouse click, the image does not show. Sometimes, instead, the reset button appears in the top left corner.
The dark gray background shows along with the lines.

Now that I managed to get the images into eclipse.
I too am having this problem :(
Was This Post Helpful? 0
  • +
  • -

#9 Dogstopper  Icon User is offline

  • The Ninjaducky
  • member icon



Reputation: 2873
  • View blog
  • Posts: 11,032
  • Joined: 15-July 08

Posted 28 May 2011 - 12:38 PM

Then post in the Java forum with the code you have, the setup you have and the errors you are getting. One of us will get around to helping you.
Was This Post Helpful? 0
  • +
  • -

#10 Ipodhero178  Icon User is offline

  • D.I.C Regular

Reputation: 5
  • View blog
  • Posts: 286
  • Joined: 22-February 09

Posted 04 June 2011 - 02:45 PM

I was curious as to how your vertical worked. It seems like it is the same code as the horizontal.
Was This Post Helpful? 0
  • +
  • -

#11 Dogstopper  Icon User is offline

  • The Ninjaducky
  • member icon



Reputation: 2873
  • View blog
  • Posts: 11,032
  • Joined: 15-July 08

Posted 04 June 2011 - 02:49 PM

Yep, the difference is this:
 if (game[j][i] != player)
 // vs.
 if (game[i][j] != player)

Was This Post Helpful? 0
  • +
  • -

#12 Ipodhero178  Icon User is offline

  • D.I.C Regular

Reputation: 5
  • View blog
  • Posts: 286
  • Joined: 22-February 09

Posted 04 June 2011 - 02:58 PM

View PostDogstopper, on 04 June 2011 - 03:49 PM, said:

Yep, the difference is this:
 if (game[j][i] != player)
 // vs.
 if (game[i][j] != player)


I see. I am working on my own Tic Tac Toe game, and had a hard time at the cell check. Do you happen to know how to not have symbols overlap?

This post has been edited by Ipodhero178: 04 June 2011 - 03:19 PM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1