Add References to git

This commit is contained in:
2023-09-28 20:23:18 +08:00
parent 4d51eb339c
commit 50b5f8c2c1
276 changed files with 56093 additions and 2 deletions

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="src" path="/MinesweeperGameState"/>
<classpathentry kind="src" path="/MineSweeperSolver"/>
<classpathentry combineaccessrules="false" kind="src" path="/Asynchronous"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

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

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>MinesweeperExplorer</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.xtext.ui.shared.xtextBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.xtext.ui.shared.xtextNature</nature>
<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,8 @@
<?xml version="1.0" encoding="ASCII"?>
<anttasks:AntTask xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:anttasks="http://org.eclipse.fx.ide.jdt/1.0" buildDirectory="${project}/build">
<deploy>
<application name="MinesweeperExplorer"/>
<info/>
</deploy>
<signjar/>
</anttasks:AntTask>

View File

@ -0,0 +1,188 @@
package minesweeper.explorer.busy;
import java.io.IOException;
import java.text.DecimalFormat;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import minesweeper.explorer.main.Graphics;
import minesweeper.solver.utility.ProgressMonitor;
public class BusyController {
private final static DecimalFormat PERCENT = new DecimalFormat("#0.000%");
@FXML private AnchorPane window;
@FXML private ProgressBar progressRun;
private Stage stage;
private Scene scene;
private Thread myThread;
private boolean finished = false;
private boolean wasCancelled = false;
private MonitorTask monitorTask;
@FXML
void initialize() {
System.out.println("Entered Busy Screen initialize method");
}
@FXML
private void handleOkayButton(ActionEvent event) {
System.out.println("handleOkayButton method entered");
}
@FXML
private void handleCancelButton(ActionEvent event) {
System.out.println("handleCancelButton method entered");
stage.close();
}
public static BusyController launch(Window owner, ParallelTask runnable, ProgressMonitor monitor) {
if (BusyController.class.getResource("BusyScreen.fxml") == null) {
System.out.println("BusyScreen.fxml not found");
}
// create the bulk runner screen
FXMLLoader loader = new FXMLLoader(BusyController.class.getResource("BusyScreen.fxml"));
Parent root = null;
try {
root = (Parent) loader.load();
} catch (IOException ex) {
System.err.println(ex.getMessage());
}
BusyController busyController = loader.getController();
if (busyController == null) {
System.out.println("Busy controller is null");
}
if (root == null) {
System.out.println("Root is null");
}
busyController.scene = new Scene(root);
busyController.stage = new Stage();
busyController.stage.setScene(busyController.scene);
busyController.stage.setTitle("Busy");
busyController.stage.getIcons().add(Graphics.ICON);
busyController.stage.setResizable(false);
busyController.stage.initOwner(owner);
busyController.stage.initModality(Modality.WINDOW_MODAL);
//busyController.stage.setOpacity(0.9);
busyController.stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
System.out.println("Entered OnCloseRequest handler");
}
});
busyController.progressRun.setProgress(0d);
//busyController.progressMonitor = monitor;
// this will update the progress bar
if (monitor != null) {
busyController.monitorTask = new MonitorTask(monitor, busyController);
new Thread(busyController.monitorTask).start();
}
busyController.myThread = Thread.currentThread();
runnable.setController(busyController);
new Thread(runnable).start();
// see if the task finishes in less than a second
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
if (!busyController.finished) {
busyController.stage.show();
}
return busyController;
}
public Stage getStage() {
return this.stage;
}
protected void finished() {
finished = true;
if (monitorTask != null) {
monitorTask.stop(); // cancel the progress monitor
}
myThread.interrupt(); // wake the thread
if (Platform.isFxApplicationThread()) {
stage.hide();
} else {
Platform.runLater(new Runnable() {
@Override
public void run() {
stage.hide();
}
});
}
}
public boolean wasCancelled() {
return wasCancelled;
}
public void update(ProgressMonitor progress) {
if (progress.getMaxProgress() == 0) {
return;
}
Platform.runLater(new Runnable() {
@Override public void run() {
double prog = (double) progress.getProgress() / (double) progress.getMaxProgress();
progressRun.setProgress(prog);
}
});
}
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="95.0" prefWidth="251.0" style="-fx-background-color: lightgrey;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="minesweeper.explorer.busy.BusyController">
<children>
<Button layoutX="161.0" layoutY="58.0" mnemonicParsing="false" onAction="#handleOkayButton" prefHeight="25.0" prefWidth="72.0" text="Cancel" />
<ProgressBar fx:id="progressRun" layoutX="14.0" layoutY="27.0" prefHeight="25.0" prefWidth="219.0" progress="0.21" style="-fx-control-inner-background: lightgrey; -fx-accent: lightgreen;" />
<Label layoutX="14.0" layoutY="2.0" prefHeight="25.0" prefWidth="219.0" text="Processing...." textAlignment="RIGHT">
<font>
<Font size="14.0" />
</font>
</Label>
<Label fx:id="winPercentage" layoutX="9.0" layoutY="57.0" prefHeight="27.0" prefWidth="155.0">
<font>
<Font size="14.0" />
</font>
</Label>
</children>
</AnchorPane>

View File

@ -0,0 +1,40 @@
package minesweeper.explorer.busy;
import minesweeper.solver.utility.ProgressMonitor;
public class MonitorTask implements Runnable {
private final ProgressMonitor progress;
private final BusyController controller;
private boolean running = true;
public MonitorTask(ProgressMonitor progress, BusyController controller) {
this.progress = progress;
this.controller = controller;
}
@Override
public void run() {
while (running) {
controller.update(progress);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
running = false;
break;
}
}
System.out.println("Monitor task ending");
}
protected void stop() {
running = false;
}
}

View File

