开始游戏之前,设定的固定大小的雷区(如8×8大小),设置雷数量。游戏开始后点开的第一个方块以及第一个方格周围的8个方格内不会有雷。
点开的方格中的数字表示该方块周围的8个方块中的雷的数量。玩家根据这些方块的数字扫除表格中的所有地雷,如果点开的方块是地雷则游戏失败,如果所有的非地雷方块被点开则游戏胜利。
2.1 地雷初始化
根据玩家设置初始化雷区大小,当玩家点开第一个雷后,在除第一个点击方块为中心的9个方块以外随机生成地雷。
2.2 打开方块
2.2.1 左键单击方块
当左键单击的方块未被标记成地雷时,打开该方块。如果手动或自动打开的方块周围8个方块无雷,则自动打开周围的8个方块,否则打开的方块显示周围地雷的数量。
2.2.2 多个键单击
当超过一个鼠标就键单击已经打开的方块时,如果该方块周围的地雷被标记数量等于周围的地雷数量,打开其余所有未被标记的方块。
2.3 标记
鼠标右键点击方块标记成地雷,再次点击标记成无法确定,再次点击恢复未标记状态。
2.4 游戏进度
在游戏过程中,一旦点击的方块是地雷,游戏失败,当所有非地雷方块被打开,游戏胜利。
最终决定使用JavaFX开发
JavaFX是SUN公司在2007年JavaOne大会上首次对外公布的以Java为基础构建的富客户端平台。
优点:多平台兼容;fxml文件简化了界面设计,SceneBuilder插件用于快捷开发图形界面。
缺点:比较冷门,文档较少。
最终游戏界面如下图所示。
下面贴上核心类,整个项目采用Idea开发,gradle管理,项目已上传至github https://github.com/Yuriey1994/Java/tree/SourceCode/MineSweeper
package org.yuriey.minesweeper;
import org.yuriey.minesweeper.listener.SweepListener;
import org.yuriey.minesweeper.model.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MineSweeper {
private Cube[][] cubes;
private GameMode mode;
private boolean failed;
private boolean finished;
private int sweptCount = 0;
private boolean generated;
private Integer usedSeconds;
private long gameStartTimeMills = -1;
private static MineSweeper singletonMineSweeper;
public boolean isFailed() {
return failed;
}
public boolean isFinished() {
return finished;
}
public static MineSweeper getSingletonMineSweeper() {
if (singletonMineSweeper == null) {
synchronized (MineSweeper.class) {
if(singletonMineSweeper == null) {
singletonMineSweeper = new MineSweeper(new EasyMode());
}
}
}
return singletonMineSweeper;
}
public MineSweeper reset(){
failed =false;
finished = false;
usedSeconds = 0;
sweptCount = 0;
gameStartTimeMills = -1;
for (int i = 0; i < cubes.length; i++) {
for (int j = 0; j < cubes[i].length; j++) {
cubes[i][j].setState(CubeState.INITIAL);
}
}
return this;
}
public MineSweeper newGame(){
cubes = new Cube[mode.getRowCount()][mode.getColumnCount()];
failed = false;
finished = false;
usedSeconds = 0;
sweptCount = 0;
gameStartTimeMills = -1;
this.generated = false;
for (int i = 0; i < mode.getRowCount(); i++) {
for (int j = 0; j < mode.getColumnCount(); j++) {
cubes[i][j] = new Cube(CubeState.INITIAL, false, 0, new CubePosition(i, j));
}
}
return this;
}
public MineSweeper newGame(GameMode mode) {
this.mode = mode;
return newGame();
}
public Integer getUsedSeconds() {
if (failed || finished) return usedSeconds;
usedSeconds = (gameStartTimeMills == -1 ? 0 : ((int) ((System.currentTimeMillis() - gameStartTimeMills) / 1000)));
return usedSeconds;
}
private SweepListener listener = new SweepListener() {
@Override
public void sweeping(Cube cube) {
System.out.println("sweeping pos=" + cube.getPosition());
}
@Override
public void swept(Cube cube) {
System.out.println("swept pos=" + cube.getPosition());
}
@Override
public void failed(Cube explodedCube) {
System.out.println("failed exploded cube=" + explodedCube.getPosition());
}
@Override
public void finished() {
System.out.println("finished!");
}
@Override
public void onStateChange(Cube cube, CubeState oldState, CubeState newState) {
}
};
public boolean isMine(int rowIndex, int colIndex) {
return getCube(rowIndex, colIndex) == null ? false : getCube(rowIndex, colIndex).isMine();
}
public int getMarkedMineCount() {
int count = 0;
for (int i = 0; i < mode.getRowCount(); i++) {
for (int j = 0; j < mode.getColumnCount(); j++) {
if (cubes[i][j].getState() == CubeState.MARK_MINE) count++;
}
}
return count;
}
public CubeState getState(int rowIndex, int colIndex) {
return isPositionValid(rowIndex, colIndex) ? cubes[rowIndex][colIndex].getState() : null;
}
public MineSweeper(GameMode mode) {
this.mode = mode;
newGame();
}
public void setSweepListener(SweepListener listener) {
this.listener = listener;
}
private void generateMines(int rowIndex, int colIndex) {
System.out.println("generate mines");
if (sweptCount > 0 || failed || generated) return;
List generatedCubes = new ArrayList<>();
Random random = new Random(System.currentTimeMillis());
int generateCount = 0;
for (int row = 0; row < mode.getRowCount(); row++) {
for (int col = 0; col < mode.getColumnCount(); col++) {
//if()
if (Math.abs(row - rowIndex) > 1 || Math.abs(col - colIndex) > 1) {//exclusive the first swept around cubes
generatedCubes.add(getCube(row, col));
}
}
}
while (generateCount++ < mode.getMineCount()) {
int index = random.nextInt(generatedCubes.size());
Cube mine = generatedCubes.remove(index);
cubes[mine.getPosition().getRowIndex()][mine.getPosition().getColIndex()].setMine(true);
}
for (int i = 0; i < mode.getRowCount(); i++) {
for (int j = 0; j < mode.getColumnCount(); j++) {
cubes[i][j].setMinesCountAround(getMinesCountAround(i, j));
}
}
generated = true;
}
void generateMines(CubePosition pos) {
generateMines(pos.getRowIndex(), pos.getColIndex());
}
public int getMinesCountAround(CubePosition pos) {
return getMinesCountAround(pos.getRowIndex(), pos.getColIndex());
}
public int getMinesCountAround(int rowIndex, int colIndex) {
if (!isPositionValid(rowIndex, colIndex)) return -1;
int count = 0;
for (int row = rowIndex - 1; row <= rowIndex + 1; row++) {
for (int col = colIndex - 1; col <= colIndex + 1; col++) {
if (isPositionValid(row, col) && cubes[row][col].isMine()) count++;
}
}
return count;
}
public boolean isSwept(int rowIndex, int colIndex) {
if (!isPositionValid(rowIndex, colIndex) || !generated) return false;
return cubes[rowIndex][colIndex].getState() == CubeState.EXCLUSIVE;
}
public void sweep(int rowIndex, int colIndex) {
if (!isPositionValid(rowIndex, colIndex) || failed) return;
if(gameStartTimeMills == -1)gameStartTimeMills = System.currentTimeMillis();
if (!generated) {
generateMines(rowIndex, colIndex);
}
Cube cube = getCube(rowIndex, colIndex);
listener.sweeping(cube);
switch (cube.getState()) {
case INITIAL:
cube.setState(cube.isMine() ? CubeState.EXPLODED : CubeState.EXCLUSIVE);
listener.swept(cube);
if (cube.isMine()) {
failed = true;
listener.failed(cube);
} else {
sweptCount++;
if (sweptCount >= (mode.getColumnCount() * mode.getRowCount() - mode.getMineCount())) {
finished = true;
listener.finished();
}
if (cube.getMinesCountAround() == 0) {
sweepAround(rowIndex, colIndex);
}
}
break;
}
}
void sweep(CubePosition pos) {
sweep(pos.getRowIndex(), pos.getColIndex());
}
public void sweepAround(CubePosition pos) {
sweepAround(pos.getRowIndex(), pos.getColIndex());
}
public void sweepAround(int row, int col) {
System.out.println("sweepAround");
if (!isPositionValid(row, col) || getCube(row, col).isMine()) return;
Cube cube = getCube(row, col);
for (int r = row - 1; r <= row + 1; r++) {
for (int c = col - 1; c <= col + 1; c++) {
if (isPositionValid(r, c)) sweep(r, c);
}
}
}
public void checkMarkedMineAndSweepAround(int row, int col) {
if (!isPositionValid(row, col) || getCube(row, col).isMine() || getCube(row, col).getState() != CubeState.EXCLUSIVE)
return;
Cube cube = getCube(row, col);
if (cube.getMinesCountAround() == getMarkedMineCountAround(cube.getPosition())) {
for (int r = row - 1; r <= row + 1; r++) {
for (int c = col - 1; c <= col + 1; c++) {
if (isPositionValid(r, c)) sweep(r, c);
}
}
}
}
public void setState(int rowIndex, int colIndex, CubeState state) {
if (!isPositionValid(rowIndex, colIndex)) return;
cubes[rowIndex][colIndex].setState(state);
}
private boolean isPositionValid(CubePosition pos) {
return isPositionValid(pos.getColIndex(), pos.getColIndex());
}
private boolean isPositionValid(int rowIndex, int colIndex) {
return rowIndex < mode.getRowCount() && colIndex < mode.getColumnCount() && rowIndex >= 0 && colIndex >= 0;
}
private Cube getCube(CubePosition pos) {
return getCube(pos.getRowIndex(), pos.getColIndex());
}
private Cube getCube(int rowIndex, int colIndex) {
return isPositionValid(rowIndex, colIndex) ? cubes[rowIndex][colIndex] : null;
}
private int getMarkedMineCountAround(CubePosition position) {
return getMarkedMineCountAround(position.getRowIndex(), position.getColIndex());
}
private int getMarkedMineCountAround(int rowIndex, int colIndex) {
if (!isPositionValid(rowIndex, colIndex)) return -1;
int count = 0;
for (int row = rowIndex - 1; row <= rowIndex + 1; row++) {
for (int col = colIndex - 1; col <= colIndex + 1; col++) {
if (isPositionValid(row, col) && cubes[row][col].getState() == CubeState.MARK_MINE) count++;
}
}
return count;
}
public void print() {
System.out.println("print cubes:");
for (int rowIndex = 0; rowIndex < cubes.length; rowIndex++) {
for (int colIndex = 0; colIndex < cubes[rowIndex].length; colIndex++) {
if (cubes[rowIndex][colIndex].isMine() && cubes[rowIndex][colIndex].getState() == CubeState.EXPLODED)
System.out.print(" # ");
else if (cubes[rowIndex][colIndex].isMine())
System.out.print(" * ");
else if (!cubes[rowIndex][colIndex].isMine())
System.out.print((cubes[rowIndex][colIndex].getState() == CubeState.EXCLUSIVE ? "|" : " ") + cubes[rowIndex][colIndex].getMinesCountAround() + (cubes[rowIndex][colIndex].getState() == CubeState.EXCLUSIVE ? "|" : " "));
}
System.out.println();
}
}
}