Finished everything minus comments
This commit is contained in:
parent
5e9d532b95
commit
1d5ade9136
23
Canvas.java
23
Canvas.java
|
@ -67,10 +67,11 @@ public class Canvas extends JPanel {
|
||||||
TIMER_DISPLAY.setNum(time);
|
TIMER_DISPLAY.setNum(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Canvas(int rows, int cols, int mines, File skin) {
|
public Canvas(File skin) {
|
||||||
ROWS = rows;
|
Difficulty difficulty = Options.getDifficulty();
|
||||||
COLS = cols;
|
ROWS = difficulty.getRows();
|
||||||
MINES = mines;
|
COLS = difficulty.getCols();
|
||||||
|
MINES = difficulty.getMines();
|
||||||
WIDTH = Tile.SIZE * COLS + 2 * BORDER_WIDTH;
|
WIDTH = Tile.SIZE * COLS + 2 * BORDER_WIDTH;
|
||||||
HEIGHT = Tile.SIZE * ROWS + 2 * BORDER_HEIGHT + BOTTOM_HEIGHT + TOP_BOX_HEIGHT;
|
HEIGHT = Tile.SIZE * ROWS + 2 * BORDER_HEIGHT + BOTTOM_HEIGHT + TOP_BOX_HEIGHT;
|
||||||
|
|
||||||
|
@ -127,7 +128,7 @@ public class Canvas extends JPanel {
|
||||||
TIMER_DISPLAY = new NumberDisplay(numberDisplayBackdrop, digits);
|
TIMER_DISPLAY = new NumberDisplay(numberDisplayBackdrop, digits);
|
||||||
FACE = new Face(this, getImageSet(tileset, Face.PRESSED_INDEX, TS_FACES_Y, Face.SIZE, Face.SIZE));
|
FACE = new Face(this, getImageSet(tileset, Face.PRESSED_INDEX, TS_FACES_Y, Face.SIZE, Face.SIZE));
|
||||||
|
|
||||||
SHIFT_KEY_LISTENER = new KeyListener() {
|
SHIFT_KEY_LISTENER = new KeyAdapter() {
|
||||||
// Shift+LMB chords, so we need some way of determining if the shift
|
// Shift+LMB chords, so we need some way of determining if the shift
|
||||||
// key is being held.
|
// key is being held.
|
||||||
@Override
|
@Override
|
||||||
|
@ -146,10 +147,6 @@ public class Canvas extends JPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void keyTyped(KeyEvent e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateCurrentTile() {
|
private void updateCurrentTile() {
|
||||||
if (currentTile != null)
|
if (currentTile != null)
|
||||||
currentTile.updatePressedState();
|
currentTile.updatePressedState();
|
||||||
|
@ -196,6 +193,7 @@ public class Canvas extends JPanel {
|
||||||
|
|
||||||
stop();
|
stop();
|
||||||
init();
|
init();
|
||||||
|
|
||||||
for (Tile[] row : BOARD)
|
for (Tile[] row : BOARD)
|
||||||
for (Tile tile : row)
|
for (Tile tile : row)
|
||||||
tile.restart();
|
tile.restart();
|
||||||
|
@ -323,7 +321,7 @@ public class Canvas extends JPanel {
|
||||||
Image bottomRightCorner = tileset.getSubimage(BORDER_WIDTH + 3, tsY, BORDER_WIDTH, BOTTOM_HEIGHT);
|
Image bottomRightCorner = tileset.getSubimage(BORDER_WIDTH + 3, tsY, BORDER_WIDTH, BOTTOM_HEIGHT);
|
||||||
|
|
||||||
BufferedImage img = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
|
BufferedImage img = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
|
||||||
Graphics g = img.getGraphics();
|
Graphics2D g = (Graphics2D) img.getGraphics();
|
||||||
|
|
||||||
int rightBorderX = WIDTH - BORDER_WIDTH;
|
int rightBorderX = WIDTH - BORDER_WIDTH;
|
||||||
int middleBorderY = BORDER_HEIGHT + TOP_BOX_HEIGHT;
|
int middleBorderY = BORDER_HEIGHT + TOP_BOX_HEIGHT;
|
||||||
|
@ -365,8 +363,8 @@ public class Canvas extends JPanel {
|
||||||
// Helper for placing components in the null layout at specific coordinates
|
// Helper for placing components in the null layout at specific coordinates
|
||||||
private void placeComponent(JComponent c, int x, int y, Insets insets) {
|
private void placeComponent(JComponent c, int x, int y, Insets insets) {
|
||||||
add(c);
|
add(c);
|
||||||
Dimension size = c.getPreferredSize();
|
c.setLocation(x + insets.left, y + insets.top);
|
||||||
c.setBounds(x + insets.left, y + insets.top, size.width, size.height);
|
c.setSize(c.getPreferredSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopGame() {
|
private void stopGame() {
|
||||||
|
@ -380,6 +378,7 @@ 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 g) {
|
||||||
|
super.paintComponent(g);
|
||||||
g.drawImage(BACKGROUND_IMAGE, 0, 0, this);
|
g.drawImage(BACKGROUND_IMAGE, 0, 0, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
70
CustomTextField.java
Normal file
70
CustomTextField.java
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import java.awt.event.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
public class CustomTextField extends JTextField {
|
||||||
|
private static final int COLS = 4;
|
||||||
|
|
||||||
|
private final int MIN_VALUE;
|
||||||
|
private final int MAX_VALUE;
|
||||||
|
|
||||||
|
public CustomTextField(int defaultValue, int minValue, int maxValue) {
|
||||||
|
super(Integer.toString(defaultValue), COLS);
|
||||||
|
|
||||||
|
MIN_VALUE = minValue;
|
||||||
|
MAX_VALUE = maxValue;
|
||||||
|
|
||||||
|
addFocusListener(new FocusAdapter() {
|
||||||
|
// Auto select when focused
|
||||||
|
@Override
|
||||||
|
public void focusGained(FocusEvent e) {
|
||||||
|
selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto format input when unfocused
|
||||||
|
@Override
|
||||||
|
public void focusLost(FocusEvent e) {
|
||||||
|
formatText();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void formatText() {
|
||||||
|
String text = getText();
|
||||||
|
|
||||||
|
// If tried to input a negative number, set it to the minimum
|
||||||
|
if (text.isEmpty() || text.charAt(0) == '-') {
|
||||||
|
setValue(getMinValue());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all the nondigit characters and all leading zeros
|
||||||
|
String filteredText = text.replaceAll("(^[^1-9]+|\\D)", "");
|
||||||
|
if (filteredText.isEmpty()) {
|
||||||
|
setValue(getMinValue());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To prevent integer overflow when parsing, just set it to max
|
||||||
|
// when it's more than 4 digits
|
||||||
|
if (filteredText.length() > 4) {
|
||||||
|
setValue(getMaxValue());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we finally have the intended int, clamp and set it
|
||||||
|
setValue(Difficulty.clampInt(Integer.parseInt(filteredText), getMinValue(), getMaxValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use protected getters and setters so we can override them if necessary
|
||||||
|
protected int getMinValue() {
|
||||||
|
return MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getMaxValue() {
|
||||||
|
return MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setValue(int value) {
|
||||||
|
setText(Integer.toString(value));
|
||||||
|
}
|
||||||
|
}
|
179
Difficulty.java
Normal file
179
Difficulty.java
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
public enum Difficulty {
|
||||||
|
BEGINNER (9, 9, 10),
|
||||||
|
INTERMEDIATE (16, 16, 40),
|
||||||
|
EXPERT (16, 30, 99),
|
||||||
|
CUSTOM;
|
||||||
|
|
||||||
|
// Custom difficulty constraints
|
||||||
|
//
|
||||||
|
// In actual minesweeper these numbers are 9, 24, 30, 10
|
||||||
|
private static final int MIN_SIZE = 8;
|
||||||
|
private static final int MAX_ROWS = 40;
|
||||||
|
private static final int MAX_COLS = 50;
|
||||||
|
private static final int MIN_MINES = 10;
|
||||||
|
// In regular minesweeper the mines are capped at just (rows-1)*(cols-1),
|
||||||
|
// but we additionally cap it at 999 because the expanded board size means
|
||||||
|
// it's possible to hit the number display cap
|
||||||
|
private static final int MAX_MINES = 999;
|
||||||
|
|
||||||
|
private int rows;
|
||||||
|
private int cols;
|
||||||
|
private int mines;
|
||||||
|
|
||||||
|
private Difficulty() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private Difficulty(int rows, int cols, int mines) {
|
||||||
|
setStats(rows, cols, mines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setStats(int rows, int cols, int mines) {
|
||||||
|
this.rows = rows;
|
||||||
|
this.cols = cols;
|
||||||
|
this.mines = mines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRows() {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCols() {
|
||||||
|
return cols;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMines() {
|
||||||
|
return mines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean setCustom(JFrame frame) {
|
||||||
|
Difficulty current = Options.getDifficulty();
|
||||||
|
|
||||||
|
// Use single-item array to store a reference to minesField, because
|
||||||
|
// minesField needs to needs to be initialized after rowsField and
|
||||||
|
// colsField but RowsColsField needs minesField
|
||||||
|
CustomTextField[] minesRef = new CustomTextField[1];
|
||||||
|
// Override CustomTextField in order to make it format minesField after
|
||||||
|
// editing (when the board size is decreased, so does the max mine
|
||||||
|
// count, so we may need to update it)
|
||||||
|
class RowsColsField extends CustomTextField {
|
||||||
|
private RowsColsField(int defaultValue, int minValue, int maxValue) {
|
||||||
|
super(defaultValue, minValue, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void formatText() {
|
||||||
|
super.formatText();
|
||||||
|
minesRef[0].formatText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomTextField rowsField = new RowsColsField(current.rows, MIN_SIZE, MAX_ROWS);
|
||||||
|
CustomTextField colsField = new RowsColsField(current.cols, MIN_SIZE, MAX_COLS);
|
||||||
|
// Override CustomTextField in order to make it dynamically determine
|
||||||
|
// the maximum value based on the number of rows and columns
|
||||||
|
CustomTextField minesField = minesRef[0] = new CustomTextField(current.mines, MIN_MINES, MAX_MINES) {
|
||||||
|
@Override
|
||||||
|
protected int getMaxValue() {
|
||||||
|
try {
|
||||||
|
int rows = Integer.parseInt(rowsField.getText());
|
||||||
|
int cols = Integer.parseInt(colsField.getText());
|
||||||
|
return getMaxMines(rows, cols);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return super.getMaxValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Because Java is lame and doesn't have builtin tuples we make do with
|
||||||
|
// casting an JComponent[][]
|
||||||
|
JComponent[][] items = {
|
||||||
|
{ new JLabel("Height:"), rowsField },
|
||||||
|
{ new JLabel("Width:"), colsField },
|
||||||
|
{ new JLabel("Mines:"), minesField },
|
||||||
|
};
|
||||||
|
|
||||||
|
int option = JOptionPane.showConfirmDialog(frame, getMessagePanel(items, 6),
|
||||||
|
"Custom Board", JOptionPane.OK_CANCEL_OPTION);
|
||||||
|
if (option != JOptionPane.OK_OPTION)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int rows, cols, mines;
|
||||||
|
try {
|
||||||
|
rows = Integer.parseInt(rowsField.getText());
|
||||||
|
cols = Integer.parseInt(colsField.getText());
|
||||||
|
mines = Integer.parseInt(minesField.getText());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp values to the allowed range, just in case something slipped
|
||||||
|
// through the focusLost events
|
||||||
|
rows = clampInt(rows, MIN_SIZE, MAX_ROWS);
|
||||||
|
cols = clampInt(cols, MIN_SIZE, MAX_COLS);
|
||||||
|
mines = clampInt(mines, MIN_MINES, getMaxMines(rows, cols));
|
||||||
|
|
||||||
|
// If we nothing changed, return false
|
||||||
|
if (current == CUSTOM && CUSTOM.rows == rows && CUSTOM.cols == cols && CUSTOM.mines == mines)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
CUSTOM.setStats(rows, cols, mines);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int clampInt(int value, int minValue, int maxValue) {
|
||||||
|
return Math.max(Math.min(value, maxValue), minValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getMaxMines(int rows, int cols) {
|
||||||
|
return Math.min((rows - 1) * (cols - 1), MAX_MINES);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JPanel getMessagePanel(JComponent[][] items, int padding) {
|
||||||
|
final int COLS = 2;
|
||||||
|
|
||||||
|
JPanel messagePanel = new JPanel(null);
|
||||||
|
|
||||||
|
for (JComponent[] row : items) {
|
||||||
|
JLabel label = (JLabel) row[0];
|
||||||
|
label.setLabelFor(row[1]);
|
||||||
|
label.setHorizontalAlignment(JLabel.TRAILING);
|
||||||
|
messagePanel.add(label);
|
||||||
|
messagePanel.add(row[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] widths = new int[COLS];
|
||||||
|
int[] heights = new int[items.length];
|
||||||
|
int[] x = new int[COLS + 1];
|
||||||
|
int[] y = new int[items.length + 1];
|
||||||
|
|
||||||
|
x[0] = padding;
|
||||||
|
for (int c = 0; c < COLS; c++) {
|
||||||
|
int maxWidth = Integer.MIN_VALUE;
|
||||||
|
for (int r = 0; r < items.length; r++)
|
||||||
|
maxWidth = Math.max(maxWidth, (int) items[r][c].getPreferredSize().getWidth());
|
||||||
|
widths[c] = maxWidth;
|
||||||
|
x[c + 1] = x[c] + maxWidth + padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int r = 0; r < items.length; r++) {
|
||||||
|
int maxHeight = Integer.MIN_VALUE;
|
||||||
|
for (int c = 0; c < COLS; c++)
|
||||||
|
maxHeight = Math.max(maxHeight, (int) items[r][c].getPreferredSize().getHeight());
|
||||||
|
heights[r] = maxHeight;
|
||||||
|
y[r + 1] = y[r] + maxHeight + padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int r = 0; r < items.length; r++)
|
||||||
|
for (int c = 0; c < COLS; c++)
|
||||||
|
items[r][c].setBounds(x[c], y[r], widths[c], heights[r]);
|
||||||
|
|
||||||
|
messagePanel.setPreferredSize(new Dimension(x[COLS], y[items.length]));
|
||||||
|
|
||||||
|
return messagePanel;
|
||||||
|
}
|
||||||
|
}
|
11
Face.java
11
Face.java
|
@ -24,7 +24,7 @@ public class Face extends JComponent {
|
||||||
pressed = false;
|
pressed = false;
|
||||||
mouseOver = false;
|
mouseOver = false;
|
||||||
setPreferredSize(new Dimension(SIZE, SIZE));
|
setPreferredSize(new Dimension(SIZE, SIZE));
|
||||||
addMouseListener(new MouseListener() {
|
addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mousePressed(MouseEvent e) {
|
public void mousePressed(MouseEvent e) {
|
||||||
pressed = true;
|
pressed = true;
|
||||||
|
@ -54,10 +54,6 @@ public class Face extends JComponent {
|
||||||
if (pressed)
|
if (pressed)
|
||||||
setFace(unpressedFace);
|
setFace(unpressedFace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseClicked(MouseEvent e) {
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +63,10 @@ public class Face extends JComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void paintComponent(Graphics g) {
|
public void paintComponent(Graphics gr) {
|
||||||
|
super.paintComponent(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,22 +7,6 @@ import javax.swing.*;
|
||||||
import javax.swing.event.*;
|
import javax.swing.event.*;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
private static enum Difficulty {
|
|
||||||
BEGINNER (9, 9, 10),
|
|
||||||
INTERMEDIATE (16, 16, 40),
|
|
||||||
EXPERT (16, 30, 99);
|
|
||||||
|
|
||||||
public final int ROWS;
|
|
||||||
public final int COLS;
|
|
||||||
public final int MINES;
|
|
||||||
|
|
||||||
Difficulty(int rows, int cols, int mines) {
|
|
||||||
this.ROWS = rows;
|
|
||||||
this.COLS = cols;
|
|
||||||
this.MINES = mines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final String DEFAULT_SKIN = "winxpskin.bmp";
|
public static final String DEFAULT_SKIN = "winxpskin.bmp";
|
||||||
public static final String SKINS_DIR = "Skins/";
|
public static final String SKINS_DIR = "Skins/";
|
||||||
public static final String HELP_FILE = "help.html";
|
public static final String HELP_FILE = "help.html";
|
||||||
|
@ -33,36 +17,41 @@ public class Main {
|
||||||
private static final JFrame FRAME = new JFrame("Minesweeper");
|
private static final JFrame FRAME = new JFrame("Minesweeper");
|
||||||
|
|
||||||
private static Canvas canvas;
|
private static Canvas canvas;
|
||||||
private static Difficulty difficulty;
|
|
||||||
private static String skin;
|
private static String skin;
|
||||||
private static boolean protectedStart;
|
|
||||||
private static boolean sound;
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
difficulty = Difficulty.INTERMEDIATE;
|
|
||||||
skin = DEFAULT_SKIN;
|
skin = DEFAULT_SKIN;
|
||||||
protectedStart = false;
|
|
||||||
sound = false;
|
|
||||||
setCanvas();
|
|
||||||
|
|
||||||
FRAME.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
FRAME.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
FRAME.setResizable(false);
|
FRAME.setResizable(false);
|
||||||
FRAME.setJMenuBar(getMenuBar());
|
FRAME.setJMenuBar(getMenuBar());
|
||||||
FRAME.add(canvas);
|
setCanvas();
|
||||||
FRAME.pack();
|
|
||||||
FRAME.setVisible(true);
|
FRAME.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isProtectedStart() {
|
|
||||||
return protectedStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean hasSound() {
|
|
||||||
return sound;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JMenuBar getMenuBar() {
|
private static JMenuBar getMenuBar() {
|
||||||
|
// Button group for the difficulty buttons
|
||||||
|
ButtonGroup difficultyButtons = new ButtonGroup();
|
||||||
|
Difficulty difficulty = Options.getDifficulty();
|
||||||
|
JRadioButtonMenuItem beginnerItem = new JRadioButtonMenuItem("Beginner",
|
||||||
|
difficulty == Difficulty.BEGINNER);
|
||||||
|
JRadioButtonMenuItem intermediateItem = new JRadioButtonMenuItem("Intermediate",
|
||||||
|
difficulty == Difficulty.INTERMEDIATE);
|
||||||
|
JRadioButtonMenuItem expertItem = new JRadioButtonMenuItem("Expert",
|
||||||
|
difficulty == Difficulty.EXPERT);
|
||||||
|
JRadioButtonMenuItem customItem = new JRadioButtonMenuItem("Custom...");
|
||||||
|
difficultyButtons.add(beginnerItem);
|
||||||
|
difficultyButtons.add(intermediateItem);
|
||||||
|
difficultyButtons.add(expertItem);
|
||||||
|
difficultyButtons.add(customItem);
|
||||||
|
|
||||||
ActionListener listener = new ActionListener() {
|
ActionListener listener = new ActionListener() {
|
||||||
|
// Keep track of the previously selected difficulty button in case a
|
||||||
|
// Custom press is canceled so we can revert back to whatever it was
|
||||||
|
// before
|
||||||
|
private ButtonModel lastDifficultyButton = difficultyButtons.getSelection();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
switch (e.getActionCommand()) {
|
switch (e.getActionCommand()) {
|
||||||
|
@ -71,21 +60,29 @@ public class Main {
|
||||||
break;
|
break;
|
||||||
case "beginner":
|
case "beginner":
|
||||||
setDifficulty(Difficulty.BEGINNER);
|
setDifficulty(Difficulty.BEGINNER);
|
||||||
|
lastDifficultyButton = beginnerItem.getModel();
|
||||||
break;
|
break;
|
||||||
case "intermediate":
|
case "intermediate":
|
||||||
setDifficulty(Difficulty.INTERMEDIATE);
|
setDifficulty(Difficulty.INTERMEDIATE);
|
||||||
|
lastDifficultyButton = intermediateItem.getModel();
|
||||||
break;
|
break;
|
||||||
case "expert":
|
case "expert":
|
||||||
setDifficulty(Difficulty.EXPERT);
|
setDifficulty(Difficulty.EXPERT);
|
||||||
|
lastDifficultyButton = expertItem.getModel();
|
||||||
break;
|
break;
|
||||||
case "custom":
|
case "custom":
|
||||||
setDifficulty(null);
|
if (Difficulty.setCustom(FRAME)) {
|
||||||
|
setDifficulty(Difficulty.CUSTOM);
|
||||||
|
lastDifficultyButton = customItem.getModel();
|
||||||
|
} else {
|
||||||
|
lastDifficultyButton.setSelected(true);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "protectedstart":
|
case "protectedstart":
|
||||||
protectedStart = !protectedStart;
|
Options.toggleProtectedStart();
|
||||||
break;
|
break;
|
||||||
case "sound":
|
case "sound":
|
||||||
sound = !sound;
|
Options.toggleSound();
|
||||||
Sound.initSounds();
|
Sound.initSounds();
|
||||||
break;
|
break;
|
||||||
case "exit":
|
case "exit":
|
||||||
|
@ -111,25 +108,17 @@ public class Main {
|
||||||
newGameItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
|
newGameItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
|
||||||
addMenuItem(newGameItem, gameMenu, KeyEvent.VK_N, "new", listener);
|
addMenuItem(newGameItem, gameMenu, KeyEvent.VK_N, "new", listener);
|
||||||
gameMenu.addSeparator();
|
gameMenu.addSeparator();
|
||||||
ButtonGroup difficultyButtons = new ButtonGroup();
|
|
||||||
JRadioButtonMenuItem beginnerItem = new JRadioButtonMenuItem("Beginner");
|
|
||||||
JRadioButtonMenuItem intermediateItem = new JRadioButtonMenuItem("Intermediate", true);
|
|
||||||
JRadioButtonMenuItem expertItem = new JRadioButtonMenuItem("Advanced");
|
|
||||||
JRadioButtonMenuItem customItem = new JRadioButtonMenuItem("Custom...");
|
|
||||||
difficultyButtons.add(beginnerItem);
|
|
||||||
difficultyButtons.add(intermediateItem);
|
|
||||||
difficultyButtons.add(expertItem);
|
|
||||||
difficultyButtons.add(customItem);
|
|
||||||
addMenuItem(beginnerItem, gameMenu, KeyEvent.VK_B, "beginner", listener);
|
addMenuItem(beginnerItem, gameMenu, KeyEvent.VK_B, "beginner", listener);
|
||||||
addMenuItem(intermediateItem, gameMenu, KeyEvent.VK_I, "intermediate", listener);
|
addMenuItem(intermediateItem, gameMenu, KeyEvent.VK_I, "intermediate", listener);
|
||||||
addMenuItem(expertItem, gameMenu, KeyEvent.VK_E, "expert", listener);
|
addMenuItem(expertItem, gameMenu, KeyEvent.VK_E, "expert", listener);
|
||||||
addMenuItem(customItem, gameMenu, KeyEvent.VK_C, "custom", listener);
|
addMenuItem(customItem, gameMenu, KeyEvent.VK_C, "custom", listener);
|
||||||
gameMenu.addSeparator();
|
gameMenu.addSeparator();
|
||||||
JCheckBoxMenuItem protectedStartItem = new JCheckBoxMenuItem("Protected Start", protectedStart);
|
JCheckBoxMenuItem protectedStartItem = new JCheckBoxMenuItem("Protected Start",
|
||||||
|
Options.isProtectedStart());
|
||||||
protectedStartItem.setToolTipText("Guarantees the starting tile has no mines next to it");
|
protectedStartItem.setToolTipText("Guarantees the starting tile has no mines next to it");
|
||||||
addMenuItem(protectedStartItem, gameMenu, KeyEvent.VK_P, "protectedstart", listener);
|
addMenuItem(protectedStartItem, gameMenu, KeyEvent.VK_P, "protectedstart", listener);
|
||||||
JCheckBoxMenuItem soundItem = new JCheckBoxMenuItem("Sound", sound);
|
JCheckBoxMenuItem soundItem = new JCheckBoxMenuItem("Sound", Options.hasSound());
|
||||||
addMenuItem(soundItem, gameMenu, KeyEvent.VK_O, "sound", listener);
|
addMenuItem(soundItem, gameMenu, KeyEvent.VK_U, "sound", listener);
|
||||||
gameMenu.addSeparator();
|
gameMenu.addSeparator();
|
||||||
addMenuItem("Exit", gameMenu, KeyEvent.VK_X, "exit", listener);
|
addMenuItem("Exit", gameMenu, KeyEvent.VK_X, "exit", listener);
|
||||||
menuBar.add(gameMenu);
|
menuBar.add(gameMenu);
|
||||||
|
@ -167,7 +156,7 @@ public class Main {
|
||||||
try (Stream<Path> dirStream = Files.list(Paths.get(SKINS_DIR))) {
|
try (Stream<Path> dirStream = Files.list(Paths.get(SKINS_DIR))) {
|
||||||
dirStream
|
dirStream
|
||||||
// Filter for only regular files
|
// Filter for only regular files
|
||||||
.filter(file -> Files.isRegularFile(file))
|
.filter(Files::isRegularFile)
|
||||||
// Cut off the directory name from the resulting string
|
// Cut off the directory name from the resulting string
|
||||||
.map(Path::getFileName)
|
.map(Path::getFileName)
|
||||||
.map(Path::toString)
|
.map(Path::toString)
|
||||||
|
@ -220,11 +209,11 @@ public class Main {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setDifficulty(Difficulty newDifficulty) {
|
private static void setDifficulty(Difficulty newDifficulty) {
|
||||||
if (newDifficulty == null);
|
// Reject switching difficulty to the already-existing one unless Custom
|
||||||
else if (difficulty == newDifficulty)
|
if (Options.getDifficulty() == newDifficulty && newDifficulty != Difficulty.CUSTOM)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
difficulty = newDifficulty;
|
Options.setDifficulty(newDifficulty);
|
||||||
replaceCanvas();
|
replaceCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,24 +239,17 @@ public class Main {
|
||||||
canvas.stop();
|
canvas.stop();
|
||||||
canvas.removeAll();
|
canvas.removeAll();
|
||||||
FRAME.remove(canvas);
|
FRAME.remove(canvas);
|
||||||
|
|
||||||
setCanvas();
|
setCanvas();
|
||||||
|
canvas.requestFocusInWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setCanvas() {
|
private static void setCanvas() {
|
||||||
canvas = getCanvas();
|
canvas = new Canvas(new File(SKINS_DIR, skin));
|
||||||
FRAME.add(canvas);
|
FRAME.add(canvas);
|
||||||
FRAME.pack();
|
FRAME.pack();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Canvas getCanvas() {
|
|
||||||
File skinFile = new File(SKINS_DIR, skin);
|
|
||||||
if (difficulty == null) {
|
|
||||||
// TODO: Make the difficulty actually change
|
|
||||||
return new Canvas(40, 50, 500, skinFile);
|
|
||||||
}
|
|
||||||
return new Canvas(difficulty.ROWS, difficulty.COLS, difficulty.MINES, skinFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
// No construction >:(
|
// No construction >:(
|
||||||
private Main() {
|
private Main() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,10 @@ public class NumberDisplay extends JComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void paintComponent(Graphics g) {
|
public void paintComponent(Graphics gr) {
|
||||||
|
super.paintComponent(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 divison, in case we repaint twice
|
||||||
// without updating the number
|
// without updating the number
|
||||||
|
|
37
Options.java
Normal file
37
Options.java
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// Public class that stores all the global static options
|
||||||
|
public class Options {
|
||||||
|
// Whether or not sound is enabled
|
||||||
|
private static boolean sound = false;
|
||||||
|
// Whether or not to force starting at 0
|
||||||
|
private static boolean protectedStart = false;
|
||||||
|
// The difficulty
|
||||||
|
private static Difficulty difficulty = Difficulty.INTERMEDIATE;
|
||||||
|
|
||||||
|
public static boolean hasSound() {
|
||||||
|
return sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void toggleSound() {
|
||||||
|
sound = !sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isProtectedStart() {
|
||||||
|
return protectedStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void toggleProtectedStart() {
|
||||||
|
protectedStart = !protectedStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Difficulty getDifficulty() {
|
||||||
|
return difficulty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDifficulty(Difficulty difficulty) {
|
||||||
|
Options.difficulty = difficulty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No constructing >:(
|
||||||
|
private Options() {
|
||||||
|
}
|
||||||
|
}
|
23
Sound.java
23
Sound.java
|
@ -14,7 +14,8 @@ public enum Sound {
|
||||||
// Named pipe to send audio requests to on Replit
|
// Named pipe to send audio requests to on Replit
|
||||||
public static final File REPLIT_PIPE = new File("/tmp/audio");
|
public static final File REPLIT_PIPE = new File("/tmp/audio");
|
||||||
|
|
||||||
// Use env vars and the existance of the above pipe to determine if running inside Replit
|
// Use env vars and the existance of the above pipe to determine if we are
|
||||||
|
// running inside Replit and should use its sound API
|
||||||
public static final boolean REPLIT_API = System.getenv("REPL_ID") != null && REPLIT_PIPE.exists();
|
public static final boolean REPLIT_API = System.getenv("REPL_ID") != null && REPLIT_PIPE.exists();
|
||||||
|
|
||||||
// Whether the clips have been initialized yet
|
// Whether the clips have been initialized yet
|
||||||
|
@ -29,15 +30,15 @@ public enum Sound {
|
||||||
// Because Replit does audio totally differently from desktop Java (it
|
// Because Replit does audio totally differently from desktop Java (it
|
||||||
// supposedly supports PulseAudio over VNC but I just could not get that
|
// supposedly supports PulseAudio over VNC but I just could not get that
|
||||||
// working), and I would like to still be able to hear things when I play
|
// working), and I would like to still be able to hear things when I play
|
||||||
// the game directly on my laptop, we have a SoundBackend interface that is
|
// the game directly on my laptop, we have an abstract SoundBackend class
|
||||||
// implemented by both a builtin javax.sound.sampled-powered backend and a
|
// that is extended by both a builtin javax.sound.sampled-powered backend
|
||||||
// Replit specific one
|
// and a Replit specific one
|
||||||
private interface SoundBackend {
|
private abstract class SoundBackend {
|
||||||
// plays the specific sound
|
// Plays the specific sound
|
||||||
public void play();
|
abstract public void play();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NativeBackend implements SoundBackend {
|
private class NativeBackend extends SoundBackend {
|
||||||
// The audio clip associated with the sound
|
// The audio clip associated with the sound
|
||||||
private final Clip CLIP;
|
private final Clip CLIP;
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ public enum Sound {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ReplitBackend implements SoundBackend {
|
private class ReplitBackend extends SoundBackend {
|
||||||
// The JSON format to send requests to Replit with; the volume is 0.1
|
// The JSON format to send requests to Replit with; the volume is 0.1
|
||||||
// because my ears got blown out the first time I exploded when it was
|
// because my ears got blown out the first time I exploded when it was
|
||||||
// at 100% volume (Replit is LOUD)
|
// at 100% volume (Replit is LOUD)
|
||||||
|
@ -99,7 +100,7 @@ public enum Sound {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Sound(String fileName) {
|
private Sound(String fileName) {
|
||||||
FILE = new File(AUDIO_DIR, fileName);
|
FILE = new File(AUDIO_DIR, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +136,7 @@ public enum Sound {
|
||||||
// The actual method that users outside this file will call to play sound
|
// The actual method that users outside this file will call to play sound
|
||||||
public void play() {
|
public void play() {
|
||||||
// Here is the check for if the option for sound is actually enabled
|
// Here is the check for if the option for sound is actually enabled
|
||||||
if (Main.hasSound())
|
if (Options.hasSound())
|
||||||
soundBackend.play();
|
soundBackend.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
Tile.java
13
Tile.java
|
@ -52,7 +52,7 @@ public class Tile extends JComponent {
|
||||||
SPECIAL_TILES = specialTiles;
|
SPECIAL_TILES = specialTiles;
|
||||||
ADJACENT_TILES = new HashSet<Tile>();
|
ADJACENT_TILES = new HashSet<Tile>();
|
||||||
|
|
||||||
TILE_MOUSE_LISTENER = new MouseListener() {
|
TILE_MOUSE_LISTENER = new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mousePressed(MouseEvent e) {
|
public void mousePressed(MouseEvent e) {
|
||||||
boolean rightClicked = false;
|
boolean rightClicked = false;
|
||||||
|
@ -115,10 +115,6 @@ public class Tile extends JComponent {
|
||||||
mouseOver = false;
|
mouseOver = false;
|
||||||
updatePressedState();
|
updatePressedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseClicked(MouseEvent e) {
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setPreferredSize(new Dimension(SIZE, SIZE));
|
setPreferredSize(new Dimension(SIZE, SIZE));
|
||||||
|
@ -139,7 +135,7 @@ public class Tile extends JComponent {
|
||||||
// adjacent to or is the starting tile, or is already a mine. Returns
|
// adjacent to or is the starting tile, or is already a mine. Returns
|
||||||
// whether or not a mine was actually placed.
|
// whether or not a mine was actually placed.
|
||||||
public boolean makeMine(Tile startTile) {
|
public boolean makeMine(Tile startTile) {
|
||||||
if (mine || this == startTile || Main.isProtectedStart() && ADJACENT_TILES.contains(startTile))
|
if (mine || this == startTile || Options.isProtectedStart() && ADJACENT_TILES.contains(startTile))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
mine = true;
|
mine = true;
|
||||||
|
@ -248,7 +244,10 @@ public class Tile extends JComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void paintComponent(Graphics g) {
|
public void paintComponent(Graphics gr) {
|
||||||
|
super.paintComponent(gr);
|
||||||
|
Graphics2D g = (Graphics2D) gr;
|
||||||
|
|
||||||
g.drawImage(getImage(), 0, 0, this);
|
g.drawImage(getImage(), 0, 0, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue