Add all comments
This commit is contained in:
parent
c9d70d737d
commit
11e5379d23
195
Canvas.java
195
Canvas.java
|
@ -5,61 +5,99 @@ import java.io.*;
|
||||||
import javax.imageio.*;
|
import javax.imageio.*;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
public class Canvas extends JPanel {
|
// Canvas class, which draws the board and also acts as a central location of
|
||||||
|
// game state that tiles and the face can refer back to
|
||||||
|
public class Canvas extends JComponent {
|
||||||
|
// Width of the tileset
|
||||||
public static final int TS_WIDTH = 144;
|
public static final int TS_WIDTH = 144;
|
||||||
|
// Height of the tileset
|
||||||
public static final int TS_HEIGHT = 122;
|
public static final int TS_HEIGHT = 122;
|
||||||
|
// Y-position of the digits in the tileset
|
||||||
public static final int TS_DIGITS_Y = 33;
|
public static final int TS_DIGITS_Y = 33;
|
||||||
|
// Y-position of the faces in the tilest
|
||||||
public static final int TS_FACES_Y = 55;
|
public static final int TS_FACES_Y = 55;
|
||||||
|
// Y-position of the miscellaneous stuff in the tileset
|
||||||
public static final int TS_MISC_Y = 82;
|
public static final int TS_MISC_Y = 82;
|
||||||
|
// X-position of the digit backdrop in the tileset
|
||||||
public static final int TS_DIGIT_BACKDROP_X = 28;
|
public static final int TS_DIGIT_BACKDROP_X = 28;
|
||||||
|
// X-position of the color pixel in the tileset
|
||||||
public static final int TS_COLOR_X = 70;
|
public static final int TS_COLOR_X = 70;
|
||||||
|
// Width of the borders
|
||||||
public static final int BORDER_WIDTH = 12;
|
public static final int BORDER_WIDTH = 12;
|
||||||
|
// Height of most of the borders
|
||||||
public static final int BORDER_HEIGHT = 11;
|
public static final int BORDER_HEIGHT = 11;
|
||||||
|
// Height of the bottom border
|
||||||
public static final int BOTTOM_HEIGHT = 12;
|
public static final int BOTTOM_HEIGHT = 12;
|
||||||
|
// Height of the box at the top, without the borders
|
||||||
public static final int TOP_BOX_HEIGHT = 33;
|
public static final int TOP_BOX_HEIGHT = 33;
|
||||||
|
// The distance of the top items from the top of the screen
|
||||||
public static final int TOP_PADDING = 15;
|
public static final int TOP_PADDING = 15;
|
||||||
|
// The distance of the flags display from the left of the screen
|
||||||
public static final int FLAGS_PADDING = 16;
|
public static final int FLAGS_PADDING = 16;
|
||||||
|
// The distance of the left side of the timer display from the right of the
|
||||||
|
// screen
|
||||||
public static final int TIMER_PADDING = 59;
|
public static final int TIMER_PADDING = 59;
|
||||||
|
|
||||||
|
// The important game attributes as final ints
|
||||||
public final int ROWS;
|
public final int ROWS;
|
||||||
public final int COLS;
|
public final int COLS;
|
||||||
public final int MINES;
|
public final int MINES;
|
||||||
public final int WIDTH;
|
public final int WIDTH;
|
||||||
public final int HEIGHT;
|
public final int HEIGHT;
|
||||||
|
|
||||||
|
// JPanel containing all the tiles
|
||||||
private final JPanel BOARD_PANEL;
|
private final JPanel BOARD_PANEL;
|
||||||
|
// Number displays for the flags and the time
|
||||||
private final NumberDisplay FLAGS_DISPLAY;
|
private final NumberDisplay FLAGS_DISPLAY;
|
||||||
private final NumberDisplay TIMER_DISPLAY;
|
private final NumberDisplay TIMER_DISPLAY;
|
||||||
|
// The face
|
||||||
private final Face FACE;
|
private final Face FACE;
|
||||||
|
// A KeyListener listening for shift to help chord
|
||||||
private final KeyListener SHIFT_KEY_LISTENER;
|
private final KeyListener SHIFT_KEY_LISTENER;
|
||||||
|
// Timer to tick up the time every second
|
||||||
private final Timer TIMER;
|
private final Timer TIMER;
|
||||||
|
// The virtual representation of the board if we want to actually reference
|
||||||
|
// the tiles instead of casting BOARD_PANEL.getComponents() becuase that's
|
||||||
|
// weird
|
||||||
private final Tile[][] BOARD;
|
private final Tile[][] BOARD;
|
||||||
private final Image BACKGROUND_IMAGE;
|
|
||||||
|
// The image to draw in the background
|
||||||
|
private Image backgroundImage;
|
||||||
|
|
||||||
// The tile that the mouse is currently over
|
// The tile that the mouse is currently over
|
||||||
private Tile currentTile;
|
private Tile currentTile;
|
||||||
|
|
||||||
|
// Whether the game has started yet (before the first click, no mines exist
|
||||||
|
// and the timer doesn't start)
|
||||||
private boolean gameStarted;
|
private boolean gameStarted;
|
||||||
|
// Whether the game has ended
|
||||||
private boolean gameEnded;
|
private boolean gameEnded;
|
||||||
|
// Whether the game was lost
|
||||||
private boolean gameLost;
|
private boolean gameLost;
|
||||||
|
// The number of tiles left to reveal, to know when to win
|
||||||
|
private int numTilesLeft;
|
||||||
|
// The number of flags left to place, for the digit display
|
||||||
|
private int numFlagsLeft;
|
||||||
|
// The time, also for the digit display
|
||||||
|
private int time;
|
||||||
|
// Input state; not used direclty, but getters for these exist in Tile, and
|
||||||
|
// this is where we can store the data for the entire game
|
||||||
private boolean holdingShift;
|
private boolean holdingShift;
|
||||||
private boolean leftMouseDown;
|
private boolean leftMouseDown;
|
||||||
private boolean rightMouseDown;
|
private boolean rightMouseDown;
|
||||||
private int numTilesLeft;
|
|
||||||
private int numFlagsLeft;
|
|
||||||
private int time;
|
|
||||||
|
|
||||||
|
// Set initial values of all the variables
|
||||||
private void init() {
|
private void init() {
|
||||||
currentTile = null;
|
currentTile = null;
|
||||||
gameStarted = false;
|
gameStarted = false;
|
||||||
gameEnded = false;
|
gameEnded = false;
|
||||||
gameLost = false;
|
gameLost = false;
|
||||||
holdingShift = false;
|
|
||||||
leftMouseDown = false;
|
|
||||||
rightMouseDown = false;
|
|
||||||
numTilesLeft = ROWS * COLS - MINES;
|
numTilesLeft = ROWS * COLS - MINES;
|
||||||
numFlagsLeft = MINES;
|
numFlagsLeft = MINES;
|
||||||
time = 0;
|
time = 0;
|
||||||
|
holdingShift = false;
|
||||||
|
leftMouseDown = false;
|
||||||
|
rightMouseDown = false;
|
||||||
|
|
||||||
addKeyListener(SHIFT_KEY_LISTENER);
|
addKeyListener(SHIFT_KEY_LISTENER);
|
||||||
|
|
||||||
|
@ -67,17 +105,13 @@ public class Canvas extends JPanel {
|
||||||
TIMER_DISPLAY.setNum(time);
|
TIMER_DISPLAY.setNum(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Canvas(File skin) {
|
// Get the tileset from Options and use it to set the images of all the
|
||||||
Difficulty difficulty = Options.getDifficulty();
|
// components inside the canvas and the background image
|
||||||
ROWS = difficulty.getRows();
|
private void setImages() {
|
||||||
COLS = difficulty.getCols();
|
// Read the tileset image
|
||||||
MINES = difficulty.getMines();
|
|
||||||
WIDTH = Tile.SIZE * COLS + 2 * BORDER_WIDTH;
|
|
||||||
HEIGHT = Tile.SIZE * ROWS + 2 * BORDER_HEIGHT + BOTTOM_HEIGHT + TOP_BOX_HEIGHT;
|
|
||||||
|
|
||||||
BufferedImage tileset;
|
BufferedImage tileset;
|
||||||
try {
|
try {
|
||||||
tileset = ImageIO.read(skin);
|
tileset = ImageIO.read(new File(Options.SKINS_DIR, Options.getSkinName()));
|
||||||
// Reject images that are too small to properly form the tileset
|
// Reject images that are too small to properly form the tileset
|
||||||
if (tileset.getWidth() < TS_WIDTH || tileset.getHeight() < TS_HEIGHT)
|
if (tileset.getWidth() < TS_WIDTH || tileset.getHeight() < TS_HEIGHT)
|
||||||
throw new IOException();
|
throw new IOException();
|
||||||
|
@ -86,20 +120,47 @@ public class Canvas extends JPanel {
|
||||||
tileset = new BufferedImage(TS_WIDTH, TS_HEIGHT, BufferedImage.TYPE_INT_RGB);
|
tileset = new BufferedImage(TS_WIDTH, TS_HEIGHT, BufferedImage.TYPE_INT_RGB);
|
||||||
}
|
}
|
||||||
|
|
||||||
BACKGROUND_IMAGE = getBackgroundImage(tileset);
|
// Set the images of the tiles
|
||||||
|
|
||||||
Image[] numberTiles = new Image[9];
|
Image[] numberTiles = new Image[9];
|
||||||
Image[] specialTiles = new Image[Tile.EXPLODED_MINE_INDEX + 1];
|
Image[] specialTiles = new Image[Tile.EXPLODED_MINE_INDEX + 1];
|
||||||
for (int i = 0; i < 9; i++)
|
for (int i = 0; i < 9; i++)
|
||||||
numberTiles[i] = getTileFromSet(tileset, i, 0);
|
numberTiles[i] = getTileFromSet(tileset, i, 0);
|
||||||
for (int i = 0; i <= Tile.EXPLODED_MINE_INDEX; i++)
|
for (int i = 0; i <= Tile.EXPLODED_MINE_INDEX; i++)
|
||||||
specialTiles[i] = getTileFromSet(tileset, i, Tile.SIZE);
|
specialTiles[i] = getTileFromSet(tileset, i, Tile.SIZE);
|
||||||
|
for (Tile[] row : BOARD)
|
||||||
|
for (Tile tile : row)
|
||||||
|
tile.setImages(numberTiles, specialTiles);
|
||||||
|
|
||||||
|
// Set the images of the the number displays
|
||||||
|
Image[] digits = getImageSet(tileset, NumberDisplay.MINUS_INDEX + 1, TS_DIGITS_Y,
|
||||||
|
NumberDisplay.DIGIT_WIDTH, NumberDisplay.DIGIT_HEIGHT);
|
||||||
|
Image numberDisplayBackdrop = tileset.getSubimage(TS_DIGIT_BACKDROP_X, TS_MISC_Y,
|
||||||
|
NumberDisplay.BACKDROP_WIDTH, NumberDisplay.BACKDROP_HEIGHT);
|
||||||
|
FLAGS_DISPLAY.setImages(digits, numberDisplayBackdrop);
|
||||||
|
TIMER_DISPLAY.setImages(digits, numberDisplayBackdrop);
|
||||||
|
|
||||||
|
// Set the images of the face
|
||||||
|
FACE.setImages(getImageSet(tileset, Face.PRESSED_INDEX + 1, TS_FACES_Y, Face.SIZE, Face.SIZE));
|
||||||
|
|
||||||
|
// Set the background image
|
||||||
|
backgroundImage = getBackgroundImage(tileset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Canvas() {
|
||||||
|
// Set game attributes based on the difficulty
|
||||||
|
Difficulty difficulty = Options.getDifficulty();
|
||||||
|
ROWS = difficulty.getRows();
|
||||||
|
COLS = difficulty.getCols();
|
||||||
|
MINES = difficulty.getMines();
|
||||||
|
WIDTH = Tile.SIZE * COLS + 2 * BORDER_WIDTH;
|
||||||
|
HEIGHT = Tile.SIZE * ROWS + 2 * BORDER_HEIGHT + BOTTOM_HEIGHT + TOP_BOX_HEIGHT;
|
||||||
|
|
||||||
|
// Initialize the tiles
|
||||||
BOARD = new Tile[ROWS][COLS];
|
BOARD = new Tile[ROWS][COLS];
|
||||||
BOARD_PANEL = new JPanel(new GridLayout(ROWS, COLS));
|
BOARD_PANEL = new JPanel(new GridLayout(ROWS, COLS));
|
||||||
for (int r = 0; r < ROWS; r++) {
|
for (int r = 0; r < ROWS; r++) {
|
||||||
for (int c = 0; c < COLS; c++) {
|
for (int c = 0; c < COLS; c++) {
|
||||||
BOARD[r][c] = new Tile(this, numberTiles, specialTiles);
|
BOARD[r][c] = new Tile(this);
|
||||||
BOARD_PANEL.add(BOARD[r][c]);
|
BOARD_PANEL.add(BOARD[r][c]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,38 +180,37 @@ public class Canvas extends JPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Image[] digits = getImageSet(tileset, NumberDisplay.MINUS_INDEX, TS_DIGITS_Y,
|
// Initialize the other components
|
||||||
NumberDisplay.DIGIT_WIDTH, NumberDisplay.DIGIT_HEIGHT);
|
FLAGS_DISPLAY = new NumberDisplay();
|
||||||
Image numberDisplayBackdrop = tileset.getSubimage(TS_DIGIT_BACKDROP_X, TS_MISC_Y,
|
TIMER_DISPLAY = new NumberDisplay();
|
||||||
NumberDisplay.BACKDROP_WIDTH, NumberDisplay.BACKDROP_HEIGHT);
|
FACE = new Face(this);
|
||||||
|
|
||||||
FLAGS_DISPLAY = new NumberDisplay(numberDisplayBackdrop, digits);
|
// Now that the components are all initialized, set all of their images
|
||||||
TIMER_DISPLAY = new NumberDisplay(numberDisplayBackdrop, digits);
|
setImages();
|
||||||
FACE = new Face(this, getImageSet(tileset, Face.PRESSED_INDEX, TS_FACES_Y, Face.SIZE, Face.SIZE));
|
|
||||||
|
|
||||||
|
// Shift+LMB chords, so we need some way of determining if the shift key
|
||||||
|
// is being held.
|
||||||
SHIFT_KEY_LISTENER = new KeyAdapter() {
|
SHIFT_KEY_LISTENER = new KeyAdapter() {
|
||||||
// Shift+LMB chords, so we need some way of determining if the shift
|
|
||||||
// key is being held.
|
|
||||||
@Override
|
@Override
|
||||||
public void keyPressed(KeyEvent e) {
|
public void keyPressed(KeyEvent e) {
|
||||||
if (e.getKeyCode() == KeyEvent.VK_SHIFT) {
|
updateShift(e, true);
|
||||||
holdingShift = true;
|
|
||||||
updateCurrentTile();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyReleased(KeyEvent e) {
|
public void keyReleased(KeyEvent e) {
|
||||||
if (e.getKeyCode() == KeyEvent.VK_SHIFT) {
|
updateShift(e, false);
|
||||||
holdingShift = false;
|
|
||||||
updateCurrentTile();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCurrentTile() {
|
// Helper method to update holdingShift to the new value if shift
|
||||||
|
// was pressed, and additionally update the pressed state of the
|
||||||
|
// current tile if that exists
|
||||||
|
private void updateShift(KeyEvent e, boolean newValue) {
|
||||||
|
if (e.getKeyCode() == KeyEvent.VK_SHIFT) {
|
||||||
|
holdingShift = newValue;
|
||||||
if (currentTile != null)
|
if (currentTile != null)
|
||||||
currentTile.updatePressedState();
|
currentTile.updatePressedState();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1 second delay
|
// 1 second delay
|
||||||
|
@ -163,12 +223,13 @@ public class Canvas extends JPanel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Play the tick sound and increment the timer number
|
||||||
Sound.TICK.play();
|
Sound.TICK.play();
|
||||||
TIMER_DISPLAY.setNum(++time);
|
TIMER_DISPLAY.setNum(++time);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Null layout, for manual placement of all game components
|
// Null layout, for fine-tuned placement of all game components
|
||||||
setLayout(null);
|
setLayout(null);
|
||||||
Insets insets = getInsets();
|
Insets insets = getInsets();
|
||||||
placeComponent(BOARD_PANEL, BORDER_WIDTH, 2 * BORDER_HEIGHT + TOP_BOX_HEIGHT, insets);
|
placeComponent(BOARD_PANEL, BORDER_WIDTH, 2 * BORDER_HEIGHT + TOP_BOX_HEIGHT, insets);
|
||||||
|
@ -182,11 +243,19 @@ public class Canvas extends JPanel {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh the skin and repaint
|
||||||
|
public void newSkin() {
|
||||||
|
setImages();
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop everything that might be running
|
||||||
public void stop() {
|
public void stop() {
|
||||||
TIMER.stop();
|
TIMER.stop();
|
||||||
removeKeyListener(SHIFT_KEY_LISTENER);
|
removeKeyListener(SHIFT_KEY_LISTENER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restart the game
|
||||||
public void restart() {
|
public void restart() {
|
||||||
if (!gameStarted)
|
if (!gameStarted)
|
||||||
return;
|
return;
|
||||||
|
@ -218,20 +287,22 @@ public class Canvas extends JPanel {
|
||||||
TIMER.start();
|
TIMER.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After every tile revealed, check for game ending
|
||||||
public void postRevealCheck() {
|
public void postRevealCheck() {
|
||||||
if (gameLost) {
|
if (gameLost) { // Loss
|
||||||
Sound.EXPLODE.play();
|
Sound.EXPLODE.play();
|
||||||
stopGame();
|
endGame();
|
||||||
FACE.setFace(Face.DEAD_INDEX);
|
FACE.setFace(Face.DEAD_INDEX);
|
||||||
} else if (numTilesLeft == 0) {
|
} else if (numTilesLeft == 0) { // Win
|
||||||
Sound.WIN.play();
|
Sound.WIN.play();
|
||||||
// Automatically flag all remaining tiles on win
|
// Automatically flag all remaining tiles on win
|
||||||
FLAGS_DISPLAY.setNum(0);
|
FLAGS_DISPLAY.setNum(0);
|
||||||
stopGame();
|
endGame();
|
||||||
FACE.setFace(Face.COOL_INDEX);
|
FACE.setFace(Face.COOL_INDEX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Modify the flag counter
|
||||||
public void modifyFlagCount(boolean addedFlag) {
|
public void modifyFlagCount(boolean addedFlag) {
|
||||||
if (addedFlag)
|
if (addedFlag)
|
||||||
numFlagsLeft--;
|
numFlagsLeft--;
|
||||||
|
@ -240,18 +311,25 @@ public class Canvas extends JPanel {
|
||||||
FLAGS_DISPLAY.setNum(numFlagsLeft);
|
FLAGS_DISPLAY.setNum(numFlagsLeft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called by tile every single time one is revealed
|
||||||
public void revealedSingleTile() {
|
public void revealedSingleTile() {
|
||||||
numTilesLeft--;
|
numTilesLeft--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called by tile if it is a mine when revealed
|
||||||
public void setLoseFlag() {
|
public void setLoseFlag() {
|
||||||
gameLost = true;
|
gameLost = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Every tile has a MouseListener, but the canvas must be aware of the mouse
|
||||||
|
// button state because other tiles that weren't the one that was initially
|
||||||
|
// clicked also need to know the mouse button state, so Canvas has a setter
|
||||||
|
// for it
|
||||||
public void setLeftMouseDown(boolean b) {
|
public void setLeftMouseDown(boolean b) {
|
||||||
if (gameEnded)
|
if (gameEnded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Face gets nervous when clicking
|
||||||
leftMouseDown = b;
|
leftMouseDown = b;
|
||||||
if (leftMouseDown)
|
if (leftMouseDown)
|
||||||
FACE.setFace(Face.SHOCKED_INDEX);
|
FACE.setFace(Face.SHOCKED_INDEX);
|
||||||
|
@ -259,7 +337,9 @@ public class Canvas extends JPanel {
|
||||||
FACE.setFace(Face.HAPPY_INDEX);
|
FACE.setFace(Face.HAPPY_INDEX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ditto
|
||||||
public void setRightMouseDown(boolean b) {
|
public void setRightMouseDown(boolean b) {
|
||||||
|
if (!gameEnded)
|
||||||
rightMouseDown = b;
|
rightMouseDown = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,10 +347,16 @@ public class Canvas extends JPanel {
|
||||||
return leftMouseDown;
|
return leftMouseDown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Chording can be done with either the right mouse button or the shift key
|
||||||
public boolean isChording() {
|
public boolean isChording() {
|
||||||
return holdingShift || rightMouseDown;
|
return holdingShift || rightMouseDown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tiles need to know what tile the mouse is currently over because the
|
||||||
|
// MouseReleased event is triggered on the tile that the mouse was pressed
|
||||||
|
// down on, not the one it was released on, but the tile that it was
|
||||||
|
// released on is the one that actually gets revealed, which we would
|
||||||
|
// otherwise have no way of knowing
|
||||||
public Tile getCurrentTile() {
|
public Tile getCurrentTile() {
|
||||||
return currentTile;
|
return currentTile;
|
||||||
}
|
}
|
||||||
|
@ -283,14 +369,18 @@ public class Canvas extends JPanel {
|
||||||
return gameLost;
|
return gameLost;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Image getTileFromSet(BufferedImage tileset, int n, int y) {
|
// Get the nth tile from the tileset, with the initial y-value of y
|
||||||
|
private Image getTileFromSet(BufferedImage tileset, int n, int y) {
|
||||||
return tileset.getSubimage(Tile.SIZE * n, y, Tile.SIZE, Tile.SIZE);
|
return tileset.getSubimage(Tile.SIZE * n, y, Tile.SIZE, Tile.SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Image[] getImageSet(BufferedImage tileset, int lastIndex, int y, int width, int height) {
|
// For the other images, which have a 1-pixel gap between them: return an
|
||||||
Image[] img = new Image[lastIndex + 1];
|
// array of images by slicing subimages of the given size from the image,
|
||||||
|
// with a 1-pixel gap between them
|
||||||
|
private Image[] getImageSet(BufferedImage tileset, int n, int y, int width, int height) {
|
||||||
|
Image[] img = new Image[n];
|
||||||
int x = 0;
|
int x = 0;
|
||||||
for (int i = 0; i <= lastIndex; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
img[i] = tileset.getSubimage(x, y, width, height);
|
img[i] = tileset.getSubimage(x, y, width, height);
|
||||||
x += width + 1;
|
x += width + 1;
|
||||||
}
|
}
|
||||||
|
@ -367,7 +457,8 @@ public class Canvas extends JPanel {
|
||||||
c.setSize(c.getPreferredSize());
|
c.setSize(c.getPreferredSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopGame() {
|
// Stop the moving parts after a win or a loss
|
||||||
|
private void endGame() {
|
||||||
TIMER.stop();
|
TIMER.stop();
|
||||||
gameEnded = true;
|
gameEnded = true;
|
||||||
for (Tile[] row : BOARD)
|
for (Tile[] row : BOARD)
|
||||||
|
@ -377,8 +468,10 @@ public class Canvas extends JPanel {
|
||||||
|
|
||||||
// Override the paintComponent method in order to add the background image
|
// Override the paintComponent method in order to add the background image
|
||||||
@Override
|
@Override
|
||||||
public void paintComponent(Graphics g) {
|
public void paintComponent(Graphics gr) {
|
||||||
super.paintComponent(g);
|
super.paintComponent(gr);
|
||||||
g.drawImage(BACKGROUND_IMAGE, 0, 0, this);
|
Graphics2D g = (Graphics2D) gr;
|
||||||
|
|
||||||
|
g.drawImage(backgroundImage, 0, 0, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
|
// Custom text field for use when creating new custom games
|
||||||
public class CustomTextField extends JTextField {
|
public class CustomTextField extends JTextField {
|
||||||
|
// The number of columns of characters to have
|
||||||
private static final int COLS = 4;
|
private static final int COLS = 4;
|
||||||
|
|
||||||
private final int MIN_VALUE;
|
private final int MIN_VALUE;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.*;
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
|
// Enum with the difficuulty options
|
||||||
public enum Difficulty {
|
public enum Difficulty {
|
||||||
BEGINNER (9, 9, 10),
|
BEGINNER (9, 9, 10),
|
||||||
INTERMEDIATE (16, 16, 40),
|
INTERMEDIATE (16, 16, 40),
|
||||||
|
@ -20,6 +20,8 @@ public enum Difficulty {
|
||||||
// it's possible to hit the number display cap
|
// it's possible to hit the number display cap
|
||||||
private static final int MAX_MINES = 999;
|
private static final int MAX_MINES = 999;
|
||||||
|
|
||||||
|
// Use nonfinal ints with getter methods because custom needs to be able to
|
||||||
|
// change its stats
|
||||||
private int rows;
|
private int rows;
|
||||||
private int cols;
|
private int cols;
|
||||||
private int mines;
|
private int mines;
|
||||||
|
@ -49,6 +51,8 @@ public enum Difficulty {
|
||||||
return mines;
|
return mines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show a popup window to select attributes of the custom game, return
|
||||||
|
// whether or not it succeeded
|
||||||
public static boolean setCustom(JFrame frame) {
|
public static boolean setCustom(JFrame frame) {
|
||||||
Difficulty current = Options.getDifficulty();
|
Difficulty current = Options.getDifficulty();
|
||||||
|
|
||||||
|
@ -71,6 +75,7 @@ public enum Difficulty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the text fields
|
||||||
CustomTextField rowsField = new RowsColsField(current.rows, MIN_SIZE, MAX_ROWS);
|
CustomTextField rowsField = new RowsColsField(current.rows, MIN_SIZE, MAX_ROWS);
|
||||||
CustomTextField colsField = new RowsColsField(current.cols, MIN_SIZE, MAX_COLS);
|
CustomTextField colsField = new RowsColsField(current.cols, MIN_SIZE, MAX_COLS);
|
||||||
// Override CustomTextField in order to make it dynamically determine
|
// Override CustomTextField in order to make it dynamically determine
|
||||||
|
@ -89,18 +94,21 @@ public enum Difficulty {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Because Java is lame and doesn't have builtin tuples we make do with
|
// Because Java is lame and doesn't have builtin tuples we make do with
|
||||||
// casting an JComponent[][]
|
// a JComponent[][] to store the structure of the input window
|
||||||
JComponent[][] items = {
|
JComponent[][] items = {
|
||||||
{ new JLabel("Height:"), rowsField },
|
{ new JLabel("Height:"), rowsField },
|
||||||
{ new JLabel("Width:"), colsField },
|
{ new JLabel("Width:"), colsField },
|
||||||
{ new JLabel("Mines:"), minesField },
|
{ new JLabel("Mines:"), minesField },
|
||||||
};
|
};
|
||||||
|
|
||||||
int option = JOptionPane.showConfirmDialog(frame, getMessagePanel(items, 6),
|
// Show the message window now and get the result
|
||||||
|
int option = JOptionPane.showConfirmDialog(frame, getMessagePanel(items),
|
||||||
"Custom Board", JOptionPane.OK_CANCEL_OPTION);
|
"Custom Board", JOptionPane.OK_CANCEL_OPTION);
|
||||||
|
// If user didn't hit OK, bail out now
|
||||||
if (option != JOptionPane.OK_OPTION)
|
if (option != JOptionPane.OK_OPTION)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Bail out if fields can't be parsed into integers
|
||||||
int rows, cols, mines;
|
int rows, cols, mines;
|
||||||
try {
|
try {
|
||||||
rows = Integer.parseInt(rowsField.getText());
|
rows = Integer.parseInt(rowsField.getText());
|
||||||
|
@ -110,8 +118,9 @@ public enum Difficulty {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clamp values to the allowed range, just in case something slipped
|
// Even though the text fields should technically only allow valid
|
||||||
// through the focusLost events
|
// inputs, we should clamp values to the allowed range, just in case
|
||||||
|
// something slipped through the focusLost events
|
||||||
rows = clampInt(rows, MIN_SIZE, MAX_ROWS);
|
rows = clampInt(rows, MIN_SIZE, MAX_ROWS);
|
||||||
cols = clampInt(cols, MIN_SIZE, MAX_COLS);
|
cols = clampInt(cols, MIN_SIZE, MAX_COLS);
|
||||||
mines = clampInt(mines, MIN_MINES, getMaxMines(rows, cols));
|
mines = clampInt(mines, MIN_MINES, getMaxMines(rows, cols));
|
||||||
|
@ -120,24 +129,38 @@ public enum Difficulty {
|
||||||
if (current == CUSTOM && CUSTOM.rows == rows && CUSTOM.cols == cols && CUSTOM.mines == mines)
|
if (current == CUSTOM && CUSTOM.rows == rows && CUSTOM.cols == cols && CUSTOM.mines == mines)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Everything succeeded, so now actually set the new stats
|
||||||
CUSTOM.setStats(rows, cols, mines);
|
CUSTOM.setStats(rows, cols, mines);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper method to implement something that every language except Java
|
||||||
|
// already has builtin
|
||||||
public static int clampInt(int value, int minValue, int maxValue) {
|
public static int clampInt(int value, int minValue, int maxValue) {
|
||||||
return Math.max(Math.min(value, maxValue), minValue);
|
return Math.max(Math.min(value, maxValue), minValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The regular formula for maximum mines is the product of rows and columns
|
||||||
|
// minus one, but also cap it at 999
|
||||||
private static int getMaxMines(int rows, int cols) {
|
private static int getMaxMines(int rows, int cols) {
|
||||||
return Math.min((rows - 1) * (cols - 1), MAX_MINES);
|
return Math.min((rows - 1) * (cols - 1), MAX_MINES);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JPanel getMessagePanel(JComponent[][] items, int padding) {
|
// Create a JPanel that has the items in a custom gridlike layout in which
|
||||||
|
// every row is as tall as the tallest element in that row and every column
|
||||||
|
// is as wide as the widest element in that column
|
||||||
|
private static JPanel getMessagePanel(JComponent[][] items) {
|
||||||
|
// The padding between the elements and from the walls
|
||||||
|
final int PADDING = 6;
|
||||||
|
// The number of columns
|
||||||
final int COLS = 2;
|
final int COLS = 2;
|
||||||
|
|
||||||
|
// Null layout because the only layout manager that can do what we we
|
||||||
|
// want is SpringLayout which the same complexity
|
||||||
JPanel messagePanel = new JPanel(null);
|
JPanel messagePanel = new JPanel(null);
|
||||||
|
|
||||||
|
// Add the items to the panel, setting the label alignment along the way
|
||||||
for (JComponent[] row : items) {
|
for (JComponent[] row : items) {
|
||||||
JLabel label = (JLabel) row[0];
|
JLabel label = (JLabel) row[0];
|
||||||
label.setLabelFor(row[1]);
|
label.setLabelFor(row[1]);
|
||||||
|
@ -146,32 +169,45 @@ public enum Difficulty {
|
||||||
messagePanel.add(row[1]);
|
messagePanel.add(row[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
int[] widths = new int[COLS];
|
// The maximum width of all the elements of each column
|
||||||
int[] heights = new int[items.length];
|
int[] maxWidths = new int[COLS];
|
||||||
|
// The maximum height of each elements of each row
|
||||||
|
int[] maxHeights = new int[items.length];
|
||||||
|
// The x-position of all elements of each column
|
||||||
int[] x = new int[COLS + 1];
|
int[] x = new int[COLS + 1];
|
||||||
|
// The y-position of all the elements of each row
|
||||||
int[] y = new int[items.length + 1];
|
int[] y = new int[items.length + 1];
|
||||||
|
// The last value of x and y hold the size of the panel itself
|
||||||
|
|
||||||
x[0] = padding;
|
// Set x and maxWidths
|
||||||
|
x[0] = PADDING;
|
||||||
for (int c = 0; c < COLS; c++) {
|
for (int c = 0; c < COLS; c++) {
|
||||||
|
// Get the maximum width among items of this row
|
||||||
int maxWidth = Integer.MIN_VALUE;
|
int maxWidth = Integer.MIN_VALUE;
|
||||||
for (int r = 0; r < items.length; r++)
|
for (int r = 0; r < items.length; r++)
|
||||||
maxWidth = Math.max(maxWidth, (int) items[r][c].getPreferredSize().getWidth());
|
maxWidth = Math.max(maxWidth, (int) items[r][c].getPreferredSize().getWidth());
|
||||||
widths[c] = maxWidth;
|
maxWidths[c] = maxWidth;
|
||||||
x[c + 1] = x[c] + maxWidth + padding;
|
// x-coordinate of the next item is the x-coordinate of this one
|
||||||
|
// plus this one's width plus padding
|
||||||
|
x[c + 1] = x[c] + maxWidth + PADDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set y and maxHeights the same way
|
||||||
|
y[0] = PADDING;
|
||||||
for (int r = 0; r < items.length; r++) {
|
for (int r = 0; r < items.length; r++) {
|
||||||
int maxHeight = Integer.MIN_VALUE;
|
int maxHeight = Integer.MIN_VALUE;
|
||||||
for (int c = 0; c < COLS; c++)
|
for (int c = 0; c < COLS; c++)
|
||||||
maxHeight = Math.max(maxHeight, (int) items[r][c].getPreferredSize().getHeight());
|
maxHeight = Math.max(maxHeight, (int) items[r][c].getPreferredSize().getHeight());
|
||||||
heights[r] = maxHeight;
|
maxHeights[r] = maxHeight;
|
||||||
y[r + 1] = y[r] + maxHeight + padding;
|
y[r + 1] = y[r] + maxHeight + PADDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actually put into use the values we just got
|
||||||
for (int r = 0; r < items.length; r++)
|
for (int r = 0; r < items.length; r++)
|
||||||
for (int c = 0; c < COLS; c++)
|
for (int c = 0; c < COLS; c++)
|
||||||
items[r][c].setBounds(x[c], y[r], widths[c], heights[r]);
|
items[r][c].setBounds(x[c], y[r], maxWidths[c], maxHeights[r]);
|
||||||
|
|
||||||
|
// Use the last values of x and y to set the size of the panel
|
||||||
messagePanel.setPreferredSize(new Dimension(x[COLS], y[items.length]));
|
messagePanel.setPreferredSize(new Dimension(x[COLS], y[items.length]));
|
||||||
|
|
||||||
return messagePanel;
|
return messagePanel;
|
||||||
|
|
33
Face.java
33
Face.java
|
@ -2,29 +2,42 @@ import java.awt.*;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
|
// The face at the top of the game
|
||||||
public class Face extends JComponent {
|
public class Face extends JComponent {
|
||||||
|
// The width and height
|
||||||
public static final int SIZE = 26;
|
public static final int SIZE = 26;
|
||||||
|
// The indexes of each face in the array
|
||||||
public static final int HAPPY_INDEX = 0;
|
public static final int HAPPY_INDEX = 0;
|
||||||
public static final int SHOCKED_INDEX = 1;
|
public static final int SHOCKED_INDEX = 1;
|
||||||
public static final int DEAD_INDEX = 2;
|
public static final int DEAD_INDEX = 2;
|
||||||
public static final int COOL_INDEX = 3;
|
public static final int COOL_INDEX = 3;
|
||||||
public static final int PRESSED_INDEX = 4;
|
public static final int PRESSED_INDEX = 4;
|
||||||
|
|
||||||
|
// The main game canvas to refer back to
|
||||||
private final Canvas GAME_CANVAS;
|
private final Canvas GAME_CANVAS;
|
||||||
private final Image[] FACES;
|
|
||||||
|
private Image[] faces;
|
||||||
|
// The current face index
|
||||||
private int face;
|
private int face;
|
||||||
|
// The last face index, before being pressed, in order to revert on unpress
|
||||||
private int unpressedFace;
|
private int unpressedFace;
|
||||||
|
// Whether the face is being pressed right now
|
||||||
private boolean pressed;
|
private boolean pressed;
|
||||||
|
// Whether the mouse is over the face
|
||||||
private boolean mouseOver;
|
private boolean mouseOver;
|
||||||
|
|
||||||
public Face(Canvas gameCanvas, Image[] faces) {
|
public Face(Canvas gameCanvas) {
|
||||||
FACES = faces;
|
|
||||||
GAME_CANVAS = gameCanvas;
|
GAME_CANVAS = gameCanvas;
|
||||||
face = HAPPY_INDEX;
|
face = HAPPY_INDEX;
|
||||||
pressed = false;
|
pressed = false;
|
||||||
mouseOver = false;
|
mouseOver = false;
|
||||||
setPreferredSize(new Dimension(SIZE, SIZE));
|
setPreferredSize(new Dimension(SIZE, SIZE));
|
||||||
|
// We want to show the pressed sprite only if the mouse is down while
|
||||||
|
// the mouse is over the face, reverting back when it's no longer over
|
||||||
|
// the face but coming back as soon as the mouse comes back
|
||||||
addMouseListener(new MouseAdapter() {
|
addMouseListener(new MouseAdapter() {
|
||||||
|
// The only way to initiate a pressed face state is when the mouse
|
||||||
|
// is over it and is pressed down
|
||||||
@Override
|
@Override
|
||||||
public void mousePressed(MouseEvent e) {
|
public void mousePressed(MouseEvent e) {
|
||||||
pressed = true;
|
pressed = true;
|
||||||
|
@ -32,6 +45,10 @@ public class Face extends JComponent {
|
||||||
setFace(PRESSED_INDEX);
|
setFace(PRESSED_INDEX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the face recieves a mouseReleased event while the mouse is
|
||||||
|
// over it, that means that it was a completed click, so restart the
|
||||||
|
// game on the canvas, also resetting back to the happy face that
|
||||||
|
// should be shown at the start of every game
|
||||||
@Override
|
@Override
|
||||||
public void mouseReleased(MouseEvent e) {
|
public void mouseReleased(MouseEvent e) {
|
||||||
pressed = false;
|
pressed = false;
|
||||||
|
@ -41,6 +58,8 @@ public class Face extends JComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If it was previously being pressed, reshow the pressed sprite as
|
||||||
|
// soon as mouse enters
|
||||||
@Override
|
@Override
|
||||||
public void mouseEntered(MouseEvent e) {
|
public void mouseEntered(MouseEvent e) {
|
||||||
mouseOver = true;
|
mouseOver = true;
|
||||||
|
@ -48,6 +67,8 @@ public class Face extends JComponent {
|
||||||
setFace(PRESSED_INDEX);
|
setFace(PRESSED_INDEX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Temporarily show the unpressed face, but keep the pressed boolean
|
||||||
|
// true in case the mouse reenters
|
||||||
@Override
|
@Override
|
||||||
public void mouseExited(MouseEvent e) {
|
public void mouseExited(MouseEvent e) {
|
||||||
mouseOver = false;
|
mouseOver = false;
|
||||||
|
@ -57,6 +78,10 @@ public class Face extends JComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setImages(Image[] faces) {
|
||||||
|
this.faces = faces;
|
||||||
|
}
|
||||||
|
|
||||||
public void setFace(int face) {
|
public void setFace(int face) {
|
||||||
this.face = face;
|
this.face = face;
|
||||||
repaint();
|
repaint();
|
||||||
|
@ -67,6 +92,6 @@ public class Face extends JComponent {
|
||||||
super.paintComponent(gr);
|
super.paintComponent(gr);
|
||||||
Graphics2D g = (Graphics2D) gr;
|
Graphics2D g = (Graphics2D) gr;
|
||||||
|
|
||||||
g.drawImage(FACES[face], 0, 0, this);
|
g.drawImage(faces[face], 0, 0, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
106
Main.java
106
Main.java
|
@ -7,21 +7,21 @@ import javax.swing.*;
|
||||||
import javax.swing.event.*;
|
import javax.swing.event.*;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
public static final String DEFAULT_SKIN = "winxpskin.bmp";
|
// HTML filenames to be shown in the Help menu
|
||||||
public static final String SKINS_DIR = "Skins/";
|
|
||||||
public static final String HELP_FILE = "help.html";
|
public static final String HELP_FILE = "help.html";
|
||||||
public static final String ABOUT_FILE = "about.html";
|
public static final String ABOUT_FILE = "about.html";
|
||||||
|
|
||||||
private static final String[] VALID_EXTENSIONS = {".bmp", ".png", ".webp"};
|
// Valid file extensions for skins to be listed in the menu
|
||||||
|
private static final String[] VALID_SKINS_EXTENSIONS = {".bmp", ".png", ".webp"};
|
||||||
|
|
||||||
|
// Game frame
|
||||||
private static final JFrame FRAME = new JFrame("Minesweeper");
|
private static final JFrame FRAME = new JFrame("Minesweeper");
|
||||||
|
|
||||||
|
// The current game Canvas
|
||||||
private static Canvas canvas;
|
private static Canvas canvas;
|
||||||
private static String skin;
|
|
||||||
|
|
||||||
|
// Entry point: simply create the frame, set the canvas, and show the frame
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
skin = DEFAULT_SKIN;
|
|
||||||
|
|
||||||
FRAME.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
FRAME.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
FRAME.setResizable(false);
|
FRAME.setResizable(false);
|
||||||
FRAME.setJMenuBar(getMenuBar());
|
FRAME.setJMenuBar(getMenuBar());
|
||||||
|
@ -30,6 +30,7 @@ public class Main {
|
||||||
FRAME.setVisible(true);
|
FRAME.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the menu bar of the frame
|
||||||
private static JMenuBar getMenuBar() {
|
private static JMenuBar getMenuBar() {
|
||||||
// Button group for the difficulty buttons
|
// Button group for the difficulty buttons
|
||||||
ButtonGroup difficultyButtons = new ButtonGroup();
|
ButtonGroup difficultyButtons = new ButtonGroup();
|
||||||
|
@ -46,12 +47,13 @@ public class Main {
|
||||||
difficultyButtons.add(expertItem);
|
difficultyButtons.add(expertItem);
|
||||||
difficultyButtons.add(customItem);
|
difficultyButtons.add(customItem);
|
||||||
|
|
||||||
|
// ActionListener for every button except the skins ones
|
||||||
ActionListener listener = new ActionListener() {
|
ActionListener listener = new ActionListener() {
|
||||||
// Keep track of the previously selected difficulty button in case a
|
// Keep track of the previously selected difficulty button in case a
|
||||||
// Custom press is canceled so we can revert back to whatever it was
|
// Custom press is canceled so we can revert the button selection
|
||||||
// before
|
|
||||||
private ButtonModel lastDifficultyButton = difficultyButtons.getSelection();
|
private ButtonModel lastDifficultyButton = difficultyButtons.getSelection();
|
||||||
|
|
||||||
|
// Parse the action command and perform the corresponding action
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
switch (e.getActionCommand()) {
|
switch (e.getActionCommand()) {
|
||||||
|
@ -71,6 +73,8 @@ public class Main {
|
||||||
lastDifficultyButton = expertItem.getModel();
|
lastDifficultyButton = expertItem.getModel();
|
||||||
break;
|
break;
|
||||||
case "custom":
|
case "custom":
|
||||||
|
// If canceled, set the currently selected button to
|
||||||
|
// whatever it was before
|
||||||
if (Difficulty.setCustom(FRAME)) {
|
if (Difficulty.setCustom(FRAME)) {
|
||||||
setDifficulty(Difficulty.CUSTOM);
|
setDifficulty(Difficulty.CUSTOM);
|
||||||
lastDifficultyButton = customItem.getModel();
|
lastDifficultyButton = customItem.getModel();
|
||||||
|
@ -102,6 +106,7 @@ public class Main {
|
||||||
|
|
||||||
JMenuBar menuBar = new JMenuBar();
|
JMenuBar menuBar = new JMenuBar();
|
||||||
|
|
||||||
|
// Create the Game item in the menu bar
|
||||||
JMenu gameMenu = new JMenu("Game");
|
JMenu gameMenu = new JMenu("Game");
|
||||||
gameMenu.setMnemonic(KeyEvent.VK_G);
|
gameMenu.setMnemonic(KeyEvent.VK_G);
|
||||||
JMenuItem newGameItem = new JMenuItem("New game");
|
JMenuItem newGameItem = new JMenuItem("New game");
|
||||||
|
@ -123,6 +128,7 @@ public class Main {
|
||||||
addMenuItem("Exit", gameMenu, KeyEvent.VK_X, "exit", listener);
|
addMenuItem("Exit", gameMenu, KeyEvent.VK_X, "exit", listener);
|
||||||
menuBar.add(gameMenu);
|
menuBar.add(gameMenu);
|
||||||
|
|
||||||
|
// Create the Help item in the menu bar
|
||||||
JMenu helpMenu = new JMenu("Help");
|
JMenu helpMenu = new JMenu("Help");
|
||||||
helpMenu.setMnemonic(KeyEvent.VK_H);
|
helpMenu.setMnemonic(KeyEvent.VK_H);
|
||||||
JMenuItem helpItem = new JMenuItem("Help");
|
JMenuItem helpItem = new JMenuItem("Help");
|
||||||
|
@ -132,28 +138,32 @@ public class Main {
|
||||||
addMenuItem("About", helpMenu, KeyEvent.VK_A, "about", listener);
|
addMenuItem("About", helpMenu, KeyEvent.VK_A, "about", listener);
|
||||||
menuBar.add(helpMenu);
|
menuBar.add(helpMenu);
|
||||||
|
|
||||||
|
// Create the Skins item in the menu bar (this one is special)
|
||||||
JMenu skinsMenu = new JMenu("Skins");
|
JMenu skinsMenu = new JMenu("Skins");
|
||||||
skinsMenu.setMnemonic(KeyEvent.VK_S);
|
skinsMenu.setMnemonic(KeyEvent.VK_S);
|
||||||
// The Skins menu should dynamically generate a menu of all image files
|
// The Skins menu should dynamically generate a menu of all image files
|
||||||
// in the skins directory when clicked in order to select one
|
// in the Skins directory when clicked in order to select one
|
||||||
skinsMenu.addMenuListener(new MenuListener() {
|
skinsMenu.addMenuListener(new MenuListener() {
|
||||||
|
// Skins have a separate ActionListener from the other buttons so we
|
||||||
|
// can simply set the action command to the name of the new skin
|
||||||
|
// without worrying about collisions or messiness
|
||||||
private static ActionListener skinListener = new ActionListener() {
|
private static ActionListener skinListener = new ActionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
// Set the skin to the new one if it isn't already that
|
// Set the skin to the new one if it isn't already that
|
||||||
String newSkin = e.getActionCommand();
|
String newSkin = e.getActionCommand();
|
||||||
if (newSkin.equals(skin))
|
if (newSkin.equals(Options.getSkinName()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
skin = newSkin;
|
Options.setSkinName(newSkin);
|
||||||
replaceCanvas();
|
canvas.newSkin();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void menuSelected(MenuEvent e) {
|
public void menuSelected(MenuEvent e) {
|
||||||
// Get a stream of all files in the skins directory
|
// Get a stream of all files in the skins directory
|
||||||
try (Stream<Path> dirStream = Files.list(Paths.get(SKINS_DIR))) {
|
try (Stream<Path> dirStream = Files.list(Paths.get(Options.SKINS_DIR))) {
|
||||||
dirStream
|
dirStream
|
||||||
// Filter for only regular files
|
// Filter for only regular files
|
||||||
.filter(Files::isRegularFile)
|
.filter(Files::isRegularFile)
|
||||||
|
@ -162,19 +172,17 @@ public class Main {
|
||||||
.map(Path::toString)
|
.map(Path::toString)
|
||||||
// Only allow a filename if it ends with one of the
|
// Only allow a filename if it ends with one of the
|
||||||
// valid extensions
|
// valid extensions
|
||||||
.filter(name -> Arrays.stream(VALID_EXTENSIONS)
|
.filter(name -> Arrays.stream(VALID_SKINS_EXTENSIONS)
|
||||||
// Use regionMatches to effectively
|
.anyMatch(ext -> name.toLowerCase().endsWith(ext)))
|
||||||
// case-insensitively endsWith()
|
// Because Files.list() does not guarantee an order,
|
||||||
.anyMatch(ext -> name.regionMatches(true,
|
// and alphabetically sorted files look objectively
|
||||||
name.length() - ext.length(), ext, 0, ext.length())))
|
// nicer in directory listings
|
||||||
// Because Files.list does not guarantee an order, and
|
|
||||||
// alphabetical files look objectively nicer in lists
|
|
||||||
.sorted()
|
.sorted()
|
||||||
// Create a radio button menu item for each one,
|
// Create a radio button menu item for each one,
|
||||||
// remembering to have it pre-selected if it's already
|
// remembering to have it pre-selected if it's already
|
||||||
// the current skin
|
// the current skin
|
||||||
.forEach(name -> addMenuItem(new JRadioButtonMenuItem(name, name.equals(skin)),
|
.forEach(name -> addMenuItem(new JRadioButtonMenuItem(name,
|
||||||
skinsMenu, name, skinListener));
|
name.equals(Options.getSkinName())), skinsMenu, name, skinListener));
|
||||||
} catch (IOException ignore) {
|
} catch (IOException ignore) {
|
||||||
// It's fine to leave it blank if we can't get results
|
// It's fine to leave it blank if we can't get results
|
||||||
}
|
}
|
||||||
|
@ -196,27 +204,8 @@ public class Main {
|
||||||
return menuBar;
|
return menuBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void messageHtmlFile(String file, String failText, String title, int messageType) {
|
// Helper items for adding menu items without needing a million copy-pasted
|
||||||
String text;
|
// lines
|
||||||
try {
|
|
||||||
// For some reason, JLabels just stop parsing HTML after they hit a
|
|
||||||
// newline, so swap the newlines out with spaces
|
|
||||||
text = Files.readString(Paths.get(file)).replace('\n', ' ');
|
|
||||||
} catch (IOException e) {
|
|
||||||
text = failText;
|
|
||||||
}
|
|
||||||
JOptionPane.showMessageDialog(FRAME, text, title, messageType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void setDifficulty(Difficulty newDifficulty) {
|
|
||||||
// Reject switching difficulty to the already-existing one unless Custom
|
|
||||||
if (Options.getDifficulty() == newDifficulty && newDifficulty != Difficulty.CUSTOM)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Options.setDifficulty(newDifficulty);
|
|
||||||
replaceCanvas();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addMenuItem(String menuTitle, JMenu menu, int mnemonic, String actionCommand,
|
private static void addMenuItem(String menuTitle, JMenu menu, int mnemonic, String actionCommand,
|
||||||
ActionListener listener) {
|
ActionListener listener) {
|
||||||
addMenuItem(new JMenuItem(menuTitle), menu, mnemonic, actionCommand, listener);
|
addMenuItem(new JMenuItem(menuTitle), menu, mnemonic, actionCommand, listener);
|
||||||
|
@ -235,17 +224,40 @@ public class Main {
|
||||||
menu.add(menuItem);
|
menu.add(menuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void replaceCanvas() {
|
// Read the given HTML file and message it with JOptionPane, using failText
|
||||||
|
// instead in case of failure
|
||||||
|
private static void messageHtmlFile(String file, String failText, String title, int messageType) {
|
||||||
|
String text;
|
||||||
|
try {
|
||||||
|
// For some reason, JLabels just stop parsing HTML after they hit a
|
||||||
|
// newline, so swap the newlines out with spaces
|
||||||
|
text = Files.readString(Paths.get(file)).replace('\n', ' ');
|
||||||
|
} catch (IOException e) {
|
||||||
|
text = failText;
|
||||||
|
}
|
||||||
|
JOptionPane.showMessageDialog(FRAME, text, title, messageType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe change the difficulty to the new difficulty
|
||||||
|
private static void setDifficulty(Difficulty newDifficulty) {
|
||||||
|
// Reject switching difficulty to the already-existing one unless
|
||||||
|
// Custom, because that will still be different
|
||||||
|
if (Options.getDifficulty() == newDifficulty && newDifficulty != Difficulty.CUSTOM)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Options.setDifficulty(newDifficulty);
|
||||||
|
|
||||||
|
// Replace the canvas with a new one, which will get the new difficulty
|
||||||
canvas.stop();
|
canvas.stop();
|
||||||
canvas.removeAll();
|
canvas.removeAll();
|
||||||
FRAME.remove(canvas);
|
FRAME.remove(canvas);
|
||||||
|
|
||||||
setCanvas();
|
setCanvas();
|
||||||
canvas.requestFocusInWindow();
|
canvas.requestFocusInWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new canvas and put it in the frame
|
||||||
private static void setCanvas() {
|
private static void setCanvas() {
|
||||||
canvas = new Canvas(new File(SKINS_DIR, skin));
|
canvas = new Canvas();
|
||||||
FRAME.add(canvas);
|
FRAME.add(canvas);
|
||||||
FRAME.pack();
|
FRAME.pack();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,37 @@
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
|
// The number displays in the game
|
||||||
public class NumberDisplay extends JComponent {
|
public class NumberDisplay extends JComponent {
|
||||||
|
// The dimensions of each individual digit
|
||||||
public static final int DIGIT_WIDTH = 11;
|
public static final int DIGIT_WIDTH = 11;
|
||||||
public static final int DIGIT_HEIGHT = 21;
|
public static final int DIGIT_HEIGHT = 21;
|
||||||
|
// The dimensions of the big box
|
||||||
public static final int BACKDROP_WIDTH = 41;
|
public static final int BACKDROP_WIDTH = 41;
|
||||||
public static final int BACKDROP_HEIGHT = 25;
|
public static final int BACKDROP_HEIGHT = 25;
|
||||||
|
// The index of the minus sign
|
||||||
public static final int MINUS_INDEX = 10;
|
public static final int MINUS_INDEX = 10;
|
||||||
|
|
||||||
|
// The x-positions of each digit: units -> tens -> hundreds
|
||||||
private static final int[] DIGIT_X = {28, 15, 2};
|
private static final int[] DIGIT_X = {28, 15, 2};
|
||||||
|
// The y-position of each digit
|
||||||
private static final int DIGIT_Y = 2;
|
private static final int DIGIT_Y = 2;
|
||||||
|
|
||||||
public final Image BACKDROP;
|
public Image backdrop;
|
||||||
public final Image[] DIGITS;
|
public Image[] digits;
|
||||||
|
|
||||||
private int num;
|
private int num;
|
||||||
private boolean negative;
|
private boolean negative;
|
||||||
|
|
||||||
public NumberDisplay(Image backdrop, Image[] digits) {
|
public NumberDisplay() {
|
||||||
BACKDROP = backdrop;
|
|
||||||
DIGITS = digits;
|
|
||||||
setPreferredSize(new Dimension(BACKDROP_WIDTH, BACKDROP_HEIGHT));
|
setPreferredSize(new Dimension(BACKDROP_WIDTH, BACKDROP_HEIGHT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setImages(Image[] digits, Image backdrop) {
|
||||||
|
this.digits = digits;
|
||||||
|
this.backdrop = backdrop;
|
||||||
|
}
|
||||||
|
|
||||||
public void setNum(int num) {
|
public void setNum(int num) {
|
||||||
setClamped(num);
|
setClamped(num);
|
||||||
repaint();
|
repaint();
|
||||||
|
@ -44,17 +53,20 @@ public class NumberDisplay extends JComponent {
|
||||||
super.paintComponent(gr);
|
super.paintComponent(gr);
|
||||||
Graphics2D g = (Graphics2D) gr;
|
Graphics2D g = (Graphics2D) gr;
|
||||||
|
|
||||||
g.drawImage(BACKDROP, 0, 0, this);
|
g.drawImage(backdrop, 0, 0, this);
|
||||||
// Preserve the original num for divison, in case we repaint twice
|
// Preserve the original num for in-place divison, because we don't want
|
||||||
// without updating the number
|
// non-graphical side effects in paintComponent
|
||||||
int num = this.num;
|
int num = this.num;
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
int digitsIndex;
|
int digitsIndex;
|
||||||
|
// Get the index of the proper digit for this position, drawing the
|
||||||
|
// minus sign in the hundreds digit if necessary
|
||||||
if (negative && i == 2)
|
if (negative && i == 2)
|
||||||
digitsIndex = MINUS_INDEX;
|
digitsIndex = MINUS_INDEX;
|
||||||
else
|
else
|
||||||
digitsIndex = num % 10;
|
digitsIndex = num % 10;
|
||||||
g.drawImage(DIGITS[digitsIndex], DIGIT_X[i], DIGIT_Y, this);
|
g.drawImage(digits[digitsIndex], DIGIT_X[i], DIGIT_Y, this);
|
||||||
|
// Next digit
|
||||||
num /= 10;
|
num /= 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
Options.java
13
Options.java
|
@ -1,11 +1,16 @@
|
||||||
// Public class that stores all the global static options
|
// Public class that stores all the global static options
|
||||||
public class Options {
|
public class Options {
|
||||||
|
// Directory with all the skins files
|
||||||
|
public static final String SKINS_DIR = "Skins/";
|
||||||
|
|
||||||
// Whether or not sound is enabled
|
// Whether or not sound is enabled
|
||||||
private static boolean sound = false;
|
private static boolean sound = false;
|
||||||
// Whether or not to force starting at 0
|
// Whether or not to force starting at 0
|
||||||
private static boolean protectedStart = false;
|
private static boolean protectedStart = false;
|
||||||
// The difficulty
|
// The difficulty
|
||||||
private static Difficulty difficulty = Difficulty.INTERMEDIATE;
|
private static Difficulty difficulty = Difficulty.INTERMEDIATE;
|
||||||
|
// The skin
|
||||||
|
private static String skinName = "winxpskin.bmp";
|
||||||
|
|
||||||
public static boolean hasSound() {
|
public static boolean hasSound() {
|
||||||
return sound;
|
return sound;
|
||||||
|
@ -31,6 +36,14 @@ public class Options {
|
||||||
Options.difficulty = difficulty;
|
Options.difficulty = difficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getSkinName() {
|
||||||
|
return skinName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setSkinName(String skinName) {
|
||||||
|
Options.skinName = skinName;
|
||||||
|
}
|
||||||
|
|
||||||
// No constructing >:(
|
// No constructing >:(
|
||||||
private Options() {
|
private Options() {
|
||||||
}
|
}
|
||||||
|
|
72
Tile.java
72
Tile.java
|
@ -1,10 +1,13 @@
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.HashSet;
|
import java.util.ArrayList;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
|
// A single tile on the game board
|
||||||
public class Tile extends JComponent {
|
public class Tile extends JComponent {
|
||||||
|
// The size of the tile
|
||||||
public static final int SIZE = 16;
|
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 REGULAR_INDEX = 0;
|
||||||
public static final int PRESSED_INDEX = 1;
|
public static final int PRESSED_INDEX = 1;
|
||||||
public static final int MINE_INDEX = 2;
|
public static final int MINE_INDEX = 2;
|
||||||
|
@ -12,27 +15,35 @@ public class Tile extends JComponent {
|
||||||
public static final int NOT_MINE_INDEX = 4;
|
public static final int NOT_MINE_INDEX = 4;
|
||||||
public static final int EXPLODED_MINE_INDEX = 5;
|
public static final int EXPLODED_MINE_INDEX = 5;
|
||||||
|
|
||||||
|
// The main game canvas to refer back to
|
||||||
private final Canvas GAME_CANVAS;
|
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
|
// Store the mouse listener for convenient removal once the game ends
|
||||||
private final MouseListener TILE_MOUSE_LISTENER;
|
private final MouseListener TILE_MOUSE_LISTENER;
|
||||||
|
|
||||||
// A set of all the Tiles that are adjacent to this one, for calculating
|
// A collection of all the Tiles that are adjacent to this one, for
|
||||||
// numbers and autorevealing zeros and chording
|
// calculating numbers and autorevealing zeros and chording
|
||||||
private final HashSet<Tile> ADJACENT_TILES;
|
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;
|
||||||
|
|
||||||
// Whether or not the tile is a mine
|
// Whether or not the tile is a mine
|
||||||
private boolean mine;
|
private boolean mine;
|
||||||
// The number of mines adjacent to the current one
|
// The number of mines adjacent to the current one
|
||||||
private int num;
|
private int num;
|
||||||
// Whether or not this mine has been revealed already
|
// Tile state
|
||||||
private boolean revealed;
|
private boolean revealed;
|
||||||
// Whether or not this mine has been flagged
|
|
||||||
private boolean flagged;
|
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;
|
private boolean pressed;
|
||||||
|
// Whether the mouse is over the tile
|
||||||
private boolean mouseOver;
|
private boolean mouseOver;
|
||||||
|
// Whether the game has ended
|
||||||
private boolean gameEnded;
|
private boolean gameEnded;
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
|
@ -46,12 +57,13 @@ public class Tile extends JComponent {
|
||||||
addMouseListener(TILE_MOUSE_LISTENER);
|
addMouseListener(TILE_MOUSE_LISTENER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tile(Canvas gameCanvas, Image[] numberTiles, Image[] specialTiles) {
|
public Tile(Canvas gameCanvas) {
|
||||||
GAME_CANVAS = gameCanvas;
|
GAME_CANVAS = gameCanvas;
|
||||||
NUMBER_TILES = numberTiles;
|
ADJACENT_TILES = new ArrayList<Tile>();
|
||||||
SPECIAL_TILES = specialTiles;
|
|
||||||
ADJACENT_TILES = new HashSet<Tile>();
|
|
||||||
|
|
||||||
|
// 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() {
|
TILE_MOUSE_LISTENER = new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mousePressed(MouseEvent e) {
|
public void mousePressed(MouseEvent e) {
|
||||||
|
@ -122,11 +134,17 @@ public class Tile extends JComponent {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setImages(Image[] numberTiles, Image[] specialTiles) {
|
||||||
|
this.numberTiles = numberTiles;
|
||||||
|
this.specialTiles = specialTiles;
|
||||||
|
}
|
||||||
|
|
||||||
public void restart() {
|
public void restart() {
|
||||||
removeMouseListener(TILE_MOUSE_LISTENER);
|
removeMouseListener(TILE_MOUSE_LISTENER);
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update ADJACENT_TILES at the start of the game
|
||||||
public void addAdjacentTile(Tile tile) {
|
public void addAdjacentTile(Tile tile) {
|
||||||
ADJACENT_TILES.add(tile);
|
ADJACENT_TILES.add(tile);
|
||||||
}
|
}
|
||||||
|
@ -145,6 +163,7 @@ public class Tile extends JComponent {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flip gameEnded and repaint one last time
|
||||||
public void gameEnd() {
|
public void gameEnd() {
|
||||||
removeMouseListener(TILE_MOUSE_LISTENER);
|
removeMouseListener(TILE_MOUSE_LISTENER);
|
||||||
gameEnded = true;
|
gameEnded = true;
|
||||||
|
@ -153,6 +172,7 @@ public class Tile extends JComponent {
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the pressed state, whenever something pressing-related happens
|
||||||
public void updatePressedState() {
|
public void updatePressedState() {
|
||||||
if (pressed != (GAME_CANVAS.isLeftMouseDown() && mouseOver)) {
|
if (pressed != (GAME_CANVAS.isLeftMouseDown() && mouseOver)) {
|
||||||
pressed = !pressed;
|
pressed = !pressed;
|
||||||
|
@ -167,6 +187,7 @@ public class Tile extends JComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flag on unrevealed tiles on right click
|
||||||
private void rightClickAction() {
|
private void rightClickAction() {
|
||||||
if (revealed)
|
if (revealed)
|
||||||
return;
|
return;
|
||||||
|
@ -176,11 +197,13 @@ public class Tile extends JComponent {
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Chord or reveal tiles on left click
|
||||||
private void leftClickAction() {
|
private void leftClickAction() {
|
||||||
if (GAME_CANVAS.isChording()) {
|
if (GAME_CANVAS.isChording()) {
|
||||||
if (!revealed)
|
if (!revealed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Check that chording should happen
|
||||||
int adjacentFlags = 0;
|
int adjacentFlags = 0;
|
||||||
for (Tile tile : ADJACENT_TILES)
|
for (Tile tile : ADJACENT_TILES)
|
||||||
if (tile.flagged)
|
if (tile.flagged)
|
||||||
|
@ -188,12 +211,14 @@ public class Tile extends JComponent {
|
||||||
if (adjacentFlags != num)
|
if (adjacentFlags != num)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Chord
|
||||||
for (Tile tile : ADJACENT_TILES)
|
for (Tile tile : ADJACENT_TILES)
|
||||||
if (!tile.flagged)
|
if (!tile.flagged)
|
||||||
tile.reveal();
|
tile.reveal();
|
||||||
} else {
|
} else {
|
||||||
if (flagged || revealed)
|
if (flagged || revealed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Ensure that mines are placed before revealing anything
|
// Ensure that mines are placed before revealing anything
|
||||||
GAME_CANVAS.tryStartGame(this);
|
GAME_CANVAS.tryStartGame(this);
|
||||||
reveal();
|
reveal();
|
||||||
|
@ -201,6 +226,7 @@ public class Tile extends JComponent {
|
||||||
GAME_CANVAS.postRevealCheck();
|
GAME_CANVAS.postRevealCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reveal the current tile
|
||||||
private void reveal() {
|
private void reveal() {
|
||||||
if (revealed)
|
if (revealed)
|
||||||
return;
|
return;
|
||||||
|
@ -210,37 +236,39 @@ public class Tile extends JComponent {
|
||||||
|
|
||||||
if (mine) {
|
if (mine) {
|
||||||
GAME_CANVAS.setLoseFlag();
|
GAME_CANVAS.setLoseFlag();
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
GAME_CANVAS.revealedSingleTile();
|
GAME_CANVAS.revealedSingleTile();
|
||||||
|
// Recursively reveal all adjacent tiles if this one is 0
|
||||||
if (num == 0)
|
if (num == 0)
|
||||||
for (Tile tile : ADJACENT_TILES)
|
for (Tile tile : ADJACENT_TILES)
|
||||||
if (!tile.flagged)
|
if (!tile.flagged)
|
||||||
tile.reveal();
|
tile.reveal();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Determine what image should be drawn at this tile
|
// Determine what image should be drawn at this tile
|
||||||
private Image getImage() {
|
private Image getImage() {
|
||||||
if (gameEnded && mine) {
|
if (gameEnded && mine) {
|
||||||
// Automatically flag mines at wins, losses keep correct flags
|
// Automatically flag mines at wins, losses keep correct flags
|
||||||
if (!GAME_CANVAS.isGameLost() || flagged)
|
if (!GAME_CANVAS.isGameLost() || flagged)
|
||||||
return SPECIAL_TILES[FLAGGED_INDEX];
|
return specialTiles[FLAGGED_INDEX];
|
||||||
if (revealed)
|
if (revealed)
|
||||||
return SPECIAL_TILES[EXPLODED_MINE_INDEX];
|
return specialTiles[EXPLODED_MINE_INDEX];
|
||||||
return SPECIAL_TILES[MINE_INDEX];
|
return specialTiles[MINE_INDEX];
|
||||||
}
|
}
|
||||||
if (revealed)
|
if (revealed)
|
||||||
return NUMBER_TILES[num];
|
return numberTiles[num];
|
||||||
if (flagged) {
|
if (flagged) {
|
||||||
// Correctly flagged mines would have been caught earlier
|
// Correctly flagged mines would have been caught earlier
|
||||||
if (gameEnded)
|
if (gameEnded)
|
||||||
return SPECIAL_TILES[NOT_MINE_INDEX];
|
return specialTiles[NOT_MINE_INDEX];
|
||||||
return SPECIAL_TILES[FLAGGED_INDEX];
|
return specialTiles[FLAGGED_INDEX];
|
||||||
}
|
}
|
||||||
// Tiles are only interactable if the game is ongoing
|
// Tiles are only interactable if the game is ongoing
|
||||||
if (pressed && !gameEnded)
|
if (pressed && !gameEnded)
|
||||||
return SPECIAL_TILES[PRESSED_INDEX];
|
return specialTiles[PRESSED_INDEX];
|
||||||
return SPECIAL_TILES[REGULAR_INDEX];
|
return specialTiles[REGULAR_INDEX];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in a new issue