rename references
This commit is contained in:
6
references/Minesweeper/MinesweeperGameState/.classpath
Normal file
6
references/Minesweeper/MinesweeperGameState/.classpath
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
1
references/Minesweeper/MinesweeperGameState/.gitignore
vendored
Normal file
1
references/Minesweeper/MinesweeperGameState/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/bin/
|
17
references/Minesweeper/MinesweeperGameState/.project
Normal file
17
references/Minesweeper/MinesweeperGameState/.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>MinesweeperGameState</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -0,0 +1,11 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
@ -0,0 +1,42 @@
|
||||
package minesweeper.gamestate;
|
||||
|
||||
import minesweeper.settings.GameSettings;
|
||||
import minesweeper.settings.GameType;
|
||||
|
||||
public class GameFactory {
|
||||
|
||||
private GameFactory() {
|
||||
|
||||
}
|
||||
|
||||
public static GameStateModelViewer create(GameType type, GameSettings settings, long gameSeed) {
|
||||
|
||||
GameStateModelViewer gsm;
|
||||
|
||||
if (type == GameType.EASY) {
|
||||
if (gameSeed != 0) {
|
||||
gsm = new GameStateEasy(settings, gameSeed);
|
||||
} else {
|
||||
gsm = new GameStateEasy(settings);
|
||||
}
|
||||
} else if (type == GameType.STANDARD) {
|
||||
if (gameSeed != 0) {
|
||||
gsm = new GameStateStandard(settings, gameSeed);
|
||||
} else {
|
||||
gsm = new GameStateStandard(settings);
|
||||
}
|
||||
} else if (type == GameType.HARD) {
|
||||
if (gameSeed != 0) {
|
||||
gsm = new GameStateHard(settings, gameSeed);
|
||||
} else {
|
||||
gsm = new GameStateHard(settings);
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected values in Game generation");
|
||||
}
|
||||
|
||||
return gsm;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,270 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package minesweeper.gamestate;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import minesweeper.random.DefaultRNG;
|
||||
import minesweeper.random.RNG;
|
||||
import minesweeper.settings.GameSettings;
|
||||
import minesweeper.structure.Location;
|
||||
|
||||
/**
|
||||
* A Version of Minesweeper which ensures the first click does not have any mines surrounding it
|
||||
* @author David
|
||||
*/
|
||||
public class GameStateEasy extends GameStateModelViewer {
|
||||
|
||||
private final int[][] board;
|
||||
|
||||
private RNG rng;
|
||||
|
||||
//private long seed;
|
||||
|
||||
public GameStateEasy(GameSettings gameSettings) {
|
||||
this(gameSettings, new Random().nextLong());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public GameStateEasy(GameSettings gameSettings, long seed) {
|
||||
super(gameSettings, seed);
|
||||
|
||||
this.board = new int[width][height];
|
||||
|
||||
this.rng = DefaultRNG.getRNG(seed);
|
||||
|
||||
}
|
||||
|
||||
// in this gamestate we are building the board ourselves
|
||||
@Override
|
||||
protected void startHandle(Location m) {
|
||||
|
||||
|
||||
int adjacent = 9;
|
||||
// corners
|
||||
if (m.x == 0 && m.y == 0 || m.x == 0 && m.y == this.height - 1 || m.x == this.width - 1 && m.y == this.height - 1 || m.x == this.width - 1 && m.y == this.height - 1) {
|
||||
adjacent = 4;
|
||||
// the edge
|
||||
} else if (m.x == 0 || m.y == 0 || m.x == this.width - 1 || m.y == this.height - 1){
|
||||
adjacent = 6;
|
||||
}
|
||||
|
||||
|
||||
if (this.mines + adjacent > this.width * this.height) {
|
||||
this.mines = this.width * this.height - adjacent;
|
||||
}
|
||||
|
||||
// create the tiles
|
||||
Integer[] indices = new Integer[this.width * this.height - adjacent];
|
||||
|
||||
// find all the non-mine tile left
|
||||
int index = 0;
|
||||
for (int y=0; y < this.height; y++) {
|
||||
for (int x=0; x < this.width; x++) {
|
||||
|
||||
if (Math.abs(x - m.x) > 1 || Math.abs(y - m.y) > 1) {
|
||||
int tile = y * this.width + x;
|
||||
indices[index++] = tile;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
shuffle(indices, rng);
|
||||
|
||||
// allocate the bombs and calculate the values
|
||||
for (int i = 0; i < this.mines; i++) {
|
||||
int tile = indices[i];
|
||||
|
||||
int x = tile % this.width;
|
||||
int y = tile / this.width;
|
||||
|
||||
board[x][y] = GameStateModel.MINE;
|
||||
|
||||
// tell all the surrounding squares they are next to a mine
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (x + DX[j] >= 0 && x + DX[j] < this.width && y + DY[j] >= 0 && y + DY[j] < this.height) {
|
||||
if (board[x+DX[j]][y+DY[j]] != GameStateModel.MINE) {
|
||||
board[x+DX[j]][y+DY[j]]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
int i=0;
|
||||
while (i < mines) {
|
||||
int y1 = (int) rng.random(this.height);
|
||||
int x1 = (int) rng.random(this.width);
|
||||
Location l1 = new Location(x1, y1);
|
||||
|
||||
// if the location is NOT the first square pressed or surrounding it
|
||||
// and the location is not already a mine then place a mine here
|
||||
if (!l1.isAdjacent(m) && !l1.equals(m)) {
|
||||
if (board[x1][y1] != GameStateModel.MINE) {
|
||||
//System.out.println("Mine added at " + x1 + "," + y1);
|
||||
board[x1][y1] = GameStateModel.MINE;
|
||||
i++;
|
||||
|
||||
// tell all the surrounding squares they are next to a mine
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (x1 + DX[j] >= 0 && x1 + DX[j] < this.width && y1 + DY[j] >= 0 && y1 + DY[j] < this.height) {
|
||||
if (board[x1+DX[j]][y1+DY[j]] != GameStateModel.MINE) {
|
||||
board[x1+DX[j]][y1+DY[j]]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// in this gamestate there is nothing to do
|
||||
@Override
|
||||
protected void placeFlagHandle(Location m) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected int queryHandle(int x, int y) {
|
||||
return board[x][y];
|
||||
}
|
||||
|
||||
|
||||
// in this gamestate we need to expand the clear if no mines are adjacent
|
||||
@Override
|
||||
protected boolean clearSquareHitMine(Location m) {
|
||||
|
||||
// if there are no mines next to this location expand reveal
|
||||
if (board[m.x][m.y] == 0) {
|
||||
explode(m);
|
||||
//clearSurround(m);
|
||||
}
|
||||
|
||||
if (board[m.x][m.y] == GameStateModel.MINE) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
//return board[m.x][m.y];
|
||||
|
||||
}
|
||||
@Override
|
||||
public int privilegedQuery(Location m, boolean showMines) {
|
||||
|
||||
int result = query(m);
|
||||
if (result == GameStateModel.MINE) { // if we can see a mine using query it must be exploded
|
||||
return GameStateModel.EXPLODED_MINE;
|
||||
}
|
||||
|
||||
if (showMines && result == GameStateModel.HIDDEN && board[m.x][m.y] == GameStateModel.MINE) {
|
||||
result = GameStateModel.MINE;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String showGameKey() {
|
||||
|
||||
return "Seed = " + seed + "(" + rng.shortname() + ")";
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
private void explode(Location loc) {
|
||||
|
||||
boolean[][] done = new boolean[width][height];
|
||||
|
||||
List<Location> interiorList = new ArrayList<>();
|
||||
|
||||
// add this location to the interior array list
|
||||
done[loc.x][loc.y] = true;
|
||||
interiorList.add(loc);
|
||||
|
||||
int processFrom = 0;
|
||||
|
||||
while (processFrom < interiorList.size()) {
|
||||
|
||||
// get the current location to process surrounding squares
|
||||
Location cl = interiorList.get(processFrom);
|
||||
|
||||
for (int i=0; i < DX.length; i++) {
|
||||
|
||||
int x1 = cl.x + DX[i];
|
||||
int y1 = cl.y + DY[i];
|
||||
|
||||
// check each of the surrounding squares which haven't already been checked
|
||||
if (x1 >= 0 && x1 < width && y1 >= 0 && y1 < height) {
|
||||
if (!done[x1][y1] && query(new Location(x1, y1)) == GameStateModel.HIDDEN) {
|
||||
|
||||
done[x1][y1] = true;
|
||||
setRevealed(x1,y1);
|
||||
|
||||
// if this square is also a zero then add it to the list of locations to be exploded
|
||||
if (board[x1][y1] == 0) {
|
||||
interiorList.add(new Location(x1, y1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
processFrom++;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
protected boolean clearSurroundHandle(Location m) {
|
||||
|
||||
// otherwise, clear around this revealed square
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (m.x + DX[j] >= 0 && m.x + DX[j] < this.width && m.y + DY[j] >= 0 && m.y + DY[j] < this.height) {
|
||||
clearSquare(new Location(m.x+DX[j], m.y+DY[j]));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports3BV() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location getStartLocation() {
|
||||
//if (x > 7 && y > 7) {
|
||||
// return new Location(3,3);
|
||||
//}
|
||||
|
||||
if (width == 8 && height == 8) {
|
||||
return new Location(2,2);
|
||||
}
|
||||
|
||||
if (width == 9 && height == 9) {
|
||||
return new Location(2,2);
|
||||
}
|
||||
|
||||
if (width == 16 && height == 16) {
|
||||
return new Location(2,2);
|
||||
}
|
||||
|
||||
//return new Location(x/2, y/2);
|
||||
return new Location(Math.min(3, width/2), Math.min(3, height/2));
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package minesweeper.gamestate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import minesweeper.random.DefaultRNG;
|
||||
import minesweeper.random.RNG;
|
||||
import minesweeper.settings.GameSettings;
|
||||
import minesweeper.structure.Location;
|
||||
|
||||
/**
|
||||
* A Version of Minesweeper where the first click can be a mine
|
||||
* @author David
|
||||
*/
|
||||
public class GameStateHard extends GameStateModelViewer {
|
||||
|
||||
private final int[][] board;
|
||||
|
||||
private RNG rng;
|
||||
|
||||
public GameStateHard(GameSettings gameSettings) {
|
||||
this(gameSettings, new Random().nextLong());
|
||||
}
|
||||
|
||||
|
||||
public GameStateHard(GameSettings gameSettings, long seed) {
|
||||
super(gameSettings, seed);
|
||||
|
||||
this.board = new int[width][height];
|
||||
|
||||
this.rng = DefaultRNG.getRNG(seed);
|
||||
}
|
||||
|
||||
// in this gamestate we are building the board ourselves
|
||||
@Override
|
||||
protected void startHandle(Location m) {
|
||||
|
||||
int i=0;
|
||||
|
||||
while (i < mines) {
|
||||
int y1 = (int) rng.random(this.height);
|
||||
int x1 = (int) rng.random(this.width);
|
||||
Location l1 = new Location(x1, y1);
|
||||
|
||||
if (board[x1][y1] != GameStateModel.MINE) {
|
||||
//System.out.println("Mine added at " + x1 + "," + y1);
|
||||
board[x1][y1] = GameStateModel.MINE;
|
||||
i++;
|
||||
|
||||
// tell all the surrounding squares they are next to a mine
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (x1 + DX[j] >= 0 && x1 + DX[j] < this.width && y1 + DY[j] >= 0 && y1 + DY[j] < this.height) {
|
||||
if (board[x1+DX[j]][y1+DY[j]] != GameStateModel.MINE) {
|
||||
board[x1+DX[j]][y1+DY[j]]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// in this gamestate there is nothing to do
|
||||
@Override
|
||||
protected void placeFlagHandle(Location m) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int queryHandle(int x, int y) {
|
||||
return board[x][y];
|
||||
}
|
||||
|
||||
|
||||
// in this gamestate we need to expand the clear if no mines are adjacent
|
||||
@Override
|
||||
protected boolean clearSquareHitMine(Location m) {
|
||||
|
||||
// if there are no mines next to this location expand reveal
|
||||
if (board[m.x][m.y] == 0) {
|
||||
explode(m);
|
||||
//clearSurround(m);
|
||||
}
|
||||
|
||||
if (board[m.x][m.y] == GameStateModel.MINE) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
//return board[m.x][m.y];
|
||||
|
||||
}
|
||||
@Override
|
||||
public int privilegedQuery(Location m, boolean showMines) {
|
||||
|
||||
int result = query(m);
|
||||
if (result == GameStateModel.MINE) { // if we can see a mine using query it must be exploded
|
||||
return GameStateModel.EXPLODED_MINE;
|
||||
}
|
||||
|
||||
if (showMines && result == GameStateModel.HIDDEN && board[m.x][m.y] == GameStateModel.MINE) {
|
||||
result = GameStateModel.MINE;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String showGameKey() {
|
||||
|
||||
return "Seed = " + seed + " (" + rng.shortname() + ")";
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
private void explode(Location loc) {
|
||||
|
||||
boolean[][] done = new boolean[width][height];
|
||||
|
||||
List<Location> interiorList = new ArrayList<>();
|
||||
|
||||
// add this location to the interior array list
|
||||
done[loc.x][loc.y] = true;
|
||||
interiorList.add(loc);
|
||||
|
||||
int processFrom = 0;
|
||||
|
||||
while (processFrom < interiorList.size()) {
|
||||
|
||||
// get the current location to process surrounding squares
|
||||
Location cl = interiorList.get(processFrom);
|
||||
|
||||
for (int i=0; i < DX.length; i++) {
|
||||
|
||||
int x1 = cl.x + DX[i];
|
||||
int y1 = cl.y + DY[i];
|
||||
|
||||
// check each of the surrounding squares which haven't already been checked
|
||||
if (x1 >= 0 && x1 < width && y1 >= 0 && y1 < height) {
|
||||
|
||||
Location l = new Location(x1, y1);
|
||||
|
||||
if (!done[x1][y1] && query(l) == GameStateModel.HIDDEN) {
|
||||
|
||||
done[x1][y1] = true;
|
||||
setRevealed(x1,y1);
|
||||
|
||||
// if this square is also a zero then add it to the list of locations to be exploded
|
||||
if (query(l) == 0) {
|
||||
interiorList.add(new Location(x1, y1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
processFrom++;
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean supports3BV() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean clearSurroundHandle(Location m) {
|
||||
|
||||
// otherwise, clear around this revealed square
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (m.x + DX[j] >= 0 && m.x + DX[j] < this.width && m.y + DY[j] >= 0 && m.y + DY[j] < this.height) {
|
||||
clearSquare(new Location(m.x+DX[j], m.y+DY[j]));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean safeOpening() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,732 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package minesweeper.gamestate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import minesweeper.random.RNG;
|
||||
import minesweeper.settings.GameSettings;
|
||||
import minesweeper.structure.Action;
|
||||
import minesweeper.structure.Location;
|
||||
|
||||
/**
|
||||
* An abstract representation of a game of Minesweeper.
|
||||
* <p>Allows a player to query squares to discover if they are HIDDEN, FLAGGED or how many mines surround them.</p>
|
||||
* <p>Supports placing flags, removing flags and clearing squares</p>
|
||||
*
|
||||
* @author David
|
||||
*/
|
||||
abstract public class GameStateModel {
|
||||
|
||||
public final static int HIDDEN = -11;
|
||||
public final static int MINE = -10;
|
||||
public final static int FLAG = -12;
|
||||
public final static int EXPLODED_MINE = - 13;
|
||||
public final static int BAD_FLAG = - 14;
|
||||
|
||||
public final static int NOT_STARTED = 0;
|
||||
public final static int STARTED = 1;
|
||||
public final static int LOST = 2;
|
||||
public final static int WON = 3;
|
||||
|
||||
protected final static int[] DX = {0, 1, 1, 1, 0, -1, -1, -1};
|
||||
protected final static int[] DY = {-1, -1, 0, 1, 1, 1, 0, -1};
|
||||
|
||||
private long startTS = 0;
|
||||
private long finishTS = 0;
|
||||
|
||||
protected final int width;
|
||||
protected final int height;
|
||||
protected int mines;
|
||||
protected final long seed;
|
||||
|
||||
private int total3BV;
|
||||
private int cleared3BV;
|
||||
private int actionCount = 0;
|
||||
|
||||
// how many tiles with values 0 - 8 are on the board
|
||||
private int[] values = new int[9];
|
||||
|
||||
protected boolean allowEarlyFinish = true;
|
||||
|
||||
/**
|
||||
* If this is true then the solver will place the remaining flags when the number of free squares = number of mines left
|
||||
*/
|
||||
protected boolean doAutoComplete = true;
|
||||
|
||||
protected boolean partialGame = false;
|
||||
|
||||
private int gameState = NOT_STARTED;
|
||||
|
||||
private int squaresRevealed = 0;
|
||||
private int flagsPlaced = 0;
|
||||
|
||||
|
||||
private final boolean[][] flag;
|
||||
private final boolean[][] revealed;
|
||||
private final boolean[][] is3BV;
|
||||
|
||||
public GameStateModel(GameSettings gameSettings) {
|
||||
this(gameSettings, 0);
|
||||
}
|
||||
|
||||
public GameStateModel(GameSettings gameSettings, long seed) {
|
||||
|
||||
this.width = gameSettings.width;
|
||||
this.height = gameSettings.height;
|
||||
this.mines = gameSettings.mines;
|
||||
this.seed = seed;
|
||||
|
||||
flag = new boolean[width][height];
|
||||
|
||||
revealed = new boolean[width][height];
|
||||
|
||||
is3BV = new boolean[width][height];
|
||||
|
||||
this.gameState = NOT_STARTED;
|
||||
|
||||
}
|
||||
|
||||
public boolean doAction(Action a) {
|
||||
|
||||
boolean result;
|
||||
|
||||
if (a.getAction() == Action.CLEAR) {
|
||||
result = clearSquare(a);
|
||||
} else if (a.getAction() == Action.FLAG) {
|
||||
result = placeFlag(a);
|
||||
} else if (a.getAction() == Action.CLEARALL) {
|
||||
result = clearSurround(a);
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
actionCount++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Action> identifyLocations(Collection<? extends Location> locs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void resign() {
|
||||
finish(GameStateModel.LOST);
|
||||
}
|
||||
|
||||
// returns false if the move is not allowed
|
||||
private boolean placeFlag(Location m) {
|
||||
|
||||
// if the game is finished then nothing to do
|
||||
if (gameState > STARTED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the location is already revealed then nothing more to do
|
||||
if (query(m) != GameStateModel.HIDDEN && query(m) != GameStateModel.FLAG) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise toggle the flag
|
||||
flag[m.x][m.y] = !flag[m.x][m.y];
|
||||
|
||||
if (flag[m.x][m.y]) {
|
||||
|
||||
//if (board[m.x][m.y] != GameState.MINE) {
|
||||
// System.out.println("DEBUG (" + m.x + "," + m.y + ") is not a mine!");
|
||||
//}
|
||||
flagsPlaced++;
|
||||
} else {
|
||||
flagsPlaced--;
|
||||
}
|
||||
|
||||
// call this handle to allow extra logic to be added by the extending class
|
||||
placeFlagHandle(m);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* to be overridden
|
||||
* @param m
|
||||
*/
|
||||
abstract protected void placeFlagHandle(Location m);
|
||||
|
||||
|
||||
// returns false if the move is not allowed
|
||||
protected boolean clearSquare(Location m) {
|
||||
|
||||
// if the game is not complete then don't allow clicks (a partial game is one loaded for analysis which doesn't contain all the mines)
|
||||
if (partialGame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// if the game is finished then nothing to do
|
||||
if (gameState > GameStateModel.STARTED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// can't reveal a location with a flag on it
|
||||
if (flag[m.x][m.y]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the game isn't started yet, then start it
|
||||
if (gameState == GameStateModel.NOT_STARTED) {
|
||||
start(m);
|
||||
}
|
||||
|
||||
// if the location is already revealed then nothing more to do
|
||||
if (query(m) != GameStateModel.HIDDEN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
// if the location is already revealed then nothing more to do
|
||||
if (revealed[m.x][m.y]) {
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
boolean mine = clearSquareHitMine(m);
|
||||
|
||||
// if we have revealed a mine we have lost
|
||||
if (mine) {
|
||||
finish(GameStateModel.LOST);
|
||||
return true;
|
||||
}
|
||||
|
||||
// if it wasn't a mine we have revealed one more square
|
||||
if (!revealed[m.x][m.y]) {
|
||||
revealed[m.x][m.y] = true;
|
||||
squaresRevealed++;
|
||||
|
||||
if (is3BV[m.x][m.y]) { // if this was a 3BV tile then we've cleared one more 3BV in this game
|
||||
cleared3BV++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if we have revealed enough locations without hitting a mine
|
||||
// we have won
|
||||
if (squaresRevealed == this.width * this.height - this.mines) {
|
||||
finishFlags();
|
||||
finish(GameStateModel.WON);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we have won and performs any necessary actions
|
||||
* @return
|
||||
*/
|
||||
protected boolean checkForWin() {
|
||||
|
||||
if (squaresRevealed == this.width * this.height - this.mines) {
|
||||
finishFlags();
|
||||
finish(GameStateModel.WON);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* to be overridden - returns true if the clear hits a mine
|
||||
* @param m
|
||||
* @return
|
||||
*/
|
||||
abstract protected boolean clearSquareHitMine(Location m);
|
||||
|
||||
|
||||
// return false if the move is not allowed
|
||||
protected boolean clearSurround(Location m) {
|
||||
|
||||
// if the game is not complete then don't allow clicks (a partial game is one loaded for analysis which doesn't contain all the mines)
|
||||
if (partialGame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the game is finished then nothing to do
|
||||
if (gameState > GameStateModel.STARTED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// can't clear around a flag
|
||||
if (flag[m.x][m.y]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the square isn't revealed then we can't clear around it
|
||||
if (query(m) == GameStateModel.HIDDEN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the number of flags is not complete then we can't clear
|
||||
if (countFlags(m.x, m.y) != query(m)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
clearSurroundHandle(m);
|
||||
|
||||
/*
|
||||
// otherwise, clear around this revealed square
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (m.x + DX[j] >= 0 && m.x + DX[j] < this.x && m.y + DY[j] >= 0 && m.y + DY[j] < this.y) {
|
||||
clearSquare(new Location(m.x+DX[j], m.y+DY[j]));
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* to be overridden - determines how a clear surround should be handled
|
||||
* @param m
|
||||
* @return
|
||||
*/
|
||||
abstract protected boolean clearSurroundHandle(Location m);
|
||||
|
||||
|
||||
// count how many flags are placed around this square
|
||||
private int countFlags(int x, int y) {
|
||||
|
||||
int result = 0;
|
||||
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (x + DX[j] >= 0 && x + DX[j] < this.width && y + DY[j] >= 0 && y + DY[j] < this.height) {
|
||||
if (flag[x+DX[j]][y+DY[j]]) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// put flags in the remaining squares if required
|
||||
protected void finishFlags() {
|
||||
|
||||
if (!doAutoComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i=0; i < width; i++) {
|
||||
for (int j=0; j < height; j++) {
|
||||
Location l = new Location(i,j);
|
||||
if (query(l) == GameStateModel.HIDDEN) {
|
||||
placeFlag(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// set up the board with the mines
|
||||
protected void start(Location m) {
|
||||
|
||||
gameState = GameStateModel.STARTED;
|
||||
|
||||
startHandle(m);
|
||||
|
||||
if (supports3BV()) {
|
||||
calculate3BV();
|
||||
}
|
||||
|
||||
startTS = System.currentTimeMillis();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* to be overridden - allows the board to be set-up on first clear click
|
||||
*/
|
||||
abstract protected void startHandle(Location m);
|
||||
|
||||
|
||||
private void finish(int outcome) {
|
||||
|
||||
finishTS = System.currentTimeMillis();
|
||||
|
||||
gameState = outcome;
|
||||
|
||||
}
|
||||
|
||||
// returns the board value at this location
|
||||
final public int query(Location m) {
|
||||
|
||||
if (!revealed[m.x][m.y]) {
|
||||
if (flag[m.x][m.y]) {
|
||||
return GameStateModel.FLAG;
|
||||
} else {
|
||||
return GameStateModel.HIDDEN;
|
||||
}
|
||||
}
|
||||
|
||||
return queryHandle(m);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* to be overridden - returns the number of mines around the square at this location.
|
||||
*/
|
||||
final protected int queryHandle(Location m) {
|
||||
return queryHandle(m.x, m.y);
|
||||
};
|
||||
|
||||
abstract protected int queryHandle(int x, int y);
|
||||
|
||||
// allows a sub class to check what has been opened
|
||||
protected boolean isHidden(int x, int y) {
|
||||
return !revealed[x][y] && !flag[x][y];
|
||||
}
|
||||
|
||||
|
||||
// allows the sub class to confirm what has been revealed to it
|
||||
protected void setRevealed(int x, int y) {
|
||||
|
||||
if (!revealed[x][y]) {
|
||||
//System.out.println("Auto Reveal at (" + x + "," + y + ")");
|
||||
revealed[x][y] = true;
|
||||
squaresRevealed++;
|
||||
|
||||
if (is3BV[x][y]) { // if this was a 3BV tile then we've cleared one more 3BV in this game
|
||||
cleared3BV++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// allows the sub class to set a square as hidden again (this is useful when moves are being echoed to an external
|
||||
// board like minesweeper X and it is noticed that for some reason the clear hasn't been honoured by the external board)
|
||||
protected void setHidden(int x, int y) {
|
||||
|
||||
if (revealed[x][y]) {
|
||||
//System.out.println("Auto Reveal at (" + x + "," + y + ")");
|
||||
revealed[x][y] = false;
|
||||
squaresRevealed--;
|
||||
|
||||
if (is3BV[x][y]) { // if this was a 3BV tile then ew've no longer cleared it
|
||||
cleared3BV--;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// allows a sub class to check what has been marked with a flag
|
||||
protected boolean isFlag(int x, int y) {
|
||||
return flag[x][y];
|
||||
}
|
||||
|
||||
|
||||
// allows the sub class to confirm that a flag has been revealed to it
|
||||
protected void setFlag(int x, int y) {
|
||||
|
||||
if (!flag[x][y]) {
|
||||
//System.out.println("Auto flag set at (" + x + "," + y + ")");
|
||||
flag[x][y] = true;
|
||||
flagsPlaced++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// allows the sub class to remove a flag stealthily (this is useful when moves are being echoed to an external
|
||||
// board like minesweeper X and it is noticed that a flag has been removed external to the solver)
|
||||
protected void removeFlag(int x, int y) {
|
||||
|
||||
if (flag[x][y]) {
|
||||
//System.out.println("Auto flag set at (" + x + "," + y + ")");
|
||||
flag[x][y] = false;
|
||||
flagsPlaced--;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void explode(Location loc) {
|
||||
|
||||
boolean[][] done = new boolean[width][height];
|
||||
|
||||
List<Location> interiorList = new ArrayList<>();
|
||||
|
||||
// add this location to the interior array list
|
||||
done[loc.x][loc.y] = true;
|
||||
interiorList.add(loc);
|
||||
|
||||
int processFrom = 0;
|
||||
|
||||
while (processFrom < interiorList.size()) {
|
||||
|
||||
// get the current location to process surrounding squares
|
||||
Location cl = interiorList.get(processFrom);
|
||||
|
||||
for (int i=0; i < DX.length; i++) {
|
||||
|
||||
int x1 = cl.x + DX[i];
|
||||
int y1 = cl.y + DY[i];
|
||||
|
||||
// check each of the surrounding squares which haven't already been checked
|
||||
if (x1 >= 0 && x1 < width && y1 >= 0 && y1 < height) {
|
||||
|
||||
Location l = new Location(x1, y1);
|
||||
|
||||
if (!done[x1][y1] && query(l) == GameStateModel.HIDDEN) {
|
||||
|
||||
done[x1][y1] = true;
|
||||
setRevealed(x1,y1);
|
||||
|
||||
// if this square is also a zero then add it to the list of locations to be exploded
|
||||
if (query(l) == 0) {
|
||||
interiorList.add(l);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
processFrom++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public int getTotal3BV() {
|
||||
return total3BV;
|
||||
}
|
||||
|
||||
public int getCleared3BV() {
|
||||
if (this.gameState == GameStateModel.WON || this.gameState == GameStateModel.LOST) {
|
||||
return cleared3BV;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public int getActionCount() {
|
||||
return actionCount;
|
||||
}
|
||||
|
||||
protected void calculate3BV() {
|
||||
|
||||
if (!supports3BV()) {
|
||||
total3BV = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// find how many of each value there is on the board
|
||||
for (int i=0; i < width; i++) {
|
||||
for (int j=0; j < height; j++) {
|
||||
if (queryHandle(i, j) != GameStateModel.MINE) {
|
||||
values[queryHandle(i, j)]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total3BV = 0;
|
||||
|
||||
boolean[][] done = new boolean[width][height];
|
||||
|
||||
List<Location> interiorList = new ArrayList<>();
|
||||
|
||||
// find all the areas enclosing zeros
|
||||
for (int i=0; i < width; i++) {
|
||||
for (int j=0; j < height; j++) {
|
||||
//Location start = new Location(i,j);
|
||||
if (!done[i][j] && queryHandle(i, j) != GameStateModel.MINE && queryHandle(i, j) == 0) {
|
||||
|
||||
//System.out.println("found zeros at " + start.display());
|
||||
total3BV++;
|
||||
is3BV[i][j] = true;
|
||||
|
||||
int processFrom = 0;
|
||||
interiorList.clear();
|
||||
interiorList.add(new Location(i,j));
|
||||
|
||||
done[i][j] = true;
|
||||
|
||||
while (processFrom < interiorList.size()) {
|
||||
|
||||
// get the current location to process surrounding squares
|
||||
Location cl = interiorList.get(processFrom);
|
||||
|
||||
for (int k=0; k < DX.length; k++) {
|
||||
|
||||
int x1 = cl.x + DX[k];
|
||||
int y1 = cl.y + DY[k];
|
||||
|
||||
// check each of the surrounding squares which haven't already been checked
|
||||
if (x1 >= 0 && x1 < width && y1 >= 0 && y1 < height) {
|
||||
|
||||
//Location l = new Location(x1, y1);
|
||||
|
||||
if (!done[x1][y1]) {
|
||||
|
||||
done[x1][y1] = true;
|
||||
|
||||
// if this square is also a zero then add it to the list of locations to be exploded
|
||||
if (queryHandle(x1, y1) != GameStateModel.MINE && queryHandle(x1, y1) == 0) {
|
||||
interiorList.add(new Location(x1, y1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
processFrom++;
|
||||
}
|
||||
|
||||
//System.out.println("Zeros = " + interiorList.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find all the non-mine tile left
|
||||
for (int i=0; i < width; i++) {
|
||||
for (int j=0; j < height; j++) {
|
||||
//Location start = new Location(i,j);
|
||||
if (queryHandle(i, j) != GameStateModel.MINE && !done[i][j]) {
|
||||
//System.out.println("found non-edge " + start.display());
|
||||
is3BV[i][j] = true;
|
||||
total3BV++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// used to shuffle an array
|
||||
static public <T> void shuffle(T[] a, RNG rng) {
|
||||
|
||||
for (int i = a.length - 1; i > 0; i--) {
|
||||
int j = (int) rng.random((i + 1));
|
||||
//System.out.println(j);
|
||||
T x = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = x;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the recommended initial start location for this game type.
|
||||
* Defaults to the top left corner.
|
||||
* @return
|
||||
*/
|
||||
public Location getStartLocation() {
|
||||
return new Location(0,0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This can be overriden to show the key used to generate the game if one is available
|
||||
* @return
|
||||
*/
|
||||
public String showGameKey() {
|
||||
|
||||
return "no key defined";
|
||||
|
||||
}
|
||||
|
||||
public long getSeed() {
|
||||
return this.seed;
|
||||
}
|
||||
|
||||
|
||||
public boolean safeOpening() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean supports3BV() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the width of the game board
|
||||
*/
|
||||
final public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the height of the game board
|
||||
*/
|
||||
final public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the original number of mines in the game
|
||||
*/
|
||||
final public int getMines() {
|
||||
return mines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the game state: NOT_STARTED, STARTED, LOST, WON
|
||||
*/
|
||||
final public int getGameState() {
|
||||
return gameState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of non-flagged mines remaining in the game
|
||||
*/
|
||||
|
||||
final public int getMinesLeft() {
|
||||
return this.mines - this.flagsPlaced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of squares hidden
|
||||
*/
|
||||
final public int getHidden() {
|
||||
return this.width * this.height - this.squaresRevealed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the occurrences of value in the game
|
||||
*/
|
||||
final public int getValueCount(int value) {
|
||||
return this.values[value];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length of time since the game started in seconds
|
||||
*/
|
||||
final public long getGameTime() {
|
||||
|
||||
if (gameState == GameStateModel.NOT_STARTED) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (gameState == GameStateModel.STARTED) {
|
||||
|
||||
return (System.currentTimeMillis() - startTS) / 1000;
|
||||
}
|
||||
|
||||
return (finishTS - startTS) / 1000;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package minesweeper.gamestate;
|
||||
|
||||
import minesweeper.settings.GameSettings;
|
||||
import minesweeper.structure.Location;
|
||||
|
||||
/**
|
||||
* This contains a method to allow the viewer to see where the mines are
|
||||
* @author David
|
||||
*
|
||||
*/
|
||||
abstract public class GameStateModelViewer extends GameStateModel {
|
||||
|
||||
public GameStateModelViewer(GameSettings gameSettings) {
|
||||
this(gameSettings, 0);
|
||||
}
|
||||
|
||||
public GameStateModelViewer(GameSettings gameSettings, long seed) {
|
||||
super(gameSettings, seed);
|
||||
}
|
||||
|
||||
// can be used by the display to get the mines
|
||||
abstract public int privilegedQuery(Location m, boolean showMines);
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,414 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package minesweeper.gamestate;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
|
||||
import minesweeper.settings.GameSettings;
|
||||
import minesweeper.structure.Location;
|
||||
|
||||
/**
|
||||
* A Version of Minesweeper which reads the board state from a file
|
||||
* @author David
|
||||
*/
|
||||
public class GameStateReader extends GameStateModelViewer {
|
||||
|
||||
private final int[][] board;
|
||||
|
||||
private File file;
|
||||
private boolean safeOpening;
|
||||
|
||||
|
||||
private GameStateReader(GameSettings gameSettings) {
|
||||
super(gameSettings, 0);
|
||||
|
||||
this.board = new int[width][height];
|
||||
}
|
||||
|
||||
|
||||
public final static GameStateModelViewer load(File file) throws Exception {
|
||||
|
||||
if (file.getName().toUpperCase().endsWith(".MBF")) {
|
||||
return loadMBF(file);
|
||||
}
|
||||
|
||||
|
||||
int width;
|
||||
int height;
|
||||
int mines;
|
||||
|
||||
int minesCount = 0;
|
||||
|
||||
GameStateReader result;
|
||||
|
||||
|
||||
int[][] tempBoard;
|
||||
|
||||
try (InputStreamReader isr = new InputStreamReader(new FileInputStream(file));
|
||||
BufferedReader reader = new BufferedReader(isr)
|
||||
){
|
||||
|
||||
String data = reader.readLine();
|
||||
|
||||
if (data == null) {
|
||||
throw new Exception("File is empty!");
|
||||
}
|
||||
|
||||
String[] header = data.trim().split("x");
|
||||
if (header.length != 3) {
|
||||
throw new Exception("Header (" + data + ") doesn't contain width, height, mine separated by 'x'");
|
||||
}
|
||||
|
||||
try {
|
||||
width = Integer.parseInt(header[0]);
|
||||
height = Integer.parseInt(header[1]);
|
||||
mines = Integer.parseInt(header[2]);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Unable to parse the values in the header (" + data + ")");
|
||||
}
|
||||
|
||||
result = new GameStateReader(GameSettings.create(width, height, mines));
|
||||
|
||||
tempBoard = new int[width][height];
|
||||
|
||||
data = reader.readLine();
|
||||
int cy=0;
|
||||
|
||||
while (data != null) {
|
||||
|
||||
if (data.trim().length() != width) {
|
||||
throw new Exception("Detail row is not the same width as the header's width value");
|
||||
}
|
||||
|
||||
int cx = 0;
|
||||
for (char c: data.trim().toCharArray()) {
|
||||
if (c == 'M' || c == 'm') {
|
||||
|
||||
minesCount++;
|
||||
|
||||
result.board[cx][cy] = GameStateModel.MINE;
|
||||
|
||||
// tell all the surrounding squares they are next to a mine
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (cx + DX[j] >= 0 && cx + DX[j] < result.width && cy + DY[j] >= 0 && cy + DY[j] < result.height) {
|
||||
if (result.board[cx+DX[j]][cy+DY[j]] != GameStateModel.MINE) {
|
||||
result.board[cx+DX[j]][cy+DY[j]]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c == 'M') {
|
||||
result.setFlag(cx, cy);
|
||||
}
|
||||
} else if (c != 'H' && c != 'h') {
|
||||
int val = Character.getNumericValue(c);
|
||||
result.setRevealed(cx, cy);
|
||||
tempBoard[cx][cy] = val;
|
||||
result.safeOpening = false; // if we have already revealed some square it isn't a safe opening
|
||||
}
|
||||
cx++;
|
||||
}
|
||||
|
||||
cy++;
|
||||
data = reader.readLine();
|
||||
|
||||
if (cy == height) {
|
||||
break;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
if (cy != height) {
|
||||
throw new Exception("Not enough rows in the file for the game defined in the header");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (mines != minesCount) {
|
||||
System.out.println("Mines in puzzle is " + minesCount + ", but mines declared is " + mines);
|
||||
result.partialGame = true;
|
||||
|
||||
// for partial games use the revealed values as given in the file
|
||||
for (int i=0; i < width; i++) {
|
||||
for (int j=0; j < height; j++) {
|
||||
result.board[i][j] = tempBoard[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
result.partialGame = false;
|
||||
}
|
||||
|
||||
result.file = file;
|
||||
|
||||
result.start(new Location(0,0));
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
public final static GameStateModelViewer loadMBF(File file) throws Exception {
|
||||
|
||||
System.out.println("Loading MBF file");
|
||||
|
||||
int width;
|
||||
int height;
|
||||
int mines;
|
||||
|
||||
GameStateReader result;
|
||||
|
||||
byte[] data = new byte[70000];
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(file)){
|
||||
|
||||
int size = 0;
|
||||
int length = 0;
|
||||
while (length != -1) {
|
||||
size = size + length;
|
||||
length = fis.read(data, size, 1024);
|
||||
System.out.println("Read " + length + " bytes");
|
||||
}
|
||||
System.out.println("Loaded " + size + " bytes in total");
|
||||
|
||||
if (size == 0) {
|
||||
throw new Exception("File is empty!");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
width = data[0];
|
||||
height = data[1];
|
||||
mines = data[2] * 256 + data[3];
|
||||
System.out.println("Width " + width+ " height " + height + " mines " + mines);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Unable to parse the board values");
|
||||
}
|
||||
|
||||
result = new GameStateReader(GameSettings.create(width, height, mines));
|
||||
|
||||
for (int i=4; i < size; i+=2) {
|
||||
int x = data[i];
|
||||
int y = data[i+1];
|
||||
|
||||
//System.out.println("mine at (" + x + "," + y + ")");
|
||||
result.board[x][y] = GameStateModel.MINE;
|
||||
|
||||
// tell all the surrounding squares they are next to a mine
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (x + DX[j] >= 0 && x + DX[j] < result.width && y + DY[j] >= 0 && y + DY[j] < result.height) {
|
||||
if (result.board[x+DX[j]][y+DY[j]] != GameStateModel.MINE) {
|
||||
result.board[x+DX[j]][y+DY[j]]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
|
||||
result.file = file;
|
||||
|
||||
result.start(new Location(0,0));
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
public final static GameStateModelViewer loadMines(int width, int height, List<Location> mines, List<Location> revealed) {
|
||||
|
||||
GameStateReader result = new GameStateReader(GameSettings.create(width, height, mines.size()));
|
||||
|
||||
for (Location mine: mines) {
|
||||
|
||||
int x = mine.x;
|
||||
int y = mine.y;
|
||||
|
||||
//System.out.println("mine at (" + x + "," + y + ")");
|
||||
result.board[x][y] = GameStateModel.MINE;
|
||||
|
||||
// tell all the surrounding squares they are next to a mine
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (x + DX[j] >= 0 && x + DX[j] < result.width && y + DY[j] >= 0 && y + DY[j] < result.height) {
|
||||
if (result.board[x+DX[j]][y+DY[j]] != GameStateModel.MINE) {
|
||||
result.board[x+DX[j]][y+DY[j]]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set the revealed locations
|
||||
for (Location tile: revealed) {
|
||||
|
||||
int x = tile.x;
|
||||
int y = tile.y;
|
||||
|
||||
result.setRevealed(x, y);
|
||||
}
|
||||
|
||||
result.partialGame = false; // indicates that the board is complete
|
||||
result.safeOpening = false;
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// in this gamestate the board is built from the file
|
||||
@Override
|
||||
protected void startHandle(Location m) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
// in this gamestate there is nothing to do
|
||||
@Override
|
||||
protected void placeFlagHandle(Location m) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected int queryHandle(int x, int y) {
|
||||
return board[x][y];
|
||||
}
|
||||
|
||||
|
||||
// in this gamestate we need to expand the clear if no mines are adjacent
|
||||
@Override
|
||||
protected boolean clearSquareHitMine(Location m) {
|
||||
|
||||
// if there are no mines next to this location expand reveal
|
||||
if (board[m.x][m.y] == 0) {
|
||||
explode(m);
|
||||
//clearSurround(m);
|
||||
}
|
||||
|
||||
if (board[m.x][m.y] == GameStateModel.MINE) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
//return board[m.x][m.y];
|
||||
|
||||
}
|
||||
@Override
|
||||
public int privilegedQuery(Location m, boolean showMines) {
|
||||
|
||||
int result = query(m);
|
||||
if (result == GameStateModel.MINE) { // if we can see a mine using query it must be exploded
|
||||
return GameStateModel.EXPLODED_MINE;
|
||||
}
|
||||
|
||||
if (showMines && result == GameStateModel.HIDDEN && board[m.x][m.y] == GameStateModel.MINE) {
|
||||
result = GameStateModel.MINE;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String showGameKey() {
|
||||
|
||||
String gameKey = "";
|
||||
|
||||
if (file != null) {
|
||||
gameKey = "file = " + file.getAbsolutePath();
|
||||
} else {
|
||||
gameKey = "Generated";
|
||||
}
|
||||
|
||||
if (partialGame) {
|
||||
gameKey = gameKey + " (Mines missing!)";
|
||||
}
|
||||
|
||||
return gameKey;
|
||||
|
||||
}
|
||||
|
||||
public boolean supports3BV() {
|
||||
return !partialGame;
|
||||
}
|
||||
|
||||
/*
|
||||
private void explode(Location loc) {
|
||||
|
||||
boolean[][] done = new boolean[width][height];
|
||||
|
||||
List<Location> interiorList = new ArrayList<>();
|
||||
|
||||
// add this location to the interior array list
|
||||
done[loc.x][loc.y] = true;
|
||||
interiorList.add(loc);
|
||||
|
||||
int processFrom = 0;
|
||||
|
||||
while (processFrom < interiorList.size()) {
|
||||
|
||||
// get the current location to process surrounding squares
|
||||
Location cl = interiorList.get(processFrom);
|
||||
|
||||
for (int i=0; i < DX.length; i++) {
|
||||
|
||||
int x1 = cl.x + DX[i];
|
||||
int y1 = cl.y + DY[i];
|
||||
|
||||
// check each of the surrounding squares which haven't already been checked
|
||||
if (x1 >= 0 && x1 < width && y1 >= 0 && y1 < height) {
|
||||
if (!done[x1][y1] && query(new Location(x1, y1)) == GameStateModel.HIDDEN) {
|
||||
|
||||
done[x1][y1] = true;
|
||||
setRevealed(x1,y1);
|
||||
|
||||
// if this square is also a zero then add it to the list of locations to be exploded
|
||||
if (board[x1][y1] == 0) {
|
||||
interiorList.add(new Location(x1, y1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
processFrom++;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
protected boolean clearSurroundHandle(Location m) {
|
||||
|
||||
// otherwise, clear around this revealed square
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (m.x + DX[j] >= 0 && m.x + DX[j] < this.width && m.y + DY[j] >= 0 && m.y + DY[j] < this.height) {
|
||||
clearSquare(new Location(m.x+DX[j], m.y+DY[j]));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean safeOpening() {
|
||||
return safeOpening;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package minesweeper.gamestate;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import minesweeper.random.DefaultRNG;
|
||||
import minesweeper.random.RNG;
|
||||
import minesweeper.settings.GameSettings;
|
||||
import minesweeper.structure.Action;
|
||||
import minesweeper.structure.Location;
|
||||
|
||||
/**
|
||||
* A Version of Minesweeper which ensures the first click is not a mine
|
||||
* @author David
|
||||
*/
|
||||
public class GameStateStandard extends GameStateModelViewer {
|
||||
|
||||
private final int[][] board;
|
||||
|
||||
private RNG rng;
|
||||
|
||||
public GameStateStandard(GameSettings gameSettings) {
|
||||
this(gameSettings, new Random().nextLong());
|
||||
}
|
||||
|
||||
|
||||
public GameStateStandard(GameSettings gameSettings, long seed) {
|
||||
super(gameSettings, seed);
|
||||
|
||||
this.board = new int[width][height];
|
||||
|
||||
this.rng = DefaultRNG.getRNG(seed);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of actions based on what is on the board, this is cheating.
|
||||
* Used by the solver to allow it to always win BFDA parts of the board.
|
||||
*/
|
||||
@Override
|
||||
public List<Action> identifyLocations(Collection<? extends Location> locs) {
|
||||
|
||||
List<Action> actions = new ArrayList<>();
|
||||
|
||||
for (Location loc: locs) {
|
||||
int value = this.board[loc.x][loc.y];
|
||||
if (value == GameStateModel.MINE) {
|
||||
actions.add(new Action(loc, Action.FLAG, MoveMethod.CHEAT, "", BigDecimal.ONE));
|
||||
} else {
|
||||
actions.add(new Action(loc, Action.CLEAR, MoveMethod.CHEAT, "", BigDecimal.ONE));
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
|
||||
}
|
||||
|
||||
// in this gamestate we are building the board ourselves
|
||||
@Override
|
||||
protected void startHandle(Location m) {
|
||||
|
||||
// create the tiles
|
||||
Integer[] indices = new Integer[this.width * this.height - 1];
|
||||
|
||||
// find all the non-mine tile left
|
||||
int index = 0;
|
||||
for (int y=0; y < this.height; y++) {
|
||||
for (int x=0; x < this.width; x++) {
|
||||
|
||||
if (x != m.x || y != m.y) {
|
||||
int tile = y * this.width + x;
|
||||
indices[index++] = tile;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
shuffle(indices, rng);
|
||||
|
||||
// allocate the bombs and calculate the values
|
||||
for (int i = 0; i < this.mines; i++) {
|
||||
int tile = indices[i];
|
||||
|
||||
int x = tile % this.width;
|
||||
int y = tile / this.width;
|
||||
|
||||
board[x][y] = GameStateModel.MINE;
|
||||
|
||||
// tell all the surrounding squares they are next to a mine
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (x + DX[j] >= 0 && x + DX[j] < this.width && y + DY[j] >= 0 && y + DY[j] < this.height) {
|
||||
if (board[x+DX[j]][y+DY[j]] != GameStateModel.MINE) {
|
||||
board[x+DX[j]][y+DY[j]]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
int i=0;
|
||||
|
||||
while (i < mines) {
|
||||
int y1 = (int) rng.random(this.height);
|
||||
int x1 = (int) rng.random(this.width);
|
||||
Location l1 = new Location(x1, y1);
|
||||
|
||||
// if the location is NOT the first square pressed
|
||||
// and the location is not already a mine then place a mine here
|
||||
if (!l1.equals(m)) {
|
||||
if (board[x1][y1] != GameStateModel.MINE) {
|
||||
//System.out.println("Mine added at " + x1 + "," + y1);
|
||||
board[x1][y1] = GameStateModel.MINE;
|
||||
i++;
|
||||
|
||||
// tell all the surrounding squares they are next to a mine
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (x1 + DX[j] >= 0 && x1 + DX[j] < this.width && y1 + DY[j] >= 0 && y1 + DY[j] < this.height) {
|
||||
if (board[x1+DX[j]][y1+DY[j]] != GameStateModel.MINE) {
|
||||
board[x1+DX[j]][y1+DY[j]]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
// in this gamestate there is nothing to do
|
||||
@Override
|
||||
protected void placeFlagHandle(Location m) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected int queryHandle(int x, int y) {
|
||||
return board[x][y];
|
||||
}
|
||||
|
||||
|
||||
// in this gamestate we need to expand the clear if no mines are adjacent
|
||||
@Override
|
||||
protected boolean clearSquareHitMine(Location m) {
|
||||
|
||||
// if there are no mines next to this location expand reveal
|
||||
if (board[m.x][m.y] == 0) {
|
||||
explode(m);
|
||||
//clearSurround(m);
|
||||
}
|
||||
|
||||
if (board[m.x][m.y] == GameStateModel.MINE) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
//return board[m.x][m.y];
|
||||
|
||||
}
|
||||
@Override
|
||||
public int privilegedQuery(Location m, boolean showMines) {
|
||||
|
||||
int result = query(m);
|
||||
if (result == GameStateModel.MINE) { // if we can see a mine using query it must be exploded
|
||||
return GameStateModel.EXPLODED_MINE;
|
||||
}
|
||||
|
||||
if (showMines && result == GameStateModel.HIDDEN && board[m.x][m.y] == GameStateModel.MINE) {
|
||||
result = GameStateModel.MINE;
|
||||
}
|
||||
|
||||
if (showMines && result == GameStateModel.FLAG && board[m.x][m.y] != GameStateModel.MINE ) {
|
||||
result = GameStateModel.BAD_FLAG;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String showGameKey() {
|
||||
|
||||
return "Seed = " + seed + " (" + rng.shortname() + ")";
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
private void explode(Location loc) {
|
||||
|
||||
boolean[][] done = new boolean[width][height];
|
||||
|
||||
List<Location> interiorList = new ArrayList<>();
|
||||
|
||||
// add this location to the interior array list
|
||||
done[loc.x][loc.y] = true;
|
||||
interiorList.add(loc);
|
||||
|
||||
int processFrom = 0;
|
||||
|
||||
while (processFrom < interiorList.size()) {
|
||||
|
||||
// get the current location to process surrounding squares
|
||||
Location cl = interiorList.get(processFrom);
|
||||
|
||||
for (int i=0; i < DX.length; i++) {
|
||||
|
||||
int x1 = cl.x + DX[i];
|
||||
int y1 = cl.y + DY[i];
|
||||
|
||||
// check each of the surrounding squares which haven't already been checked
|
||||
if (x1 >= 0 && x1 < width && y1 >= 0 && y1 < height) {
|
||||
if (!done[x1][y1] && query(new Location(x1, y1)) == GameStateModel.HIDDEN) {
|
||||
|
||||
done[x1][y1] = true;
|
||||
setRevealed(x1,y1);
|
||||
|
||||
// if this square is also a zero then add it to the list of locations to be exploded
|
||||
if (board[x1][y1] == 0) {
|
||||
interiorList.add(new Location(x1, y1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
processFrom++;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean supports3BV() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean clearSurroundHandle(Location m) {
|
||||
|
||||
// otherwise, clear around this revealed square
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (m.x + DX[j] >= 0 && m.x + DX[j] < this.width && m.y + DY[j] >= 0 && m.y + DY[j] < this.height) {
|
||||
clearSquare(new Location(m.x+DX[j], m.y+DY[j]));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package minesweeper.gamestate;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import minesweeper.random.DefaultRNG;
|
||||
import minesweeper.random.RNG;
|
||||
import minesweeper.settings.GameSettings;
|
||||
import minesweeper.structure.Location;
|
||||
|
||||
/**
|
||||
* A Version of Minesweeper which ensures the first click is not a mine
|
||||
* @author David
|
||||
*/
|
||||
public class GameStateStandardWith8 extends GameStateModelViewer {
|
||||
|
||||
private final int[][] board;
|
||||
|
||||
private RNG rng;
|
||||
|
||||
public GameStateStandardWith8(GameSettings gameSettings) {
|
||||
this(gameSettings, new Random().nextLong());
|
||||
}
|
||||
|
||||
|
||||
public GameStateStandardWith8(GameSettings gameSettings, long seed) {
|
||||
super(gameSettings, seed);
|
||||
|
||||
this.board = new int[width][height];
|
||||
|
||||
this.rng = DefaultRNG.getRNG(seed);
|
||||
}
|
||||
|
||||
// in this gamestate we are building the board ourselves
|
||||
@Override
|
||||
protected void startHandle(Location m) {
|
||||
|
||||
int i=0;
|
||||
|
||||
Location locationOf8 = null;
|
||||
boolean placed8 = false;
|
||||
while (!placed8) {
|
||||
int y1 = 1 + (int) rng.random(this.height - 2);
|
||||
int x1 = 1 + (int) rng.random(this.width - 2);
|
||||
locationOf8 = new Location(x1, y1);
|
||||
|
||||
// place the 8 mines around a single tile
|
||||
if (!locationOf8.equals(m) && !locationOf8.isAdjacent(m)) {
|
||||
for (int k=0; k < DX.length; k++) {
|
||||
|
||||
int x2 = x1 + DX[k];
|
||||
int y2 = y1 + DY[k];
|
||||
|
||||
//System.out.println("Mine added at " + x2 + "," + y2);
|
||||
board[x2][y2] = GameStateModel.MINE;
|
||||
i++;
|
||||
|
||||
// tell all the surrounding squares they are next to a mine
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (x2 + DX[j] >= 0 && x2 + DX[j] < this.width && y2 + DY[j] >= 0 && y2 + DY[j] < this.height) {
|
||||
if (board[x2+DX[j]][y2+DY[j]] != GameStateModel.MINE) {
|
||||
board[x2+DX[j]][y2+DY[j]]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
placed8 = true;
|
||||
}
|
||||
}
|
||||
|
||||
while (i < mines) {
|
||||
int y1 = (int) rng.random(this.height);
|
||||
int x1 = (int) rng.random(this.width);
|
||||
Location l1 = new Location(x1, y1);
|
||||
|
||||
// if the location is NOT the first square pressed
|
||||
// and the location is not already a mine then place a mine here
|
||||
if (!l1.equals(m) && !l1.equals(locationOf8)) {
|
||||
if (board[x1][y1] != GameStateModel.MINE) {
|
||||
//System.out.println("Mine added at " + x1 + "," + y1);
|
||||
board[x1][y1] = GameStateModel.MINE;
|
||||
i++;
|
||||
|
||||
// tell all the surrounding squares they are next to a mine
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (x1 + DX[j] >= 0 && x1 + DX[j] < this.width && y1 + DY[j] >= 0 && y1 + DY[j] < this.height) {
|
||||
if (board[x1+DX[j]][y1+DY[j]] != GameStateModel.MINE) {
|
||||
board[x1+DX[j]][y1+DY[j]]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// in this gamestate there is nothing to do
|
||||
@Override
|
||||
protected void placeFlagHandle(Location m) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected int queryHandle(int x, int y) {
|
||||
return board[x][y];
|
||||
}
|
||||
|
||||
// in this gamestate we need to expand the clear if no mines are adjacent
|
||||
@Override
|
||||
protected boolean clearSquareHitMine(Location m) {
|
||||
|
||||
// if there are no mines next to this location expand reveal
|
||||
if (board[m.x][m.y] == 0) {
|
||||
explode(m);
|
||||
//clearSurround(m);
|
||||
}
|
||||
|
||||
if (board[m.x][m.y] == GameStateModel.MINE) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
//return board[m.x][m.y];
|
||||
|
||||
}
|
||||
@Override
|
||||
public int privilegedQuery(Location m, boolean showMines) {
|
||||
|
||||
int result = query(m);
|
||||
if (result == GameStateModel.MINE) { // if we can see a mine using query it must be exploded
|
||||
return GameStateModel.EXPLODED_MINE;
|
||||
}
|
||||
|
||||
if (showMines && result == GameStateModel.HIDDEN && board[m.x][m.y] == GameStateModel.MINE) {
|
||||
result = GameStateModel.MINE;
|
||||
}
|
||||
|
||||
if (showMines && result == GameStateModel.FLAG && board[m.x][m.y] != GameStateModel.MINE ) {
|
||||
result = GameStateModel.BAD_FLAG;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String showGameKey() {
|
||||
|
||||
return "Seed = " + seed + " (" + rng.shortname() + ")";
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports3BV() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean clearSurroundHandle(Location m) {
|
||||
|
||||
// otherwise, clear around this revealed square
|
||||
for (int j=0; j < DX.length; j++) {
|
||||
if (m.x + DX[j] >= 0 && m.x + DX[j] < this.width && m.y + DY[j] >= 0 && m.y + DY[j] < this.height) {
|
||||
clearSquare(new Location(m.x+DX[j], m.y+DY[j]));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package minesweeper.gamestate;
|
||||
|
||||
public enum MoveMethod {
|
||||
|
||||
HUMAN("Human"),
|
||||
BOOK("Opening Book"),
|
||||
CORRECTION("Correction"),
|
||||
TRIVIAL("Trivial analysis"),
|
||||
LOCAL("Local analysis"),
|
||||
PROBABILITY_ENGINE("Probability Engine"),
|
||||
BRUTE_FORCE("Brute Force"),
|
||||
BRUTE_FORCE_DEEP_ANALYSIS("Brute Force Deep Analysis"),
|
||||
GUESS("Guess"),
|
||||
ROLLOUT("Rollout"),
|
||||
UNAVOIDABLE_GUESS("Unavoidable Guess"),
|
||||
CHEAT("Cheat");
|
||||
|
||||
public final String description;
|
||||
|
||||
|
||||
private MoveMethod(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package minesweeper.random;
|
||||
|
||||
public class DefaultRNG {
|
||||
|
||||
static private Class<? extends RNG> defaultRNG = RNGJava.class;
|
||||
|
||||
|
||||
/**
|
||||
* Set the default RNG implementation used when creating the mine sweeper boards
|
||||
* @param rngClass
|
||||
*/
|
||||
public static void setDefaultRNGClass(Class<? extends RNG> rngClass) {
|
||||
defaultRNG = rngClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the default RNG implementation used when creating the mine sweeper boards
|
||||
* @param rngClass
|
||||
*/
|
||||
public static Class<? extends RNG> getDefaultRNGClass() {
|
||||
return defaultRNG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of the default random number generator with seed
|
||||
* @return
|
||||
*/
|
||||
public static RNG getRNG(long seed) {
|
||||
|
||||
RNG rng = null;
|
||||
try {
|
||||
rng = defaultRNG.newInstance();
|
||||
rng.seed(seed);
|
||||
} catch (InstantiationException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SecurityException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return rng;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package minesweeper.random;
|
||||
|
||||
public interface RNG {
|
||||
|
||||
public void seed(long seed);
|
||||
|
||||
public long random(int in);
|
||||
|
||||
public String name();
|
||||
|
||||
public String shortname();
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package minesweeper.random;
|
||||
|
||||
|
||||
public class RNGJSF implements RNG {
|
||||
|
||||
|
||||
private int[] s;
|
||||
|
||||
public RNGJSF() {
|
||||
}
|
||||
|
||||
public RNGJSF(long seed) {
|
||||
seed(seed);
|
||||
}
|
||||
|
||||
public void seed(long seed) {
|
||||
|
||||
int seed1 = (int) Math.abs(seed);
|
||||
int seed2 = (int) ((Math.abs(seed) >>> 32) & 0x001FFFFFl);
|
||||
|
||||
//System.out.println(seed1 + " " + seed2);
|
||||
|
||||
s = new int[] {0xf1ea5eed, seed1, seed2, seed1};
|
||||
for (int i = 0; i < 20; i++) random(1);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public long random(int in) {
|
||||
|
||||
int e = s[0] - (s[1] << 27 | s[1] >>> 5);
|
||||
|
||||
s[0] = s[1] ^ (s[2] << 17 | s[2] >>> 15);
|
||||
s[1] = s[2] + s[3];
|
||||
s[2] = s[3] + e;
|
||||
s[3] = s[0] + e;
|
||||
|
||||
//System.out.println(e + " " + s[0] + " " + s[1] + " " + s[2] + " " + s[3]);
|
||||
|
||||
return ((s[3] & 0xFFFFFFFFl) * in) >>> 32;
|
||||
|
||||
}
|
||||
|
||||
private long bit32(long x) {
|
||||
return x & 0xFFFFFFFFl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "JSF random numbers";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String shortname() {
|
||||
return "JSF";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package minesweeper.random;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class RNGJava implements RNG {
|
||||
|
||||
static String shortName = "Java RNG";
|
||||
|
||||
private Random rng = new Random();
|
||||
|
||||
public RNGJava() {
|
||||
}
|
||||
|
||||
public RNGJava(long seed) {
|
||||
seed(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seed(long seed) {
|
||||
rng = new Random(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long random(int in) {
|
||||
|
||||
if (in == 0) {
|
||||
return rng.nextLong();
|
||||
} else {
|
||||
return rng.nextInt(in);
|
||||
}
|
||||
//return (long) Math.floor(rng.nextDouble() * in);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "Standard Java random numbers";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String shortname() {
|
||||
return "Java RNG";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package minesweeper.random;
|
||||
|
||||
|
||||
public class RNGKiss64 implements RNG {
|
||||
|
||||
|
||||
private long kiss64_x = 1234567890987654321l;
|
||||
private long kiss64_c = 123456123456123456l;
|
||||
private long kiss64_y = 362436362436362436l;
|
||||
private long kiss64_z = 1066149217761810l;
|
||||
private long kiss64_t = 0;
|
||||
|
||||
public RNGKiss64() {
|
||||
}
|
||||
|
||||
public RNGKiss64(long seed) {
|
||||
seed(seed);
|
||||
}
|
||||
|
||||
public void seed(long seed) {
|
||||
|
||||
kiss64_x = seed | 1;
|
||||
kiss64_c = seed | 2;
|
||||
kiss64_y = seed | 4;
|
||||
kiss64_z = seed | 8;
|
||||
kiss64_t = 0;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public long random(int in) {
|
||||
|
||||
// multiply with carry
|
||||
kiss64_t = (kiss64_x << 58) + kiss64_c;
|
||||
kiss64_c = (kiss64_x >>> 6); // unsigned right shift
|
||||
kiss64_x += kiss64_t;
|
||||
|
||||
|
||||
//kiss64_c += (kiss64_x < kiss64_t)?1l:0l;
|
||||
|
||||
kiss64_c += Long.compareUnsigned(kiss64_x, kiss64_t) < 0 ? 1l:0l;
|
||||
|
||||
// XOR shift
|
||||
kiss64_y ^= (kiss64_y << 13);
|
||||
kiss64_y ^= (kiss64_y >>> 17); // unsigned right shift
|
||||
kiss64_y ^= (kiss64_y << 43);
|
||||
|
||||
// Congruential
|
||||
kiss64_z = 6906969069l * kiss64_z + 1234567l;
|
||||
long rand = kiss64_x + kiss64_y + kiss64_z;
|
||||
|
||||
if (in == 0) {
|
||||
return rand;
|
||||
} else {
|
||||
return ((rand & 0xFFFFFFFFl) * in) >>> 32; // unsigned right shift;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "KISS64 random numbers";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String shortname() {
|
||||
return "KISS64";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package minesweeper.settings;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class GameSettings {
|
||||
|
||||
/**
|
||||
* 9x9/10
|
||||
*/
|
||||
final static public GameSettings BEGINNER = new GameSettings(9, 9, 10, "Beginner");
|
||||
/**
|
||||
* 16x16/40
|
||||
*/
|
||||
final static public GameSettings ADVANCED = new GameSettings(16, 16, 40, "Advanced");
|
||||
/**
|
||||
* 30x16/99
|
||||
*/
|
||||
final static public GameSettings EXPERT = new GameSettings(30, 16, 99, "Expert");
|
||||
|
||||
final static private List<GameSettings> standardSettings = Arrays.asList(BEGINNER, ADVANCED, EXPERT);
|
||||
|
||||
final public int width;
|
||||
final public int height;
|
||||
final public int mines;
|
||||
final public String name;
|
||||
|
||||
private GameSettings(int width, int height, int mines) {
|
||||
this(width, height, mines, "Custom");
|
||||
}
|
||||
|
||||
private GameSettings(int width, int height, int mines, String name) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.mines = mines;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static GameSettings create(int width, int height, int mines) {
|
||||
|
||||
for (GameSettings game: standardSettings) {
|
||||
if (game.width == width && game.height == height && game.mines == mines) {
|
||||
return game;
|
||||
}
|
||||
}
|
||||
|
||||
return new GameSettings(width, height, mines, "Custom");
|
||||
|
||||
}
|
||||
|
||||
public String description() {
|
||||
|
||||
return name + " (" + width + "," + height + "," + mines + ")";
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
return width + "x" + height + "/" + mines;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package minesweeper.settings;
|
||||
|
||||
public enum GameType {
|
||||
|
||||
/**
|
||||
* Game starts with a guaranteed zero
|
||||
*/
|
||||
EASY("Zero"),
|
||||
|
||||
/**
|
||||
* Game starts with a guaranteed safe position (which could be a zero)
|
||||
*/
|
||||
STANDARD("Safe"),
|
||||
|
||||
/**
|
||||
* No guaranteed safe start (could be a mine)
|
||||
*/
|
||||
HARD("Unsafe");
|
||||
|
||||
|
||||
public final String name;
|
||||
|
||||
private GameType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package minesweeper.structure;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Comparator;
|
||||
|
||||
import minesweeper.gamestate.MoveMethod;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author David
|
||||
*/
|
||||
public class Action extends Location {
|
||||
|
||||
public static final int CLEAR = 1;
|
||||
public static final int CLEARALL = 2;
|
||||
public static final int FLAG = 3;
|
||||
|
||||
public static final DecimalFormat FORMAT_2DP = new DecimalFormat("#0.00");
|
||||
|
||||
private final static String[] ACTION = {"", "Clear", "Clear around", "Place flag"};
|
||||
private final static BigDecimal MINUS_ONE = new BigDecimal("-1");
|
||||
private final static BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100);
|
||||
|
||||
static long globalUID = 0;
|
||||
|
||||
private final long myUID;
|
||||
private final int action;
|
||||
private final BigDecimal bigProb;
|
||||
|
||||
private final boolean certainty;
|
||||
|
||||
//private int type = 0;
|
||||
private final MoveMethod moveMethod;
|
||||
|
||||
String comment;
|
||||
|
||||
// used by the human player
|
||||
public Action(int x, int y, int a) {
|
||||
this(new Location(x, y), a);
|
||||
}
|
||||
|
||||
// used by the human player
|
||||
public Action(Location l, int a) {
|
||||
this(l, a, MoveMethod.HUMAN, "", MINUS_ONE);
|
||||
}
|
||||
|
||||
// used by the computer coach
|
||||
public Action(Location l, int a, MoveMethod moveMethod, String comment, BigDecimal bigProb) {
|
||||
this(l, a, moveMethod, comment, bigProb, globalUID++);
|
||||
|
||||
}
|
||||
|
||||
// used by the computer coach to force a move to be earlier in the list - e.g. when we need to remove a flag placed by the human player before we can clear the square
|
||||
public Action(Location l, int a, MoveMethod moveMethod, String comment, BigDecimal bigProb, long uid) {
|
||||
super(l.x, l.y);
|
||||
|
||||
this.action = a;
|
||||
this.comment = comment;
|
||||
this.bigProb = bigProb;
|
||||
this.moveMethod = moveMethod;
|
||||
|
||||
this.myUID = uid;
|
||||
if (bigProb.compareTo(BigDecimal.ONE) == 0) {
|
||||
this.certainty = true;
|
||||
} else {
|
||||
this.certainty = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public int getAction() {
|
||||
return this.action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when this action is 100% certain
|
||||
*/
|
||||
public boolean isCertainty() {
|
||||
return this.certainty;
|
||||
}
|
||||
|
||||
public BigDecimal getBigProb() {
|
||||
return bigProb;
|
||||
}
|
||||
|
||||
public MoveMethod getMoveMethod() {
|
||||
return this.moveMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
String result = Action.ACTION[this.action] + " at " + super.toString() + " by " + moveMethod.description + " " + comment;
|
||||
|
||||
if (bigProb.compareTo(BigDecimal.ONE) < 0) {
|
||||
result = result + " with a probability of " + FORMAT_2DP.format(bigProb.multiply(ONE_HUNDRED)) + "%";
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* sort by the UID field which is a sequence of when the move was found
|
||||
*/
|
||||
static public final Comparator<Action> SORT_BY_MOVE_NUMBER = new Comparator<Action>() {
|
||||
@Override
|
||||
public int compare(Action o1, Action o2) {
|
||||
|
||||
return (int) (o1.myUID - o2.myUID);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package minesweeper.structure;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Collection of locations and some operations
|
||||
* @author David
|
||||
*
|
||||
*/
|
||||
public class Area {
|
||||
|
||||
public final static Area EMPTY_AREA = new Area(new HashSet<>());
|
||||
|
||||
private final Set<Location> area;
|
||||
private final Set<Location> readOnlyArea;
|
||||
|
||||
/**
|
||||
* Build an area based on a collection of locations
|
||||
*/
|
||||
public Area(Collection<Location> area) {
|
||||
this.area = new HashSet<>(area);
|
||||
this.readOnlyArea = Collections.unmodifiableSet(this.area);
|
||||
}
|
||||
|
||||
/**
|
||||
* wrap a pre-built set
|
||||
*/
|
||||
public Area(Set<Location> area) {
|
||||
this.area = area;
|
||||
this.readOnlyArea = Collections.unmodifiableSet(this.area);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this area contains the location
|
||||
*/
|
||||
public boolean contains(Location loc) {
|
||||
return area.contains(loc);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if this area contains all the locations in the subset
|
||||
*/
|
||||
public boolean supersetOf(Area subset) {
|
||||
return area.containsAll(subset.area);
|
||||
}
|
||||
|
||||
public Collection<Location> getLocations() {
|
||||
return readOnlyArea;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return area.size();
|
||||
}
|
||||
|
||||
public Area add(Location add) {
|
||||
if (area.contains(add)) {
|
||||
return this;
|
||||
}
|
||||
Set<Location> result = new HashSet<>(this.size());
|
||||
result.addAll(area);
|
||||
result.add(add);
|
||||
return new Area(result);
|
||||
}
|
||||
|
||||
public Area remove(Location remove) {
|
||||
if (!area.contains(remove)) {
|
||||
return this;
|
||||
}
|
||||
Set<Location> result = new HashSet<>(this.size());
|
||||
result.addAll(area);
|
||||
result.remove(remove);
|
||||
return new Area(result);
|
||||
}
|
||||
|
||||
public Area merge(Area with) {
|
||||
Set<Location> result = new HashSet<>(this.size() + with.size());
|
||||
result.addAll(area);
|
||||
result.addAll(with.area);
|
||||
return new Area(result);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package minesweeper.structure;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author David
|
||||
*/
|
||||
public class Location implements Comparable<Location> {
|
||||
|
||||
public final int x;
|
||||
public final int y;
|
||||
|
||||
protected final int sortOrder;
|
||||
|
||||
public Location(int x, int y) {
|
||||
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
||||
this.sortOrder = y * 10000 + x;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + x + "," + y + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* target is one if the 8 squares surrounding this location
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
public boolean isAdjacent(Location target) {
|
||||
|
||||
int dx = Math.abs(this.x - target.x);
|
||||
int dy = Math.abs(this.y - target.y);
|
||||
|
||||
if (dx > 1 || dy > 1 || (dx == 0 && dy == 0)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Returns true if m describes the same location
|
||||
*/
|
||||
public boolean equals(Object m) {
|
||||
|
||||
if (m instanceof Location) {
|
||||
if (this.x == ((Location) m).x && this.y == ((Location) m).y) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return sortOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Location arg0) {
|
||||
return this.sortOrder - arg0.sortOrder;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user