255 lines
6 KiB
Java
255 lines
6 KiB
Java
import java.awt.*;
|
|
import java.util.HashSet;
|
|
import java.awt.event.*;
|
|
import javax.swing.*;
|
|
|
|
public class Tile extends JComponent {
|
|
public static final int SIZE = 16;
|
|
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;
|
|
|
|
private final Canvas GAME_CANVAS;
|
|
private final Image[] NUMBER_TILES;
|
|
private final Image[] SPECIAL_TILES;
|
|
|
|
// Store the mouse listener for convenient removal once the game ends
|
|
private final MouseListener TILE_MOUSE_LISTENER;
|
|
|
|
// A set of all the Tiles that are adjacent to this one, for calculating
|
|
// numbers and autorevealing zeros and chording
|
|
private final HashSet<Tile> ADJACENT_TILES;
|
|
|
|
// Whether or not the tile is a mine
|
|
private boolean mine;
|
|
// The number of mines adjacent to the current one
|
|
private int num;
|
|
// Whether or not this mine has been revealed already
|
|
private boolean revealed;
|
|
// Whether or not this mine has been flagged
|
|
private boolean flagged;
|
|
private boolean pressed;
|
|
private boolean mouseOver;
|
|
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, Image[] numberTiles, Image[] specialTiles) {
|
|
GAME_CANVAS = gameCanvas;
|
|
NUMBER_TILES = numberTiles;
|
|
SPECIAL_TILES = specialTiles;
|
|
ADJACENT_TILES = new HashSet<Tile>();
|
|
|
|
TILE_MOUSE_LISTENER = new MouseListener() {
|
|
@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();
|
|
}
|
|
|
|
@Override
|
|
public void mouseClicked(MouseEvent e) {
|
|
}
|
|
};
|
|
|
|
setPreferredSize(new Dimension(SIZE, SIZE));
|
|
|
|
init();
|
|
}
|
|
|
|
public void restart() {
|
|
removeMouseListener(TILE_MOUSE_LISTENER);
|
|
init();
|
|
}
|
|
|
|
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 || Main.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;
|
|
}
|
|
|
|
public void gameEnd() {
|
|
removeMouseListener(TILE_MOUSE_LISTENER);
|
|
gameEnded = true;
|
|
// Only repaint the tiles that actually change
|
|
if (mine || flagged)
|
|
repaint();
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void rightClickAction() {
|
|
if (revealed)
|
|
return;
|
|
|
|
flagged = !flagged;
|
|
GAME_CANVAS.modifyFlagCount(flagged);
|
|
repaint();
|
|
}
|
|
|
|
private void leftClickAction() {
|
|
if (GAME_CANVAS.isChording()) {
|
|
if (!revealed)
|
|
return;
|
|
|
|
int adjacentFlags = 0;
|
|
for (Tile tile : ADJACENT_TILES)
|
|
if (tile.flagged)
|
|
adjacentFlags++;
|
|
if (adjacentFlags != num)
|
|
return;
|
|
|
|
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();
|
|
}
|
|
|
|
private void reveal() {
|
|
if (revealed)
|
|
return;
|
|
|
|
revealed = true;
|
|
repaint();
|
|
|
|
if (mine) {
|
|
GAME_CANVAS.setLoseFlag();
|
|
} else {
|
|
GAME_CANVAS.revealedSingleTile();
|
|
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 SPECIAL_TILES[FLAGGED_INDEX];
|
|
if (revealed)
|
|
return SPECIAL_TILES[EXPLODED_MINE_INDEX];
|
|
return SPECIAL_TILES[MINE_INDEX];
|
|
}
|
|
if (revealed)
|
|
return NUMBER_TILES[num];
|
|
if (flagged) {
|
|
// Correctly flagged mines would have been caught earlier
|
|
if (gameEnded)
|
|
return SPECIAL_TILES[NOT_MINE_INDEX];
|
|
return SPECIAL_TILES[FLAGGED_INDEX];
|
|
}
|
|
// Tiles are only interactable if the game is ongoing
|
|
if (pressed && !gameEnded)
|
|
return SPECIAL_TILES[PRESSED_INDEX];
|
|
return SPECIAL_TILES[REGULAR_INDEX];
|
|
}
|
|
|
|
@Override
|
|
public void paintComponent(Graphics g) {
|
|
g.drawImage(getImage(), 0, 0, this);
|
|
}
|
|
}
|