@ -0,0 +1,39 @@
package minesweeper.explorer.busy;
import java.util.concurrent.CountDownLatch;
public abstract class ParallelTask<T> implements Runnable {
private CountDownLatch executionCompleted = new CountDownLatch(1);
private BusyController controller;
@Override
public void run() {
doExecute();
executionCompleted.countDown();
controller.finished();
}
abstract public void doExecute();
public void await() {
try {
executionCompleted.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
controller.finished();
}
abstract public T getResult();
abstract public int getMaxProgress();
abstract public int getProgress();
protected void setController(BusyController controller) {
this.controller = controller;
}
}

View File

@ -0,0 +1,199 @@
/*
* 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.explorer.gamestate;
import minesweeper.explorer.structure.Board;
import minesweeper.explorer.structure.Tile;
import minesweeper.gamestate.GameStateModel;
import minesweeper.gamestate.GameStateModelViewer;
import minesweeper.settings.GameSettings;
import minesweeper.structure.Location;
/**
* A Version of Minesweeper which reads the board state from the explorer details
* @author David
*/
public class GameStateExplorer extends GameStateModelViewer {
private final int[][] board;
private boolean safeOpening;
private GameStateExplorer(GameSettings gameSettings) {
super(gameSettings, 0);
this.board = new int[width][height];
}
public final static GameStateExplorer build(Board board, int mines) throws Exception {
int width = board.getGameWidth();
int height = board.getGameHeight();
GameStateExplorer result = new GameStateExplorer(GameSettings.create(width, height, mines));
result.partialGame = true; // indicates that the board is not complete
//result.start(new Location(0,0));
result.safeOpening = false;
for (int x=0; x < width; x++) {
for (int y=0; y < height; y++) {
Tile tile = board.getTile(x, y);
if (tile.isFlagged()) {
result.setFlag(x, y);
} else if (!tile.isCovered()) {
result.board[x][y] = tile.getValue();
result.setRevealed(x, y);
} else {
result.setHidden(x, y);
}
}
}
result.start(new Location(0,0));
return result;
}
// in this gamestate the board is pre-built
@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 partial = "";
if (partialGame) {
partial = " (Mines missing!)";
}
return "explorer game";
}
/*
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,97 @@
package minesweeper.explorer.main;
import java.math.BigInteger;
import minesweeper.explorer.gamestate.GameStateExplorer;
import minesweeper.explorer.structure.Board;
import minesweeper.gamestate.GameStateModel;
import minesweeper.solver.Solver;
import minesweeper.solver.settings.SettingsFactory;
import minesweeper.solver.settings.SolverSettings;
import minesweeper.solver.settings.SettingsFactory.Setting;
public class BoardMonitor implements Runnable {
private final MainScreenController controller;
private int lastHash = 1;
public BoardMonitor(MainScreenController controller) {
this.controller = controller;
}
@Override
public void run() {
System.out.println("Starting monitor thread");
String msg = "";
boolean activeButtons = true;
while (true) {
Board board = controller.getCurrentBoard();
int hash = board.getHashValue();
// if we haven't placed too many mines
if (hash != lastHash) {
controller.removeIndicators();
if (board.getFlagsPlaced() <= controller.getTotalMines()) {
lastHash = hash;
GameStateModel gs = null;
try {
gs = GameStateExplorer.build(board, controller.getTotalMines());
SolverSettings settings = SettingsFactory.GetSettings(Setting.VERY_LARGE_ANALYSIS);
Solver solver = new Solver(gs, settings, true);
BigInteger solutionCount = solver.getSolutionCount();
if (solutionCount.signum() == 0) {
msg = "There are no solutions for this board";
activeButtons = false;
} else {
//System.out.println(solutionCount.bitLength());
activeButtons = true;
if (solutionCount.bitLength() > 50) {
msg = MainScreenController.EXPONENT_DISPLAY.format(solutionCount) + " solutions remain";
} else {
msg = MainScreenController.NUMBER_DISPLAY.format(solutionCount) + " solutions remain";
}
}
//System.out.println(solutionCount);
} catch (Exception e) {
msg = "Unable to calculate solution count: " + e.getMessage();
activeButtons = false;
e.printStackTrace();
}
} else {
msg = "Invalid number of mines";
activeButtons = false;
//System.out.println("Too many mines placed");
}
}
controller.setSolutionLine(msg);
controller.setButtonsEnabled(activeButtons);
board = null;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,125 @@
package minesweeper.explorer.main;
import java.io.IOException;
import java.text.DecimalFormat;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
public class Explorer extends Application {
public static String APPLICATION_NAME = "Minesweeper explorer";
public static String VERSION = "0.0";
{
System.out.println("Java version " + System.getProperty("java.runtime.version"));
System.out.println("Java vendor " + System.getProperty("java.vendor"));
}
public static final Background BACKGROUND_PINK = new Background(new BackgroundFill(Color.PINK, null, null));
public static final Background BACKGROUND_SILVER = new Background(new BackgroundFill(Color.SILVER, null, null));
//private final int tileSize = 24;
public static final DecimalFormat PERCENT = new DecimalFormat("#0.000%");
public static final DecimalFormat TWO_DP = new DecimalFormat("#0.00");
public static final Background GREY_BACKGROUND = new Background(new BackgroundFill(Color.LIGHTGREY, null, null));
private static Stage primaryStage;
//private static final Graphics graphics = new Graphics();
@Override
public void start(Stage primaryStage) {
System.out.println("Starting...");
if (Explorer.class.getResource("MainScreen.fxml") == null) {
System.out.println("MainScreen.fxml not found");
}
// create the helper screen
FXMLLoader loader = new FXMLLoader(Explorer.class.getResource("MainScreen.fxml"));
Parent root = null;
try {
root = (Parent) loader.load();
} catch (IOException ex) {
System.err.println(ex.getMessage());
}
Explorer.primaryStage = primaryStage;
try {
//BorderPane root = new BorderPane();
Scene scene = new Scene(root,900,550);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.setTitle(APPLICATION_NAME);
primaryStage.getIcons().add(Graphics.ICON);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
System.out.println("Main screen running...");
MainScreenController mainScreenController = loader.getController();
if (mainScreenController == null) {
System.out.println("MainScreenController not found");
}
mainScreenController.setGraphicsSet(new Graphics());
mainScreenController.newExpertBoard();
BoardMonitor monitor = new BoardMonitor(mainScreenController);
Thread monitorThread = new Thread(monitor, "Monitor");
monitorThread.setDaemon(true);
monitorThread.start();
// actions to perform when a close request is received
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
System.out.println("Minesweeper explorer has received a close request");
//mainScreenController.kill();
Platform.exit();
}
});
double x = primaryStage.getX();
double y = primaryStage.getY();
mainScreenController.getTileValueController().getStage().setX(x + primaryStage.getWidth());
mainScreenController.getTileValueController().getStage().setY(y);
System.out.println("...Startup finished.");
}
public static void main(String[] args) {
launch(args);
}
public static void setSubTitle(String subTitle) {
if (subTitle == null || subTitle.trim().isEmpty()) {
primaryStage.setTitle(APPLICATION_NAME);
} else {
primaryStage.setTitle(APPLICATION_NAME + " - " + subTitle);
}
}
}

View File

@ -0,0 +1,182 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package minesweeper.explorer.main;
import java.net.URL;
import javafx.scene.image.Image;
/**
*
* @author David
*/
public class Graphics {
public static final double[] SUPPORTED_SIZES = {12, 16, 24, 32, 48};
public class GraphicsSet {
private double size;
private Image hidden;
private Image mineBang;
private Image flag;
private Image[] number = new Image[9];
private Image mine;
public double getSize() {
return this.size;
}
public Image getLed(int c) {
return led[c];
}
public Image getSmallLed(int c) {
return smallLED[c];
}
public Image getNumber(int c) {
return number[c];
}
public Image getExploded() {
return mineBang;
}
public Image getMine() {
return mine;
}
public Image getFlag() {
return flag;
}
public Image getHidden() {
return hidden;
}
}
private static Image[] led = new Image[10];
private static Image[] smallLED = new Image[10];
public static final Image ICON;
static {
ICON = clean("resources/images/flagged.png", 24, 24);
}
private GraphicsSet[] graphicsSets;
public Graphics() {
graphicsSets = new GraphicsSet[SUPPORTED_SIZES.length];
int index=0;
for (double size: SUPPORTED_SIZES) {
GraphicsSet gs = new GraphicsSet();
gs.size = size;
gs.hidden = clean("resources/images/hidden.png", size);
gs.mineBang = clean("resources/images/exploded.png", size);
gs.flag = clean("resources/images/flagged.png", size);
gs.mine = clean("resources/images/mine.png", size);
gs.number[0] = clean("resources/images/0.png", size);
gs.number[1] = clean("resources/images/1.png", size);
gs.number[2] = clean("resources/images/2.png", size);
gs.number[3] = clean("resources/images/3.png", size);
gs.number[4] = clean("resources/images/4.png", size);
gs.number[5] = clean("resources/images/5.png", size);
gs.number[6] = clean("resources/images/6.png", size);
gs.number[7] = clean("resources/images/7.png", size);
gs.number[8] = clean("resources/images/8.png", size);
graphicsSets[index] = gs;
index++;
}
led[0] = clean("resources/images/led0.png", 24, 40);
led[1] = clean("resources/images/led1.png", 24, 40);
led[2] = clean("resources/images/led2.png", 24, 40);
led[3] = clean("resources/images/led3.png", 24, 40);
led[4] = clean("resources/images/led4.png", 24, 40);
led[5] = clean("resources/images/led5.png", 24, 40);
led[6] = clean("resources/images/led6.png", 24, 40);
led[7] = clean("resources/images/led7.png", 24, 40);
led[8] = clean("resources/images/led8.png", 24, 40);
led[9] = clean("resources/images/led9.png", 24, 40);
smallLED[0] = clean("resources/images/led0.png", 12, 20);
smallLED[1] = clean("resources/images/led1.png", 12, 20);
smallLED[2] = clean("resources/images/led2.png", 12, 20);
smallLED[3] = clean("resources/images/led3.png", 12, 20);
smallLED[4] = clean("resources/images/led4.png", 12, 20);
smallLED[5] = clean("resources/images/led5.png", 12, 20);
smallLED[6] = clean("resources/images/led6.png", 12, 20);
smallLED[7] = clean("resources/images/led7.png", 12, 20);
smallLED[8] = clean("resources/images/led8.png", 12, 20);
smallLED[9] = clean("resources/images/led9.png", 12, 20);
}
public static Image getLed(int c) {
return led[c];
}
public static Image getSmallLed(int c) {
return smallLED[c];
}
public GraphicsSet getGraphicsSet(double size) {
for (GraphicsSet gs: graphicsSets) {
if (gs.getSize() == size) {
return gs;
}
}
System.out.println("No graphics set with size " + size + " - defaulting to 24");
for (GraphicsSet gs: graphicsSets) {
if (gs.getSize() == 24) {
return gs;
}
}
System.err.println("No graphics set with size 24");
return null;
}
// in case we want to do some image manipulation
static private Image clean(String resourceName, double size) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
URL url = cl.getResource(resourceName);
if (url == null) {
System.out.println(resourceName + " not found");
}
return new Image(url.toExternalForm(), size, size, true, true);
}
// in case we want to do some image manipulation
static private Image clean(String resourceName, int width, int height) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
URL url = cl.getResource(resourceName);
if (url == null) {
System.out.println(resourceName + " not found");
}
return new Image(url.toExternalForm(), width, height, true, true);
}
}

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Do not edit this file it is generated by e(fx)clipse from ../src/minesweeper/explorer/main/MainScreen.fxgraph
-->
<?import javafx.scene.text.*?>
<?import javafx.scene.canvas.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="475.0" prefWidth="850.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="minesweeper.explorer.main.MainScreenController">
<children>
<AnchorPane fx:id="boardDisplayArea" layoutY="104.0" prefHeight="296.0" prefWidth="554.0" style="-fx-background-color: lightgrey;" AnchorPane.bottomAnchor="60.0" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="75.0" />
<MenuBar prefHeight="25.0" prefWidth="554.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<menus>
<Menu mnemonicParsing="false" text="Explorer">
<items>
<Menu mnemonicParsing="false" text="Reset board">
<items>
<MenuItem mnemonicParsing="false" onAction="#clearBoardToZero" text="To all zero" />
<MenuItem mnemonicParsing="false" onAction="#clearBoardToHidden" text="To all hidden" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="New board">
<items>
<MenuItem mnemonicParsing="false" onAction="#newBeginnerBoard" text="9x9/10" />
<MenuItem mnemonicParsing="false" onAction="#newIntermediateBoard" text="16x16/40" />
<MenuItem mnemonicParsing="false" onAction="#newExpertBoard" text="30x16/99" />
<MenuItem mnemonicParsing="false" onAction="#newCustomBoard" text="Custom size" />
</items>
</Menu>
</items>
</Menu>
<Menu mnemonicParsing="false" text="File">
<items>
<MenuItem mnemonicParsing="false" onAction="#saveBoard" text="Save board" />
<MenuItem mnemonicParsing="false" onAction="#loadBoard" text="Load board" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Settings">
<items>
<Menu mnemonicParsing="false" text="Tile size">
<items>
<RadioMenuItem fx:id="tileSize12" mnemonicParsing="false" onAction="#resizeBoard" text="12 pixels">
<toggleGroup>
<ToggleGroup fx:id="tilesize" />
</toggleGroup>
</RadioMenuItem>
<RadioMenuItem fx:id="tileSize16" mnemonicParsing="false" onAction="#resizeBoard" text="16 pixels" toggleGroup="$tilesize" />
<RadioMenuItem fx:id="tileSize24" mnemonicParsing="false" onAction="#resizeBoard" selected="true" text="24 Pixels" toggleGroup="$tilesize" />
<RadioMenuItem fx:id="tileSize32" mnemonicParsing="false" onAction="#resizeBoard" text="32 pixels" toggleGroup="$tilesize" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Rollout strength">
<items>
<CheckMenuItem fx:id="useLongTermSafetyRollout" mnemonicParsing="false" selected="true" text="Use long term safety" />
<CheckMenuItem fx:id="useTestModeRollout" mnemonicParsing="false" text="Use test mode" />
<RadioMenuItem fx:id="rolloutNoBF" mnemonicParsing="false" text="No Brute force">
<toggleGroup>
<ToggleGroup fx:id="rolloutStrength" />
</toggleGroup>
</RadioMenuItem>
<RadioMenuItem fx:id="rollout40" mnemonicParsing="false" text="Brute force 40 solns" toggleGroup="$rolloutStrength" />
<RadioMenuItem fx:id="rollout400" mnemonicParsing="false" selected="true" text="Brute force 400 solns" toggleGroup="$rolloutStrength" />
<RadioMenuItem fx:id="rollout4000" mnemonicParsing="false" text="Brute force 4000 solns" toggleGroup="$rolloutStrength" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Guess method">
<items>
<RadioMenuItem fx:id="safetyProgress" mnemonicParsing="false" text="Safety with progress">
<toggleGroup>
<ToggleGroup fx:id="guessMethod" />
</toggleGroup></RadioMenuItem>
<RadioMenuItem fx:id="secondarySafetyProgress" mnemonicParsing="false" selected="true" text="Secondary safety with progress" toggleGroup="$guessMethod" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Solver">
<items>
<CheckMenuItem fx:id="useBruteForce" mnemonicParsing="false" selected="true" text="Use brute force" />
<CheckMenuItem fx:id="useLongTermSafety" mnemonicParsing="false" selected="true" text="Use long term safety" />
<CheckMenuItem fx:id="use5050Detection" mnemonicParsing="false" selected="true" text="Use 50/50 detection" />
<CheckMenuItem fx:id="useTestMode" mnemonicParsing="false" text="Use test mode" />
</items>
</Menu>
</items>
</Menu>
</menus>
</MenuBar>
<AnchorPane layoutY="525.0" maxHeight="-Infinity" prefHeight="60.0" prefWidth="554.0" style="-fx-background-color: lightgrey;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
<children>
<Label fx:id="messageLine" layoutY="4.0" maxHeight="-Infinity" prefHeight="20.0" prefWidth="554.0" text="Label" AnchorPane.bottomAnchor="36.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
<font>
<Font size="14.0" />
</font></Label>
<Label fx:id="solutionLine" layoutY="30.0" maxHeight="-Infinity" prefHeight="20.0" prefWidth="554.0" text="Label" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="30.0">
<font>
<Font size="14.0" />
</font></Label>
</children></AnchorPane>
<AnchorPane fx:id="header" layoutY="25.0" maxHeight="-Infinity" prefHeight="50.0" prefWidth="554.0" style="-fx-background-color: lightgrey;" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="25.0" />
<AnchorPane layoutX="-38.0" layoutY="35.0" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="365.0" prefWidth="150.0" style="-fx-background-color: lightgrey;" AnchorPane.bottomAnchor="60.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="25.0">
<children>
<CheckBox fx:id="checkBoxLockMineCount" layoutX="7.0" layoutY="14.0" mnemonicParsing="false" text="Lock mine count">
<font>
<Font size="14.0" />
</font></CheckBox>
<Button fx:id="buttonSolve" layoutX="14.0" layoutY="131.0" mnemonicParsing="false" onAction="#solveButtonPressed" prefHeight="25.0" prefWidth="109.0" text="Solver's move" />
<Button fx:id="buttonAnalyse" layoutX="14.0" layoutY="175.0" mnemonicParsing="false" onAction="#analyseButtonPressed" prefHeight="25.0" prefWidth="109.0" text="Analyse" />
<Button fx:id="buttonRollout" layoutX="14.0" layoutY="224.0" mnemonicParsing="false" onAction="#rolloutButtonPressed" prefHeight="25.0" prefWidth="109.0" text="Rollout..." />
</children>
</AnchorPane>
</children>
</AnchorPane>

