javasweeper/Tile.java

282 lines
7 KiB
Java
Raw Permalink Normal View History

2023-05-21 15:13:45 -07:00
import java.awt.*;
2023-05-25 19:44:14 -07:00
import java.util.ArrayList;
2023-05-21 15:13:45 -07:00
import java.awt.event.*;
import javax.swing.*;
2023-05-25 19:44:14 -07:00
// A single tile on the game board
2023-05-21 15:13:45 -07:00
public class Tile extends JComponent {
2023-05-25 19:44:14 -07:00
// The size of the tile
2023-05-21 15:13:45 -07:00
public static final int SIZE = 16;
2023-05-25 19:44:14 -07:00
// The indexes of each special image in the special tiles array
2023-05-21 15:13:45 -07:00
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;
2023-05-25 19:44:14 -07:00
// The main game canvas to refer back to
2023-05-21 15:13:45 -07:00
private final Canvas GAME_CANVAS;
// Store the mouse listener for convenient removal once the game ends
private final MouseListener TILE_MOUSE_LISTENER;
2023-05-25 19:44:14 -07:00
// A collection of all the Tiles that are adjacent to this one, for
// calculating numbers and autorevealing zeros and chording
private final ArrayList<Tile> ADJACENT_TILES;
// The numbered tiles 0-9
private Image[] numberTiles;
// All the other possible tiles (see *_INDEX above)
private Image[] specialTiles;
2023-05-21 15:13:45 -07:00
// Whether or not the tile is a mine
private boolean mine;
// The number of mines adjacent to the current one
private int num;
2023-05-25 19:44:14 -07:00
// Tile state
2023-05-21 15:13:45 -07:00
private boolean revealed;
private boolean flagged;
2023-05-25 19:44:14 -07:00
// 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)
2023-05-21 15:13:45 -07:00
private boolean pressed;
2023-05-25 19:44:14 -07:00
// Whether the mouse is over the tile
2023-05-21 15:13:45 -07:00
private boolean mouseOver;
2023-05-25 19:44:14 -07:00
// Whether the game has ended
2023-05-21 15:13:45 -07:00
private boolean gameEnded;
private void init() {
mine = false;
num = 0;
revealed = false;
flagged = false;
pressed = false;
mouseOver = false;
gameEnded = false;
addMouseListener(TILE_MOUSE_LISTENER);
}
2023-05-25 19:44:14 -07:00
public Tile(Canvas gameCanvas) {
2023-05-21 15:13:45 -07:00
GAME_CANVAS = gameCanvas;
2023-05-25 19:44:14 -07:00
ADJACENT_TILES = new ArrayList<Tile>();
2023-05-21 15:13:45 -07:00
2023-05-25 19:44:14 -07:00
// 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
2023-05-24 17:34:20 -07:00
TILE_MOUSE_LISTENER = new MouseAdapter() {
2023-05-21 15:13:45 -07:00
@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();
}
2023-05-25 19:44:14 -07:00
public void setImages(Image[] numberTiles, Image[] specialTiles) {
this.numberTiles = numberTiles;
this.specialTiles = specialTiles;
}
2023-05-21 15:13:45 -07:00
public void restart() {
removeMouseListener(TILE_MOUSE_LISTENER);
init();
}
2023-05-25 19:44:14 -07:00
// Update ADJACENT_TILES at the start of the game
2023-05-21 15:13:45 -07:00
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) {
2023-05-24 17:34:20 -07:00
if (mine || this == startTile || Options.isProtectedStart() && ADJACENT_TILES.contains(startTile))
2023-05-21 15:13:45 -07:00
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;
}
2023-05-25 19:44:14 -07:00
// Flip gameEnded and repaint one last time
2023-05-21 15:13:45 -07:00
public void gameEnd() {
removeMouseListener(TILE_MOUSE_LISTENER);
gameEnded = true;
// Only repaint the tiles that actually change
if (mine || flagged)
repaint();
}
2023-05-25 19:44:14 -07:00
// Update the pressed state, whenever something pressing-related happens
2023-05-21 15:13:45 -07:00
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();
}
}
}
2023-05-25 19:44:14 -07:00
// Flag on unrevealed tiles on right click
2023-05-21 15:13:45 -07:00
private void rightClickAction() {
if (revealed)
return;
flagged = !flagged;
GAME_CANVAS.modifyFlagCount(flagged);
repaint();
}
2023-05-25 19:44:14 -07:00
// Chord or reveal tiles on left click
2023-05-21 15:13:45 -07:00
private void leftClickAction() {
if (GAME_CANVAS.isChording()) {
if (!revealed)
return;
2023-05-25 19:44:14 -07:00
// Check that chording should happen
2023-05-21 15:13:45 -07:00
int adjacentFlags = 0;
for (Tile tile : ADJACENT_TILES)
if (tile.flagged)
adjacentFlags++;
if (adjacentFlags != num)
return;
2023-05-25 19:44:14 -07:00
// Chord
2023-05-21 15:13:45 -07:00
for (Tile tile : ADJACENT_TILES)
if (!tile.flagged)
tile.reveal();
} else {
if (flagged || revealed)
return;
2023-05-25 19:44:14 -07:00
2023-05-21 15:13:45 -07:00
// Ensure that mines are placed before revealing anything
GAME_CANVAS.tryStartGame(this);
reveal();
}
GAME_CANVAS.postRevealCheck();
}
2023-05-25 19:44:14 -07:00
// Reveal the current tile
2023-05-21 15:13:45 -07:00
private void reveal() {
if (revealed)
return;
revealed = true;
repaint();
if (mine) {
GAME_CANVAS.setLoseFlag();
2023-05-25 19:44:14 -07:00
return;
2023-05-21 15:13:45 -07:00
}
2023-05-25 19:44:14 -07:00
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();
2023-05-21 15:13:45 -07:00
}
// 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)
2023-05-25 19:44:14 -07:00
return specialTiles[FLAGGED_INDEX];
2023-05-21 15:13:45 -07:00
if (revealed)
2023-05-25 19:44:14 -07:00
return specialTiles[EXPLODED_MINE_INDEX];
return specialTiles[MINE_INDEX];
2023-05-21 15:13:45 -07:00
}
if (revealed)
2023-05-25 19:44:14 -07:00
return numberTiles[num];
2023-05-21 15:13:45 -07:00
if (flagged) {
// Correctly flagged mines would have been caught earlier
if (gameEnded)
2023-05-25 19:44:14 -07:00
return specialTiles[NOT_MINE_INDEX];
return specialTiles[FLAGGED_INDEX];
2023-05-21 15:13:45 -07:00
}
// Tiles are only interactable if the game is ongoing
if (pressed && !gameEnded)
2023-05-25 19:44:14 -07:00
return specialTiles[PRESSED_INDEX];
return specialTiles[REGULAR_INDEX];
2023-05-21 15:13:45 -07:00
}
@Override
2023-05-24 17:34:20 -07:00
public void paintComponent(Graphics gr) {
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
2023-05-21 15:13:45 -07:00
g.drawImage(getImage(), 0, 0, this);
}
}