diff --git a/src/main/cpp/net/eagle0/shardok/ai/AICommandEvaluator.cpp b/src/main/cpp/net/eagle0/shardok/ai/AICommandEvaluator.cpp index 01b524e84d..6d8e08983c 100644 --- a/src/main/cpp/net/eagle0/shardok/ai/AICommandEvaluator.cpp +++ b/src/main/cpp/net/eagle0/shardok/ai/AICommandEvaluator.cpp @@ -155,10 +155,12 @@ static auto LeafMoveFollowUpScoreBonus( AICommandEvaluator::AICommandEvaluator( const AIScoreCalculator& scorer, const APDCache& apdCache, - BattalionTypeGetter battalionTypeGetter) + BattalionTypeGetter battalionTypeGetter, + const bool scoreMoveFollowUps) : scorer_(scorer), apdCache_(apdCache), - battalionTypeGetter_(std::move(battalionTypeGetter)) {} + battalionTypeGetter_(std::move(battalionTypeGetter)), + scoreMoveFollowUps_(scoreMoveFollowUps) {} auto AICommandEvaluator::PerformLookahead( const PlayerId pid, @@ -293,8 +295,9 @@ auto AICommandEvaluator::EvaluateWithRandomness( const auto guessedCommands = guessedEngine.GetAvailableCommandsForAIPlayer(pid); const ScoreValue leafMoveFollowUpBonus = - remainingLookahead <= 0 ? LeafMoveFollowUpScoreBonus(guessedEngine, pid, commandIndex) - : 0.0; + scoreMoveFollowUps_ && remainingLookahead <= 0 + ? LeafMoveFollowUpScoreBonus(guessedEngine, pid, commandIndex) + : 0.0; auto innerEngine = std::make_shared(guessedEngine, false); try { innerEngine->PostCommand(pid, commandIndex, randomGenerator); diff --git a/src/main/cpp/net/eagle0/shardok/ai/AICommandEvaluator.hpp b/src/main/cpp/net/eagle0/shardok/ai/AICommandEvaluator.hpp index 404279dd87..59b34d2f44 100644 --- a/src/main/cpp/net/eagle0/shardok/ai/AICommandEvaluator.hpp +++ b/src/main/cpp/net/eagle0/shardok/ai/AICommandEvaluator.hpp @@ -35,7 +35,8 @@ public: AICommandEvaluator( const AIScoreCalculator& scorer, const APDCache& apdCache, - BattalionTypeGetter battalionTypeGetter); // Pass by value + BattalionTypeGetter battalionTypeGetter, + bool scoreMoveFollowUps = true); // Pass by value /// Evaluates the score for a particular command index with lookahead. struct EvaluationResult { @@ -79,6 +80,7 @@ private: const AIScoreCalculator& scorer_; const APDCache& apdCache_; BattalionTypeGetter battalionTypeGetter_; + bool scoreMoveFollowUps_; struct ImmediateAndLookaheadScore { ScoreValue immediateScore{}; diff --git a/src/main/cpp/net/eagle0/shardok/ai/IterativeDeepeningAI.cpp b/src/main/cpp/net/eagle0/shardok/ai/IterativeDeepeningAI.cpp index d65691d1e7..0a9d83dd0d 100644 --- a/src/main/cpp/net/eagle0/shardok/ai/IterativeDeepeningAI.cpp +++ b/src/main/cpp/net/eagle0/shardok/ai/IterativeDeepeningAI.cpp @@ -44,14 +44,16 @@ IterativeDeepeningAI::IterativeDeepeningAI( const CoordsSet& castleCoords, const AIScoreCalculator& scorer, const APDCache& apdCache, - BattalionTypeGetter battalionTypeGetter) + BattalionTypeGetter battalionTypeGetter, + const bool scoreMoveFollowUps) : playerId(playerId), isDefender(isDefender), strategy(std::move(strategy)), castleCoords(castleCoords), scorer(scorer), apdCache(apdCache), - battalionTypeGetter(std::move(battalionTypeGetter)) {} + battalionTypeGetter(std::move(battalionTypeGetter)), + scoreMoveFollowUps(scoreMoveFollowUps) {} auto IterativeDeepeningAI::IterativeSearch( const GameSettingsSPtr& settings, @@ -333,7 +335,7 @@ auto IterativeDeepeningAI::SearchCommandAtDepthWithEngine( const auto deadline = startTime + timeBudget.remainingBudget; // Create command evaluator for lookahead search - AICommandEvaluator evaluator(scorer, apdCache, battalionTypeGetter); + AICommandEvaluator evaluator(scorer, apdCache, battalionTypeGetter, scoreMoveFollowUps); // Get the future from EvaluateCommand - don't wait yet // Note: EvaluateCommand expects remainingLookahead, not desiredDepth diff --git a/src/main/cpp/net/eagle0/shardok/ai/IterativeDeepeningAI.hpp b/src/main/cpp/net/eagle0/shardok/ai/IterativeDeepeningAI.hpp index 2a3e42cd96..0d861736bd 100644 --- a/src/main/cpp/net/eagle0/shardok/ai/IterativeDeepeningAI.hpp +++ b/src/main/cpp/net/eagle0/shardok/ai/IterativeDeepeningAI.hpp @@ -64,7 +64,8 @@ public: const CoordsSet& castleCoords, const AIScoreCalculator& scorer, const APDCache& apdCache, - BattalionTypeGetter battalionTypeGetter); // Pass by value + BattalionTypeGetter battalionTypeGetter, + bool scoreMoveFollowUps); // Pass by value [[nodiscard]] SearchResult IterativeSearch( const GameSettingsSPtr& settings, @@ -80,6 +81,7 @@ private: const AIScoreCalculator& scorer; const APDCache& apdCache; BattalionTypeGetter battalionTypeGetter; + bool scoreMoveFollowUps; // Reusable vectors to reduce memory allocations mutable std::vector> scoresByDepth; diff --git a/src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.cpp b/src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.cpp index 82cc49b6a5..37c457b078 100644 --- a/src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.cpp +++ b/src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.cpp @@ -65,12 +65,14 @@ ShardokAIClient::ShardokAIClient( const SettingsGetter &settings, const AIAlgorithmType aiAlgorithmType, const ScoringCalculatorType scoringCalculatorType, - const mcts::MCTSConfig &mctsConfig) + const mcts::MCTSConfig &mctsConfig, + const bool scoreMoveFollowUps) : playerId(playerId), isDefender(isDefender), isAllAiBattle(isAllAiBattle), aiAlgorithmType(aiAlgorithmType), scoringCalculatorType(scoringCalculatorType), + scoreMoveFollowUps(scoreMoveFollowUps), alCache(std::make_unique(hexMap, settings)), waterCrossingCommandChooser(playerId, apdCache), mctsConfig(mctsConfig) { @@ -365,7 +367,8 @@ auto ShardokAIClient::StandardChooseCommandIndex( castleCoords, *scorer, apdCache, - battalionTypeGetter); + battalionTypeGetter, + scoreMoveFollowUps); search_result = ai.IterativeSearch(settings, guessedState, realAvailableCommands, timeBudget); } diff --git a/src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.hpp b/src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.hpp index cad72e4572..e312b35de3 100644 --- a/src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.hpp +++ b/src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.hpp @@ -44,6 +44,7 @@ private: const bool isAllAiBattle; // Whether this battle has only AI players (for faster time budgets) const AIAlgorithmType aiAlgorithmType; const ScoringCalculatorType scoringCalculatorType; + const bool scoreMoveFollowUps; APDCache apdCache = std::make_shared(); ALCache alCache; @@ -75,7 +76,8 @@ public: const SettingsGetter& settings, AIAlgorithmType aiAlgorithmType, ScoringCalculatorType scoringCalculatorType, - const mcts::MCTSConfig& mctsConfig); + const mcts::MCTSConfig& mctsConfig, + bool scoreMoveFollowUps = true); ~ShardokAIClient() = default; [[nodiscard]] auto GetPlayerId() const -> PlayerId { return playerId; } diff --git a/src/main/cpp/net/eagle0/shardok/ai_battle_simulator/AiBattleSimulator.cpp b/src/main/cpp/net/eagle0/shardok/ai_battle_simulator/AiBattleSimulator.cpp index 4944d007e6..ea3e502102 100644 --- a/src/main/cpp/net/eagle0/shardok/ai_battle_simulator/AiBattleSimulator.cpp +++ b/src/main/cpp/net/eagle0/shardok/ai_battle_simulator/AiBattleSimulator.cpp @@ -571,6 +571,8 @@ std::unique_ptr AiBattleSimulator::CreateAIClient( const ScoringCalculatorType scoringType = ConvertScoringCalculatorType(playerConfig.scoring_calculator()); const auto mctsConfig = BuildMCTSConfig(playerConfig, config_.random_seed(), playerId); + const bool scoreMoveFollowUps = + !playerConfig.has_score_move_follow_ups() || playerConfig.score_move_follow_ups(); return ai_testing_common::AIClientFactory::Create( playerId, @@ -580,7 +582,8 @@ std::unique_ptr AiBattleSimulator::CreateAIClient( algorithmType, scoringType, true, - mctsConfig); + mctsConfig, + scoreMoveFollowUps); } BattleResult AiBattleSimulator::RunSetupPhase( diff --git a/src/main/cpp/net/eagle0/shardok/ai_testing_common/AIClientFactory.cpp b/src/main/cpp/net/eagle0/shardok/ai_testing_common/AIClientFactory.cpp index c72152bb41..65d234feca 100644 --- a/src/main/cpp/net/eagle0/shardok/ai_testing_common/AIClientFactory.cpp +++ b/src/main/cpp/net/eagle0/shardok/ai_testing_common/AIClientFactory.cpp @@ -17,7 +17,8 @@ auto AIClientFactory::Create( AIAlgorithmType algorithmType, ScoringCalculatorType scoringType, bool isAllAiBattle, - mcts::MCTSConfig mctsConfig) -> std::unique_ptr { + mcts::MCTSConfig mctsConfig, + const bool scoreMoveFollowUps) -> std::unique_ptr { return std::make_unique( playerId, isDefender, @@ -26,7 +27,8 @@ auto AIClientFactory::Create( settings, algorithmType, scoringType, - mctsConfig); + mctsConfig, + scoreMoveFollowUps); } } // namespace shardok::ai_testing_common diff --git a/src/main/cpp/net/eagle0/shardok/ai_testing_common/AIClientFactory.hpp b/src/main/cpp/net/eagle0/shardok/ai_testing_common/AIClientFactory.hpp index 694206eeb9..50d75aa363 100644 --- a/src/main/cpp/net/eagle0/shardok/ai_testing_common/AIClientFactory.hpp +++ b/src/main/cpp/net/eagle0/shardok/ai_testing_common/AIClientFactory.hpp @@ -62,7 +62,8 @@ public: AIAlgorithmType algorithmType = AIAlgorithmType::ITERATIVE_DEEPENING, ScoringCalculatorType scoringType = ScoringCalculatorType::STANDARD, bool isAllAiBattle = false, - mcts::MCTSConfig mctsConfig = mcts::MCTSConfig{}) -> std::unique_ptr; + mcts::MCTSConfig mctsConfig = mcts::MCTSConfig{}, + bool scoreMoveFollowUps = true) -> std::unique_ptr; }; } // namespace ai_testing_common diff --git a/src/main/protobuf/net/eagle0/shardok/ai_battle_simulator/ai_battle_config.proto b/src/main/protobuf/net/eagle0/shardok/ai_battle_simulator/ai_battle_config.proto index 0bd61cab15..4fd1279e04 100644 --- a/src/main/protobuf/net/eagle0/shardok/ai_battle_simulator/ai_battle_config.proto +++ b/src/main/protobuf/net/eagle0/shardok/ai_battle_simulator/ai_battle_config.proto @@ -126,6 +126,10 @@ message PlayerConfig { // MCTS settings, used only when ai_algorithm is MCTS. MctsConfig mcts_config = 4; + + // Whether iterative deepening should add leaf-score bonuses for move follow-up actions. + // Defaults to true, matching production behavior. + optional bool score_move_follow_ups = 5; } // Complete battle configuration