View File

@ -0,0 +1,799 @@
package minesweeper.explorer.main;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.Label;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import minesweeper.explorer.busy.BusyController;
import minesweeper.explorer.busy.ParallelTask;
import minesweeper.explorer.gamestate.GameStateExplorer;
import minesweeper.explorer.main.Graphics.GraphicsSet;
import minesweeper.explorer.rollout.RolloutController;
import minesweeper.explorer.structure.Board;
import minesweeper.explorer.structure.Expander;
import minesweeper.explorer.structure.LedDigits;
import minesweeper.explorer.structure.Tile;
import minesweeper.gamestate.GameStateModel;
import minesweeper.solver.RolloutGenerator;
import minesweeper.solver.Solver;
import minesweeper.solver.constructs.EvaluatedLocation;
import minesweeper.solver.settings.PlayStyle;
import minesweeper.solver.settings.SettingsFactory;
import minesweeper.solver.settings.SettingsFactory.Setting;
import minesweeper.solver.settings.SolverSettings.GuessMethod;
import minesweeper.solver.settings.SolverSettings;
import minesweeper.solver.utility.ProgressMonitor;
import minesweeper.structure.Action;
import minesweeper.structure.Area;
import minesweeper.structure.Location;
public class MainScreenController {
public final static DecimalFormat EXPONENT_DISPLAY = new DecimalFormat("##0.###E0");
public final static DecimalFormat NUMBER_DISPLAY = new DecimalFormat("#,##0");
private class Indicator extends Rectangle {
public Indicator(Action action) {
super(action.x * graphicsSet.getSize(), action.y * graphicsSet.getSize(), graphicsSet.getSize(), graphicsSet.getSize());
setMouseTransparent(true);
if (action.getAction() == Action.CLEAR) {
this.setFill(Color.GREEN);
if (!action.isCertainty()) {
this.setStroke(Color.DARKRED);
this.setStrokeWidth(5);
this.setStrokeType(StrokeType.INSIDE);
}
this.setOpacity(0.5d);
} else if (action.getAction() == Action.FLAG) {
this.setFill(Color.RED);
this.setOpacity(0.5d);
} if (action.getAction() == Action.CLEARALL) {
this.setFill(Color.BLUE);
this.setOpacity(0.5d);
}
}
public Indicator(Location action, Paint colour) {
super(action.x * graphicsSet.getSize(), action.y * graphicsSet.getSize(), graphicsSet.getSize(), graphicsSet.getSize());
setMouseTransparent(true);
this.setFill(colour);
this.setOpacity(0.5d);
}
}
@FXML private AnchorPane boardDisplayArea;
@FXML private AnchorPane header;
@FXML private Label messageLine;
@FXML private Label solutionLine;
@FXML private Button buttonSolve;
@FXML private Button buttonAnalyse;
@FXML private Button buttonRollout;
@FXML private CheckBox checkBoxLockMineCount;
@FXML private RadioMenuItem rollout4000;
@FXML private RadioMenuItem rollout400;
@FXML private RadioMenuItem rollout40;
@FXML private RadioMenuItem rolloutNoBF;
@FXML private RadioMenuItem tileSize12;
@FXML private RadioMenuItem tileSize16;
@FXML private RadioMenuItem tileSize24;
@FXML private RadioMenuItem tileSize32;
@FXML private RadioMenuItem secondarySafetyProgress;
@FXML private RadioMenuItem safetyProgress;
@FXML private CheckMenuItem useBruteForce;
@FXML private CheckMenuItem useLongTermSafety;
@FXML private CheckMenuItem use5050Detection;
@FXML private CheckMenuItem useTestMode;
@FXML private CheckMenuItem useLongTermSafetyRollout;
@FXML private CheckMenuItem useTestModeRollout;
private TileValuesController tileValueController;
private Graphics graphics;
private GraphicsSet graphicsSet;
private Expander boardExpander = new Expander(0, 0, 6, 24, Color.BLACK);
private Board currentBoard;
private LedDigits minesToFind;
private LedDigits minesPlaced;
private List<Indicator> indicators = new ArrayList<>();
private FileChooser fileChooser = new FileChooser();
private File fileSelected = null;
private RolloutController rolloutController;
@FXML
void initialize() {
System.out.println("Entered Main Screen Controller initialize method");
// set-up the filechooser
ExtensionFilter ef1 = new ExtensionFilter("All files", "*.*");
ExtensionFilter ef2 = new ExtensionFilter("Minesweeper board", "*.mine");
//ExtensionFilter ef3 = new ExtensionFilter("Minesweeper board", "*.board");
//ExtensionFilter ef4 = new ExtensionFilter("Minesweeper board", "*.mbf");
fileChooser.getExtensionFilters().addAll(ef1, ef2);
fileChooser.setSelectedExtensionFilter(ef2);
fileChooser.setInitialDirectory(new File(System.getProperty("user.dir")));
tileValueController = TileValuesController.launch(null);
}
@FXML
public void clearBoardToZero() {
System.out.println("At clear board to zero");
clearBoard(false);
}
@FXML
public void clearBoardToHidden() {
System.out.println("At clear board to hidden");
clearBoard(true);
}
@FXML
public void newBeginnerBoard() {
newBoard(9, 9, 10);
}
@FXML
public void newIntermediateBoard() {
newBoard(16, 16, 40);
}
@FXML
public void newExpertBoard() {
newBoard(30, 16, 99);
}
@FXML
public void resizeBoard() {
System.out.println("Resizing the board");
if (tileSize12.isSelected()) {
this.graphicsSet = new Graphics().getGraphicsSet(12);
} else if (tileSize16.isSelected()) {
this.graphicsSet = new Graphics().getGraphicsSet(16);
} else if (tileSize24.isSelected()) {
this.graphicsSet = new Graphics().getGraphicsSet(24);
} else {
this.graphicsSet = new Graphics().getGraphicsSet(32);
}
currentBoard.resizeBoard(graphicsSet);
//remove, recreate and add the board expander
getBoardDisplayArea().getChildren().remove(boardExpander);
boardExpander = new Expander(0, 0, 6, graphicsSet.getSize(), Color.BLACK);
boardExpander.setCenterX(currentBoard.getGameWidth() * graphicsSet.getSize());
boardExpander.setCenterY(currentBoard.getGameHeight() * graphicsSet.getSize());
getBoardDisplayArea().getChildren().add(boardExpander);
// remove all the indicators
removeIndicators();
}
@FXML
public void loadBoard() {
fileChooser.setTitle("Open game to analyse");
if (fileSelected != null) {
fileChooser.setInitialDirectory(fileSelected.getParentFile());
}
fileSelected = fileChooser.showOpenDialog(boardDisplayArea.getScene().getWindow());
if (fileSelected != null) {
try {
loadFromFile(fileSelected);
} catch (Exception e) {
setSolutionLine("Unable to load file: " + e.getMessage());
e.printStackTrace();
}
}
}
@FXML
public void saveBoard() {
fileChooser.setTitle("Save board position");
if (fileSelected != null) {
fileChooser.setInitialDirectory(fileSelected.getParentFile());
}
fileSelected = fileChooser.showSaveDialog(boardDisplayArea.getScene().getWindow());
try {
saveToFile(fileSelected);
} catch (Exception e) {
System.out.println("Error writing to output file");
e.printStackTrace();
}
}
@FXML
public void newCustomBoard() {
int boardWidth = (int) (boardExpander.getCenterX() / this.graphicsSet.getSize());
int boardHeight = (int) (boardExpander.getCenterY() / this.graphicsSet.getSize());
newBoard(boardWidth,boardHeight, 0);
}
@FXML
public void rolloutButtonPressed() {
System.out.println("Rollout button pressed");
GameStateModel gs = null;
try {
gs = GameStateExplorer.build(currentBoard, minesToFind.getValue() + minesPlaced.getValue());
} catch (Exception e) {
e.printStackTrace();
}
GuessMethod guessMethod;
if (secondarySafetyProgress.isSelected()) {
guessMethod = GuessMethod.SECONDARY_SAFETY_PROGRESS;
} else {
guessMethod = GuessMethod.SAFETY_PROGRESS;
}
SolverSettings settings;
if (rolloutNoBF.isSelected()) {
settings = SettingsFactory.GetSettings(Setting.NO_BRUTE_FORCE).setGuessMethod(guessMethod);
} else if (rollout40.isSelected()) {
settings = SettingsFactory.GetSettings(Setting.TINY_ANALYSIS).setGuessMethod(guessMethod);
} else if (rollout400.isSelected()) {
settings = SettingsFactory.GetSettings(Setting.SMALL_ANALYSIS).setGuessMethod(guessMethod);
} else {
settings = SettingsFactory.GetSettings(Setting.LARGE_ANALYSIS).setGuessMethod(guessMethod);
}
settings.setLongTermSafety(useLongTermSafetyRollout.isSelected());
settings.setTestMode(useTestModeRollout.isSelected());
Solver solver = new Solver(gs, settings, true);
try {
RolloutGenerator gen = solver.getRolloutGenerator();
if (rolloutController == null) {
rolloutController = RolloutController.launch(boardDisplayArea.getScene().getWindow(), gen, settings);
}
rolloutController.show(gen, settings);
} catch (Exception e1) {
e1.printStackTrace();
}
}
@FXML
public void analyseButtonPressed() {
System.out.println("Analyse button pressed");
currentBoard.setGameInformation(null, 0);
GameStateModel gs = null;
try {
gs = GameStateExplorer.build(currentBoard, minesToFind.getValue() + minesPlaced.getValue());
} catch (Exception e) {
e.printStackTrace();
}
SolverSettings settings = SettingsFactory.GetSettings(Setting.SMALL_ANALYSIS);
Solver solver = new Solver(gs, settings, false);
solver.setPlayStyle(PlayStyle.NO_FLAG);
ProgressMonitor pm = new ProgressMonitor();
// run this task in parallel while locking the screen against any other actions
ParallelTask<Boolean> pt = new ParallelTask<Boolean>() {
@Override
public void doExecute() {
try {
int hash = currentBoard.getHashValue();
currentBoard.setGameInformation(solver.runTileAnalysis(pm), hash);
} catch (Exception e) {
e.printStackTrace();
setSolutionLine("Unable to process:" + e.getMessage());
}
Platform.runLater(new Runnable() {
@Override
public void run() {
}
});
}
@Override
public Boolean getResult() {
return true;
}
@Override
public int getMaxProgress() {
return pm.getMaxProgress();
}
@Override
public int getProgress() {
return pm.getProgress();
}
};
BusyController.launch(boardDisplayArea.getScene().getWindow(), pt, pm);
}
@FXML
public void solveButtonPressed() {
System.out.println("Solve button pressed");
GameStateModel gs = null;
try {
gs = GameStateExplorer.build(currentBoard, minesToFind.getValue() + minesPlaced.getValue());
} catch (Exception e) {
e.printStackTrace();
}
GuessMethod guessMethod;
if (secondarySafetyProgress.isSelected()) {
guessMethod = GuessMethod.SECONDARY_SAFETY_PROGRESS;
} else {
guessMethod = GuessMethod.SAFETY_PROGRESS;
}
SolverSettings settings;
if (this.useBruteForce.isSelected()) {
settings = SettingsFactory.GetSettings(Setting.MAX_ANALYSIS).setGuessMethod(guessMethod);
} else {
settings = SettingsFactory.GetSettings(Setting.NO_BRUTE_FORCE).setGuessMethod(guessMethod);
}
settings.setLongTermSafety(this.useLongTermSafety.isSelected());
settings.setTestMode(this.useTestMode.isSelected());
settings.set5050Check(this.use5050Detection.isSelected());
//SolverSettings settings = SettingsFactory.GetSettings(Setting.MAX_ANALYSIS).setGuessMethod(guessMethod);
Solver solver = new Solver(gs, settings, true);
// run this task in parallel while locking the screen against any other actions
ParallelTask<Action[]> pt = new ParallelTask<Action[]>() {
@Override
public void doExecute() {
solver.start();
Platform.runLater(new Runnable() {
@Override
public void run() {
Action[] actions = solver.getResult();
if (actions.length == 0) {
messageLine.setText("No suggestion returned by the solver");
} else {
Action a = actions[0];
messageLine.setText(a.toString());
removeIndicators();
for (Action action: actions) {
indicators.add(new Indicator(action));
}
List<EvaluatedLocation> els = solver.getEvaluatedLocations();
if (els != null) {
for (EvaluatedLocation el: els) {
// don't show evaluated positions which are actually chosen to be played
boolean ignore = false;
for (Action action: actions) {
if (el.equals(action)) {
ignore = true;
}
}
if (!ignore) {
indicators.add(new Indicator(el, Color.ORANGE));
}
}
}
Area dead = solver.getDeadLocations();
if (dead != null) {
for (Location loc: dead.getLocations()) {
// don't show evaluated positions which are actually chosen to be played
boolean ignore = false;
for (Action action: actions) {
if (loc.equals(action)) {
ignore = true;
}
}
if (!ignore) {
indicators.add(new Indicator(loc, Color.BLACK));
}
}
}
currentBoard.getChildren().addAll(indicators);
}
}
});
}
@Override
public Action[] getResult() {
return solver.getResult();
}
@Override
public int getMaxProgress() {
return 0;
}
@Override
public int getProgress() {
return 0;
}
};
BusyController.launch(boardDisplayArea.getScene().getWindow(), pt, null);
}
protected void removeIndicators() {
if (Platform.isFxApplicationThread()) {
doRemoveIndicators();
} else {
Platform.runLater(new Runnable() {
@Override
public void run() {
doRemoveIndicators();
}
});
}
}
private void doRemoveIndicators() {
currentBoard.getChildren().removeAll(indicators);
indicators.clear();
}
private void clearBoard(boolean covered) {
removeIndicators();
//currentBoard.getChildren().removeAll(indicators);
//indicators.clear();
if (checkBoxLockMineCount.isSelected()) {
}
if (checkBoxLockMineCount.isSelected() && covered) {
minesToFind.setValue(minesToFind.getValue() + minesPlaced.getValue());
} else {
minesToFind.setValue(0);
}
this.currentBoard.clearBoard(covered);
}
private void newBoard(int width, int height, int mines) {
// create new board
Board newBoard = new Board(this, width, height);
newBoard.clearBoard(true); // all covered to start with
setNewBoard(newBoard, mines);
}
public void loadFromFile(File file) throws Exception {
int width;
int height;
int mines;
int minesCount = 0;
Board result;
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 Board(this, 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()) {
Tile tile = result.getTile(cx, cy);
if (c == 'M' || c == 'F') {
//System.out.println("Set mine " + tile.asText());
minesCount++;
result.setFlag(tile, true);
} else if (c == 'm') {
//System.out.println("unfound mine " + tile.asText());
tile.setCovered(true);
}else if (c != 'H' && c != 'h') {
int val = Character.getNumericValue(c);
//System.out.println("Set value " + tile.asText() + " to " + val);
tile.setCovered(false);
tile.setValue(val);
} else {
//System.out.println("Set covered " + tile.asText());
tile.setCovered(true);
}
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;
}
int minesLeft = Math.max(0, mines - minesCount);
setNewBoard(result, minesLeft);
}
private void setNewBoard(Board board, int minesLeft) {
// tidy up old details
if (minesPlaced != null) {
minesPlaced.removeValueListener();
}
// remove current board graphics
getBoardDisplayArea().getChildren().clear();
indicators.clear();
// create new board
currentBoard = board;
checkBoxLockMineCount.setSelected(minesLeft != 0);
boardExpander.setCenterX(board.getGameWidth() * graphicsSet.getSize());
boardExpander.setCenterY(board.getGameHeight() * graphicsSet.getSize());
getBoardDisplayArea().getChildren().addAll(currentBoard, boardExpander);
minesToFind = new LedDigits(4);
minesToFind.relocate(10, 5);
minesToFind.setBackground(Explorer.GREY_BACKGROUND);
getHeader().getChildren().add(minesToFind);
minesToFind.setValue(minesLeft);
minesPlaced = new LedDigits(4, true);
minesPlaced.relocate(120, 5);
minesPlaced.setBackground(Explorer.GREY_BACKGROUND);
minesPlaced.setValueListener(currentBoard.getMinesPlacedProperty());
minesPlaced.setValue(currentBoard.getFlagsPlaced());
getHeader().getChildren().add(minesPlaced);
messageLine.setText("Build a board");
solutionLine.setText("");
Explorer.setSubTitle(board.getGameWidth() + " x " + board.getGameHeight());
}
private void saveToFile(File file) throws Exception {
if (file == null) {
return;
}
int width = currentBoard.getGameWidth();
int height = currentBoard.getGameHeight();
int mines = currentBoard.getFlagsPlaced() + minesToFind.getValue();
List<String> records = new ArrayList<>();
String header = width + "x" + height + "x" + mines;
records.add(header);
for (int y=0; y < height; y++) {
StringBuilder record = new StringBuilder();
for (int x=0; x < width; x++) {
Tile tile = currentBoard.getTile(x, y);
if (tile.isFlagged()) {
record.append("M");
} else if (tile.isCovered()) {
record.append("h");
} else {
record.append(String.valueOf(tile.getValue()));
}
}
records.add(record.toString());
}
records.add("Game created by Minesweeper Explorer vsn " + Explorer.VERSION);
try (PrintStream output = new PrintStream(file)) {
for (String record: records) {
output.println(record);
}
} catch (Exception e) {
throw e;
}
}
public Board getCurrentBoard() {
return this.currentBoard;
}
public int getTotalMines() {
return minesToFind.getValue() + minesPlaced.getValue();
}
public LedDigits getMinesToFindController() {
return minesToFind;
}
public void setGraphicsSet(Graphics graphics) {
this.graphics = graphics;
this.graphicsSet = graphics.getGraphicsSet(24);
boardExpander = new Expander(0, 0, 6, graphicsSet.getSize(), Color.BLACK);
}
public GraphicsSet getGraphicsSet() {
return this.graphicsSet;
}
public AnchorPane getHeader() {
return header;
}
public AnchorPane getBoardDisplayArea() {
return boardDisplayArea;
}
public TileValuesController getTileValueController() {
return this.tileValueController;
}
public boolean mineCountLocked() {
return checkBoxLockMineCount.isSelected();
}
public void setSolutionLine(String text) {
if (Platform.isFxApplicationThread()) {
solutionLine.setText(text);
} else {
Platform.runLater(new Runnable() {
@Override
public void run() {
solutionLine.setText(text);
}
});
}
}
public void setButtonsEnabled(boolean enable) {
if (Platform.isFxApplicationThread()) {
buttonSolve.setDisable(!enable);
buttonAnalyse.setDisable(!enable);
buttonRollout.setDisable(!enable);
} else {
Platform.runLater(new Runnable() {
@Override
public void run() {
buttonSolve.setDisable(!enable);
buttonAnalyse.setDisable(!enable);
buttonRollout.setDisable(!enable);
}
});
}
}
}

