javasweeper/Difficulty.java

216 lines
7.1 KiB
Java
Raw Permalink Normal View History

2023-05-24 17:34:20 -07:00
import java.awt.*;
import javax.swing.*;
2023-05-25 19:44:14 -07:00
// Enum with the difficuulty options
2023-05-24 17:34:20 -07:00
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;
2023-05-25 19:44:14 -07:00
// Use nonfinal ints with getter methods because custom needs to be able to
// change its stats
2023-05-24 17:34:20 -07:00
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;
}
2023-05-25 19:44:14 -07:00
// Show a popup window to select attributes of the custom game, return
// whether or not it succeeded
2023-05-24 17:34:20 -07:00
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();
}
}
2023-05-25 19:44:14 -07:00
// Initialize the text fields
2023-05-24 17:34:20 -07:00
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
2023-05-25 19:44:14 -07:00
// a JComponent[][] to store the structure of the input window
2023-05-24 17:34:20 -07:00
JComponent[][] items = {
{ new JLabel("Height:"), rowsField },
{ new JLabel("Width:"), colsField },
{ new JLabel("Mines:"), minesField },
};
2023-05-25 19:44:14 -07:00
// Show the message window now and get the result
int option = JOptionPane.showConfirmDialog(frame, getMessagePanel(items),
2023-05-24 17:34:20 -07:00
"Custom Board", JOptionPane.OK_CANCEL_OPTION);
2023-05-25 19:44:14 -07:00
// If user didn't hit OK, bail out now
2023-05-24 17:34:20 -07:00
if (option != JOptionPane.OK_OPTION)
return false;
2023-05-25 19:44:14 -07:00
// Bail out if fields can't be parsed into integers
2023-05-24 17:34:20 -07:00
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;
}
2023-05-25 19:44:14 -07:00
// Even though the text fields should technically only allow valid
// inputs, we should clamp values to the allowed range, just in case
// something slipped through the focusLost events
2023-05-24 17:34:20 -07:00
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;
2023-05-25 19:44:14 -07:00
// Everything succeeded, so now actually set the new stats
2023-05-24 17:34:20 -07:00
CUSTOM.setStats(rows, cols, mines);
return true;
}
2023-05-25 19:44:14 -07:00
// Helper method to implement something that every language except Java
// already has builtin
2023-05-24 17:34:20 -07:00
public static int clampInt(int value, int minValue, int maxValue) {
return Math.max(Math.min(value, maxValue), minValue);
}
2023-05-25 19:44:14 -07:00
// The regular formula for maximum mines is the product of rows and columns
// minus one, but also cap it at 999
2023-05-24 17:34:20 -07:00
private static int getMaxMines(int rows, int cols) {
return Math.min((rows - 1) * (cols - 1), MAX_MINES);
}
2023-05-25 19:44:14 -07:00
// 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
2023-05-24 17:34:20 -07:00
final int COLS = 2;
2023-05-25 19:44:14 -07:00
// Null layout because the only layout manager that can do what we we
// want is SpringLayout which the same complexity
2023-05-24 17:34:20 -07:00
JPanel messagePanel = new JPanel(null);
2023-05-25 19:44:14 -07:00
// Add the items to the panel, setting the label alignment along the way
2023-05-24 17:34:20 -07:00
for (JComponent[] row : items) {
JLabel label = (JLabel) row[0];
label.setLabelFor(row[1]);
label.setHorizontalAlignment(JLabel.TRAILING);
messagePanel.add(label);
messagePanel.add(row[1]);
}
2023-05-25 19:44:14 -07:00
// The maximum width of all the elements of each column
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
2023-05-24 17:34:20 -07:00
int[] x = new int[COLS + 1];
2023-05-25 19:44:14 -07:00
// The y-position of all the elements of each row
2023-05-24 17:34:20 -07:00
int[] y = new int[items.length + 1];
2023-05-25 19:44:14 -07:00
// The last value of x and y hold the size of the panel itself
2023-05-24 17:34:20 -07:00
2023-05-25 19:44:14 -07:00
// Set x and maxWidths
x[0] = PADDING;
2023-05-24 17:34:20 -07:00
for (int c = 0; c < COLS; c++) {
2023-05-25 19:44:14 -07:00
// Get the maximum width among items of this row
2023-05-24 17:34:20 -07:00
int maxWidth = Integer.MIN_VALUE;
for (int r = 0; r < items.length; r++)
maxWidth = Math.max(maxWidth, (int) items[r][c].getPreferredSize().getWidth());
2023-05-25 19:44:14 -07:00
maxWidths[c] = maxWidth;
// 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;
2023-05-24 17:34:20 -07:00
}
2023-05-25 19:44:14 -07:00
// Set y and maxHeights the same way
y[0] = PADDING;
2023-05-24 17:34:20 -07:00
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());
2023-05-25 19:44:14 -07:00
maxHeights[r] = maxHeight;
y[r + 1] = y[r] + maxHeight + PADDING;
2023-05-24 17:34:20 -07:00
}
2023-05-25 19:44:14 -07:00
// Actually put into use the values we just got
2023-05-24 17:34:20 -07:00
for (int r = 0; r < items.length; r++)
for (int c = 0; c < COLS; c++)
2023-05-25 19:44:14 -07:00
items[r][c].setBounds(x[c], y[r], maxWidths[c], maxHeights[r]);
2023-05-24 17:34:20 -07:00
2023-05-25 19:44:14 -07:00
// Use the last values of x and y to set the size of the panel
2023-05-24 17:34:20 -07:00
messagePanel.setPreferredSize(new Dimension(x[COLS], y[items.length]));
return messagePanel;
}
}