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); }
/** * 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); }
/** * 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); }
/** * 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; }
/** * 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); }