View File

@ -0,0 +1,40 @@
package minesweeper.explorer.main;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import minesweeper.solver.constructs.InformationLocation.ByValue;
public class TileValueData {
private final StringProperty value;
private final StringProperty probability;
private final StringProperty clears;
public TileValueData(ByValue bv) {
this.value = new SimpleStringProperty(String.valueOf(bv.value));
this.probability = new SimpleStringProperty(Explorer.PERCENT.format(bv.probability));
this.clears = new SimpleStringProperty(String.valueOf(bv.clears));
}
public TileValueData(String value, String prob, String clears) {
this.value = new SimpleStringProperty(value);
this.probability = new SimpleStringProperty(prob);
this.clears = new SimpleStringProperty(clears);
}
public StringProperty probabilityProperty() {
return probability;
}
public StringProperty clearsProperty() {
return clears;
}
public StringProperty valueProperty() {
return value;
}
}

View File

@ -0,0 +1,186 @@
package minesweeper.explorer.main;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import minesweeper.solver.constructs.InformationLocation;
import minesweeper.solver.constructs.InformationLocation.ByValue;
public class TileValuesController {
private final static String WINDOW_NAME = "Tile values";
private Stage stage;
private Scene scene;
@FXML private TableView<TileValueData> resultsTable;
@FXML private TableColumn<TileValueData, String> columnValue;
@FXML private TableColumn<TileValueData, String> columnProbability;
@FXML private TableColumn<TileValueData, String> columnClears;
private boolean closed = false;
private ObservableList<TileValueData> items = FXCollections.observableArrayList();
@FXML
void initialize() {
System.out.println("Entered Tile Values Screen initialize method");
}
public static TileValuesController launch(Window owner) {
if (TileValuesController.class.getResource("TileValuesScreen.fxml") == null) {
System.out.println("TileValuesScreen.fxml not found");
}
// create the bulk runner screen
FXMLLoader loader = new FXMLLoader(TileValuesController.class.getResource("TileValuesScreen.fxml"));
Parent root = null;
try {
root = (Parent) loader.load();
} catch (IOException ex) {
System.err.println(ex.getMessage());
}
TileValuesController tileValuesController = loader.getController();
if (tileValuesController == null) {
System.out.println("Custom is null");
}
if (root == null) {
System.out.println("Root is null");
}
tileValuesController.scene = new Scene(root);
tileValuesController.stage = new Stage();
tileValuesController.stage.setScene(tileValuesController.scene);
tileValuesController.stage.setTitle(WINDOW_NAME);
tileValuesController.stage.getIcons().add(Graphics.ICON);
tileValuesController.stage.setResizable(true);
//custom.stage.initOwner(owner);
//custom.stage.initModality(Modality.WINDOW_MODAL);
tileValuesController.stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
System.out.println("Entered OnCloseRequest handler for Tile Values Screen");
tileValuesController.closed = true;
tileValuesController.items.clear(); // clear down the table to encourage the items to be garbage collected
}
});
tileValuesController.resultsTable.setItems(tileValuesController.items);
tileValuesController.resultsTable.getSelectionModel();
tileValuesController.columnValue.setCellValueFactory(new PropertyValueFactory<TileValueData, String>("value"));
tileValuesController.columnProbability.setCellValueFactory(new PropertyValueFactory<TileValueData, String>("probability"));
tileValuesController.columnClears.setCellValueFactory(new PropertyValueFactory<TileValueData, String>("clears"));
tileValuesController.getStage().show();
System.out.println("Tile values screen running...");
//System.out.println("Columns = " + custom.resultsTable.getColumns().size());
return tileValuesController;
}
public Stage getStage() {
return this.stage;
}
public boolean update(final InformationLocation il) {
if (closed) { // if the window has been closed then let anyone who calls know
return false;
}
Platform.runLater(new Runnable() {
@Override public void run() {
items.clear();
if (il == null || il.getByValueData() == null) {
return;
}
stage.setTitle(WINDOW_NAME + " (" + il.x + "," + il.y + ")");
BigDecimal safe2Prog = il.getSecondarySafety().multiply(BigDecimal.ONE.add(il.getProgressProbability().multiply(new BigDecimal("0.1")))); // = 2nd Safety * (1 + progress*0.1);
BigDecimal essrSafe = null;
if (il.getSafety().compareTo(BigDecimal.ZERO) != 0) {
essrSafe = il.getExpectedSolutionSpaceReduction().divide(il.getSafety(), 3, RoundingMode.HALF_UP);
}
BigDecimal safetyESSL = BigDecimal.ONE.subtract(il.getExpectedSolutionSpaceReduction()).multiply(il.getSecondarySafety()).multiply(il.getLongTermSafety()).setScale(3, RoundingMode.HALF_UP);
for (ByValue bv: il.getByValueData()) {
//System.out.println(bv.probability);
items.add(new TileValueData(bv));
}
items.add(new TileValueData("Safety", Explorer.PERCENT.format(il.getSafety()), ""));
items.add(new TileValueData("Progress", Explorer.PERCENT.format(il.getProgressProbability()), Explorer.TWO_DP.format(il.getExpectedClears())));
items.add(new TileValueData("Safety.prog20%", Explorer.PERCENT.format(il.getWeighting()), ""));
items.add(new TileValueData("2nd Safety", Explorer.PERCENT.format(il.getSecondarySafety()), ""));
items.add(new TileValueData("2nd Safety.prog10%", Explorer.PERCENT.format(safe2Prog), ""));
items.add(new TileValueData("Long Term Safety", Explorer.PERCENT.format(il.getLongTermSafety()), ""));
items.add(new TileValueData("Exp Soln left", Explorer.PERCENT.format(il.getExpectedSolutionSpaceReduction()), ""));
if (essrSafe != null) {
items.add(new TileValueData("ESL/Safe", essrSafe.toPlainString(), ""));
}
if (il.getMTanzerRatio() != null) {
items.add(new TileValueData("prog/(1-safe)", il.getMTanzerRatio().toPlainString(), ""));
} else {
items.add(new TileValueData("prog/(1-safe)", "Infinity", ""));
}
items.add(new TileValueData("ESSR.Safety", safetyESSL.toPlainString(), ""));
//if (il.getPoweredRatio() != null) {
// items.add(new TileValueData("Power/(1-safe)", il.getPoweredRatio().toPlainString(), ""));
//} else {
// items.add(new TileValueData("Power/(1-safe)", "Infinity", ""));
//}
}
});
return true;
}
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="552.0" prefWidth="348.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="minesweeper.explorer.main.TileValuesController">
<children>
<TableView fx:id="resultsTable" prefHeight="514.0" prefWidth="446.0" AnchorPane.bottomAnchor="40.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn fx:id="columnValue" prefWidth="159.0" style="-fx-alignment: CENTER;" text="Value" />
<TableColumn fx:id="columnProbability" prefWidth="96.0" style="-fx-alignment: CENTER;" text="Probability" />
<TableColumn fx:id="columnClears" prefWidth="91.0" style="-fx-alignment: CENTER;" text="Living clears" />
</columns>
</TableView>
</children>
</AnchorPane>

