Compare commits

...

No commits in common. "v1.0" and "develop" have entirely different histories.

13 changed files with 258 additions and 197 deletions

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<classpath> <classpath>
<classpathentry kind="src" path="src"/> <classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="res"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
<attributes> <attributes>
<attribute name="module" value="true"/> <attribute name="module" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="lib" path="res"/>
<classpathentry kind="output" path="bin"/> <classpathentry kind="output" path="bin"/>
</classpath> </classpath>

View File

@ -1,22 +1,12 @@
package dev.kske.minesweeper; package dev.kske.minesweeper;
import java.awt.BasicStroke; import java.awt.*;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.swing.JPanel; import javax.swing.JPanel;
@ -28,16 +18,17 @@ import javax.swing.JPanel;
*/ */
public class Board extends JPanel { public class Board extends JPanel {
private static final long serialVersionUID = -279269871397851420L; private static final long serialVersionUID = -279269871397851420L;
private static final int tileSize = 32; private static final int tileSize = 32;
private static Map<String, Image> icons; private static Map<String, Image> icons;
private int boardWidth, boardHeight; private int boardWidth, boardHeight;
private GameState gameState; private GameState gameState;
private int mines, activeTiles, flaggedTiles; private int mines, activeTiles, flaggedTiles;
private Tile[][] board; private Tile[][] board;
private BoardConfig boardConfig; private BoardConfig boardConfig;
private boolean minesPlaced;
private Instant start, finish; private Instant start, finish;
@ -45,12 +36,16 @@ public class Board extends JPanel {
static { static {
icons = new HashMap<>(); icons = new HashMap<>();
final String[] names = { "mine2", "mine4", "tile", "tile3" }; for (String name : new String[] {
for (String name : names) { "mine2", "mine4", "tile", "tile3"
}) {
icons.put(name, TextureLoader.loadScaledImage(name, tileSize)); icons.put(name, TextureLoader.loadScaledImage(name, tileSize));
} }
} }
/**
* Creates an instance of {@link Board}.
*/
public Board() { public Board() {
// Not using a layout manager // Not using a layout manager
super(null); super(null);
@ -59,10 +54,11 @@ public class Board extends JPanel {
@Override @Override
public void mousePressed(MouseEvent evt) { public void mousePressed(MouseEvent evt) {
int n = evt.getX() / tileSize, m = evt.getY() / tileSize; int n = evt.getX() / tileSize, m = evt.getY() / tileSize;
Tile tile = board[n][m]; Tile tile = board[n][m];
if (tile.isTouched() || gameState != GameState.ACTIVE) return; if (tile.isTouched() || gameState != GameState.ACTIVE)
return;
switch (evt.getButton()) { switch (evt.getButton()) {
case MouseEvent.BUTTON1: case MouseEvent.BUTTON1:
touchTile(n, m); touchTile(n, m);
@ -74,18 +70,24 @@ public class Board extends JPanel {
}); });
} }
/**
* Initializes the board with a given configuration. This does not include mine placement.
*
* @param config the configuration used
*/
public void init(BoardConfig config) { public void init(BoardConfig config) {
boardConfig = config; boardConfig = config;
boardWidth = config.width; boardWidth = config.width;
boardHeight = config.height; boardHeight = config.height;
setPreferredSize(new Dimension(config.width * tileSize, config.height * tileSize)); setPreferredSize(new Dimension(config.width * tileSize, config.height * tileSize));
gameState = GameState.ACTIVE; gameState = GameState.ACTIVE;
mines = config.mines; mines = config.mines;
activeTiles = boardWidth * boardHeight; activeTiles = boardWidth * boardHeight;
flaggedTiles = 0; flaggedTiles = 0;
minesPlaced = false;
notifyFlaggedTilesEvent(new FlaggedTilesEvent(this, flaggedTiles)); notifyFlaggedTilesEvent(new FlaggedTilesEvent(this, flaggedTiles));
@ -94,13 +96,15 @@ public class Board extends JPanel {
for (int i = 0; i < boardWidth; i++) for (int i = 0; i < boardWidth; i++)
for (int j = 0; j < boardHeight; j++) for (int j = 0; j < boardHeight; j++)
board[i][j] = new Tile(); board[i][j] = new Tile();
initMines();
repaint(); repaint();
revalidate(); revalidate();
start = Instant.now(); start = Instant.now();
} }
/**
* Re-initializes the board with the cached configuration, thereby resetting it.
*/
public void reset() { public void reset() {
init(boardConfig); init(boardConfig);
} }
@ -110,39 +114,54 @@ public class Board extends JPanel {
super.paintComponent(g); super.paintComponent(g);
for (int i = 0; i < boardWidth; i++) for (int i = 0; i < boardWidth; i++)
for (int j = 0; j < boardHeight; j++) { for (int j = 0; j < boardHeight; j++) {
Tile tile = board[i][j]; Tile tile = board[i][j];
int x = i * tileSize, y = j * tileSize; int x = i * tileSize, y = j * tileSize;
// Draw background // Draw background
g.setColor(Color.gray); g.setColor(Color.gray);
g.fillRect(x, y, x + tileSize, y + tileSize); g.fillRect(x, y, x + tileSize, y + tileSize);
// Draw tile with normal mine // Draw tile with normal mine
if (gameState == GameState.LOST && tile.isMine()) g.drawImage(icons.get("mine2"), x, y, this); if (gameState == GameState.LOST && tile.isMine())
g.drawImage(icons.get("mine2"), x, y, this);
// Draw tile with diffused mine // Draw tile with diffused mine
else if (gameState == GameState.WON && tile.isMine()) g.drawImage(icons.get("mine4"), x, y, this); else
else if (tile.isTouched()) { if (gameState == GameState.WON && tile.isMine())
g.drawImage(icons.get("mine4"), x, y, this);
else
if (tile.isTouched()) {
// Draw tile with mine // Draw tile with mine
if (tile.isMine()) g.drawImage(icons.get("mine2"), x, y, this); if (tile.isMine())
g.drawImage(icons.get("mine2"), x, y, this);
// Draw flagged tile // Draw flagged tile
else if (tile.isDrawSurroundingMines() && tile.getSurroundingMines() > 0) { else
// Draw number of surrounding mines if (
String numStr = String.valueOf(tile.getSurroundingMines()); tile.isDrawSurroundingMines() && tile.getSurroundingMines() > 0
g.setFont(new Font("Arial", Font.BOLD, 18)); ) {
g.setColor(Color.red); // Draw number of surrounding mines
FontMetrics fm = g.getFontMetrics(); String numStr = String.valueOf(tile.getSurroundingMines());
int w = fm.stringWidth(numStr), h = fm.getHeight(); g.setFont(new Font("Arial", Font.BOLD, 18));
g.drawString(numStr, x + (tileSize - w) / 2, y + (tileSize - h) / 2 + fm.getAscent()); g.setColor(Color.red);
} FontMetrics fm = g.getFontMetrics();
} int w = fm.stringWidth(numStr), h = fm.getHeight();
g.drawString(
numStr,
x + (tileSize - w) / 2,
y + (tileSize - h) / 2 + fm.getAscent()
);
}
}
// Draw flagged tile // Draw flagged tile
else if (tile.isFlagged()) g.drawImage(icons.get("tile3"), x, y, this); else
if (tile.isFlagged())
g.drawImage(icons.get("tile3"), x, y, this);
// Draw normal tile // Draw normal tile
else g.drawImage(icons.get("tile"), x, y, this); else
g.drawImage(icons.get("tile"), x, y, this);
// Draw grid // Draw grid
((Graphics2D) g).setStroke(new BasicStroke(2.0f)); ((Graphics2D) g).setStroke(new BasicStroke(2.0f));
@ -151,6 +170,11 @@ public class Board extends JPanel {
} }
} }
/**
* Registers a game listener that is notified when game events occur.
*
* @param listener the game listener to register
*/
public void registerGameListener(GameListener listener) { public void registerGameListener(GameListener listener) {
listeners.add(listener); listeners.add(listener);
} }
@ -168,15 +192,15 @@ public class Board extends JPanel {
} }
private void initMines() { private void initMines() {
int remaining = mines; int remaining = mines;
Random random = new Random(); Random random = new Random();
while (remaining > 0) { while (remaining > 0) {
// Randomly select a tile // Randomly select a tile
int n = random.nextInt(boardWidth); int n = random.nextInt(boardWidth);
int m = random.nextInt(boardHeight); int m = random.nextInt(boardHeight);
// Check if the selected tile already is a mine and is not touched // Check if the selected tile already is a mine and is not touched
if (!board[n][m].isMine()) { if (!board[n][m].isTouched() && !board[n][m].isMine()) {
// Decrement the counter // Decrement the counter
remaining--; remaining--;
@ -189,6 +213,7 @@ public class Board extends JPanel {
board[i][j].setSurroundingMines(board[i][j].getSurroundingMines() + 1); board[i][j].setSurroundingMines(board[i][j].getSurroundingMines() + 1);
} }
} }
minesPlaced = true;
} }
private void touchTile(int n, int m) { private void touchTile(int n, int m) {
@ -204,21 +229,25 @@ public class Board extends JPanel {
flaggedTiles--; flaggedTiles--;
notifyFlaggedTilesEvent(new FlaggedTilesEvent(this, flaggedTiles)); notifyFlaggedTilesEvent(new FlaggedTilesEvent(this, flaggedTiles));
} }
// Test if the game is won or lost // Test if the game is won or lost
if (tile.isMine()) { if (tile.isMine()) {
gameState = GameState.LOST; gameState = GameState.LOST;
onGameOver(); onGameOver();
} else if (mines == activeTiles) { } else
gameState = GameState.WON; if (mines == activeTiles) {
onGameOver(); gameState = GameState.WON;
} onGameOver();
}
// Place the mines if this was the first touch
if (!minesPlaced)
initMines();
// Touch surrounding tiles when there are zero surrounding mines // Touch surrounding tiles when there are zero surrounding mines
else if (tile.getSurroundingMines() == 0) if (tile.getSurroundingMines() == 0)
for (int i = Math.max(0, n - 1); i < Math.min(n + 2, board.length); i++) for (int i = Math.max(0, n - 1); i < Math.min(n + 2, board.length); i++)
for (int j = Math.max(0, m - 1); j < Math.min(m + 2, board[i].length); j++) for (int j = Math.max(0, m - 1); j < Math.min(m + 2, board[i].length); j++)
if (i != n || j != m) touchTile(i, j); if (i != n || j != m)
touchTile(i, j);
repaintTile(n, m); repaintTile(n, m);
} }
@ -248,11 +277,23 @@ public class Board extends JPanel {
notifyGameStateEvent(evt); notifyGameStateEvent(evt);
} }
/**
* @return the total number of mines
*/
public int getMines() { return mines; } public int getMines() { return mines; }
/**
* @return the number of active tiles
*/
public int getActiveTiles() { return activeTiles; } public int getActiveTiles() { return activeTiles; }
/**
* @return the number of flagged files
*/
public int getFlaggedTiles() { return flaggedTiles; } public int getFlaggedTiles() { return flaggedTiles; }
/**
* @return the current configuration
*/
public BoardConfig getBoardConfig() { return boardConfig; } public BoardConfig getBoardConfig() { return boardConfig; }
} }

View File

@ -3,6 +3,8 @@ package dev.kske.minesweeper;
import java.io.Serializable; import java.io.Serializable;
/** /**
* Defines board configuration consisting of board with and height as well as mine count.
* <p>
* Project: <strong>Minesweeper</strong><br> * Project: <strong>Minesweeper</strong><br>
* File: <strong>BoardConfig.java</strong><br> * File: <strong>BoardConfig.java</strong><br>
* Created: <strong>01.04.2019</strong><br> * Created: <strong>01.04.2019</strong><br>
@ -12,14 +14,15 @@ public class BoardConfig implements Serializable {
private static final long serialVersionUID = -6083006887427383946L; private static final long serialVersionUID = -6083006887427383946L;
public static final BoardConfig EASY = new BoardConfig(8, 8, 10), MEDIUM = new BoardConfig(16, 16, 40), public static final BoardConfig EASY = new BoardConfig(8, 8, 10), MEDIUM
HARD = new BoardConfig(30, 16, 99); = new BoardConfig(16, 16, 40),
HARD = new BoardConfig(30, 16, 99);
public final int width, height, mines; public final int width, height, mines;
public BoardConfig(int width, int height, int mines) { public BoardConfig(int width, int height, int mines) {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.mines = mines; this.mines = mines;
} }
} }

View File

@ -1,16 +1,8 @@
package dev.kske.minesweeper; package dev.kske.minesweeper;
import java.awt.BorderLayout; import java.awt.*;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridLayout;
import javax.swing.JButton; import javax.swing.*;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.border.EmptyBorder; import javax.swing.border.EmptyBorder;
/** /**
@ -21,13 +13,15 @@ import javax.swing.border.EmptyBorder;
*/ */
public class CustomDialog extends JDialog { public class CustomDialog extends JDialog {
private static final long serialVersionUID = -4019516811065781434L; private static final long serialVersionUID = -4019516811065781434L;
private final JPanel mcontentPanel = new JPanel(); private final JPanel mcontentPanel = new JPanel();
private BoardConfig result; private BoardConfig result;
/** /**
* Create the dialog. * Create the dialog.
*
* @param owner the frame on top of which the dialog will be displayed
*/ */
public CustomDialog(Frame owner) { public CustomDialog(Frame owner) {
super(owner, ModalityType.APPLICATION_MODAL); super(owner, ModalityType.APPLICATION_MODAL);
@ -47,7 +41,9 @@ public class CustomDialog extends JDialog {
lblBoardWidth.setFont(new Font("Tahoma", Font.PLAIN, 14)); lblBoardWidth.setFont(new Font("Tahoma", Font.PLAIN, 14));
mcontentPanel.add(lblBoardWidth); mcontentPanel.add(lblBoardWidth);
JSlider sliderBoardWidth = new JSlider(); JSlider sliderBoardWidth = new JSlider();
sliderBoardWidth.addChangeListener((evt) -> lblBoardWidth.setText(String.valueOf(sliderBoardWidth.getValue()))); sliderBoardWidth.addChangeListener(
(evt) -> lblBoardWidth.setText(String.valueOf(sliderBoardWidth.getValue()))
);
sliderBoardWidth.setValue(16); sliderBoardWidth.setValue(16);
sliderBoardWidth.setMinimum(2); sliderBoardWidth.setMinimum(2);
sliderBoardWidth.setMaximum(30); sliderBoardWidth.setMaximum(30);
@ -76,7 +72,9 @@ public class CustomDialog extends JDialog {
lblNumMines.setFont(new Font("Tahoma", Font.PLAIN, 14)); lblNumMines.setFont(new Font("Tahoma", Font.PLAIN, 14));
mcontentPanel.add(lblNumMines); mcontentPanel.add(lblNumMines);
JSlider sliderNumMines = new JSlider(); JSlider sliderNumMines = new JSlider();
sliderNumMines.addChangeListener((evt) -> lblNumMines.setText(String.valueOf(sliderNumMines.getValue()))); sliderNumMines.addChangeListener(
(evt) -> lblNumMines.setText(String.valueOf(sliderNumMines.getValue()))
);
sliderNumMines.setValue(16); sliderNumMines.setValue(16);
sliderNumMines.setMinimum(2); sliderNumMines.setMinimum(2);
sliderNumMines.setMaximum(200); sliderNumMines.setMaximum(200);
@ -89,8 +87,11 @@ public class CustomDialog extends JDialog {
JButton okButton = new JButton("Start Game"); JButton okButton = new JButton("Start Game");
okButton.setActionCommand("OK"); okButton.setActionCommand("OK");
okButton.addActionListener((evt) -> { okButton.addActionListener((evt) -> {
result = new BoardConfig(sliderBoardWidth.getValue(), sliderBoardHeight.getValue(), result = new BoardConfig(
sliderNumMines.getValue()); sliderBoardWidth.getValue(),
sliderBoardHeight.getValue(),
sliderNumMines.getValue()
);
dispose(); dispose();
}); });
buttonPane.add(okButton); buttonPane.add(okButton);
@ -105,6 +106,11 @@ public class CustomDialog extends JDialog {
} }
} }
/**
* Displays the dialog.
*
* @return the board configuration defined by the user
*/
public BoardConfig showDialog() { public BoardConfig showDialog() {
setVisible(true); setVisible(true);
return result; return result;

View File

@ -12,13 +12,13 @@ public class FlaggedTilesEvent extends EventObject {
private static final long serialVersionUID = -5809857531886339312L; private static final long serialVersionUID = -5809857531886339312L;
private final Board board; private final Board board;
private final int flagged; private final int flagged;
public FlaggedTilesEvent(Object source, int flagged) { public FlaggedTilesEvent(Object source, int flagged) {
super(source); super(source);
board = (Board) source; board = (Board) source;
this.flagged = flagged; this.flagged = flagged;
} }
public Board getBoard() { return board; } public Board getBoard() { return board; }

View File

@ -12,17 +12,19 @@ public class GameOverEvent extends EventObject {
private static final long serialVersionUID = -966111253980213845L; private static final long serialVersionUID = -966111253980213845L;
private final Board board; private final Board board;
private final GameState gameState; private final GameState gameState;
private final BoardConfig boardConfig; private final BoardConfig boardConfig;
private final int duration; private final int duration;
public GameOverEvent(Object source, GameState gameState, BoardConfig boardConfig, int duration) { public GameOverEvent(
Object source, GameState gameState, BoardConfig boardConfig, int duration
) {
super(source); super(source);
board = (Board) source; board = (Board) source;
this.gameState = gameState; this.gameState = gameState;
this.boardConfig = boardConfig; this.boardConfig = boardConfig;
this.duration = duration; this.duration = duration;
} }
public Board getBoard() { return board; } public Board getBoard() { return board; }

View File

@ -1,8 +1,6 @@
package dev.kske.minesweeper; package dev.kske.minesweeper;
import static dev.kske.minesweeper.BoardConfig.EASY; import static dev.kske.minesweeper.BoardConfig.*;
import static dev.kske.minesweeper.BoardConfig.HARD;
import static dev.kske.minesweeper.BoardConfig.MEDIUM;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.EventQueue; import java.awt.EventQueue;
@ -10,17 +8,7 @@ import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter; import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent; import java.awt.event.WindowEvent;
import javax.swing.JButton; import javax.swing.*;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.Timer;
import javax.swing.UIManager;
/** /**
* Project: <strong>Minesweeper</strong><br> * Project: <strong>Minesweeper</strong><br>
@ -30,17 +18,19 @@ import javax.swing.UIManager;
*/ */
public class Minesweeper { public class Minesweeper {
private static final String VERSION = "1.0"; private static final String VERSION = "1.1";
private JFrame mframe; private JFrame mframe;
private Board board; private Board board;
private Timer timer; private Timer timer;
private int gameTime; private int gameTime;
private ScoreManager scoreManager; private ScoreManager scoreManager;
/** /**
* Launch the application. * Launch the application.
*
* @param args command line arguments are ignored
*/ */
public static void main(String[] args) { public static void main(String[] args) {
EventQueue.invokeLater(() -> { EventQueue.invokeLater(() -> {
@ -71,7 +61,6 @@ public class Minesweeper {
} catch (Exception ex) { } catch (Exception ex) {
ex.printStackTrace(); ex.printStackTrace();
} }
mframe = new JFrame(); mframe = new JFrame();
mframe.addWindowListener(new WindowAdapter() { mframe.addWindowListener(new WindowAdapter() {
@ -115,11 +104,16 @@ public class Minesweeper {
JLabel lblRemainingMines = new JLabel("Remaining Mines: " + EASY.mines); JLabel lblRemainingMines = new JLabel("Remaining Mines: " + EASY.mines);
panel.add(lblRemainingMines, BorderLayout.SOUTH); panel.add(lblRemainingMines, BorderLayout.SOUTH);
lblRemainingMines.setHorizontalAlignment(SwingConstants.LEFT); lblRemainingMines.setHorizontalAlignment(SwingConstants.LEFT);
btnRestart.addActionListener((evt) -> { board.reset(); gameTime = 0; timer.restart(); }); btnRestart.addActionListener((evt) -> {
board.reset();
gameTime = 0;
timer.restart();
});
mframe.pack(); mframe.pack();
board.registerGameListener(new GameListener() { board.registerGameListener(new GameListener() {
@SuppressWarnings("incomplete-switch")
@Override @Override
public void onGameOverEvent(GameOverEvent evt) { public void onGameOverEvent(GameOverEvent evt) {
timer.stop(); timer.stop();
@ -135,7 +129,8 @@ public class Minesweeper {
@Override @Override
public void onFlaggedTilesEvent(FlaggedTilesEvent evt) { public void onFlaggedTilesEvent(FlaggedTilesEvent evt) {
lblRemainingMines.setText("Remaining Mines: " + (evt.getBoard().getMines() - evt.getFlagged())); lblRemainingMines
.setText("Remaining Mines: " + (evt.getBoard().getMines() - evt.getFlagged()));
mframe.pack(); mframe.pack();
} }
}); });
@ -151,10 +146,10 @@ public class Minesweeper {
{ {
var gameMenu = new JMenu("Game"); var gameMenu = new JMenu("Game");
var easyMenuItem = new JMenuItem("Easy"); var easyMenuItem = new JMenuItem("Easy");
var mediumMenuItem = new JMenuItem("Medium"); var mediumMenuItem = new JMenuItem("Medium");
var hardMenuItem = new JMenuItem("Hard"); var hardMenuItem = new JMenuItem("Hard");
var customMenuItem = new JMenuItem("Custom"); var customMenuItem = new JMenuItem("Custom");
gameMenu.setMnemonic(KeyEvent.VK_G); gameMenu.setMnemonic(KeyEvent.VK_G);
easyMenuItem.setMnemonic(KeyEvent.VK_E); easyMenuItem.setMnemonic(KeyEvent.VK_E);
@ -167,7 +162,8 @@ public class Minesweeper {
hardMenuItem.addActionListener((evt) -> initGame(HARD)); hardMenuItem.addActionListener((evt) -> initGame(HARD));
customMenuItem.addActionListener((evt) -> { customMenuItem.addActionListener((evt) -> {
BoardConfig cfg = new CustomDialog(mframe).showDialog(); BoardConfig cfg = new CustomDialog(mframe).showDialog();
if (cfg != null) initGame(cfg); if (cfg != null)
initGame(cfg);
}); });
gameMenu.add(easyMenuItem); gameMenu.add(easyMenuItem);
@ -177,13 +173,12 @@ public class Minesweeper {
gameMenu.add(customMenuItem); gameMenu.add(customMenuItem);
menubar.add(gameMenu); menubar.add(gameMenu);
} }
{ {
var highscoreMenu = new JMenu("Highscores"); var highscoreMenu = new JMenu("Highscores");
var easyMenuItem = new JMenuItem("Easy"); var easyMenuItem = new JMenuItem("Easy");
var mediumMenuItem = new JMenuItem("Medium"); var mediumMenuItem = new JMenuItem("Medium");
var hardMenuItem = new JMenuItem("Hard"); var hardMenuItem = new JMenuItem("Hard");
highscoreMenu.setMnemonic(KeyEvent.VK_H); highscoreMenu.setMnemonic(KeyEvent.VK_H);
easyMenuItem.setMnemonic(KeyEvent.VK_E); easyMenuItem.setMnemonic(KeyEvent.VK_E);
@ -199,16 +194,17 @@ public class Minesweeper {
highscoreMenu.add(hardMenuItem); highscoreMenu.add(hardMenuItem);
menubar.add(highscoreMenu); menubar.add(highscoreMenu);
} }
{ {
var aboutMenuItem = new JMenuItem("About"); var aboutMenuItem = new JMenuItem("About");
aboutMenuItem.addActionListener((evt) -> JOptionPane.showMessageDialog(board, aboutMenuItem.addActionListener(
"Minesweeper version " + VERSION + "\nby Kai S. K. Engelbart")); (evt) -> JOptionPane.showMessageDialog(board,
"Minesweeper version " + VERSION + "\nby Kai S. K. Engelbart"
)
);
menubar.add(aboutMenuItem); menubar.add(aboutMenuItem);
} }
mframe.setJMenuBar(menubar); mframe.setJMenuBar(menubar);
} }

View File

@ -13,14 +13,14 @@ public class Score implements Comparable<Score>, Serializable {
private static final long serialVersionUID = 3384023296639779740L; private static final long serialVersionUID = 3384023296639779740L;
private final String name; private final String name;
private final int duration; private final int duration;
private final Date date; private final Date date;
public Score(String name, int duration, Date date) { public Score(String name, int duration, Date date) {
this.name = name; this.name = name;
this.duration = duration; this.duration = duration;
this.date = date; this.date = date;
} }
public String getName() { return name; } public String getName() { return name; }

View File

@ -6,11 +6,7 @@ import java.text.SimpleDateFormat;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import javax.swing.JDialog; import javax.swing.*;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.SwingConstants;
/** /**
* Project: <strong>Minesweeper</strong><br> * Project: <strong>Minesweeper</strong><br>
@ -25,6 +21,10 @@ public class ScoreDialog extends JDialog {
/** /**
* Create the dialog. * Create the dialog.
*
* @param scores the scores to display
* @param boardConfigName the name of the board configuration with which the scores are
* associated
*/ */
public ScoreDialog(List<Score> scores, String boardConfigName) { public ScoreDialog(List<Score> scores, String boardConfigName) {
setModal(true); setModal(true);
@ -32,17 +32,18 @@ public class ScoreDialog extends JDialog {
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
getContentPane().setLayout(new BorderLayout(0, 0)); getContentPane().setLayout(new BorderLayout(0, 0));
String[] columnNames = { "Place", "Name", "Duration", "Date" }; String[] columnNames = {
String[][] data = new String[scores.size()][4]; "Place", "Name", "Duration", "Date"
};
String[][] data = new String[scores.size()][4];
Iterator<Score> iter = scores.iterator(); Iterator<Score> iter = scores.iterator();
for(int i = 0; i < data.length; i++) { for (int i = 0; i < data.length; i++) {
Score s = iter.next(); Score s = iter.next();
data[i][0] = String.valueOf(i + 1); data[i][0] = String.valueOf(i + 1);
data[i][1] = s.getName(); data[i][1] = s.getName();
data[i][2] = String.valueOf(s.getDuration()); data[i][2] = String.valueOf(s.getDuration());
data[i][3] = new SimpleDateFormat().format(s.getDate()); data[i][3] = new SimpleDateFormat().format(s.getDate());
} }
mtable = new JTable(data, columnNames); mtable = new JTable(data, columnNames);
getContentPane().add(mtable); getContentPane().add(mtable);

View File

@ -1,15 +1,8 @@
package dev.kske.minesweeper; package dev.kske.minesweeper;
import static dev.kske.minesweeper.BoardConfig.EASY; import static dev.kske.minesweeper.BoardConfig.*;
import static dev.kske.minesweeper.BoardConfig.HARD;
import static dev.kske.minesweeper.BoardConfig.MEDIUM;
import java.io.FileInputStream; import java.io.*;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -24,31 +17,39 @@ import javax.swing.JOptionPane;
*/ */
public class ScoreManager { public class ScoreManager {
private List<Score> easy, medium, hard; private List<Score> easy, medium, hard;
private final String scoresFile = "scores.ser"; private final String scoresFile = "scores.ser";
public ScoreManager() { public ScoreManager() {
easy = new ArrayList<>(); easy = new ArrayList<>();
medium = new ArrayList<>(); medium = new ArrayList<>();
hard = new ArrayList<>(); hard = new ArrayList<>();
} }
public void addScore(GameOverEvent evt) { public void addScore(GameOverEvent evt) {
// Determine board config // Determine board config
BoardConfig config = evt.getBoardConfig(); BoardConfig config = evt.getBoardConfig();
if (config == EASY && (easy.size() < 10 || easy.get(9).getDuration() > evt.getDuration())) { if (config == EASY && (easy.size() < 10 || easy.get(9).getDuration() > evt.getDuration())) {
String name = JOptionPane.showInputDialog("Please enter your name"); String name = JOptionPane.showInputDialog("Please enter your name");
Score score = new Score(name, evt.getDuration(), new Date()); Score score = new Score(name, evt.getDuration(), new Date());
sortInsert(score, easy); sortInsert(score, easy);
} else if (config == MEDIUM && (medium.size() < 10 || medium.get(9).getDuration() > evt.getDuration())) { } else
String name = JOptionPane.showInputDialog("Please enter your name"); if (
Score score = new Score(name, evt.getDuration(), new Date()); config == MEDIUM
sortInsert(score, medium); && (medium.size() < 10 || medium.get(9).getDuration() > evt.getDuration())
} else if (config == HARD && (hard.size() < 10 || hard.get(9).getDuration() > evt.getDuration())) { ) {
String name = JOptionPane.showInputDialog("Please enter your name"); String name = JOptionPane.showInputDialog("Please enter your name");
Score score = new Score(name, evt.getDuration(), new Date()); Score score = new Score(name, evt.getDuration(), new Date());
sortInsert(score, hard); sortInsert(score, medium);
} } else
if (
config == HARD
&& (hard.size() < 10 || hard.get(9).getDuration() > evt.getDuration())
) {
String name = JOptionPane.showInputDialog("Please enter your name");
Score score = new Score(name, evt.getDuration(), new Date());
sortInsert(score, hard);
}
} }
private void sortInsert(Score score, List<Score> list) { private void sortInsert(Score score, List<Score> list) {
@ -76,16 +77,23 @@ public class ScoreManager {
public void loadScores() { public void loadScores() {
try (var in = new ObjectInputStream(new FileInputStream(scoresFile))) { try (var in = new ObjectInputStream(new FileInputStream(scoresFile))) {
Object obj = in.readObject(); Object obj = in.readObject();
if (obj instanceof ArrayList<?>) easy = (ArrayList<Score>) obj; if (obj instanceof ArrayList<?>)
easy = (ArrayList<Score>) obj;
obj = in.readObject(); obj = in.readObject();
if (obj instanceof ArrayList<?>) medium = (ArrayList<Score>) obj; if (obj instanceof ArrayList<?>)
medium = (ArrayList<Score>) obj;
obj = in.readObject(); obj = in.readObject();
if (obj instanceof ArrayList<?>) hard = (ArrayList<Score>) obj; if (obj instanceof ArrayList<?>)
else throw new IOException("Serialized object has the wrong class."); hard = (ArrayList<Score>) obj;
else
throw new IOException("Serialized object has the wrong class.");
} catch (FileNotFoundException ex) {} catch (IOException | ClassNotFoundException ex) { } catch (FileNotFoundException ex) {} catch (IOException | ClassNotFoundException ex) {
JOptionPane.showMessageDialog(null, JOptionPane.showMessageDialog(
"The score file seems to be corrupted. It will be replaced when closing the game.", "File error", null,
JOptionPane.ERROR_MESSAGE); "The score file seems to be corrupted. It will be replaced when closing the game.",
"File error",
JOptionPane.ERROR_MESSAGE
);
} }
} }

View File

@ -2,7 +2,6 @@ package dev.kske.minesweeper;
import java.awt.Image; import java.awt.Image;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@ -28,7 +27,7 @@ public class TextureLoader {
public static Image loadScaledImage(String name, int scale) { public static Image loadScaledImage(String name, int scale) {
BufferedImage in = null; BufferedImage in = null;
try { try {
in = ImageIO.read(new File("res" + File.separator + name + ".png")); in = ImageIO.read(TextureLoader.class.getResourceAsStream("/" + name + ".png"));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }

View File

@ -10,8 +10,8 @@ public class Tile {
private boolean mine, flagged, touched; private boolean mine, flagged, touched;
private boolean drawSurroundingMines; private boolean drawSurroundingMines;
private int surroundingMines; private int surroundingMines;
public Tile() { public Tile() {
mine = flagged = touched = drawSurroundingMines = false; mine = flagged = touched = drawSurroundingMines = false;
@ -39,5 +39,7 @@ public class Tile {
public int getSurroundingMines() { return surroundingMines; } public int getSurroundingMines() { return surroundingMines; }
public void setSurroundingMines(int surroundingMines) { this.surroundingMines = surroundingMines; } public void setSurroundingMines(int surroundingMines) {
this.surroundingMines = surroundingMines;
}
} }

View File

@ -1,3 +1,6 @@
/**
* Contains all classes related to the game.
*/
module Minesweeper { module Minesweeper {
requires java.desktop; requires java.desktop;
} }