Skip to content

Commit

Permalink
add: optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
JerichoFletcher committed May 21, 2023
1 parent 643b8a4 commit 638065c
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 85 deletions.
11 changes: 7 additions & 4 deletions Minesolver/Game/Board.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public void PrintBoardToConsole() {
ConsoleHelper.Write(((row + 1) % 10).ToString(), ConsoleColor.DarkGray);
} else {
Cell cell = cells[row, col];
if(flags.Contains((row, col))) {
if(flags.Contains((row + 1, col + 1))) {
ConsoleHelper.Write("F", ConsoleColor.DarkRed, ConsoleColor.DarkGray);
} else if(cell.IsUncovered) {
int cellValue = cell.Value ?? int.MinValue;
Expand Down Expand Up @@ -178,7 +178,10 @@ public void Click(int clickRow, int clickCol) {
} else {
revealedNumbers.Add((clickRow, clickCol));
EachNeighbor(clickRow, clickCol, (r, c) => {
if(Peek(r, c) == null) outerCoveredSquares.Add((r, c));
if(Peek(r, c) == null) {
innerCoveredSquares.Remove((r, c));
outerCoveredSquares.Add((r, c));
}
});
}

Expand All @@ -194,14 +197,14 @@ public void Click(int clickRow, int clickCol) {
public void SetFlag(int peekRow, int peekCol, bool flag) {
if(!WithinBounds(peekRow, peekCol)) throw new ArgumentOutOfRangeException($"Position {peekRow}:{peekCol} is out of bounds for mapsize {RowCount}x{ColCount}");
(int row, int col) = (peekRow - 1, peekCol - 1);
bool _ = flag ? flags.Add((row, col)) : flags.Remove((row, col));
bool _ = flag ? flags.Add((peekRow, peekCol)) : flags.Remove((peekRow, peekCol));
RemainingMineCount = Math.Max(MineCount - flags.Count, 0);
}

public bool GetFlag(int peekRow, int peekCol) {
if(!WithinBounds(peekRow, peekCol)) throw new ArgumentOutOfRangeException($"Position {peekRow}:{peekCol} is out of bounds for mapsize {RowCount}x{ColCount}");
(int row, int col) = (peekRow - 1, peekCol - 1);
return flags.Contains((row, col));
return flags.Contains((peekRow, peekCol));
}

private void BuildCells() {
Expand Down
124 changes: 70 additions & 54 deletions Minesolver/Solver/ProbabilisticSolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,8 @@ public void Solve() {
.ToArray();

int select = rand.Next(minProbabilitySquares.Length);
Console.WriteLine($"Selecting {select} from {minProbabilitySquares.Length}");
(int row, int col) = minProbabilitySquares[select];
//Console.WriteLine($"Result probability table:\n{string.Join('\n', probabilityBoard.Select(pair => $"{pair.Key}: {pair.Value}"))}");
//Console.WriteLine($"Candidate squares:\n{string.Join('\n', minProbabilitySquares.Select(key => $"{key}: {probabilityBoard[key]}"))}");
//Console.WriteLine($"Trying to click ({row}, {col}) [{probabilityBoard.First().Value} of index {select} in length {minProbabilitySquares.Length}]");
Board.Click(row, col);

Console.Clear();
Expand Down Expand Up @@ -99,43 +97,41 @@ public void SolveImmediate(int attemptCount) {

ConsoleHelper.Write("Press ENTER to start...", ConsoleColor.Green);
Console.ReadLine();
ConsoleHelper.WriteLine($"Starting {attemptCount} worker threads...", ConsoleColor.Cyan);
ConsoleHelper.WriteLine($"Queueing {attemptCount} worker threads...", ConsoleColor.Cyan);

HashSet<Thread> threads = new HashSet<Thread>();
Random rand = new Random();
for(int i = 1; i <= attemptCount; i++) {
ThreadPool.QueueUserWorkItem(attemptNumber => {
Random rand = new Random();
int threadNumber = (int)(attemptNumber ?? rand.Next());
Board tBoard = new Board(Board.MineCount, Board.RowCount, Board.ColCount);
try {
while(!tBoard.Finished) {
probabilityBoard = ComputeProbabilities(tBoard);
while(!tBoard.Finished) {
probabilityBoard = ComputeProbabilities(tBoard);
// Flag definitely mined squares
IEnumerable<(int, int)> definiteMinedSquares = probabilityBoard
.Where(pair => pair.Value == 1f)
.Select(pair => pair.Key);
foreach((int minedRow, int minedCol) in definiteMinedSquares) {
tBoard.SetFlag(minedRow, minedCol, true);
}
// Flag definitely mined squares
IEnumerable<(int, int)> definiteMinedSquares = probabilityBoard
.Where(pair => pair.Value == 1f)
.Select(pair => pair.Key);
foreach((int minedRow, int minedCol) in definiteMinedSquares) {
tBoard.SetFlag(minedRow, minedCol, true);
}
(int row, int col)[] minProbabilitySquares = probabilityBoard
.Where(pair => probabilityBoard.All(otherPair => pair.Value <= otherPair.Value))
.Select(pair => pair.Key)
.ToArray();
(int row, int col)[] minProbabilitySquares = probabilityBoard
.Where(pair => probabilityBoard.All(otherPair => pair.Value <= otherPair.Value))
.Select(pair => pair.Key)
.ToArray();
try {
int select = rand.Next(minProbabilitySquares.Length);
(int row, int col) = minProbabilitySquares[select];
//Console.WriteLine($"Result probability table:\n{string.Join('\n', probabilityBoard.Select(pair => $"{pair.Key}: {pair.Value}"))}");
//Console.WriteLine($"Candidate squares:\n{string.Join('\n', minProbabilitySquares.Select(key => $"{key}: {probabilityBoard[key]}"))}");
//Console.WriteLine($"Trying to click ({row}, {col}) [{probabilityBoard.First().Value} of index {select} in length {minProbabilitySquares.Length}]");
tBoard.Click(row, col);
} catch(Exception e) {
ReportResult(threadNumber, null, $"Array length: {minProbabilitySquares.Length}\nFull probability board:\n{string.Join('\n', probabilityBoard)}\n{e}");
return;
}
} catch(Exception e) {
ReportResult(threadNumber, null, e.Message);
}
ReportResult(threadNumber, tBoard.State == BoardState.Win);
}, i);
}
Expand Down Expand Up @@ -180,46 +176,66 @@ private void ReportResult(int threadNumber, bool? win, string message = "") {

// Search for all covered squares
foreach((int r, int c) in board.CoveredSquares) {
if(board.Peek(r, c) == null) {
coveredSquaresMined.Add((r, c), 0);
}
coveredSquaresMined.Add((r, c), 0);
}

//Console.WriteLine("Board state:");
//Console.WriteLine($" {board.CoveredSquares.Count()} covered squares: {board.InnerCoveredSquares.Count()} inner + {board.OuterCoveredSquares.Count()} outer");
//Console.WriteLine($" {board.RevealedNumbers.Count()} revealed number squares");
// Iterate through all combinations of mines
int combCount = 0;
//Console.WriteLine($"Checking {Board.CoveredSquares.Combinations(Board.MineCount).Count()} of possible {Board.CoveredSquares.Combinations().Count()} configurations:");
foreach((int mineRow, int mineCol)[] mineComb in board.CoveredSquares.Combinations(board.MineCount)) {
bool valid = true;

// Verify count of mines
foreach((int numRow, int numCol) in board.RevealedNumbers) {
int mineCount = 0;
board.EachNeighbor(numRow, numCol, (checkRow, checkCol) => {
if(mineComb.Contains((checkRow, checkCol))) mineCount++;
});
if(mineCount != board.Peek(numRow, numCol)) {
valid = false;
break;
int minOuterMineCount = Math.Max(0, board.RemainingMineCount - (board.CoveredSquares.Count() - board.OuterCoveredSquares.Count()));
int maxOuterMineCount = Math.Min(board.OuterCoveredSquares.Count(), board.RemainingMineCount);
for(int outerMineCount = minOuterMineCount; outerMineCount <= maxOuterMineCount; outerMineCount++) {
int innerMineCount = board.RemainingMineCount - outerMineCount;
int innerMineCombCount = board.InnerCoveredSquares.Count() != 0 ? Combinatorics.Combination(board.InnerCoveredSquares.Count(), innerMineCount) : 1;
int innerMineCombOccurence = board.InnerCoveredSquares.Count() != 0 ? Combinatorics.Combination(board.InnerCoveredSquares.Count() - 1, innerMineCount - 1) : 0;
//Console.WriteLine($"Checking for outer mine count = {outerMineCount}, {innerMineCount} inner mines combo {innerMineCombOccurence} occurence each in {innerMineCombCount} combinations");

IEnumerable<(int mineRow, int mineCol)[]> outerMineConfigurations = board.OuterCoveredSquares.Combinations(outerMineCount);
int validConfigs = 0;
foreach(var outerMineComb in outerMineConfigurations.Select(array => new List<(int, int)>(array))) {
outerMineComb.AddRange(board.FlaggedSquares);
bool valid = true;

//Console.WriteLine($"Considering {string.Join(',', outerMineComb)}...");
// Verify count of mines
foreach((int numRow, int numCol) in board.RevealedNumbers) {
int mineCount = 0;
board.EachNeighbor(numRow, numCol, (checkRow, checkCol) => {
if(outerMineComb.Contains((checkRow, checkCol))) mineCount++;
});
if(mineCount != board.Peek(numRow, numCol)) {
valid = false;
break;
}
}
if(!valid) continue;
validConfigs++;

//Console.WriteLine($"Adding config {string.Join(',', outerMineComb)}");
// Add combination to the map
combCount += innerMineCombCount;
foreach((int, int) key in outerMineComb) {
coveredSquaresMined[key] += innerMineCombCount;
}
}
if(!valid) continue;

// Add combination to the map
//Console.WriteLine($"Adding configuration {string.Join(',', mineComb)}");
combCount++;
foreach((int, int) key in mineComb) {
//Console.Write($" Adding count of {key} from {coveredSquaresMined[key]} to ");
coveredSquaresMined[key]++;
//Console.WriteLine(coveredSquaresMined[key]);
foreach((int, int) key in board.InnerCoveredSquares) {
coveredSquaresMined[key] += validConfigs * innerMineCombOccurence;
}
//Console.WriteLine($"");
}

// Calculate probability value
foreach(KeyValuePair<(int, int), int> pair in coveredSquaresMined) {
probabilityBoard.Add(pair.Key, (float)pair.Value / combCount);
// Calculate covered squares probability value
foreach(KeyValuePair<(int r, int c), int> pair in coveredSquaresMined) {
if(board.GetFlag(pair.Key.r, pair.Key.c)) {
probabilityBoard.Add(pair.Key, 1f);
} else {
probabilityBoard.Add(pair.Key, combCount == 0 ? 0 : ((float)pair.Value / combCount));
}
}

//Console.WriteLine($"Calculated:\n{string.Join('\n', probabilityBoard)}");
//Console.ReadLine();
return probabilityBoard;
}
}
Expand Down
51 changes: 24 additions & 27 deletions Minesolver/Utility/Combinatorics.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,5 @@
namespace Minesolver.Utility {
internal static class Combinatorics {
//public static IEnumerable<T[]> Combinations<T>(this IEnumerable<T> source) {
// if(source == null) throw new ArgumentNullException(nameof(source));

// T[] sourceBuffer = source.ToArray();
// return Enumerable
// .Range(0, 1 << sourceBuffer.Length)
// .Select(mask =>
// sourceBuffer.Where((_, i) => (mask & (1 << i)) != 0)
// .ToArray()
// );
//}

//public static IEnumerable<T[]> Combinations<T>(this IEnumerable<T> source, int length) {
// if(source == null) throw new ArgumentNullException(nameof(source));
// if(length < 0 || length > source.Count()) throw new ArgumentOutOfRangeException(nameof(source));

// return source.Combinations()
// .Where(comb => comb.Length == length);
//}


internal static class Combinatorics {
private static IEnumerable<int[]> Combinations(int m, int n) {
int[] result = new int[m];
Stack<int> stack = new Stack<int>(m);
Expand All @@ -45,15 +24,33 @@ private static IEnumerable<int[]> Combinations(int m, int n) {
public static IEnumerable<T[]> Combinations<T>(this IEnumerable<T> source, int m) {
T[] array = source.ToArray();
if(array.Length < m) throw new ArgumentException("Array length can't be less than number of selected elements");
if(m < 1) throw new ArgumentException("Number of selected elements can't be less than 1");
if(m < 0) throw new ArgumentOutOfRangeException(nameof(m));

T[] result = new T[m];
foreach(int[] j in Combinations(m, array.Length)) {
for(int i = 0; i < m; i++) {
result[i] = array[j[i]];
}
if(m == 0) {
yield return result;
} else {
foreach(int[] j in Combinations(m, array.Length)) {
for(int i = 0; i < m; i++) {
result[i] = array[j[i]];
}
yield return result;
}
}
}

public static int Combination(int n, int k) {
if(n < 0) throw new ArgumentOutOfRangeException(nameof(n));
if(k < 0 || k > n) return 0;

int c = 1;
for(int num = n; num > Math.Max(k, n - k); num--) {
c *= num;
}
for(int den = Math.Min(k, n - k); den > 1; den--) {
c /= den;
}
return c;
}
}
}

0 comments on commit 638065c

Please sign in to comment.