View File

@ -0,0 +1 @@
/* JavaFX CSS - Leave this comment until you have at least create one rule which uses -fx-Property */

View File

@ -0,0 +1,207 @@
package minesweeper.explorer.rollout;
import java.math.BigDecimal;
import java.util.Random;
import minesweeper.gamestate.GameStateModel;
import minesweeper.gamestate.MoveMethod;
import minesweeper.solver.RolloutGenerator;
import minesweeper.solver.Solver;
import minesweeper.solver.settings.SolverSettings;
import minesweeper.structure.Action;
import minesweeper.structure.Location;
public class BulkRunner implements Runnable {
private boolean stop = false;
private final int maxSteps;
private final RolloutController controller;
private final Location startLocation;
private final Location safeTile;
private final RolloutGenerator rollout;
private final SolverSettings preferences;
private final long seed;
//private final Random seeder;
private int steps = 0;
private int wins = 0;
private boolean[] mastery = new boolean[100];
private int masteryCount = 0;
private int maxMasteryCount = 0;
private int winStreak;
private int maxWinStreak;
private int guesses;
private double fairness = 0;
//private ResultsController resultsController;
private boolean showGames;
private boolean winsOnly;
public BulkRunner(RolloutController controller, int iterations, RolloutGenerator rollout, Location startLocation, boolean safeStart, SolverSettings preferences, long seed) {
this.controller = controller;
this.maxSteps = iterations;
this.rollout = rollout;
this.startLocation = startLocation;
this.preferences = preferences;
this.seed = seed;
if (safeStart) {
this.safeTile = startLocation;
} else {
this.safeTile = null;
}
if (showGames) {
//resultsController = ResultsController.launch(null, gameSettings, gameType);
}
}
@Override
public void run() {
System.out.println("At BulkRunner run method using seed " + seed);
Random seeder = new Random(seed);
while (!stop && steps < maxSteps) {
GameStateModel gs = rollout.generateGame(seeder.nextLong(), safeTile);
Solver solver = new Solver(gs, preferences, false);
gs.doAction(new Action(startLocation, Action.CLEAR));
int state = gs.getGameState();
boolean win;
if (state == GameStateModel.LOST || state == GameStateModel.WON) { // if we have won or lost on the first move nothing more to do
win = (state == GameStateModel.WON);
} else { // otherwise use the solver to play the game
win = playGame(gs, solver);
}
// reduce mastery if the game 100 ago was a win
int masteryIndex = steps % 100;
if (mastery[masteryIndex]) {
masteryCount--;
}
if (win) {
wins++;
// update win streak
winStreak++;
maxWinStreak = Math.max(maxWinStreak, winStreak);
// update mastery
mastery[masteryIndex] = true;
masteryCount++;
maxMasteryCount = Math.max(masteryCount, maxMasteryCount);
} else {
winStreak = 0;
mastery[masteryIndex] = false;
}
/*
if (showGames && (win || !win && !winsOnly)) {
if (!resultsController.update(gs)) { // this returns false if the window has been closed
showGames = false;
resultsController = null;
System.out.println("Results window has been closed... will no longer send data to it");
}
}
*/
steps++;
//controller.update(steps, maxSteps, wins, guesses, fairness, maxWinStreak, maxMasteryCount);
}
stop = true;
System.out.println("BulkRunner run method ending with wins = " + wins + " of " + steps);
}
private boolean playGame(GameStateModel gs, Solver solver) {
int state;
play: while (true) {
Action[] moves;
try {
solver.start();
moves = solver.getResult();
} catch (Exception e) {
System.out.println("Game " + gs.showGameKey() + " has thrown an exception! ");
e.printStackTrace();
stop = true;
return false;
}
if (moves.length == 0) {
System.err.println("No moves returned by the solver for game " + gs.showGameKey());
stop = true;
return false;
}
// play all the moves until all done, or the game is won or lost
for (int i=0; i < moves.length; i++) {
boolean result = gs.doAction(moves[i]);
state = gs.getGameState();
// keep track of how many guesses and their fairness
if (state == GameStateModel.STARTED || state == GameStateModel.WON) {
if (!moves[i].isCertainty() ) {
guesses++;
fairness = fairness + 1d;
}
} else { // otherwise the guess resulted in a loss
if (!moves[i].isCertainty()) {
guesses++;
BigDecimal prob = moves[i].getBigProb();
fairness = fairness - prob.doubleValue() / (1d - prob.doubleValue());
}
}
if (state == GameStateModel.LOST || state == GameStateModel.WON) {
break play;
}
}
}
if (state == GameStateModel.LOST) {
return false;
} else {
return true;
}
}
public void forceStop() {
System.out.println("Bulk run being requested to stop");
stop = true;
}
public boolean isFinished() {
return stop;
}
public int getWins() {
return wins;
}
}

View File

