Added score management
- Renamed GameStateEvent to GameOverEvent - Added a game time counter to Board - Added a game duration property to BoardConfig and GameOverEvent - Added Score and ScoreDialog classes - Added a Set for managing scores to Minesweeper
This commit is contained in:
parent
53e9c7ea26
commit
43d3d94756
1
.gitignore
vendored
1
.gitignore
vendored
@ -53,3 +53,4 @@ local.properties
|
|||||||
.cache-main
|
.cache-main
|
||||||
.scala_dependencies
|
.scala_dependencies
|
||||||
.worksheet
|
.worksheet
|
||||||
|
/scores.ser
|
||||||
|
@ -10,6 +10,8 @@ import java.awt.Graphics2D;
|
|||||||
import java.awt.Image;
|
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.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -35,7 +37,9 @@ public class Board extends JPanel {
|
|||||||
private GameState gameState;
|
private GameState gameState;
|
||||||
private int mines, activeTiles, flaggedTiles;
|
private int mines, activeTiles, flaggedTiles;
|
||||||
private Tile[][] board;
|
private Tile[][] board;
|
||||||
private BoardConfig initialConfig;
|
private BoardConfig boardConfig;
|
||||||
|
|
||||||
|
private Instant start, finish;
|
||||||
|
|
||||||
private List<GameListener> listeners;
|
private List<GameListener> listeners;
|
||||||
|
|
||||||
@ -71,7 +75,7 @@ public class Board extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void init(BoardConfig config) {
|
public void init(BoardConfig config) {
|
||||||
initialConfig = config;
|
boardConfig = config;
|
||||||
|
|
||||||
boardWidth = config.width;
|
boardWidth = config.width;
|
||||||
boardHeight = config.height;
|
boardHeight = config.height;
|
||||||
@ -91,10 +95,12 @@ public class Board extends JPanel {
|
|||||||
initMines();
|
initMines();
|
||||||
repaint();
|
repaint();
|
||||||
revalidate();
|
revalidate();
|
||||||
|
|
||||||
|
start = Instant.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
init(initialConfig);
|
init(boardConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -147,8 +153,8 @@ public class Board extends JPanel {
|
|||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyGameStateEvent(GameStateEvent evt) {
|
private void notifyGameStateEvent(GameOverEvent evt) {
|
||||||
listeners.forEach(listener -> listener.onGameStateEvent(evt));
|
listeners.forEach(listener -> listener.onGameOverEvent(evt));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyFlaggedTilesEvent(FlaggedTilesEvent evt) {
|
private void notifyFlaggedTilesEvent(FlaggedTilesEvent evt) {
|
||||||
@ -200,14 +206,10 @@ public class Board extends JPanel {
|
|||||||
// 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;
|
||||||
repaint();
|
onGameOver();
|
||||||
GameStateEvent evt = new GameStateEvent(this, gameState);
|
|
||||||
notifyGameStateEvent(evt);
|
|
||||||
} else if (mines == activeTiles) {
|
} else if (mines == activeTiles) {
|
||||||
gameState = GameState.WON;
|
gameState = GameState.WON;
|
||||||
repaint();
|
onGameOver();
|
||||||
GameStateEvent evt = new GameStateEvent(this, gameState);
|
|
||||||
notifyGameStateEvent(evt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Touch surrounding tiles when there are zero surrounding mines
|
// Touch surrounding tiles when there are zero surrounding mines
|
||||||
@ -235,6 +237,15 @@ public class Board extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onGameOver() {
|
||||||
|
finish = Instant.now();
|
||||||
|
int duration = (int) Duration.between(start, finish).toMillis();
|
||||||
|
|
||||||
|
repaint();
|
||||||
|
GameOverEvent evt = new GameOverEvent(this, gameState, boardConfig, duration);
|
||||||
|
notifyGameStateEvent(evt);
|
||||||
|
}
|
||||||
|
|
||||||
public int getMines() { return mines; }
|
public int getMines() { return mines; }
|
||||||
|
|
||||||
public int getActiveTiles() { return activeTiles; }
|
public int getActiveTiles() { return activeTiles; }
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package dev.kske.minesweeper;
|
package dev.kske.minesweeper;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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>
|
||||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||||
*/
|
*/
|
||||||
public class BoardConfig {
|
public class BoardConfig implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -6083006887427383946L;
|
||||||
|
|
||||||
public final int width, height, mines;
|
public final int width, height, mines;
|
||||||
|
|
||||||
@ -15,4 +19,9 @@ public class BoardConfig {
|
|||||||
this.height = height;
|
this.height = height;
|
||||||
this.mines = mines;
|
this.mines = mines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%d %d %d", width, height, mines);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ package dev.kske.minesweeper;
|
|||||||
*/
|
*/
|
||||||
public interface GameListener {
|
public interface GameListener {
|
||||||
|
|
||||||
void onGameStateEvent(GameStateEvent evt);
|
void onGameOverEvent(GameOverEvent evt);
|
||||||
|
|
||||||
void onFlaggedTilesEvent(FlaggedTilesEvent evt);
|
void onFlaggedTilesEvent(FlaggedTilesEvent evt);
|
||||||
}
|
}
|
||||||
|
35
src/dev/kske/minesweeper/GameOverEvent.java
Normal file
35
src/dev/kske/minesweeper/GameOverEvent.java
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package dev.kske.minesweeper;
|
||||||
|
|
||||||
|
import java.util.EventObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>Minesweeper</strong><br>
|
||||||
|
* File: <strong>GameOverEvent.java</strong><br>
|
||||||
|
* Created: <strong>03.04.2019</strong><br>
|
||||||
|
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||||
|
*/
|
||||||
|
public class GameOverEvent extends EventObject {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -966111253980213845L;
|
||||||
|
|
||||||
|
private final Board board;
|
||||||
|
private final GameState gameState;
|
||||||
|
private final BoardConfig boardConfig;
|
||||||
|
private final int duration;
|
||||||
|
|
||||||
|
public GameOverEvent(Object source, GameState gameState, BoardConfig boardConfig, int duration) {
|
||||||
|
super(source);
|
||||||
|
board = (Board) source;
|
||||||
|
this.gameState = gameState;
|
||||||
|
this.boardConfig = boardConfig;
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Board getBoard() { return board; }
|
||||||
|
|
||||||
|
public GameState getGameState() { return gameState; }
|
||||||
|
|
||||||
|
public BoardConfig getBoardConfig() { return boardConfig; }
|
||||||
|
|
||||||
|
public int getDuration() { return duration; }
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
package dev.kske.minesweeper;
|
|
||||||
|
|
||||||
import java.util.EventObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Project: <strong>Minesweeper</strong><br>
|
|
||||||
* File: <strong>GameStateEvent.java</strong><br>
|
|
||||||
* Created: <strong>03.04.2019</strong><br>
|
|
||||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
|
||||||
*/
|
|
||||||
public class GameStateEvent extends EventObject {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -966111253980213845L;
|
|
||||||
|
|
||||||
private final Board board;
|
|
||||||
private final GameState gameState;
|
|
||||||
|
|
||||||
public GameStateEvent(Object source, GameState gameState) {
|
|
||||||
super(source);
|
|
||||||
board = (Board) source;
|
|
||||||
this.gameState = gameState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Board getBoard() { return board; }
|
|
||||||
|
|
||||||
public GameState getGameState() { return gameState; }
|
|
||||||
}
|
|
@ -4,6 +4,16 @@ import java.awt.BorderLayout;
|
|||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
import java.awt.FlowLayout;
|
import java.awt.FlowLayout;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JFrame;
|
import javax.swing.JFrame;
|
||||||
@ -24,11 +34,13 @@ import javax.swing.UIManager;
|
|||||||
*/
|
*/
|
||||||
public class Minesweeper {
|
public class Minesweeper {
|
||||||
|
|
||||||
private static final String VERSION = "1.0 JE";
|
private static final String VERSION = "1.1 JE";
|
||||||
|
|
||||||
private JFrame mframe;
|
private JFrame mframe;
|
||||||
|
|
||||||
private Board board;
|
private Board board;
|
||||||
|
private TreeSet<Score> scores;
|
||||||
|
private final String scoresFile = "scores.ser";
|
||||||
private final BoardConfig easyConfig = new BoardConfig(8, 8, 10), mediumConfig = new BoardConfig(16, 16, 40),
|
private final BoardConfig easyConfig = new BoardConfig(8, 8, 10), mediumConfig = new BoardConfig(16, 16, 40),
|
||||||
hardConfig = new BoardConfig(30, 16, 99);
|
hardConfig = new BoardConfig(30, 16, 99);
|
||||||
|
|
||||||
@ -61,12 +73,19 @@ public class Minesweeper {
|
|||||||
*/
|
*/
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
try {
|
try {
|
||||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
UIManager.setLookAndFeel(UIManager.createLookAndFeel("Nimbus"));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
mframe = new JFrame();
|
mframe = new JFrame();
|
||||||
|
mframe.addWindowListener(new WindowAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowClosing(WindowEvent e) {
|
||||||
|
saveScores();
|
||||||
|
}
|
||||||
|
});
|
||||||
mframe.setResizable(false);
|
mframe.setResizable(false);
|
||||||
mframe.setTitle("Minesweeper");
|
mframe.setTitle("Minesweeper");
|
||||||
mframe.setBounds(100, 100, 198, 123);
|
mframe.setBounds(100, 100, 198, 123);
|
||||||
@ -90,13 +109,18 @@ public class Minesweeper {
|
|||||||
board.registerGameListener(new GameListener() {
|
board.registerGameListener(new GameListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onGameStateEvent(GameStateEvent evt) {
|
public void onGameOverEvent(GameOverEvent evt) {
|
||||||
switch (evt.getGameState()) {
|
switch (evt.getGameState()) {
|
||||||
case LOST:
|
case LOST:
|
||||||
JOptionPane.showMessageDialog(mframe, "Game lost!");
|
JOptionPane.showMessageDialog(mframe, "Game lost!");
|
||||||
break;
|
break;
|
||||||
case WON:
|
case WON:
|
||||||
JOptionPane.showMessageDialog(mframe, "Game won!");
|
JOptionPane.showMessageDialog(mframe, "Game won!");
|
||||||
|
if (scores.size() < 10 || scores.last().getDuration() > evt.getDuration()) {
|
||||||
|
String name = JOptionPane.showInputDialog("Please enter your name");
|
||||||
|
Score score = new Score(name, evt.getDuration(), new Date(), evt.getBoardConfig());
|
||||||
|
scores.add(score);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,13 +136,16 @@ public class Minesweeper {
|
|||||||
headerPanel.add(btnRestart);
|
headerPanel.add(btnRestart);
|
||||||
btnRestart.addActionListener((evt) -> board.reset());
|
btnRestart.addActionListener((evt) -> board.reset());
|
||||||
mframe.pack();
|
mframe.pack();
|
||||||
|
|
||||||
|
loadScores();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createMenuBar() {
|
private void createMenuBar() {
|
||||||
var menubar = new JMenuBar();
|
var menubar = new JMenuBar();
|
||||||
|
|
||||||
var gameMenu = new JMenu("Game");
|
var gameMenu = new JMenu("Game");
|
||||||
var aboutMenuItem = new JMenuItem("About");
|
var highscoreMenuItem = new JMenuItem("Highscores");
|
||||||
|
var aboutMenuItem = new JMenuItem("About");
|
||||||
|
|
||||||
var easyMenuItem = new JMenuItem("Easy");
|
var easyMenuItem = new JMenuItem("Easy");
|
||||||
var mediumMenuItem = new JMenuItem("Medium");
|
var mediumMenuItem = new JMenuItem("Medium");
|
||||||
@ -138,9 +165,10 @@ public class Minesweeper {
|
|||||||
BoardConfig cfg = new CustomDialog(mframe).showDialog();
|
BoardConfig cfg = new CustomDialog(mframe).showDialog();
|
||||||
if (cfg != null) initGame(cfg);
|
if (cfg != null) initGame(cfg);
|
||||||
});
|
});
|
||||||
aboutMenuItem.addActionListener((evt) -> {
|
|
||||||
JOptionPane.showMessageDialog(board, "Minesweeper version " + VERSION + "\nby Kai S. K. Engelbart");
|
highscoreMenuItem.addActionListener((evt) -> new ScoreDialog(scores).setVisible(true));
|
||||||
});
|
aboutMenuItem.addActionListener((evt) -> JOptionPane.showMessageDialog(board,
|
||||||
|
"Minesweeper version " + VERSION + "\nby Kai S. K. Engelbart"));
|
||||||
|
|
||||||
gameMenu.add(easyMenuItem);
|
gameMenu.add(easyMenuItem);
|
||||||
gameMenu.add(mediumMenuItem);
|
gameMenu.add(mediumMenuItem);
|
||||||
@ -148,11 +176,32 @@ public class Minesweeper {
|
|||||||
gameMenu.addSeparator();
|
gameMenu.addSeparator();
|
||||||
gameMenu.add(customMenuItem);
|
gameMenu.add(customMenuItem);
|
||||||
menubar.add(gameMenu);
|
menubar.add(gameMenu);
|
||||||
|
menubar.add(highscoreMenuItem);
|
||||||
menubar.add(aboutMenuItem);
|
menubar.add(aboutMenuItem);
|
||||||
|
|
||||||
mframe.setJMenuBar(menubar);
|
mframe.setJMenuBar(menubar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void loadScores() {
|
||||||
|
try (var in = new ObjectInputStream(new FileInputStream(scoresFile))) {
|
||||||
|
scores = (TreeSet<Score>) in.readObject();
|
||||||
|
} catch (FileNotFoundException ex) {
|
||||||
|
scores = new TreeSet<>();
|
||||||
|
} catch (IOException | ClassNotFoundException ex) {
|
||||||
|
JOptionPane.showMessageDialog(mframe, "The score file seems to be corrupted. It will be replaced when closing the game.", "File error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
scores = new TreeSet<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveScores() {
|
||||||
|
try (var out = new ObjectOutputStream(new FileOutputStream(scoresFile))) {
|
||||||
|
out.writeObject(scores);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initGame(BoardConfig config) {
|
private void initGame(BoardConfig config) {
|
||||||
board.init(config);
|
board.init(config);
|
||||||
mframe.pack();
|
mframe.pack();
|
||||||
|
40
src/dev/kske/minesweeper/Score.java
Normal file
40
src/dev/kske/minesweeper/Score.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package dev.kske.minesweeper;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>Minesweeper</strong><br>
|
||||||
|
* File: <strong>Score.java</strong><br>
|
||||||
|
* Created: <strong>16.04.2019</strong><br>
|
||||||
|
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||||
|
*/
|
||||||
|
public class Score implements Comparable<Score>, Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 3384023296639779740L;
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final int duration;
|
||||||
|
private final Date date;
|
||||||
|
private final BoardConfig boardConfig;
|
||||||
|
|
||||||
|
public Score(String name, int duration, Date date, BoardConfig boardConfig) {
|
||||||
|
this.name = name;
|
||||||
|
this.duration = duration;
|
||||||
|
this.date = date;
|
||||||
|
this.boardConfig = boardConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() { return name; }
|
||||||
|
|
||||||
|
public int getDuration() { return duration; }
|
||||||
|
|
||||||
|
public Date getDate() { return date; }
|
||||||
|
|
||||||
|
public BoardConfig getBoardConfig() { return boardConfig; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Score other) {
|
||||||
|
return Integer.compare(duration, other.duration);
|
||||||
|
}
|
||||||
|
}
|
59
src/dev/kske/minesweeper/ScoreDialog.java
Normal file
59
src/dev/kske/minesweeper/ScoreDialog.java
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package dev.kske.minesweeper;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.swing.JDialog;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.SwingConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>Minesweeper</strong><br>
|
||||||
|
* File: <strong>ScoreDialog.java</strong><br>
|
||||||
|
* Created: <strong>16.04.2019</strong><br>
|
||||||
|
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||||
|
*/
|
||||||
|
public class ScoreDialog extends JDialog {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 3637727047056147815L;
|
||||||
|
private JTable mtable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the dialog.
|
||||||
|
*/
|
||||||
|
public ScoreDialog(Set<Score> scores) {
|
||||||
|
setBounds(100, 100, 450, 300);
|
||||||
|
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
|
||||||
|
getContentPane().setLayout(new BorderLayout(0, 0));
|
||||||
|
|
||||||
|
String[] columnNames = {"Name", "Game duration", "Board Config", "Date"};
|
||||||
|
String[][] data = new String[scores.size()][4];
|
||||||
|
Iterator<Score> iter = scores.iterator();
|
||||||
|
for(int i = 0; i < data.length; i++) {
|
||||||
|
Score s = iter.next();
|
||||||
|
data[i][0] = s.getName();
|
||||||
|
data[i][1] = String.valueOf(s.getDuration());
|
||||||
|
data[i][2] = s.getBoardConfig().toString();
|
||||||
|
data[i][3] = new SimpleDateFormat().format(s.getDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
mtable = new JTable(data, columnNames);
|
||||||
|
getContentPane().add(mtable);
|
||||||
|
|
||||||
|
JPanel panel = new JPanel();
|
||||||
|
getContentPane().add(panel, BorderLayout.NORTH);
|
||||||
|
panel.setLayout(new BorderLayout(0, 0));
|
||||||
|
|
||||||
|
panel.add(mtable.getTableHeader(), BorderLayout.CENTER);
|
||||||
|
|
||||||
|
JLabel lblHighscores = new JLabel("Highscores");
|
||||||
|
panel.add(lblHighscores, BorderLayout.NORTH);
|
||||||
|
lblHighscores.setFont(new Font("Tahoma", Font.BOLD, 16));
|
||||||
|
lblHighscores.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user