Add References to git
9
info/Minesweeper/MinesweeperExplorer/.classpath
Normal 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>
|
1
info/Minesweeper/MinesweeperExplorer/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/bin/
|
23
info/Minesweeper/MinesweeperExplorer/.project
Normal 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>
|
@ -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
|
8
info/Minesweeper/MinesweeperExplorer/build.fxbuild
Normal 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>
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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>
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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>
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
@ -0,0 +1 @@
|
||||
/* JavaFX CSS - Leave this comment until you have at least create one rule which uses -fx-Property */
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
@ -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>
|
@ -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");
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
BIN
info/Minesweeper/MinesweeperExplorer/src/resources/images/0.png
Normal file
After Width: | Height: | Size: 697 B |
BIN
info/Minesweeper/MinesweeperExplorer/src/resources/images/1.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
info/Minesweeper/MinesweeperExplorer/src/resources/images/2.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
info/Minesweeper/MinesweeperExplorer/src/resources/images/3.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
info/Minesweeper/MinesweeperExplorer/src/resources/images/4.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
info/Minesweeper/MinesweeperExplorer/src/resources/images/5.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
info/Minesweeper/MinesweeperExplorer/src/resources/images/6.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
info/Minesweeper/MinesweeperExplorer/src/resources/images/7.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
info/Minesweeper/MinesweeperExplorer/src/resources/images/8.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 871 B |
After Width: | Height: | Size: 916 B |
After Width: | Height: | Size: 977 B |
After Width: | Height: | Size: 973 B |
After Width: | Height: | Size: 879 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 990 B |
After Width: | Height: | Size: 948 B |
After Width: | Height: | Size: 991 B |
After Width: | Height: | Size: 749 B |
After Width: | Height: | Size: 929 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 2.3 KiB |