@ -0,0 +1,359 @@
package minesweeper.explorer.rollout;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Random;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import minesweeper.explorer.main.Explorer;
import minesweeper.explorer.main.Graphics;
import minesweeper.solver.RolloutGenerator;
import minesweeper.solver.bulk.BulkEvent;
import minesweeper.solver.bulk.BulkListener;
import minesweeper.solver.bulk.BulkRollout;
import minesweeper.solver.settings.SolverSettings;
import minesweeper.solver.utility.Timer;
import minesweeper.structure.Location;
public class RolloutController {
private final static DecimalFormat PERCENT = new DecimalFormat("#0.000%");
private final static int DEFAULT_ROLLOUTS = 10000;
private final static String[] THREAD_DROPDOWN = {"on 1 Thread", "on 2 Threads", "on 3 Threads", "on 4 Threads", "on 6 Threads", "on 8 Threads"};
private final static int[] THREAD_NUMBER = {1, 2, 3, 4, 6, 8};
@FXML private AnchorPane window;
@FXML private TextField gameCount;
@FXML private TextField gameSeed;
@FXML private Label winPercentage;
@FXML private Label fairnessPercentage;
@FXML private Label bestWinStreak;
@FXML private Label bestMastery;
@FXML private Label totalGuesses;
@FXML private ProgressBar progressRun;
@FXML private Label progressRunLabel;
@FXML private TextField startLocX;
@FXML private TextField startLocY;
@FXML private CheckBox safeStart;
@FXML private Label messageBox;
@FXML private ChoiceBox<String> threadsCombo;
private Stage stage;
private Scene scene;
private int gamesMax;
private long gameGenerator;
//private GameSettings gameSettings;
//private GameType gameType;
private Location startLocation;
private SolverSettings preferences;
private RolloutGenerator generator;
//private ResultsController resultsController;
//private BulkRunner bulkRunner;
private BulkRollout bulkRunner;
private boolean wasCancelled = false;
@FXML
void initialize() {
System.out.println("Entered Rollout Screen initialize method");
}
@FXML
private void handleNewSeedButton(ActionEvent event) {
gameSeed.setText(String.valueOf(new Random().nextLong()));
}
@FXML
private void handleOkayButton(ActionEvent event) {
System.out.println("handleOkayButton method entered");
if (bulkRunner != null && !bulkRunner.isFinished()) {
System.out.println("Previous bulk run still running");
return;
}
if (gameCount.getText().trim().isEmpty()) {
gamesMax = DEFAULT_ROLLOUTS;
} else {
try {
gamesMax = Integer.parseInt(gameCount.getText().trim());
if (gamesMax < 1) {
gamesMax = DEFAULT_ROLLOUTS;
}
} catch (NumberFormatException e) {
gamesMax = DEFAULT_ROLLOUTS;
}
}
gameCount.setText(String.valueOf(gamesMax));
if (gameSeed.getText().trim().isEmpty()) {
gameGenerator = new Random().nextLong();
} else {
try {
gameGenerator = Long.parseLong(gameSeed.getText().trim());
} catch (NumberFormatException e) {
gameGenerator = new Random().nextLong();
//gameSeed.setText("");
}
}
startLocation = null;
if (!startLocX.getText().trim().isEmpty() && !startLocY.getText().trim().isEmpty()) {
try {
int startX = Integer.parseInt(startLocX.getText().trim());
int startY = Integer.parseInt(startLocY.getText().trim());
if (startX >= 0 && startX < generator.getWidth() && startY >= 0 && startY < generator.getHeight()) {
startLocation = new Location(startX, startY);
System.out.println("Start location set to " + startLocation.toString());
} else {
System.out.println("Start location out of bounds");
}
} catch (NumberFormatException e) {
System.out.println("Start location can't be parsed");
}
} else {
System.out.println("Start location is not populated");
}
if (startLocation == null) {
startLocX.setBackground(Explorer.BACKGROUND_PINK);
startLocY.setBackground(Explorer.BACKGROUND_PINK);
} else {
startLocX.setBackground(Explorer.BACKGROUND_SILVER);
startLocY.setBackground(Explorer.BACKGROUND_SILVER);
gameSeed.setText(String.valueOf(gameGenerator));
String dropdown = threadsCombo.getValue();
int threads = 2;
for (int i = 0; i < THREAD_DROPDOWN.length; i++) {
if (dropdown.equals(THREAD_DROPDOWN[i])) {
threads = THREAD_NUMBER[i];
break;
}
}
bulkRunner = new BulkRollout(new Random(gameGenerator), gamesMax, generator, startLocation, safeStart.isSelected(), preferences, threads);
bulkRunner.registerEventListener(new BulkListener() {
@Override
public void intervalAction(BulkEvent event) {
update(event);
}
});
messageBox.setText("Starting...");
new Thread(bulkRunner, "Bulk Run").start();
}
}
@FXML
private void handleCancelButton(ActionEvent event) {
System.out.println("handleCancelButton method entered");
stage.close();
}
public void show(RolloutGenerator generator, SolverSettings preferences ) {
this.generator = generator;
this.preferences = preferences;
this.stage.setTitle("Rollout - " + generator + " - " + preferences.getGuessMethod().name);
this.stage.show();
}
public static RolloutController launch(Window owner, RolloutGenerator generator, SolverSettings preferences ) {
if (RolloutController.class.getResource("RolloutScreen.fxml") == null) {
System.out.println("RolloutScreen.fxml not found");
}
// create the bulk runner screen
FXMLLoader loader = new FXMLLoader(RolloutController.class.getResource("RolloutScreen.fxml"));
Parent root = null;
try {
root = (Parent) loader.load();
} catch (IOException ex) {
System.err.println(ex.getMessage());
}
RolloutController custom = loader.getController();
if (custom == null) {
System.out.println("Custom is null");
}
if (root == null) {
System.out.println("Root is null");
}
//custom.generator = generator;
//custom.preferences = preferences;
custom.scene = new Scene(root);
custom.stage = new Stage();
custom.stage.setScene(custom.scene);
custom.stage.setTitle("Rollout");
custom.stage.getIcons().add(Graphics.ICON);
custom.stage.setResizable(false);
custom.stage.initOwner(owner);
custom.stage.initModality(Modality.WINDOW_MODAL);
custom.stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
System.out.println("Entered OnCloseRequest handler");
if (custom.bulkRunner != null && !custom.bulkRunner.isFinished()) {
custom.bulkRunner.stop();
}
System.gc();
}
});
custom.gameCount.setText(String.valueOf(DEFAULT_ROLLOUTS));
custom.progressRun.setProgress(0d);
custom.progressRunLabel.setText("");
custom.threadsCombo.getItems().addAll(THREAD_DROPDOWN);
custom.threadsCombo.setValue(THREAD_DROPDOWN[1]);
//custom.getStage().show();
return custom;
}
public Stage getStage() {
return this.stage;
}
public boolean wasCancelled() {
return wasCancelled;
}
private void update(BulkEvent event) {
Platform.runLater(new Runnable() {
@Override public void run() {
double prog = (double) event.getGamesPlayed() / (double) event.getGamesToPlay();
progressRun.setProgress(prog);
progressRunLabel.setText(event.getGamesPlayed() + "(" + event.getGamesWon() + ") /" + event.getGamesToPlay());
double winPerc = (double) event.getGamesWon() / (double) event.getGamesPlayed();
double err = Math.sqrt(winPerc * ( 1- winPerc) / (double) event.getGamesPlayed()) * 1.9599d;
winPercentage.setText(PERCENT.format(winPerc) + " +/- " + PERCENT.format(err));
totalGuesses.setText(String.valueOf(event.getTotalGuesses()));
String fairnessText = PERCENT.format(event.getFairness());
fairnessPercentage.setText(fairnessText);
bestWinStreak.setText(String.valueOf(event.getWinStreak()));
bestMastery.setText(String.valueOf(event.getMastery()));
if (event.getGamesPlayed() == event.getGamesToPlay()) {
messageBox.setText("Duration " + Timer.humanReadable(event.getTimeSoFar()));
} else if (event.isFinished()) {
messageBox.setText("Stopped after " + Timer.humanReadable(event.getTimeSoFar()));
} else {
messageBox.setText("Time left " + Timer.humanReadable(event.getEstimatedTimeLeft()));
}
}
});
}
/*
public void update(int steps, int maxSteps, int wins, int guesses, double fairness, int winStreak, int mastery) {
Platform.runLater(new Runnable() {
@Override public void run() {
double prog = (double) steps / (double) maxSteps;
progressRun.setProgress(prog);
progressRunLabel.setText(steps + "(" + wins + ") /" + maxSteps);
double winPerc = (double) wins / (double) steps;
double err = Math.sqrt(winPerc * ( 1- winPerc) / (double) steps) * 1.9599d;
winPercentage.setText(PERCENT.format(winPerc) + " +/- " + PERCENT.format(err));
totalGuesses.setText(String.valueOf(guesses));
String fairnessText;
if (guesses == 0) {
fairnessText = "--";
} else {
fairnessText = PERCENT.format(fairness / guesses);
}
fairnessPercentage.setText(fairnessText);
bestWinStreak.setText(String.valueOf(winStreak));
bestMastery.setText(String.valueOf(mastery));
}
});
}
*/
}

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="277.0" prefWidth="400.0" style="-fx-background-color: lightgrey;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="minesweeper.explorer.rollout.RolloutController">
<children>
<Button layoutX="317.0" layoutY="246.0" mnemonicParsing="false" onAction="#handleOkayButton" prefHeight="25.0" prefWidth="72.0" text="Start" />
<ProgressBar fx:id="progressRun" layoutX="94.0" layoutY="213.0" prefHeight="25.0" prefWidth="293.0" progress="0.21" style="-fx-control-inner-background: lightgrey; -fx-accent: lightgreen;" />
<TextField fx:id="gameCount" layoutX="95.0" layoutY="18.0" prefHeight="25.0" prefWidth="149.0" style="-fx-background-color: silver;" />
<Label layoutX="15.0" layoutY="132.0" prefHeight="25.0" prefWidth="72.0" text="Win rate">
<font>
<Font size="14.0" />
</font></Label>
<Label layoutX="14.0" layoutY="18.0" prefHeight="25.0" prefWidth="72.0" text="Games" textAlignment="RIGHT">
<font>
<Font size="14.0" />
</font>
</Label>
<Label layoutX="13.0" layoutY="213.0" prefHeight="25.0" prefWidth="72.0" text="Progress" textAlignment="RIGHT">
<font>
<Font size="14.0" />
</font>
</Label>
<Label fx:id="progressRunLabel" alignment="CENTER" layoutX="110.0" layoutY="213.0" prefHeight="25.0" prefWidth="267.0" text="590/1000">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<Label fx:id="winPercentage" layoutX="96.0" layoutY="131.0" prefHeight="27.0" prefWidth="293.0">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Label>
<Label layoutX="14.0" layoutY="57.0" prefHeight="25.0" prefWidth="72.0" text="Seed" textAlignment="RIGHT">
<font>
<Font size="14.0" />
</font>
</Label>
<TextField fx:id="gameSeed" layoutX="95.0" layoutY="57.0" style="-fx-background-color: silver;" />
<Button layoutX="253.0" layoutY="57.0" mnemonicParsing="false" onAction="#handleNewSeedButton" prefHeight="25.0" prefWidth="72.0" text="New seed" />
<Label layoutX="14.0" layoutY="100.0" prefHeight="25.0" prefWidth="92.0" text="Start Loc: X=" textAlignment="RIGHT">
<font>
<Font size="14.0" />
</font>
</Label>
<TextField fx:id="startLocX" layoutX="95.0" layoutY="100.0" prefHeight="25.0" prefWidth="43.0" style="-fx-background-color: silver;" />
<Label layoutX="145.0" layoutY="100.0" prefHeight="25.0" prefWidth="29.0" text="Y=" textAlignment="RIGHT">
<font>
<Font size="14.0" />
</font>
</Label>
<TextField fx:id="startLocY" layoutX="165.0" layoutY="100.0" prefHeight="25.0" prefWidth="43.0" style="-fx-background-color: silver;" />
<Label layoutX="193.0" layoutY="158.0" prefHeight="25.0" prefWidth="72.0" text="Fairness">
<font>
<Font size="14.0" />
</font>
</Label>
<Label fx:id="fairnessPercentage" layoutX="288.0" layoutY="157.0" prefHeight="27.0" prefWidth="98.0">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<Label layoutX="15.0" layoutY="184.0" prefHeight="25.0" prefWidth="72.0" text="Best streak">
<font>
<Font size="14.0" />
</font>
</Label>
<Label fx:id="bestWinStreak" layoutX="96.0" layoutY="183.0" prefHeight="27.0" prefWidth="84.0">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<Label fx:id="bestMastery" layoutX="288.0" layoutY="183.0" prefHeight="27.0" prefWidth="98.0">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<Label layoutX="193.0" layoutY="184.0" prefHeight="25.0" prefWidth="92.0" text="Best mastery">
<font>
<Font size="14.0" />
</font>
</Label>
<Label fx:id="totalGuesses" layoutX="95.0" layoutY="157.0" prefHeight="27.0" prefWidth="84.0">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<Label layoutX="14.0" layoutY="158.0" prefHeight="25.0" prefWidth="72.0" text="Guesses">
<font>
<Font size="14.0" />
</font>
</Label>
<CheckBox fx:id="safeStart" layoutX="229.0" layoutY="104.0" mnemonicParsing="false" prefHeight="20.0" prefWidth="98.0" text="Ensure safe">
<font>
<Font size="14.0" />
</font>
</CheckBox>
<ChoiceBox fx:id="threadsCombo" layoutX="257.0" layoutY="18.0" prefHeight="25.0" prefWidth="122.0" />
<Label fx:id="messageBox" layoutX="13.0" layoutY="246.0" prefHeight="25.0" prefWidth="301.0" textAlignment="RIGHT">
<font>
<Font size="14.0" />
</font>
</Label>
</children>
</AnchorPane>

