import java.awt.*; import java.util.ArrayList; import java.awt.event.*; import javax.swing.*; // A single tile on the game board public class Tile extends JComponent { // The size of the tile public static final int SIZE = 16; // The indexes of each special image in the special tiles array public static final int REGULAR_INDEX = 0; public static final int PRESSED_INDEX = 1; public static final int MINE_INDEX = 2; public static final int FLAGGED_INDEX = 3; public static final int NOT_MINE_INDEX = 4; public static final int EXPLODED_MINE_INDEX = 5; // The main game canvas to refer back to private final Canvas GAME_CANVAS; // Store the mouse listener for convenient removal once the game ends private final MouseListener TILE_MOUSE_LISTENER; // A collection of all the Tiles that are adjacent to this one, for // calculating numbers and autorevealing zeros and chording private final ArrayList ADJACENT_TILES; // The numbered tiles 0-9 private Image[] numberTiles; // All the other possible tiles (see *_INDEX above) private Image[] specialTiles; // Whether or not the tile is a mine private boolean mine; // The number of mines adjacent to the current one private int num; // Tile state private boolean revealed; private boolean flagged; // Whether this tile should show the pressed sprite (the mouse is down and // this tile is either the currently moused-over tile or is adjacent to it // while chording) private boolean pressed; // Whether the mouse is over the tile private boolean mouseOver; // Whether the game has ended private boolean gameEnded; private void init() { mine = false; num = 0; revealed = false; flagged = false; pressed = false; mouseOver = false; gameEnded = false; addMouseListener(TILE_MOUSE_LISTENER); } public Tile(Canvas gameCanvas) { GAME_CANVAS = gameCanvas; ADJACENT_TILES = new ArrayList(); // Every tile has its own mouse listener in order for the game to always // know when the mouse moves over from one tile to the other in order to // update the pressed state of the tiles TILE_MOUSE_LISTENER = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { boolean rightClicked = false; switch (e.getButton()) { case MouseEvent.BUTTON1: GAME_CANVAS.setLeftMouseDown(true); break; case MouseEvent.BUTTON3: rightClicked = true; GAME_CANVAS.setRightMouseDown(true); break; default: return; } Tile tile = GAME_CANVAS.getCurrentTile(); if (tile == null) return; if (rightClicked) tile.rightClickAction(); tile.updatePressedState(); } @Override public void mouseReleased(MouseEvent e) { boolean leftClicked = false; switch (e.getButton()) { case MouseEvent.BUTTON1: leftClicked = true; GAME_CANVAS.setLeftMouseDown(false); break; case MouseEvent.BUTTON3: GAME_CANVAS.setRightMouseDown(false); break; default: return; } Tile tile = GAME_CANVAS.getCurrentTile(); if (tile == null) return; if (leftClicked) tile.leftClickAction(); tile.updatePressedState(); } @Override public void mouseEntered(MouseEvent e) { GAME_CANVAS.setCurrentTile(Tile.this); mouseOver = true; updatePressedState(); } @Override public void mouseExited(MouseEvent e) { if (GAME_CANVAS.getCurrentTile() == Tile.this) GAME_CANVAS.setCurrentTile(null); mouseOver = false; updatePressedState(); } }; setPreferredSize(new Dimension(SIZE, SIZE)); init(); } public void setImages(Image[] numberTiles, Image[] specialTiles) { this.numberTiles = numberTiles; this.specialTiles = specialTiles; } public void restart() { removeMouseListener(TILE_MOUSE_LISTENER); init(); } // Update ADJACENT_TILES at the start of the game public void addAdjacentTile(Tile tile) { ADJACENT_TILES.add(tile); } // At the start of the game, place a mine at this tile, unless it is // adjacent to or is the starting tile, or is already a mine. Returns // whether or not a mine was actually placed. public boolean makeMine(Tile startTile) { if (mine || this == startTile || Options.isProtectedStart() && ADJACENT_TILES.contains(startTile)) return false; mine = true; // Increment the number on the adjacent tiles when a new mine is decided for (Tile tile : ADJACENT_TILES) tile.num++; return true; } // Flip gameEnded and repaint one last time public void gameEnd() { removeMouseListener(TILE_MOUSE_LISTENER); gameEnded = true; // Only repaint the tiles that actually change if (mine || flagged) repaint(); } // Update the pressed state, whenever something pressing-related happens public void updatePressedState() { if (pressed != (GAME_CANVAS.isLeftMouseDown() && mouseOver)) { pressed = !pressed; repaint(); } boolean pressAdjacent = pressed && GAME_CANVAS.isChording(); for (Tile tile : ADJACENT_TILES) { if (tile.pressed != pressAdjacent) { tile.pressed = pressAdjacent; tile.repaint(); } } } // Flag on unrevealed tiles on right click private void rightClickAction() { if (revealed) return; flagged = !flagged; GAME_CANVAS.modifyFlagCount(flagged); repaint(); } // Chord or reveal tiles on left click private void leftClickAction() { if (GAME_CANVAS.isChording()) { if (!revealed) return; // Check that chording should happen int adjacentFlags = 0; for (Tile tile : ADJACENT_TILES) if (tile.flagged) adjacentFlags++; if (adjacentFlags != num) return; // Chord for (Tile tile : ADJACENT_TILES) if (!tile.flagged) tile.reveal(); } else { if (flagged || revealed) return; // Ensure that mines are placed before revealing anything GAME_CANVAS.tryStartGame(this); reveal(); } GAME_CANVAS.postRevealCheck(); } // Reveal the current tile private void reveal() { if (revealed) return; revealed = true; repaint(); if (mine) { GAME_CANVAS.setLoseFlag(); return; } GAME_CANVAS.revealedSingleTile(); // Recursively reveal all adjacent tiles if this one is 0 if (num == 0) for (Tile tile : ADJACENT_TILES) if (!tile.flagged) tile.reveal(); } // Determine what image should be drawn at this tile private Image getImage() { if (gameEnded && mine) { // Automatically flag mines at wins, losses keep correct flags if (!GAME_CANVAS.isGameLost() || flagged) return specialTiles[FLAGGED_INDEX]; if (revealed) return specialTiles[EXPLODED_MINE_INDEX]; return specialTiles[MINE_INDEX]; } if (revealed) return numberTiles[num]; if (flagged) { // Correctly flagged mines would have been caught earlier if (gameEnded) return specialTiles[NOT_MINE_INDEX]; return specialTiles[FLAGGED_INDEX]; } // Tiles are only interactable if the game is ongoing if (pressed && !gameEnded) return specialTiles[PRESSED_INDEX]; return specialTiles[REGULAR_INDEX]; } @Override public void paintComponent(Graphics gr) { super.paintComponent(gr); Graphics2D g = (Graphics2D) gr; g.drawImage(getImage(), 0, 0, this); } }