/* @(#)ConcentrationGame.java 1.0 16 October 2004 */ import edu.neu.ccs.*; import edu.neu.ccs.gui.*; import edu.neu.ccs.util.*; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.awt.font.*; import javax.swing.*; /** The class for the Concentration game. */ class ConcentrationGame implements JPTConstants { /** The font size of the large font. */ private static int fontSize = 72; /** The large font for the congratulations message and text based tiles. */ private static Font largeFont = new Font("serif", Font.PLAIN, fontSize); /** The paintable for the congratulations message. */ private static TextPaintable congratulations = new TextPaintable("Congratulations", largeFont); /** The height of the letter A for centering text vertically in a tile. */ private static int heightOfA = getHeightOfA(); /** The tile size. */ private static final int edge = 100; /** The tile bounds. */ private static final Rectangle2D bounds = new Rectangle2D.Double(0, 0, edge, edge); /** The tile background color. */ private static final Color tileBackground = Colors.burlywood; /** The panel background color. */ private static Color panelBackground = Colors.cornflowerblue; /** The images obtained from the image files in the image directory. */ private static Image[] photoImages = ImageTools.readImageFiles("./Images"); /** The length of the photo images array. */ private static int photoLength = photoImages.length; /** The images obtained algorithmically from the game shapes. */ private static Image[] shapeImages = GameShapes.getGameShapeImages(); /** The length of the game shape images array. */ private static int shapeLength = shapeImages.length; /** The images obtained from letters of the alphabet. */ private static Image[] alphabetImages = makeAlphabetImages(); /** The length of the alphabet images array. */ private static int alphabetLength = alphabetImages.length; /** The images obtained from numbers from 0 to 99. */ private static Image[] numberImages = makeNumberImages(); /** The length of the number images array. */ private static int numberLength = numberImages.length; /** The images in the current game. */ private Image[] images = null; /** The length of the current images array. */ int length = 0; /** The size of the tile square. */ private static int gridSize = 0; /** The count of tiles in the tile square. */ private static int tileCount = 0; /** The image tiles corresponding to the selected images. */ private ImageTile[][] tiles = null; /** * The image index list that selects each image an even number of times * to provide image pairs for the tile square. * * This array should have size = tileCount. */ int[] imageIndexList = null; /** The number of guesses. */ private int guesses = 0; /** The number of matches. */ private int matches = 0; /** Selected tile #1. */ private ImageTile tile1 = null; /** Selected tile #2. */ private ImageTile tile2 = null; /** The frame for the game. */ private JPTFrame frame; /** The panel with the tiles. */ private TablePanel tilePanel; /** The progress message with guesses and matches. */ private Annotation message; /** The button to launch a new game. */ private JButton newGameButton; /** The button to reveal the solution to an impatient user. */ private JButton revealAllButton; /** The button panel. */ private TablePanel miniPanel; /** The main enclosure panel. */ private TablePanel mainPanel; /** The radio panel for selecting photo or shape images. */ private static RadioPanel selectImages = new RadioPanel( new String[] { "Use photo images for tiles", "Use shape images for tiles", "Use letters from A to Z for tiles", "Use numbers from 0 to 99 for tiles" }, 0 ); /** The radio panel for selecting the tile grid size. */ private static RadioPanel selectGridSize = new RadioPanel( new String[] { "Use 3 by 3 grid", "Use 4 by 4 grid", "Use 5 by 5 grid", "Use 6 by 6 grid" }, 3 ); /** The enclosure for the radio panels. */ private static TablePanel initialPanel = new TablePanel( new Object[] { new Display(selectImages, null, "Select Images" ), new Display(selectGridSize, null, "Select Grid Size") }, VERTICAL, 10, 10, CENTER); /** The game over flag. */ private boolean gameOver = false; /** The action for showing or hiding tile pairs. */ private MouseAction clickAction = new MouseAction("Click Action") { public void mouseActionPerformed(MouseEvent mevt) { click(mevt); } }; /** The action for revealing all tiles. */ private SimpleAction revealAllAction = new SimpleAction("Reveal All") { public void perform() { revealAll(); } }; /** The action to launch a new game. */ private SimpleAction newGameAction = new SimpleAction("New Game") { public void perform() { frame.dispose(); ConcentrationGame.newGame(); } }; /** The constructor. */ public ConcentrationGame() { initializeGame(); } /** The initializer. */ private void initializeGame() { GeneralDialog.showOKDialog(initialPanel, "Concentration Choices"); switch (selectImages.getSelectedIndex()) { case 1: images = shapeImages; length = shapeLength; break; case 2: images = alphabetImages; length = alphabetLength; break; case 3: images = numberImages; length = numberLength; break; case 0: default: images = photoImages; length = photoLength; break; } if (length == 0) { GeneralDialog.showOKDialog("No image tiles", "Error"); return; } gridSize = 3 + selectGridSize.getSelectedIndex(); tileCount = gridSize * gridSize; if ((tileCount % 2) != 0) tileCount--; selectRandomizedImageIndexList(); makeTiles(); makePanels(); } /** Selects a list of image index pairs that are then randomized. */ private void selectRandomizedImageIndexList() { // select a random set of image indices int neededImages = tileCount / 2; if (length >= neededImages) imageIndexList = ProbStatTools.selectWithNoRepetition(length, neededImages); else imageIndexList = ProbStatTools.selectWithRepetition(length, neededImages); // repeat each image index twice imageIndexList = ProbStatTools.repeatData(imageIndexList, 2); // permute the image index list imageIndexList = ProbStatTools.randomPermutation(imageIndexList); } /** Makes the tiles for the game. */ private void makeTiles() { tiles = new ImageTile[gridSize][gridSize]; int skipIndex = -1; if ((gridSize % 2) != 0) skipIndex = gridSize / 2; int listIndex = 0; for (int row = 0; row < gridSize; row++) for (int col = 0; col < gridSize; col++) { if ((row == skipIndex) && (col == skipIndex)) continue; int imageIndex = imageIndexList[listIndex]; tiles[row][col] = new ImageTile (images[imageIndex], tileBackground, edge, edge, false, true); tiles[row][col].getMouseActionAdapter(). addMouseReleasedAction(clickAction); listIndex++; } } /** Make the panels for the main GUI. */ private void makePanels() { tilePanel = new TablePanel(tiles, 5, 5, CENTER); setMessage(); newGameButton = new JButton(newGameAction); revealAllButton = new JButton(revealAllAction); miniPanel = new TablePanel( new Object[] { newGameButton, revealAllButton }, HORIZONTAL, 40, 40, CENTER); mainPanel = new TablePanel( new Object[] { tilePanel, message, miniPanel }, VERTICAL, 10, 10, CENTER); mainPanel.setMinimumColumnWidth (0, 4 * TextFieldView.getSampleWidth("0000000000")); tilePanel.setBackground(panelBackground); miniPanel.setBackground(panelBackground); mainPanel.setBackground(panelBackground); newGameButton.setBackground(panelBackground); revealAllButton.setBackground(panelBackground); } /** Check for end of game and do any appropriate actions. */ private void checkForEndOfGame() { if (matches >= (tileCount / 2)) GeneralDialog.showOKDialog(congratulations, ""); } /** Set the message with guesses and matches. */ private void setMessage() { String text = "Guesses = " + guesses + " Matches = " + matches; if (matches> 0) { double ratio = (100.0 * matches) / guesses; text += " Ratio = " + ((int) ratio) + "%"; } if (message == null) message = new Annotation(text); else message.setText(text); } /** The click action method. */ private synchronized void click(MouseEvent mevt) { ImageTile tile = (ImageTile) mevt.getComponent(); boolean doClick = !gameOver && tile.isMouseActive(); if (doClick) { // make invisible the tiles that did not match if ((tile1 != null) && (tile2 != null)) { tile1.setTileVisible(false); tile2.setTileVisible(false); tile1 = null; tile2 = null; } // make the first tile visible if (tile1 == null) { tile1 = tile; tile1.setTileVisible(true); } // make the second tile visible else { tile2 = tile; tile2.setTileVisible(true); guesses++; // check match of tile1 and tile2 if (tile1.isEqualTo(tile2)) { tile1.setMouseActive(false); tile2.setMouseActive(false); tile1 = null; tile2 = null; matches++; } setMessage(); checkForEndOfGame(); } } } /** The method to reveal the solution. */ public synchronized void revealAll() { // disable mouse actions first for (int row = 0; row < gridSize; row++) for (int col = 0; col < gridSize; col++) { ImageTile tile = tiles[row][col]; if (tile != null) { tile.setMouseActive(false); } } // then make all tiles visible for (int row = 0; row < gridSize; row++) for (int col = 0; col < gridSize; col++) { ImageTile tile = tiles[row][col]; if (tile != null) { tile.setTileVisible(true); } } gameOver = true; } /** Launch a new Concentration game. */ public static void newGame() { ConcentrationGame game = new ConcentrationGame(); game.frame = JPTFrame.createQuickJPTFrame("Concentration", game.mainPanel, NORTH); } /** Generate the alphabet images array. */ private static Image[] makeAlphabetImages() { Image[] images = new Image[26]; int baseIndex = Character.getNumericValue('A'); for (char c = 'A'; c <= 'Z'; c++) { int i = Character.getNumericValue(c) - baseIndex; images[i] = makeImageFromText("" + c); } return images; } /** Generate the number images array. */ private static Image[] makeNumberImages() { Image[] images = new Image[100]; for (int i = 0; i <= 99; i++) images[i] = makeImageFromText("" + i); return images; } /** Make an image from text. */ private static Image makeImageFromText(String s) { TextPaintable paintable = new TextPaintable (s, largeFont, Colors.black, TextBounds.TIGHT, TextAnchor.CENTER_BASELINE, edge/2, (edge + heightOfA)/2); paintable.setDefaultBounds2D(bounds); return PaintableTools.makeBufferedImage(paintable); } /** Find the text height of the letter A. */ private static int getHeightOfA() { TextPaintable paintable = new TextPaintable ("A", largeFont, Colors.black, TextBounds.TIGHT, TextAnchor.CENTER_BASELINE, 0, 0); Rectangle2D textbounds = paintable.getBounds2D(); return (int) textbounds.getHeight(); } }