package com.hearthsim.util.factory;
import com.hearthsim.card.Deck;
import com.hearthsim.exception.HSException;
import com.hearthsim.player.playercontroller.BoardScorer;
import com.hearthsim.util.tree.HearthTreeNode;
import com.hearthsim.util.tree.RandomEffectNode;
import com.hearthsim.util.tree.StopNode;
import java.util.ArrayList;
import java.util.TreeSet;
public class BreadthBoardStateFactory extends BoardStateFactoryBase {
public BreadthBoardStateFactory(Deck deckPlayer0, Deck deckPlayer1) {
super(deckPlayer0, deckPlayer1);
}
// Currently only used to test lethal combinations. AI should use depthFirstSearch instead.
public void addChildLayers(HearthTreeNode boardStateNode, int maxDepth) throws HSException {
// Saving seen states lets us prune duplicate states. This saves us a ton of time when the trees get deep.
TreeSet<Integer> states = new TreeSet<>();
// We need to process things by batch so we can track how deep we've gone.
ArrayList<HearthTreeNode> currentDepth = new ArrayList<>();
ArrayList<HearthTreeNode> nextDepth = new ArrayList<>();
currentDepth.add(boardStateNode);
// Debugging variables for tracking loop complications
boolean dupeSkipOn = true;
int depthReached = 0;
int processedCount = 0;
int childCount = 0;
int stateCompareCount = 0;
int dupeSkip = 0;
HearthTreeNode current;
while(maxDepth > 0) {
while(!currentDepth.isEmpty()) {
processedCount++;
current = currentDepth.remove(0);
// The game ended; nothing left to do.
if (current.data_.isLethalState()) {
continue;
}
// Try to use existing children if possible. This can happen after an auto-populating node (e.g., Battlecries)
ArrayList<HearthTreeNode> children;
if (current.isLeaf()) {
children = this.createChildren(current);
} else {
children = new ArrayList<>(current.getChildren());
}
current.clearChildren(); // TODO suboptimal but we need to remove existing children so we can check for duplicates
for (HearthTreeNode child : children) {
childCount++;
if (!(child instanceof StopNode)) {
if (dupeSkipOn) {
if (states.size() > 0) {
stateCompareCount += Math.log(states.size()); // .contains uses quick search
// Using .hashCode lets us use TreeSet and .contains to look for dupes
if (states.contains(child.data_.hashCode())) {
dupeSkip++;
continue;
}
}
states.add(child.data_.hashCode());
}
nextDepth.add(child); // Add to next depth so the inner loop will eventually process it
} else {
this.addChildLayers(child, maxDepth - 1);
}
current.addChild(child);
}
}
if (nextDepth.isEmpty()) {
break;
}
depthReached++;
maxDepth--;
ArrayList<HearthTreeNode> old = currentDepth;
currentDepth = nextDepth;
nextDepth = old; // We processed all of the previous depth so we don't need to .clear().
}
log.debug("createChildLayers summary depthReached=" + depthReached + " processedCount=" + processedCount
+ " childCount=" + childCount + " compareCount=" + stateCompareCount + " dupeSkip=" + dupeSkip);
}
private void processScoresForTree(HearthTreeNode root, BoardScorer ai, boolean scoringPruning) {
double score = ai.boardScore(root.data_);
root.setScore(score);
if (root.isLeaf()) {
root.setBestChildScore(score);
} else {
HearthTreeNode best = null;
for (HearthTreeNode child : root.getChildren()) {
this.processScoresForTree(child, ai, scoringPruning);
if (best == null || best.getBestChildScore() < child.getBestChildScore()) {
best = child;
}
}
// We need to process this after the children have populated their scores
if (root instanceof RandomEffectNode) {
double boardScore = ((RandomEffectNode)root).weightedAverageBestChildScore();
root.setScore(boardScore);
root.setBestChildScore(boardScore);
} else {
if (best != null) {
root.setBestChildScore(best.getBestChildScore());
if (scoringPruning) {
root.clearChildren();
root.addChild(best);
}
}
}
}
}
@Override
public HearthTreeNode doMoves(HearthTreeNode root, BoardScorer ai) throws HSException {
this.breadthFirstSearch(root, ai, 100, false);
return root;
}
protected void breadthFirstSearch(HearthTreeNode root, BoardScorer ai, int maxDepth, boolean prune) throws HSException {
this.addChildLayers(root, maxDepth);
this.processScoresForTree(root, ai, prune);
}
}