CS 162, Spring 2004
Programming Assignment 2
Due: Thursday, April 15

Memory Game

The memory game is a childrens game, often played with cards or tiles. The tiles (or cards) have a face up or face down, come in pairs, and are initally randomly arranged all face down. The player selects one tile, which is fliped over so the player can see the face. Then the player selects another. If they match then both are removed. If they do not match then both are fliped back. The object of the game is to remove all the tiles in the fewest possible moves. (This is a one player game, in the two player game you keep the tiles and the object is to get as many tiles as possible).

In our version we will use graphics for the tiles. In order to make the game interesting, you will play against a clock, and the object will be to remove all the tiles before the clock ticks down.

Start by reading the text chapters 9 and 10. Play close attention to Section 10.5, although I'll be giving you a lot of the code. We are developing applications, not applets, but most of the commands that Cay discusses work the same for both.

Our tiles will be represented by rectangles of colors, 75 pixels wide and and 40 pixes high. Our playing surface will be 400 pixels wide and 300 high. As Horstmann describes in Section 10.5, the first step in your application is to create a JFrame (a window that includes features such as menu bars, a title, and the like) that includes a Panel (a drawing surface). I'll show you this bit of code (I've eliminated the comments, but kept them in the skeleton file):


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

public class Memory 
{
	public static void main(String [] args) 
	{
		JFrame frame = new JFrame();
		frame.setTitle("Memory Game");
		frame.setSize(400, 300);
		frame.setDefaultCloseOperation(3); // EXIT_ON_CLOSE
		MyPanel board = new MyPanel();
		frame.setContentPane(board);
		frame.show();
	}
}

class MyPanel extends JPanel 
{

	public MyPanel() {
		// ...
	}

	private Tile[][] tiles = new Tile[4][4];
	private int counter = 30;
	private String text = "Click on Tile";

	public void paintComponent(Graphics g) 
	{
      		int text_location_x = 200;
      		int text_location_y = 250;
      		super.paintComponent(g);
      		g.drawString(text, text_location_x, text_location_y);
		for (int i = 0; i < 4; i++)
			for (int j = 0; j < 4; j++)
				tiles[i][j].draw(g);
	}

	public class MyCounter implements ActionListener 
	{
		public void actionPerformed(ActionEvent event) 
		{
			// ...
			repaint();
		}
	}

	class MouseSpy extends MouseAdapter 
	{
		public void mouseClicked(MouseEvent event)
		{
			int x = event.getX();
			int y = event.getY();
			// ...
			repaint();
		}
	}
}

The class MyPanel is where all the action takes place. I know we haven't really discussed inheritance yet, but we are only using that feature to a small extent here. The class MyPanel will inherit from the system class JPanel. When it comes time to draw the window, the function paintComponent will be called. There are two classes nested inside of MyPanel, MyCounter and MouseSpy. More on those in a moment.

What is our playing surface? It will consist of two parts. The first is a two-dimensional array of tiles. I'm calling this array tiles, and you can see the declaration in the code above. The second is a text message, which will be counting down until the game is finished. The paint command shown above displays these two items.

Since Tiles are just assigned a fixed position, they can actually be in any one of THREE states. The tile can be face up, face down, or empty (no longer in play). To make your life just a little bit easier, I'll provide the class Tile for you:


class Tile 
{
	public Tile(int ix, int iy, Color ic) 
	{
		x = ix;
		y = iy;
		state = DOWN;
		color = ic;
	}

	public static final int UP = 1;
	public static final int DOWN = 2;
	public static final int EMPTY = 3;

   	public static final int HEIGHT = 40;
   	public static final int WIDTH = 75;


	private Color color;
	private int x, y;
	private int state;

	public boolean stateIs(int s) 
	{
		return state == s;
	}

	public boolean sameColor(Tile d) 
	{
		return color.equals(d.color);
	}

	public void draw(Graphics g) 
	{
		if (state == EMPTY)
			g.setColor(Color.white);
		else if (state == DOWN)
			g.setColor(Color.black);
		else
			g.setColor(color);
		g.fillRect(x, y, WIDTH, HEIGHT);
	}

