From 1d5ade9136da417bc9594ef24e4b2593d6ce2a25 Mon Sep 17 00:00:00 2001 From: eriedaberrie Date: Wed, 24 May 2023 17:34:20 -0700 Subject: [PATCH] Finished everything minus comments --- Canvas.java | 23 +++--- CustomTextField.java | 70 +++++++++++++++++ Difficulty.java | 179 +++++++++++++++++++++++++++++++++++++++++++ Face.java | 11 ++- Main.java | 146 ++++++++++++++++------------------- NumberDisplay.java | 5 +- Options.java | 37 +++++++++ Sound.java | 23 +++--- Tile.java | 13 ++-- 9 files changed, 388 insertions(+), 119 deletions(-) create mode 100644 CustomTextField.java create mode 100644 Difficulty.java create mode 100644 Options.java diff --git a/Canvas.java b/Canvas.java index 81f8bc8..9b8fc9f 100644 --- a/Canvas.java +++ b/Canvas.java @@ -67,10 +67,11 @@ public class Canvas extends JPanel { TIMER_DISPLAY.setNum(time); } - public Canvas(int rows, int cols, int mines, File skin) { - ROWS = rows; - COLS = cols; - MINES = mines; + public Canvas(File skin) { + 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; @@ -127,7 +128,7 @@ public class Canvas extends JPanel { TIMER_DISPLAY = new NumberDisplay(numberDisplayBackdrop, digits); 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 // key is being held. @Override @@ -146,10 +147,6 @@ public class Canvas extends JPanel { } } - @Override - public void keyTyped(KeyEvent e) { - } - private void updateCurrentTile() { if (currentTile != null) currentTile.updatePressedState(); @@ -196,6 +193,7 @@ public class Canvas extends JPanel { stop(); init(); + for (Tile[] row : BOARD) for (Tile tile : row) tile.restart(); @@ -323,7 +321,7 @@ public class Canvas extends JPanel { Image bottomRightCorner = tileset.getSubimage(BORDER_WIDTH + 3, tsY, BORDER_WIDTH, BOTTOM_HEIGHT); 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 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 private void placeComponent(JComponent c, int x, int y, Insets insets) { add(c); - Dimension size = c.getPreferredSize(); - c.setBounds(x + insets.left, y + insets.top, size.width, size.height); + c.setLocation(x + insets.left, y + insets.top); + c.setSize(c.getPreferredSize()); } private void stopGame() { @@ -380,6 +378,7 @@ public class Canvas extends JPanel { // Override the paintComponent method in order to add the background image @Override public void paintComponent(Graphics g) { + super.paintComponent(g); g.drawImage(BACKGROUND_IMAGE, 0, 0, this); } } diff --git a/CustomTextField.java b/CustomTextField.java new file mode 100644 index 0000000..8d54959 --- /dev/null +++ b/CustomTextField.java @@ -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)); + } +} diff --git a/Difficulty.java b/Difficulty.java new file mode 100644 index 0000000..5ddf4c4 --- /dev/null +++ b/Difficulty.java @@ -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; + } +} diff --git a/Face.java b/Face.java index 3913cf0..6be0d31 100644 --- a/Face.java +++ b/Face.java @@ -24,7 +24,7 @@ public class Face extends JComponent { pressed = false; mouseOver = false; setPreferredSize(new Dimension(SIZE, SIZE)); - addMouseListener(new MouseListener() { + addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { pressed = true; @@ -54,10 +54,6 @@ public class Face extends JComponent { if (pressed) setFace(unpressedFace); } - - @Override - public void mouseClicked(MouseEvent e) { - } }); } @@ -67,7 +63,10 @@ public class Face extends JComponent { } @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); } } diff --git a/Main.java b/Main.java index ba87757..4dec5a6 100644 --- a/Main.java +++ b/Main.java @@ -7,22 +7,6 @@ import javax.swing.*; import javax.swing.event.*; 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 SKINS_DIR = "Skins/"; 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 Canvas canvas; - private static Difficulty difficulty; private static String skin; - private static boolean protectedStart; - private static boolean sound; public static void main(String[] args) { - difficulty = Difficulty.INTERMEDIATE; skin = DEFAULT_SKIN; - protectedStart = false; - sound = false; - setCanvas(); FRAME.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); FRAME.setResizable(false); FRAME.setJMenuBar(getMenuBar()); - FRAME.add(canvas); - FRAME.pack(); + setCanvas(); + FRAME.setVisible(true); } - public static boolean isProtectedStart() { - return protectedStart; - } - - public static boolean hasSound() { - return sound; - } - 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() { + // 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 public void actionPerformed(ActionEvent e) { switch (e.getActionCommand()) { @@ -71,21 +60,29 @@ public class Main { break; case "beginner": setDifficulty(Difficulty.BEGINNER); + lastDifficultyButton = beginnerItem.getModel(); break; case "intermediate": setDifficulty(Difficulty.INTERMEDIATE); + lastDifficultyButton = intermediateItem.getModel(); break; case "expert": setDifficulty(Difficulty.EXPERT); + lastDifficultyButton = expertItem.getModel(); break; case "custom": - setDifficulty(null); + if (Difficulty.setCustom(FRAME)) { + setDifficulty(Difficulty.CUSTOM); + lastDifficultyButton = customItem.getModel(); + } else { + lastDifficultyButton.setSelected(true); + } break; case "protectedstart": - protectedStart = !protectedStart; + Options.toggleProtectedStart(); break; case "sound": - sound = !sound; + Options.toggleSound(); Sound.initSounds(); break; case "exit": @@ -111,25 +108,17 @@ public class Main { newGameItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0)); addMenuItem(newGameItem, gameMenu, KeyEvent.VK_N, "new", listener); 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(intermediateItem, gameMenu, KeyEvent.VK_I, "intermediate", listener); addMenuItem(expertItem, gameMenu, KeyEvent.VK_E, "expert", listener); addMenuItem(customItem, gameMenu, KeyEvent.VK_C, "custom", listener); 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"); addMenuItem(protectedStartItem, gameMenu, KeyEvent.VK_P, "protectedstart", listener); - JCheckBoxMenuItem soundItem = new JCheckBoxMenuItem("Sound", sound); - addMenuItem(soundItem, gameMenu, KeyEvent.VK_O, "sound", listener); + JCheckBoxMenuItem soundItem = new JCheckBoxMenuItem("Sound", Options.hasSound()); + addMenuItem(soundItem, gameMenu, KeyEvent.VK_U, "sound", listener); gameMenu.addSeparator(); addMenuItem("Exit", gameMenu, KeyEvent.VK_X, "exit", listener); menuBar.add(gameMenu); @@ -166,26 +155,26 @@ public class Main { // Get a stream of all files in the skins directory try (Stream dirStream = Files.list(Paths.get(SKINS_DIR))) { dirStream - // Filter for only regular files - .filter(file -> Files.isRegularFile(file)) - // Cut off the directory name from the resulting string - .map(Path::getFileName) - .map(Path::toString) - // Only allow a filename if it ends with one of the - // valid extensions - .filter(name -> Arrays.stream(VALID_EXTENSIONS) - // Use regionMatches to effectively - // case-insensitively endsWith() - .anyMatch(ext -> name.regionMatches(true, - name.length() - ext.length(), ext, 0, ext.length()))) - // Because Files.list does not guarantee an order, and - // alphabetical files look objectively nicer in lists - .sorted() - // Create a radio button menu item for each one, - // remembering to have it pre-selected if it's already - // the current skin - .forEach(name -> addMenuItem(new JRadioButtonMenuItem(name, name.equals(skin)), - skinsMenu, name, skinListener)); + // Filter for only regular files + .filter(Files::isRegularFile) + // Cut off the directory name from the resulting string + .map(Path::getFileName) + .map(Path::toString) + // Only allow a filename if it ends with one of the + // valid extensions + .filter(name -> Arrays.stream(VALID_EXTENSIONS) + // Use regionMatches to effectively + // case-insensitively endsWith() + .anyMatch(ext -> name.regionMatches(true, + name.length() - ext.length(), ext, 0, ext.length()))) + // Because Files.list does not guarantee an order, and + // alphabetical files look objectively nicer in lists + .sorted() + // Create a radio button menu item for each one, + // remembering to have it pre-selected if it's already + // the current skin + .forEach(name -> addMenuItem(new JRadioButtonMenuItem(name, name.equals(skin)), + skinsMenu, name, skinListener)); } catch (IOException ignore) { // It's fine to leave it blank if we can't get results } @@ -220,11 +209,11 @@ public class Main { } private static void setDifficulty(Difficulty newDifficulty) { - if (newDifficulty == null); - else if (difficulty == newDifficulty) + // Reject switching difficulty to the already-existing one unless Custom + if (Options.getDifficulty() == newDifficulty && newDifficulty != Difficulty.CUSTOM) return; - difficulty = newDifficulty; + Options.setDifficulty(newDifficulty); replaceCanvas(); } @@ -250,24 +239,17 @@ public class Main { canvas.stop(); canvas.removeAll(); FRAME.remove(canvas); + setCanvas(); - } + canvas.requestFocusInWindow(); +} private static void setCanvas() { - canvas = getCanvas(); + canvas = new Canvas(new File(SKINS_DIR, skin)); FRAME.add(canvas); 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 >:( private Main() { } diff --git a/NumberDisplay.java b/NumberDisplay.java index 36cfc82..cf2de09 100644 --- a/NumberDisplay.java +++ b/NumberDisplay.java @@ -40,7 +40,10 @@ public class NumberDisplay extends JComponent { } @Override - public void paintComponent(Graphics g) { + public void paintComponent(Graphics gr) { + super.paintComponent(gr); + Graphics2D g = (Graphics2D) gr; + g.drawImage(BACKDROP, 0, 0, this); // Preserve the original num for divison, in case we repaint twice // without updating the number diff --git a/Options.java b/Options.java new file mode 100644 index 0000000..38fec51 --- /dev/null +++ b/Options.java @@ -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() { + } +} diff --git a/Sound.java b/Sound.java index 30ac23a..5f53e39 100644 --- a/Sound.java +++ b/Sound.java @@ -14,7 +14,8 @@ public enum Sound { // Named pipe to send audio requests to on Replit 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(); // Whether the clips have been initialized yet @@ -29,15 +30,15 @@ public enum Sound { // Because Replit does audio totally differently from desktop Java (it // 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 - // the game directly on my laptop, we have a SoundBackend interface that is - // implemented by both a builtin javax.sound.sampled-powered backend and a - // Replit specific one - private interface SoundBackend { - // plays the specific sound - public void play(); + // the game directly on my laptop, we have an abstract SoundBackend class + // that is extended by both a builtin javax.sound.sampled-powered backend + // and a Replit specific one + private abstract class SoundBackend { + // Plays the specific sound + abstract public void play(); } - private class NativeBackend implements SoundBackend { + private class NativeBackend extends SoundBackend { // The audio clip associated with the sound 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 // because my ears got blown out the first time I exploded when it was // 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); } @@ -135,7 +136,7 @@ public enum Sound { // The actual method that users outside this file will call to play sound public void play() { // Here is the check for if the option for sound is actually enabled - if (Main.hasSound()) + if (Options.hasSound()) soundBackend.play(); } } diff --git a/Tile.java b/Tile.java index 21135d9..008bc5b 100644 --- a/Tile.java +++ b/Tile.java @@ -52,7 +52,7 @@ public class Tile extends JComponent { SPECIAL_TILES = specialTiles; ADJACENT_TILES = new HashSet(); - TILE_MOUSE_LISTENER = new MouseListener() { + TILE_MOUSE_LISTENER = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { boolean rightClicked = false; @@ -115,10 +115,6 @@ public class Tile extends JComponent { mouseOver = false; updatePressedState(); } - - @Override - public void mouseClicked(MouseEvent e) { - } }; 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 // whether or not a mine was actually placed. 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; mine = true; @@ -248,7 +244,10 @@ public class Tile extends JComponent { } @Override - public void paintComponent(Graphics g) { + public void paintComponent(Graphics gr) { + super.paintComponent(gr); + Graphics2D g = (Graphics2D) gr; + g.drawImage(getImage(), 0, 0, this); } }