Compare commits

...
Author SHA1 Message Date
adminandClaude 3e91f1792c Consolidate chance node output in MCTS sequence display
When displaying the best sequence, chance nodes (probabilistic actions like
START_FIRE) now show outcome probabilities inline instead of appearing as
duplicate entries. Format: "action [N outcomes: prob%->score ...]"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 21:42:41 -08:00
@@ -687,6 +687,24 @@ auto AbstractMCTSAI::LogSearchResults(
result.nodesEvaluated,
rootNode->visitCount);
// Helper to check if a node is a "chance parent" - all children have the same action
// This indicates the children represent different probabilistic outcomes of the same action
auto isChanceParent = [](const MCTSNode* node) -> bool {
if (!node || node->children.size() < 2) return false;
const std::string* firstDesc = nullptr;
for (const auto& child : node->children) {
if (child->isRedundant) continue;
if (!child->action) return false;
const auto& desc = child->action->getDescription();
if (!firstDesc) {
firstDesc = &desc;
} else if (*firstDesc != desc) {
return false; // Different actions = not a chance node
}
}
return true;
};
// Log best sequence from chosen action (following most-visited path)
std::vector<const MCTSNode*> bestSequence;
bestSequence.reserve(10);
@@ -712,18 +730,50 @@ auto AbstractMCTSAI::LogSearchResults(
if (!bestSequence.empty()) {
printf("MCTS: Best sequence from chosen action (final: %.2f):\n", sequenceScore);
int displayedStep = 0;
for (size_t i = 0; i < bestSequence.size(); ++i) {
const auto* node = bestSequence[i];
printf(" %zu.", i + 1);
// Check if this node's parent was a chance node - if so, skip displaying this
// outcome node separately since we already consolidated it with the action
if (i > 0 && node->parent && isChanceParent(node->parent)) {
// This is an outcome of a chance node - already displayed, skip
continue;
}
displayedStep++;
printf(" %d.", displayedStep);
if (node->action) { printf(" %s", node->action->getDescription().c_str()); }
// Check if this node is a chance parent (has multiple outcome children)
if (isChanceParent(node)) {
// Collect outcome information
std::vector<std::pair<double, double>> outcomes; // (weight, lookahead)
double totalWeight = 0;
for (const auto& child : node->children) {
if (child->isRedundant) continue;
outcomes.emplace_back(child->actionWeight, child->lookaheadScore);
totalWeight += child->actionWeight;
}
printf(" [%zu outcomes:", outcomes.size());
for (size_t j = 0; j < std::min(outcomes.size(), size_t(3)); ++j) {
const double pct =
totalWeight > 0 ? (outcomes[j].first / totalWeight * 100) : 0;
printf(" %.0f%%->%.1f", pct, outcomes[j].second);
}
if (outcomes.size() > 3) printf(" ...");
printf("]");
}
printf(" (visits:%d, immediate:%.2f, lookahead:%.2f)\n",
node->visitCount,
node->immediateScore,
node->lookaheadScore);
// For non-root nodes in the sequence, show what the top alternatives were
// This helps diagnose if opponent moves are being properly explored
if (i > 0 && node->parent && !node->parent->children.empty()) {
// Skip showing alternatives for chance outcome nodes (parent was chance)
if (i > 0 && node->parent && !node->parent->children.empty() &&
!isChanceParent(node->parent)) {
// Collect all siblings (including this node) and sort by visit count
std::vector<const MCTSNode*> siblings;
siblings.reserve(node->parent->children.size());