	public void flip() 
	{
		if (state == UP)
			state = DOWN;
		else if (state == DOWN)
			state = UP;
	}

	public void makeEmpty() 
	{
		state = EMPTY;
	}
}

Tiles are initialized with an x, y location on the playing surface, and a color. In addition to the colors black and white, we will use the following eight predefined colors: Color.blue, Color.red, Color.green, Color.orange, Color.pink, Color.cyan, Color.yellow and Color.magenta. The tile prints as a block 75 by 40 pixels of solid color. There are methods to test the state of a tile, test if two tiles are the same color, flip a tile, and to make a tile empty.

The following are the steps necessary to produce the game.

  1. First, Figure out how to initialize the playing surface with tiles. To do this, you need to fill the array tiles with instances of class Tile. The tiles should be indented 20 pixes from the left and from the top, with a five pixel gap between tiles on both sides. You can initially make all tiles the same color (e.g., Color.red). You won't be able to see the color in any case, since tiles are initally face down. Do this in the constructor for MyPanel. When you are successful you should be able to start the application and see the playing surface. Use the close box on the application window to quit.

  2. Next, add code in the constructor for MyPanel to make an instance of MouseSpy into a mouse listener. (See section 10.2 and advanced topic 10.1 for information on this). When the mouse is pressed, the values x and y tell you where the mouse click has taken place. Add logic to determine which, if any, tile the mouse was over. Test your program by flipping that tile. The call on repaint will then cause the playing surface to be redrawn, and you should see your tile is flipped.

  3. Make your tiles different colors. Remember that colors should come in pairs - two blue tiles, two red ones, and so on. I found the easiest way to do this was to create a one-dimensional array of Colors, initialize the array with pairs (two blues, two reds, etc), then randomly scramble the array, before using it to initialize the tiles. You should remember how to randomly scramble an array from the first assignment. You should be able to test your randomizing algorithm by flipping your tiles to see that they come in different orders. During debugging of the next step (the game logic) you might want to comment out the randomizing step so that you know where the different colored tiles are found. (But remember to set it back before handing in your solution).

  4. Add the game logic. Best place to do this is in the mouseClicked method. Here is an outline of the logic
    
    Find tile under mouse click
    If tile is down
       If this is first tile flipped
          remember coordinates, and flip tile
       else
          if tile is same color as previous flip
    	 mark both tiles as empty
          else
    	 flip first tile back
    
    
    Note that yes, this logic flips only the first tile, and therefore only shows you one tile on each move of the game. (To flip both would have required a pause before deciding to erase both or flip both back, which would have made the game more complex).

  5. Finally, add the counter that is limiting the playing time. See Chapter 9 for how to create a Timer. Do this in the constructor for MyPanel. The timer should fire each 1000 milleseconds. As the timer fires it should decrement a counter. Change the text of the display message (the variable named text) to indicate the value of the counter. You can so this by saying something like
    	text = "Counter: " + counter
    
    When the counter reaches zero, change the text message to indicate the game is over. If you start the counter from 30 it gives you 30 seconds to play the game.

That's it. Once you have those pieces your game should work.

The assignment is the game as described. If you want to, on your own you might want to add logic to do things like figure out if the player has won and change the text accordingly, allow multiple plays, perhaps decreasing the counter each time the player wins, or changing the graphic representation of the tiles. But do not hand in these additions.

You must use Horstmanns formatting style. I've provided a skeleton you can use as a starting point, although you are not obligated to use this. Note that many textbooks would have the main class (Memory in this example) be either a subclass of JFrame, or a subclass of JPanel. The latter would eliminate the need for the separate class MyPanel. There is nothing wrong with this. However, Horstmann keeps them separate in order to more clearly show classes are doing what. I think that is a good idea, so I've followed his approach. If you put all your classes in one file remember that only the main class Memory is declared as public. If you want to put your classes in separate files (Memory, Tile, MyPanel) then that is ok as well. I've allowed up to four files in the submission area.