Add References to git
This commit is contained in:
606
info/Minesweeper2/MinesweeperSolver/BruteForce.cs
Normal file
606
info/Minesweeper2/MinesweeperSolver/BruteForce.cs
Normal file
@ -0,0 +1,606 @@
|
||||
using System;
|
||||
using System.CodeDom;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
/**
|
||||
* Performs a brute force search on the provided squares using the iterator
|
||||
*
|
||||
*/
|
||||
public class Cruncher {
|
||||
|
||||
public const sbyte BOMB = -10;
|
||||
|
||||
readonly private SolverInfo information;
|
||||
private readonly WitnessWebIterator iterator;
|
||||
readonly private List<SolverTile> tiles;
|
||||
readonly private List<BoxWitness> witnesses;
|
||||
readonly private sbyte[] currentFlagsTiles;
|
||||
readonly private sbyte[] currentFlagsWitnesses;
|
||||
|
||||
private int candidates = 0; // number of samples which satisfy the current board state
|
||||
private readonly BruteForceAnalysis bfa;
|
||||
|
||||
public Cruncher(SolverInfo information, WitnessWebIterator iterator, List<BoxWitness> witnesses, BruteForceAnalysis bfa) {
|
||||
|
||||
this.information = information;
|
||||
this.iterator = iterator; // the iterator
|
||||
this.tiles = iterator.getTiles(); // the tiles the iterator is iterating over
|
||||
this.witnesses = witnesses; // the dependent witnesses (class BoxWitness) which need to be checked to see if they are satisfied
|
||||
|
||||
this.bfa = bfa;
|
||||
|
||||
// determine how many found mines are currently next to each tile
|
||||
this.currentFlagsTiles = new sbyte[this.tiles.Count];
|
||||
for (int i = 0; i < this.tiles.Count; i++) {
|
||||
this.currentFlagsTiles[i] = (sbyte) this.information.AdjacentTileInfo(this.tiles[i]).mines;
|
||||
}
|
||||
|
||||
|
||||
// determine how many found mines are currently next to each witness
|
||||
this.currentFlagsWitnesses = new sbyte[this.witnesses.Count];
|
||||
for (int i = 0; i < this.witnesses.Count; i++) {
|
||||
this.currentFlagsWitnesses[i] = (sbyte) this.information.AdjacentTileInfo(this.witnesses[i].GetTile()).mines;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static BruteForceAnalysis PerformBruteForce(SolverInfo information, WitnessWebIterator[] iterators, List<BoxWitness> witnesses) {
|
||||
|
||||
BruteForceAnalysis bfa = new BruteForceAnalysis(information, iterators[0].getTiles(), SolverMain.MAX_BFDA_SOLUTIONS, null);
|
||||
|
||||
Cruncher[] crunchers = new Cruncher[iterators.Length];
|
||||
Task[] tasks = new Task[iterators.Length];
|
||||
|
||||
for (int i=0; i < iterators.Length; i++) {
|
||||
|
||||
crunchers[i] = new Cruncher(information, iterators[i], witnesses, bfa);
|
||||
Cruncher cruncher = crunchers[i];
|
||||
|
||||
tasks[i] = Task.Factory.StartNew(() => { cruncher.Crunch(); });
|
||||
}
|
||||
Task.WaitAll(tasks);
|
||||
|
||||
int solutions = 0;
|
||||
int iterations = 0;
|
||||
for (int i = 0; i < iterators.Length; i++) {
|
||||
solutions = solutions + crunchers[i].GetSolutionsFound();
|
||||
iterations = iterations + iterators[i].GetIterations();
|
||||
}
|
||||
|
||||
information.Write("Solutions found by brute force " + solutions + " after " + iterations + " iterations");
|
||||
|
||||
return bfa;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void Crunch() {
|
||||
|
||||
int[] sample = this.iterator.GetSample();
|
||||
|
||||
while (sample != null) {
|
||||
|
||||
if (this.CheckSample(sample)) {
|
||||
candidates++;
|
||||
}
|
||||
|
||||
sample = this.iterator.GetSample();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<SolverTile> getTiles() {
|
||||
return iterator.getTiles();
|
||||
}
|
||||
|
||||
// this checks whether the positions of the mines are a valid candidate solution
|
||||
private bool CheckSample(int[] sample) {
|
||||
|
||||
// get the tiles which are mines in this sample
|
||||
SolverTile[] mine = new SolverTile[sample.Length];
|
||||
for (int i = 0; i < sample.Length; i++) {
|
||||
mine[i] = this.tiles[sample[i]];
|
||||
}
|
||||
|
||||
for (int i = 0; i < this.witnesses.Count; i++) {
|
||||
|
||||
int flags1 = this.currentFlagsWitnesses[i];
|
||||
int flags2 = 0;
|
||||
|
||||
// count how many candidate mines are next to this witness
|
||||
for (int j = 0; j < mine.Length; j++) {
|
||||
if (mine[j].IsAdjacent(this.witnesses[i].GetTile())) {
|
||||
flags2++;
|
||||
}
|
||||
}
|
||||
|
||||
int flags3 = this.witnesses[i].GetTile().GetValue(); // number of flags indicated on the tile
|
||||
|
||||
if (flags3 != flags1 + flags2) {
|
||||
//Console.WriteLine("Failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//if it is a good solution then calculate the distribution if required
|
||||
|
||||
//Console.WriteLine("Solution found");
|
||||
|
||||
sbyte[] solution = new sbyte[this.tiles.Count];
|
||||
|
||||
for (int i = 0; i < this.tiles.Count; i++) {
|
||||
|
||||
bool isMine = false;
|
||||
for (int j = 0; j < sample.Length; j++) {
|
||||
if (i == sample[j]) {
|
||||
isMine = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we are a mine then it doesn't matter how many mines surround us
|
||||
if (!isMine) {
|
||||
sbyte flags2 = this.currentFlagsTiles[i];
|
||||
// count how many candidate mines are next to this square
|
||||
for (int j = 0; j < mine.Length; j++) {
|
||||
if (mine[j].IsAdjacent(this.tiles[i])) {
|
||||
flags2++;
|
||||
}
|
||||
}
|
||||
solution[i] = flags2;
|
||||
} else {
|
||||
solution[i] = BOMB;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bfa.AddSolution(solution);
|
||||
|
||||
/*
|
||||
string output = "";
|
||||
for (int i = 0; i < mine.length; i++) {
|
||||
output = output + mine[i].asText();
|
||||
}
|
||||
console.log(output);
|
||||
*/
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public BruteForceAnalysis GetBruteForceAnalysis() {
|
||||
return this.bfa;
|
||||
}
|
||||
|
||||
public int GetSolutionsFound() {
|
||||
return this.candidates;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// create an iterator which is like a set of rotating wheels
|
||||
public class WitnessWebIterator {
|
||||
|
||||
private int[] sample;
|
||||
private SequentialIterator[] cogs;
|
||||
private int[] squareOffset;
|
||||
private int[] mineOffset;
|
||||
private List<SolverTile> tiles;
|
||||
|
||||
private int iterationsDone = 0;
|
||||
|
||||
readonly private int top;
|
||||
readonly private int bottom;
|
||||
|
||||
private bool done = false;
|
||||
|
||||
/*
|
||||
// if rotation is -1 then this does all the possible iterations
|
||||
// if rotation is not - 1 then this locks the first 'cog' in that position and iterates the remaining cogs. This allows parallel processing based on the position of the first 'cog'
|
||||
public WitnessWebIterator(ProbabilityEngine pe, List<SolverTile> allCoveredTiles, int rotation) {
|
||||
|
||||
this.tiles = new List<SolverTile>(); // list of tiles being iterated over
|
||||
|
||||
this.cogs = new SequentialIterator[pe.GetIndependentWitnesses().Count + 1]; // array of cogs
|
||||
this.squareOffset = new int[pe.GetIndependentWitnesses().Count + 1]; // int array
|
||||
this.mineOffset = new int[pe.GetIndependentWitnesses().Count + 1]; // int array
|
||||
|
||||
this.iterationsDone = 0;
|
||||
|
||||
this.done = false;
|
||||
|
||||
//this.probabilityEngine = pe;
|
||||
|
||||
// if we are setting the position of the top cog then it can't ever change
|
||||
if (rotation == -1) {
|
||||
this.bottom = 0;
|
||||
} else {
|
||||
this.bottom = 1;
|
||||
}
|
||||
|
||||
List<SolverTile> loc = new List<SolverTile>(); // array of locations
|
||||
|
||||
List<BoxWitness> indWitnesses = pe.GetIndependentWitnesses();
|
||||
|
||||
int cogi = 0;
|
||||
int indSquares = 0;
|
||||
int indMines = 0;
|
||||
|
||||
// create an array of locations in the order of independent witnesses
|
||||
foreach (BoxWitness w in indWitnesses) {
|
||||
|
||||
this.squareOffset[cogi] = indSquares;
|
||||
this.mineOffset[cogi] = indMines;
|
||||
this.cogs[cogi] = new SequentialIterator(w.GetMinesToFind(), w.GetAdjacentTiles().Count);
|
||||
cogi++;
|
||||
|
||||
indSquares = indSquares + w.GetAdjacentTiles().Count;
|
||||
indMines = indMines + w.GetMinesToFind();
|
||||
|
||||
loc.AddRange(w.GetAdjacentTiles());
|
||||
|
||||
}
|
||||
|
||||
//System.out.println("Mines left = " + (mines - indMines));
|
||||
//System.out.println("Squrs left = " + (web.getSquares().length - indSquares));
|
||||
|
||||
// the last cog has the remaining squares and mines
|
||||
|
||||
//add the rest of the locations
|
||||
for (int i = 0; i < allCoveredTiles.Count; i++) {
|
||||
|
||||
SolverTile l = allCoveredTiles[i];
|
||||
bool skip = false;
|
||||
for (int j = 0; j < loc.Count; j++) {
|
||||
|
||||
SolverTile m = loc[j];
|
||||
|
||||
if (l.IsEqual(m)) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!skip) {
|
||||
loc.Add(l);
|
||||
}
|
||||
}
|
||||
|
||||
this.tiles = loc;
|
||||
|
||||
SolverInfo information = pe.GetSolverInfo();
|
||||
|
||||
int minesLeft = information.GetMinesLeft() - information.GetExcludedMineCount();
|
||||
int tilesLeft = information.GetTilesLeft() - information.GetExcludedTiles().Count;
|
||||
|
||||
information.Write("Mines left " + minesLeft);
|
||||
information.Write("Independent Mines " + indMines);
|
||||
information.Write("Tiles left " + tilesLeft);
|
||||
information.Write("Independent tiles " + indSquares);
|
||||
|
||||
|
||||
// if there are more mines left then squares then no solution is possible
|
||||
// if there are not enough mines to satisfy the minimum we know are needed
|
||||
if (minesLeft - indMines > tilesLeft - indSquares
|
||||
|| indMines > minesLeft) {
|
||||
this.done = true;
|
||||
this.top = 0;
|
||||
Console.WriteLine("Nothing to do in this iterator");
|
||||
return;
|
||||
}
|
||||
|
||||
// if there are no mines left then no need for a cog
|
||||
if (minesLeft > indMines) {
|
||||
this.squareOffset[cogi] = indSquares;
|
||||
this.mineOffset[cogi] = indMines;
|
||||
this.cogs[cogi] = new SequentialIterator(minesLeft - indMines, tilesLeft - indSquares);
|
||||
this.top = cogi;
|
||||
} else {
|
||||
top = cogi - 1;
|
||||
}
|
||||
|
||||
//this.top = this.cogs.Length - 1;
|
||||
|
||||
this.sample = new int[minesLeft]; // make the sample array the size of the number of mines
|
||||
|
||||
// if we are locking and rotating the top cog then do it
|
||||
if (rotation != -1) {
|
||||
for (var i = 0; i < rotation; i++) {
|
||||
this.cogs[0].GetNextSample();
|
||||
}
|
||||
}
|
||||
|
||||
// now set up the initial sample position
|
||||
for (int i = 0; i < this.top; i++) {
|
||||
int[] s = this.cogs[i].GetNextSample();
|
||||
for (int j = 0; j < s.Length; j++) {
|
||||
this.sample[this.mineOffset[i] + j] = this.squareOffset[i] + s[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// if rotation is -1 then this does all the possible iterations
|
||||
// if rotation is not - 1 then this locks the first 'cog' in that position and iterates the remaining cogs. This allows parallel processing based on the position of the first 'cog'
|
||||
public WitnessWebIterator(SolverInfo information, List<BoxWitness> independentWitnesses, List<BoxWitness> depdendentWitnesses
|
||||
, List<SolverTile> allCoveredTiles, int minesLeft, int tilesLeft, int rotation) {
|
||||
|
||||
this.tiles = new List<SolverTile>(); // list of tiles being iterated over
|
||||
|
||||
int cogs;
|
||||
if (independentWitnesses == null) {
|
||||
cogs = 1;
|
||||
} else {
|
||||
cogs = independentWitnesses.Count + 1;
|
||||
}
|
||||
|
||||
this.cogs = new SequentialIterator[cogs]; // array of cogs
|
||||
this.squareOffset = new int[cogs]; // int array
|
||||
this.mineOffset = new int[cogs]; // int array
|
||||
|
||||
this.iterationsDone = 0;
|
||||
|
||||
this.done = false;
|
||||
|
||||
//this.probabilityEngine = pe;
|
||||
|
||||
// if we are setting the position of the top cog then it can't ever change
|
||||
if (rotation == -1) {
|
||||
this.bottom = 0;
|
||||
} else {
|
||||
this.bottom = 1;
|
||||
}
|
||||
|
||||
List<SolverTile> loc = new List<SolverTile>(); // array of locations
|
||||
|
||||
int cogi = 0;
|
||||
int indSquares = 0;
|
||||
int indMines = 0;
|
||||
|
||||
// create an array of locations in the order of independent witnesses
|
||||
if (independentWitnesses != null) {
|
||||
foreach (BoxWitness w in independentWitnesses) {
|
||||
|
||||
this.squareOffset[cogi] = indSquares;
|
||||
this.mineOffset[cogi] = indMines;
|
||||
this.cogs[cogi] = new SequentialIterator(w.GetMinesToFind(), w.GetAdjacentTiles().Count);
|
||||
cogi++;
|
||||
|
||||
indSquares = indSquares + w.GetAdjacentTiles().Count;
|
||||
indMines = indMines + w.GetMinesToFind();
|
||||
|
||||
loc.AddRange(w.GetAdjacentTiles());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//System.out.println("Mines left = " + (mines - indMines));
|
||||
//System.out.println("Squrs left = " + (web.getSquares().length - indSquares));
|
||||
|
||||
// the last cog has the remaining squares and mines
|
||||
|
||||
//add the rest of the locations
|
||||
for (int i = 0; i < allCoveredTiles.Count; i++) {
|
||||
|
||||
SolverTile l = allCoveredTiles[i];
|
||||
bool skip = false;
|
||||
for (int j = 0; j < loc.Count; j++) {
|
||||
|
||||
SolverTile m = loc[j];
|
||||
|
||||
if (l.IsEqual(m)) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!skip) {
|
||||
loc.Add(l);
|
||||
}
|
||||
}
|
||||
|
||||
this.tiles = loc;
|
||||
|
||||
information.Write("Mines left " + minesLeft);
|
||||
information.Write("Independent Mines " + indMines);
|
||||
information.Write("Tiles left " + tilesLeft);
|
||||
information.Write("Independent tiles " + indSquares);
|
||||
|
||||
|
||||
// if there are more mines left then squares then no solution is possible
|
||||
// if there are not enough mines to satisfy the minimum we know are needed
|
||||
if (minesLeft - indMines > tilesLeft - indSquares
|
||||
|| indMines > minesLeft) {
|
||||
this.done = true;
|
||||
this.top = 0;
|
||||
Console.WriteLine("Nothing to do in this iterator");
|
||||
return;
|
||||
}
|
||||
|
||||
// if there are no mines left then no need for a cog
|
||||
if (minesLeft > indMines) {
|
||||
this.squareOffset[cogi] = indSquares;
|
||||
this.mineOffset[cogi] = indMines;
|
||||
this.cogs[cogi] = new SequentialIterator(minesLeft - indMines, tilesLeft - indSquares);
|
||||
this.top = cogi;
|
||||
} else {
|
||||
top = cogi - 1;
|
||||
}
|
||||
|
||||
//this.top = this.cogs.Length - 1;
|
||||
|
||||
this.sample = new int[minesLeft]; // make the sample array the size of the number of mines
|
||||
|
||||
// if we are locking and rotating the top cog then do it
|
||||
if (rotation != -1) {
|
||||
for (var i = 0; i < rotation; i++) {
|
||||
this.cogs[0].GetNextSample();
|
||||
}
|
||||
}
|
||||
|
||||
// now set up the initial sample position
|
||||
for (int i = 0; i < this.top; i++) {
|
||||
int[] s = this.cogs[i].GetNextSample();
|
||||
for (int j = 0; j < s.Length; j++) {
|
||||
this.sample[this.mineOffset[i] + j] = this.squareOffset[i] + s[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int[] GetSample() {
|
||||
|
||||
|
||||
if (this.done) {
|
||||
Console.WriteLine("**** attempting to iterator when already completed ****");
|
||||
return null;
|
||||
}
|
||||
int index = this.top;
|
||||
|
||||
int[] s = this.cogs[index].GetNextSample();
|
||||
|
||||
while (s == null && index != this.bottom) {
|
||||
index--;
|
||||
s = this.cogs[index].GetNextSample();
|
||||
}
|
||||
|
||||
if (index == this.bottom && s == null) {
|
||||
this.done = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int j = 0; j < s.Length; j++) {
|
||||
this.sample[this.mineOffset[index] + j] = this.squareOffset[index] + s[j];
|
||||
}
|
||||
index++;
|
||||
while (index <= this.top) {
|
||||
this.cogs[index] = new SequentialIterator(this.cogs[index].GetNumberBalls(), this.cogs[index].GetNumberHoles());
|
||||
s = this.cogs[index].GetNextSample();
|
||||
for (int j = 0; j < s.Length; j++) {
|
||||
this.sample[this.mineOffset[index] + j] = this.squareOffset[index] + s[j];
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
/*
|
||||
String output = "";
|
||||
for (int j = 0; j < sample.Length; j++) {
|
||||
output = output + this.sample[j] + " ";
|
||||
}
|
||||
Console.WriteLine(output);
|
||||
*/
|
||||
|
||||
this.iterationsDone++;
|
||||
|
||||
return this.sample;
|
||||
|
||||
}
|
||||
|
||||
public List<SolverTile> getTiles() {
|
||||
return this.tiles;
|
||||
}
|
||||
|
||||
|
||||
public int GetIterations() {
|
||||
return iterationsDone;
|
||||
}
|
||||
|
||||
/*
|
||||
// if the location is a Independent witness then we know it will always
|
||||
// have exactly the correct amount of mines around it since that is what
|
||||
// this iterator does
|
||||
public bool WitnessAlwaysSatisfied(SolverTile location) {
|
||||
|
||||
for (var i = 0; i < this.probabilityEngine.independentWitness.length; i++) {
|
||||
if (this.probabilityEngine.independentWitness[i].equals(location)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class SequentialIterator {
|
||||
|
||||
readonly private int[] sample;
|
||||
readonly private int numberHoles;
|
||||
readonly private int numberBalls;
|
||||
|
||||
private bool more;
|
||||
private int index;
|
||||
|
||||
// a sequential iterator that puts n-balls in m-holes once in each possible way
|
||||
public SequentialIterator(int n, int m) {
|
||||
|
||||
this.numberHoles = m;
|
||||
this.numberBalls = n;
|
||||
|
||||
this.sample = new int[n];
|
||||
|
||||
this.more = true;
|
||||
|
||||
this.index = n - 1;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
this.sample[i] = i;
|
||||
}
|
||||
|
||||
// reduce the iterator by 1, since the first getSample() will increase it
|
||||
// by 1 again
|
||||
this.sample[this.index]--;
|
||||
|
||||
//Console.WriteLine("Sequential Iterator has " + this.numberBalls + " mines and " + this.numberHoles + " squares");
|
||||
|
||||
}
|
||||
|
||||
public int[] GetNextSample() {
|
||||
|
||||
if (!this.more) {
|
||||
Console.WriteLine("**** Trying to iterate after the end ****");
|
||||
return null;
|
||||
}
|
||||
|
||||
this.index = this.numberBalls - 1;
|
||||
|
||||
// add on one to the iterator
|
||||
this.sample[this.index]++;
|
||||
|
||||
// if we have rolled off the end then move backwards until we can fit
|
||||
// the next iteration
|
||||
while (this.sample[this.index] >= this.numberHoles - this.numberBalls + 1 + this.index) {
|
||||
if (this.index == 0) {
|
||||
this.more = false;
|
||||
return null;
|
||||
} else {
|
||||
this.index--;
|
||||
this.sample[this.index]++;
|
||||
}
|
||||
}
|
||||
|
||||
// roll forward
|
||||
while (this.index != this.numberBalls - 1) {
|
||||
this.index++;
|
||||
this.sample[this.index] = this.sample[this.index - 1] + 1;
|
||||
}
|
||||
|
||||
return this.sample;
|
||||
|
||||
}
|
||||
|
||||
public int GetNumberBalls() {
|
||||
return this.numberBalls;
|
||||
}
|
||||
|
||||
public int GetNumberHoles() {
|
||||
return this.numberHoles;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
837
info/Minesweeper2/MinesweeperSolver/BruteForceAnalysis.cs
Normal file
837
info/Minesweeper2/MinesweeperSolver/BruteForceAnalysis.cs
Normal file
@ -0,0 +1,837 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using static MinesweeperControl.MinesweeperGame;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
public class BruteForceAnalysis {
|
||||
|
||||
// used to hold all the solutions left in the game
|
||||
public class SolutionTable {
|
||||
|
||||
private readonly object locker = new object();
|
||||
|
||||
private readonly BruteForceAnalysis bfa;
|
||||
private readonly sbyte[][] solutions;
|
||||
private int size = 0;
|
||||
|
||||
public SolutionTable(BruteForceAnalysis bfa, int maxSize) {
|
||||
this.bfa = bfa;
|
||||
solutions = new sbyte[maxSize][];
|
||||
}
|
||||
|
||||
public void AddSolution(sbyte[] solution) {
|
||||
lock(locker) {
|
||||
solutions[size] = solution;
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public sbyte[] Get(int index) {
|
||||
return solutions[index];
|
||||
}
|
||||
|
||||
public void SortSolutions(int start, int end, int index) {
|
||||
|
||||
Array.Sort(solutions, start, end - start, bfa.sorters[index]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This sorts solutions by the value of a position
|
||||
*/
|
||||
public class SortSolutions : IComparer<sbyte[]> {
|
||||
|
||||
private readonly int sortIndex;
|
||||
|
||||
public SortSolutions(int index) {
|
||||
sortIndex = index;
|
||||
}
|
||||
|
||||
public int Compare(sbyte[] o1, sbyte[] o2) {
|
||||
return o1[sortIndex] - o2[sortIndex];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A key to uniquely identify a position
|
||||
*/
|
||||
public class Position {
|
||||
|
||||
private readonly byte[] position;
|
||||
private int hash;
|
||||
|
||||
public Position(int size) {
|
||||
position = new byte[size];
|
||||
for (int i = 0; i < position.Length; i++) {
|
||||
position[i] = 15;
|
||||
}
|
||||
}
|
||||
|
||||
public Position(Position p, int index, int value) {
|
||||
// copy and update to reflect the new position
|
||||
position = new byte[p.position.Length];
|
||||
Array.Copy(p.position, position, p.position.Length);
|
||||
position[index] = (byte)(value + 50);
|
||||
}
|
||||
|
||||
// copied from String hash
|
||||
public override int GetHashCode() {
|
||||
int h = hash;
|
||||
if (h == 0 && position.Length > 0) {
|
||||
for (int i = 0; i < position.Length; i++) {
|
||||
h = 31 * h + position[i];
|
||||
}
|
||||
hash = h;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public override bool Equals(Object o) {
|
||||
if (o is Position) {
|
||||
for (int i = 0; i < position.Length; i++) {
|
||||
if (this.position[i] != ((Position)o).position[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Positions on the board which can still reveal information about the game.
|
||||
*/
|
||||
public class LivingLocation : IComparable<LivingLocation> {
|
||||
|
||||
//private int winningLines = 0;
|
||||
public bool pruned = false;
|
||||
public readonly short index;
|
||||
public int mineCount = 0; // number of remaining solutions which have a mine in this position
|
||||
public int maxSolutions = 0; // the maximum number of solutions that can be remaining after clicking here
|
||||
public int zeroSolutions = 0; // the number of solutions that have a '0' value here
|
||||
public sbyte maxValue = -1;
|
||||
public sbyte minValue = -1;
|
||||
public byte count; // number of possible values at this location
|
||||
|
||||
public Node[] children;
|
||||
private readonly BruteForceAnalysis bfa;
|
||||
|
||||
public LivingLocation(BruteForceAnalysis bfa, short index) {
|
||||
this.index = index;
|
||||
this.bfa = bfa;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the Nodes which are created if we play this move. Up to 9 positions where this locations reveals a value [0-8].
|
||||
* @param location
|
||||
* @return
|
||||
*/
|
||||
public void BuildChildNodes(Node parent) {
|
||||
|
||||
// sort the solutions by possible values
|
||||
bfa.allSolutions.SortSolutions(parent.startLocation, parent.endLocation, this.index);
|
||||
int index = parent.startLocation;
|
||||
|
||||
// skip over the mines
|
||||
while (index < parent.endLocation && bfa.allSolutions.Get(index)[this.index] == Cruncher.BOMB) {
|
||||
index++;
|
||||
}
|
||||
|
||||
Node[] work = new Node[9];
|
||||
for (int i = this.minValue; i < this.maxValue + 1; i++) {
|
||||
|
||||
// if the node is in the cache then use it
|
||||
Position pos = new Position(parent.position, this.index, i);
|
||||
|
||||
if (!bfa.cache.TryGetValue(pos, out Node temp1)) { // if value not in cache
|
||||
|
||||
Node temp = new Node(bfa, pos);
|
||||
|
||||
temp.startLocation = index;
|
||||
// find all solutions for this values at this location
|
||||
while (index < parent.endLocation && bfa.allSolutions.Get(index)[this.index] == i) {
|
||||
index++;
|
||||
}
|
||||
temp.endLocation = index;
|
||||
|
||||
work[i] = temp;
|
||||
|
||||
} else { // value in cache
|
||||
|
||||
//System.out.println("In cache " + temp.position.key + " " + temp1.position.key);
|
||||
//if (!temp.equals(temp1)) {
|
||||
// System.out.println("Cache not equal!!");
|
||||
//}
|
||||
//temp1.fromCache = true;
|
||||
work[i] = temp1;
|
||||
bfa.cacheHit++;
|
||||
bfa.cacheWinningLines = bfa.cacheWinningLines + temp1.winningLines;
|
||||
// skip past these details in the array
|
||||
while (index < parent.endLocation && bfa.allSolutions.Get(index)[this.index] <= i) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (index != parent.endLocation) {
|
||||
Console.WriteLine("Didn't read all the elements in the array; index = " + index + " end = " + parent.endLocation);
|
||||
}
|
||||
|
||||
|
||||
for (int i = this.minValue; i <= this.maxValue; i++) {
|
||||
if (work[i].GetSolutionSize() > 0) {
|
||||
//if (!work[i].fromCache) {
|
||||
// work[i].determineLivingLocations(this.livingLocations, living.index);
|
||||
//}
|
||||
} else {
|
||||
work[i] = null; // if no solutions then don't hold on to the details
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.children = work;
|
||||
|
||||
}
|
||||
|
||||
public int CompareTo(LivingLocation o) {
|
||||
|
||||
// return location most likely to be clear - this has to be first, the logic depends upon it
|
||||
int test = this.mineCount - o.mineCount;
|
||||
if (test != 0) {
|
||||
return test;
|
||||
}
|
||||
|
||||
// then the location most likely to have a zero
|
||||
test = o.zeroSolutions - this.zeroSolutions;
|
||||
if (test != 0) {
|
||||
return test;
|
||||
}
|
||||
|
||||
// then by most number of different possible values
|
||||
test = o.count - this.count;
|
||||
if (test != 0) {
|
||||
return test;
|
||||
}
|
||||
|
||||
// then by the maxSolutions - ascending
|
||||
return this.maxSolutions - o.maxSolutions;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of a possible state of the game
|
||||
*/
|
||||
public class Node {
|
||||
|
||||
public Position position; // representation of the position we are analysing / have reached
|
||||
|
||||
public int winningLines = 0; // this is the number of winning lines below this position in the tree
|
||||
public int work = 0; // this is a measure of how much work was needed to calculate WinningLines value
|
||||
private bool fromCache = false; // indicates whether this position came from the cache
|
||||
|
||||
public int startLocation; // the first solution in the solution array that applies to this position
|
||||
public int endLocation; // the last + 1 solution in the solution array that applies to this position
|
||||
|
||||
public List<LivingLocation> livingLocations; // these are the locations which need to be analysed
|
||||
|
||||
public LivingLocation bestLiving; // after analysis this is the location that represents best play
|
||||
|
||||
private readonly BruteForceAnalysis bfa;
|
||||
|
||||
public Node(BruteForceAnalysis bfa, int size) {
|
||||
position = new Position(size);
|
||||
this.bfa = bfa;
|
||||
}
|
||||
|
||||
public Node(BruteForceAnalysis bfa, Position position) {
|
||||
this.position = position;
|
||||
this.bfa = bfa;
|
||||
}
|
||||
|
||||
public List<LivingLocation> GetLivingLocations() {
|
||||
return livingLocations;
|
||||
}
|
||||
|
||||
public int GetSolutionSize() {
|
||||
return endLocation - startLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the probability of winning the game from the position this node represents (winningLines / solution size)
|
||||
* @return
|
||||
*/
|
||||
public double GetProbability() {
|
||||
|
||||
return ((double)winningLines) / GetSolutionSize();
|
||||
//return BigDecimal.valueOf(winningLines).divide(BigDecimal.valueOf(getSolutionSize()), Solver.DP, RoundingMode.HALF_UP);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of winning lines if this move is played at this position
|
||||
* Used at top of the game tree
|
||||
*/
|
||||
public int GetWinningLines(LivingLocation move) {
|
||||
|
||||
//if we can never exceed the cutoff then no point continuing
|
||||
if (SolverMain.PRUNE_BF_ANALYSIS && this.GetSolutionSize() - move.mineCount <= this.winningLines) {
|
||||
move.pruned = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int winningLines = GetWinningLines(1, move, this.winningLines);
|
||||
|
||||
if (winningLines > this.winningLines) {
|
||||
this.winningLines = winningLines;
|
||||
}
|
||||
|
||||
return winningLines;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the number of winning lines if this move is played at this position
|
||||
* Used when exploring the game tree
|
||||
*/
|
||||
public int GetWinningLines(int depth, LivingLocation move, int cutoff) {
|
||||
|
||||
int result = 0;
|
||||
|
||||
bfa.processCount++;
|
||||
if (bfa.processCount > SolverMain.BRUTE_FORCE_ANALYSIS_MAX_NODES) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int notMines = this.GetSolutionSize() - move.mineCount;
|
||||
|
||||
move.BuildChildNodes(this);
|
||||
|
||||
foreach (Node child in move.children) {
|
||||
|
||||
if (child == null) {
|
||||
continue; // continue the loop but ignore this entry
|
||||
}
|
||||
|
||||
int maxWinningLines = result + notMines;
|
||||
|
||||
// if the max possible winning lines is less than the current cutoff then no point doing the analysis
|
||||
if (SolverMain.PRUNE_BF_ANALYSIS && maxWinningLines <= cutoff) {
|
||||
move.pruned = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if (child.fromCache) { // nothing more to do, since we did it before
|
||||
this.work++;
|
||||
} else {
|
||||
|
||||
child.DetermineLivingLocations(this.livingLocations, move.index);
|
||||
this.work++;
|
||||
|
||||
if (child.GetLivingLocations().Count == 0) { // no further information ==> all solution indistinguishable ==> 1 winning line
|
||||
|
||||
child.winningLines = 1;
|
||||
|
||||
} else { // not cached and not terminal node, so we need to do the recursion
|
||||
|
||||
foreach (LivingLocation childMove in child.GetLivingLocations()) {
|
||||
|
||||
// if the number of safe solutions <= the best winning lines then we can't do any better, so skip the rest
|
||||
if (child.GetSolutionSize() - childMove.mineCount <= child.winningLines) {
|
||||
break;
|
||||
}
|
||||
|
||||
// now calculate the winning lines for each of these children
|
||||
int winningLines = child.GetWinningLines(depth + 1, childMove, child.winningLines);
|
||||
if (child.winningLines < winningLines || (child.bestLiving != null && child.winningLines == winningLines && child.bestLiving.mineCount < childMove.mineCount)) {
|
||||
child.winningLines = winningLines;
|
||||
child.bestLiving = childMove;
|
||||
}
|
||||
|
||||
// if there are no mines then this is a 100% safe move, so skip any further analysis since it can't be any better
|
||||
if (childMove.mineCount == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// no need to hold onto the living location once we have determined the best of them
|
||||
child.livingLocations = null;
|
||||
|
||||
//if (depth > solver.preferences.BRUTE_FORCE_ANALYSIS_TREE_DEPTH) { // stop holding the tree beyond this depth
|
||||
// child.bestLiving = null;
|
||||
//}
|
||||
|
||||
// add the child to the cache if it didn't come from there and it is carrying sufficient winning lines
|
||||
if (child.work > 30) {
|
||||
child.work = 0;
|
||||
child.fromCache = true;
|
||||
bfa.cacheSize++;
|
||||
bfa.cache.Add(child.position, child);
|
||||
} else {
|
||||
this.work = this.work + child.work;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (depth > SolverMain.BRUTE_FORCE_ANALYSIS_TREE_DEPTH) { // stop holding the tree beyond this depth
|
||||
child.bestLiving = null;
|
||||
}
|
||||
|
||||
// store the aggregate winning lines
|
||||
result = result + child.winningLines;
|
||||
|
||||
notMines = notMines - child.GetSolutionSize(); // reduce the number of not mines
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* this generates a list of Location that are still alive, (i.e. have more than one possible value) from a list of previously living locations
|
||||
* Index is the move which has just been played (in terms of the off-set to the position[] array)
|
||||
*/
|
||||
public void DetermineLivingLocations(List<LivingLocation> liveLocs, int index) {
|
||||
|
||||
List<LivingLocation> living = new List<LivingLocation>(liveLocs.Count);
|
||||
|
||||
foreach (LivingLocation live in liveLocs) {
|
||||
|
||||
if (live.index == index) { // if this is the same move we just played then no need to analyse it - definitely now non-living.
|
||||
continue;
|
||||
}
|
||||
|
||||
int value;
|
||||
|
||||
int[] valueCount = bfa.ResetValues();
|
||||
int mines = 0;
|
||||
int maxSolutions = 0;
|
||||
byte count = 0;
|
||||
sbyte minValue = 0;
|
||||
sbyte maxValue = 0;
|
||||
|
||||
for (int j = startLocation; j < endLocation; j++) {
|
||||
value = bfa.allSolutions.Get(j)[live.index];
|
||||
if (value != Cruncher.BOMB) {
|
||||
//values[value] = true;
|
||||
valueCount[value]++;
|
||||
} else {
|
||||
mines++;
|
||||
}
|
||||
}
|
||||
|
||||
// find the new minimum value and maximum value for this location (can't be wider than the previous min and max)
|
||||
for (sbyte j = live.minValue; j <= live.maxValue; j++) {
|
||||
if (valueCount[j] > 0) {
|
||||
if (count == 0) {
|
||||
minValue = j;
|
||||
}
|
||||
maxValue = j;
|
||||
count++;
|
||||
if (maxSolutions < valueCount[j]) {
|
||||
maxSolutions = valueCount[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count > 1) {
|
||||
LivingLocation alive = new LivingLocation(bfa, live.index);
|
||||
alive.mineCount = mines;
|
||||
alive.count = count;
|
||||
alive.minValue = minValue;
|
||||
alive.maxValue = maxValue;
|
||||
alive.maxSolutions = maxSolutions;
|
||||
alive.zeroSolutions = valueCount[0];
|
||||
living.Add(alive);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
living.Sort();
|
||||
//Collections.sort(living);
|
||||
|
||||
this.livingLocations = living;
|
||||
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
return position.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(Object o) {
|
||||
if (o is Node) {
|
||||
return position.Equals(((Node)o).position);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// start of main class
|
||||
|
||||
private static readonly String INDENT = "................................................................................";
|
||||
|
||||
//private static readonly BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100);
|
||||
|
||||
public int processCount = 0;
|
||||
|
||||
private readonly SolverInfo solver;
|
||||
private readonly int maxSolutionSize;
|
||||
|
||||
//private Node top;
|
||||
|
||||
private readonly List<SolverTile> locations; // the positions being analysed
|
||||
private readonly List<SolverTile> startLocations; // the positions which will be considered for the first move
|
||||
|
||||
private readonly SolutionTable allSolutions;
|
||||
|
||||
//private readonly String scope;
|
||||
|
||||
private Node currentNode;
|
||||
private SolverTile expectedMove;
|
||||
|
||||
private readonly SortSolutions[] sorters;
|
||||
|
||||
private int cacheHit = 0;
|
||||
public int cacheSize = 0;
|
||||
private int cacheWinningLines = 0;
|
||||
private bool allDead = false; // this is true if all the locations are dead
|
||||
private bool tooMany = false;
|
||||
private bool completed = false;
|
||||
|
||||
// some work areas to prevent having to instantiate many 1000's of copies of them
|
||||
//private final boolean[] values = new boolean[9];
|
||||
private readonly int[] valueCount = new int[9];
|
||||
|
||||
private Dictionary<Position, Node> cache = new Dictionary<Position, Node>(5000);
|
||||
|
||||
public BruteForceAnalysis(SolverInfo solver, List<SolverTile> locations, int size, List<SolverTile> startLocations) {
|
||||
|
||||
this.solver = solver;
|
||||
this.locations = locations;
|
||||
this.maxSolutionSize = size;
|
||||
|
||||
//this.top = new Node();
|
||||
sorters = new SortSolutions[locations.Count];
|
||||
for (int i = 0; i < sorters.Length; i++) {
|
||||
sorters[i] = new SortSolutions(i);
|
||||
}
|
||||
|
||||
this.allSolutions = new SolutionTable(this, size);
|
||||
|
||||
this.startLocations = startLocations;
|
||||
|
||||
}
|
||||
|
||||
public void AddSolution(sbyte[] solution) {
|
||||
|
||||
if (solution.Length != locations.Count) {
|
||||
throw new Exception("Solution does not have the correct number of locations");
|
||||
}
|
||||
|
||||
if (allSolutions.GetSize() >= maxSolutionSize) {
|
||||
tooMany = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
String text = "";
|
||||
for (int i=0; i < solution.length; i++) {
|
||||
text = text + solution[i] + " ";
|
||||
}
|
||||
solver.display(text);
|
||||
*/
|
||||
|
||||
allSolutions.AddSolution(solution);
|
||||
|
||||
}
|
||||
|
||||
public void process() {
|
||||
|
||||
long start = DateTime.Now.Ticks;
|
||||
|
||||
solver.Write("----- Brute Force Deep Analysis starting ----");
|
||||
solver.Write(allSolutions.GetSize() + " solutions in BruteForceAnalysis");
|
||||
|
||||
// create the top node
|
||||
Node top = buildTopNode(allSolutions);
|
||||
|
||||
if (top.GetLivingLocations().Count == 0) {
|
||||
allDead = true;
|
||||
}
|
||||
|
||||
int best = 0;
|
||||
|
||||
foreach (LivingLocation move in top.GetLivingLocations()) {
|
||||
|
||||
// check that the move is in the startLocation list
|
||||
if (startLocations != null) {
|
||||
bool found = false;
|
||||
foreach (SolverTile l in startLocations) {
|
||||
if (locations[move.index].Equals(l)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) { // if not then skip this move
|
||||
solver.Write(move.index + " " + locations[move.index].AsText() + " is not a starting location");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int winningLines = top.GetWinningLines(move); // calculate the number of winning lines if this move is played
|
||||
|
||||
if (best < winningLines || (top.bestLiving != null && best == winningLines && top.bestLiving.mineCount < move.mineCount)) {
|
||||
best = winningLines;
|
||||
top.bestLiving = move;
|
||||
}
|
||||
|
||||
double singleProb = (allSolutions.GetSize() - move.mineCount) / allSolutions.GetSize();
|
||||
|
||||
if (move.pruned) {
|
||||
solver.Write(move.index + " " + locations[move.index].AsText() + " is living with " + move.count + " possible values and probability " + singleProb + ", this location was pruned");
|
||||
} else {
|
||||
solver.Write(move.index + " " + locations[move.index].AsText() + " is living with " + move.count + " possible values and probability " + singleProb + ", winning lines " + winningLines);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
top.winningLines = best;
|
||||
|
||||
currentNode = top;
|
||||
|
||||
if (processCount < SolverMain.BRUTE_FORCE_ANALYSIS_MAX_NODES) {
|
||||
this.completed = true;
|
||||
//if (solver.isShowProbabilityTree()) {
|
||||
// solver.newLine("--------- Probability Tree dump start ---------");
|
||||
// showTree(0, 0, top);
|
||||
// solver.newLine("---------- Probability Tree dump end ----------");
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
// clear down the cache
|
||||
cache.Clear();
|
||||
|
||||
long end = DateTime.Now.Ticks;
|
||||
|
||||
solver.Write("Total nodes in cache = " + cacheSize + ", total cache hits = " + cacheHit + ", total winning lines saved = " + this.cacheWinningLines);
|
||||
solver.Write("process took " + (end - start) + " milliseconds and explored " + processCount + " nodes");
|
||||
solver.Write("----- Brute Force Deep Analysis finished ----");
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a top of tree node based on the solutions provided
|
||||
*/
|
||||
private Node buildTopNode(SolutionTable solutionTable) {
|
||||
|
||||
Node result = new Node(this, locations.Count);
|
||||
|
||||
result.startLocation = 0;
|
||||
result.endLocation = solutionTable.GetSize();
|
||||
|
||||
List<LivingLocation> living = new List<LivingLocation>();
|
||||
|
||||
for (short i = 0; i < locations.Count; i++) {
|
||||
int value;
|
||||
|
||||
int[] valueCount = ResetValues();
|
||||
int mines = 0;
|
||||
int maxSolutions = 0;
|
||||
byte count = 0;
|
||||
sbyte minValue = 0;
|
||||
sbyte maxValue = 0;
|
||||
|
||||
for (int j = 0; j < result.GetSolutionSize(); j++) {
|
||||
if (solutionTable.Get(j)[i] != Cruncher.BOMB) {
|
||||
value = solutionTable.Get(j)[i];
|
||||
//values[value] = true;
|
||||
valueCount[value]++;
|
||||
} else {
|
||||
mines++;
|
||||
}
|
||||
}
|
||||
|
||||
for (sbyte j = 0; j < valueCount.Length; j++) {
|
||||
if (valueCount[j] > 0) {
|
||||
if (count == 0) {
|
||||
minValue = j;
|
||||
}
|
||||
maxValue = j;
|
||||
count++;
|
||||
if (maxSolutions < valueCount[j]) {
|
||||
maxSolutions = valueCount[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count > 1) {
|
||||
LivingLocation alive = new LivingLocation(this, i);
|
||||
alive.mineCount = mines;
|
||||
alive.count = count;
|
||||
alive.minValue = minValue;
|
||||
alive.maxValue = maxValue;
|
||||
alive.maxSolutions = maxSolutions;
|
||||
alive.zeroSolutions = valueCount[0];
|
||||
living.Add(alive);
|
||||
} else {
|
||||
solver.Write(locations[i].AsText() + " is dead with value " + minValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
living.Sort();
|
||||
//Collections.sort(living);
|
||||
|
||||
result.livingLocations = living;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public int[] ResetValues() {
|
||||
for (int i = 0; i < valueCount.Length; i++) {
|
||||
valueCount[i] = 0;
|
||||
}
|
||||
return valueCount;
|
||||
}
|
||||
|
||||
public int GetSolutionCount() {
|
||||
return allSolutions.GetSize();
|
||||
}
|
||||
|
||||
|
||||
public int GetNodeCount() {
|
||||
return processCount;
|
||||
}
|
||||
|
||||
public SolverAction GetNextMove() {
|
||||
|
||||
LivingLocation bestLiving = getBestLocation(currentNode);
|
||||
|
||||
if (bestLiving == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SolverTile loc = this.locations[bestLiving.index];
|
||||
|
||||
//solver.display("first best move is " + loc.display());
|
||||
double prob = 1 - bestLiving.mineCount / currentNode.GetSolutionSize();
|
||||
|
||||
while (!loc.IsHidden()) {
|
||||
int value = loc.GetValue();
|
||||
|
||||
currentNode = bestLiving.children[value];
|
||||
bestLiving = getBestLocation(currentNode);
|
||||
if (bestLiving == null) {
|
||||
return null;
|
||||
}
|
||||
prob = 1 - ((double) bestLiving.mineCount) / currentNode.GetSolutionSize();
|
||||
|
||||
loc = this.locations[bestLiving.index];
|
||||
|
||||
}
|
||||
|
||||
solver.Write("mines = " + bestLiving.mineCount + " solutions = " + currentNode.GetSolutionSize());
|
||||
for (int i = 0; i < bestLiving.children.Length; i++) {
|
||||
if (bestLiving.children[i] == null) {
|
||||
//solver.display("Value of " + i + " is not possible");
|
||||
continue; //ignore this node but continue the loop
|
||||
}
|
||||
|
||||
String probText;
|
||||
if (bestLiving.children[i].bestLiving == null) {
|
||||
probText = (100 / (bestLiving.children[i].GetSolutionSize())) + "%";
|
||||
} else {
|
||||
probText = bestLiving.children[i].GetProbability() * 100 + "%";
|
||||
}
|
||||
solver.Write("Value of " + i + " leaves " + bestLiving.children[i].GetSolutionSize() + " solutions and winning probability " + probText + " (work size " + bestLiving.children[i].work + ")");
|
||||
}
|
||||
|
||||
//String text = " (solve " + (currentNode.GetProbability() * 100) + "%)";
|
||||
SolverAction action = new SolverAction(loc, ActionType.Clear, 0.5);
|
||||
|
||||
expectedMove = loc;
|
||||
|
||||
return action;
|
||||
|
||||
}
|
||||
|
||||
private LivingLocation getBestLocation(Node node) {
|
||||
return node.bestLiving;
|
||||
}
|
||||
|
||||
private void ShowTree(int depth, int value, Node node) {
|
||||
|
||||
String condition;
|
||||
if (depth == 0) {
|
||||
condition = node.GetSolutionSize() + " solutions remain";
|
||||
} else {
|
||||
condition = "When '" + value + "' ==> " + node.GetSolutionSize() + " solutions remain";
|
||||
}
|
||||
|
||||
if (node.bestLiving == null) {
|
||||
String line1 = INDENT.Substring(0, depth * 3) + condition + " Solve chance " + node.GetProbability() * 100 + "%";
|
||||
Console.WriteLine(line1);
|
||||
return;
|
||||
}
|
||||
|
||||
SolverTile loc = this.locations[node.bestLiving.index];
|
||||
|
||||
double prob = 1 - node.bestLiving.mineCount / node.GetSolutionSize();
|
||||
|
||||
|
||||
String line = INDENT.Substring(0, depth * 3) + condition + " play " + loc.AsText() + " Survival chance " + prob * 100 + "%, Solve chance " + node.GetProbability() * 100 + "%";
|
||||
|
||||
Console.WriteLine(line);
|
||||
|
||||
//for (Node nextNode: node.bestLiving.children) {
|
||||
for (int val = 0; val < node.bestLiving.children.Length; val++) {
|
||||
Node nextNode = node.bestLiving.children[val];
|
||||
if (nextNode != null) {
|
||||
ShowTree(depth + 1, val, nextNode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public bool IsComplete() {
|
||||
return this.completed;
|
||||
}
|
||||
|
||||
public SolverTile GetExpectedMove() {
|
||||
return expectedMove;
|
||||
}
|
||||
|
||||
//private String percentage(double prob) {
|
||||
// return Action.FORMAT_2DP.format(prob.multiply(ONE_HUNDRED));
|
||||
//}
|
||||
|
||||
public bool GetAllDead() {
|
||||
return allDead;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
45
info/Minesweeper2/MinesweeperSolver/Combination.cs
Normal file
45
info/Minesweeper2/MinesweeperSolver/Combination.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
public class Combination {
|
||||
|
||||
public static BigInteger Calculate(int mines, int squares) {
|
||||
|
||||
long start = DateTime.Now.Ticks;
|
||||
|
||||
BigInteger top = 1;
|
||||
BigInteger bot = 1;
|
||||
|
||||
var range = Math.Min(mines, squares - mines);
|
||||
|
||||
// calculate the combination.
|
||||
for (int i = 0; i < range; i++) {
|
||||
top = top * (squares - i);
|
||||
bot = bot * (i + 1);
|
||||
}
|
||||
|
||||
BigInteger result = top / bot;
|
||||
|
||||
//SolverMain.Write(squares + " pick " + mines + " in " + result + " ways");
|
||||
//SolverMain.Write("Combination duration " + (DateTime.Now.Ticks - start) + " ticks");
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private static readonly BigInteger[] power10n = { BigInteger.One, new BigInteger(10), new BigInteger(100), new BigInteger(1000), new BigInteger(10000), new BigInteger(100000), new BigInteger(1000000) };
|
||||
private static readonly int[] power10 = { 1, 10, 100, 1000, 10000, 100000, 1000000 };
|
||||
|
||||
public static double DivideBigIntegerToDouble(BigInteger numerator, BigInteger denominator, int dp) {
|
||||
|
||||
var work = numerator * power10n[dp] / denominator;
|
||||
|
||||
var result = (double) work / power10[dp];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
<HasSharedItems>true</HasSharedItems>
|
||||
<SharedGUID>60591e9a-f066-4af3-b1e3-b20b2b2b0b1d</SharedGUID>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<Import_RootNamespace>MinesweeperSolver</Import_RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Binomial.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)BruteForce.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)BruteForceAnalysis.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Combination.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)PrimeSieve.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SolutionCounter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ProbabilityEngine.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SolverAction.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SolverActionHeader.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SolverInfo.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SolverMain.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SolverTile.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
13
info/Minesweeper2/MinesweeperSolver/MinesweeperSolver.shproj
Normal file
13
info/Minesweeper2/MinesweeperSolver/MinesweeperSolver.shproj
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>60591e9a-f066-4af3-b1e3-b20b2b2b0b1d</ProjectGuid>
|
||||
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
|
||||
<PropertyGroup />
|
||||
<Import Project="MinesweeperSolver.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
|
||||
</Project>
|
129
info/Minesweeper2/MinesweeperSolver/PrimeSieve.cs
Normal file
129
info/Minesweeper2/MinesweeperSolver/PrimeSieve.cs
Normal file
@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
public class PrimeSieve {
|
||||
|
||||
// iterator for prime numbers
|
||||
public class Primes : IEnumerable<int>, IEnumerator<int> {
|
||||
|
||||
private int index = 0;
|
||||
private readonly int stop;
|
||||
private int nextPrime;
|
||||
private readonly bool[] composite;
|
||||
|
||||
|
||||
public int Current => Next();
|
||||
|
||||
object IEnumerator.Current => Next();
|
||||
|
||||
public Primes(bool[] composite, int start, int stop) {
|
||||
this.index = start;
|
||||
this.stop = stop;
|
||||
this.composite = composite;
|
||||
this.nextPrime = findNext();
|
||||
}
|
||||
|
||||
public int Next() {
|
||||
int result = nextPrime;
|
||||
nextPrime = findNext();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int findNext() {
|
||||
|
||||
int next = -1;
|
||||
while (index <= stop && next == -1) {
|
||||
if (!composite[index]) {
|
||||
next = index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
return next;
|
||||
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
|
||||
}
|
||||
|
||||
public bool MoveNext() {
|
||||
return (nextPrime != -1);
|
||||
}
|
||||
|
||||
public void Reset() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IEnumerator<int> GetEnumerator() {
|
||||
return this;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly bool[] composite;
|
||||
private readonly int max;
|
||||
|
||||
public PrimeSieve(int n) {
|
||||
|
||||
if (n < 2) {
|
||||
max = 2;
|
||||
} else {
|
||||
max = n;
|
||||
}
|
||||
|
||||
composite = new bool[max + 1];
|
||||
|
||||
int rootN = (int)Math.Floor(Math.Sqrt(n));
|
||||
|
||||
for (int i = 2; i < rootN; i++) {
|
||||
|
||||
// if this is a prime number (not composite) then sieve the array
|
||||
if (!composite[i]) {
|
||||
int index = i + i;
|
||||
while (index <= max) {
|
||||
composite[index] = true;
|
||||
index = index + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public bool IsPrime(int n) {
|
||||
if (n <= 1 || n > max) {
|
||||
throw new Exception("Test value " + n + " is out of range 2 - " + max);
|
||||
}
|
||||
|
||||
return !composite [n];
|
||||
}
|
||||
|
||||
public IEnumerable<int> getPrimesIterable(int start, int stop) {
|
||||
|
||||
if (start > stop) {
|
||||
throw new Exception("start " + start + " must be <= to stop " + stop);
|
||||
}
|
||||
if (start <= 1 || start > max) {
|
||||
throw new Exception("Start value " + start + " is out of range 2 - " + max);
|
||||
}
|
||||
if (stop <= 1 || stop > max) {
|
||||
throw new Exception("Stop value " + stop + " is out of range 2 - " + max);
|
||||
}
|
||||
|
||||
return new Primes(composite, start, stop);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
2055
info/Minesweeper2/MinesweeperSolver/ProbabilityEngine.cs
Normal file
2055
info/Minesweeper2/MinesweeperSolver/ProbabilityEngine.cs
Normal file
File diff suppressed because it is too large
Load Diff
859
info/Minesweeper2/MinesweeperSolver/SolutionCounter.cs
Normal file
859
info/Minesweeper2/MinesweeperSolver/SolutionCounter.cs
Normal file
@ -0,0 +1,859 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using static MinesweeperControl.MinesweeperGame;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
public class SolutionCounter {
|
||||
|
||||
private readonly int[][] SMALL_COMBINATIONS = new int[][] { new int[] { 1 }, new int[] { 1, 1 }, new int[] { 1, 2, 1 }, new int[] { 1, 3, 3, 1 }, new int[] { 1, 4, 6, 4, 1 }, new int[] { 1, 5, 10, 10, 5, 1 }, new int[] { 1, 6, 15, 20, 15, 6, 1 }, new int[] { 1, 7, 21, 35, 35, 21, 7, 1 }, new int[] { 1, 8, 28, 56, 70, 56, 28, 8, 1 } };
|
||||
|
||||
private readonly List<SolverTile> witnessed;
|
||||
private readonly List<BoxWitness> prunedWitnesses = new List<BoxWitness>(); // a subset of allWitnesses with equivalent witnesses removed
|
||||
private readonly List<Box> boxes = new List<Box>();
|
||||
private readonly List<BoxWitness> boxWitnesses = new List<BoxWitness>();
|
||||
private bool[] mask;
|
||||
|
||||
private readonly Dictionary<SolverTile, Box> boxLookup = new Dictionary<SolverTile, Box>(); // a lookup which finds which box a tile belongs to (an efficiency enhancement)
|
||||
//private readonly List<DeadCandidate> deadCandidates = new List<DeadCandidate>();
|
||||
|
||||
private List<ProbabilityLine> workingProbs = new List<ProbabilityLine>();
|
||||
private readonly List<ProbabilityLine> heldProbs = new List<ProbabilityLine>();
|
||||
private readonly List<EdgeStore> edgeStore = new List<EdgeStore>(); // stores independent edge analysis until we know we have to merge them
|
||||
|
||||
|
||||
private readonly int minesLeft;
|
||||
private readonly int tilesLeft;
|
||||
private readonly int tilesOffEdge;
|
||||
private readonly int minTotalMines;
|
||||
private readonly int maxTotalMines;
|
||||
private int recursions;
|
||||
|
||||
// used to find the range of mine counts which offer the 2.5% - 97.5% weighted average
|
||||
private int edgeMinesMin;
|
||||
private int edgeMinesMax;
|
||||
private int edgeMinesMinLeft;
|
||||
private int edgeMinesMaxLeft;
|
||||
private int mineCountUpperCutoff;
|
||||
private int mineCountLowerCutoff;
|
||||
private bool truncatedProbs = false; // this gets set when the number of held probabilies exceeds the permitted threshold
|
||||
|
||||
private BigInteger finalSolutionCount = 0;
|
||||
private BigInteger solutionCountMultiplier = 1;
|
||||
|
||||
private int clearCount = 0;
|
||||
|
||||
private readonly SolverInfo information;
|
||||
|
||||
public SolutionCounter(SolverInfo information, List<SolverTile> allWitnesses, List<SolverTile> allWitnessed, int squaresLeft, int minesLeft) {
|
||||
|
||||
this.information = information;
|
||||
|
||||
this.witnessed = allWitnessed;
|
||||
|
||||
// constraints in the game
|
||||
this.minesLeft = minesLeft;
|
||||
this.tilesLeft = squaresLeft;
|
||||
this.tilesOffEdge = squaresLeft - allWitnessed.Count; // squares left off the edge and unrevealed
|
||||
this.minTotalMines = minesLeft - this.tilesOffEdge; //we can't use so few mines that we can't fit the remainder elsewhere on the board
|
||||
this.maxTotalMines = minesLeft;
|
||||
|
||||
this.mineCountUpperCutoff = minesLeft;
|
||||
this.mineCountLowerCutoff = minTotalMines;
|
||||
|
||||
information.Write("Tiles off edge " + tilesOffEdge);
|
||||
|
||||
//this.boxProb = []; // the probabilities end up here
|
||||
|
||||
// generate a BoxWitness for each witness tile and also create a list of pruned witnesses for the brute force search
|
||||
int pruned = 0;
|
||||
foreach (SolverTile wit in allWitnesses) {
|
||||
|
||||
BoxWitness boxWit = new BoxWitness(information, wit);
|
||||
|
||||
// if the witness is a duplicate then don't store it
|
||||
bool duplicate = false;
|
||||
foreach (BoxWitness w in this.boxWitnesses) {
|
||||
|
||||
if (w.Equivalent(boxWit)) {
|
||||
//if (boardState.getWitnessValue(w) - boardState.countAdjacentConfirmedFlags(w) != boardState.getWitnessValue(wit) - boardState.countAdjacentConfirmedFlags(wit)) {
|
||||
// boardState.display(w.display() + " and " + wit.display() + " share unrevealed squares but have different mine totals!");
|
||||
// validWeb = false;
|
||||
//}
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!duplicate) {
|
||||
this.prunedWitnesses.Add(boxWit);
|
||||
} else {
|
||||
pruned++;
|
||||
}
|
||||
this.boxWitnesses.Add(boxWit); // all witnesses are needed for the probability engine
|
||||
}
|
||||
information.Write("Pruned " + pruned + " witnesses as duplicates");
|
||||
information.Write("There are " + this.boxWitnesses.Count + " Box witnesses");
|
||||
|
||||
// allocate each of the witnessed squares to a box
|
||||
int uid = 0;
|
||||
foreach (SolverTile tile in this.witnessed) {
|
||||
|
||||
// for each adjacent tile see if it is a witness
|
||||
int count = 0;
|
||||
//foreach (SolverTile adjTile in information.GetAdjacentTiles(tile)) {
|
||||
// if (information.GetWitnesses().Contains(adjTile)) {
|
||||
// count++;
|
||||
// }
|
||||
//}
|
||||
|
||||
// count how many adjacent witnesses the tile has
|
||||
foreach (SolverTile tile1 in allWitnesses) {
|
||||
if (tile.IsAdjacent(tile1)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
// see if the witnessed tile fits any existing boxes
|
||||
bool found = false;
|
||||
foreach (Box box in this.boxes) {
|
||||
|
||||
if (box.Fits(tile, count)) {
|
||||
box.Add(tile);
|
||||
boxLookup.Add(tile, box); // add this to the lookup
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if not found create a new box and store it
|
||||
if (!found) {
|
||||
Box box = new Box(this.boxWitnesses, tile, uid++);
|
||||
this.boxes.Add(box);
|
||||
boxLookup.Add(tile, box); // add this to the lookup
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// calculate the min and max mines for each box
|
||||
foreach (Box box in this.boxes) {
|
||||
box.Calculate(this.minesLeft);
|
||||
//console.log("Box " + box.tiles[0].asText() + " has min mines = " + box.minMines + " and max mines = " + box.maxMines);
|
||||
}
|
||||
|
||||
// Report how many boxes each witness is adjacent to
|
||||
foreach (BoxWitness boxWit in this.boxWitnesses) {
|
||||
information.Write("Witness " + boxWit.GetTile().AsText() + " is adjacent to " + boxWit.GetBoxes().Count + " boxes and has " + boxWit.GetMinesToFind() + " mines to find");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// calculate a probability for each un-revealed tile on the board
|
||||
public void Process() {
|
||||
|
||||
this.mask = new bool[this.boxes.Count];
|
||||
|
||||
// create an initial solution of no mines anywhere
|
||||
ProbabilityLine held = new ProbabilityLine(this.boxes.Count);
|
||||
held.SetSolutionCount(1);
|
||||
this.heldProbs.Add(held);
|
||||
|
||||
// add an empty probability line to get us started
|
||||
this.workingProbs.Add(new ProbabilityLine(this.boxes.Count));
|
||||
|
||||
NextWitness nextWitness = FindFirstWitness();
|
||||
|
||||
while (nextWitness != null) {
|
||||
|
||||
// mark the new boxes as processed - which they will be soon
|
||||
foreach (Box box in nextWitness.GetNewBoxes()) {
|
||||
this.mask[box.GetUID()] = true;
|
||||
}
|
||||
|
||||
this.workingProbs = MergeProbabilities(nextWitness);
|
||||
|
||||
nextWitness = FindNextWitness(nextWitness);
|
||||
|
||||
}
|
||||
|
||||
CalculateBoxProbabilities();
|
||||
|
||||
}
|
||||
|
||||
|
||||
// take the next witness details and merge them into the currently held details
|
||||
private List<ProbabilityLine> MergeProbabilities(NextWitness nw) {
|
||||
|
||||
List<ProbabilityLine> newProbs = new List<ProbabilityLine>();
|
||||
|
||||
foreach (ProbabilityLine pl in this.workingProbs) {
|
||||
|
||||
int missingMines = nw.GetBoxWitness().GetMinesToFind() - (int)CountPlacedMines(pl, nw);
|
||||
|
||||
if (missingMines < 0) {
|
||||
//console.log("Missing mines < 0 ==> ignoring line");
|
||||
// too many mines placed around this witness previously, so this probability can't be valid
|
||||
} else if (missingMines == 0) {
|
||||
//console.log("Missing mines = 0 ==> keeping line as is");
|
||||
newProbs.Add(pl); // witness already exactly satisfied, so nothing to do
|
||||
} else if (nw.GetNewBoxes().Count == 0) {
|
||||
//console.log("new boxes = 0 ==> ignoring line since nowhere for mines to go");
|
||||
// nowhere to put the new mines, so this probability can't be valid
|
||||
} else {
|
||||
|
||||
List<ProbabilityLine> result = DistributeMissingMines(pl, nw, missingMines, 0);
|
||||
newProbs.AddRange(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//if (newProbs.length == 0) {
|
||||
// console.log("Returning no lines from merge probability !!");
|
||||
//}
|
||||
|
||||
return newProbs;
|
||||
|
||||
}
|
||||
|
||||
// counts the number of mines already placed
|
||||
private BigInteger CountPlacedMines(ProbabilityLine pl, NextWitness nw) {
|
||||
|
||||
BigInteger result = 0;
|
||||
|
||||
foreach (Box b in nw.GetOldBoxes()) {
|
||||
|
||||
result = result + pl.GetMineBoxCount(b.GetUID());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// this is used to recursively place the missing Mines into the available boxes for the probability line
|
||||
private List<ProbabilityLine> DistributeMissingMines(ProbabilityLine pl, NextWitness nw, int missingMines, int index) {
|
||||
|
||||
//console.log("Distributing " + missingMines + " missing mines to box " + nw.newBoxes[index].uid);
|
||||
|
||||
this.recursions++;
|
||||
if (this.recursions % 100000 == 0) {
|
||||
information.Write("Solution Counter recursision = " + recursions);
|
||||
}
|
||||
|
||||
List<ProbabilityLine> result = new List<ProbabilityLine>();
|
||||
|
||||
// if there is only one box left to put the missing mines we have reach the end of this branch of recursion
|
||||
if (nw.GetNewBoxes().Count - index == 1) {
|
||||
// if there are too many for this box then the probability can't be valid
|
||||
if (nw.GetNewBoxes()[index].GetMaxMines() < missingMines) {
|
||||
//console.log("Abandon (1)");
|
||||
return result;
|
||||
}
|
||||
// if there are too few for this box then the probability can't be valid
|
||||
if (nw.GetNewBoxes()[index].GetMinMines() > missingMines) {
|
||||
//console.log("Abandon (2)");
|
||||
return result;
|
||||
}
|
||||
// if there are too many for this game then the probability can't be valid
|
||||
if (pl.GetMineCount() + missingMines > this.maxTotalMines) {
|
||||
//console.log("Abandon (3)");
|
||||
return result;
|
||||
}
|
||||
|
||||
// otherwise place the mines in the probability line
|
||||
pl.SetMineBoxCount(nw.GetNewBoxes()[index].GetUID(), new BigInteger(missingMines));
|
||||
|
||||
pl.SetMineCount(pl.GetMineCount() + missingMines);
|
||||
result.Add(pl);
|
||||
//console.log("Distribute missing mines line after " + pl.mineBoxCount);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// this is the recursion
|
||||
int maxToPlace = Math.Min(nw.GetNewBoxes()[index].GetMaxMines(), missingMines);
|
||||
|
||||
for (int i = nw.GetNewBoxes()[index].GetMinMines(); i <= maxToPlace; i++) {
|
||||
|
||||
ProbabilityLine npl = ExtendProbabilityLine(pl, nw.GetNewBoxes()[index], i);
|
||||
|
||||
List<ProbabilityLine> r1 = DistributeMissingMines(npl, nw, missingMines - i, index + 1);
|
||||
result.AddRange(r1);
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
// create a new probability line by taking the old and adding the mines to the new Box
|
||||
private ProbabilityLine ExtendProbabilityLine(ProbabilityLine pl, Box newBox, int mines) {
|
||||
|
||||
//console.log("Extended probability line: Adding " + mines + " mines to box " + newBox.uid);
|
||||
//console.log("Extended probability line before" + pl.mineBoxCount);
|
||||
|
||||
ProbabilityLine result = new ProbabilityLine(this.boxes.Count);
|
||||
|
||||
result.SetMineCount(pl.GetMineCount() + mines);
|
||||
|
||||
result.CopyMineBoxCount(pl);
|
||||
|
||||
result.SetMineBoxCount(newBox.GetUID(), new BigInteger(mines));
|
||||
|
||||
//console.log("Extended probability line after " + result.mineBoxCount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// here we store the information we have found about this independent edge for later use
|
||||
private void StoreProbabilities() {
|
||||
|
||||
List<ProbabilityLine> crunched = CrunchByMineCount(this.workingProbs);
|
||||
|
||||
//Console.WriteLine("Edge has " + crunched.Count + " probability lines after consolidation");
|
||||
|
||||
edgeStore.Add(new EdgeStore(crunched, this.mask)); // store the existing data
|
||||
|
||||
workingProbs.Clear(); // and get a new list for the next independent edge
|
||||
|
||||
this.workingProbs.Add(new ProbabilityLine(this.boxes.Count)); // add a new starter probability line
|
||||
|
||||
this.mask = new bool[boxes.Count];
|
||||
|
||||
}
|
||||
|
||||
// this combines newly generated probabilities with ones we have already stored from other independent sets of witnesses
|
||||
private void CombineProbabilities() {
|
||||
|
||||
List<ProbabilityLine> result = new List<ProbabilityLine>();
|
||||
|
||||
if (this.workingProbs.Count == 0) {
|
||||
information.Write("working probabilites list is empty!!");
|
||||
return;
|
||||
}
|
||||
|
||||
// see if we can find a common divisor
|
||||
BigInteger hcd = workingProbs[0].GetSolutionCount();
|
||||
foreach (ProbabilityLine pl in workingProbs) {
|
||||
hcd = BigInteger.GreatestCommonDivisor(hcd, pl.GetSolutionCount());
|
||||
}
|
||||
foreach (ProbabilityLine pl in heldProbs) {
|
||||
hcd = BigInteger.GreatestCommonDivisor(hcd, pl.GetSolutionCount());
|
||||
}
|
||||
information.Write("Greatest Common Divisor is " + hcd);
|
||||
|
||||
solutionCountMultiplier = solutionCountMultiplier * hcd;
|
||||
|
||||
int mineCountMin = workingProbs[0].GetMineCount() + heldProbs[0].GetMineCount();
|
||||
|
||||
// shrink the window
|
||||
edgeMinesMinLeft = edgeMinesMinLeft - workingProbs[0].GetMineCount();
|
||||
edgeMinesMaxLeft = edgeMinesMaxLeft - workingProbs[workingProbs.Count - 1].GetMineCount();
|
||||
|
||||
foreach (ProbabilityLine pl in workingProbs) {
|
||||
|
||||
BigInteger plSolCount = pl.GetSolutionCount() / hcd;
|
||||
|
||||
foreach (ProbabilityLine epl in heldProbs) {
|
||||
|
||||
// if the mine count can never reach the lower cuttoff then ignore it
|
||||
if (pl.GetMineCount() + epl.GetMineCount() + edgeMinesMaxLeft < this.mineCountLowerCutoff) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the mine count will always be pushed beyonf the upper cuttoff then ignore it
|
||||
if (pl.GetMineCount() + epl.GetMineCount() + edgeMinesMinLeft > this.mineCountUpperCutoff) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ProbabilityLine newpl = new ProbabilityLine(this.boxes.Count);
|
||||
|
||||
newpl.SetMineCount(pl.GetMineCount() + epl.GetMineCount());
|
||||
|
||||
BigInteger eplSolCount = epl.GetSolutionCount() / hcd;
|
||||
|
||||
newpl.SetSolutionCount(pl.GetSolutionCount() * eplSolCount);
|
||||
|
||||
for (int k = 0; k < this.boxes.Count; k++) {
|
||||
|
||||
BigInteger w1 = pl.GetMineBoxCount(k) * eplSolCount;
|
||||
BigInteger w2 = epl.GetMineBoxCount(k) * plSolCount;
|
||||
newpl.SetMineBoxCount(k, w1 + w2);
|
||||
|
||||
}
|
||||
|
||||
result.Add(newpl);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Console.WriteLine("Solution multiplier is " + solutionCountMultiplier);
|
||||
|
||||
this.heldProbs.Clear();
|
||||
|
||||
// if result is empty this is an impossible position
|
||||
if (result.Count == 0) {
|
||||
Console.WriteLine("Impossible position encountered");
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.Count == 1) {
|
||||
heldProbs.AddRange(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// sort into mine order
|
||||
result.Sort();
|
||||
|
||||
// and combine them into a single probability line for each mine count
|
||||
int mc = result[0].GetMineCount();
|
||||
ProbabilityLine npl = new ProbabilityLine(this.boxes.Count);
|
||||
npl.SetMineCount(mc);
|
||||
|
||||
int startMC = mc;
|
||||
|
||||
foreach (ProbabilityLine pl in result) {
|
||||
|
||||
if (pl.GetMineCount() != mc) {
|
||||
|
||||
this.heldProbs.Add(npl);
|
||||
mc = pl.GetMineCount();
|
||||
npl = new ProbabilityLine(this.boxes.Count);
|
||||
npl.SetMineCount(mc);
|
||||
}
|
||||
npl.SetSolutionCount(npl.GetSolutionCount() + pl.GetSolutionCount());
|
||||
|
||||
for (int j = 0; j < this.boxes.Count; j++) {
|
||||
npl.SetMineBoxCount(j, npl.GetMineBoxCount(j) + pl.GetMineBoxCount(j));
|
||||
}
|
||||
}
|
||||
|
||||
this.heldProbs.Add(npl);
|
||||
|
||||
}
|
||||
|
||||
private List<ProbabilityLine> CrunchByMineCount(List<ProbabilityLine> target) {
|
||||
|
||||
//if (target.Count == 0) {
|
||||
// return target;
|
||||
//}
|
||||
|
||||
// sort the solutions by number of mines
|
||||
target.Sort();
|
||||
|
||||
List<ProbabilityLine> result = new List<ProbabilityLine>();
|
||||
|
||||
int mc = target[0].GetMineCount();
|
||||
ProbabilityLine npl = new ProbabilityLine(this.boxes.Count);
|
||||
npl.SetMineCount(mc);
|
||||
|
||||
foreach (ProbabilityLine pl in target) {
|
||||
|
||||
if (pl.GetMineCount() != mc) {
|
||||
result.Add(npl);
|
||||
mc = pl.GetMineCount();
|
||||
npl = new ProbabilityLine(this.boxes.Count);
|
||||
npl.SetMineCount(mc);
|
||||
}
|
||||
MergeLineProbabilities(npl, pl);
|
||||
}
|
||||
|
||||
//if (npl.GetMineCount() >= minTotalMines) {
|
||||
result.Add(npl);
|
||||
//}
|
||||
//information.Write("Probability line has " + npl.GetMineCount() + " mines");
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
// calculate how many ways this solution can be generated and roll them into one
|
||||
private void MergeLineProbabilities(ProbabilityLine npl, ProbabilityLine pl) {
|
||||
|
||||
BigInteger solutions = 1;
|
||||
for (int i = 0; i < this.boxes.Count; i++) {
|
||||
solutions = solutions * (BigInteger)SMALL_COMBINATIONS[this.boxes[i].GetTiles().Count][(int)pl.GetMineBoxCount(i)];
|
||||
}
|
||||
|
||||
npl.SetSolutionCount(npl.GetSolutionCount() + solutions);
|
||||
|
||||
for (int i = 0; i < this.boxes.Count; i++) {
|
||||
if (this.mask[i]) { // if this box has been involved in this solution - if we don't do this the hash gets corrupted by boxes = 0 mines because they weren't part of this edge
|
||||
npl.SetMineBoxCount(i, npl.GetMineBoxCount(i) + pl.GetMineBoxCount(i) * solutions);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// return any witness which hasn't been processed
|
||||
private NextWitness FindFirstWitness() {
|
||||
|
||||
BoxWitness excluded = null;
|
||||
foreach (BoxWitness boxWit in this.boxWitnesses) {
|
||||
if (!boxWit.IsProcessed()) {
|
||||
return new NextWitness(boxWit);
|
||||
} else if (!boxWit.IsProcessed()) {
|
||||
excluded = boxWit;
|
||||
}
|
||||
}
|
||||
if (excluded != null) {
|
||||
return new NextWitness(excluded);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// look for the next witness to process
|
||||
private NextWitness FindNextWitness(NextWitness prevWitness) {
|
||||
|
||||
// flag the last set of details as processed
|
||||
prevWitness.GetBoxWitness().SetProcessed(true);
|
||||
|
||||
foreach (Box newBox in prevWitness.GetNewBoxes()) {
|
||||
newBox.SetProcessed(true);
|
||||
}
|
||||
|
||||
int bestTodo = 99999;
|
||||
BoxWitness bestWitness = null;
|
||||
|
||||
// and find a witness which is on the boundary of what has already been processed
|
||||
foreach (Box b in this.boxes) {
|
||||
if (b.IsProcessed()) {
|
||||
foreach (BoxWitness w in b.GetBoxWitnesses()) {
|
||||
if (!w.IsProcessed()) {
|
||||
int todo = 0;
|
||||
foreach (Box b1 in w.GetBoxes()) {
|
||||
if (!b1.IsProcessed()) {
|
||||
todo++;
|
||||
}
|
||||
}
|
||||
if (todo == 0) { // prioritise the witnesses which have the least boxes left to process
|
||||
return new NextWitness(w);
|
||||
} else if (todo < bestTodo) {
|
||||
bestTodo = todo;
|
||||
bestWitness = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestWitness != null) {
|
||||
return new NextWitness(bestWitness);
|
||||
} else {
|
||||
information.Write("Ending independent edge");
|
||||
}
|
||||
|
||||
//independentGroups++;
|
||||
|
||||
// since we have calculated all the mines in an independent set of witnesses we can crunch them down and store them for later
|
||||
|
||||
// store the details for this edge
|
||||
StoreProbabilities();
|
||||
|
||||
|
||||
// get an unprocessed witness
|
||||
NextWitness nw = FindFirstWitness();
|
||||
if (nw != null) {
|
||||
information.Write("Starting a new independent edge");
|
||||
}
|
||||
|
||||
// If there is nothing else to process then either do the local clears or calculate the probabilities
|
||||
if (nw == null) {
|
||||
|
||||
edgeStore.Sort(EdgeStore.SortByLineCount);
|
||||
|
||||
AnalyseAllEdges();
|
||||
|
||||
long start = DateTime.Now.Ticks;
|
||||
foreach (EdgeStore edgeDetails in edgeStore) {
|
||||
workingProbs = edgeDetails.data;
|
||||
CombineProbabilities();
|
||||
}
|
||||
information.Write("Combined all edges in " + (DateTime.Now.Ticks - start) + " ticks");
|
||||
|
||||
}
|
||||
|
||||
// return the next witness to process
|
||||
return nw;
|
||||
|
||||
}
|
||||
|
||||
// take a look at what edges we have and show some information - trying to get some ideas on how we can get faster good guesses
|
||||
private void AnalyseAllEdges() {
|
||||
|
||||
//Console.WriteLine("Number of tiles off the edge " + tilesOffEdge);
|
||||
//Console.WriteLine("Number of mines to find " + minesLeft);
|
||||
|
||||
this.edgeMinesMin = 0;
|
||||
this.edgeMinesMax = 0;
|
||||
|
||||
foreach (EdgeStore edge in edgeStore) {
|
||||
int edgeMinMines = edge.data[0].GetMineCount();
|
||||
int edgeMaxMines = edge.data[edge.data.Count - 1].GetMineCount();
|
||||
|
||||
edgeMinesMin = edgeMinesMin + edgeMinMines;
|
||||
edgeMinesMax = edgeMinesMax + edgeMaxMines;
|
||||
}
|
||||
|
||||
information.Write("Min mines on all edges " + edgeMinesMin + ", max " + edgeMinesMax);
|
||||
this.edgeMinesMaxLeft = this.edgeMinesMax; // these values are used in the merge logic to reduce the number of lines need to keep
|
||||
this.edgeMinesMinLeft = this.edgeMinesMin;
|
||||
|
||||
this.mineCountLowerCutoff = this.edgeMinesMin;
|
||||
this.mineCountUpperCutoff = Math.Min(this.edgeMinesMax, this.minesLeft); // can't have more mines than are left
|
||||
|
||||
// comment this out when doing large board analysis
|
||||
return;
|
||||
|
||||
// the code below reduces the range of mine count values to just be the 'significant' range
|
||||
|
||||
List<ProbabilityLine> store = new List<ProbabilityLine>();
|
||||
List<ProbabilityLine> store1 = new List<ProbabilityLine>();
|
||||
|
||||
ProbabilityLine init = new ProbabilityLine(0);
|
||||
init.SetSolutionCount(1);
|
||||
|
||||
store.Add(init);
|
||||
|
||||
// combine all the edges to determine the relative weights of the mine count
|
||||
foreach (EdgeStore edgeDetails in edgeStore) {
|
||||
|
||||
foreach (ProbabilityLine pl in edgeDetails.data) {
|
||||
|
||||
BigInteger plSolCount = pl.GetSolutionCount();
|
||||
|
||||
foreach (ProbabilityLine epl in store) {
|
||||
|
||||
if (pl.GetMineCount() + epl.GetMineCount() <= this.maxTotalMines) {
|
||||
|
||||
ProbabilityLine newpl = new ProbabilityLine(0);
|
||||
|
||||
newpl.SetMineCount(pl.GetMineCount() + epl.GetMineCount());
|
||||
|
||||
BigInteger eplSolCount = epl.GetSolutionCount();
|
||||
|
||||
newpl.SetSolutionCount(pl.GetSolutionCount() * eplSolCount);
|
||||
|
||||
store1.Add(newpl);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store.Clear();
|
||||
|
||||
// sort into mine order
|
||||
store1.Sort();
|
||||
|
||||
int mc = store1[0].GetMineCount();
|
||||
ProbabilityLine npl = new ProbabilityLine(0);
|
||||
npl.SetMineCount(mc);
|
||||
|
||||
foreach (ProbabilityLine pl in store1) {
|
||||
|
||||
if (pl.GetMineCount() != mc) {
|
||||
|
||||
store.Add(npl);
|
||||
mc = pl.GetMineCount();
|
||||
npl = new ProbabilityLine(0);
|
||||
npl.SetMineCount(mc);
|
||||
}
|
||||
npl.SetSolutionCount(npl.GetSolutionCount() + pl.GetSolutionCount());
|
||||
|
||||
}
|
||||
|
||||
store.Add(npl);
|
||||
store1.Clear();
|
||||
}
|
||||
|
||||
BigInteger total = 0;
|
||||
int mineValues = 0;
|
||||
foreach (ProbabilityLine pl in store) {
|
||||
if (pl.GetMineCount() >= this.minTotalMines) { // if the mine count for this solution is less than the minimum it can't be valid
|
||||
BigInteger mult = SolverMain.Calculate(this.minesLeft - pl.GetMineCount(), this.tilesOffEdge); //# of ways the rest of the board can be formed
|
||||
total = total + mult * pl.GetSolutionCount();
|
||||
mineValues++;
|
||||
}
|
||||
}
|
||||
|
||||
//this.mineCountLowerCutoff = this.edgeMinesMin;
|
||||
//this.mineCountUpperCutoff = Math.Min(this.edgeMinesMax, this.minesLeft); // can't have more mines than are left
|
||||
BigInteger soFar = 0;
|
||||
foreach (ProbabilityLine pl in store) {
|
||||
if (pl.GetMineCount() >= this.minTotalMines) { // if the mine count for this solution is less than the minimum it can't be valid
|
||||
BigInteger mult = SolverMain.Calculate(this.minesLeft - pl.GetMineCount(), this.tilesOffEdge); //# of ways the rest of the board can be formed
|
||||
soFar = soFar + mult * pl.GetSolutionCount();
|
||||
double perc = Combination.DivideBigIntegerToDouble(soFar, total, 6) * 100;
|
||||
//Console.WriteLine("Mine count " + pl.GetMineCount() + " has solution count " + pl.GetSolutionCount() + " multiplier " + mult + " running % " + perc);
|
||||
//Console.WriteLine("Mine count " + pl.GetMineCount() + " has solution count " + pl.GetSolutionCount() + " has running % " + perc);
|
||||
|
||||
if (mineValues > 30 && perc < 2.5) {
|
||||
this.mineCountLowerCutoff = pl.GetMineCount();
|
||||
}
|
||||
|
||||
if (mineValues > 30 && perc > 97.5) {
|
||||
this.mineCountUpperCutoff = pl.GetMineCount();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
information.Write("Significant range " + this.mineCountLowerCutoff + " - " + this.mineCountUpperCutoff);
|
||||
//this.edgeMinesMaxLeft = this.edgeMinesMax;
|
||||
//this.edgeMinesMinLeft = this.edgeMinesMin;
|
||||
|
||||
|
||||
return;
|
||||
|
||||
// below here are experimental ideas on getting a good guess on very large boards
|
||||
|
||||
int midRangeAllMines = (this.mineCountLowerCutoff + this.mineCountUpperCutoff) / 2;
|
||||
BigInteger[] tally = new BigInteger[boxes.Count];
|
||||
double[] probability = new double[boxes.Count];
|
||||
|
||||
|
||||
foreach (EdgeStore edgeDetails in edgeStore) {
|
||||
|
||||
int sizeRangeEdgeMines = (edgeDetails.data[edgeDetails.data.Count - 1].GetMineCount() - edgeDetails.data[0].GetMineCount()) / 2;
|
||||
|
||||
int start = (this.mineCountLowerCutoff - edgeDetails.data[0].GetMineCount() + this.mineCountUpperCutoff - edgeDetails.data[edgeDetails.data.Count - 1].GetMineCount()) / 2;
|
||||
//int start = midRangeAllMines - sizeRangeEdgeMines;
|
||||
|
||||
//BigInteger mult = Combination.Calculate(this.minesLeft - start, this.tilesOffEdge);
|
||||
BigInteger totalTally = 0;
|
||||
|
||||
foreach (ProbabilityLine pl in edgeDetails.data) {
|
||||
|
||||
BigInteger mult = Combination.Calculate(this.minesLeft - start - pl.GetMineCount(), this.tilesOffEdge);
|
||||
totalTally += mult * pl.GetSolutionCount();
|
||||
for (int i = 0; i < boxes.Count; i++) {
|
||||
if (edgeDetails.mask[i]) {
|
||||
BigInteger work = pl.GetMineBoxCount(i) * mult;
|
||||
tally[i] += work;
|
||||
}
|
||||
}
|
||||
|
||||
//mult = mult * (this.tilesOffEdge - start) / (start + 1);
|
||||
//start++;
|
||||
|
||||
}
|
||||
|
||||
for (int i = 0; i < boxes.Count; i++) {
|
||||
if (edgeDetails.mask[i]) {
|
||||
probability[i] = Combination.DivideBigIntegerToDouble(tally[i], totalTally, 6) / boxes[i].GetTiles().Count;
|
||||
}
|
||||
}
|
||||
|
||||
int minIndex = -1;
|
||||
for (int i = 0; i < boxes.Count; i++) {
|
||||
if (edgeDetails.mask[i]) {
|
||||
if (minIndex == -1 || probability[i] < probability[minIndex]) {
|
||||
minIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (minIndex != -1) {
|
||||
information.Write("Best guess is " + boxes[minIndex].GetTiles()[0].AsText() + " with " + (1 - probability[minIndex]));
|
||||
} else {
|
||||
information.Write("No Guess found");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// here we expand the localised solution to one across the whole board and
|
||||
// sum them together to create a definitive probability for each box
|
||||
private void CalculateBoxProbabilities() {
|
||||
|
||||
//long start = DateTime.Now.Ticks;
|
||||
|
||||
if (truncatedProbs) {
|
||||
Console.WriteLine("probability line combining was truncated");
|
||||
}
|
||||
|
||||
information.Write("Solution count multiplier is " + this.solutionCountMultiplier);
|
||||
|
||||
BigInteger[] tally = new BigInteger[this.boxes.Count];
|
||||
|
||||
// total game tally
|
||||
BigInteger totalTally = 0;
|
||||
|
||||
// outside a box tally
|
||||
BigInteger outsideTally = 0;
|
||||
|
||||
//console.log("There are " + this.heldProbs.length + " different mine counts on the edge");
|
||||
|
||||
bool[] emptyBox = new bool[boxes.Count];
|
||||
for (int i = 0; i < emptyBox.Length; i++) {
|
||||
emptyBox[i] = true;
|
||||
}
|
||||
|
||||
int linesProcessed = 0;
|
||||
// calculate how many mines
|
||||
foreach (ProbabilityLine pl in this.heldProbs) {
|
||||
|
||||
//console.log("Mine count is " + pl.mineCount + " with solution count " + pl.solutionCount + " mineBoxCount = " + pl.mineBoxCount);
|
||||
|
||||
if (pl.GetMineCount() >= this.minTotalMines) { // if the mine count for this solution is less than the minimum it can't be valid
|
||||
|
||||
linesProcessed++;
|
||||
|
||||
//console.log("Mines left " + this.minesLeft + " mines on PL " + pl.mineCount + " squares left = " + this.squaresLeft);
|
||||
BigInteger mult = SolverMain.Calculate(this.minesLeft - pl.GetMineCount(), this.tilesOffEdge); //# of ways the rest of the board can be formed
|
||||
|
||||
information.Write("Mines in solution " + pl.GetMineCount() + " solution count " + pl.GetSolutionCount() + " multiplier " + mult);
|
||||
|
||||
outsideTally = outsideTally + mult * new BigInteger(this.minesLeft - pl.GetMineCount()) * (pl.GetSolutionCount());
|
||||
|
||||
// this is all the possible ways the mines can be placed across the whole game
|
||||
totalTally = totalTally + mult * (pl.GetSolutionCount());
|
||||
|
||||
for (int i = 0; i < emptyBox.Length; i++) {
|
||||
if (pl.GetMineBoxCount(i) != 0) {
|
||||
emptyBox[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// determine how many clear squares there are
|
||||
if (totalTally > 0) {
|
||||
for (int i = 0; i < emptyBox.Length; i++) {
|
||||
if (emptyBox[i]) {
|
||||
clearCount = clearCount + boxes[i].GetTiles().Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.finalSolutionCount = totalTally * solutionCountMultiplier;
|
||||
|
||||
information.Write("Game has " + this.finalSolutionCount + " candidate solutions");
|
||||
|
||||
}
|
||||
|
||||
public BigInteger GetSolutionCount() {
|
||||
return this.finalSolutionCount;
|
||||
}
|
||||
|
||||
public int getClearCount() {
|
||||
return this.clearCount;
|
||||
}
|
||||
|
||||
public int GetSolutionCountMagnitude() {
|
||||
return (int)Math.Floor(BigInteger.Log10(this.finalSolutionCount));
|
||||
}
|
||||
|
||||
public SolverInfo GetSolverInfo() {
|
||||
return this.information;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
23
info/Minesweeper2/MinesweeperSolver/SolverAction.cs
Normal file
23
info/Minesweeper2/MinesweeperSolver/SolverAction.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using static MinesweeperControl.MinesweeperGame;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
public class SolverAction : GameAction, IComparable<SolverAction> {
|
||||
|
||||
public readonly double safeProbability;
|
||||
|
||||
public readonly bool isDead;
|
||||
//public readonly bool isExcluded;
|
||||
|
||||
public SolverAction(SolverTile tile, ActionType action, double safeprob) : base(tile.x, tile.y, action) {
|
||||
|
||||
this.safeProbability = safeprob;
|
||||
this.isDead = tile.IsDead();
|
||||
//this.isExcluded = tile.IsExcluded();
|
||||
}
|
||||
|
||||
public int CompareTo(SolverAction other) {
|
||||
return safeProbability.CompareTo(other.safeProbability);
|
||||
}
|
||||
}
|
||||
}
|
22
info/Minesweeper2/MinesweeperSolver/SolverActionHeader.cs
Normal file
22
info/Minesweeper2/MinesweeperSolver/SolverActionHeader.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
class SolverActionHeader {
|
||||
|
||||
public readonly IList<SolverAction> solverActions;
|
||||
public readonly IList<SolverAction> deadActions;
|
||||
|
||||
public SolverActionHeader() {
|
||||
List<SolverAction> empty = new List<SolverAction>();
|
||||
this.solverActions = empty.AsReadOnly();
|
||||
this.deadActions = empty.AsReadOnly();
|
||||
}
|
||||
|
||||
public SolverActionHeader(List<SolverAction> solverActions, List<SolverAction> deadActions) {
|
||||
this.solverActions = solverActions.AsReadOnly();
|
||||
this.deadActions = deadActions.AsReadOnly();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
377
info/Minesweeper2/MinesweeperSolver/SolverInfo.cs
Normal file
377
info/Minesweeper2/MinesweeperSolver/SolverInfo.cs
Normal file
@ -0,0 +1,377 @@
|
||||
using MinesweeperControl;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static MinesweeperControl.MinesweeperGame;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
public class SolverInfo {
|
||||
|
||||
private static readonly bool EXCLUDE_ON = false;
|
||||
|
||||
// contains information about what surrounds a tile
|
||||
public class AdjacentInfo {
|
||||
|
||||
public readonly int mines;
|
||||
public readonly int hidden;
|
||||
public readonly int excluded;
|
||||
|
||||
public AdjacentInfo(int mines, int hidden, int excluded) {
|
||||
this.mines = mines;
|
||||
this.hidden = hidden;
|
||||
this.excluded = excluded;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private readonly int key = new Random().Next();
|
||||
|
||||
public readonly GameDescription description;
|
||||
public readonly bool verbose;
|
||||
|
||||
private GameStatus gameStatus = GameStatus.NotStarted;
|
||||
private readonly SolverTile[,] tiles;
|
||||
private int tilesLeft;
|
||||
|
||||
private ProbabilityEngine probabilityEngine = null; // this is the probability engine from the last solver run
|
||||
private BruteForceAnalysis bfa = null; // this is the brute force analysis from the last solver run
|
||||
|
||||
private readonly HashSet<SolverTile> livingWitnesses = new HashSet<SolverTile>(20); // this is a set of tiles which are clear and have hidden neighbours.
|
||||
private readonly HashSet<SolverTile> knownMines; // this is a set of tiles which are known to be mines.
|
||||
private readonly HashSet<SolverTile> pendingClears = new HashSet<SolverTile>(); // this is a set of tiles which are clear and haven't yet been clicked.
|
||||
|
||||
|
||||
private readonly HashSet<SolverTile> deadTiles = new HashSet<SolverTile>(); // this is a set of tiles which are known to be dead.
|
||||
//private readonly HashSet<SolverTile> excludedWitnesses = new HashSet<SolverTile>(); // this is a set of witnesses which no longer influence the solving of the game.
|
||||
//private readonly HashSet<SolverTile> excludedTiles = new HashSet<SolverTile>(); // this is a set of tiles which no longer influence the solving of the game.
|
||||
//private int excludedMinesCount = 0; // number of mines which are with the exluded tiles area
|
||||
|
||||
|
||||
private readonly List<SolverTile> newClears = new List<SolverTile>();
|
||||
|
||||
public SolverInfo(GameDescription description) : this(description, false) {
|
||||
}
|
||||
|
||||
public SolverInfo(GameDescription description, bool verbose) {
|
||||
|
||||
this.description = description;
|
||||
this.verbose = verbose;
|
||||
|
||||
tiles = new SolverTile[description.width, description.height];
|
||||
|
||||
// populate the grid with solver tiles showing what we know so far ... which is nothing
|
||||
for (int x = 0; x < description.width; x++) {
|
||||
for (int y = 0; y < description.height; y++) {
|
||||
tiles[x, y] = new SolverTile(key, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
tilesLeft = description.height * description.width;
|
||||
|
||||
knownMines = new HashSet<SolverTile>(description.mines);
|
||||
|
||||
}
|
||||
|
||||
public void AddInformation(GameResult information) {
|
||||
|
||||
//long start = DateTime.Now.Ticks;
|
||||
|
||||
gameStatus = information.status;
|
||||
|
||||
this.probabilityEngine = null; // previous probability engine invalidated
|
||||
|
||||
newClears.Clear();
|
||||
|
||||
// the game results tell us when a tile is cleared, flagged or unflagged
|
||||
foreach (ActionResult ar in information.actionResults) {
|
||||
|
||||
if (ar.resultType == ResultType.Cleared) {
|
||||
tilesLeft--;
|
||||
tiles[ar.x, ar.y].SetValue(ar.value);
|
||||
|
||||
SolverTile tile = tiles[ar.x, ar.y];
|
||||
newClears.Add(tile);
|
||||
if (tile.IsDead()) {
|
||||
deadTiles.Remove(tile);
|
||||
}
|
||||
//if (tile.IsExcluded()) {
|
||||
// excludedTiles.Remove(tile);
|
||||
//}
|
||||
//pendingClears.Remove(tile);
|
||||
|
||||
} else if (ar.resultType == ResultType.Flagged) {
|
||||
tiles[ar.x, ar.y].SetFlagged(true);
|
||||
} else if (ar.resultType == ResultType.Hidden) {
|
||||
tiles[ar.x, ar.y].SetFlagged(false);
|
||||
} else if (ar.resultType == ResultType.Exploded) {
|
||||
SolverTile tile = tiles[ar.x, ar.y];
|
||||
tile.SetFlagged(false);
|
||||
MineFound(tile);
|
||||
this.bfa = null; // can't walk the bfa tree if we've trodden on a mine
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// find and mark tiles as exhausted
|
||||
//int removed = 0;
|
||||
//if (newClears.Count > 0) {
|
||||
|
||||
foreach (SolverTile tile in livingWitnesses) {
|
||||
|
||||
if (AdjacentTileInfo(tile).hidden == 0) {
|
||||
tile.SetExhausted();
|
||||
}
|
||||
}
|
||||
// remove all exhausted tiles from the list of witnesses
|
||||
livingWitnesses.RemoveWhere(x => x.IsExhausted());
|
||||
|
||||
//}
|
||||
|
||||
// add new witnesses which aren't exhausted
|
||||
foreach (SolverTile newTile in newClears) {
|
||||
AdjacentInfo adjInfo = AdjacentTileInfo(newTile);
|
||||
if (adjInfo.hidden != 0) {
|
||||
if (adjInfo.excluded != adjInfo.hidden) {
|
||||
livingWitnesses.Add(newTile);
|
||||
} else {
|
||||
//excludedWitnesses.Add(newTile);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Write("There are " + livingWitnesses.Count + " living witnesses (" + removed + " deleted)");
|
||||
//Write("Adding Information to Solver took " + (DateTime.Now.Ticks - start) + " ticks");
|
||||
}
|
||||
|
||||
public int GetMinesLeft() {
|
||||
return description.mines - knownMines.Count; // mines left to find = mines in game - mines found
|
||||
}
|
||||
|
||||
public int GetTilesLeft() {
|
||||
return this.tilesLeft;
|
||||
}
|
||||
|
||||
// sets the tile as a mine, stores it for future use and returns true if it is currently flagged
|
||||
public bool MineFound(SolverTile tile) {
|
||||
|
||||
// if this is already known to be mine then nothing to do
|
||||
if (tile.IsMine()) {
|
||||
return tile.IsFlagged();
|
||||
}
|
||||
|
||||
tilesLeft--;
|
||||
|
||||
tile.SetAsMine(key);
|
||||
|
||||
knownMines.Add(tile);
|
||||
|
||||
if (tile.IsDead()) {
|
||||
deadTiles.Remove(tile); // remove the tile if it was on the dead list
|
||||
}
|
||||
//if (tile.IsExcluded()) {
|
||||
// excludedTiles.Remove(tile); // remove the tile if it was excluded
|
||||
// excludedMinesCount--;
|
||||
//}
|
||||
|
||||
return tile.IsFlagged();
|
||||
|
||||
}
|
||||
|
||||
public void SetTileToDead(SolverTile tile) {
|
||||
|
||||
if (tile.IsMine()) {
|
||||
Write("ERROR: Trying to set a mine tile to dead " + tile.AsText());
|
||||
return;
|
||||
}
|
||||
|
||||
tile.SetDead(key); // mark it
|
||||
deadTiles.Add(tile); // and add to the set
|
||||
|
||||
}
|
||||
|
||||
public HashSet<SolverTile> GetDeadTiles() {
|
||||
return deadTiles;
|
||||
}
|
||||
|
||||
/*
|
||||
public void ExcludeMines(int n) {
|
||||
|
||||
if (!EXCLUDE_ON) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.excludedMinesCount = this.excludedMinesCount + n;
|
||||
}
|
||||
|
||||
public int GetExcludedMineCount() {
|
||||
return this.excludedMinesCount;
|
||||
}
|
||||
|
||||
public void ExcludeTile(SolverTile tile) {
|
||||
|
||||
if (!EXCLUDE_ON) {
|
||||
return;
|
||||
}
|
||||
|
||||
tile.SetExcluded();
|
||||
excludedTiles.Add(tile);
|
||||
}
|
||||
|
||||
public HashSet<SolverTile> GetExcludedTiles() {
|
||||
return this.excludedTiles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move a tile from the list of witnesses to the list of excluded witnesses
|
||||
/// </summary>
|
||||
public void ExcludeWitness(SolverTile tile) {
|
||||
|
||||
if (!EXCLUDE_ON) {
|
||||
return;
|
||||
}
|
||||
|
||||
tile.SetExcluded();
|
||||
if (livingWitnesses.Remove(tile)) {
|
||||
excludedWitnesses.Add(tile);
|
||||
}
|
||||
}
|
||||
|
||||
public HashSet<SolverTile> GetExcludedWitnesses() {
|
||||
return this.excludedWitnesses;
|
||||
}
|
||||
*/
|
||||
|
||||
public void SetBruteForceAnalysis(BruteForceAnalysis bfa) {
|
||||
this.bfa = bfa;
|
||||
}
|
||||
|
||||
public BruteForceAnalysis GetBruteForceAnalysis() {
|
||||
return this.bfa;
|
||||
}
|
||||
|
||||
public void SetProbabilityEngine(ProbabilityEngine pe) {
|
||||
this.probabilityEngine = pe;
|
||||
}
|
||||
|
||||
// returns the probability the tile is safe if known. Or -1 otherwise.
|
||||
public double GetProbability(int x, int y) {
|
||||
|
||||
// if we are out of bounds then nothing to say
|
||||
if (x < 0 || x >= description.width || y < 0 || y >= description.height) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SolverTile tile = tiles[x, y];
|
||||
|
||||
// an unflagged mine
|
||||
if (tile.IsMine() && !tile.IsFlagged()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// otherwise if revealed then nothing to say
|
||||
if (!tile.IsHidden()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
//if (tile.IsExcluded()) {
|
||||
// return -1;
|
||||
//}
|
||||
|
||||
if (probabilityEngine != null) {
|
||||
return probabilityEngine.GetProbability(tile);
|
||||
} else if (pendingClears.Contains(tile)) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//public bool IsTileDead(SolverTile tile) {
|
||||
// return deadTiles.Contains(tile);
|
||||
//}
|
||||
|
||||
// allow the gui to see if a tile is dead
|
||||
//public bool IsTileDead(int x, int y) {
|
||||
// return IsTileDead(tiles[x, y]);
|
||||
//}
|
||||
|
||||
// Adds the tile to a list of known clears for future use
|
||||
public void ClearFound(SolverTile tile) {
|
||||
pendingClears.Add(tile);
|
||||
}
|
||||
|
||||
// returns all the indices adjacent to this index
|
||||
public List<SolverTile> GetAdjacentTiles(SolverTile tile) {
|
||||
|
||||
// have we already calculated the adjacent tiles
|
||||
List<SolverTile> adjTiles = tile.GetAdjacentTiles();
|
||||
if (adjTiles != null) {
|
||||
return adjTiles;
|
||||
}
|
||||
|
||||
adjTiles = new List<SolverTile>();
|
||||
|
||||
int first_row = Math.Max(0, tile.y - 1);
|
||||
int last_row = Math.Min(description.height - 1, tile.y + 1);
|
||||
|
||||
int first_col = Math.Max(0, tile.x - 1);
|
||||
int last_col = Math.Min(description.width - 1, tile.x + 1);
|
||||
|
||||
for (int r = first_row; r <= last_row; r++) {
|
||||
for (int c = first_col; c <= last_col; c++) {
|
||||
if (r != tile.y || c != tile.x) {
|
||||
adjTiles.Add(tiles[c, r]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remember them for next time
|
||||
tile.SetAdjacentTiles(adjTiles);
|
||||
|
||||
return adjTiles;
|
||||
}
|
||||
|
||||
public AdjacentInfo AdjacentTileInfo(SolverTile tile) {
|
||||
|
||||
int hidden = 0;
|
||||
int mines = 0;
|
||||
int excluded = 0;
|
||||
foreach (SolverTile adjTile in GetAdjacentTiles(tile)) {
|
||||
if (adjTile.IsMine()) {
|
||||
mines++;
|
||||
} else if (adjTile.IsHidden() ) {
|
||||
hidden++;
|
||||
//if (adjTile.IsExcluded()) {
|
||||
// excluded++;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
return new AdjacentInfo(mines, hidden, excluded);
|
||||
}
|
||||
|
||||
public GameStatus GetGameStatus() {
|
||||
return gameStatus;
|
||||
}
|
||||
|
||||
public SolverTile GetTile(int x, int y) {
|
||||
return tiles[x, y];
|
||||
}
|
||||
|
||||
public HashSet<SolverTile> GetWitnesses() {
|
||||
return livingWitnesses;
|
||||
}
|
||||
|
||||
public HashSet<SolverTile> GetKnownMines() {
|
||||
return knownMines;
|
||||
}
|
||||
|
||||
public void Write(string text) {
|
||||
if (verbose) {
|
||||
Console.WriteLine(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
614
info/Minesweeper2/MinesweeperSolver/SolverMain.cs
Normal file
614
info/Minesweeper2/MinesweeperSolver/SolverMain.cs
Normal file
@ -0,0 +1,614 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using static MinesweeperControl.MinesweeperGame;
|
||||
using static MinesweeperSolver.SolverInfo;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
|
||||
|
||||
class SolverMain {
|
||||
|
||||
public static readonly BigInteger PARALLEL_MINIMUM = new BigInteger(10000);
|
||||
public static readonly BigInteger MAX_BRUTE_FORCE_ITERATIONS = new BigInteger(10000000);
|
||||
|
||||
public const int BRUTE_FORCE_ANALYSIS_TREE_DEPTH = 20;
|
||||
public const int MAX_BFDA_SOLUTIONS = 400;
|
||||
public const int BRUTE_FORCE_ANALYSIS_MAX_NODES = 150000;
|
||||
public const bool PRUNE_BF_ANALYSIS = true;
|
||||
|
||||
private static readonly Binomial binomial = new Binomial(500000, 500);
|
||||
|
||||
public static SolverActionHeader FindActions(SolverInfo information) {
|
||||
|
||||
long time1 = DateTime.Now.Ticks;
|
||||
|
||||
List<SolverAction> actions ;
|
||||
|
||||
if (information.GetGameStatus() == GameStatus.Lost) {
|
||||
information.Write("Game has been lost already - no valid moves");
|
||||
return new SolverActionHeader();
|
||||
} else if (information.GetGameStatus() == GameStatus.Won) {
|
||||
information.Write("Game has been won already - no valid moves");
|
||||
return new SolverActionHeader();
|
||||
} else if (information.GetGameStatus() == GameStatus.NotStarted) {
|
||||
information.Write("Game has not yet started - currently unable to provide help");
|
||||
return new SolverActionHeader();
|
||||
}
|
||||
|
||||
// are we walking down a brute force deep analysis tree?
|
||||
BruteForceAnalysis lastBfa = information.GetBruteForceAnalysis();
|
||||
if (lastBfa != null) { // yes
|
||||
SolverTile expectedMove = lastBfa.GetExpectedMove();
|
||||
if (expectedMove != null && expectedMove.IsHidden()) { // the expected move wasn't played !
|
||||
information.Write("The expected Brute Force Analysis move " + expectedMove.AsText() + " wasn't played");
|
||||
information.SetBruteForceAnalysis(null);
|
||||
} else {
|
||||
SolverAction move = lastBfa.GetNextMove();
|
||||
if (move != null) {
|
||||
information.Write("Next Brute Force Deep Analysis move is " + move.AsText());
|
||||
actions = new List<SolverAction>(1);
|
||||
actions.Add(move);
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
actions = FindTrivialActions(information);
|
||||
|
||||
long time2 = DateTime.Now.Ticks;
|
||||
|
||||
information.Write("Finding Trivial Actions took " + (time2 - time1) + " ticks");
|
||||
|
||||
// if we have some actions from the trivial search use them
|
||||
if (actions.Count > 0) {
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
|
||||
|
||||
if (information.GetTilesLeft() == information.GetDeadTiles().Count) {
|
||||
information.Write("All tiles remaining are dead");
|
||||
|
||||
// when all the tiles are dead return the first one (they are all equally good or bad)
|
||||
foreach (SolverTile guess in information.GetDeadTiles()) {
|
||||
actions.Add(new SolverAction(guess, ActionType.Clear, 0.5)); // not all 0.5 safe though !!
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
}
|
||||
|
||||
// get all the witnessed tiles
|
||||
|
||||
List<SolverTile> witnesses = new List<SolverTile>(information.GetWitnesses());
|
||||
HashSet<SolverTile> witnessedSet = new HashSet<SolverTile>();
|
||||
|
||||
foreach (SolverTile witness in witnesses) {
|
||||
|
||||
foreach(SolverTile adjTile in information.GetAdjacentTiles(witness)) {
|
||||
|
||||
if (adjTile.IsHidden()) {
|
||||
witnessedSet.Add(adjTile);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
List<SolverTile> witnessed = new List<SolverTile>(witnessedSet);
|
||||
int livingTilesLeft = information.GetTilesLeft();
|
||||
int livingMinesLeft = information.GetMinesLeft();
|
||||
int offEdgeTilesLeft = information.GetTilesLeft() - witnessed.Count;
|
||||
|
||||
//information.Write("Excluded tiles " + information.GetExcludedTiles().Count + " out of " + information.GetTilesLeft());
|
||||
//information.Write("Excluded witnesses " + information.GetExcludedWitnesses().Count);
|
||||
//information.Write("Excluded mines " + information.GetExcludedMineCount() + " out of " + information.GetMinesLeft());
|
||||
|
||||
// if there are no living mines but some living tiles then the living tiles can be cleared
|
||||
if (livingMinesLeft == 0 && livingTilesLeft > 0) {
|
||||
information.Write("There are no living mines left - all living tiles must be clearable");
|
||||
|
||||
for (int x = 0; x < information.description.width; x++) {
|
||||
for (int y = 0; y < information.description.height; y++) {
|
||||
SolverTile tile = information.GetTile(x, y);
|
||||
if (tile.IsHidden() && !tile.IsMine()) {
|
||||
actions.Add(new SolverAction(tile, ActionType.Clear, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
|
||||
|
||||
SolutionCounter solutionCounter = new SolutionCounter(information, witnesses, witnessed, livingTilesLeft, livingMinesLeft);
|
||||
solutionCounter.Process();
|
||||
information.Write("Solution counter says " + solutionCounter.GetSolutionCount() + " solutions and " + solutionCounter.getClearCount() + " clears");
|
||||
|
||||
|
||||
//ProbabilityEngine pe = new ProbabilityEngine(information, witnesses, witnessed, information.GetTilesLeft(), information.GetMinesLeft());
|
||||
ProbabilityEngine pe = new ProbabilityEngine(information, witnesses, witnessed, livingTilesLeft, livingMinesLeft);
|
||||
|
||||
pe.Process();
|
||||
|
||||
long time3 = DateTime.Now.Ticks;
|
||||
|
||||
information.Write("Probability Engine took " + (time3 - time2) + " ticks");
|
||||
|
||||
// have we found any local clears which we can use
|
||||
List<SolverTile> localClears = pe.GetLocalClears();
|
||||
if (localClears.Count > 0) {
|
||||
foreach (SolverTile tile in localClears) { // place each local clear into an action
|
||||
actions.Add(new SolverAction(tile, ActionType.Clear, 1));
|
||||
}
|
||||
information.Write("The probability engine has found " + localClears.Count + " safe Local Clears");
|
||||
|
||||
// add any mines to it
|
||||
List<SolverTile> minesFound = pe.GetMinesFound();
|
||||
foreach (SolverTile tile in minesFound) { // place each mine found into an action
|
||||
information.MineFound(tile);
|
||||
actions.Add(new SolverAction(tile, ActionType.Flag, 0));
|
||||
}
|
||||
information.Write("The probability engine has found " + minesFound.Count + " mines");
|
||||
|
||||
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
|
||||
if (pe.GetBestEdgeProbability() == 1) {
|
||||
actions = pe.GetBestCandidates(1);
|
||||
information.Write("The probability engine has found " + actions.Count + " safe Clears");
|
||||
|
||||
// add any mines to it
|
||||
List<SolverTile> minesFound = pe.GetMinesFound();
|
||||
foreach (SolverTile tile in minesFound) { // place each mine found into an action
|
||||
information.MineFound(tile);
|
||||
actions.Add(new SolverAction(tile, ActionType.Flag, 0));
|
||||
}
|
||||
information.Write("The probability engine has found " + minesFound.Count + " mines");
|
||||
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
|
||||
// dead edge (all tiles on the edge are dead and there is only one mine count value)
|
||||
if (pe.GetDeadEdge().Count != 0) {
|
||||
SolverTile tile = pe.GetDeadEdge()[0];
|
||||
information.Write("Probability engine has found a dead area, guessing at " + tile.AsText());
|
||||
double probability = Combination.DivideBigIntegerToDouble(BigInteger.One, pe.GetDeadEdgeSolutionCount(), 6);
|
||||
actions.Add(new SolverAction(tile, ActionType.Clear, probability));
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
|
||||
// Isolated edges found (all adjacent tiles are also on the edge and there is only one mine count value)
|
||||
if (pe.GetOutcome() == ProbabilityEngine.Outcome.ISOLATED_EDGE) {
|
||||
information.Write("Probability engine has found an isolated area");
|
||||
|
||||
Cruncher cruncher = pe.getIsolatedEdgeCruncher();
|
||||
|
||||
// determine all possible solutions
|
||||
cruncher.Crunch();
|
||||
|
||||
// determine best way to solver them
|
||||
BruteForceAnalysis bfa = cruncher.GetBruteForceAnalysis();
|
||||
bfa.process();
|
||||
|
||||
// if after trying to process the data we can't complete then abandon it
|
||||
if (!bfa.IsComplete()) {
|
||||
information.Write("Abandoned the Brute Force Analysis after " + bfa.GetNodeCount() + " steps");
|
||||
bfa = null;
|
||||
|
||||
} else { // otherwise try and get the best long term move
|
||||
information.Write("Built probability tree from " + bfa.GetSolutionCount() + " solutions in " + bfa.GetNodeCount() + " steps");
|
||||
SolverAction move = bfa.GetNextMove();
|
||||
if (move != null) {
|
||||
information.SetBruteForceAnalysis(bfa); // save the details so we can walk the tree
|
||||
information.Write("Brute Force Analysis: " + move.AsText());
|
||||
actions.Add(move);
|
||||
return BuildActionHeader(information, actions);
|
||||
} else if (bfa.GetAllDead()) {
|
||||
|
||||
SolverTile tile = cruncher.getTiles()[0];
|
||||
information.Write("Brute Force Analysis has decided all tiles are dead on the Isolated Edge, guessing at " + tile.AsText());
|
||||
double probability = Combination.DivideBigIntegerToDouble(BigInteger.One, bfa.GetSolutionCount(), 6);
|
||||
actions.Add(new SolverAction(tile, ActionType.Clear, probability));
|
||||
return BuildActionHeader(information, actions);
|
||||
|
||||
} else {
|
||||
information.Write("Brute Force Analysis: no move found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// after this point we know the probability engine didn't return any certain clears. But there are still some special cases when everything off edge is either clear or a mine
|
||||
|
||||
// If there are tiles off the edge and they are definitely safe then clear them all, or mines then flag them
|
||||
if (offEdgeTilesLeft > 0 && (pe.GetOffEdgeProbability() == 1 || pe.GetOffEdgeProbability() == 0)) {
|
||||
information.Write("Looking for the certain moves off the edge found by the probability engine");
|
||||
bool clear;
|
||||
if (pe.GetOffEdgeProbability() == 1) {
|
||||
information.Write("All off edge tiles are clear");
|
||||
clear = true;
|
||||
} else {
|
||||
information.Write("All off edge tiles are mines");
|
||||
clear = false;
|
||||
}
|
||||
|
||||
for (int x=0; x < information.description.width; x++) {
|
||||
for (int y = 0; y < information.description.height; y++) {
|
||||
|
||||
SolverTile tile = information.GetTile(x, y);
|
||||
|
||||
if (tile.IsHidden() && !witnessedSet.Contains(tile)) {
|
||||
if (clear) {
|
||||
information.Write(tile.AsText() + " is clear");
|
||||
actions.Add(new SolverAction(tile, ActionType.Clear, 1));
|
||||
} else {
|
||||
information.Write(tile.AsText() + " is mine");
|
||||
information.MineFound(tile);
|
||||
actions.Add(new SolverAction(tile, ActionType.Flag, 0));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (actions.Count > 0) {
|
||||
|
||||
// add any mines to it
|
||||
List<SolverTile> minesFound = pe.GetMinesFound();
|
||||
foreach (SolverTile tile in minesFound) { // place each mine found into an action
|
||||
information.MineFound(tile);
|
||||
actions.Add(new SolverAction(tile, ActionType.Flag, 0));
|
||||
}
|
||||
information.Write("The probability engine has found " + minesFound.Count + " mines");
|
||||
|
||||
return BuildActionHeader(information, actions);
|
||||
} else {
|
||||
Console.WriteLine("No Actions found!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// these are guesses
|
||||
List<SolverAction> guesses = pe.GetBestCandidates(1);
|
||||
|
||||
// we know the Probability Engine completed so hold onto the information for the gui
|
||||
information.SetProbabilityEngine(pe);
|
||||
|
||||
// if there aren't many possible solutions then do a brute force search
|
||||
if (pe.GetSolutionCount() <= MAX_BFDA_SOLUTIONS) {
|
||||
|
||||
//if (minesFound.Count > 0) {
|
||||
// information.Write("Not doing a brute force analysis because we found some mines using the probability engine");
|
||||
// return BuildActionHeader(information, actions);
|
||||
//}
|
||||
|
||||
// find a set of independent witnesses we can use as the base of the iteration
|
||||
pe.GenerateIndependentWitnesses();
|
||||
|
||||
BigInteger expectedIterations = pe.GetIndependentIterations() * SolverMain.Calculate(livingMinesLeft - pe.GetIndependentMines(), livingTilesLeft - pe.GetIndependentTiles());
|
||||
|
||||
information.Write("Expected Brute Force iterations " + expectedIterations);
|
||||
|
||||
// do the brute force if there are not too many iterations
|
||||
if (expectedIterations < MAX_BRUTE_FORCE_ITERATIONS) {
|
||||
|
||||
List<SolverTile> allCoveredTiles = new List<SolverTile>();
|
||||
|
||||
for (int x = 0; x < information.description.width; x++) {
|
||||
for (int y = 0; y < information.description.height; y++) {
|
||||
SolverTile tile = information.GetTile(x, y);
|
||||
if (tile.IsHidden() && !tile.IsMine()) {
|
||||
allCoveredTiles.Add(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WitnessWebIterator[] iterators = BuildParallelIterators(information, pe, allCoveredTiles, expectedIterations);
|
||||
BruteForceAnalysis bfa = Cruncher.PerformBruteForce(information, iterators, pe.GetDependentWitnesses());
|
||||
|
||||
bfa.process();
|
||||
|
||||
// if after trying to process the data we can't complete then abandon it
|
||||
if (!bfa.IsComplete()) {
|
||||
information.Write("Abandoned the Brute Force Analysis after " + bfa.GetNodeCount() + " steps");
|
||||
bfa = null;
|
||||
|
||||
} else { // otherwise try and get the best long term move
|
||||
information.Write("Built probability tree from " + bfa.GetSolutionCount() + " solutions in " + bfa.GetNodeCount() + " steps");
|
||||
SolverAction move = bfa.GetNextMove();
|
||||
if (move != null) {
|
||||
information.SetBruteForceAnalysis(bfa); // save the details so we can walk the tree
|
||||
information.Write("Brute Force Analysis: " + move.AsText());
|
||||
actions.Add(move);
|
||||
return BuildActionHeader(information, actions);
|
||||
} else {
|
||||
information.Write("Brute Force Analysis: no move found!");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
information.Write("Too many iterations, Brute Force not atempted");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (guesses.Count == 0) { // find an off edge guess
|
||||
if (offEdgeTilesLeft > 0) {
|
||||
information.Write("getting an off edge guess");
|
||||
SolverTile tile = OffEdgeGuess(information, witnessedSet);
|
||||
SolverAction action = new SolverAction(tile, ActionType.Clear, pe.GetOffEdgeProbability());
|
||||
information.Write(action.AsText());
|
||||
actions.Add(action);
|
||||
} else {
|
||||
if (information.GetDeadTiles().Count > 0) {
|
||||
information.Write("Finding a dead tile to guess");
|
||||
SolverTile tile = null;
|
||||
foreach (SolverTile deadTile in information.GetDeadTiles()) { // get the first dead tile
|
||||
tile = deadTile;
|
||||
break;
|
||||
}
|
||||
SolverAction action = new SolverAction(tile, ActionType.Clear, 0.5); // probability may not be 0.5
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else if (guesses.Count > 1) { // if we have more than 1 guess then do some tie break logic
|
||||
information.Write("Doing a tie break for " + guesses.Count + " actions");
|
||||
actions = DoTieBreak(guesses);
|
||||
|
||||
} else {
|
||||
actions = guesses;
|
||||
}
|
||||
|
||||
return BuildActionHeader(information, actions);
|
||||
|
||||
}
|
||||
|
||||
private static SolverActionHeader BuildActionHeader(SolverInfo information, List<SolverAction> actions) {
|
||||
|
||||
HashSet<SolverTile> dead = information.GetDeadTiles();
|
||||
|
||||
// add any dead tiles to the list of actions
|
||||
List<SolverAction> deadList = new List<SolverAction>();
|
||||
foreach (SolverTile dt in dead) {
|
||||
deadList.Add(new SolverAction(dt, ActionType.Dead, 1));
|
||||
}
|
||||
|
||||
return new SolverActionHeader(actions, deadList);
|
||||
|
||||
}
|
||||
|
||||
private static List<SolverAction> FindTrivialActions(SolverInfo information) {
|
||||
|
||||
List<SolverAction> actions = new List<SolverAction>();
|
||||
|
||||
// provide actions for known mines which aren't flagged
|
||||
foreach (SolverTile tile in information.GetKnownMines()) {
|
||||
if (!tile.IsFlagged()) {
|
||||
actions.Add(new SolverAction(tile, ActionType.Flag, 0)); // request it is flagged
|
||||
}
|
||||
}
|
||||
|
||||
actions.AddRange(ScanForTrivialActions(information, information.GetWitnesses()));
|
||||
|
||||
//actions.AddRange(ScanForTrivialActions(information, information.GetExcludedWitnesses()));
|
||||
|
||||
information.Write("Found " + actions.Count + " trivial actions");
|
||||
|
||||
return actions;
|
||||
|
||||
}
|
||||
|
||||
private static List<SolverAction> ScanForTrivialActions(SolverInfo information, HashSet<SolverTile> set) {
|
||||
|
||||
List<SolverAction> actions = new List<SolverAction>(); ;
|
||||
|
||||
HashSet<SolverTile> alreadyProcessed = new HashSet<SolverTile>();
|
||||
|
||||
foreach (SolverTile tile in set) {
|
||||
|
||||
AdjacentInfo adjInfo = information.AdjacentTileInfo(tile);
|
||||
|
||||
if (tile.GetValue() == adjInfo.mines) { // if we have the correct number of mines then the remaining hidden tiles can be cleared
|
||||
foreach (SolverTile adjTile in information.GetAdjacentTiles(tile)) {
|
||||
if (!alreadyProcessed.Contains(adjTile) && adjTile.IsHidden() && !adjTile.IsMine()) {
|
||||
alreadyProcessed.Add(adjTile); // avoid marking as cleared more than once
|
||||
//Utility.Write(adjTile.AsText() + " can be cleared");
|
||||
actions.Add(new SolverAction(adjTile, ActionType.Clear, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tile.GetValue() == adjInfo.mines + adjInfo.hidden) { // If Hidden + Mines we already know about = tile value then the rest must be mines
|
||||
foreach (SolverTile adjTile in information.GetAdjacentTiles(tile)) {
|
||||
if (!alreadyProcessed.Contains(adjTile) && adjTile.IsHidden() && !adjTile.IsMine()) {
|
||||
alreadyProcessed.Add(adjTile); // avoid marking as a mine more than once
|
||||
//Utility.Write(adjTile.AsText() + " is a mine");
|
||||
if (!information.MineFound(adjTile)) {
|
||||
actions.Add(new SolverAction(adjTile, ActionType.Flag, 0)); // and request it is flagged
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2 mines to find and only 3 tiles to put them
|
||||
if (tile.GetValue() - adjInfo.mines == 2 && adjInfo.hidden == 3) {
|
||||
foreach (SolverTile adjTile in information.GetAdjacentTiles(tile)) {
|
||||
if (!adjTile.IsHidden() && !adjTile.IsMine() && !adjTile.IsExhausted()) {
|
||||
|
||||
AdjacentInfo tileAdjInfo = information.AdjacentTileInfo(adjTile);
|
||||
|
||||
if (adjTile.GetValue() - tileAdjInfo.mines == 1) { // an adjacent tile only needs to find one mine
|
||||
|
||||
int notAdjacentCount = 0;
|
||||
SolverTile notAdjTile = null;
|
||||
foreach (SolverTile adjTile2 in information.GetAdjacentTiles(tile)) {
|
||||
if (adjTile2.IsHidden() && !adjTile2.IsAdjacent(adjTile)) {
|
||||
notAdjacentCount++;
|
||||
notAdjTile = adjTile2;
|
||||
}
|
||||
}
|
||||
|
||||
if (notAdjacentCount == 1 && !alreadyProcessed.Contains(notAdjTile)) { // we share all but one tile, so that one tile must contain the extra mine
|
||||
alreadyProcessed.Add(notAdjTile); // avoid marking as a mine more than once
|
||||
if (!information.MineFound(notAdjTile)) {
|
||||
actions.Add(new SolverAction(notAdjTile, ActionType.Flag, 0)); // and request it is flagged
|
||||
break; // restrict to finding one mine at a time (the action of marking the mine throws any further analysis out)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subtraction method
|
||||
//continue; // skip this bit
|
||||
foreach (SolverTile adjTile in information.GetAdjacentTiles(tile)) {
|
||||
|
||||
if (!adjTile.IsHidden() && !adjTile.IsMine() && !adjTile.IsExhausted()) {
|
||||
|
||||
AdjacentInfo tileAdjInfo = information.AdjacentTileInfo(adjTile);
|
||||
|
||||
if (adjTile.GetValue() - tileAdjInfo.mines == tile.GetValue() - adjInfo.mines) { // if the adjacent tile is revealed and shares the same number of mines to find
|
||||
// If all the adjacent tiles adjacent tiles are also adjacent to the original tile
|
||||
bool allAdjacent = true;
|
||||
foreach (SolverTile adjTile2 in information.GetAdjacentTiles(adjTile)) {
|
||||
if (adjTile2.IsHidden() && !adjTile2.IsAdjacent(tile)) {
|
||||
allAdjacent = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allAdjacent) {
|
||||
//information.Write(tile.AsText() + " can be subtracted by " + adjTile.AsText());
|
||||
foreach (SolverTile adjTile2 in information.GetAdjacentTiles(tile)) {
|
||||
if (adjTile2.IsHidden() && !adjTile2.IsAdjacent(adjTile) && !alreadyProcessed.Contains(adjTile2)) {
|
||||
alreadyProcessed.Add(adjTile2); // avoid marking as a mine more than once
|
||||
actions.Add(new SolverAction(adjTile2, ActionType.Clear, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private static SolverTile OffEdgeGuess(SolverInfo information, HashSet<SolverTile> witnessedSet) {
|
||||
|
||||
// see if the corners are available
|
||||
SolverTile bestGuess = information.GetTile(0, 0);
|
||||
if (bestGuess.IsHidden() && !witnessedSet.Contains(bestGuess)) {
|
||||
return bestGuess;
|
||||
}
|
||||
bestGuess = information.GetTile(0, information.description.height - 1);
|
||||
if (bestGuess.IsHidden() && !witnessedSet.Contains(bestGuess)) {
|
||||
return bestGuess;
|
||||
}
|
||||
bestGuess = information.GetTile(information.description.width - 1, 0);
|
||||
if (bestGuess.IsHidden() && !witnessedSet.Contains(bestGuess)) {
|
||||
return bestGuess;
|
||||
}
|
||||
bestGuess = information.GetTile(information.description.width - 1, information.description.height - 1);
|
||||
if (bestGuess.IsHidden() && !witnessedSet.Contains(bestGuess)) {
|
||||
return bestGuess;
|
||||
}
|
||||
|
||||
bestGuess = null;
|
||||
int bestGuessCount = 9;
|
||||
|
||||
for (int x = 0; x < information.description.width; x++) {
|
||||
for (int y = 0; y < information.description.height; y++) {
|
||||
|
||||
SolverTile tile = information.GetTile(x, y);
|
||||
|
||||
if (tile.IsHidden() && !witnessedSet.Contains(tile)) {
|
||||
AdjacentInfo adjInfo = information.AdjacentTileInfo(tile);
|
||||
if (adjInfo.hidden < bestGuessCount) {
|
||||
bestGuess = tile;
|
||||
bestGuessCount = adjInfo.hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestGuess;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// do tie break logic
|
||||
private static List<SolverAction> DoTieBreak(List<SolverAction> actions) {
|
||||
|
||||
List<SolverAction> result = new List<SolverAction>();
|
||||
|
||||
// find and return a not dead tile
|
||||
foreach (SolverAction sa in actions) {
|
||||
if (!sa.isDead) {
|
||||
result.Add(sa);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise return the first one
|
||||
result.Add(actions[0]);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
// break a witness web search into a number of non-overlapping iterators
|
||||
private static WitnessWebIterator[] BuildParallelIterators(SolverInfo information, ProbabilityEngine pe, List<SolverTile> allCovered, BigInteger expectedIterations) {
|
||||
|
||||
information.Write("Building parallel iterators");
|
||||
|
||||
//information.Write("Non independent iterations = " + pe.GetNonIndependentIterations(mines));
|
||||
|
||||
int minesLeft = information.GetMinesLeft();
|
||||
|
||||
//the number of witnesses available
|
||||
int totalWitnesses = pe.GetIndependentWitnesses().Count + pe.GetDependentWitnesses().Count;
|
||||
|
||||
// if there is only one cog then we can't lock it,so send back a single iterator
|
||||
if (pe.GetIndependentWitnesses().Count == 1 && pe.GetIndependentMines() >= minesLeft || expectedIterations.CompareTo(PARALLEL_MINIMUM) < 0 || totalWitnesses == 0) {
|
||||
information.Write("Only a single iterator will be used");
|
||||
WitnessWebIterator[] one = new WitnessWebIterator[1];
|
||||
one[0] = new WitnessWebIterator(information, pe.GetIndependentWitnesses(), pe.GetDependentWitnesses(), allCovered, minesLeft, information.GetTilesLeft(), -1);
|
||||
return one;
|
||||
}
|
||||
|
||||
int witMines = pe.GetIndependentWitnesses()[0].GetMinesToFind();
|
||||
int squares = pe.GetIndependentWitnesses()[0].GetAdjacentTiles().Count;
|
||||
|
||||
BigInteger bigIterations = Calculate(witMines, squares);
|
||||
|
||||
int iter = (int) bigIterations;
|
||||
|
||||
information.Write("The first cog has " + iter + " iterations, so parallel processing is possible");
|
||||
|
||||
WitnessWebIterator[] result = new WitnessWebIterator[iter];
|
||||
|
||||
for (int i = 0; i < iter; i++) {
|
||||
// create a iterator with a lock first got at position i
|
||||
result[i] = new WitnessWebIterator(information, pe.GetIndependentWitnesses(), pe.GetDependentWitnesses(), allCovered, minesLeft, information.GetTilesLeft(), i);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static BigInteger Calculate(int mines, int squares) {
|
||||
return binomial.Generate(mines, squares);
|
||||
}
|
||||
|
||||
public static void Initialise() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
189
info/Minesweeper2/MinesweeperSolver/SolverTile.cs
Normal file
189
info/Minesweeper2/MinesweeperSolver/SolverTile.cs
Normal file
@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using static MinesweeperSolver.SolverInfo;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
public class SolverTile {
|
||||
|
||||
public readonly int x;
|
||||
public readonly int y;
|
||||
private readonly long key; // the key identifies which SolverInfo created these tiles and locks down some methods.
|
||||
// details about me
|
||||
private bool isMine = false;
|
||||
private bool isFlagged = false;
|
||||
private bool isHidden = true;
|
||||
private int value = 0;
|
||||
private bool isDead = false; // a dead tile is one which provides no information if clicked. Should be avoided unless all tiles are dead.
|
||||
|
||||
private bool exhausted = false; // an exhausted tile is where it is cleared and all its neighbours are either cleared or a mine
|
||||
|
||||
//private bool newGrowth = true; // a tile is new growth the first time it is revealed. Once it has been analysed by the Probability Engine it stops being new growth.
|
||||
|
||||
//private bool excluded = false; // an excluded tile is one which no longer needs to be included in the propability engine since it no longer affects other tiles
|
||||
|
||||
private List<SolverTile> adjacentTiles = null;
|
||||
|
||||
public SolverTile(long key, int x, int y) {
|
||||
|
||||
this.key = key;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
||||
}
|
||||
|
||||
public bool IsMine() {
|
||||
return isMine;
|
||||
}
|
||||
|
||||
// this should only be called from the SolverInfo class
|
||||
public void SetAsMine(long key) {
|
||||
|
||||
if (key != this.key) {
|
||||
throw new Exception("Unauthorised access to the SetAsMine method");
|
||||
}
|
||||
|
||||
this.isMine = true;
|
||||
this.isHidden = false; // no longer hidden we know it is a mine
|
||||
}
|
||||
|
||||
public bool IsFlagged() {
|
||||
return this.isFlagged;
|
||||
}
|
||||
|
||||
public void SetFlagged(bool flagged) {
|
||||
isFlagged = flagged;
|
||||
}
|
||||
|
||||
public bool IsHidden() {
|
||||
return this.isHidden;
|
||||
}
|
||||
|
||||
public void SetValue(int value) {
|
||||
this.isHidden = false;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int GetValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public bool IsExhausted() {
|
||||
return this.exhausted;
|
||||
}
|
||||
|
||||
public void SetExhausted() {
|
||||
this.exhausted = true;
|
||||
}
|
||||
|
||||
// this should only be called from from the SolverInfo class
|
||||
public void SetDead(long key) {
|
||||
if (key != this.key) {
|
||||
throw new Exception("Unauthorised access to the SetDead method");
|
||||
}
|
||||
this.isDead = true;
|
||||
}
|
||||
|
||||
//public bool IsNewGrowth() {
|
||||
// return newGrowth;
|
||||
//}
|
||||
|
||||
//public void SetExamined() {
|
||||
// this.newGrowth = false;
|
||||
//}
|
||||
|
||||
public bool IsDead() {
|
||||
|
||||
return isDead;
|
||||
}
|
||||
|
||||
public List<SolverTile> GetAdjacentTiles() {
|
||||
return this.adjacentTiles;
|
||||
}
|
||||
|
||||
public void SetAdjacentTiles(List<SolverTile> adjTiles) {
|
||||
this.adjacentTiles = adjTiles;
|
||||
}
|
||||
|
||||
/*
|
||||
public void SetExcluded() {
|
||||
this.excluded = true;
|
||||
}
|
||||
|
||||
public bool IsExcluded() {
|
||||
return this.excluded;
|
||||
}
|
||||
*/
|
||||
|
||||
public bool IsEqual(SolverTile tile) {
|
||||
if (this.x == tile.x && this.y == tile.y) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the tile provided is adjacent to this tile
|
||||
public bool IsAdjacent(SolverTile tile) {
|
||||
|
||||
var dx = Math.Abs(this.x - tile.x);
|
||||
if (dx > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var dy = Math.Abs(this.y - tile.y);
|
||||
if (dy > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dx == 0 && dy == 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
// adjacent and not equal
|
||||
if (dx < 2 && dy < 2 && !(dx == 0 && dy == 0)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
//<summary> return the number of hidden tiles shared by these tiles
|
||||
public int NumberOfSharedHiddenTiles(SolverTile tile) {
|
||||
|
||||
// if the locations are too far apart they can't share any of the same squares
|
||||
if (Math.Abs(tile.x - this.x) > 2 || Math.Abs(tile.y - this.y) > 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
foreach (SolverTile tile1 in this.GetAdjacentTiles()) {
|
||||
if (!tile1.IsHidden()) {
|
||||
continue;
|
||||
}
|
||||
foreach (SolverTile tile2 in tile.GetAdjacentTiles()) {
|
||||
if (!tile2.IsHidden()) {
|
||||
continue;
|
||||
}
|
||||
if (tile1.IsEqual(tile2)) { // if they share a tile then return true
|
||||
count ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no shared tile found
|
||||
return count;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public string AsText() {
|
||||
return "(" + x + "," + y + ")";
|
||||
}
|
||||
}
|
||||
}
|
177
info/Minesweeper2/MinesweeperSolver/binomial.cs
Normal file
177
info/Minesweeper2/MinesweeperSolver/binomial.cs
Normal file
@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
|
||||
public class Binomial {
|
||||
|
||||
private static readonly double LOG10 = Math.Log(10);
|
||||
|
||||
private readonly int max;
|
||||
private readonly PrimeSieve ps;
|
||||
|
||||
private readonly BigInteger[,] binomialLookup;
|
||||
private readonly int lookupLimit;
|
||||
|
||||
public Binomial(int max, int lookup) {
|
||||
|
||||
this.max = max;
|
||||
|
||||
ps = new PrimeSieve(this.max);
|
||||
|
||||
if (lookup < 10) {
|
||||
lookup = 10;
|
||||
}
|
||||
this.lookupLimit = lookup;
|
||||
|
||||
int lookup2 = lookup / 2;
|
||||
|
||||
binomialLookup = new BigInteger[lookup + 1, lookup2 + 1];
|
||||
|
||||
for (int total = 1; total <= lookup; total++) {
|
||||
for (int choose = 0; choose <= total / 2; choose++) {
|
||||
//try {
|
||||
binomialLookup[total, choose] = Generate(choose, total);
|
||||
//Console.WriteLine("Binomial " + total + " choose " + choose + " is " + binomialLookup[total, choose]);
|
||||
//} catch (Exception e) {
|
||||
// throw e;
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public BigInteger Generate(int k, int n) {
|
||||
|
||||
if (n == 0 && k == 0) {
|
||||
return BigInteger.One;
|
||||
}
|
||||
|
||||
if (n < 1) {
|
||||
throw new Exception("Binomial: 1 <= n required, but n was " + n);
|
||||
}
|
||||
|
||||
if (0 > k || k > n) {
|
||||
throw new Exception("Binomial: 0 <= k and k <= n required, but n was " + n + " and k was " + k);
|
||||
}
|
||||
|
||||
int choose = Math.Min(k, n - k);
|
||||
|
||||
if (n <= lookupLimit && binomialLookup[n, choose] != 0) { // it is zero when it hasn't been built yet
|
||||
return binomialLookup[n, choose];
|
||||
} else if (choose < 125) {
|
||||
return Combination(choose, n);
|
||||
} else if (n <= max) {
|
||||
return CombinationLarge(choose, n);
|
||||
} else {
|
||||
return CombinationApprox(choose, n);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static BigInteger Combination(int mines, int squares) {
|
||||
|
||||
BigInteger top = BigInteger.One;
|
||||
BigInteger bot = BigInteger.One;
|
||||
|
||||
int range = Math.Min(mines, squares - mines);
|
||||
|
||||
// calculate the combination.
|
||||
for (int i = 0; i < range; i++) {
|
||||
top = top * new BigInteger(squares - i);
|
||||
bot = bot * new BigInteger(i + 1);
|
||||
}
|
||||
|
||||
BigInteger result = top / bot;
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private BigInteger CombinationLarge(int k, int n) {
|
||||
|
||||
if ((k == 0) || (k == n)) return BigInteger.One;
|
||||
|
||||
int n2 = n / 2;
|
||||
|
||||
if (k > n2) {
|
||||
k = n - k;
|
||||
}
|
||||
|
||||
int nk = n - k;
|
||||
|
||||
int rootN = (int)Math.Floor(Math.Sqrt(n));
|
||||
|
||||
BigInteger result = BigInteger.One;
|
||||
|
||||
foreach (int prime in ps.getPrimesIterable(2, n)) {
|
||||
|
||||
if (prime > nk) {
|
||||
result = result * new BigInteger(prime);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prime > n2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prime > rootN) {
|
||||
if (n % prime < k % prime) {
|
||||
result = result * new BigInteger(prime);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int r = 0, N = n, K = k, p = 1;
|
||||
|
||||
while (N > 0) {
|
||||
r = (N % prime) < (K % prime + r) ? 1 : 0;
|
||||
if (r == 1) {
|
||||
p *= prime;
|
||||
}
|
||||
N /= prime;
|
||||
K /= prime;
|
||||
}
|
||||
if (p > 1) {
|
||||
result = result * new BigInteger(p);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// use the stirling approximation for factorials to create an approximate combinatorial
|
||||
public BigInteger CombinationApprox(int k, int n) {
|
||||
|
||||
double logComb = (LogFactorialApprox(n) - LogFactorialApprox(k) - LogFactorialApprox(n - k));
|
||||
|
||||
int power = (int) Math.Floor(logComb);
|
||||
|
||||
int dp = Math.Min(6, power);
|
||||
|
||||
//double value = Math.Exp((logComb - power + dp) * LOG10);
|
||||
//BigInteger result = new BigInteger(sigDigit) * BigInteger.Pow(new BigInteger(10), power - dp);
|
||||
|
||||
// find the significant digits
|
||||
int sigDigits = (int) Math.Round(Math.Pow(10, logComb - power + dp));
|
||||
|
||||
String scientific = "" + sigDigits + "E" + (power - dp);
|
||||
|
||||
BigInteger result = BigInteger.Parse(scientific, NumberStyles.AllowExponent);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// returns an approximation for Log(n!)
|
||||
private double LogFactorialApprox(int n) {
|
||||
return n * Math.Log10(n) + 0.5d * Math.Log10(2 * Math.PI * n);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user