View File

@ -0,0 +1,424 @@
package minesweeper.explorer.structure;
import java.util.Map;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Popup;
import minesweeper.explorer.main.Explorer;
import minesweeper.explorer.main.Graphics.GraphicsSet;
import minesweeper.explorer.main.MainScreenController;
import minesweeper.solver.constructs.InformationLocation;
import minesweeper.structure.Location;
public class Board extends AnchorPane {
private final EventHandler<MouseEvent> TOOLTIP = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
//System.out.println(event.getX() + "," + event.getY());
// exited the board
if (event.getEventType() == MouseEvent.MOUSE_EXITED) {
hideTooltip();
return;
}
toolTip.setX(event.getScreenX() + 10);
toolTip.setY(event.getScreenY() - 10);
int boardX = (int) (event.getX() / graphicsSet.getSize());
int boardY = (int) (event.getY() / graphicsSet.getSize());
// exited the board
if (boardX < 0 || boardX >= width || boardY < 0 || boardY >= height) {
hideTooltip();
return;
}
Tile tile = tiles[boardX][boardY];
if (event.isPrimaryButtonDown() && !tile.equals(draggedTile)) {
draggedTile = tile;
if (tile.isFlagged()) { // if flagged do nothing
} else { // otherwise toggle between covered and uncovered
tile.setCovered(!tile.isCovered());
}
}
if (!tile.isCovered() || tile.isFlagged()) { // flag or not hidden
hideTooltip();
} else {
showTooltip(event.getScreenX() + 10, event.getScreenY() - 10, tile);
//populateTileDetails(tile);
}
}
};
protected class AdjacentDetails {
public final int flags;
public final int notflags;
private AdjacentDetails(int mines, int notMines) {
this.flags = mines;
this.notflags = notMines;
}
}
private Popup toolTip = new Popup();
private Text tooltipText = new Text();
// details for controlling dragging behavour;
private Tile draggedTile;
private final int width;
private final int height;
private GraphicsSet graphicsSet;
private final MainScreenController controller;
private ReadOnlyIntegerWrapper flagsPlaced = new ReadOnlyIntegerWrapper();
private Map<Location, InformationLocation> gameInformation;
private int gameInfoHash = 0;
private final Tile[][] tiles;
public Board(MainScreenController controller, int width, int height) {
super();
this.width = width;
this.height = height;
this.controller = controller;
this.graphicsSet = controller.getGraphicsSet();
this.tiles = new Tile[width][height];
clearBoard(false);
for (int x=0; x < this.width; x++) {
this.getChildren().addAll(tiles[x]);
}
toolTip.getContent().addAll(tooltipText);
tooltipText.setText("Test");
tooltipText.setFont(new Font(20));
this.setOnMouseMoved(TOOLTIP);
this.setOnMouseEntered(TOOLTIP);
this.setOnMouseExited(TOOLTIP);
this.setOnMouseDragged(TOOLTIP);
}
public void setFlag(Tile tile, boolean spread) {
if (tile.isFlagged()) {
return;
}
tile.setFlagged(true);
flagsPlaced.set(flagsPlaced.get() + 1);
if (controller.mineCountLocked()) {
int minesToFind = controller.getMinesToFindController().getValue() - 1;
controller.getMinesToFindController().setValue(minesToFind);
}
if (!spread) {
return;
}
int startx = Math.max(0, tile.getTileX() - 1);
int endx = Math.min(width - 1, tile.getTileX() + 1);
int starty = Math.max(0, tile.getTileY() - 1);
int endy = Math.min(height - 1, tile.getTileY() + 1);
//System.out.println("x: " + startx + " - " + endx);
//System.out.println("y: " + starty + " - " + endy);
for (int x=startx; x <= endx; x++) {
for (int y=starty; y <= endy; y++) {
if (x == tile.getTileX() && y == tile.getTileY()) {
continue;
}
Tile adjTile = tiles[x][y];
int adjMines = getAdjacentDetails(adjTile).flags;
// keep the adjacent value in step if it was in step to start with
if (adjTile.getValue() == adjMines - 1) {
adjTile.setValue(adjMines);
}
}
}
}
public void RemoveFlag(Tile tile) {
if (!tile.isFlagged()) {
return;
}
tile.setFlagged(false);
flagsPlaced.set(flagsPlaced.get() - 1);
if (controller.mineCountLocked()) {
int minesToFind = controller.getMinesToFindController().getValue() + 1;
controller.getMinesToFindController().setValue(minesToFind);
}
int startx = Math.max(0, tile.getTileX() - 1);
int endx = Math.min(width - 1, tile.getTileX() + 1);
int starty = Math.max(0, tile.getTileY() - 1);
int endy = Math.min(height - 1, tile.getTileY() + 1);
//System.out.println("x: " + startx + " - " + endx);
//System.out.println("y: " + starty + " - " + endy);
for (int x=startx; x <= endx; x++) {
for (int y=starty; y <= endy; y++) {
if (x == tile.getTileX() && y == tile.getTileY()) {
continue;
}
Tile adjTile = tiles[x][y];
int adjMines = getAdjacentDetails(adjTile).flags;
// keep the adjacent value in step if it was in step to start with
if (adjTile.getValue() == adjMines + 1) {
adjTile.setValue(adjMines);
}
}
}
}
protected void coverAdjacentZeroTiles(Tile tile, boolean covered) {
int startx = Math.max(0, tile.getTileX() - 1);
int endx = Math.min(width - 1, tile.getTileX() + 1);
int starty = Math.max(0, tile.getTileY() - 1);
int endy = Math.min(height - 1, tile.getTileY() + 1);
//System.out.println("x: " + startx + " - " + endx);
//System.out.println("y: " + starty + " - " + endy);
for (int x=startx; x <= endx; x++) {
for (int y=starty; y <= endy; y++) {
if (x == tile.getTileX() && y == tile.getTileY()) {
continue;
}
Tile adjTile = tiles[x][y];
if (adjTile.getValue() == 0) {
adjTile.setCovered(covered);
}
}
}
}
protected AdjacentDetails getAdjacentDetails(Tile tile) {
int flags = 0;
int notMines = 0;
int startx = Math.max(0, tile.getTileX() - 1);
int endx = Math.min(width - 1, tile.getTileX() + 1);
int starty = Math.max(0, tile.getTileY() - 1);
int endy = Math.min(height - 1, tile.getTileY() + 1);
//System.out.println("x: " + startx + " - " + endx);
//System.out.println("y: " + starty + " - " + endy);
for (int x=startx; x <= endx; x++) {
for (int y=starty; y <= endy; y++) {
if (x == tile.getTileX() && y == tile.getTileY()) {
continue;
}
Tile adjTile = tiles[x][y];
if (adjTile.isFlagged()) {
flags++;
} else {
notMines++;
}
}
}
return new AdjacentDetails(flags, notMines);
}
public void clearBoard(boolean covered) {
for (int x=0; x < this.width; x++) {
for (int y=0; y < this.height; y++) {
if (tiles[x][y] == null) {
tiles[x][y] = new Tile(graphicsSet, this, x, y);
} else {
tiles[x][y].reset();
tiles[x][y].setCovered(covered);
}
}
}
flagsPlaced.set(0);
setGameInformation(null, 0);
}
public void resizeBoard(GraphicsSet graphicsSet) {
if (this.graphicsSet.getSize() == graphicsSet.getSize() ) {
return;
}
this.graphicsSet = graphicsSet;
for (int x=0; x < this.width; x++) {
for (int y=0; y < this.height; y++) {
tiles[x][y].resizeTile(graphicsSet);
}
}
}
public ReadOnlyIntegerProperty getMinesPlacedProperty() {
return flagsPlaced.getReadOnlyProperty();
}
public void setDraggedTile(Tile tile) {
this.draggedTile = tile;
}
public int getFlagsPlaced() {
return flagsPlaced.get();
}
public Tile getTile(int x, int y) {
return tiles[x][y];
}
public int getGameWidth() {
return this.width;
}
public int getGameHeight() {
return this.height;
}
public void showTooltip(double x, double y, Tile tile) {
if (gameInformation == null || gameInfoHash != getHashValue()) {
tooltipText.setText("Press 'Analyse'" + System.lineSeparator() + "for details");
populateTileDetails(null);
} else {
InformationLocation il = gameInformation.get(tile.getLocation());
if (il != null) {
tooltipText.setText(Explorer.PERCENT.format(il.getSafety()) + " safe");
populateTileDetails(tile);
}
}
toolTip.setX(x);
toolTip.setY(y);
toolTip.show(this.getScene().getWindow());
}
public void populateTileDetails(Tile tile) {
if (gameInformation == null || tile == null) {
//System.out.println("Game information not found");
controller.getTileValueController().update(null);
return;
}
InformationLocation il = gameInformation.get(tile.getLocation());
controller.getTileValueController().update(il);
}
public void hideTooltip() {
toolTip.hide();
}
public void setGameInformation(Map<Location, InformationLocation> info, int hashValue) {
this.gameInfoHash = hashValue;
this.gameInformation = info;
if (info != null) {
for (InformationLocation il: info.values()) {
Tile tile = getTile(il.x, il.y);
tile.setTextValue(il.getSafety());
}
}
}
public int getHashValue() {
int hash = 31*31*31 * controller.getTotalMines() + 31*31 * flagsPlaced.get() + 31 * width + height;
for (int x=0; x < this.width; x++) {
for (int y=0; y < this.height; y++) {
Tile tile = tiles[x][y];
if (tile == null) {
} else {
if (tile.isFlagged()) {
hash = 31 * hash + 13;
} else if (tile.isCovered()) {
hash = 31 * hash + 12;
} else {
hash = 31 * hash + tile.getValue();
}
}
}
}
return hash;
}
@Override
protected void finalize() {
System.out.println("At finalize() for Board.java");
}
}

