/* @(#)ConcentrationGame.java 26 September 2007 */ import edu.neu.ccs.*; import edu.neu.ccs.gui.*; import edu.neu.ccs.util.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; /** *

The class for the Concentration game.

* *

Uses Java Power Tools 2.6.0.

* *

Copyright, Richard Rasala, 2007.

* * @author Richard Rasala */ class ConcentrationGame extends DisplayPanel { /** The minimum tile size: 50. */ public static final int MINIMUM_TILE_SIZE = 50; /** The minimum grid size: 3. */ public static final int MINIMUM_GRID_SIZE = 3; /** The maximum grid size: 10. */ public static final int MAXIMUM_GRID_SIZE = 10; /** The paintables for the current game. */ protected Paintable[] paintables = null; /** The length of the current paintables array. */ protected int length = 0; /** The tile size in pixels. */ protected int tileSize = 0; /** The game grid size. */ protected int gridSize = 0; /** The middle index to skip if the grid size is odd. */ protected int gridSkip = -1; /** *

The count of active tiles in the game grid.

* *

If gridSize is even, * this is gridSize*gridSize.

* *

If gridSize is odd, * this is gridSize*gridSize-1.

*/ protected int tileCount = 0; /** *

The tile boxes in the grid.

* *

The array size should be tileCount. * The tiles should inserted in the paintable sequence * by rows. If gridSize is odd, then the * center tile position should be skipped.

*/ protected TileBox[] tiles = null; /** *

The paintable contents corresponding to the tile boxes.

* *

The array size should be tileCount.

* *

The paintable contents[i] should correspond * to the tile tiles[i].

*/ protected Paintable[] contents = null; /** The paintable sequence for the tile boxes. */ protected PaintableSequence sequence = new PaintableSequence(); /** The game board. */ protected PaintableComponent board = new PaintableComponent(sequence); /** The mouse action adapter for the game board. */ protected MouseActionAdapter adapter = board.getMouseActionAdapter(); /** The blank paintable. */ protected Paintable blank = null; /** The number of guesses. */ protected int guesses = 0; /** The number of matches. */ protected int matches = 0; /** Selected index #1. */ protected int index1 = -1; /** Selected index #2. */ protected int index2 = -1; /** The tile background color. */ protected Color tileBackground = Colors.burlywood; /** The game background color. */ protected Color gameBackground = Colors.cornflowerblue; /** The panel background color. */ protected Color panelBackground = Colors.white; /** The button background color. */ protected Color buttonBackground = Colors.yellow; /** The action for showing or hiding tile pairs. */ protected MouseAction clickAction = new MouseAction("Click Action") { public void mouseActionPerformed(MouseEvent mevt) { click(mevt); } }; /** The action to launch a new game. */ protected SimpleAction newGameAction = new SimpleAction("New Game") { public void perform() { newGame(); } }; /** The action to reveal all tiles. */ protected SimpleAction revealAllAction = new SimpleAction("Reveal All") { public void perform() { revealAll(); } }; /** The tile and table panel gap. */ protected int gap = 10; /** The progress message with guesses and matches. */ protected Annotation message = new Annotation(" "); /** The button to launch a new game. */ protected JButton newGameButton = new JButton(newGameAction); /** The button to reveal the solution to an impatient user. */ protected JButton revealAllButton = new JButton(revealAllAction); /** The default constructor. */ public ConcentrationGame() { this(null, 0, 0); } /** *

The constructor that supplies the paintables, * the tile size, and the grid size.

* *

If the paintables are null then * a default set of shape paintables will be used, * and the tile size and grid size will be adjusted.

* *

The tile size will be forced to be at least * MINIMUM_TILE_SIZE.

* *

The grid size will be forced to be between * MINIMUM_GRID_SIZE and MAXIMUM_GRID_SIZE.

*/ public ConcentrationGame (Paintable[] paintables, int tileSize, int gridSize) { if ((paintables == null) || (paintables.length < 1)) { GamePaintables gamePaintables = new GamePaintables(); paintables = gamePaintables.getShapePaintables(); tileSize = gamePaintables.getTileSize(); gridSize = 6; } tileSize = (tileSize > MINIMUM_TILE_SIZE) ? tileSize : MINIMUM_TILE_SIZE; gridSize = (gridSize > MAXIMUM_GRID_SIZE) ? MAXIMUM_GRID_SIZE : (gridSize < MINIMUM_GRID_SIZE) ? MINIMUM_GRID_SIZE : gridSize; this.paintables = paintables; this.tileSize = tileSize; this.gridSize = gridSize; length = paintables.length; initializeGame(); } /** The initializer. */ protected void initializeGame() { makeTiles(); makeGUI(); newGame(); } /** * Makes the tile boxes for the game * and related objects. */ protected void makeTiles() { tileCount = gridSize * gridSize; gridSkip = -1; if ((gridSize % 2) != 0) { tileCount --; gridSkip = gridSize / 2; } tiles = new TileBox[tileCount]; contents = new Paintable[tileCount]; int spacing = tileSize + gap; XRect bounds = new XRect(0, 0, tileSize, tileSize); blank = new ShapePaintable(); blank.setDefaultOriginalBounds2D(bounds); int index = 0; for (int row = 0; row < gridSize; row++) { int y = gap + row * spacing; for (int col = 0; col < gridSize; col++) { if ((row == gridSkip) && (col == gridSkip)) continue; int x = gap + col * spacing; TileBox tile = tiles[index] = new TileBox(blank); tile.setDefaultOriginalBounds2D(bounds); tile.moveCornerTo(x, y); tile.setBackgroundPaint(tileBackground); sequence.appendPaintable(tile); index++; } } int pixels = gap + gridSize * spacing; XRect sequenceBounds = new XRect(0, 0, pixels, pixels); sequence.setDefaultBounds2D(sequenceBounds); } /** * Make the game GUI and add decorations. */ protected void makeGUI() { board.lineBorder(Colors.black, 2); board.setBackground(gameBackground); board.setOpaque(true); newGameButton.setBackground(buttonBackground); revealAllButton.setBackground(buttonBackground); Object[] buttonStuff = { newGameButton, revealAllButton }; HTable buttonTable = new HTable(buttonStuff, 2 * gap, 2 * gap, CENTER); buttonTable.setBackground(panelBackground); Object[] mainStuff = { board, message, buttonTable }; VTable mainTable = new VTable(mainStuff, gap, gap, CENTER); mainTable.emptyBorder(gap); mainTable.setBackground(panelBackground); addObject(mainTable); adapter.addMouseClickedAction(clickAction); } /** Launch a new Concentration game. */ protected synchronized void newGame() { for (int i = 0; i < tileCount; i++) { tiles[i].setPaintable(blank); } guesses = 0; matches = 0; setMessage(); int[] indexList = selectRandomizedIndexList(); for (int i = 0; i < tileCount; i++) { int k = indexList[i]; contents[i] = paintables[k]; } index1 = -1; index2 = -1; } /** *

Return a list of paintable indices * that have been selected randomly, * then duplicated to produce index pairs, * and then randomized again.

* *

The size of the array returned is * tileCount.

*/ protected int[] selectRandomizedIndexList() { int[] indexList = null; // since each paintable must appear twice in the game grid // randomly select tileCount/2 paintable indices int paintableCount = tileCount / 2; if (length >= paintableCount) indexList = ProbStatTools.selectWithNoRepetition(length, paintableCount); else indexList = ProbStatTools.selectWithRepetition(length, paintableCount); // duplicate each index indexList = ProbStatTools.repeatData(indexList, 2); // randomize the index list indexList = ProbStatTools.randomPermutation(indexList); return indexList; } /** The method to reveal the solution. */ protected synchronized void revealAll() { for (int i = 0; i < tileCount; i++) tiles[i].setPaintable(contents[i]); index1 = -1; index2 = -1; } /** The click action method. */ protected synchronized void click(MouseEvent mevt) { // check for an earlier invalid match if ((index1 >= 0) && (index2 >= 0)) { tiles[index1].setPaintable(blank); tiles[index2].setPaintable(blank); index1 = -1; index2 = -1; } int x = mevt.getX(); int y = mevt.getY(); int index = sequence.hitsItemAtIndex(x, y); if (index < 0) return; Tile tile = tiles[index]; Paintable paintable = tile.getPaintable(); if (paintable != blank) return; if (index1 < 0) { index1 = index; tile.setPaintable(contents[index]); return; } if (index2 < 0) { index2 = index; tile.setPaintable(contents[index]); guesses++; if (contents[index1] == contents[index2]) { matches++; // ensure that match is considered valid index1 = -1; index2 = -1; } setMessage(); } } /** Set the message with guesses and matches. */ protected void setMessage() { String text = "Guesses = " + guesses + " Matches = " + matches; if (guesses > 0) { double ratio = (100.0 * matches) / guesses; text += " Ratio = " + ((int) ratio) + "%"; } message.setText(text); } /** *

This main method opens a new ConcentrationOptions * panel in a frame. From that panel, one or more instances of a * ConcentrationGame may be opened.

* *

If args are supplied, they are passed to the constructor * of the ConcentrationOptions panel.

*/ public static void main(String[] args) { new ConcentrationOptions(args).frame("Options", WEST); } }