Initial commit

This commit is contained in:
uohlhv 2021-09-16 20:44:09 +08:00
commit 8d1714777d
30 changed files with 1689 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.DS_Store
/bin
Minesweeper.dat
Minesweeper.jar

12
README.md Normal file
View File

@ -0,0 +1,12 @@
# Requirement
- Oracle JDK >= 1.8
- Add JDK's `/bin` directory into environment variable `$PATH`.
# Compilation
Execute `compile.sh` (`compile.bat` for Windows).
# Archive
Execute `archive.sh` (`archive.bat` for Windows).

8
archive.bat Normal file
View File

@ -0,0 +1,8 @@
@echo off
cd bin
jar cvfm "%~dp0Minesweeper.jar" "%~dp0manifest.mf" *
cd ..
echo.
echo 完成!
echo.
pause

4
archive.sh Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
cd bin || exit
jar cvfm ../Minesweeper.jar ../manifest.mf ./*

10
compile.bat Normal file
View File

@ -0,0 +1,10 @@
@echo off
if exist bin (
del /Q bin
)
javac -d bin src\*.java
xcopy /Y "%~dp0res\*" "%~dp0bin"
echo.
echo 完成!
echo.
pause

7
compile.sh Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
if [ -d bin ]; then rm -rf bin; fi
javac -d bin src/*.java
cp res/* bin
echo "完成!"

1
manifest.mf Normal file
View File

@ -0,0 +1 @@
Main-Class: Main

BIN
res/410.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
res/420.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
res/430.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
res/432.wav Normal file

Binary file not shown.

BIN
res/433.wav Normal file

Binary file not shown.

BIN
res/434.wav Normal file

Binary file not shown.

BIN
res/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

150
src/BestTimesDialog.java Normal file
View File

@ -0,0 +1,150 @@
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.BorderFactory;
public class BestTimesDialog extends JDialog implements ActionListener, WindowListener {
private static final long serialVersionUID = -459646110674871028L;
private Minesweeper main;
private JLabel bRecord;
private JLabel iRecord;
private JLabel eRecord;
private JLabel iName;
private JLabel bName;
private JLabel eName;
public BestTimesDialog(Minesweeper main) {
super(main, "Fastest Mine Sweepers", true);
this.main = main;
setLayout(new BorderLayout());
JPanel panelTop = new JPanel();
JPanel panelBottom = new JPanel();
Storage data = this.main.getStorage();
this.bRecord = new JLabel(data.getRecord(this.main.BEGINNER) + " seconds");
this.iRecord = new JLabel(data.getRecord(this.main.INTERMEDIATE) + " seconds");
this.eRecord = new JLabel(data.getRecord(this.main.EXPERT) + " seconds");
this.bName = new JLabel(data.getName(this.main.BEGINNER));
this.iName = new JLabel(data.getName(this.main.INTERMEDIATE));
this.eName = new JLabel(data.getName(this.main.EXPERT));
panelTop.setBorder(BorderFactory.createEmptyBorder(15, 10, 15, 10));
panelBottom.setBorder(BorderFactory.createEmptyBorder(0, 10, 15, 10));
add(panelTop, "North");
add(panelBottom);
panelTop.setLayout(new GridBagLayout());
panelBottom.setLayout(new GridBagLayout());
GridBagConstraints ct =
new GridBagConstraints(
3,
3,
1,
1,
0.0,
1.0,
GridBagConstraints.CENTER,
GridBagConstraints.NONE,
new Insets(2, 10, 2, 10),
0,
0);
GridBagConstraints cb =
new GridBagConstraints(
2,
1,
1,
1,
0.0,
1.0,
GridBagConstraints.CENTER,
GridBagConstraints.NONE,
new Insets(2, 10, 2, 10),
0,
0);
ct.gridx = 0;
ct.anchor = GridBagConstraints.WEST;
ct.gridy = 0;
panelTop.add(new JLabel("Beginner:"), ct);
ct.gridy = 1;
panelTop.add(new JLabel("Intermediate:"), ct);
ct.gridy = 2;
panelTop.add(new JLabel("Expert:"), ct);
ct.gridx = 1;
ct.gridy = GridBagConstraints.RELATIVE;
ct.weightx = 1.0;
ct.anchor = GridBagConstraints.CENTER;
ct.gridy = 0;
panelTop.add(this.bRecord, ct);
ct.gridy = 1;
panelTop.add(this.iRecord, ct);
ct.gridy = 2;
panelTop.add(this.eRecord, ct);
ct.gridx = 2;
ct.gridy = 0;
panelTop.add(this.bName, ct);
ct.gridy = 1;
panelTop.add(this.iName, ct);
ct.gridy = 2;
panelTop.add(this.eName, ct);
cb.gridx = 0;
cb.gridy = 0;
JButton reset = new JButton("Reset Scores");
panelBottom.add(reset, cb);
reset.addActionListener(this);
cb.gridx = 1;
cb.gridy = 0;
JButton ok = new JButton("OK");
panelBottom.add(ok, cb);
this.getRootPane().setDefaultButton(ok);
ok.addActionListener(this);
pack();
setResizable(false);
Rectangle screenSize = getGraphicsConfiguration().getBounds();
setLocation(
screenSize.x + screenSize.width / 2 - getSize().width / 2,
screenSize.y + screenSize.height / 2 - getSize().height / 2);
setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (cmd.equals("Reset Scores")) {
Storage data = this.main.getStorage();
data.setRecord(0, 999);
data.setRecord(1, 999);
data.setRecord(2, 999);
data.setName(0, "Anonymous");
data.setName(1, "Anonymous");
data.setName(2, "Anonymous");
this.bRecord.setText("999 seconds");
this.iRecord.setText("999 seconds");
this.eRecord.setText("999 seconds");
this.bName.setText("Anonymous");
this.iName.setText("Anonymous");
this.eName.setText("Anonymous");
return;
}
this.dispose();
}
@Override
public void windowClosing(WindowEvent e) {}
@Override
public void windowOpened(WindowEvent e) {}
@Override
public void windowClosed(WindowEvent e) {}
@Override
public void windowIconified(WindowEvent e) {}
@Override
public void windowDeiconified(WindowEvent e) {}
@Override
public void windowActivated(WindowEvent e) {}
@Override
public void windowDeactivated(WindowEvent e) {}
}

170
src/Board.java Normal file
View File

@ -0,0 +1,170 @@
import java.awt.*;
import javax.swing.*;
import javax.swing.BorderFactory;
import javax.swing.plaf.basic.BasicBorders.ButtonBorder;
public class Board extends JPanel {
private static final long serialVersionUID = -8979636904753517635L;
private Minesweeper main;
private Tile[][] tiles;
private int height;
private int width;
private int numOfMines;
private void addMines() {
if (this.numOfMines >= this.width * this.height) {
this.numOfMines = this.width * this.height - 1;
}
int nextRow, nextCol;
int i = 0;
while (i < this.numOfMines) {
nextRow = Math.round((float) Math.random() * (this.height - 1));
nextCol = Math.round((float) Math.random() * (this.width - 1));
if (!this.tiles[nextRow][nextCol].isMine()) {
this.tiles[nextRow][nextCol].setMine();
i += 1;
}
}
this.setTileValues();
}
private void setTileValues() {
for (int i = 0; i < this.height; i += 1) {
for (int j = 0; j < this.width; j += 1) {
if (this.tiles[i][j].isMine()) {
continue;
}
int mines = 0;
for (int k = i - 1; k <= i + 1; k += 1) {
for (int l = j - 1; l <= j + 1; l += 1) {
if (!(k < 0 || l < 0 || k >= this.height || l >= this.width)
&& !(k == i && l == j)
&& this.tiles[k][l].isMine()) {
mines += 1;
}
}
}
this.tiles[i][j].setValue(mines);
}
}
if (this.main.getStorage().isCreepy()) {
this.addDuds();
}
}
private void addDuds() {
for (int i = 0; i < this.height; i += 1) {
for (int j = 0; j < this.width; j += 1) {
if (this.tiles[i][j].isMine() && Math.round((float) Math.random() * 5) == 0) {
this.tiles[i][j].setDud();
}
}
}
}
public Board(Minesweeper main) {
this.main = main;
setBackground(new Color(192, 192, 192));
setBorder(
BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(5, 5, 5, 5),
new ButtonBorder(
Color.white, Color.white, new Color(128, 128, 128), new Color(128, 128, 128))));
}
public Minesweeper getMain() {
return this.main;
}
public int getMines() {
return this.numOfMines;
}
public void setBoard(int width, int height, int mines) {
setVisible(false);
this.height = height;
this.width = width;
this.numOfMines = mines;
setLayout(new GridLayout(this.height, this.width, 0, 0));
removeAll();
this.tiles = new Tile[this.height][this.width];
for (int i = 0; i < this.height; i += 1) {
for (int j = 0; j < this.width; j += 1) {
add(this.tiles[i][j] = new Tile(i, j, this));
}
}
this.addMines();
setVisible(true);
}
public void expand(int row, int col) {
setVisible(false);
for (int i = row - 1; i <= row + 1; i += 1) {
for (int j = col - 1; j <= col + 1; j += 1) {
if (!(i < 0 || j < 0 || i >= this.height || j >= this.width) && !(i == row && j == col)) {
Tile tile = this.tiles[i][j];
if (!tile.isUncovered() && !tile.isFlagged() && !tile.isUnsure() && !tile.isDud()) {
tile.uncover(true);
}
}
}
}
setVisible(true);
}
public void showMines() {
setVisible(false);
for (int i = 0; i < this.height; i += 1) {
for (int j = 0; j < this.width; j += 1) {
Tile tile = this.tiles[i][j];
if (tile.isMine()
&& !tile.isUncovered()
&& !tile.isFlagged()
&& !tile.isUnsure()
&& !tile.isDud()) {
tile.setTileIcon("covered mine");
} else if ((!tile.isMine() || tile.isDud()) && tile.isFlagged()) {
tile.setTileIcon("misflagged");
}
}
}
setVisible(true);
}
public void showFlags() {
setVisible(false);
for (int i = 0; i < this.height; i += 1) {
for (int j = 0; j < this.width; j += 1) {
Tile tile = this.tiles[i][j];
if (tile.isMine() && !tile.isUncovered() && !tile.isFlagged()) {
tile.setTileIcon("flagged");
}
}
}
setVisible(true);
}
public void removeAllMouseListeners() {
for (int i = 0; i < this.height; i += 1) {
for (int j = 0; j < this.width; j += 1) {
this.tiles[i][j].removeMouseListener(this.tiles[i][j]);
}
}
}
public void checkWon(boolean expanding) {
for (int i = 0; i < this.height; i += 1) {
for (int j = 0; j < this.width; j += 1) {
Tile tile = this.tiles[i][j];
if ((tile.isFlagged() && !tile.isMine())
|| (!tile.isUncovered() && !tile.isFlagged() && !tile.isMine())) {
return;
}
}
}
this.main.complete(expanding);
}
}

75
src/CounterDisplay.java Normal file
View File

@ -0,0 +1,75 @@
import java.awt.*;
import javax.swing.*;
import javax.swing.BorderFactory;
import javax.swing.plaf.basic.BasicBorders.ButtonBorder;
public class CounterDisplay extends JPanel {
private static final long serialVersionUID = 8590228173889847000L;
private Board board;
private Digit hundred;
private Digit tenth;
private Digit first;
private int mines;
public CounterDisplay(Board board) {
this.board = board;
this.hundred = new Digit(this.board);
this.tenth = new Digit(this.board);
this.first = new Digit(this.board);
this.mines = this.board.getMines();
this.updateDisplay();
setBackground(new Color(192, 192, 192));
setBorder(
BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(5, 5, 5, 5),
new ButtonBorder(
Color.white, Color.white, new Color(128, 128, 128), new Color(128, 128, 128))));
setVisible(false);
setLayout(new GridLayout(1, 3, 0, 0));
removeAll();
add(this.hundred);
add(this.tenth);
add(this.first);
setVisible(true);
}
public void increase() {
this.mines += 1;
this.updateDisplay();
}
public void decrease() {
this.mines -= 1;
if (this.mines < 0) {
this.negativeLoop();
} else {
this.updateDisplay();
}
}
public void reset() {
this.mines = this.board.getMines();
this.updateDisplay();
}
public void clear() {
this.mines = 0;
this.updateDisplay();
}
public void updateDisplay() {
this.hundred.setDigitIcon(String.valueOf(mines / 100));
this.tenth.setDigitIcon(String.valueOf(mines / 10 % 10));
this.first.setDigitIcon(String.valueOf(mines % 10));
}
public void negativeLoop() {
this.hundred.setDigitIcon("-");
int temp = Math.abs(mines) % 100;
this.tenth.setDigitIcon(String.valueOf(temp / 10 % 10));
this.first.setDigitIcon(String.valueOf(temp % 10));
}
}

154
src/CustomDialog.java Normal file
View File

@ -0,0 +1,154 @@
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.BorderFactory;
public class CustomDialog extends JDialog implements ActionListener, WindowListener {
private static final long serialVersionUID = 3593586540539735229L;
private Minesweeper main;
private JTextField height = new JTextField(5);
private JTextField width = new JTextField(5);
private JTextField mines = new JTextField(5);
private int hValue;
private int wValue;
private int mValue;
public CustomDialog(Minesweeper main) {
super(main, "Custom Field", true);
this.main = main;
JPanel panel = new JPanel();
Storage data = this.main.getStorage();
this.hValue = data.getHeight();
this.wValue = data.getWidth();
this.mValue = data.getMines();
this.height.setText(String.valueOf(this.hValue));
this.width.setText(String.valueOf(this.wValue));
this.mines.setText(String.valueOf(this.mValue));
panel.setBorder(BorderFactory.createEmptyBorder(30, 10, 30, 10));
add(panel);
panel.setLayout(new GridBagLayout());
GridBagConstraints c =
new GridBagConstraints(
3,
3,
1,
1,
0.0,
1.0,
GridBagConstraints.CENTER,
GridBagConstraints.NONE,
new Insets(2, 10, 2, 10),
0,
0);
c.gridx = 3;
c.fill = GridBagConstraints.HORIZONTAL;
c.gridy = 0;
JButton ok = new JButton("OK");
panel.add(ok, c);
this.getRootPane().setDefaultButton(ok);
ok.addActionListener(this);
c.gridx = 3;
c.gridy = 2;
JButton cancel = new JButton("Cancel");
panel.add(cancel, c);
cancel.addActionListener(this);
c.gridx = 0;
c.anchor = GridBagConstraints.EAST;
c.gridy = 0;
panel.add(new JLabel("Height:"), c);
c.gridy = 1;
panel.add(new JLabel("Width:"), c);
c.gridy = 2;
panel.add(new JLabel("Mines:"), c);
c.gridx = 1;
c.gridy = GridBagConstraints.RELATIVE;
c.weightx = 1.0;
c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.CENTER;
c.gridy = 0;
panel.add(height, c);
c.gridy = 1;
panel.add(width, c);
c.gridy = 2;
panel.add(mines, c);
pack();
setResizable(false);
Rectangle screenSize = getGraphicsConfiguration().getBounds();
setLocation(
screenSize.x + screenSize.width / 2 - getSize().width / 2,
screenSize.y + screenSize.height / 2 - getSize().height / 2);
setVisible(true);
}
public boolean setValues() {
try {
this.wValue = Integer.decode(width.getText()).intValue();
if (this.wValue < 9) {
this.wValue = 9;
} else if (this.wValue > 30) {
this.wValue = 30;
}
this.hValue = Integer.decode(height.getText()).intValue();
if (this.hValue < 9) {
this.hValue = 9;
} else if (this.hValue > 24) {
this.hValue = 24;
}
this.mValue = Integer.decode(mines.getText()).intValue();
if (this.mValue < 10) {
this.mValue = 10;
} else if (this.mValue > 668) {
this.mValue = 668;
}
} catch (NumberFormatException e) {
return false;
}
return true;
}
public int getHeightValue() {
return this.hValue;
}
public int getWidthValue() {
return this.wValue;
}
public int getMinesValue() {
return this.mValue;
}
@Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (cmd.equals("Cancel") || this.setValues()) {
this.dispose();
}
}
@Override
public void windowClosing(WindowEvent e) {
this.dispose();
}
@Override
public void windowOpened(WindowEvent e) {}
@Override
public void windowClosed(WindowEvent e) {}
@Override
public void windowIconified(WindowEvent e) {}
@Override
public void windowDeiconified(WindowEvent e) {}
@Override
public void windowActivated(WindowEvent e) {}
@Override
public void windowDeactivated(WindowEvent e) {}
}

49
src/Digit.java Normal file
View File

@ -0,0 +1,49 @@
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class Digit extends JLabel {
private static final long serialVersionUID = -4719882476458914430L;
private Board board;
private BufferedImage image;
private int value = 0;
private final int HEIGHT = 23;
private final int WIDTH = 13;
public Digit(Board board) {
super("", JLabel.CENTER);
this.board = board;
this.image = this.board.getMain().getImage().getDigit();
this.setDigitIcon("0");
setVerticalAlignment(JLabel.CENTER);
setHorizontalAlignment(JLabel.CENTER);
setPreferredSize(new Dimension(this.WIDTH, this.HEIGHT));
}
public int topMargin(String number) {
String[] numbers = {
"-", " ", "9", "8", "7", "6",
"5", "4", "3", "2", "1", "0"
};
for (int i = 0; i < numbers.length; i += 1) {
if (numbers[i].equals(number)) {
return i;
}
}
return 1;
}
public void setDigitIcon(String value) {
setIcon(
new ImageIcon(
this.image.getSubimage(
0, this.HEIGHT * this.topMargin(value), this.WIDTH, this.HEIGHT)));
}
public void setValue(int value) {
this.value = value;
this.setDigitIcon(String.valueOf(this.value));
}
}

43
src/Info.java Normal file
View File

@ -0,0 +1,43 @@
import java.awt.*;
import javax.swing.*;
import javax.swing.BorderFactory;
import javax.swing.plaf.basic.BasicBorders.ButtonBorder;
public class Info extends JPanel {
private static final long serialVersionUID = 3806487248823604535L;
private Board board;
private CounterDisplay counter;
private Smiley smiley;
private TimerDisplay timer;
public Info(Minesweeper main) {
this.board = main.getBoard();
this.counter = new CounterDisplay(this.board);
this.smiley = new Smiley(this.board);
this.timer = new TimerDisplay(this.board);
setBackground(new Color(192, 192, 192));
setBorder(
BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(5, 5, 0, 5),
new ButtonBorder(
Color.white, Color.white, new Color(128, 128, 128), new Color(128, 128, 128))));
setLayout(new BorderLayout());
add(this.counter, "West");
add(this.smiley, "Center");
add(this.timer, "East");
setVisible(true);
}
public CounterDisplay getCounter() {
return this.counter;
}
public Smiley getSmiley() {
return this.smiley;
}
public TimerDisplay getTimer() {
return this.timer;
}
}

5
src/Main.java Normal file
View File

@ -0,0 +1,5 @@
public class Main {
public static void main(String[] args) {
new Minesweeper();
}
}

118
src/Menubar.java Normal file
View File

@ -0,0 +1,118 @@
import java.awt.event.*;
import javax.swing.*;
public class Menubar extends JMenuBar implements ActionListener {
private static final long serialVersionUID = 865388774192274414L;
private Minesweeper main;
private JCheckBoxMenuItem creepy;
private JCheckBoxMenuItem sound;
public Menubar(Minesweeper main) {
this.main = main;
JMenu game = new JMenu("Game");
add(game);
JMenuItem newGame = new JMenuItem("New");
game.add(newGame);
newGame.addActionListener(this);
game.addSeparator();
Storage data = this.main.getStorage();
JRadioButtonMenuItem beginner =
new JRadioButtonMenuItem(
"Beginner", (data.getDifficulty() == this.main.BEGINNER) ? true : false);
JRadioButtonMenuItem intermediate =
new JRadioButtonMenuItem(
"Intermediate", (data.getDifficulty() == this.main.INTERMEDIATE) ? true : false);
JRadioButtonMenuItem expert =
new JRadioButtonMenuItem(
"Expert", (data.getDifficulty() == this.main.EXPERT) ? true : false);
JRadioButtonMenuItem custom =
new JRadioButtonMenuItem(
"Custom...", (data.getDifficulty() == this.main.CUSTOM) ? true : false);
this.creepy = new JCheckBoxMenuItem("Creepy Mode", (data.isCreepy()) ? true : false);
game.add(beginner);
game.add(intermediate);
game.add(expert);
game.add(custom);
game.add(creepy);
ButtonGroup difficulties = new ButtonGroup();
difficulties.add(beginner);
difficulties.add(intermediate);
difficulties.add(expert);
difficulties.add(custom);
beginner.addActionListener(this);
intermediate.addActionListener(this);
expert.addActionListener(this);
custom.addActionListener(this);
custom.setActionCommand("Custom");
creepy.addActionListener(this);
game.addSeparator();
this.sound = new JCheckBoxMenuItem("Sound", (data.isSoundEnabled()) ? true : false);
game.add(sound);
sound.addActionListener(this);
game.addSeparator();
JMenuItem bestTimes = new JMenuItem("Best Times...");
game.add(bestTimes);
bestTimes.addActionListener(this);
bestTimes.setActionCommand("Best Times");
game.addSeparator();
JMenuItem exit = new JMenuItem("Exit");
game.add(exit);
exit.addActionListener(this);
}
@Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (cmd.equals("New")) {
this.main.newGame();
} else {
Storage data = this.main.getStorage();
if (cmd.equals("Beginner")) {
data.setDifficulty(this.main.BEGINNER);
this.main.newGame();
} else if (cmd.equals("Intermediate")) {
data.setDifficulty(this.main.INTERMEDIATE);
this.main.newGame();
} else if (cmd.equals("Expert")) {
data.setDifficulty(this.main.EXPERT);
this.main.newGame();
} else if (cmd.equals("Custom")) {
CustomDialog dialog = new CustomDialog(this.main);
data.setDifficulty(this.main.CUSTOM);
data.setHeight(dialog.getHeightValue());
data.setWidth(dialog.getWidthValue());
data.setMines(dialog.getMinesValue());
this.main.newGame();
} else if (cmd.equals("Creepy Mode")) {
data.switchCreepy();
if (!data.isSoundEnabled()) {
data.switchSound();
this.sound.setSelected(true);
}
if (this.main.isGameStarted()) {
this.main.newGame();
}
} else if (cmd.equals("Sound")) {
data.switchSound();
if (data.isCreepy()) {
data.switchCreepy();
this.creepy.setSelected(false);
this.main.newGame();
} else if (this.main.isGameStarted()) {
PlaySound play = this.main.getPlay();
if (play.isPlaying()) {
play.stop();
} else {
play.tick();
}
}
} else if (cmd.equals("Best Times")) {
new BestTimesDialog(this.main);
} else if (cmd.equals("Exit")) {
System.exit(0);
}
}
}
}

151
src/Minesweeper.java Normal file
View File

@ -0,0 +1,151 @@
import java.awt.*;
import javax.swing.*;
public class Minesweeper extends JFrame {
private static final long serialVersionUID = -711696178703199107L;
private Board board;
private Info info;
private PlaySound play;
private Resource images;
private Storage data;
private boolean gameStarted;
private final String RES_GAME_ICON = "icon.png";
public final int BEGINNER = 0;
public final int INTERMEDIATE = 1;
public final int EXPERT = 2;
public final int CUSTOM = 3;
private void setLocationCenter() {
Rectangle screen = getGraphicsConfiguration().getBounds();
setLocation(
screen.x + screen.width / 2 - getSize().width / 2,
screen.y + screen.height / 2 - getSize().height / 2);
}
public Minesweeper() {
super("Minesweeper");
this.data = new Storage();
this.images = new Resource();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource(RES_GAME_ICON)));
setJMenuBar(new Menubar(this));
setLayout(new BorderLayout());
this.data = new Storage();
this.board = new Board(this);
this.board.setBoard(
this.data.getWidth(),
this.data.getHeight(),
this.data.getMines()); // must call setBoard before create instances of Info
this.info = new Info(this);
this.play = new PlaySound();
this.gameStarted = false;
add(this.info, "North");
add(this.board);
pack();
setResizable(false);
this.setLocationCenter();
setVisible(true);
}
public void newGame() {
this.board.setBoard(this.data.getWidth(), this.data.getHeight(), this.data.getMines());
pack();
if (this.isGameStarted()) {
this.info.getTimer().stop();
if (this.data.isSoundEnabled()) {
this.play.stop();
}
}
this.info.getCounter().reset();
this.info.getSmiley().setSmileyIcon("smile");
this.info.getTimer().reset();
this.stopGame();
}
public void delGame() {
this.stopGame();
if (this.data.isSoundEnabled()) {
this.play.stop();
this.play.boom();
}
this.info.getTimer().stop();
this.info.getSmiley().setSmileyIcon("death");
this.board.removeAllMouseListeners();
this.board.showMines();
}
public Board getBoard() {
return this.board;
}
public Info getInfo() {
return this.info;
}
public PlaySound getPlay() {
return this.play;
}
public Resource getImage() {
return this.images;
}
public Storage getStorage() {
return this.data;
}
public boolean isGameStarted() {
return this.gameStarted;
}
public void startGame() {
this.gameStarted = true;
}
public void stopGame() {
this.gameStarted = false;
}
public void complete(boolean expanding) {
this.stopGame();
this.info.getTimer().stop();
this.info.getSmiley().setSmileyIcon("boss");
this.board.removeAllMouseListeners();
this.board.showFlags();
this.info.getCounter().clear();
if (this.data.isSoundEnabled() && !this.data.isCreepy() && !expanding) {
this.play.stop();
this.play.win();
}
if (this.isNewRecord() && !this.data.isCreepy()) {
new NewRecordDialog(this);
}
}
public boolean isNewRecord() {
int seconds = this.info.getTimer().getTime();
switch (this.data.getDifficulty()) {
case 0:
if (seconds < this.data.getRecord(this.BEGINNER)) {
return true;
}
break;
case 1:
if (seconds < this.data.getRecord(this.INTERMEDIATE)) {
return true;
}
break;
case 2:
if (seconds < this.data.getRecord(this.EXPERT)) {
return true;
}
break;
default:
}
return false;
}
}

98
src/NewRecordDialog.java Normal file
View File

@ -0,0 +1,98 @@
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.BorderFactory;
public class NewRecordDialog extends JDialog implements ActionListener, WindowListener {
private static final long serialVersionUID = -7317311608209462993L;
private Minesweeper main;
private JTextField name;
public NewRecordDialog(Minesweeper main) {
super(main, "", true);
this.main = main;
this.name = new JTextField("Anonymous");
JPanel panel = new JPanel();
add(panel);
panel.setLayout(new GridBagLayout());
panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
GridBagConstraints c =
new GridBagConstraints(
1,
3,
1,
1,
0.0,
1.0,
GridBagConstraints.CENTER,
GridBagConstraints.NONE,
new Insets(15, 15, 15, 15),
0,
0);
c.gridx = 0;
c.gridy = 0;
panel.add(
new JLabel(
"<html><center>You have the fastest time<br>for "
+ this.getLevel()
+ " level.<br>Please enter your name.</center></html>"),
c);
c.gridy = 1;
panel.add(this.name, c);
c.gridy = 2;
JButton ok = new JButton("OK");
panel.add(ok, c);
this.getRootPane().setDefaultButton(ok);
ok.addActionListener(this);
pack();
setResizable(false);
Rectangle screenSize = getGraphicsConfiguration().getBounds();
setLocation(
screenSize.x + screenSize.width / 2 - getSize().width / 2,
screenSize.y + screenSize.height / 2 - getSize().height / 2);
setVisible(true);
}
public String getLevel() {
switch (this.main.getStorage().getDifficulty()) {
case 0:
return "beginner";
case 1:
return "intermediate";
case 2:
return "expert";
}
return "";
}
@Override
public void actionPerformed(ActionEvent e) {
Storage data = this.main.getStorage();
data.setRecord(data.getDifficulty(), this.main.getInfo().getTimer().getTime());
data.setName(data.getDifficulty(), this.name.getText());
this.dispose();
}
@Override
public void windowClosing(WindowEvent e) {}
@Override
public void windowOpened(WindowEvent e) {}
@Override
public void windowClosed(WindowEvent e) {}
@Override
public void windowIconified(WindowEvent e) {}
@Override
public void windowDeiconified(WindowEvent e) {}
@Override
public void windowActivated(WindowEvent e) {}
@Override
public void windowDeactivated(WindowEvent e) {}
}

64
src/PlaySound.java Normal file
View File

@ -0,0 +1,64 @@
import java.io.*;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;
import javax.sound.sampled.*;
public class PlaySound {
private Timer timer;
private boolean playing;
private final String RES_TICK = "432.wav";
private final String RES_WIN = "433.wav";
private final String RES_BOOM = "434.wav";
public PlaySound() {
this.playing = false;
}
public boolean isPlaying() {
return this.playing;
}
public void play(String filename) {
URL f = getClass().getResource(filename);
try {
AudioInputStream in = AudioSystem.getAudioInputStream(f);
Clip clip = AudioSystem.getClip();
clip.open(in);
clip.start();
} catch (UnsupportedAudioFileException e) {
} catch (IOException e) {
} catch (LineUnavailableException e) {
}
}
public void tick() {
this.playing = true;
this.timer = new Timer();
TimerTask tt =
new TimerTask() {
@Override
public void run() {
play(RES_TICK);
}
};
this.timer.scheduleAtFixedRate(tt, 0, 1000);
}
public void win() {
this.play(this.RES_WIN);
}
public void boom() {
this.play(this.RES_BOOM);
}
public void stop() {
if (this.playing) {
this.timer.cancel();
this.playing = false;
}
}
}

42
src/Resource.java Normal file
View File

@ -0,0 +1,42 @@
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.*;
public class Resource {
private BufferedImage digit;
private BufferedImage smiley;
private BufferedImage tile;
private final String RES_TILE = "410.png";
private final String RES_DIGIT = "420.png";
private final String RES_SMILEY = "430.png";
private BufferedImage loadImage(String filename) {
BufferedImage image;
try {
image = ImageIO.read(getClass().getResourceAsStream(filename));
} catch (Exception e) {
JOptionPane.showMessageDialog(null, "Error: Image Loading Failed.");
image = null;
}
return image;
}
public Resource() {
this.digit = this.loadImage(this.RES_DIGIT);
this.smiley = this.loadImage(this.RES_SMILEY);
this.tile = this.loadImage(this.RES_TILE);
}
public BufferedImage getDigit() {
return this.digit;
}
public BufferedImage getSmiley() {
return this.smiley;
}
public BufferedImage getTile() {
return this.tile;
}
}

64
src/Smiley.java Normal file
View File

@ -0,0 +1,64 @@
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class Smiley extends JLabel implements MouseListener {
private static final long serialVersionUID = -1028805830774712972L;
private Board board;
private BufferedImage image;
private final int SIZE = 24;
public Smiley(Board board) {
super("", JLabel.CENTER);
this.board = board;
this.image = this.board.getMain().getImage().getSmiley();
this.setSmileyIcon("smile");
addMouseListener(this);
setVerticalAlignment(JLabel.CENTER);
setHorizontalAlignment(JLabel.CENTER);
setPreferredSize(new Dimension(this.SIZE, this.SIZE));
}
public int topMargin(String state) {
String[] states = {"smile down", "boss", "death", "oops", "smile"};
for (int i = 0; i < states.length; i += 1) {
if (states[i].equals(state)) {
return i;
}
}
return 3;
}
public void setSmileyIcon(String state) {
setIcon(
new ImageIcon(
this.image.getSubimage(0, this.SIZE * this.topMargin(state), this.SIZE, this.SIZE)));
}
@Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
this.setSmileyIcon("smile down");
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
this.setSmileyIcon("smile");
this.board.getMain().newGame();
}
}
@Override
public void mouseExited(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseClicked(MouseEvent e) {}
}

178
src/Storage.java Normal file
View File

@ -0,0 +1,178 @@
import java.io.*;
public class Storage {
private int difficulty;
private int height;
private int width;
private int mines;
private boolean creepy;
private boolean soundEnabled;
private int[] records;
private String[] names;
private final int NUM_OF_DIFFICULTIES = 3;
private final String DATA_FILE = "Minesweeper.dat";
private void open(final String filename) throws FileNotFoundException, IOException {
DataInputStream in = new DataInputStream(new FileInputStream(filename));
this.difficulty = in.readInt();
this.height = in.readInt();
this.width = in.readInt();
this.mines = in.readInt();
this.creepy = in.readBoolean();
this.soundEnabled = in.readBoolean();
for (int i = 0; i < NUM_OF_DIFFICULTIES; i += 1) {
this.records[i] = in.readInt();
this.names[i] = in.readUTF();
}
in.close();
}
public Storage() {
this.records = new int[this.NUM_OF_DIFFICULTIES];
this.names = new String[this.NUM_OF_DIFFICULTIES];
try {
this.open(this.DATA_FILE);
} catch (Exception e) {
this.difficulty = 0;
this.height = 9;
this.width = 9;
this.mines = 10;
this.creepy = false;
this.soundEnabled = false;
this.records = new int[] {999, 999, 999};
this.names = new String[] {"Anonymous", "Anonymous", "Anonymous"};
try {
this.save(this.DATA_FILE);
} catch (Exception v) {
}
}
}
public void save(final String filename) throws FileNotFoundException, IOException {
DataOutputStream out = new DataOutputStream(new FileOutputStream(filename));
out.writeInt(this.difficulty);
out.writeInt(this.height);
out.writeInt(this.width);
out.writeInt(this.mines);
out.writeBoolean(this.creepy);
out.writeBoolean(this.soundEnabled);
for (int i = 0; i < NUM_OF_DIFFICULTIES; i += 1) {
out.writeInt(this.records[i]);
out.writeUTF(this.names[i]);
}
out.close();
}
public int getDifficulty() {
return this.difficulty;
}
public int getHeight() {
return this.height;
}
public int getWidth() {
return this.width;
}
public int getMines() {
return this.mines;
}
public boolean isCreepy() {
return this.creepy;
}
public boolean isSoundEnabled() {
return this.soundEnabled;
}
public int getRecord(int difficulty) {
return this.records[difficulty];
}
public String getName(int difficulty) {
return this.names[difficulty];
}
public void setDifficulty(int difficulty) {
this.difficulty = difficulty;
switch (difficulty) {
case 0:
this.height = 9;
this.width = 9;
this.mines = 10;
break;
case 1:
this.height = 16;
this.width = 16;
this.mines = 40;
break;
case 2:
this.height = 16;
this.width = 30;
this.mines = 99;
}
try {
this.save(this.DATA_FILE);
} catch (Exception e) {
}
}
public void setHeight(int height) {
this.height = height;
try {
this.save(this.DATA_FILE);
} catch (Exception e) {
}
}
public void setWidth(int width) {
this.width = width;
try {
this.save(this.DATA_FILE);
} catch (Exception e) {
}
}
public void setMines(int mines) {
this.mines = mines;
try {
this.save(this.DATA_FILE);
} catch (Exception e) {
}
}
public void switchCreepy() {
this.creepy = !this.creepy;
try {
this.save(this.DATA_FILE);
} catch (Exception e) {
}
}
public void switchSound() {
this.soundEnabled = !this.soundEnabled;
try {
this.save(this.DATA_FILE);
} catch (Exception e) {
}
}
public void setRecord(int difficulty, int score) {
this.records[difficulty] = score;
try {
this.save(this.DATA_FILE);
} catch (Exception e) {
}
}
public void setName(int difficulty, String name) {
this.names[difficulty] = name;
try {
this.save(this.DATA_FILE);
} catch (Exception e) {
}
}
}

199
src/Tile.java Normal file
View File

@ -0,0 +1,199 @@
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class Tile extends JLabel implements MouseListener {
private static final long serialVersionUID = -6873580061751396083L;
private Board board;
private BufferedImage image;
private boolean uncovered;
private boolean flagged;
private boolean unsure;
private boolean dud;
private int row;
private int col;
private int value; // -1~8 (-1: mine)
private final int SIZE = 16;
public Tile(int row, int col, Board board) {
super("", JLabel.CENTER);
this.board = board;
this.image = this.board.getMain().getImage().getTile();
this.uncovered = false;
this.flagged = false;
this.unsure = false;
this.dud = false;
this.row = row;
this.col = col;
this.setValue(0);
this.setTileIcon("covered");
addMouseListener(this);
setVerticalAlignment(JLabel.CENTER);
setHorizontalAlignment(JLabel.CENTER);
setPreferredSize(new Dimension(this.SIZE, this.SIZE));
}
public int topMargin(String state) {
String[] states = {
"covered",
"flagged",
"unsure",
"-1",
"misflagged",
"covered mine",
"unsure down",
"8",
"7",
"6",
"5",
"4",
"3",
"2",
"1",
"0"
};
for (int i = 0; i < states.length; i += 1) {
if (states[i].equals(state)) {
return i;
}
}
return 6;
}
public void setTileIcon(String state) {
setIcon(
new ImageIcon(
this.image.getSubimage(0, this.SIZE * this.topMargin(state), this.SIZE, this.SIZE)));
}
public int getValue() {
return this.value;
}
public boolean isMine() {
return (this.value == -1 || this.dud);
}
public boolean isUncovered() {
return this.uncovered;
}
public boolean isFlagged() {
return this.flagged;
}
public boolean isUnsure() {
return this.unsure;
}
public boolean isDud() {
return this.dud;
}
public void setValue(int value) {
this.value = value;
}
public void setMine() {
this.value = -1;
}
public void setDud() {
this.dud = true;
}
public void uncover(boolean expanding) {
this.uncovered = true;
Minesweeper main = this.board.getMain();
if (this.value >= 0 || this.isDud()) {
if (this.isDud()) {
this.setTileIcon("unsure down");
} else {
this.setTileIcon(String.valueOf(this.value));
}
if (this.value == 0 && !this.isDud()) {
this.board.expand(row, col);
}
if (main.getStorage().isCreepy()
&& !this.isDud()
&& !expanding
&& Math.round((float) Math.random() * 10) == 0) {
main.getPlay().boom();
}
this.board.checkWon(expanding);
} else {
this.setTileIcon("-1");
main.delGame();
}
}
public void uncover() {
this.uncover(false);
}
public void toggleFlagged() {
Info info = this.board.getMain().getInfo();
if (!this.flagged && !this.unsure) {
this.flagged = true;
this.setTileIcon("flagged");
info.getCounter().decrease();
} else if (this.flagged) {
this.flagged = false;
this.unsure = true;
this.setTileIcon("unsure");
info.getCounter().increase();
} else {
this.flagged = false;
this.setTileIcon("covered");
if (!this.unsure) {
info.getCounter().increase();
}
this.unsure = false;
}
}
@Override
public void mousePressed(MouseEvent e) {
Minesweeper main = this.board.getMain();
if (!main.isGameStarted()) {
main.startGame();
main.getInfo().getTimer().start();
if (main.getStorage().isSoundEnabled() && !main.getStorage().isCreepy()) {
main.getPlay().tick();
}
}
if (SwingUtilities.isLeftMouseButton(e) && !this.uncovered && !this.flagged) {
this.setTileIcon("0");
} else if (SwingUtilities.isMiddleMouseButton(e)) {
if (!this.flagged) {
this.setTileIcon("0");
}
main.getInfo().getSmiley().setSmileyIcon("oops");
}
}
@Override
public void mouseReleased(MouseEvent e) {
this.board.getMain().getInfo().getSmiley().setSmileyIcon("smile");
if ((SwingUtilities.isLeftMouseButton(e) || SwingUtilities.isMiddleMouseButton(e))
&& !this.flagged) {
this.uncover();
} else if (SwingUtilities.isRightMouseButton(e) && !this.uncovered) {
this.toggleFlagged();
}
}
@Override
public void mouseExited(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseClicked(MouseEvent e) {}
}

83
src/TimerDisplay.java Normal file
View File

@ -0,0 +1,83 @@
import java.awt.*;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.*;
import javax.swing.BorderFactory;
import javax.swing.plaf.basic.BasicBorders.ButtonBorder;
public class TimerDisplay extends JPanel {
private static final long serialVersionUID = -1661506846325986431L;
private Board board;
private Timer timer;
private Digit hundred;
private Digit tenth;
private Digit first;
private int seconds;
private boolean stopped;
public TimerDisplay(Board board) {
this.board = board;
this.hundred = new Digit(this.board);
this.tenth = new Digit(this.board);
this.first = new Digit(this.board);
setBackground(new Color(192, 192, 192));
setBorder(
BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(5, 5, 5, 5),
new ButtonBorder(
Color.white, Color.white, new Color(128, 128, 128), new Color(128, 128, 128))));
setVisible(false);
setLayout(new GridLayout(1, 3, 0, 0));
removeAll();
add(this.hundred);
add(this.tenth);
add(this.first);
setVisible(true);
}
public int getTime() {
return this.seconds;
}
public void start() {
this.stopped = false;
this.timer = new Timer();
TimerTask tt =
new TimerTask() {
@Override
public void run() {
increase();
}
};
this.timer.scheduleAtFixedRate(tt, 0, 1000);
}
public void stop() {
if (!this.stopped) {
this.timer.cancel();
this.stopped = true;
}
}
public void increase() {
this.seconds += 1;
if (this.seconds >= 999) {
this.stop();
}
this.updateDisplay();
}
public void reset() {
this.seconds = 0;
this.updateDisplay();
}
public void updateDisplay() {
this.hundred.setDigitIcon(String.valueOf(seconds / 100));
this.tenth.setDigitIcon(String.valueOf(seconds / 10 % 10));
this.first.setDigitIcon(String.valueOf(seconds % 10));
}
}