View File

@ -0,0 +1,95 @@
package minesweeper.explorer.structure;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Popup;
public class Expander extends Circle {
// generic mouse click event for tiles
private final EventHandler<MouseEvent> DRAGGED = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
Expander expander = (Expander) event.getSource();
Point2D xy = expander.localToParent(event.getX(), event.getY());
expander.setCenterX(xy.getX());
expander.setCenterY(xy.getY());
toolTip.setX(event.getScreenX() + 10);
toolTip.setY(event.getScreenY() - 10);
int boardX = (int) (xy.getX() / size);
int boardY = (int) (xy.getY() / size);
String text = "(" + boardX + "," + boardY + ")";
popupText.setText(text);
toolTip.show(me().getScene().getWindow());
/*
if (event.getEventType() == MouseEvent.MOUSE_EXITED) {
toolTip.hide();
} else if (event.getEventType() == MouseEvent.MOUSE_ENTERED) {
toolTip.show(window.getScene().getWindow());
}
*/
}
};
// generic mouse click event for tiles
private final EventHandler<MouseEvent> DRAG_ENDED = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
toolTip.hide();
}
};
// generic mouse click event for tiles
private final EventHandler<MouseEvent> ENTERED = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
toolTip.setX(event.getScreenX() + 10);
toolTip.setY(event.getScreenY() - 10);
toolTip.show(me().getScene().getWindow());
}
};
private Popup toolTip = new Popup();
private Text popupText = new Text();
private double size;
public Expander(int x, int y, int radius, double size, Color fill) {
super(x * size, y * size, radius, fill);
toolTip.getContent().addAll(popupText);
popupText.setText("Test");
popupText.setFont(new Font(20));
this.size = size;
this.setOnMouseDragged(DRAGGED);
this.setOnMouseReleased(DRAG_ENDED);
//this.setOnMouseEntered(ENTERED);
//this.setOnMouseExited(DRAG_ENDED);
}
private Expander me() {
return this;
}
}

View File

@ -0,0 +1,89 @@
package minesweeper.explorer.structure;
import javafx.event.EventHandler;
import javafx.scene.image.ImageView;
import javafx.scene.input.ScrollEvent;
import minesweeper.explorer.main.Graphics;
public class LedDigit extends ImageView {
// generic mouse scroll event for led digits
private final static EventHandler<ScrollEvent> SCROLLED = new EventHandler<ScrollEvent>() {
@Override
public void handle(ScrollEvent event) {
LedDigit digit = (LedDigit) event.getSource();
if (digit.owner.isLocked()) {
return;
}
//System.out.println("Scroll detected on digit " + event.getTextDeltaY());
int delta;
if (event.getTextDeltaY() < 0) {
delta = 1;
} else {
delta = -1;
}
digit.rotateValue(delta);
}
};
private final LedDigits owner;
private int size;
private int value = 0;
public LedDigit(LedDigits owner, int size) {
this.owner = owner;
this.size = size;
this.setOnScroll(SCROLLED);
this.setPickOnBounds(true);
doDraw();
}
private void doDraw() {
setImage(Graphics.getLed(value));
}
public int getValue() {
return this.value;
}
public void setValue(int value) {
this.value = value;
doDraw();
}
private void rotateValue(int delta) {
int value = owner.getValue() + delta * this.size;
if (value < 0) {
value = 0;
} else if (value > 999) {
value = 999;
}
//int newValue = (value + delta) % 10;
//if (newValue < 0) {
// newValue = newValue + 10;
//}
owner.setValue(value);
}
}

View File

@ -0,0 +1,105 @@
package minesweeper.explorer.structure;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.HBox;
public class LedDigits extends HBox {
ChangeListener VALUE_LISTENER = new ChangeListener() {
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
setValue((Integer) newValue);
}
};
ReadOnlyIntegerProperty monitoredProperty;
private final int numberOfDigits;
private final LedDigit[] digits;
private final int maxValue;
private boolean locked;
//private int value;
public LedDigits(int numberOfDigits) {
this(numberOfDigits, false);
}
public LedDigits(int numberOfDigits, boolean locked) {
this.numberOfDigits = numberOfDigits;
this.digits = new LedDigit[numberOfDigits];
this.locked = locked;
int size = 1;
for (int i=0; i < numberOfDigits; i++) {
digits[numberOfDigits - i - 1] = new LedDigit(this, size);
size = size * 10;
}
this.maxValue = size - 1;
//System.out.println(this.maxValue);
// add the digits to the container
this.getChildren().addAll(digits);
}
private void doDraw() {
}
public void setValueListener(ReadOnlyIntegerProperty valueProperty) {
monitoredProperty = valueProperty;
monitoredProperty.addListener(VALUE_LISTENER);
}
public void removeValueListener() {
if (monitoredProperty == null) {
return;
}
monitoredProperty.removeListener(VALUE_LISTENER);
monitoredProperty = null;
}
public void setValue(int value) {
if (value < 0) {
value = 0;
} else if (value > this.maxValue) {
value = this.maxValue;
}
int work = value;
for (int i=numberOfDigits - 1; i >= 0; i-- ) {
int digitValue = work % 10;
work = (work - digitValue) / 10;
digits[i].setValue(digitValue);
}
doDraw();
}
public int getValue() {
int work = 0;
int exponent = 1;
for (int i=numberOfDigits - 1; i >= 0; i-- ) {
work = work + digits[i].getValue() * exponent;
exponent = exponent * 10;
}
return work;
}
public boolean isLocked() {
return locked;
}
}

View File

@ -0,0 +1,276 @@
package minesweeper.explorer.structure;
import java.math.BigDecimal;
import java.math.RoundingMode;
import javafx.event.EventHandler;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import minesweeper.explorer.main.Graphics.GraphicsSet;
import minesweeper.explorer.structure.Board.AdjacentDetails;
import minesweeper.structure.Location;
public class Tile extends StackPane {
// generic mouse click event for tiles
private final static EventHandler<MouseEvent> CLICKED = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
Tile tile = (Tile) event.getSource();
//System.out.println("Click detected on tile " + tile.asText());
if (event.getButton() == MouseButton.SECONDARY) {
if (tile.isFlagged()) {
tile.board.RemoveFlag(tile);
} else {
tile.board.setFlag(tile , true);
}
}
if (event.getButton() == MouseButton.PRIMARY) {
if (tile.isFlagged()) { // if flagged do nothing
} else { // otherwise toggle between covered and uncovered
tile.setCovered(!tile.isCovered());
tile.board.setDraggedTile(tile); // let the board know which tile is being dragged (if it is)
}
}
}
};
// generic mouse click event for tiles
private final static EventHandler<ScrollEvent> SCROLLED = new EventHandler<ScrollEvent>() {
@Override
public void handle(ScrollEvent event) {
Tile tile = (Tile) event.getSource();
//System.out.println("Scroll detected on tile " + tile.asText() + " TextDeltaY() is " + event.getTextDeltaY());
int delta;
if (event.getTextDeltaY() < 0) {
delta = 1;
} else {
delta = -1;
}
tile.rotateValue(delta);
}
};
private ImageView image;
private Text text;
private final int x;
private final int y;
private final Board board;
private final Location location;
private String textValue = "";
private GraphicsSet graphicsSet;
private boolean covered;
private boolean flagged;
private int value;
public Tile(GraphicsSet graphicsSet, Board board, int x, int y) {
this.image = new ImageView();
this.text = new Text("");
this.text.setScaleX(graphicsSet.getSize() / 24d);
this.text.setScaleY(graphicsSet.getSize() / 24d);
this.getChildren().addAll(image, text);
this.board = board;
this.x = x;
this.y = y;
this.location = new Location(x, y);
this.graphicsSet = graphicsSet;
this.relocate(x * graphicsSet.getSize(), y * graphicsSet.getSize());
reset();
this.setOnMousePressed(CLICKED);
this.setOnScroll(SCROLLED);
}
private void doDraw() {
if (flagged) {
this.image.setImage(graphicsSet.getFlag());
} else if (covered) {
this.image.setImage(graphicsSet.getHidden());
} else {
this.image.setImage(graphicsSet.getNumber(value));
}
showTextValue();
}
public void reset() {
covered = false;
//mine = false;
flagged = false;
value = 0;
textValue = "";
doDraw();
}
public void resizeTile(GraphicsSet graphicsSet) {
if (this.graphicsSet.getSize() == graphicsSet.getSize()) {
return;
}
this.graphicsSet = graphicsSet;
this.text.setScaleX(graphicsSet.getSize() / 24d);
this.text.setScaleY(graphicsSet.getSize() / 24d);
this.relocate(x * graphicsSet.getSize(), y * graphicsSet.getSize());
doDraw();
}
public boolean isCovered() {
return covered;
}
public void setCovered(boolean covered) {
this.covered = covered;
doDraw();
}
protected void setTextValue(BigDecimal safety) {
safety = BigDecimal.ONE.subtract(safety).multiply(BigDecimal.valueOf(100));
if (safety.compareTo(BigDecimal.TEN) < 0) {
safety = safety.setScale(1, RoundingMode.HALF_UP);
} else {
safety = safety.setScale(0, RoundingMode.HALF_UP);
}
textValue = safety.toPlainString();
showTextValue();
}
private void showTextValue() {
if (isCovered() && !isFlagged()) {
text.setText(textValue);
} else {
text.setText("");
}
}
public boolean isFlagged() {
return flagged;
}
public void setFlagged(boolean flagged) {
this.flagged = flagged;
if (this.flagged) { // if flagged then also covered
this.covered = true;
}
doDraw();
}
private void rotateValue(int delta) {
AdjacentDetails adjDetails = board.getAdjacentDetails(this);
int minValue = adjDetails.flags;
int maxValue = adjDetails.flags + adjDetails.notflags;
int range = maxValue - minValue;
//System.out.println("Min = " + minValue + " max = " + maxValue);
int newValue;
if (this.covered) {
this.covered = false;
if (delta < 0) {
newValue = maxValue;
} else {
newValue = minValue;
}
} else if (range == 0) {
newValue = minValue;
} else {
newValue = minValue + ((value - minValue + delta) % (range + 1));
}
if (newValue < minValue) {
newValue = newValue + range + 1;
}
setValue(newValue);
}
public int getValue() {
return value;
}
public void setValue(int value) {
// don't set a value for tiles which are mines or flagged
if (this.flagged) {
return;
}
this.value = value;
doDraw();
}
@Override
public String toString() {
String text = "(" + this.x + "," + this.y + ")";
return text;
}
public int getTileX() {
return this.x;
}
public int getTileY() {
return this.y;
}
public Location getLocation() {
return this.location;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 916 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB