180 lines
5.2 KiB
Java
180 lines
5.2 KiB
Java
|
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;
|
||
|
}
|
||
|
}
|