rename references

This commit is contained in:
2023-09-28 20:24:18 +08:00
parent 50b5f8c2c1
commit 0be2781d70
274 changed files with 0 additions and 0 deletions

View 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>

View File

@ -0,0 +1 @@
/bin/

View 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>

View File

@ -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

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
};
}

View File

@ -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);
}
}

View File

@ -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;
}
}