Compare commits

..
Author SHA1 Message Date
admin 0df9700fdd Isolate Shardok ARM64 registry auth 2026-06-22 06:48:29 -07:00
632 changed files with 4657 additions and 20637 deletions
+15 -1
View File
@@ -255,7 +255,7 @@ jobs:
echo "=== All images pushed successfully ==="
deploy:
runs-on: ubuntu-latest
runs-on: [self-hosted, bazel]
needs: [build-all]
if: needs.build-all.outputs.skip_build != 'true' && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true'))
environment: production
@@ -334,11 +334,25 @@ jobs:
ssh-keyscan -H "$DO_DROPLET_IP" >> ~/.ssh/known_hosts 2>/dev/null || true
- name: Download warmup binary
id: download-warmup
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: warmup-binary
path: scripts/bin/
- name: Build warmup binary fallback
if: steps.download-warmup.outcome != 'success'
run: |
echo "::warning::warmup-binary artifact was unavailable; rebuilding warmup binary before deploy"
./ci/github_actions/ensure_bazel_installed.sh
bazel build \
--platforms=//:linux_x86_64 \
--extra_toolchains=@llvm_toolchain_linux//:cc-toolchain-x86_64-linux \
//ci:warmup_tar
mkdir -p scripts/bin
tar -xf bazel-bin/ci/warmup_tar.tar -C scripts/bin --strip-components=1
- name: Copy config files to droplet
run: |
# Create directory structure on remote
+1 -3
View File
@@ -177,12 +177,10 @@ jobs:
env:
DO_CDN_PAT: ${{ secrets.DO_CDN_PAT }}
run: |
UNITY_VERSION=$(grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //')
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
curl -s -X DELETE "https://api.digitalocean.com/v2/cdn/endpoints/8c98df29-6f0c-4704-8e82-ca40a2b81aa8/cache" \
-H "Authorization: Bearer $DO_CDN_PAT" \
-H "Content-Type: application/json" \
-d "{\"files\": [\"addressables/iOS/$UNITY_MAJOR_MINOR/*\"]}" \
-d '{"files": ["addressables/iOS/*"]}' \
--fail || echo "Warning: CDN purge failed (non-fatal)"
- name: Archive Build Log
+1 -5
View File
@@ -267,7 +267,6 @@ jobs:
runs-on: [self-hosted, macOS, notarize]
outputs:
deployed_version: ${{ steps.deploy-mac.outputs.deployed_version }}
deployed_unity_major_minor: ${{ steps.deploy-mac.outputs.deployed_unity_major_minor }}
concurrency:
group: mac-deploy-${{ github.ref }}
cancel-in-progress: false
@@ -496,9 +495,6 @@ jobs:
rm "$SPARKLE_PRIVATE_KEY_PATH"
echo "deployed_version=$VERSION" >> $GITHUB_OUTPUT
UNITY_VERSION=$(grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //')
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
echo "deployed_unity_major_minor=$UNITY_MAJOR_MINOR" >> $GITHUB_OUTPUT
# Artifact cleanup is handled by the cleanup job on ubuntu-latest
- name: Cleanup build directory
@@ -519,7 +515,7 @@ jobs:
curl -s -X DELETE "https://api.digitalocean.com/v2/cdn/endpoints/8c98df29-6f0c-4704-8e82-ca40a2b81aa8/cache" \
-H "Authorization: Bearer $DO_CDN_PAT" \
-H "Content-Type: application/json" \
-d '{"files": ["mac/*", "addressables/StandaloneOSX/${{ needs.deploy-mac.outputs.deployed_unity_major_minor }}/*"]}' \
-d '{"files": ["mac/*", "addressables/StandaloneOSX/*"]}' \
--fail || echo "Warning: CDN purge failed (non-fatal)"
curl -X POST "https://admin.eagle0.net/notify-update?platform=mac&version=${{ needs.deploy-mac.outputs.deployed_version }}&required=false" \
+18 -6
View File
@@ -36,6 +36,8 @@ permissions:
jobs:
build-shardok-arm64:
runs-on: [self-hosted, bazel]
env:
DOCKER_CONFIG: ${{ runner.temp }}/shardok-arm64-docker-config
outputs:
image_tag: ${{ steps.push-shardok.outputs.image_tag }}
steps:
@@ -148,8 +150,14 @@ jobs:
env:
DO_REGISTRY_TOKEN: ${{ secrets.DO_REGISTRY_TOKEN }}
run: |
if [ -z "${DO_REGISTRY_TOKEN}" ]; then
echo "ERROR: DO_REGISTRY_TOKEN is not set"
exit 1
fi
mkdir -p "${DOCKER_CONFIG}"
AUTH=$(echo -n "${DO_REGISTRY_TOKEN}:${DO_REGISTRY_TOKEN}" | base64)
echo "{\"auths\":{\"registry.digitalocean.com\":{\"auth\":\"${AUTH}\"}}}" > ~/.docker/config.json
echo "{\"auths\":{\"registry.digitalocean.com\":{\"auth\":\"${AUTH}\"}}}" > "${DOCKER_CONFIG}/config.json"
- name: Push Shardok ARM64 image to DigitalOcean
id: push-shardok
@@ -196,14 +204,13 @@ jobs:
echo "Also tagged as: registry.digitalocean.com/eagle0/shardok-server:arm64-latest"
deploy-hetzner:
# Keep this on a self-hosted Mac runner for IPv6 reachability to Hetzner,
# but do not require a Bazel runner slot for artifact transport/deploy work.
runs-on: [self-hosted, macOS, ARM64]
runs-on: [self-hosted, bazel]
needs: [build-shardok-arm64]
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
environment: production
env:
SHARDOK_IMAGE: ${{ needs.build-shardok-arm64.outputs.image_tag }}
DOCKER_CONFIG: ${{ runner.temp }}/shardok-arm64-deploy-docker-config
steps:
- name: Setup SSH key
run: |
@@ -219,6 +226,11 @@ jobs:
run: |
set -ex
if [ -z "${DO_REGISTRY_TOKEN}" ]; then
echo "ERROR: DO_REGISTRY_TOKEN is not set"
exit 1
fi
# Hetzner is IPv6-only, while Docker can be redirected to IPv4-only
# DigitalOcean registry/blob endpoints. Pull on the runner and copy
# the Docker-loadable tarball over SSH instead.
@@ -235,8 +247,8 @@ jobs:
fi
AUTH=$(echo -n "${DO_REGISTRY_TOKEN}:${DO_REGISTRY_TOKEN}" | base64)
mkdir -p ~/.docker
echo "{\"auths\":{\"registry.digitalocean.com\":{\"auth\":\"${AUTH}\"}}}" > ~/.docker/config.json
mkdir -p "${DOCKER_CONFIG}"
echo "{\"auths\":{\"registry.digitalocean.com\":{\"auth\":\"${AUTH}\"}}}" > "${DOCKER_CONFIG}/config.json"
IMAGE_TAR="${RUNNER_TEMP}/shardok-arm64-image.tar"
"$CRANE" pull "${SHARDOK_IMAGE}" "$IMAGE_TAR"
-19
View File
@@ -29,16 +29,6 @@ If you catch yourself about to run `git push origin main` or `git push origin <b
If you catch yourself about to run `gh pr merge`, STOP. Only the user merges PRs.
## Commit History During Review
Prefer new follow-up commits over amending existing commits once a PR is open and the user is actively reviewing or
testing it. This preserves working interim states so the user can inspect, compare, or return to a known-good point
while improvements continue.
Use `git commit --amend` and force-push only when the user explicitly asks for history cleanup, when fixing metadata
before review has started, or when repairing a local commit that has not been pushed. If you believe squashing is the
right choice despite an open review, explain why and ask first.
## Worktree Usage
Prefer using the current worktree for small requested fixes. Create a separate worktree only when the user asks for one,
@@ -68,15 +58,6 @@ For PRs expected to trigger substantial Bazel work in CI, run the relevant Bazel
workstation is fast, and local Bazel runs can help hydrate the remote cache for the CI builders while still giving
earlier signal on failures.
## Cleanup PR Batching
When making mechanical cleanup changes, group 3-5 files per PR when the files are receiving the same kind of change
and can be reviewed as one pattern. Do not open one PR per file for nearly identical few-line cleanups, such as replacing
the same unsafe accessor pattern with the same contextual failure pattern across multiple Scala files.
Keep separate PRs for changes that involve different semantics, materially different risk, unrelated subsystems, or
files whose tests/validation strategy makes them better reviewed independently.
## GitHub CLI PR Bodies
When creating or editing PR descriptions with multiline bodies, write the body to a temporary markdown file and pass it
+13
View File
@@ -40,6 +40,19 @@ if [ -f "$INFO_PLIST" ]; then
/usr/libexec/PlistBuddy -c "Set :ITSAppUsesNonExemptEncryption false" "$INFO_PLIST" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :ITSAppUsesNonExemptEncryption bool false" "$INFO_PLIST"
echo "Set ITSAppUsesNonExemptEncryption=false in Info.plist"
# Remove UIApplicationSceneManifest if present.
# Unity 6 generates this key, which opts the app into the iOS scene-based
# lifecycle. This breaks Application.deepLinkActivated because iOS routes
# deep link URLs through UISceneDelegate instead of UIApplicationDelegate,
# and Unity doesn't implement the scene delegate path.
# See: https://issuetracker.unity3d.com/issues/in-135632
if /usr/libexec/PlistBuddy -c "Print :UIApplicationSceneManifest" "$INFO_PLIST" 2>/dev/null; then
echo "Found UIApplicationSceneManifest in Info.plist — removing to fix deep link handling"
/usr/libexec/PlistBuddy -c "Delete :UIApplicationSceneManifest" "$INFO_PLIST"
else
echo "No UIApplicationSceneManifest in Info.plist (deep links should work)"
fi
fi
# Unity always generates "Unity-iPhone" as the main app scheme
+6 -9
View File
@@ -13,10 +13,7 @@ set -euxo pipefail
BUILD_TARGET=$1
WORKSPACE=$(pwd)
UNITY_PROJECT="$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
UNITY_VERSION=$(grep "m_EditorVersion:" "$UNITY_PROJECT/ProjectSettings/ProjectVersion.txt" | head -1 | sed 's/m_EditorVersion: //')
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
ADDRESSABLES_PREFIX="addressables/$BUILD_TARGET/$UNITY_MAJOR_MINOR"
SERVER_DATA="$UNITY_PROJECT/ServerData/$BUILD_TARGET/$UNITY_MAJOR_MINOR"
SERVER_DATA="$UNITY_PROJECT/ServerData/$BUILD_TARGET"
# DigitalOcean Spaces configuration (same region as other eagle0 buckets)
DO_ENDPOINT="https://sfo3.digitaloceanspaces.com"
@@ -78,7 +75,7 @@ summarize_pending_uploads() {
sorted_uploads_file=$(mktemp)
trap 'rm -f "$sync_args_file" "$uploads_file" "$sorted_uploads_file"' RETURN
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
--endpoint-url "$DO_ENDPOINT" \
--acl public-read \
--size-only \
@@ -181,7 +178,7 @@ if [ ! -d "$SERVER_DATA" ]; then
fi
echo "Uploading Addressables bundles from $SERVER_DATA"
echo "Target: s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/"
echo "Target: s3://$DO_BUCKET/addressables/$BUILD_TARGET/"
# Configure AWS CLI for DigitalOcean Spaces
export AWS_ACCESS_KEY_ID="$ACCESS_KEY_ID"
@@ -196,13 +193,13 @@ summarize_pending_uploads
#
# --delete removes files in destination that don't exist in source.
# --acl public-read makes files publicly accessible.
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
--endpoint-url "$DO_ENDPOINT" \
--acl public-read \
--size-only \
--delete
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
--endpoint-url "$DO_ENDPOINT" \
--acl public-read \
--exclude "*" \
@@ -211,4 +208,4 @@ aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
--include "*.json"
echo "Addressables upload complete"
echo "Files available at: https://assets.eagle0.net/$ADDRESSABLES_PREFIX/"
echo "Files available at: https://assets.eagle0.net/addressables/$BUILD_TARGET/"
Binary file not shown.
@@ -37,7 +37,7 @@ int main(const int argc, char** argv) {
void ModifyMap(HexMapProto& map) {
const int terrainCount = map.terrain_size();
for (int i = 0; i < terrainCount; ++i) {
for (int i = 0; i < terrainCount; i++) {
const auto existingTerrainType = map.terrain(i).type();
const auto newTerrain =
static_cast<net::eagle0::shardok::common::Terrain_Type>(existingTerrainType + 2);
@@ -37,17 +37,16 @@ auto CalculateMap(
.name = mapName,
.positionsRequiringCrossing = {}};
for (std::size_t i = 0; i < hexMap->attacker_starting_positions()->size(); ++i) {
for (std::size_t i = 0; i < hexMap->attacker_starting_positions()->size(); i++) {
const auto* positionList = hexMap->attacker_starting_positions()->Get(i);
const auto* positions = positionList->positions();
if (positions->empty()) continue;
if (positions->size() != 10) {
if (positionList->positions()->empty()) continue;
if (positionList->positions()->size() != 10) {
std::cerr << "Should have 10 starting positions!" << '\n';
std::abort();
}
int positionsRequiringCrossing = 0;
for (const auto* startingPosition : *positions) {
for (const auto* startingPosition : *positionList->positions()) {
for (const shardok::Coords& castlePosition : criticalTileLocations) {
if (!shardok::CanReach(
*startingPosition,
@@ -58,7 +57,7 @@ auto CalculateMap(
battalionType)) {
++positionsRequiringCrossing;
break;
}
};
}
}
info.positionsRequiringCrossing[static_cast<int>(i)] = positionsRequiringCrossing;
@@ -32,10 +32,8 @@ auto main(const int argc, char** argv) -> int {
const shardok::BattalionTypeSPtr& battalionType = gameSettings->GetGetter().GetBattalionType(
net::eagle0::shardok::storage::fb::BattalionTypeId_LIGHT_INFANTRY);
const std::vector<std::string> mapNames = GetMapNames();
std::vector<OneMapInfo> mapInfos{};
mapInfos.reserve(mapNames.size());
for (const std::string& mapName : mapNames) {
for (const std::string& mapName : GetMapNames()) {
std::shared_ptr<shardok::ActionPointDistancesCache> apdCache =
std::make_shared<shardok::ActionPointDistancesCache>();
@@ -3,7 +3,6 @@
//
#include <algorithm>
#include <array>
#include <climits>
#include <iostream>
#include <memory>
@@ -11,8 +10,6 @@
#include <random>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/common/UnitConversions.hpp"
@@ -47,27 +44,25 @@ void runBattle(const std::vector<shardok::Unit>& units) {
if (fileNames.empty()) { throw std::runtime_error("No map files found"); }
if (units.size() < 2) { throw std::runtime_error("Need at least two units to run a battle"); }
const auto mapName = fileNames[randInt(0, static_cast<int>(fileNames.size()) - 1)];
const auto month = randInt(1, 12);
auto mapName = fileNames[randInt(0, (int)fileNames.size() - 1)];
auto month = randInt(1, 12);
const auto maxP1UnitCount = std::min<size_t>(20, units.size() - 1);
const auto p1UnitCount = randInt(1, static_cast<int>(maxP1UnitCount));
auto p1UnitCount = randInt(1, static_cast<int>(maxP1UnitCount));
std::vector<shardok::Unit> p1Units{};
p1Units.reserve(p1UnitCount);
for (int i = 0; i < p1UnitCount; ++i) {
for (int i = 0; i < p1UnitCount; i++) {
auto unit = units[i];
unit.mutate_player_id(0);
p1Units.push_back(std::move(unit));
p1Units.push_back(unit);
}
const auto maxP2UnitCount = std::min<size_t>(20, units.size() - p1UnitCount);
const auto p2UnitCount = randInt(1, static_cast<int>(maxP2UnitCount));
auto p2UnitCount = randInt(1, static_cast<int>(maxP2UnitCount));
std::vector<shardok::Unit> p2Units{};
p2Units.reserve(p2UnitCount);
for (int i = p1UnitCount; i < p1UnitCount + p2UnitCount; ++i) {
for (int i = p1UnitCount; i < p1UnitCount + p2UnitCount; i++) {
auto unit = units[i];
unit.mutate_player_id(1);
p2Units.push_back(std::move(unit));
p2Units.push_back(unit);
}
std::cout << "map name is " << mapName << ", " << p1UnitCount << " attacking " << p2UnitCount
@@ -80,34 +75,32 @@ void runBattle(const std::vector<shardok::Unit>& units) {
VictoryConditionProto::VICTORY_CONDITION_LAST_PLAYER_STANDING,
VictoryConditionProto::VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS};
std::vector<shardok::PlayerInfoWithUnits> playerInfos;
playerInfos.reserve(2);
playerInfos.emplace_back(
auto p1 = shardok::PlayerInfoWithUnits(
0,
true,
5,
false,
10000,
attVictoryConditions,
std::vector<shardok::PlayerId>{},
{},
p1Units,
false);
playerInfos.emplace_back(
auto p2 = shardok::PlayerInfoWithUnits(
1,
true,
7,
true,
10000,
defVictoryConditions,
std::vector<shardok::PlayerId>{},
{},
p2Units,
false);
const std::string gameId = std::to_string(randInt(INT_MIN, INT_MAX));
std::string gameId = std::to_string(randInt(INT_MIN, INT_MAX));
manager->UnlockedCreateSpecifiedGame(
gameId,
playerInfos,
{p1, p2},
mapName,
"",
month,
@@ -142,7 +135,10 @@ auto LoadUnits() -> std::vector<shardok::Unit> {
auto main(int /*argc*/, char** argv) -> int {
#if FIXED_SEED
std::srand(4564564);
g.seed(378473);
#else
std::srand(std::time(nullptr));
#endif
FilesystemUtils::SetExecPath(argv[0]);
@@ -156,9 +152,9 @@ auto main(int /*argc*/, char** argv) -> int {
manager = std::make_unique<shardok::ShardokGamesManager>(std::vector<std::string>());
std::array<std::thread, THREAD_COUNT> threads;
std::thread threads[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; ++i) {
for (int i = 0; i < THREAD_COUNT; i++) {
threads[i] = std::thread(runBattlesThread, randomUnits);
}
@@ -16,6 +16,9 @@ namespace shardok {
constexpr double kOverpowerRatio = 2.0;
constexpr double kBraveWaterCostMultiplier = 1.2;
using std::pair;
using std::shared_ptr;
DIST_T NormalizedCostWhenBraving(const DIST_T cost) {
if (cost >= static_cast<double>(ActionPointDistances::IMPOSSIBLE) / kBraveWaterCostMultiplier)
return ActionPointDistances::IMPOSSIBLE;
@@ -153,7 +156,6 @@ auto GenerateTargetPriorities(
vector<TargetAndAttackLocations>{});
vector<TargetAndDistance> targetsWithDistance;
targetsWithDistance.reserve(targets.size());
// Get APDs directly from cache (now with built-in thread-local optimization)
const auto& battType = battalionTypeGetter(unit->battalion().type());
@@ -233,4 +235,4 @@ auto GenerateTargetPriorities(
return allTargetsUnitsAndDistances;
}
} // namespace shardok
} // namespace shardok
@@ -27,7 +27,7 @@ struct TargetAndAttackLocations {
Coords target;
CoordsSet attackLocations;
[[nodiscard]] auto operator==(const TargetAndAttackLocations& rhs) const -> bool = default;
auto operator==(const TargetAndAttackLocations& rhs) const -> bool = default;
};
struct TargetPriorityList {
@@ -39,7 +39,7 @@ struct TargetPriorityList {
priorityOrder(po) {}
};
[[nodiscard]] auto EffectiveDistance(
auto EffectiveDistance(
const Unit* unit,
const HexMap* map,
const AttackLocations& attackLocations,
@@ -47,7 +47,7 @@ struct TargetPriorityList {
const BattalionTypeGetter& battalionTypeGetter,
ActionPoints braveWaterCost) -> DIST_T;
[[nodiscard]] auto EffectiveDistance(
auto EffectiveDistance(
const Unit* unit,
const HexMap* map,
const CoordsSet& locations,
@@ -55,14 +55,14 @@ struct TargetPriorityList {
const BattalionTypeGetter& battalionTypeGetter,
ActionPoints braveWaterCost) -> DIST_T;
[[nodiscard]] auto EffectiveDistance(
auto EffectiveDistance(
const Unit* unit,
const ActionPointDistances* notBravingApd,
const ActionPointDistances* bravingApd,
const CoordsSet& locations) -> DIST_T;
// Chooses a list of targets in priority order for each unit.
[[nodiscard]] auto GenerateTargetPriorities(
auto GenerateTargetPriorities(
const std::vector<const Unit*>& occupants,
const HexMap* map,
const CoordsSet& targets,
@@ -53,8 +53,8 @@ AttackLocationsCache::AttackLocationsCache(const HexMap *hexMap, const SettingsG
auto emptySet = CoordsSet(columnCount, rowCount);
for (MapIndex r = 0; r < hexMap->row_count(); ++r) {
for (MapIndex c = 0; c < hexMap->column_count(); ++c) {
for (MapIndex r = 0; r < hexMap->row_count(); r++) {
for (MapIndex c = 0; c < hexMap->column_count(); c++) {
auto location = Coords(r, c);
const auto terrain = GetTerrain(hexMap, location);
@@ -33,7 +33,6 @@ auto AIAttackerStrategySelector::BestAttackerStrategy(
bool canFlee = false;
vector<const Unit*> attackerUnits{};
attackerUnits.reserve(gameState->units()->size());
CoordsSet defenderPositions(gameState->hex_map());
for (const Unit* unit : *gameState->units()) {
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
@@ -132,4 +131,4 @@ auto AIAttackerStrategySelector::BestAttackerStrategy(
return chosenStrategy;
}
} // namespace shardok
} // namespace shardok
@@ -21,12 +21,16 @@
namespace shardok {
// No need to forward declare internal functions - use the public interface instead
// Helper constants and static variables
static const std::vector<double> kAverageSequence = {0.5};
static const auto kAverageGenerator = std::make_shared<SequenceRandomGenerator>(kAverageSequence);
constexpr bool kMultithread = true;
constexpr bool kLogging = false;
// Helper function to determine if a command type is deterministic
static auto IsDeterministic(const CommandType type) -> bool {
switch (type) {
case net::eagle0::shardok::common::MOVE_COMMAND:
@@ -59,6 +63,7 @@ static auto RandomnessSampleForRepeat(const int repeatIteration, const int maxRe
return static_cast<double>(repeatIteration) / static_cast<double>(maxRepeatCount - 1);
}
// Helper function to sort commands by score
static auto CommandSorter(
const AICommandEvaluator::IndexAndScore& l,
const AICommandEvaluator::IndexAndScore& r) -> bool {
@@ -78,7 +83,7 @@ AICommandEvaluator::AICommandEvaluator(
BattalionTypeGetter battalionTypeGetter)
: scorer_(scorer),
apdCache_(apdCache),
battalionTypeGetter_(std::move(battalionTypeGetter)) {}
battalionTypeGetter_(std::move(battalionTypeGetter)) {} // Move the function object
auto AICommandEvaluator::PerformLookahead(
const PlayerId pid,
@@ -322,16 +327,16 @@ auto AICommandEvaluator::FindBestCommand(
// Structure to hold all command evaluation data
struct CommandEvaluation {
size_t index{};
CommandType type{};
ScoreValue immediateScore{};
size_t index;
CommandType type;
ScoreValue immediateScore;
bool immediateCompleted = true;
std::vector<std::future<EvaluationResult>> lookaheadFutures;
};
std::vector<CommandEvaluation> commandEvaluations(commandCount);
for (uint32_t index = 0; index < commandCount; ++index) {
for (uint32_t index = 0; index < commandCount; index++) {
const auto originalIndex = filteredIndices[index];
const auto& guessedDescriptor = guessedDescriptors->at(originalIndex);
const auto guessedCommandType = guessedDescriptor->GetCommandType();
@@ -417,8 +422,7 @@ auto AICommandEvaluator::FindBestCommand(
} else {
ScoreValue sum = 0.0;
const int sampleCount = std::max(1, maxRepeatCount);
commandEvaluations[index].lookaheadFutures.reserve(sampleCount);
for (int repeatIteration = 0; repeatIteration < sampleCount; ++repeatIteration) {
for (int repeatIteration = 0; repeatIteration < sampleCount; repeatIteration++) {
// In each iteration, use a double from [0, 1] as the random roll
auto sequence =
std::vector{RandomnessSampleForRepeat(repeatIteration, sampleCount)};
@@ -428,7 +432,7 @@ auto AICommandEvaluator::FindBestCommand(
originalIndex,
remainingLookahead,
maxRepeatCount,
std::make_shared<SequenceRandomGenerator>(std::move(sequence)),
std::make_shared<SequenceRandomGenerator>(sequence),
guessedEngine,
attackerStrategy,
allCastleCoords,
@@ -507,15 +511,13 @@ auto AICommandEvaluator::EvaluateCommand(
}
const auto& guessedDescriptor = guessedDescriptors->at(commandIndex);
const auto guessedCommandType = guessedDescriptor->GetCommandType();
if (guessedCommandType == net::eagle0::shardok::common::END_TURN_COMMAND) {
if (const auto guessedCommandType = guessedDescriptor->GetCommandType();
guessedCommandType == net::eagle0::shardok::common::END_TURN_COMMAND) {
std::promise<EvaluationResult> p;
p.set_value(EvaluationResult{.score = currentUtility, .completed = true});
return p.get_future();
}
if (IsDeterministic(guessedCommandType)) {
} else if (IsDeterministic(guessedCommandType)) {
auto evaluation = EvaluateWithRandomness(
pid,
isDefender,
@@ -528,9 +530,7 @@ auto AICommandEvaluator::EvaluateCommand(
allCastleCoords,
deadline);
return std::move(evaluation.lookaheadScore);
}
if (guessedDescriptor->HasOdds()) {
} else if (guessedDescriptor->HasOdds()) {
const auto successChancePercentile = guessedDescriptor->GetOddsPercentile();
const double successChance = static_cast<double>(successChancePercentile) / 100.0;
@@ -575,47 +575,47 @@ auto AICommandEvaluator::EvaluateCommand(
.score = std::lerp(failure.score, success.score, successChance),
.completed = true};
});
} else {
// For non-deterministic commands without odds, use multiple attempts
std::vector<std::future<EvaluationResult>> lookaheadFutures;
const int sampleCount = std::max(1, maxRepeatCount);
lookaheadFutures.reserve(sampleCount);
for (int repeatIteration = 0; repeatIteration < sampleCount; repeatIteration++) {
auto sequence = std::vector{RandomnessSampleForRepeat(repeatIteration, sampleCount)};
auto evaluation = EvaluateWithRandomness(
pid,
isDefender,
commandIndex,
remainingLookahead,
maxRepeatCount,
std::make_shared<SequenceRandomGenerator>(sequence),
guessedEngine,
attackerStrategy,
allCastleCoords,
deadline);
lookaheadFutures.push_back(std::move(evaluation.lookaheadScore));
}
// Return a future that computes the average when needed
return std::async(
std::launch::deferred,
[lookaheadFutures = std::move(lookaheadFutures),
sampleCount]() mutable -> EvaluationResult {
ScoreValue total = 0.0;
bool completed = true;
for (auto& future : lookaheadFutures) {
const auto result = future.get();
if (!result.completed) { completed = false; }
total += result.score;
}
if (!completed) { return EvaluationResult{.score = 0.0, .completed = false}; }
return EvaluationResult{
.score = total / static_cast<ScoreValue>(sampleCount),
.completed = true};
});
}
// For non-deterministic commands without odds, use multiple attempts
std::vector<std::future<EvaluationResult>> lookaheadFutures;
const int sampleCount = std::max(1, maxRepeatCount);
lookaheadFutures.reserve(sampleCount);
for (int repeatIteration = 0; repeatIteration < sampleCount; ++repeatIteration) {
auto sequence = std::vector{RandomnessSampleForRepeat(repeatIteration, sampleCount)};
auto evaluation = EvaluateWithRandomness(
pid,
isDefender,
commandIndex,
remainingLookahead,
maxRepeatCount,
std::make_shared<SequenceRandomGenerator>(std::move(sequence)),
guessedEngine,
attackerStrategy,
allCastleCoords,
deadline);
lookaheadFutures.push_back(std::move(evaluation.lookaheadScore));
}
// Return a future that computes the average when needed
return std::async(
std::launch::deferred,
[lookaheadFutures = std::move(lookaheadFutures),
sampleCount]() mutable -> EvaluationResult {
ScoreValue total = 0.0;
bool completed = true;
for (auto& future : lookaheadFutures) {
const auto result = future.get();
if (!result.completed) { completed = false; }
total += result.score;
}
if (!completed) { return EvaluationResult{.score = 0.0, .completed = false}; }
return EvaluationResult{
.score = total / static_cast<ScoreValue>(sampleCount),
.completed = true};
});
}
} // namespace shardok
@@ -57,11 +57,11 @@ public:
/// Find the best command among all available commands at the given depth.
struct IndexAndScore {
size_t index{};
CommandType type{};
ScoreValue lookaheadScore{};
ScoreValue immediateScore{};
bool completed{};
size_t index;
CommandType type;
ScoreValue lookaheadScore;
ScoreValue immediateScore;
bool completed;
};
[[nodiscard]] auto FindBestCommand(
@@ -78,11 +78,11 @@ public:
private:
const AIScoreCalculator& scorer_;
const APDCache& apdCache_;
BattalionTypeGetter battalionTypeGetter_;
BattalionTypeGetter battalionTypeGetter_; // Store by value
struct ImmediateAndLookaheadScore {
ScoreValue immediateScore{};
bool completed{};
ScoreValue immediateScore;
bool completed;
std::future<EvaluationResult> lookaheadScore;
};
@@ -32,7 +32,6 @@ auto AIDefenderStrategySelector::BestDefenderStrategy(
bool canFlee = false;
vector<UnitId> attackerUnitIdsRequiringWaterCrossing{};
attackerUnitIdsRequiringWaterCrossing.reserve(gameState->units()->size());
for (const auto& player : *gameState->player_infos()) {
if (!player->is_defender()) {
const auto& unitIdsRequiringWaterCrossing = UnitIdsRequiringWaterCrossing(
@@ -104,14 +104,17 @@ auto DefenderDistanceBuf(
pointCostToDesiredTargetWithBraving);
}
std::ranges::sort(pointCosts, [](const WithoutAndWith &left, const WithoutAndWith &right) {
return left.without == right.without ? left.with < right.with
: left.without < right.without;
});
std::sort(
begin(pointCosts),
end(pointCosts),
[](const WithoutAndWith &left, const WithoutAndWith &right) {
return left.without == right.without ? left.with < right.with
: left.without < right.without;
});
// experiment: add dummy values to the end with very high cost, so there's never an advantage
// to leaving a defender unit alive in a castle.
for (size_t i = attackerUnits.size(); i < 20; ++i) { pointCosts.emplace_back(999, 999); }
for (size_t i = attackerUnits.size(); i < 20; i++) { pointCosts.emplace_back(999, 999); }
double sum = 0.0;
double decr = kPerUnitDebufDecay;
@@ -13,7 +13,7 @@
namespace shardok {
[[nodiscard]] auto DefenderDistanceBuf(
auto DefenderDistanceBuf(
const Coords &defenderLocation,
const HexMap *hexMap,
const MapId &mapId,
@@ -5,32 +5,18 @@
#ifndef EAGLE0_SHARDOK_AI_AI_EXPERIMENT_CONFIG_HPP
#define EAGLE0_SHARDOK_AI_AI_EXPERIMENT_CONFIG_HPP
#include <charconv>
#include <cstdlib>
#include <string_view>
#include <system_error>
#include <string>
namespace shardok {
[[nodiscard]] inline auto CurrentAIExperimentId() -> int {
inline auto CurrentAIExperimentId() -> int {
const char* rawValue = std::getenv("SHARDOK_SCORING_EXPERIMENT_ID");
if (rawValue == nullptr) { return 0; }
const std::string_view value(rawValue);
if (value.empty()) { return 0; }
int experimentId = 0;
const auto parseResult =
std::from_chars(value.data(), value.data() + value.size(), experimentId);
if (parseResult.ec != std::errc{} || parseResult.ptr != value.data() + value.size()) {
return 0;
}
return experimentId;
if (rawValue == nullptr || std::string(rawValue).empty()) { return 0; }
return std::atoi(rawValue);
}
[[nodiscard]] inline auto IsAIExperiment(const int id) -> bool {
return CurrentAIExperimentId() == id;
}
inline auto IsAIExperiment(const int id) -> bool { return CurrentAIExperimentId() == id; }
} // namespace shardok
@@ -5,6 +5,9 @@
#ifndef EAGLE0_SHARDOK_AI_AI_MINIMUM_DISTANCE_AND_TARGET_HPP
#define EAGLE0_SHARDOK_AI_AI_MINIMUM_DISTANCE_AND_TARGET_HPP
#include <utility>
#include <vector>
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistances.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/coords.pb.h"
@@ -12,18 +15,19 @@
namespace shardok {
using net::eagle0::shardok::storage::fb::Coords;
using std::shared_ptr;
struct CoordsAndDistance {
Coords coords;
int distance;
};
[[nodiscard]] auto MinimumDistanceAndTarget(
auto MinimumDistanceAndTarget(
const ActionPointDistances *apd,
const Coords &origin,
const CoordsSet &destinations) -> CoordsAndDistance;
[[nodiscard]] auto MinimumDistance(
auto MinimumDistance(
const ActionPointDistances *apd,
const Coords &origin,
const CoordsSet &destinations) -> int;
@@ -20,12 +20,12 @@ using GameState = net::eagle0::shardok::storage::fb::GameState;
using PlayerInfo = net::eagle0::shardok::storage::fb::PlayerInfo;
using Unit = net::eagle0::shardok::storage::fb::Unit;
[[nodiscard]] auto HasAttachedHeroWithProfession(
auto HasAttachedHeroWithProfession(
const Unit *unit,
net::eagle0::shardok::storage::fb::Profession profession) -> bool;
[[nodiscard]] auto CastleClaimCapableAttackerUnitCount(const GameStateW &gameState) -> int;
[[nodiscard]] auto PlayerInfoForPid(const GameStateW &, PlayerId pid) -> const PlayerInfo *;
auto CastleClaimCapableAttackerUnitCount(const GameStateW &gameState) -> int;
auto PlayerInfoForPid(const GameStateW &, PlayerId pid) -> const PlayerInfo *;
} // namespace shardok
@@ -26,13 +26,11 @@ struct AIStrategy {
};
extern AIStrategy FleeStrategy;
[[nodiscard]] auto AttackUnitsStrategy(const vector<TargetPriorityList>& targetPriorities)
-> AIStrategy;
[[nodiscard]] auto AttackCastlesStrategy(const vector<TargetPriorityList>& targetPriorities)
-> AIStrategy;
auto AttackUnitsStrategy(const vector<TargetPriorityList>& targetPriorities) -> AIStrategy;
auto AttackCastlesStrategy(const vector<TargetPriorityList>& targetPriorities) -> AIStrategy;
extern AIStrategy HoldCastlesStrategy;
extern AIStrategy ScatterStrategy;
[[nodiscard]] auto CrossRiversStrategy(const CoordsSet& targetLocations) -> AIStrategy;
auto CrossRiversStrategy(const CoordsSet& targetLocations) -> AIStrategy;
} // namespace shardok
@@ -25,12 +25,7 @@ class AIEvaluationCounter {
public:
AIEvaluationCounter();
~AIEvaluationCounter();
AIEvaluationCounter(const AIEvaluationCounter&) = delete;
auto operator=(const AIEvaluationCounter&) -> AIEvaluationCounter& = delete;
AIEvaluationCounter(AIEvaluationCounter&&) = delete;
auto operator=(AIEvaluationCounter&&) -> AIEvaluationCounter& = delete;
[[nodiscard]] static int GetCurrentCount();
static int GetCurrentCount();
};
// Configuration structure for iterative deepening time budget
@@ -44,10 +39,10 @@ struct AITimeBudget {
// Time budget is calculated dynamically based on number of available commands:
// budget = msPerCommand × numCommands (clamped to 200-5000ms)
// If isAllAiBattle is true, uses allAiBattleTimeBudgetMaximum instead of the normal maximum.
[[nodiscard]] auto CalculateTimeBudget(
auto CalculateTimeBudget(
PlayerId playerId,
const GameSettingsSPtr& settings,
const GameStateW& state,
const GameSettingsSPtr &settings,
const GameStateW &state,
size_t numCommands,
bool isAllAiBattle) -> AITimeBudget;
@@ -11,6 +11,10 @@
namespace shardok {
using std::begin;
using std::end;
using std::shared_ptr;
constexpr double kProfessionValue = 200;
constexpr double kVigorScoreMultiplier = 5.0;
constexpr double kCastleMultiplierBonus = 1.0;
@@ -276,7 +280,7 @@ auto GetRangedAttackBonus(
const int meteorRange,
const double minVigorToCast,
const ActionPoints archeryActionPointCost) -> double {
double bestRangedAttackValue = 0.0;
vector<double> rangedAttackValues{};
const auto &enemyUnits = isAttacker ? defenderUnits : attackerUnits;
const auto &friendlyUnits = isAttacker ? attackerUnits : defenderUnits;
@@ -292,7 +296,7 @@ auto GetRangedAttackBonus(
net::eagle0::shardok::storage::fb::MultiroundMagicState_NONE)) {
if (unit->attached_hero().profession_info().meteor_cast_state() ==
net::eagle0::shardok::storage::fb::MultiroundMagicState_NONE) {
bestRangedAttackValue = std::max(bestRangedAttackValue, lightningValue(unit));
rangedAttackValues.push_back(lightningValue(unit));
} else {
const double mv = meteorValue(
unit,
@@ -302,14 +306,14 @@ auto GetRangedAttackBonus(
friendlyUnits,
meteorRange,
minVigorToCast);
bestRangedAttackValue = std::max(bestRangedAttackValue, mv);
rangedAttackValues.push_back(mv);
}
}
if (attackLocations.EngineerLocations().Contains(unitLocation) && unit->has_attached_hero() &&
unit->attached_hero().profession_info().profession() ==
net::eagle0::shardok::storage::fb::Profession_ENGINEER) {
bestRangedAttackValue = std::max(bestRangedAttackValue, reduceValue(unit, terrain));
rangedAttackValues.push_back(reduceValue(unit, terrain));
}
if (attackLocations.NecromancerLocations().Contains(unitLocation) &&
@@ -317,24 +321,23 @@ auto GetRangedAttackBonus(
unit->attached_hero().profession_info().profession() ==
net::eagle0::shardok::storage::fb::Profession_NECROMANCER &&
unit->attached_hero().control_info().controlled_unit_id() != -1) {
bestRangedAttackValue = std::max(bestRangedAttackValue, fearValue(enemyUnits));
rangedAttackValues.push_back(fearValue(enemyUnits));
}
if (attackLocations.ArcheryLocations().Contains(unitLocation) &&
unit->volleys_remaining() > 0) {
bestRangedAttackValue =
std::max(bestRangedAttackValue, archeryValue(enemyUnits, canShootNow));
rangedAttackValues.push_back(archeryValue(enemyUnits, canShootNow));
}
// Longbowmen in castle can archer adjacent
if (attackLocations.AdjacentLocations().Contains(unitLocation) &&
unit->volleys_remaining() > 0 && terrain->modifier().castle().present() &&
unit->battalion().type() == net::eagle0::shardok::storage::fb::BattalionTypeId_LONGBOWMEN) {
bestRangedAttackValue =
std::max(bestRangedAttackValue, archeryValue(enemyUnits, canShootNow));
rangedAttackValues.push_back(archeryValue(enemyUnits, canShootNow));
}
return bestRangedAttackValue;
if (rangedAttackValues.empty()) return 0.0;
return *std::max_element(begin(rangedAttackValues), end(rangedAttackValues));
}
auto UnitValue(
@@ -17,9 +17,9 @@ using net::eagle0::shardok::storage::fb::Unit;
using ScoreValue = double;
class AttackLocations;
[[nodiscard]] auto ContextFreeUnitValue(const Unit *unit) -> ScoreValue;
auto ContextFreeUnitValue(const Unit *unit) -> ScoreValue;
[[nodiscard]] auto GetRangedAttackBonus(
auto GetRangedAttackBonus(
const Unit *unit,
bool isAttacker,
const HexMap *map,
@@ -32,7 +32,7 @@ class AttackLocations;
double minVigorToCast,
ActionPoints archeryActionPointCost = 0) -> double;
[[nodiscard]] auto UnitValue(
auto UnitValue(
const Unit *unit,
bool isAttacker,
const std::vector<const Unit *> &attackerUnits,
@@ -18,7 +18,7 @@ auto UnitIdsRequiringWaterCrossing(
const BattalionTypeGetter &battalionTypeGetter) -> vector<UnitId> {
// Put out all the fires, except on bridges
fb::HexMapW mapCopy = fb::CopyHexMap(gameState->hex_map());
for (uint32_t index = 0; index < mapCopy->terrain()->size(); ++index) {
for (uint32_t index = 0; index < mapCopy->terrain()->size(); index++) {
if (IsWater(mapCopy->terrain()->Get(index)->type())) continue;
// const_cast is safe because we own the mutable buffer (mapCopy)
const_cast<net::eagle0::shardok::storage::fb::Terrain *>(
@@ -155,7 +155,7 @@ auto WaterCrossingTiles(
// 1) the tile is water
// 2) the tile does not already have a bridge (that is not on fire)
// 3) the tile is not already frozen
for (int index = 0; index < mapSize; ++index) {
for (int index = 0; index < mapSize; index++) {
const auto startTerrain = *hexMap->terrain()->Get(index);
if (!IsWater(startTerrain.type())) continue;
@@ -31,7 +31,7 @@ static inline void AssertValid(const Coords& c, const HexMap* hexMap) {
}
// Units that need a water crossing to reach at least one of the destinations
[[nodiscard]] auto UnitIdsRequiringWaterCrossing(
auto UnitIdsRequiringWaterCrossing(
const GameStateW& gameState,
PlayerId pid,
const CoordsSet& destinations,
@@ -39,14 +39,14 @@ static inline void AssertValid(const Coords& c, const HexMap* hexMap) {
const BattalionTypeGetter& battalionTypeGetter) -> vector<UnitId>;
// Units belonging to the player that are capable of creating water crossings
[[nodiscard]] auto UnitIdsToCreateWaterCrossing(
auto UnitIdsToCreateWaterCrossing(
const GameStateW& gameState,
PlayerId pid,
const BattalionTypeGetter& battalionTypeGetter) -> vector<UnitId>;
// Whether a unit of the given type can reach destination from origin, given the current state
// of the map
[[nodiscard]] auto CanReach(
auto CanReach(
const Coords& origin,
const Coords& destination,
const HexMap* hexMap,
@@ -56,12 +56,11 @@ static inline void AssertValid(const Coords& c, const HexMap* hexMap) {
// Returns the *non*-water tiles from which you could bridge/freeze water to allow a unit to cross
// from origin to destination
[[nodiscard]] auto CrossingStartLocations(const HexMap* hexMap, const CoordsSet& waterCrossingTiles)
-> CoordsSet;
auto CrossingStartLocations(const HexMap* hexMap, const CoordsSet& waterCrossingTiles) -> CoordsSet;
// Returns the water tiles that, if they were bridged/frozen, would allow a unit to cross from
// origin to destination
[[nodiscard]] auto WaterCrossingTiles(
auto WaterCrossingTiles(
const Coords& origin,
const Coords& destination,
const HexMap* hexMap,
@@ -69,7 +68,7 @@ static inline void AssertValid(const Coords& c, const HexMap* hexMap) {
const BattalionTypeSPtr& battalionType) -> CoordsSet;
// Returns the set of tiles that the attacker should try to approach in order to bridge/freeze
[[nodiscard]] auto IntendedCrossingStarts(
auto IntendedCrossingStarts(
const GameStateW& gameState,
const vector<UnitId>& unitIdsCreatingCrossing,
const CoordsSet& tilesToStartCrossingFrom,
@@ -77,7 +76,7 @@ static inline void AssertValid(const Coords& c, const HexMap* hexMap) {
const BattalionTypeGetter& battalionTypeGetter) -> CoordsSet;
// Calculate score based on water crossing strategy
[[nodiscard]] auto WaterCrossingScore(
auto WaterCrossingScore(
PlayerId playerId,
const BattalionTypeGetter& battalionTypeGetter,
const GameStateW& gameState,
@@ -51,7 +51,7 @@ IterativeDeepeningAI::IterativeDeepeningAI(
castleCoords(castleCoords),
scorer(scorer),
apdCache(apdCache),
battalionTypeGetter(std::move(battalionTypeGetter)) {}
battalionTypeGetter(std::move(battalionTypeGetter)) {} // Move the function object
auto IterativeDeepeningAI::IterativeSearch(
const GameSettingsSPtr& settings,
@@ -79,7 +79,7 @@ private:
CoordsSet castleCoords;
const AIScoreCalculator& scorer;
const APDCache& apdCache;
BattalionTypeGetter battalionTypeGetter;
BattalionTypeGetter battalionTypeGetter; // Store by value, not reference!
// Reusable vectors to reduce memory allocations
mutable std::vector<std::vector<ScoreValue>> scoresByDepth;
@@ -182,7 +182,7 @@ auto ShardokAIClient::StandardChooseCommandIndex(
<< " round=" << guessedState->current_round() << "\n";
const auto maxDump = std::max(commandCount, realAvailableCommands->size());
for (size_t i = 0; i < maxDump; ++i) {
for (size_t i = 0; i < maxDump; i++) {
const auto realName = i < realAvailableCommands->size()
? net::eagle0::shardok::common::CommandType_Name(
(*realAvailableCommands)[i]->GetCommandType())
@@ -214,13 +214,13 @@ auto ShardokAIClient::StandardChooseCommandIndex(
// Group by unit to find the missing position per unit
std::unordered_map<int, std::set<std::pair<int, int>>> realPositions;
std::unordered_map<int, std::set<std::pair<int, int>>> guessedPositions;
for (size_t i = 0; i < realAvailableCommands->size(); ++i) {
for (size_t i = 0; i < realAvailableCommands->size(); i++) {
const auto &cmd = (*realAvailableCommands)[i];
realPositions[cmd->GetActorUnitId()].emplace(
cmd->GetTargetRow(),
cmd->GetTargetColumn());
}
for (size_t i = 0; i < commandCount; ++i) {
for (size_t i = 0; i < commandCount; i++) {
const auto &cmd = (*guessedCommands)[i];
guessedPositions[cmd->GetActorUnitId()].emplace(
cmd->GetTargetRow(),
@@ -250,7 +250,7 @@ auto ShardokAIClient::StandardChooseCommandIndex(
// Dump all guessed state units for full picture
std::cerr << " Guessed state units (" << guessedState->units()->size() << " total):\n";
for (size_t i = 0; i < guessedState->units()->size(); ++i) {
for (size_t i = 0; i < guessedState->units()->size(); i++) {
const auto *u = guessedState->units()->Get(static_cast<unsigned int>(i));
std::cerr << " unit_id=" << u->unit_id() << " player=" << u->player_id()
<< " status=" << static_cast<int>(u->status()) << " loc=("
@@ -263,7 +263,7 @@ auto ShardokAIClient::StandardChooseCommandIndex(
"AI command count mismatch: guessed=" + std::to_string(commandCount) +
" real=" + std::to_string(realAvailableCommands->size()));
}
for (size_t i = 0; i < commandCount; ++i) {
for (size_t i = 0; i < commandCount; i++) {
CheckCommand((*realAvailableCommands)[i], (*guessedCommands)[i]);
}
@@ -490,7 +490,7 @@ auto ShardokAIClient::ChooseCommandIndex(
if (totalChoices % 1000 == 0) {
std::cout << "TYPES CHOSEN:\n";
vector<std::pair<int, CommandType>> choices{};
for (int i = 0; i <= net::eagle0::shardok::common::CommandType_MAX; ++i) {
for (int i = 0; i <= net::eagle0::shardok::common::CommandType_MAX; i++) {
if (typeChosenCount[i] != 0) {
choices.emplace_back(typeChosenCount[i], static_cast<CommandType>(i));
}
@@ -18,7 +18,7 @@ TranspositionTable::TranspositionTable() : table(TABLE_SIZE) {
clear();
}
auto TranspositionTable::hashGameState(const GameStateW& state) const -> uint64_t {
uint64_t TranspositionTable::hashGameState(const GameStateW& state) const {
// The FlatBuffer is contiguous in memory and units are sorted by ID,
// so we can just hash the raw bytes for order-independent hashing
// Use ComputeFNV1aHash to avoid creating a string copy
@@ -28,8 +28,8 @@ public:
std::atomic<uint64_t> stores{0};
std::atomic<uint64_t> collisions{0};
[[nodiscard]] auto hitRate() const -> double {
const uint64_t p = probes.load();
double hitRate() const {
uint64_t p = probes.load();
return p > 0 ? (100.0 * hits.load() / p) : 0.0;
}
@@ -60,7 +60,7 @@ private:
std::atomic<uint16_t> current_age{0};
// Hash function for FlatBuffer game state
[[nodiscard]] auto hashGameState(const GameStateW& state) const -> uint64_t;
uint64_t hashGameState(const GameStateW& state) const;
public:
TranspositionTable();
@@ -78,7 +78,7 @@ public:
void incrementAge() { current_age++; }
// Get statistics
[[nodiscard]] auto getStats() const -> const Stats& { return stats; }
const Stats& getStats() const { return stats; }
// Print statistics to stdout
void printStats() const;
@@ -37,23 +37,6 @@ auto LegalActionsCacheKey(const uint64_t stateHash, const bool rootIsDefender) -
return rootIsDefender ? stateHash ^ kDefenderRoleHash : stateHash;
}
auto SortActionsByWeight(
std::vector<std::unique_ptr<MCTSAction>> actions,
const std::vector<double>& weights) -> std::vector<std::unique_ptr<MCTSAction>> {
std::vector<size_t> sortedIndices(actions.size());
std::iota(sortedIndices.begin(), sortedIndices.end(), 0);
std::ranges::sort(sortedIndices, [&weights](size_t a, size_t b) {
return weights[a] > weights[b];
});
std::vector<std::unique_ptr<MCTSAction>> sortedActions;
sortedActions.reserve(actions.size());
for (size_t idx : sortedIndices) { sortedActions.push_back(std::move(actions[idx])); }
return sortedActions;
}
} // namespace
// Shared cache for legal actions (uses lock-free parallel hash map for thread safety)
@@ -299,7 +282,19 @@ std::vector<std::unique_ptr<MCTSAction>> ShardokGameEngine::getLegalActions(
// Sort actions by weight (descending) to ensure MCTS explores high-value actions first
const std::vector<double> weights = getActionWeights(actions, state);
return SortActionsByWeight(std::move(actions), weights);
std::vector<size_t> sortedIndices(actions.size());
std::iota(sortedIndices.begin(), sortedIndices.end(), 0);
std::sort(sortedIndices.begin(), sortedIndices.end(), [&weights](size_t a, size_t b) {
return weights[a] > weights[b];
});
std::vector<std::unique_ptr<MCTSAction>> sortedActions;
sortedActions.reserve(actions.size());
for (size_t idx : sortedIndices) { sortedActions.push_back(std::move(actions[idx])); }
return sortedActions;
}
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
@@ -360,7 +355,20 @@ std::vector<std::unique_ptr<MCTSAction>> ShardokGameEngine::getLegalActions(
// get explored deeply. Original indices are preserved in ShardokAction::getIndex()
const std::vector<double> weights = getActionWeights(actions, state);
actions = SortActionsByWeight(std::move(actions), weights);
// Create index vector for sorting
std::vector<size_t> sortedIndices(actions.size());
std::iota(sortedIndices.begin(), sortedIndices.end(), 0);
// Sort indices by weight (descending)
std::sort(sortedIndices.begin(), sortedIndices.end(), [&weights](size_t a, size_t b) {
return weights[a] > weights[b];
});
// Reorder actions according to sorted indices
std::vector<std::unique_ptr<MCTSAction>> sortedActions;
sortedActions.reserve(actions.size());
for (size_t idx : sortedIndices) { sortedActions.push_back(std::move(actions[idx])); }
actions = std::move(sortedActions);
const auto actionsEnd = std::chrono::high_resolution_clock::now();
timeInLegalActionsComputation_.fetch_add(
@@ -398,8 +406,9 @@ std::vector<size_t> ShardokGameEngine::filterActions(
const MCTSGameState& /*state*/) const {
// All filtering is already done in getLegalActions() using AICommandFilter
// This method is used by simulation policies and doesn't need additional filtering
std::vector<size_t> indices(actions.size());
std::iota(indices.begin(), indices.end(), 0);
std::vector<size_t> indices;
indices.reserve(actions.size());
for (size_t i = 0; i < actions.size(); ++i) { indices.push_back(i); }
return indices;
}
@@ -5,6 +5,8 @@
#ifndef EAGLE0_SHARDOK_AI_SCORE_AI_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_SCORE_AI_SCORE_CALCULATOR_HPP
#include <future>
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCTypes.h"
@@ -14,6 +16,7 @@
namespace shardok {
using shardok::PlayerId;
using std::future;
using ScoreValue = double;
@@ -160,8 +160,6 @@ auto AttackerHoldsCriticalTilesVictoryScore(
ActionPoints braveWaterCost) -> ScoreValue {
vector<const Unit*> playerUnits{};
vector<const Unit*> claimablePlayerUnits{};
playerUnits.reserve(gameState->units()->size());
claimablePlayerUnits.reserve(gameState->units()->size());
for (const Unit* unit : *gameState->units()) {
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
unit->player_id() == player->player_id()) {
@@ -262,8 +260,6 @@ auto LastPlayerStandingVictoryScore(
vector<const Unit*> playerUnits{};
vector<const Unit*> otherUnits{};
playerUnits.reserve(gameState->units()->size());
otherUnits.reserve(gameState->units()->size());
for (const Unit* unit : *gameState->units()) {
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
@@ -23,7 +23,7 @@ using net::eagle0::shardok::storage::fb::PlayerInfo;
using ScoreValue = double;
[[nodiscard]] auto AttackerHoldsCriticalTilesVictoryScore(
auto AttackerHoldsCriticalTilesVictoryScore(
const GameStateW& gameState,
const CoordsSet& criticalTileLocations,
const PlayerInfo* player,
@@ -32,12 +32,12 @@ using ScoreValue = double;
const BattalionTypeGetter& battalionTypeGetter,
ActionPoints braveWaterCost) -> ScoreValue;
[[nodiscard]] auto DefenderHoldsCriticalTilesVictoryScore(
auto DefenderHoldsCriticalTilesVictoryScore(
const GameStateW& gameState,
const CoordsSet& criticalTileLocations,
const PlayerInfo* player) -> ScoreValue;
[[nodiscard]] auto LastPlayerStandingVictoryScore(
auto LastPlayerStandingVictoryScore(
const GameStateW& gameState,
const PlayerInfo* player,
const APDCache& apdCache,
@@ -230,7 +230,7 @@ auto MakeMCTSOptimizedAIScoreCalculator(
std::vector<BattalionTypeSPtr> battalionTypes(BattalionTypeId::BattalionTypeId_MAX + 1);
for (int typeId = BattalionTypeId::BattalionTypeId_MIN;
typeId <= BattalionTypeId::BattalionTypeId_MAX;
++typeId) {
typeId++) {
auto battalionTypeId = static_cast<BattalionTypeId>(typeId);
battalionTypes[battalionTypeId] = settingsGetter.GetBattalionType(battalionTypeId);
}
@@ -144,10 +144,6 @@ private:
public:
AttackerScoreTimer() : startTime(std::chrono::high_resolution_clock::now()) {}
AttackerScoreTimer(const AttackerScoreTimer &) = delete;
auto operator=(const AttackerScoreTimer &) -> AttackerScoreTimer & = delete;
AttackerScoreTimer(AttackerScoreTimer &&) = delete;
auto operator=(AttackerScoreTimer &&) -> AttackerScoreTimer & = delete;
~AttackerScoreTimer() {
auto endTime = std::chrono::high_resolution_clock::now();
@@ -356,7 +352,7 @@ auto MakeStandardAIScoreCalculatorWithExperiment(
std::vector<BattalionTypeSPtr> battalionTypes(BattalionTypeId::BattalionTypeId_MAX + 1);
for (int typeId = BattalionTypeId::BattalionTypeId_MIN;
typeId <= BattalionTypeId::BattalionTypeId_MAX;
++typeId) {
typeId++) {
auto battalionTypeId = static_cast<BattalionTypeId>(typeId);
battalionTypes[battalionTypeId] = settingsGetter.GetBattalionType(battalionTypeId);
}
@@ -7,7 +7,8 @@
#include "src/main/cpp/net/eagle0/shardok/ai/AIScoreUtilities.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
namespace shardok::score_calculator_internal {
namespace shardok {
namespace score_calculator_internal {
auto EffectiveDistanceCache::GetOrCompute(
const Unit* unit,
@@ -124,4 +125,5 @@ auto AttackerMultiplierForTargetDistance(
isLateGame);
}
} // namespace shardok::score_calculator_internal
} // namespace score_calculator_internal
} // namespace shardok
@@ -24,7 +24,8 @@
#include "src/main/flatbuffer/net/eagle0/shardok/storage/hex_map_generated.h"
#include "src/main/flatbuffer/net/eagle0/shardok/storage/unit_generated.h"
namespace shardok::score_calculator_internal {
namespace shardok {
namespace score_calculator_internal {
using HexMap = net::eagle0::shardok::storage::fb::HexMap;
using Unit = net::eagle0::shardok::storage::fb::Unit;
@@ -50,13 +51,13 @@ struct EffectiveDistanceCache {
struct CacheKey {
UnitId unitId;
Coords target;
[[nodiscard]] bool operator==(const CacheKey& other) const {
bool operator==(const CacheKey& other) const {
return unitId == other.unitId && target == other.target;
}
};
struct CacheKeyHash {
[[nodiscard]] size_t operator()(const CacheKey& key) const {
size_t operator()(const CacheKey& key) const {
return std::hash<UnitId>{}(key.unitId) ^ (std::hash<int>{}(key.target.row()) << 1) ^
(std::hash<int>{}(key.target.column()) << 2);
}
@@ -64,7 +65,7 @@ struct EffectiveDistanceCache {
mutable gtl::flat_hash_map<CacheKey, DIST_T, CacheKeyHash> cache;
[[nodiscard]] DIST_T GetOrCompute(
DIST_T GetOrCompute(
const Unit* unit,
const Coords& target,
const ActionPointDistances* notBravingApd,
@@ -74,12 +75,11 @@ struct EffectiveDistanceCache {
/// Calculate score for FLEE strategy
/// Returns negative score based on fleeing units
[[nodiscard]] auto FleeStrategyScoreForState(const GameStateW& gameState, PlayerId playerId)
-> ScoreValue;
auto FleeStrategyScoreForState(const GameStateW& gameState, PlayerId playerId) -> ScoreValue;
/// Calculate attacker multiplier based on distance to priority targets
/// This is used to weight attacker units by their proximity to objectives
[[nodiscard]] auto AttackerMultiplierForTargetDistance(
auto AttackerMultiplierForTargetDistance(
const Unit* attackingUnit,
const std::vector<TargetAndAttackLocations>& priorityList,
const std::vector<const Unit*>& occupants,
@@ -89,6 +89,7 @@ struct EffectiveDistanceCache {
const ActionPointDistances* bravingApd,
bool isLateGame) -> double;
} // namespace shardok::score_calculator_internal
} // namespace score_calculator_internal
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_SCORE_PRIVATE_AI_SCORE_CALCULATOR_SHARED_UTILITIES_HPP
@@ -47,19 +47,19 @@ auto AbstractAIScoreCalculator::CalculateUnitsScoreComponents(
// Memoization cache for EffectiveDistance calls
EffectiveDistanceCache distanceCache;
// Early return for empty game states
const size_t estimatedUnitCount = cachedUnits->size();
if (estimatedUnitCount == 0) { return UnitsScoreComponents{0.0, 0.0}; }
std::vector<const Unit *> attackerUnits{};
std::vector<const Unit *> defenderUnits{};
// Pre-allocate vectors based on estimated unit ratios to avoid reallocations
attackerUnits.reserve(estimatedUnitCount);
defenderUnits.reserve(estimatedUnitCount);
const size_t estimatedUnitCount = cachedUnits->size();
attackerUnits.reserve(estimatedUnitCount - 1);
defenderUnits.reserve(estimatedUnitCount - 1);
double attackerUnitsValue = 0;
double defenderUnitsValue = 0;
// Early return for empty game states
if (cachedUnits->size() == 0) { return UnitsScoreComponents{0.0, 0.0}; }
auto occupants = Occupants(*cachedUnits, cachedRowCount, cachedColumnCount);
for (const Unit *unit : *cachedUnits) {
@@ -10,7 +10,8 @@
#include <iostream>
#include <sstream>
namespace shardok::ai_battle_simulator {
namespace shardok {
namespace ai_battle_simulator {
using net::eagle0::shardok::ai_battle_simulator::AIAlgorithmType;
using net::eagle0::shardok::ai_battle_simulator::PlayerConfig;
@@ -109,4 +110,5 @@ std::string AiBattleConfigLoader::ToJsonString(const BattleConfigProto& config)
return jsonString;
}
} // namespace shardok::ai_battle_simulator
} // namespace ai_battle_simulator
} // namespace shardok
@@ -13,7 +13,8 @@
#include "src/main/protobuf/net/eagle0/shardok/ai_battle_simulator/ai_battle_config.pb.h"
#pragma clang diagnostic pop
namespace shardok::ai_battle_simulator {
namespace shardok {
namespace ai_battle_simulator {
using BattleConfigProto = net::eagle0::shardok::ai_battle_simulator::BattleConfig;
@@ -40,6 +41,7 @@ public:
[[nodiscard]] static std::string ToJsonString(const BattleConfigProto& config);
};
} // namespace shardok::ai_battle_simulator
} // namespace ai_battle_simulator
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_CONFIG_HPP
@@ -8,7 +8,6 @@
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <utility>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
@@ -28,7 +27,8 @@
#include "src/main/protobuf/net/eagle0/shardok/common/player_info.pb.h"
#pragma clang diagnostic pop
namespace shardok::ai_battle_simulator {
namespace shardok {
namespace ai_battle_simulator {
namespace {
@@ -330,6 +330,9 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
// Load map
auto hexMapProto = LoadMap(config_.map_name());
// Create player info protos
std::vector<net::eagle0::shardok::common::PlayerInfo> playerInfoProtos;
// Attacker info
net::eagle0::shardok::common::PlayerInfo attackerInfo;
attackerInfo.set_player_id(ATTACKER_ID);
@@ -339,6 +342,7 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
net::eagle0::shardok::common::VICTORY_CONDITION_LAST_PLAYER_STANDING);
attackerInfo.add_victory_conditions(
net::eagle0::shardok::common::VICTORY_CONDITION_HOLDS_CRITICAL_TILES);
playerInfoProtos.push_back(attackerInfo);
// Defender info
net::eagle0::shardok::common::PlayerInfo defenderInfo;
@@ -349,14 +353,10 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
net::eagle0::shardok::common::VICTORY_CONDITION_LAST_PLAYER_STANDING);
defenderInfo.add_victory_conditions(
net::eagle0::shardok::common::VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS);
std::vector<net::eagle0::shardok::common::PlayerInfo> playerInfoProtos{
std::move(attackerInfo),
std::move(defenderInfo)};
playerInfoProtos.push_back(defenderInfo);
// Create units from config
std::vector<net::eagle0::shardok::storage::fb::Unit> units;
units.reserve(config_.attacker().units_size() + config_.defender().units_size());
// Attacker units
const int attackerUnitCount = config_.attacker().units_size();
@@ -450,7 +450,7 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
unit.mutable_opponent_knowledge()->Mutate(0, 0);
unit.mutable_opponent_knowledge()->Mutate(1, 0);
units.push_back(std::move(unit));
units.push_back(unit);
}
// Defender units
@@ -546,7 +546,7 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
unit.mutable_opponent_knowledge()->Mutate(0, 0);
unit.mutable_opponent_knowledge()->Mutate(1, 0);
units.push_back(std::move(unit));
units.push_back(unit);
}
// Create game state
@@ -914,7 +914,6 @@ BattleResult AiBattleSimulator::CreateResultFromGameState(
// Collect surviving units (for winner, or all units if draw)
const auto* units = state->units();
if (units) {
result.survivingUnits.reserve(units->size());
for (const auto* unit : *units) {
if (!unit) { continue; }
@@ -958,4 +957,5 @@ BattleResult AiBattleSimulator::CreateResultFromGameState(
return result;
}
} // namespace shardok::ai_battle_simulator
} // namespace ai_battle_simulator
} // namespace shardok
@@ -30,9 +30,7 @@ class ShardokEngine;
// Type alias for hex map
using HexMap = net::eagle0::shardok::storage::fb::HexMap;
} // namespace shardok
namespace shardok::ai_battle_simulator {
namespace ai_battle_simulator {
using BattleConfigProto = net::eagle0::shardok::ai_battle_simulator::BattleConfig;
@@ -148,6 +146,7 @@ private:
CreateResultFromGameState(const GameStateW& state, int totalRounds, int totalCommands) const;
};
} // namespace shardok::ai_battle_simulator
} // namespace ai_battle_simulator
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_SIMULATOR_HPP
@@ -88,7 +88,8 @@ void GenerateConfigFile(const std::string& outputPath) {
// Output to file
std::ofstream outFile(outputPath);
if (!outFile.is_open()) {
throw std::runtime_error("Failed to open output file: " + outputPath);
std::cerr << "Error: Failed to open output file: " << outputPath << "\n";
std::exit(1);
}
outFile << jsonString << "\n";
std::cout << "Sample config written to: " << outputPath << "\n";
@@ -294,7 +295,8 @@ BattleResult RunBattle(
if (!config) {
restoreOutput();
throw std::runtime_error("Failed to load config file");
std::cerr << "Error: Failed to load config file\n";
std::exit(1);
}
// Create simulator
@@ -326,8 +328,6 @@ int main(int argc, char* argv[]) {
std::vector<std::string> configPaths;
std::vector<std::string> statePaths;
configPaths.reserve(argc - 1);
statePaths.reserve(argc - 1);
std::string outputPath;
std::string tracePath;
std::string runLabel;
@@ -2,6 +2,7 @@
// Converts saved Eagle ShardokBattle/GameState protobuf blobs into AI battle configs.
//
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <optional>
@@ -145,9 +146,9 @@ int main(int argc, char *argv[]) {
} else if (arg.starts_with("--output=")) {
outputPath = arg.substr(9);
} else if (arg.starts_with("--seed=")) {
seed = std::stoi(arg.substr(7));
seed = std::atoi(arg.substr(7).c_str());
} else if (arg.starts_with("--max-rounds=")) {
maxRounds = std::stoi(arg.substr(13));
maxRounds = std::atoi(arg.substr(13).c_str());
} else if (arg.starts_with("--attacker-scoring=")) {
const auto scoring = ParseScoring(arg.substr(19));
if (!scoring.has_value()) { throw std::runtime_error("Unknown attacker scoring"); }
@@ -115,7 +115,6 @@ int main(int argc, char *argv[]) {
std::string resultPath;
std::string stateFbPath;
std::vector<std::string> actionViewPaths;
actionViewPaths.reserve(argc - 1);
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
@@ -146,9 +145,6 @@ int main(int argc, char *argv[]) {
std::vector<int> attackerFactions;
std::vector<int> defenderFactions;
std::vector<std::string> playerSides;
attackerFactions.reserve(battle.players_size());
defenderFactions.reserve(battle.players_size());
playerSides.reserve(battle.players_size());
bool aiVsAi = true;
for (const auto &player : battle.players()) {
if (player.eagle_fid() == 3 || player.eagle_fid() == 4) { aiVsAi = false; }
@@ -191,8 +187,7 @@ int main(int argc, char *argv[]) {
}
std::cout << "],\"has_game_status\":" << (hasGameStatus ? "true" : "false") << "}\n";
return 0;
}
if (!resultPath.empty()) {
} else if (!resultPath.empty()) {
ActionResult result;
if (!result.ParseFromString(ReadFile(resultPath))) {
throw std::runtime_error("Failed to parse Shardok action result proto");
@@ -4,10 +4,9 @@
#include "AIPerformanceRunner.hpp"
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include "PerformanceTestGameStateBuilder.hpp"
@@ -48,7 +47,7 @@ auto CompletionReasonToString(EvaluationCompletionReason reason) -> std::string
/**
* Parse command line arguments into a configuration struct.
*/
auto ParseCommandLineArgs(int argc, char* argv[]) -> std::optional<PerformanceTestConfig> {
auto ParseCommandLineArgs(int argc, char* argv[]) -> PerformanceTestConfig {
PerformanceTestConfig config;
for (int i = 1; i < argc; ++i) {
@@ -64,7 +63,7 @@ auto ParseCommandLineArgs(int argc, char* argv[]) -> std::optional<PerformanceTe
<< " --defender=BOOL AI is defender (default: false)\n"
<< " --verbose Enable verbose output\n"
<< " --help, -h Show this help message\n";
return std::nullopt;
std::exit(0);
} else if (arg.starts_with("--map=")) {
config.mapName = arg.substr(6);
} else if (arg.starts_with("--turns=")) {
@@ -75,8 +74,9 @@ auto ParseCommandLineArgs(int argc, char* argv[]) -> std::optional<PerformanceTe
} else if (arg == "--verbose") {
config.verbose = true;
} else {
throw std::invalid_argument(
"Unknown argument: " + arg + "\nUse --help for usage information.");
std::cerr << "Unknown argument: " << arg << "\n";
std::cerr << "Use --help for usage information.\n";
std::exit(1);
}
}
@@ -96,9 +96,7 @@ int main(int argc, char* argv[]) {
std::cout << "==============================\n";
// Parse command line arguments
const auto parsedConfig = ParseCommandLineArgs(argc, argv);
if (!parsedConfig.has_value()) { return 0; }
const auto& config = parsedConfig.value();
auto config = ParseCommandLineArgs(argc, argv);
if (config.verbose) {
std::cout << "Configuration:\n";
@@ -165,7 +163,6 @@ int main(int argc, char* argv[]) {
std::cout << "Running AI performance test for " << config.numTurns << " turns...\n";
std::vector<AIPerformanceMetrics> metrics;
if (config.numTurns > 0) { metrics.reserve(config.numTurns); }
for (int turn = 0; turn < config.numTurns; ++turn) {
// Check if AI can make a move
@@ -4,9 +4,7 @@
#include "PerformanceTestGameStateBuilder.hpp"
#include <algorithm>
#include <filesystem>
#include <utility>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai_testing_common/GameSettingsFactory.hpp"
@@ -55,7 +53,6 @@ auto PerformanceTestGameStateBuilder::CreateCustomTestGameState(
// Create player info protos
std::vector<net::eagle0::shardok::common::PlayerInfo> playerInfoProtos;
playerInfoProtos.reserve(2);
// AI player
net::eagle0::shardok::common::PlayerInfo aiPlayerInfo;
@@ -71,7 +68,7 @@ auto PerformanceTestGameStateBuilder::CreateCustomTestGameState(
aiPlayerInfo.add_victory_conditions(
net::eagle0::shardok::common::VICTORY_CONDITION_HOLDS_CRITICAL_TILES);
}
playerInfoProtos.push_back(std::move(aiPlayerInfo));
playerInfoProtos.push_back(aiPlayerInfo);
// Human player
net::eagle0::shardok::common::PlayerInfo humanPlayerInfo;
@@ -87,16 +84,13 @@ auto PerformanceTestGameStateBuilder::CreateCustomTestGameState(
humanPlayerInfo.add_victory_conditions(
net::eagle0::shardok::common::VICTORY_CONDITION_HOLDS_CRITICAL_TILES);
}
playerInfoProtos.push_back(std::move(humanPlayerInfo));
playerInfoProtos.push_back(humanPlayerInfo);
// Create units
const int clampedAiUnitCount = std::clamp(aiUnitCount, 0, 6);
const int clampedHumanUnitCount = std::clamp(humanUnitCount, 0, 6);
std::vector<net::eagle0::shardok::storage::fb::Unit> units;
units.reserve(clampedAiUnitCount + clampedHumanUnitCount);
// Create AI units in reserve (location -1, -1)
for (int i = 0; i < clampedAiUnitCount; ++i) {
for (int i = 0; i < aiUnitCount && i < 6; ++i) {
units.push_back(AddGenericUnit(
AI_PLAYER_ID,
i, // Unit ID
@@ -107,10 +101,10 @@ auto PerformanceTestGameStateBuilder::CreateCustomTestGameState(
}
// Create human units in reserve (location -1, -1)
for (int i = 0; i < clampedHumanUnitCount; ++i) {
for (int i = 0; i < humanUnitCount && i < 6; ++i) {
units.push_back(AddGenericUnit(
HUMAN_PLAYER_ID,
clampedAiUnitCount + i, // Unit ID starting after generated AI units
aiUnitCount + i, // Unit ID starting aiUnitCount
net::eagle0::shardok::storage::fb::Coords(-1, -1), // Reserve location
NO_PROFESSION,
HEAVY_INFANTRY_BATTALION_TYPE,
@@ -241,4 +235,4 @@ auto PerformanceTestGameStateBuilder::AddGenericUnit(
return unit;
}
} // namespace shardok
} // namespace shardok
@@ -7,7 +7,8 @@
#include "src/main/cpp/net/eagle0/common/mcts/abstract/MCTSTypes.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.hpp"
namespace shardok::ai_testing_common {
namespace shardok {
namespace ai_testing_common {
auto AIClientFactory::Create(
PlayerId playerId,
@@ -29,4 +30,5 @@ auto AIClientFactory::Create(
mctsConfig);
}
} // namespace shardok::ai_testing_common
} // namespace ai_testing_common
} // namespace shardok
@@ -8,7 +8,8 @@
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
#include "src/main/flatbuffer/net/eagle0/shardok/storage/game_state.hpp"
namespace shardok::ai_testing_common {
namespace shardok {
namespace ai_testing_common {
auto GamePhaseRunner::RunSetupPhase(
ShardokEngine& engine,
@@ -57,4 +58,5 @@ auto GamePhaseRunner::RunSingleTurn(ShardokEngine& engine, PlayerId playerId, Sh
return true;
}
} // namespace shardok::ai_testing_common
} // namespace ai_testing_common
} // namespace shardok
@@ -9,7 +9,8 @@
#include "src/main/cpp/net/eagle0/common/byte_vector.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/BattalionTypeRegistrar.hpp"
namespace shardok::ai_testing_common {
namespace shardok {
namespace ai_testing_common {
auto GameSettingsFactory::CreateDefault() -> GameSettingsSPtr {
auto settings = std::make_shared<GameSettings>();
@@ -30,4 +31,5 @@ auto GameSettingsFactory::CreateDefault() -> GameSettingsSPtr {
return settings;
}
} // namespace shardok::ai_testing_common
} // namespace ai_testing_common
} // namespace shardok
@@ -10,7 +10,8 @@
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"
namespace shardok::ai_testing_common {
namespace shardok {
namespace ai_testing_common {
/**
* Factory class for creating GameSettings instances with standard configuration.
@@ -32,6 +33,7 @@ public:
static auto CreateDefault() -> GameSettingsSPtr;
};
} // namespace shardok::ai_testing_common
} // namespace ai_testing_common
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_TESTING_COMMON_GAME_SETTINGS_FACTORY_HPP
@@ -24,6 +24,11 @@
namespace shardok {
using net::eagle0::common::ScopedShardokLatencyTrace;
using std::condition_variable;
using std::mutex;
using std::scoped_lock;
using std::unique_lock;
using std::weak_ptr;
using net::eagle0::shardok::common::GameStatus;
@@ -61,9 +66,9 @@ public:
[[nodiscard]] auto what() const noexcept -> const char * override { return message.c_str(); }
};
std::vector<std::shared_ptr<ShardokAIClient>> ShardokGameController::MakeAIClients(
const std::unique_ptr<ShardokEngine> &e) {
std::vector<std::shared_ptr<ShardokAIClient>> aic;
vector<shared_ptr<ShardokAIClient>> ShardokGameController::MakeAIClients(
const unique_ptr<ShardokEngine> &e) {
vector<shared_ptr<ShardokAIClient>> aic;
mcts::MCTSConfig mctsConfig;
mctsConfig.backpropagationPolicy = mcts::MCTSBackpropagationPolicy::MINIMAX;
@@ -73,7 +78,6 @@ std::vector<std::shared_ptr<ShardokAIClient>> ShardokGameController::MakeAIClien
// Check if all players are AI - if so, use faster time budgets
const auto &playerInfos = e->GetPlayerInfos();
aic.reserve(playerInfos.size());
const bool isAllAiBattle =
std::ranges::all_of(playerInfos, [](const auto &pi) { return pi.is_ai(); });
@@ -103,7 +107,7 @@ std::vector<std::shared_ptr<ShardokAIClient>> ShardokGameController::MakeAIClien
void ShardokGameController::LockedNotifyClients() const { updateCondition.notify_all(); }
auto ShardokGameController::LockedAIClientForPid(PlayerId pid) const
-> std::shared_ptr<ShardokAIClient> {
-> shared_ptr<ShardokAIClient> {
const auto it = std::ranges::find_if(aiClients, [pid](const auto &client) {
return client->GetPlayerId() == pid;
});
@@ -116,7 +120,7 @@ void ShardokGameController::DoAIThread() {
while (aiThreadKeepGoing) {
// Phase 1: Gather data for AI decision (brief lock)
std::shared_ptr<ShardokAIClient> aiClient;
shared_ptr<ShardokAIClient> aiClient;
PlayerId playerId;
GameSettingsSPtr settings;
net::eagle0::shardok::api::GameStateView gsv;
@@ -124,7 +128,7 @@ void ShardokGameController::DoAIThread() {
size_t expectedHistoryCount;
{
std::unique_lock lk(masterLock);
unique_lock lk(masterLock);
if (engine->GameIsOver()) {
aiThreadKeepGoing = false;
@@ -168,7 +172,7 @@ void ShardokGameController::DoAIThread() {
// Phase 3: Post the command (brief lock)
{
std::unique_lock lk(masterLock);
unique_lock lk(masterLock);
// Verify state hasn't changed while we were thinking
if (engine->GetUnfilteredHistoryCount() != expectedHistoryCount) {
@@ -224,7 +228,7 @@ void ShardokGameController::DoAIThread() {
ShardokGameController::~ShardokGameController() {
{
std::unique_lock lk(masterLock);
unique_lock lk(masterLock);
aiThreadKeepGoing = false;
aiCondition.notify_one();
}
@@ -232,7 +236,7 @@ ShardokGameController::~ShardokGameController() {
}
void CheckFactionId(
const std::unique_ptr<ShardokEngine> &engine,
const unique_ptr<ShardokEngine> &engine,
const PlayerId shardokPlayerId,
const int eagleFactionId) {
bool found = false;
@@ -252,7 +256,7 @@ void ShardokGameController::PostCommand(
const int eagleFactionId,
const int64_t token,
const int64_t commandIndex,
const std::optional<int32_t> roll) {
const optional<int32_t> roll) {
ScopedShardokLatencyTrace totalTrace(
"controller_post_command_total",
cachedGameId,
@@ -260,7 +264,7 @@ void ShardokGameController::PostCommand(
{"eagle_faction_id", std::to_string(eagleFactionId)},
{"token", std::to_string(token)},
{"command_index", std::to_string(commandIndex)}});
std::scoped_lock<std::mutex> guard(masterLock);
scoped_lock<mutex> guard(masterLock);
CheckFactionId(engine, shardokPlayerId, eagleFactionId);
@@ -299,7 +303,7 @@ void ShardokGameController::PostPlacementCommands(
const PlayerId shardokPlayerId,
const int eagleFactionId,
const int64_t token,
const std::vector<UnitPlacementInfo> &infos) {
const vector<UnitPlacementInfo> &infos) {
ScopedShardokLatencyTrace totalTrace(
"controller_post_placement_commands_total",
cachedGameId,
@@ -308,7 +312,7 @@ void ShardokGameController::PostPlacementCommands(
{"token", std::to_string(token)},
{"placement_count", std::to_string(infos.size())}});
{
std::scoped_lock<std::mutex> guard(masterLock);
scoped_lock<mutex> guard(masterLock);
CheckFactionId(engine, shardokPlayerId, eagleFactionId);
@@ -352,7 +356,7 @@ void ShardokGameController::PostPlacementCommands(
}
auto ShardokGameController::GetCurrentGameStateBytes() -> byte_vector {
std::scoped_lock<std::mutex> guard(masterLock);
scoped_lock<mutex> guard(masterLock);
return engine->GetCurrentGameStateBytes();
}
@@ -364,9 +368,9 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
AllUpdates updates{};
incomingRegistrations++;
std::vector<net::eagle0::shardok::storage::ShardokActionWithResultingState> awrs;
vector<net::eagle0::shardok::storage::ShardokActionWithResultingState> awrs;
{
std::unique_lock<std::mutex> guard(masterLock);
unique_lock<mutex> guard(masterLock);
updates.newUnfilteredCount = engine->GetUnfilteredHistoryCount();
@@ -396,7 +400,7 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
auto fid = pi.eagle_faction_id();
std::vector<net::eagle0::shardok::api::ActionResultView> actionResultViews;
vector<net::eagle0::shardok::api::ActionResultView> actionResultViews;
{
ScopedShardokLatencyTrace trace(
"engine_filter_new_results",
@@ -407,7 +411,7 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
actionResultViews = engine->FilterNewResults(pi.player_id(), startingActionId);
}
std::shared_ptr<AvailableCommands> acs = nullptr;
shared_ptr<AvailableCommands> acs = nullptr;
const auto availableCommands = [&] {
ScopedShardokLatencyTrace trace(
@@ -426,11 +430,9 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
if (!availableCommands.empty() || !previewCommands.empty()) {
acs = std::make_shared<AvailableCommands>();
acs->mutable_current_command()->Reserve(availableCommands.size());
*acs->mutable_current_command() = {
std::begin(availableCommands),
std::end(availableCommands)};
acs->mutable_preview_command()->Reserve(previewCommands.size());
*acs->mutable_preview_command() = {
std::begin(previewCommands),
std::end(previewCommands)};
@@ -481,7 +483,6 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
// Extract battle progress data from the FlatBuffer game state
const auto *gs = engine->GetCurrentGameState().Get();
updates.currentRound = gs->current_round();
updates.playerTroopCounts.reserve(gs->player_infos()->size());
for (const auto *player : *gs->player_infos()) {
int32_t troopCount = 0;
for (const auto *unit : *gs->units()) {
@@ -503,37 +504,36 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
}
auto ShardokGameController::GameIsOver() -> bool {
std::scoped_lock<std::mutex> guard(masterLock);
scoped_lock<mutex> guard(masterLock);
const auto &gameStatus = engine->GetGameStatus();
return ShardokEngine::GameIsOver(gameStatus);
}
auto ShardokGameController::GameOverStatus() -> net::eagle0::shardok::common::GameStatus {
std::scoped_lock<std::mutex> guard(masterLock);
scoped_lock<mutex> guard(masterLock);
return fb::ToProto(engine->GetGameStatus());
}
auto ShardokGameController::EndGameUnits()
-> std::vector<net::eagle0::shardok::storage::ResolvedUnit> {
std::scoped_lock<std::mutex> guard(masterLock);
auto ShardokGameController::EndGameUnits() -> vector<net::eagle0::shardok::storage::ResolvedUnit> {
scoped_lock<mutex> guard(masterLock);
return engine->EndGameUnits();
}
auto ShardokGameController::ResolvedPlayerInfos()
-> std::vector<net::eagle0::shardok::common::PlayerInfo> {
std::scoped_lock<std::mutex> guard(masterLock);
-> vector<net::eagle0::shardok::common::PlayerInfo> {
scoped_lock<mutex> guard(masterLock);
return engine->GetPlayerInfos();
}
void ShardokGameController::RegisterSubscriber(std::shared_ptr<StreamSubscriber> subscriber) {
std::scoped_lock<std::mutex> guard(subscriberLock);
scoped_lock<mutex> guard(subscriberLock);
subscribers.push_back(subscriber);
}
void ShardokGameController::UnregisterSubscriber(const StreamSubscriber *subscriber) {
std::scoped_lock<std::mutex> guard(subscriberLock);
scoped_lock<mutex> guard(subscriberLock);
subscribers.erase(
std::remove_if(
subscribers.begin(),
@@ -556,7 +556,7 @@ auto ShardokGameController::WaitForUpdatesAndPush(
GameOverInfo gameOverInfo{};
{
std::unique_lock<std::mutex> guard(masterLock);
unique_lock<mutex> guard(masterLock);
// Wait for updates, game over, or AI thread failure
updateCondition.wait(guard, [this, lastPushedActionId] {
@@ -10,14 +10,9 @@
#define EAGLE0_SHARDOK_CONTROLLER_SHARDOK_GAME_CONTROLLER_HPP
#include <atomic>
#include <condition_variable>
#include <ctime>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <utility>
#include <vector>
@@ -31,27 +26,31 @@
namespace shardok {
using net::eagle0::shardok::api::ActionResultView;
using net::eagle0::shardok::api::AvailableCommands;
using std::optional;
using std::shared_ptr;
using std::unique_ptr;
using std::weak_ptr;
// Forward declaration
class ShardokGameController;
/// Info about a game that has ended, for notifying subscribers
struct GameOverInfo {
std::vector<net::eagle0::shardok::common::PlayerInfo> playerInfos;
std::vector<net::eagle0::shardok::storage::ResolvedUnit> endGameUnits;
vector<net::eagle0::shardok::common::PlayerInfo> playerInfos;
vector<net::eagle0::shardok::storage::ResolvedUnit> endGameUnits;
net::eagle0::shardok::common::GameStatus gameStatus;
};
/// Updates for a single player (includes faction ID for client routing)
struct OnePlayerUpdates {
int32_t eagleFactionId;
std::vector<ActionResultView> resultViews;
std::shared_ptr<AvailableCommands> availableCommands;
vector<ActionResultView> resultViews;
shared_ptr<AvailableCommands> availableCommands;
OnePlayerUpdates(
const int32_t fid,
const std::vector<ActionResultView>& arvs,
const std::shared_ptr<AvailableCommands>& acs)
const vector<ActionResultView>& arvs,
const shared_ptr<AvailableCommands>& acs)
: eagleFactionId(fid),
resultViews(arvs),
availableCommands(acs) {}
@@ -71,12 +70,12 @@ public:
/// Called when new game updates are available
virtual void OnUpdate(
const std::vector<ActionResult>& mainResults,
const std::vector<OnePlayerUpdates>& filteredResults,
const vector<ActionResult>& mainResults,
const vector<OnePlayerUpdates>& filteredResults,
int32_t newUnfilteredCount,
const byte_vector& currentGameState,
int32_t currentRound,
const std::vector<std::pair<int32_t, int32_t>>& playerTroopCounts) = 0;
const vector<std::pair<int32_t, int32_t>>& playerTroopCounts) = 0;
/// Called when the game ends
virtual void OnGameOver(const GameOverInfo& info) = 0;
@@ -102,52 +101,51 @@ private:
mutable std::mutex subscriberLock{};
std::vector<std::weak_ptr<StreamSubscriber>> subscribers{};
std::string serializedRequest;
string serializedRequest;
std::unique_ptr<ShardokEngine> engine;
unique_ptr<ShardokEngine> engine;
// Cached immutable data - safe to access without lock since it never changes after construction
const GameId cachedGameId;
std::atomic_int incomingRegistrations = 0;
const std::string mapName;
const std::string logFilePath;
const string mapName;
const string logFilePath;
// AI thread error state - written once by AI thread before setting atomic flag
std::atomic<bool> aiThreadFailed{false};
std::string aiThreadErrorMessage;
std::vector<std::shared_ptr<ShardokAIClient>> aiClients;
vector<shared_ptr<ShardokAIClient>> aiClients;
std::thread aiThread;
// Each entry maps an Eagle faction id of a non-participant watcher to the
// Shardok pids of participants whose allies list named this watcher. Used
// when generating per-watcher filtered views in GetUpdates.
const std::vector<std::pair<int32_t, std::vector<PlayerId>>> watcherAllies;
const vector<std::pair<int32_t, vector<PlayerId>>> watcherAllies;
static auto MakeLogFilePath() -> std::string {
const std::time_t timer = std::time(nullptr);
static auto MakeLogFilePath() -> string {
const time_t timer = time(nullptr);
char buf[255];
std::strftime(buf, 255, "/tmp/playthrough_%Y_%m_%d_%H%M%S.log", std::localtime(&timer));
return std::string(buf);
strftime(buf, 255, "/tmp/playthrough_%Y_%m_%d_%H%M%S.log", localtime(&timer));
return string(buf);
}
void LockedNotifyClients() const;
auto LockedAIClientForPid(PlayerId pid) const -> std::shared_ptr<ShardokAIClient>;
auto LockedAIClientForPid(PlayerId pid) const -> shared_ptr<ShardokAIClient>;
static std::vector<std::shared_ptr<ShardokAIClient>> MakeAIClients(
const std::unique_ptr<ShardokEngine>& e);
static vector<shared_ptr<ShardokAIClient>> MakeAIClients(const unique_ptr<ShardokEngine>& e);
void DoAIThread();
public:
ShardokGameController(
std::unique_ptr<ShardokEngine> e,
std::string mapName,
std::string serializedRequest = "",
std::vector<std::pair<int32_t, std::vector<PlayerId>>> watcherAllies = {})
unique_ptr<ShardokEngine> e,
string mapName,
string serializedRequest = "",
vector<std::pair<int32_t, vector<PlayerId>>> watcherAllies = {})
: serializedRequest(std::move(serializedRequest)),
engine(std::move(e)),
cachedGameId(engine->GetGameId()),
@@ -162,40 +160,40 @@ public:
~ShardokGameController();
auto SerializedRequest() const -> std::string { return serializedRequest; }
auto SerializedRequest() const -> string { return serializedRequest; }
void PostCommand(
PlayerId shardokPlayerId,
int eagleFactionId,
int64_t token,
int64_t commandIndex,
std::optional<int32_t> roll);
optional<int32_t> roll);
void PostPlacementCommands(
PlayerId shardokPlayerId,
int eagleFactionId,
int64_t token,
const std::vector<UnitPlacementInfo>& infos);
const vector<UnitPlacementInfo>& infos);
struct AllUpdates {
std::vector<ActionResult> mainResults;
std::vector<OnePlayerUpdates> filteredResults;
vector<ActionResult> mainResults;
vector<OnePlayerUpdates> filteredResults;
int32_t newUnfilteredCount;
byte_vector currentGameState;
int32_t currentRound = 0;
std::vector<std::pair<int32_t, int32_t>> playerTroopCounts; // (playerId, troopCount)
vector<std::pair<int32_t, int32_t>> playerTroopCounts; // (playerId, troopCount)
};
auto GetUpdates(int64_t startingActionId) -> AllUpdates;
auto GetCurrentGameStateBytes() -> byte_vector;
auto GameIsOver() -> bool;
auto GameOverStatus() -> net::eagle0::shardok::common::GameStatus;
auto ResolvedPlayerInfos() -> std::vector<net::eagle0::shardok::common::PlayerInfo>;
auto EndGameUnits() -> std::vector<net::eagle0::shardok::storage::ResolvedUnit>;
auto ResolvedPlayerInfos() -> vector<net::eagle0::shardok::common::PlayerInfo>;
auto EndGameUnits() -> vector<net::eagle0::shardok::storage::ResolvedUnit>;
[[nodiscard]] auto GetGameId() const -> GameId { return cachedGameId; }
[[nodiscard]] auto GetLogFilePath() const -> std::string { return logFilePath; }
[[nodiscard]] auto GetLogFilePath() const -> string { return logFilePath; }
[[nodiscard]] auto HasAIThreadError() const -> bool { return aiThreadFailed.load(); }
[[nodiscard]] auto GetAIThreadError() const -> std::string { return aiThreadErrorMessage; }
@@ -9,7 +9,6 @@
#include "AvailableCommandsFactory.hpp"
#include <algorithm>
#include <iterator>
#include <memory>
#include <ranges>
@@ -23,6 +22,8 @@
#include "src/main/cpp/net/eagle0/shardok/library/view_filters/GameStateFilter.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/view_filters/GameStateGuesser.hpp"
using std::make_shared;
namespace shardok {
class AvailableCommandsFactoryImpl : public AvailableCommandsFactory {
@@ -32,21 +33,23 @@ private:
static auto CannotBecomeOutlaw(const GameStateW &gameState, PlayerId pid) -> bool {
if (pid == UNCONTROLLED_PLAYER_ID) return false;
const auto &player = std::ranges::find_if(
*gameState->player_infos(),
const auto &player = std::find_if(
begin(*gameState->player_infos()),
end(*gameState->player_infos()),
[pid](const PlayerInfoFb *pi) { return pi->player_id() == pid; });
return (player != std::end(*gameState->player_infos()) && player->cannot_become_outlaw());
return (player != end(*gameState->player_infos()) && player->cannot_become_outlaw());
}
static auto IsAttacker(const GameStateW &gameState, PlayerId pid) -> bool {
if (pid == UNCONTROLLED_PLAYER_ID) return false;
const auto &player = std::ranges::find_if(
*gameState->player_infos(),
const auto &player = std::find_if(
begin(*gameState->player_infos()),
end(*gameState->player_infos()),
[pid](const PlayerInfoFb *pi) { return pi->player_id() == pid; });
return (player == std::end(*gameState->player_infos()) || !player->is_defender());
return (player == end(*gameState->player_infos()) || !player->is_defender());
}
const std::vector<std::shared_ptr<const CommandFactory>> commandFactories;
@@ -95,7 +98,7 @@ auto AvailableCommandsFactoryImpl::GetPlayerSetupCommands(
gameState,
reinforcementPositions);
return std::make_shared<CommandList>(previewCommands);
return make_shared<CommandList>(previewCommands);
}
void AvailableCommandsFactoryImpl::AddAvailableCommandsForOneUnit(
@@ -115,7 +118,6 @@ void AvailableCommandsFactoryImpl::AddAvailableCommandsForOneUnit(
const bool isEligibleCharger = unit->unit_id() == gameState->eligible_charger_id();
std::vector<UnitId> possibleChargees{};
possibleChargees.reserve(gameState->possible_chargee_ids()->size());
for (const UnitId cid : *gameState->possible_chargee_ids()) {
if (cid != -1) { possibleChargees.push_back(cid); }
}
@@ -194,9 +196,11 @@ auto AvailableCommandsFactoryImpl::GetAvailableCommands(
const std::optional<std::vector<Coords>> &reinforcementPositions) const -> CommandListSPtr {
CommandList commands{};
std::vector<AdjacentTile> adjacentTiles;
playerSetupCommandFactory
.AddAvailablePlayerSetupCommands(commands, playerId, gameState, reinforcementPositions);
if (!commands.empty()) return std::make_shared<CommandList>(commands);
if (!commands.empty()) return make_shared<CommandList>(commands);
for (const Unit *unit : *gameState->units()) {
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
@@ -236,7 +240,6 @@ auto AvailableCommandsFactoryImpl::GetAvailableCommands(
/* onlyFollowUps=*/true);
std::unordered_set<CommandType> followUpTypes{};
followUpTypes.reserve(followUpCommands.size());
for (const auto &followUpCmd : followUpCommands) {
if (followUpCmd->CanBeFollowUp()) {
followUpTypes.insert(followUpCmd->GetCommandType());
@@ -249,7 +252,7 @@ auto AvailableCommandsFactoryImpl::GetAvailableCommands(
}
if (commands.empty()) return nullptr;
return std::make_shared<CommandList>(commands);
return make_shared<CommandList>(commands);
}
} // namespace shardok
@@ -189,12 +189,12 @@ struct BattalionType {
}
[[nodiscard]] auto GetCostToEnterTerrain(const TerrainProto &terrain) const -> ActionCost {
if (HasBridge(terrain.modifier())) {
return {ActionCost::standard,
if (HasBridge(terrain.modifier()))
return ActionCost(
ActionCost::standard,
ActionPoints(std::ceil(
100.0 * minimumCostToEnterBridge /
terrain.modifier().bridge().integrity().value()))};
}
terrain.modifier().bridge().integrity().value())));
if (IsFrozen(terrain.modifier())) return costToEnterIce;
auto terrainCost = GetCostToEnterTerrainType(terrain.type());
@@ -208,11 +208,11 @@ struct BattalionType {
[[nodiscard]] auto GetCostToEnterTerrain(
const net::eagle0::shardok::storage::fb::Terrain_::Type terrainType,
const net::eagle0::shardok::storage::fb::TileModifier &tm) const -> ActionCost {
if (tm.bridge().present()) {
return {ActionCost::standard,
if (tm.bridge().present())
return ActionCost(
ActionCost::standard,
ActionPoints(
std::ceil(100.0 * minimumCostToEnterBridge / tm.bridge().integrity()))};
}
std::ceil(100.0 * minimumCostToEnterBridge / tm.bridge().integrity())));
if (tm.ice().present()) return costToEnterIce;
auto terrainCost = GetCostToEnterTerrainType(terrainType);
@@ -261,8 +261,13 @@ struct BattalionType {
trainingDifferenceMultiplierForStunChanceOnCharge * trainingDifference;
OtherFactor trainingFactor =
MakeOtherFactor(static_cast<int16_t>(trainingValue), "training difference");
const std::array otherFactors = {trainingFactor};
return MakeOdds(static_cast<int16_t>(baseStunChanceOnCharge), 0, 0, 0, {}, otherFactors);
return MakeOdds(
static_cast<int16_t>(baseStunChanceOnCharge),
0,
0,
0,
{},
{trainingFactor});
}
[[nodiscard]] auto GetDailyFoodCostPerTroop() const -> double {
@@ -44,7 +44,7 @@ inline constexpr DamageType DamageType_aessence = DamageType::DamageType_aessenc
inline constexpr DamageType DamageType_impact = DamageType::DamageType_impact;
inline constexpr int NUM_DAMAGE_TYPES = static_cast<int>(DamageType::NUM_DAMAGE_TYPES);
[[nodiscard]] static inline bool DamageTypeGetsTerrainDefense(const DamageType type) {
static inline bool DamageTypeGetsTerrainDefense(const DamageType type) {
switch (type) {
case DamageType_slashing:
case DamageType_puncturing:
@@ -132,13 +132,11 @@ public:
return damageByType.at(PenetratingIndex(type));
};
[[nodiscard]] bool operator==(const CombatDamage& cmpr) const {
return damageByType == cmpr.damageByType;
}
bool operator==(const CombatDamage& cmpr) const { return damageByType == cmpr.damageByType; }
[[nodiscard]] bool operator!=(const CombatDamage& cmpr) const { return !(*this == cmpr); }
bool operator!=(const CombatDamage& cmpr) const { return !(*this == cmpr); }
[[nodiscard]] bool operator<=(const CombatDamage& cmpr) const {
bool operator<=(const CombatDamage& cmpr) const {
return std::ranges::equal(
damageByType,
cmpr.damageByType,
@@ -147,7 +145,7 @@ public:
});
}
[[nodiscard]] bool operator>=(const CombatDamage& cmpr) const {
bool operator>=(const CombatDamage& cmpr) const {
return std::ranges::equal(
damageByType,
cmpr.damageByType,
@@ -156,15 +154,11 @@ public:
});
}
[[nodiscard]] bool operator>(const CombatDamage& cmpr) const {
return ((*this >= cmpr) && !(*this == cmpr));
}
bool operator>(const CombatDamage& cmpr) const { return ((*this >= cmpr) && !(*this == cmpr)); }
[[nodiscard]] bool operator<(const CombatDamage& cmpr) const {
return ((*this <= cmpr) && !(*this == cmpr));
}
bool operator<(const CombatDamage& cmpr) const { return ((*this <= cmpr) && !(*this == cmpr)); }
[[nodiscard]] CombatDamage operator+(const CombatDamage& add) const {
CombatDamage operator+(const CombatDamage& add) const {
return Builder()
.SetSlashing(
GetNormalDamageOfType(DamageType_slashing) +
@@ -225,7 +219,7 @@ public:
.Build();
}
[[nodiscard]] CombatDamage operator*(const double factor) const {
CombatDamage operator*(const double factor) const {
return Builder()
.SetSlashing(GetNormalDamageOfType(DamageType_slashing) * factor)
.SetPuncturing(GetNormalDamageOfType(DamageType_puncturing) * factor)
@@ -448,7 +442,7 @@ public:
[[nodiscard]] auto ToBuilder() const -> Builder {
Builder builder = Builder();
for (int i = 0; i < static_cast<int>(NUM_DAMAGE_TYPES); ++i) {
for (int i = 0; i < static_cast<int>(NUM_DAMAGE_TYPES); i++) {
const auto type = static_cast<DamageType>(i);
builder.SetDamageByType(type, false, this->GetNormalDamageOfType(type));
builder.SetDamageByType(type, true, this->GetPenetratingDamageOfType(type));
@@ -457,9 +451,7 @@ public:
}
};
[[nodiscard]] inline CombatDamage operator*(const double factor, const CombatDamage& dmg) {
return dmg * factor;
}
inline CombatDamage operator*(const double factor, const CombatDamage& dmg) { return dmg * factor; }
} // namespace shardok
#endif // EAGLE0_SHARDOK_LIBRARY_COMBAT_DAMAGE_HPP
@@ -9,7 +9,6 @@
#include "FireUtils.hpp"
#include <algorithm>
#include <array>
#include "src/main/cpp/net/eagle0/shardok/library/map/TileModifier.hpp"
@@ -104,14 +103,14 @@ auto GetExtinguishOdds(
const int16_t windFactor = -weather->wind().speed_in_mph();
const std::vector<double> stats = {agility, agility, intelligence};
std::vector<OtherFactor> others;
if (ownTile) {
const std::array others = {
MakeOtherFactor(settings.Backing().extinguish_own_tile_buf(), "ownTile")};
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, others);
others.push_back(MakeOtherFactor(settings.Backing().extinguish_own_tile_buf(), "ownTile"));
}
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats);
const std::vector<double> stats = {agility, agility, intelligence};
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, others);
}
auto GetStartFireOdds(
@@ -129,13 +128,12 @@ auto GetStartFireOdds(
const int16_t windFactor = -currentWeather.wind().speed_in_mph();
const std::vector<double> stats = {agility, intelligence, intelligence};
if (isMage) {
const std::array otherFactors = {MakeOtherFactor(+20, "mage")};
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
}
std::vector<OtherFactor> otherFactors;
if (isMage) { otherFactors.push_back(MakeOtherFactor(+20, "mage")); }
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats);
const std::vector<double> stats = {agility, intelligence, intelligence};
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
}
} // namespace shardok
@@ -21,23 +21,19 @@ using WeatherFb = net::eagle0::shardok::storage::fb::Weather;
using TerrainProto = net::eagle0::shardok::common::Terrain;
using Terrain = net::eagle0::shardok::storage::fb::Terrain;
[[nodiscard]] auto PropensityByTerrain(const Terrain *terrain, const SettingsGetter &settings)
-> int;
auto PropensityByTerrain(const Terrain *terrain, const SettingsGetter &settings) -> int;
[[nodiscard]] auto PropensityByTerrain(const TerrainProto &terrain, const SettingsGetter &settings)
-> int;
auto PropensityByTerrain(const TerrainProto &terrain, const SettingsGetter &settings) -> int;
[[nodiscard]] auto PropensityByTerrain(
auto PropensityByTerrain(
const TerrainType &terrainType,
const TileModifierProto &modifier,
const SettingsGetter &settings) -> int;
[[nodiscard]] auto GetFireDamage(
int troops,
double openEndedPercentileRoll1,
double openEndedPercentileRoll2) -> CombatDamage;
auto GetFireDamage(int troops, double openEndedPercentileRoll1, double openEndedPercentileRoll2)
-> CombatDamage;
[[nodiscard]] auto GetExtinguishOdds(
auto GetExtinguishOdds(
const SettingsGetter &settings,
const Terrain *terrain,
const WeatherFb *currentWeather,
@@ -45,7 +41,7 @@ using Terrain = net::eagle0::shardok::storage::fb::Terrain;
double intelligence,
bool ownTile) -> PercentileRollOdds;
[[nodiscard]] auto GetStartFireOdds(
auto GetStartFireOdds(
const SettingsGetter &settings,
const Terrain &terrain,
const WeatherFb &currentWeather,
@@ -36,19 +36,25 @@ public:
using BaseType::BaseType;
// Default constructor
GameStateW() = default;
GameStateW() : BaseType() {}
// Copy constructor
GameStateW(const GameStateW& other) = default;
GameStateW(const GameStateW& other) : BaseType(other) {}
// Move constructor
GameStateW(GameStateW&& other) noexcept = default;
GameStateW(GameStateW&& other) noexcept : BaseType(std::move(other)) {}
// Copy assignment
GameStateW& operator=(const GameStateW& other) = default;
GameStateW& operator=(const GameStateW& other) {
BaseType::operator=(other);
return *this;
}
// Move assignment
GameStateW& operator=(GameStateW&& other) noexcept = default;
GameStateW& operator=(GameStateW&& other) noexcept {
BaseType::operator=(std::move(other));
return *this;
}
// Constructor from base type
GameStateW(const BaseType& base) : BaseType(base) {}
@@ -13,7 +13,7 @@
#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
namespace shardok {
double BonusFromStat(const double stat) { return pow(stat - 50.0, 3.0) / 5000.0; }
double BonusFromStat(const double stat) { return pow(((double)stat - 50.0), 3.0) / 5000.0; }
int16_t AverageBonusFromStats(const std::vector<double> &stats) {
double totalBonus = 0.0;
@@ -23,7 +23,7 @@ int16_t AverageBonusFromStats(const std::vector<double> &stats) {
}
int16_t GetSuccessChance(const PercentileRollOdds &odds) {
return static_cast<int16_t>(std::floor(
return int16_t(std::floor(
100.0 * RandomGenerator::ChanceOpenEndedPercentileAtOrAbove(100 - GetTotalOdds(odds)) +
0.5));
}
@@ -42,7 +42,7 @@ PercentileRollOdds MakeOdds(
const int weath,
const int wind,
const int statF,
const std::span<const OtherFactor> others) {
const std::vector<OtherFactor> &others) {
PercentileRollOdds odds;
odds.set_base_difficulty(base);
@@ -61,7 +61,7 @@ PercentileRollOdds MakeOdds(
const int weath,
const int wind,
const std::vector<double> &stats,
const std::span<const OtherFactor> others) {
const std::vector<OtherFactor> &others) {
return MakeOdds(base, terr, weath, wind, AverageBonusFromStats(stats), others);
}
@@ -9,7 +9,6 @@
#ifndef EAGLE0_SHARDOK_LIBRARY_PERCENTILE_ROLL_ODDS_HPP
#define EAGLE0_SHARDOK_LIBRARY_PERCENTILE_ROLL_ODDS_HPP
#include <span>
#include <string>
#include <vector>
@@ -34,7 +33,7 @@ PercentileRollOdds MakeOdds(
int weath = 0,
int wind = 0,
int statF = 0,
std::span<const OtherFactor> others = {});
const std::vector<OtherFactor> &others = std::vector<OtherFactor>());
PercentileRollOdds MakeOdds(
int base,
@@ -42,7 +41,7 @@ PercentileRollOdds MakeOdds(
int weath,
int wind,
const std::vector<double> &stats,
std::span<const OtherFactor> others = {});
const std::vector<OtherFactor> &others = std::vector<OtherFactor>());
int16_t GetTotalOdds(const PercentileRollOdds &odds);
@@ -42,7 +42,7 @@ private:
[[nodiscard]] virtual auto InternalExecute(
const GameStateW& currentState,
const std::shared_ptr<RandomGenerator>& generator) const -> std::vector<ActionResult> {
return InternalExecuteWithRoll(currentState, generator, std::nullopt);
return InternalExecuteWithRoll(currentState, generator, std::optional<int32_t>());
}
[[nodiscard]] virtual auto InternalExecuteWithRoll(
@@ -77,11 +77,11 @@ ShardokEngine::ShardokEngine(
ShardokEngine::ShardokEngine(
const GameSettingsSPtr &settings,
std::vector<Unit> &&unplacedUnorderedUnits,
vector<Unit> &&unplacedUnorderedUnits,
const HexMapProto &map,
const int month,
const GameId &existingGameId,
const std::vector<PlayerInfoProto> &playerInfos,
const vector<PlayerInfoProto> &playerInfos,
const bool trackHistory)
: gameSettings(settings),
settingsGetter(settings->GetGetter()),
@@ -106,7 +106,7 @@ ShardokEngine::ShardokEngine(
ApplyAndAddActionResult(setupStartAction);
}
void ShardokEngine::ApplyAndAddActionResults(const std::vector<ActionResultProto> &results) {
void ShardokEngine::ApplyAndAddActionResults(const vector<ActionResultProto> &results) {
for (const ActionResultProto &result : results) { ApplyAndAddActionResult(result); }
}
@@ -122,7 +122,7 @@ void ShardokEngine::ApplyAndAddActionResult(const ActionResultProto &result) {
ShardokEngine::ShardokEngine(
const GameSettingsSPtr &settings,
const std::vector<ShardokActionWithResultingState> &history,
const vector<ShardokActionWithResultingState> &history,
const bool trackHistory)
: gameSettings(settings),
settingsGetter(settings->GetGetter()),
@@ -156,11 +156,11 @@ auto ShardokEngine::GetGameStateAtStartOfAction(const ActionId startingActionId)
}
auto ShardokEngine::GetGameHistory(const ActionId lastUpdatedActionId) const
-> std::vector<ShardokActionWithResultingState> {
-> vector<ShardokActionWithResultingState> {
if (lastUpdatedActionId >= static_cast<int64_t>(actionHistory.size()) + startingHistoryCount) {
return std::vector<ShardokActionWithResultingState>();
return vector<ShardokActionWithResultingState>();
}
return std::vector<ShardokActionWithResultingState>(
return vector<ShardokActionWithResultingState>(
std::begin(actionHistory) + lastUpdatedActionId - startingHistoryCount,
std::end(actionHistory));
}
@@ -172,17 +172,14 @@ auto ShardokEngine::GetGameStateView(const PlayerId askingPlayer) const
[[nodiscard]] auto ShardokEngine::FilterNewResults(
PlayerId askingPlayer,
int64_t previousActionCount) const
-> std::vector<net::eagle0::shardok::api::ActionResultView> {
int64_t previousActionCount) const -> vector<net::eagle0::shardok::api::ActionResultView> {
if (previousActionCount < startingHistoryCount) {
throw ShardokInternalErrorException("Unable to fetch history before startingHistoryCount");
}
std::vector<ShardokActionWithResultingState> unfilteredHistory =
GetGameHistory(previousActionCount);
vector<ShardokActionWithResultingState> unfilteredHistory = GetGameHistory(previousActionCount);
std::vector<net::eagle0::shardok::api::ActionResultView> filteredHistory{};
filteredHistory.reserve(unfilteredHistory.size());
vector<net::eagle0::shardok::api::ActionResultView> filteredHistory{};
GameStateW startingState = (previousActionCount == 0)
? GameStateW{}
@@ -196,7 +193,9 @@ auto ShardokEngine::GetGameStateView(const PlayerId askingPlayer) const
GameState *previousStatePtr = nullptr;
GameStateView &previousView = startingView;
for (const ShardokActionWithResultingState &awrs : unfilteredHistory) {
for (vector<ShardokActionWithResultingState> newHistory =
{unfilteredHistory.begin(), unfilteredHistory.end()};
const ShardokActionWithResultingState &awrs : newHistory) {
GameStateView viewAfter = GameStateFilteredForPlayer(
settingsGetter,
GameStateW::FromByteString(awrs.state_after_fb()),
@@ -221,18 +220,16 @@ auto ShardokEngine::GetGameStateView(const PlayerId askingPlayer) const
[[nodiscard]] auto ShardokEngine::FilterNewResultsForWatcher(
const PlayerId askingPlayer,
const std::vector<PlayerId> &alliedPids,
const vector<PlayerId> &alliedPids,
const int64_t previousActionCount) const
-> std::vector<net::eagle0::shardok::api::ActionResultView> {
-> vector<net::eagle0::shardok::api::ActionResultView> {
if (previousActionCount < startingHistoryCount) {
throw ShardokInternalErrorException("Unable to fetch history before startingHistoryCount");
}
std::vector<ShardokActionWithResultingState> unfilteredHistory =
GetGameHistory(previousActionCount);
vector<ShardokActionWithResultingState> unfilteredHistory = GetGameHistory(previousActionCount);
std::vector<net::eagle0::shardok::api::ActionResultView> filteredHistory{};
filteredHistory.reserve(unfilteredHistory.size());
vector<net::eagle0::shardok::api::ActionResultView> filteredHistory{};
GameStateW startingState = (previousActionCount == 0)
? GameStateW{}
@@ -274,7 +271,7 @@ auto ShardokEngine::GetGameStateView(const PlayerId askingPlayer) const
}
auto ShardokEngine::GetFilteredGameHistory(const PlayerId askingPlayer) const
-> std::vector<net::eagle0::shardok::api::ActionResultView> {
-> vector<net::eagle0::shardok::api::ActionResultView> {
return FilterNewResults(askingPlayer, 0);
}
@@ -426,7 +423,7 @@ void ShardokEngine::ResumeAfterReinforcementPlacement(
void ShardokEngine::PostPlacementCommands(
const PlayerId player,
const std::vector<UnitPlacementInfo> &placementInfos,
const vector<UnitPlacementInfo> &placementInfos,
const std::shared_ptr<RandomGenerator> &randomGenerator) {
if (GetCurrentPlayerId() != player) {
throw ShardokClientErrorException("Posting for a player who's not the current player ID!");
@@ -443,7 +440,7 @@ void ShardokEngine::PostPlacementCommands(
reinforcementPositions);
// first make sure they're all valid and there are no duplicates
for (size_t i = 0; i < placementInfos.size(); ++i) {
for (size_t i = 0; i < placementInfos.size(); i++) {
const UnitPlacementInfo &pi = placementInfos[i];
const auto it = std::ranges::find_if(*placementCommands, [pi](const CommandSPtr &cmd) {
@@ -455,7 +452,7 @@ void ShardokEngine::PostPlacementCommands(
}
// check that we're not double-filling any location or double-placing any unit
for (size_t j = i + 1; j < placementInfos.size(); ++j) {
for (size_t j = i + 1; j < placementInfos.size(); j++) {
const UnitPlacementInfo &other = placementInfos[j];
if (pi.unitId == other.unitId)
@@ -474,8 +471,7 @@ void ShardokEngine::PostPlacementCommands(
return cmd->GetCommandProto().actor().value() == pi.unitId &&
cmd->GetCommandProto().target() == pi.location;
});
for (std::vector<ActionResult> onePlacementResults =
(*it)->Execute(gameState, randomGenerator);
for (vector<ActionResult> onePlacementResults = (*it)->Execute(gameState, randomGenerator);
const ActionResultProto &oneResult : onePlacementResults) {
HandleActionResult(oneResult, randomGenerator);
}
@@ -568,7 +564,7 @@ void ShardokEngine::PostActionUnchecked(
const CommandSPtr &command,
const std::shared_ptr<RandomGenerator> &randomGenerator,
const std::optional<int32_t> roll) {
for (const std::vector<ActionResultProto> results =
for (const vector<ActionResultProto> results =
command->CanUseClientRoll()
? command->ExecuteWithRoll(gameState, randomGenerator, roll)
: command->Execute(gameState, randomGenerator);
@@ -618,7 +614,7 @@ void ShardokEngine::HandleActionResult(
auto ShardokEngine::HandleUnitFallingIntoWater(
const Terrain *terrain,
const Unit *unit,
std::shared_ptr<RandomGenerator> randomGenerator) const -> std::vector<ActionResultProto> {
std::shared_ptr<RandomGenerator> randomGenerator) const -> vector<ActionResultProto> {
// Do nothing if this unit can travel across water
if (const auto &battType = settingsGetter.GetBattalionType(unit->battalion().type());
battType->GetCostToEnterTerrain(terrain).type != ActionCost::impossible) {
@@ -633,9 +629,8 @@ auto ShardokEngine::HandleUnitFallingIntoWater(
return fallAction.Execute(gameState, std::move(randomGenerator));
}
auto CommandProtosFromActionList(const CommandList &actionList) -> std::vector<CommandProto> {
std::vector<CommandProto> protos;
protos.reserve(actionList.size());
auto CommandProtosFromActionList(const CommandList &actionList) -> vector<CommandProto> {
vector<CommandProto> protos;
std::transform(
std::begin(actionList),
@@ -646,7 +641,7 @@ auto CommandProtosFromActionList(const CommandList &actionList) -> std::vector<C
return protos;
}
auto ShardokEngine::GetPreviewCommands(const PlayerId playerId) const -> std::vector<CommandProto> {
auto ShardokEngine::GetPreviewCommands(const PlayerId playerId) const -> vector<CommandProto> {
if (GameIsOver(GetGameStatus())) {
// The game is over
return {};
@@ -675,7 +670,7 @@ auto ShardokEngine::GetAvailableCommandsForAIPlayer(const PlayerId playerId) con
}
auto ShardokEngine::GetAvailableCommandProtos(const PlayerId playerId, const bool includeFollowUps)
const -> std::vector<CommandProto> {
const -> vector<CommandProto> {
if (GameIsOver(GetGameStatus())) {
// The game is over
return {};
@@ -708,8 +703,7 @@ auto ShardokEngine::UncachedGetAvailableCommands(
using SecuredByPlayerByUnitId = std::unordered_map<UnitId, PlayerId>;
auto SecuredByPlayerByUnitIdFromHistory(
const std::vector<ShardokActionWithResultingState> &actionHistory)
-> SecuredByPlayerByUnitId {
const vector<ShardokActionWithResultingState> &actionHistory) -> SecuredByPlayerByUnitId {
SecuredByPlayerByUnitId securedByPlayerByUnitId;
for (const auto &actionWithResultingState : actionHistory) {
for (const auto &resolvedUnit : actionWithResultingState.action_result().resolved_units()) {
@@ -726,7 +720,7 @@ auto SecuredByPlayerByUnitIdFromHistory(
}
void AddUnits(
std::vector<net::eagle0::shardok::storage::ResolvedUnit> &to,
vector<net::eagle0::shardok::storage::ResolvedUnit> &to,
const Units &from,
const SecuredByPlayerByUnitId &securedByPlayerByUnitId) {
for (const auto &unit : from) {
@@ -775,12 +769,11 @@ void AddUnits(
}
AddUnitToResolved(ru, *unit);
to.push_back(std::move(ru));
to.push_back(ru);
}
}
auto ShardokEngine::EndGameUnits() const
-> std::vector<net::eagle0::shardok::storage::ResolvedUnit> {
auto ShardokEngine::EndGameUnits() const -> vector<net::eagle0::shardok::storage::ResolvedUnit> {
if (const auto state = GetGameStatus()->state();
state != net::eagle0::shardok::storage::fb::GameStatus_::State_VICTORY) {
throw ShardokInternalErrorException(
@@ -789,8 +782,7 @@ auto ShardokEngine::EndGameUnits() const
const auto &gs = GetCurrentGameState();
std::vector<net::eagle0::shardok::storage::ResolvedUnit> endgameUnits;
endgameUnits.reserve(gs->units()->size());
vector<net::eagle0::shardok::storage::ResolvedUnit> endgameUnits;
AddUnits(endgameUnits, *gs->units(), SecuredByPlayerByUnitIdFromHistory(actionHistory));
return endgameUnits;
@@ -11,9 +11,7 @@
#include <flatbuffers/flatbuffers.h>
#include <memory>
#include <optional>
#include <vector>
#include "ShardokAction.hpp"
#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
@@ -29,6 +27,12 @@
#include "src/main/protobuf/net/eagle0/shardok/storage/action_with_resulting_state.pb.h"
namespace shardok {
using std::begin;
using std::end;
using std::optional;
using std::shared_ptr;
using std::vector;
using net::eagle0::shardok::api::UnitView;
using PlayerInfoProto = net::eagle0::shardok::common::PlayerInfo;
using net::eagle0::shardok::storage::ShardokActionWithResultingState;
@@ -39,14 +43,14 @@ class ShardokEngine {
private:
const GameSettingsSPtr gameSettings;
const SettingsGetter settingsGetter;
const std::shared_ptr<AvailableCommandsFactory> availableCommandsFactory;
const shared_ptr<AvailableCommandsFactory> availableCommandsFactory;
GameStateW gameState;
const bool trackHistory;
const int32_t startingHistoryCount = 0;
const GameStateW startingHistoryState;
mutable std::vector<ShardokActionWithResultingState> actionHistory;
mutable vector<ShardokActionWithResultingState> actionHistory;
const CoordsSet criticalTileCoords;
@@ -57,12 +61,12 @@ private:
void ApplyAndAddActionResult(const ActionResult &result);
void ApplyAndAddActionResults(const std::vector<ActionResult> &results);
void ApplyAndAddActionResults(const vector<ActionResult> &results);
[[nodiscard]] auto HandleUnitFallingIntoWater(
const Terrain *terrain,
const fb::Unit *unit,
std::shared_ptr<RandomGenerator> randomGenerator) const -> std::vector<ActionResult>;
std::shared_ptr<RandomGenerator> randomGenerator) const -> vector<ActionResult>;
void HandleActionResult(
const ActionResult &actionResult,
@@ -101,17 +105,17 @@ public:
// Constructor for a brand new game
ShardokEngine(
const GameSettingsSPtr &settings,
std::vector<Unit> &&unplacedUnorderedUnits,
vector<Unit> &&unplacedUnorderedUnits,
const HexMapProto &map,
int month,
const GameId &existingGameId,
const std::vector<PlayerInfoProto> &playerInfos,
const vector<PlayerInfoProto> &playerInfos,
bool trackHistory = true);
// Constructor for a game with history
ShardokEngine(
const GameSettingsSPtr &settings,
const std::vector<ShardokActionWithResultingState> &history,
const vector<ShardokActionWithResultingState> &history,
bool trackHistory = true);
// Copy constructor
@@ -130,12 +134,12 @@ public:
}
[[nodiscard]] auto EndGameUnits() const
-> std::vector<net::eagle0::shardok::storage::ResolvedUnit>; // Throws if the game is
// not over
-> vector<net::eagle0::shardok::storage::ResolvedUnit>; // Throws if the game is not
// over
// Controller API
[[nodiscard]] auto GetGameHistory(ActionId lastUpdatedActionId) const
-> std::vector<ShardokActionWithResultingState>;
-> vector<ShardokActionWithResultingState>;
[[nodiscard]] auto GetUnfilteredHistoryCount() const -> size_t {
return actionHistory.size() + startingHistoryCount;
@@ -146,7 +150,7 @@ public:
-> net::eagle0::shardok::api::GameStateView;
[[nodiscard]] auto FilterNewResults(PlayerId askingPlayer, int64_t previousActionCount) const
-> std::vector<net::eagle0::shardok::api::ActionResultView>;
-> vector<net::eagle0::shardok::api::ActionResultView>;
// Variant for non-participant watchers: askingPlayer is a sentinel (not in
// player_infos) and alliedPids names the Shardok pids whose units the
@@ -154,18 +158,18 @@ public:
// and hidden actions.
[[nodiscard]] auto FilterNewResultsForWatcher(
PlayerId askingPlayer,
const std::vector<PlayerId> &alliedPids,
const vector<PlayerId> &alliedPids,
int64_t previousActionCount) const
-> std::vector<net::eagle0::shardok::api::ActionResultView>;
-> vector<net::eagle0::shardok::api::ActionResultView>;
[[nodiscard]] auto GetFilteredGameHistory(PlayerId askingPlayer) const
-> std::vector<net::eagle0::shardok::api::ActionResultView>;
-> vector<net::eagle0::shardok::api::ActionResultView>;
[[nodiscard]] auto GetUnitById(PlayerId askingPlayer, UnitId unitId) const -> UnitView;
void PostPlacementCommands(
PlayerId player,
const std::vector<UnitPlacementInfo> &placementInfos,
const vector<UnitPlacementInfo> &placementInfos,
const std::shared_ptr<RandomGenerator> &randomGenerator = nullptr);
void PostFinishedPlacementCommand(
PlayerId player,
@@ -182,11 +186,11 @@ public:
PlayerId playerId,
std::shared_ptr<RandomGenerator> randomGenerator) -> int;
[[nodiscard]] auto GetPreviewCommands(PlayerId playerId) const -> std::vector<CommandProto>;
[[nodiscard]] auto GetPreviewCommands(PlayerId playerId) const -> vector<CommandProto>;
[[nodiscard]] auto GetAvailableCommandsForAIPlayer(PlayerId playerId) const -> CommandListSPtr;
[[nodiscard]] auto GetAvailableCommandProtos(PlayerId playerId, bool includeFollowUps) const
-> std::vector<CommandProto>;
-> vector<CommandProto>;
[[nodiscard]] auto GetGameId() const -> GameId {
return GetCurrentGameState()->game_id()->str();
@@ -194,9 +198,9 @@ public:
[[nodiscard]] auto GetMonth() const -> int { return GetCurrentGameState()->month(); }
[[nodiscard]] auto GetPlayerInfos() const -> std::vector<PlayerInfoProto> {
[[nodiscard]] auto GetPlayerInfos() const -> vector<PlayerInfoProto> {
const auto &currentGameState = GetCurrentGameState();
std::vector<PlayerInfoProto> protos{};
vector<PlayerInfoProto> protos{};
for (const auto *const piFB : *currentGameState->player_infos()) {
protos.push_back(fb::ToPlayerInfoProto(piFB));
}
@@ -79,8 +79,6 @@ auto PlayerSetupCommandFactory::AddAvailablePlayerSetupCommands(
const auto unplacedUnits = ReserveUnitsForPlayer(gameState->units(), playerId);
if (unplacedUnits.empty()) return;
existingCommands.reserve(
existingCommands.size() + (unplacedUnits.size() * reinforcementPositions->size()));
for (const auto &[unitId, unit] : unplacedUnits) {
for (const Coords &position : *reinforcementPositions) {
if (!gameState.GetOccupant(position)) {
@@ -11,6 +11,8 @@
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
namespace shardok {
using std::begin;
using std::end;
struct ActionPointDistances::BraveableTileInfo {
struct BraveableFromInfo {
@@ -29,9 +31,8 @@ auto ActionPointDistances::BraveWaterPossibleCoords(const HexMap* hexMap) const
std::shared_ptr<BraveableTileInfo> info = std::make_shared<BraveableTileInfo>(hexMap);
CoordsSet& braveWaterPossibleCoords = info->cs;
const int indexCount = hexMap->row_count() * hexMap->column_count();
info->details.reserve(indexCount);
const auto indexToCoords = CreateIndexToCoords(hexMap);
for (int i = 0; i < indexCount; ++i) {
for (int i = 0; i < indexCount; i++) {
if (const Terrain* terrain = hexMap->terrain()->Get(i);
IsWater(terrain->type()) && !terrain->modifier().bridge().present() &&
!terrain->modifier().ice().present()) {
@@ -71,7 +72,7 @@ auto ActionPointDistances::CreateIndexToCoords(const HexMap* hexMap) -> std::vec
const int indexCount = hexMap->row_count() * columnCount;
std::vector<Coords> indexToCoords;
indexToCoords.reserve(indexCount);
for (int i = 0; i < indexCount; ++i) {
for (int i = 0; i < indexCount; i++) {
indexToCoords.emplace_back(
static_cast<int8_t>(i / columnCount),
static_cast<int8_t>(i % columnCount));
@@ -85,7 +86,7 @@ auto ActionPointDistances::CreateAdjacencyTable(const HexMap* hexMap)
const int indexCount = hexMap->row_count() * columnCount;
std::vector<std::array<int, 6>> adjacencyTable;
adjacencyTable.reserve(indexCount);
for (int i = 0; i < indexCount; ++i) {
for (int i = 0; i < indexCount; i++) {
const Coords coords(
static_cast<int8_t>(i / columnCount),
static_cast<int8_t>(i % columnCount));
@@ -119,7 +120,7 @@ void ActionPointDistances::PopulateOne(
// Find starting index (the one with distance 0)
const int distanceCount = static_cast<int>(ds.size());
for (int i = 0; i < distanceCount; ++i) {
for (int i = 0; i < distanceCount; i++) {
if (ds[i] == 0) {
pq.emplace(0, i);
break;
@@ -141,7 +142,7 @@ void ActionPointDistances::PopulateOne(
// Prefetch terrain data for all neighbors to reduce memory stalls
const std::array<int, 6>& neighbors = adjacencyTable[currentIndex];
for (int i = 0; i < 6 && neighbors[i] != -1; ++i) {
for (int i = 0; i < 6 && neighbors[i] != -1; i++) {
__builtin_prefetch(hexMap->terrain()->Get(neighbors[i]), 0, 3);
}
@@ -240,10 +241,11 @@ OnDemandActionPointDistances::OnDemandActionPointDistances(
battalionType(std::move(battTp)),
distances(static_cast<size_t>(map->row_count() * map->column_count())) {
const int indexCount = map->row_count() * map->column_count();
distances.resize(indexCount);
auto braveWaterPossibleCoords = includeBravingWater ? BraveWaterPossibleCoords(map) : nullptr;
for (int fromIndex = 0; fromIndex < indexCount; ++fromIndex) {
for (int fromIndex = 0; fromIndex < indexCount; fromIndex++) {
distances[fromIndex] = std::async(
std::launch::deferred,
&OnDemandActionPointDistances::GenerateDistances,
@@ -43,7 +43,7 @@ protected:
const std::shared_ptr<BraveableTileInfo> &braveWaterPossibleCoords,
const std::vector<Coords> &indexToCoords,
const std::vector<std::array<int, 6>> &adjacencyTable);
[[nodiscard]] static auto GenerateDistances(
static auto GenerateDistances(
int fromIndex,
const HexMap *hexMap,
bool includeBravingWater,
@@ -51,15 +51,13 @@ protected:
const BattalionTypeSPtr &battalionType,
const std::shared_ptr<BraveableTileInfo> &braveWaterPossibleCoords)
-> std::vector<DIST_T>;
[[nodiscard]] auto BraveWaterPossibleCoords(const HexMap *hexMap) const
-> std::shared_ptr<BraveableTileInfo>;
auto BraveWaterPossibleCoords(const HexMap *hexMap) const -> std::shared_ptr<BraveableTileInfo>;
// Create coordinate lookup table for efficient index->coords conversion
[[nodiscard]] static auto CreateIndexToCoords(const HexMap *hexMap) -> std::vector<Coords>;
static auto CreateIndexToCoords(const HexMap *hexMap) -> std::vector<Coords>;
// Create adjacency lookup table for efficient neighbor access
[[nodiscard]] static auto CreateAdjacencyTable(const HexMap *hexMap)
-> std::vector<std::array<int, 6>>;
static auto CreateAdjacencyTable(const HexMap *hexMap) -> std::vector<std::array<int, 6>>;
[[nodiscard]] auto ToIndex(const Coords &coords) const -> int {
return coords.row() * column_count + coords.column();
@@ -72,9 +70,9 @@ public:
virtual ~ActionPointDistances() = default;
[[nodiscard]] virtual auto Distance(int fromIndex, int toIndex) const -> DIST_T = 0;
virtual auto Distance(int fromIndex, int toIndex) const -> DIST_T = 0;
[[nodiscard]] virtual auto Distance(const Coords &from, const Coords &to) const -> DIST_T = 0;
virtual auto Distance(const Coords &from, const Coords &to) const -> DIST_T = 0;
};
class OnDemandActionPointDistances final : public ActionPointDistances {
@@ -93,11 +91,11 @@ public:
~OnDemandActionPointDistances() override = default;
[[nodiscard]] auto Distance(const int fromIndex, const int toIndex) const -> int16_t override {
auto Distance(const int fromIndex, const int toIndex) const -> int16_t override {
return distances[fromIndex].get()[toIndex];
}
[[nodiscard]] auto Distance(const Coords &from, const Coords &to) const -> int16_t override {
auto Distance(const Coords &from, const Coords &to) const -> int16_t override {
return Distance(ToIndex(from), ToIndex(to));
}
};
@@ -77,7 +77,7 @@ static auto CreateIceClearedMap(const HexMap* map) -> fb::HexMapW {
auto* mutableMap = mapCopy.Get();
auto* terrainVec = mutableMap->mutable_terrain();
for (size_t i = 0; i < terrainVec->size(); ++i) {
for (size_t i = 0; i < terrainVec->size(); i++) {
// Only process tiles with ice
// const_cast is safe here because we own the mutable buffer (mapCopy)
if (auto* terrain = const_cast<Terrain*>(terrainVec->GetMutableObject(i));
@@ -22,7 +22,7 @@ struct MapId {
uint64_t terrainTypesId;
uint64_t modifierId;
[[nodiscard]] auto operator==(const MapId& other) const -> bool {
auto operator==(const MapId& other) const -> bool {
return terrainTypesId == other.terrainTypesId && modifierId == other.modifierId;
}
};
@@ -34,7 +34,7 @@ struct FullCacheKey {
bool includeBravingWater;
int braveWaterCost;
[[nodiscard]] bool operator==(const FullCacheKey& other) const {
bool operator==(const FullCacheKey& other) const {
return mapId == other.mapId && battalionTypeId == other.battalionTypeId &&
includeBravingWater == other.includeBravingWater &&
braveWaterCost == other.braveWaterCost;
@@ -43,7 +43,7 @@ struct FullCacheKey {
// Hash function for FullCacheKey
struct FullCacheKeyHash {
[[nodiscard]] size_t operator()(const FullCacheKey& key) const {
size_t operator()(const FullCacheKey& key) const {
// Pack small fields into a single 64-bit value
uint64_t packed = (static_cast<uint64_t>(key.battalionTypeId) << 32) |
(static_cast<uint64_t>(key.braveWaterCost) << 1) |
@@ -85,7 +85,7 @@ private:
// Epoch system removed - TLS cache uses size-based eviction instead
// Helper to build cache key
[[nodiscard]] static auto MakeCacheKey(
static auto MakeCacheKey(
const MapId& mapId,
const BattalionTypeSPtr& battalionType,
bool includeBravingWater,
@@ -107,7 +107,7 @@ public:
bool includeBravingWater,
int braveWaterActionPointCost = -1) -> const ActionPointDistances*;
[[nodiscard]] static auto GetMapId(const HexMap* map) -> MapId;
static auto GetMapId(const HexMap* map) -> MapId;
// Consolidate the thread-safe cache into the persistent cache and clear
// the current thread's local cache. This is only safe if we know reads
@@ -116,7 +116,7 @@ public:
// Cache management methods
static void ClearThreadLocalCache();
[[nodiscard]] static size_t GetThreadLocalCacheSize();
static size_t GetThreadLocalCacheSize();
};
using APDCache = std::shared_ptr<ActionPointDistancesCache>;
@@ -12,7 +12,7 @@
// Dynamic thread count based on hardware capabilities
static const int ASYNC_COUNT = []() {
const int cores = static_cast<int>(std::thread::hardware_concurrency());
// Use cores-4 to leave room for OS and other processes, minimum 4 threads
// Use cores-2 to leave room for OS and other processes, minimum 4 threads
const int threadCount = std::max(4, cores - 4);
std::cerr << "ActionPointDistances using " << threadCount << " threads (detected " << cores
<< " cores)\n";
@@ -35,20 +35,20 @@ auto FixedActionPointDistances::Create(
const int indexCount = map->row_count() * map->column_count();
vector<std::future<vector<vector<DIST_T>>>> futures(ASYNC_COUNT);
vector<std::future<vector<vector<DIST_T>>>> futures(indexCount);
auto braveWaterPossibleCoords =
includeBravingWater ? apd->BraveWaterPossibleCoords(map) : nullptr;
int chunkSize = (indexCount + ASYNC_COUNT - 1) / ASYNC_COUNT;
// Break into chunks for async
for (int chunkIdx = 0; chunkIdx < ASYNC_COUNT; ++chunkIdx) {
for (int chunkIdx = 0; chunkIdx < ASYNC_COUNT; chunkIdx++) {
futures[chunkIdx] = std::async(std::launch::async, [=]() -> vector<vector<DIST_T>> {
vector<vector<DIST_T>> chunkVec;
chunkVec.reserve(chunkSize);
const int chunkStartIndex = chunkIdx * chunkSize;
for (int i = 0; i < chunkSize; ++i) {
for (int i = 0; i < chunkSize; i++) {
const auto fromIndex = chunkStartIndex + i;
if (fromIndex >= indexCount) { continue; }
chunkVec.push_back(ActionPointDistances::GenerateDistances(
@@ -65,7 +65,7 @@ auto FixedActionPointDistances::Create(
apd->distances.reserve(indexCount);
for (int chunkIdx = 0; chunkIdx < ASYNC_COUNT; ++chunkIdx) {
for (int chunkIdx = 0; chunkIdx < ASYNC_COUNT; chunkIdx++) {
auto resultsVec = futures[chunkIdx].get();
apd->distances.insert(apd->distances.end(), resultsVec.begin(), resultsVec.end());
}
@@ -24,7 +24,7 @@ private:
public:
// Factory method to create FixedActionPointDistances
[[nodiscard]] static auto Create(
static auto Create(
const HexMap *map,
const BattalionTypeSPtr &battalionType,
bool includeBravingWater,
@@ -68,7 +68,7 @@ void ApplyResolvedUnit(
// If a VIP was captured, alter the morale of all this player's units
if (status == net::eagle0::shardok::storage::fb::UnitStatus_CAPTURED_UNIT &&
unit.has_attached_hero() && unit.attached_hero().is_vip()) {
for (uint32_t i = 0; i < inoutState->units()->size(); ++i) {
for (uint32_t i = 0; i < inoutState->units()->size(); i++) {
auto *playerUnit = GetMutableUnit(inoutState, i);
if (playerUnit->player_id() != unit.player_id()) continue;
if (playerUnit->unit_id() == unit.unit_id()) continue;
@@ -248,12 +248,12 @@ void MutatingApplyResult(
static_cast<net::eagle0::shardok::storage::fb::GameStatus_::State>(
result.game_status().state())));
const int winningShardokIdCount = result.game_status().winning_shardok_ids_size();
for (int i = 0; i < winningShardokIdCount; ++i) {
for (int i = 0; i < winningShardokIdCount; i++) {
mutatingGameState->mutable_status()->mutable_winning_shardok_ids()->Mutate(
i,
result.game_status().winning_shardok_ids(i));
}
for (int i = winningShardokIdCount; i < 10; ++i) {
for (int i = winningShardokIdCount; i < 10; i++) {
mutatingGameState->mutable_status()->mutable_winning_shardok_ids()->Mutate(i, -1);
}
@@ -302,17 +302,16 @@ void MutatingApplyResult(
const Unit *changedUnit = &UnitFromBytes(changedUnitBytes);
internalAssert(
changedUnit->unit_id() < static_cast<int32_t>(mutatingGameState->units()->size()),
changedUnit->unit_id() < (int32_t)mutatingGameState->units()->size(),
"Got a changed unit outside acceptable range");
const auto battalionSizeAfter = changedUnit->battalion().size();
const int battalionSizeBefore =
(changedUnit->unit_id() < static_cast<int32_t>(initialUnitCount))
? mutatingGameState->units()
->Get(changedUnit->unit_id())
->battalion()
.size()
: changedUnit->battalion().size();
const int battalionSizeBefore = (changedUnit->unit_id() < (int32_t)initialUnitCount)
? mutatingGameState->units()
->Get(changedUnit->unit_id())
->battalion()
.size()
: changedUnit->battalion().size();
auto status = changedUnit->status();
if (IsDestroyed(*changedUnit)) {
@@ -385,10 +384,10 @@ void MutatingApplyResult(
internalAssert(mutatingGameState->mutate_eligible_charger_id(-1));
}
const int possibleChargeeCount = result.possible_chargees_size();
for (int i = 0; i < possibleChargeeCount; ++i) {
for (int i = 0; i < possibleChargeeCount; i++) {
mutatingGameState->mutable_possible_chargee_ids()->Mutate(i, result.possible_chargees(i));
}
for (int i = possibleChargeeCount; i < 6; ++i) {
for (int i = possibleChargeeCount; i < 6; i++) {
mutatingGameState->mutable_possible_chargee_ids()->Mutate(i, -1);
}
@@ -23,11 +23,11 @@ void MutatingApplyResult(
GameStateW& mutatingGameState,
const ActionResultProto& actionResult,
const SettingsGetter& settings);
[[nodiscard]] auto ApplyResult(
auto ApplyResult(
GameStateW startingState,
const ActionResultProto& actionResult,
const SettingsGetter& settings) -> GameStateW;
[[nodiscard]] auto ApplyResults(
auto ApplyResults(
const GameStateW& startingState,
const std::vector<ActionResultProto>& actionResults,
const SettingsGetter& settings) -> GameStateW;
@@ -5,7 +5,6 @@
#include "src/main/cpp/net/eagle0/shardok/library/action_result_applier/GameStateCopier.hpp"
#include <cstring>
#include <utility>
namespace shardok {
@@ -19,7 +18,7 @@ auto CopyWithExtraUnits(const GameStateW& original, int additionalCount) -> Game
// Add the requested units plus some extra slack for future use
int extraSlack = std::max(5, additionalCount * 2);
for (int i = 0; i < additionalCount + extraSlack; ++i) {
for (int i = 0; i < additionalCount + extraSlack; i++) {
Unit unit;
unit.mutate_unit_id(static_cast<int16_t>(endGST.units.size()));
if (i < additionalCount) {
@@ -32,7 +31,7 @@ auto CopyWithExtraUnits(const GameStateW& original, int additionalCount) -> Game
unit.mutable_location().mutate_row(-1);
unit.mutable_location().mutate_column(-1);
}
endGST.units.push_back(std::move(unit));
endGST.units.push_back(unit);
}
// Copy occupied tiles bitfield from original GameState (much faster than O(n) rebuild)
@@ -9,8 +9,7 @@
namespace shardok {
[[nodiscard]] auto CopyWithExtraUnits(const GameStateW& original, int additionalCount)
-> GameStateW;
auto CopyWithExtraUnits(const GameStateW& original, int additionalCount) -> GameStateW;
} // namespace shardok
@@ -4,8 +4,6 @@
#include "src/main/cpp/net/eagle0/shardok/library/action_result_applier/UnitHelpers.hpp"
#include <cstring>
namespace shardok::fb {
using namespace net::eagle0::shardok::storage::fb;
@@ -29,7 +27,7 @@ auto ApplyUnit(
}
}
std::memcpy(toUnit, fromUnit, sizeof(Unit));
memcpy(toUnit, fromUnit, sizeof(Unit));
toUnit->mutate_status(status);
if (toUnit->has_attached_hero()) {
@@ -4,8 +4,6 @@
#include "EndPlayerSetupCommand.hpp"
#include <utility>
#include "src/main/cpp/net/eagle0/shardok/library/action_result_applier/ActionResultApplier.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/actions/NewRoundAction.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/actions/StartPlayerTurnAction.hpp"
@@ -35,9 +33,9 @@ auto EndPlayerSetupCommand::InternalExecute(
gameStartResult.mutable_game_status()->set_state(
GameStatus::State::GameStatus_State_GAME_RUNNING);
gameStartResult.mutable_game_status()->set_description("The game has begun!");
allResults.push_back(gameStartResult);
startingGameState = ApplyResult(startingGameState, gameStartResult, settings);
allResults.push_back(std::move(gameStartResult));
auto newRoundResults =
NewRoundAction(startingGameState, settings).Execute(currentState, generator);
@@ -63,4 +61,4 @@ auto EndPlayerSetupCommand::GetCommandProto() const -> shardok::CommandProto {
return proto;
}
} // namespace shardok
} // namespace shardok
@@ -8,7 +8,6 @@
#include "FallIntoWaterAction.hpp"
#include <array>
#include <utility>
#include "src/main/cpp/net/eagle0/shardok/library/map/Coordinates.hpp"
@@ -56,17 +55,16 @@ auto EscapeChance(
const int16_t windFactor = -currentWeather.wind().speed_in_mph() * 2;
const std::vector<double> stats = {intelligence, agility};
std::vector<OtherFactor> otherFactors;
if (terrain.modifier().fire().present()) {
const std::array otherFactors = {MakeOtherFactor(-50, "fire")};
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
otherFactors.push_back(MakeOtherFactor(-50, "fire"));
}
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats);
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
}
auto ToVector(const Units &units) -> vector<const Unit *> {
auto v = vector<const Unit *>{};
v.reserve(units.size());
for (const Unit *u : units) { v.push_back(u); }
return v;
}
@@ -78,7 +76,6 @@ auto FallIntoWaterAction::AdjacentsWithTerrain(
const CoordsSet adjacentCoords = HexMapUtils::GetAdjacentCoords(gameState->hex_map(), location);
vector<AdjacentWithTerrain> vec{};
vec.reserve(adjacentCoords.size());
for (const Coords &adjCoords : adjacentCoords) {
const auto *possibleOccupant = Occupant(gameState->units(), adjCoords);
@@ -106,7 +103,6 @@ auto FallIntoWaterAction::InternalExecute(
const GameStateW &currentState,
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResult> {
vector<ActionResult> results{};
results.reserve(2);
auto fallerTerrain =
*GetTerrain(currentState->hex_map(), currentState->units()->Get(fallerId)->location());
@@ -56,7 +56,7 @@ auto IceAdjustmentAction::InternalExecute(
*resultProto.mutable_target_coords() = ToCoordsProto(coords);
*resultProto.add_changed_tile_modifiers() = MakeTmc(coords, newTerrain.modifier());
results.push_back(std::move(resultProto));
results.push_back(resultProto);
if (terrain.modifier().ice().present() && !IsFrozen(newTerrain.modifier()) &&
fallAction != nullptr) {
@@ -71,4 +71,4 @@ auto IceAdjustmentAction::InternalExecute(
return results;
}
} // namespace shardok
} // namespace shardok
@@ -160,7 +160,6 @@ auto MeteorCastAction::InternalExecute(
const GameStateW &currentState,
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResultProto> {
vector<ActionResultProto> allResults{};
allResults.reserve(actorIds.size() * 16);
auto runningGameState = currentState;
for (const UnitId &actorId : actorIds) {
const Unit *actorBefore = currentState->units()->Get(actorId);
@@ -184,7 +183,7 @@ auto MeteorCastAction::PerformOneActorCast(
mainResult.mutable_actor()->set_value(actorBefore->unit_id());
*mainResult.mutable_target_coords() =
ToCoordsProto(actorBefore->attached_hero().profession_info().cast_target());
results.push_back(std::move(mainResult));
results.push_back(mainResult);
const Coords target = actorBefore->attached_hero().profession_info().cast_target();
if (const Unit *possibleOccupant = runningGameState.GetOccupant(target)) {
@@ -90,7 +90,7 @@ auto BurnStructuresResult(const GameStateW &gameState, const SettingsGetter &set
*result.mutable_changed_tile_modifiers() = {burnedTmcs.begin(), burnedTmcs.end()};
return result;
} else {
return std::nullopt;
return std::optional<ActionResultProto>();
}
}
@@ -151,7 +151,7 @@ auto NewWeather(
return MakeWeather(
newConditions,
MakeWind(
static_cast<net::eagle0::shardok::common::HexMapDirection>(newWindDirection),
(net::eagle0::shardok::common::HexMapDirection)newWindDirection,
newWindSpeed));
}
@@ -159,7 +159,6 @@ auto NewRoundAction::InternalExecute(
const GameStateW &currentState,
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResultProto> {
vector<ActionResultProto> results{};
results.reserve(4);
GameStateW runningGameState = startingGameState;
@@ -178,7 +177,6 @@ auto NewRoundAction::InternalExecute(
ActionList fireSpreadAction = fireSpreadActionFactory->MakeFireSpreadActions(
runningGameState->hex_map(),
runningGameState->weather());
results.reserve(results.size() + fireSpreadAction.size() + 1);
for (const ActionSPtr &action : fireSpreadAction) {
auto fireSpreadResults = action->Execute(currentState, generator);
for (const auto &result : fireSpreadResults) {
@@ -193,7 +191,6 @@ auto NewRoundAction::InternalExecute(
*runningGameState->units(),
runningGameState->month(),
runningGameState->weather());
results.reserve(results.size() + iceAdjustmentAction.size());
for (const ActionSPtr &action : iceAdjustmentAction) {
auto iceAdjustmentResults = action->Execute(currentState, generator);
for (const auto &result : iceAdjustmentResults) {
@@ -223,7 +220,6 @@ auto NewRoundAction::InternalExecute(
ActionList fireOutActions = fireOutActionFactory->MakeFireOutActions(
runningGameState->hex_map(),
runningGameState->weather());
results.reserve(results.size() + fireOutActions.size());
for (const ActionSPtr &action : fireOutActions) {
auto fireOutResults = action->Execute(currentState, generator);
for (const auto &result : fireOutResults) {
@@ -25,7 +25,6 @@ auto PerformUndeadCommandsAction::InternalExecute(
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResultProto> {
GameStateW runningGameState = startingGameState;
vector<ActionResultProto> allResults{};
allResults.reserve(runningGameState->units()->size());
while (runningGameState->current_player() == UNCONTROLLED_PLAYER_ID) {
auto commands = availableCommandsFactory->GetAvailableCommands(
@@ -57,7 +56,6 @@ auto ChooseUndeadCommand(
const HexMap *hexMap,
const std::shared_ptr<RandomGenerator> &randomGenerator) -> CommandSPtr {
CommandList attackCommands;
attackCommands.reserve(commands->size());
for (const auto &command : *commands) {
const CommandProto &possibleAttackCommandProto = command->GetCommandProto();
@@ -78,7 +76,6 @@ auto ChooseUndeadCommand(
}
CommandList moveCommands;
moveCommands.reserve(commands->size());
CommandSPtr bestMoveCommand = nullptr;
for (const auto &command : *commands) {
@@ -4,8 +4,6 @@
#include "SnowAdjustmentAction.hpp"
#include <utility>
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/TileModifierHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/TileModifier.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/TileModifierWithCoords.hpp"
@@ -104,8 +102,8 @@ auto SnowAdjustmentAction::InternalExecute(
*resultProto.mutable_target_coords() = ToCoordsProto(coords);
*resultProto.add_changed_tile_modifiers() = MakeTmc(coords, newTerrain.modifier());
results.push_back(std::move(resultProto));
results.push_back(resultProto);
return results;
}
} // namespace shardok
} // namespace shardok
@@ -25,9 +25,10 @@ static inline auto GameIsOver(const GameStatusFb* status) -> bool {
}
auto UpdateGameStatusAction::GetPlayerInfo(int playerId) const -> const PlayerInfoFb* {
return *std::ranges::find_if(*gameState->player_infos(), [playerId](const PlayerInfoFb* pi) {
return pi->player_id() == playerId;
});
return *std::find_if(
std::begin(*gameState->player_infos()),
std::end(*gameState->player_infos()),
[playerId](const PlayerInfoFb* pi) { return pi->player_id() == playerId; });
}
static inline auto HasVictoryCondition(const PlayerInfoFb* pi, const VictoryCondition condition)
@@ -48,9 +49,7 @@ auto UpdateGameStatusAction::SurvivingPlayers() const -> std::unordered_set<Play
for (const Unit* unit : *gameState->units()) {
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
if (unit->player_id() < 0 ||
static_cast<uint32_t>(unit->player_id()) >= gameState->player_infos()->size())
continue;
if ((uint32_t)unit->player_id() >= gameState->player_infos()->size()) continue;
if (!unit->hidden() && (unit->battalion().size() > 0 ||
(unit->has_attached_hero() && unit->attached_hero().vigor() > 0))) {
@@ -29,7 +29,7 @@ auto UpdateOpponentKnowledgeAction::InternalExecute(
auto unitAfter = *unit;
for (PlayerId pid = 0; pid < 10; ++pid) {
for (PlayerId pid = 0; pid < 10; pid++) {
if (pid == unitPid) continue;
if (static_cast<unsigned int>(pid) >= playerCount) continue;
int bump = PlayerIsDefender(currentState, pid) ? defenderKnowledgeGain
@@ -4,8 +4,6 @@
#include "BlowBridgeCommandFactory.hpp"
#include <array>
#include "src/main/cpp/net/eagle0/shardok/library/commands/BlowBridgeCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
@@ -94,13 +92,13 @@ auto GetBlowBridgeOdds(
constexpr int16_t weatherFactor = 0;
constexpr int16_t windFactor = 0;
const std::vector stats = {strength, agility, intelligence};
std::vector<OtherFactor> otherFactors;
if (hasForestAccess) {
const std::array otherFactors = {
MakeOtherFactor(settings.Backing().blow_bridge_forest_bonus(), "forest nearby")};
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
otherFactors.push_back(
MakeOtherFactor(settings.Backing().blow_bridge_forest_bonus(), "forest nearby"));
}
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats);
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
}
} // namespace shardok
@@ -4,8 +4,6 @@
#include "BraveWaterCommandFactory.hpp"
#include <array>
#include "src/main/cpp/net/eagle0/shardok/library/commands/BraveWaterCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
@@ -41,20 +39,13 @@ PercentileRollOdds GetBraveWaterOdds(
wisdom * settings.Backing().brave_water_wisdom_factor());
const double armamentFactor = armament * settings.Backing().brave_water_armament_factor();
auto otherFactors = vector<OtherFactor>{MakeOtherFactor(armamentFactor, "Armament")};
if (isRanger) {
const std::array otherFactors = {
MakeOtherFactor(armamentFactor, "Armament"),
MakeOtherFactor(settings.Backing().brave_water_ranger_bonus(), "Ranger")};
return MakeOdds(
/*base*/ baseOdds,
/*terrain*/ 0,
weatherFactor,
/*wind*/ 0,
statFactor,
otherFactors);
otherFactors.emplace_back(
MakeOtherFactor(settings.Backing().brave_water_ranger_bonus(), "Ranger"));
}
const std::array otherFactors = {MakeOtherFactor(armamentFactor, "Armament")};
return MakeOdds(
/*base*/ baseOdds,
/*terrain*/ 0,
@@ -126,7 +117,7 @@ void BraveWaterCommandFactory::AddAvailableBraveWaterCommands(
}
for (const auto &target : braveWaterTargets) {
std::optional<UnitId> ambusher = std::nullopt;
std::optional<UnitId> ambusher = std::optional<UnitId>();
const auto &targetOccupant = Occupant(units, target);
if (targetOccupant) { ambusher = targetOccupant->unit_id(); }
commands.push_back(BraveWaterCommandFactory(settings)
@@ -4,8 +4,6 @@
#include "BuildBridgeCommandFactory.hpp"
#include <array>
#include "src/main/cpp/net/eagle0/shardok/library/commands/BuildBridgeCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
@@ -83,13 +81,13 @@ auto GetBuildBridgeOdds(
constexpr int16_t weatherFactor = 0;
constexpr int16_t windFactor = 0;
const std::vector stats = {strength, agility, intelligence};
std::vector<OtherFactor> otherFactors;
if (hasForestAccess) {
const std::array otherFactors = {
MakeOtherFactor(settings.Backing().build_bridge_forest_bonus(), "forest nearby")};
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
otherFactors.push_back(
MakeOtherFactor(settings.Backing().build_bridge_forest_bonus(), "forest nearby"));
}
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats);
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
}
} // namespace shardok
@@ -4,8 +4,6 @@
#include "CommandFactoriesList.hpp"
#include <memory>
#include "src/main/cpp/net/eagle0/shardok/library/command_factories/BlowBridgeCommandFactory.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/command_factories/BraveWaterCommandFactory.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/command_factories/BuildBridgeCommandFactory.hpp"
@@ -36,38 +34,40 @@
namespace shardok {
using std::make_shared;
auto MakeFactories(const SettingsGetter& settings)
-> std::vector<std::shared_ptr<const CommandFactory>> {
// Command factories that produce "must-do" commands like Meteor Target should be first.
// Ultimately I should probably move that metadata into the CommandFactory and sort it here.
return {std::make_shared<MeteorTargetCommandFactory>(settings),
std::make_shared<ControlCommandFactory>(settings),
std::make_shared<ArcheryCommandFactory>(settings),
std::make_shared<BlowBridgeCommandFactory>(settings),
std::make_shared<BraveWaterCommandFactory>(settings),
std::make_shared<BuildBridgeCommandFactory>(settings),
std::make_shared<ChallengeDuelCommandFactory>(settings),
std::make_shared<ChargeCommandFactory>(settings),
std::make_shared<EvacuatePrisonersCommandFactory>(settings),
std::make_shared<ExtinguishFireCommandFactory>(settings),
std::make_shared<FearCommandFactory>(settings),
std::make_shared<FleeCommandFactory>(settings),
std::make_shared<FortifyCommandFactory>(settings),
std::make_shared<FreezeWaterCommandFactory>(settings),
std::make_shared<HideCommandFactory>(settings),
std::make_shared<HolyWaveCommandFactory>(settings),
std::make_shared<LightningBoltCommandFactory>(settings),
std::make_shared<MeleeCommandFactory>(settings),
std::make_shared<MeteorStartCommandFactory>(settings),
std::make_shared<RaiseDeadCommandFactory>(settings),
std::make_shared<ReduceCommandFactory>(settings),
std::make_shared<ReinforceCommandFactory>(settings),
std::make_shared<RepairCommandFactory>(settings),
std::make_shared<RetreatCommandFactory>(settings),
std::make_shared<ScoutCommandFactory>(settings),
std::make_shared<StartFireCommandFactory>(settings),
std::make_shared<MoveCommandFactory>(settings)};
return {make_shared<MeteorTargetCommandFactory>(settings),
make_shared<ControlCommandFactory>(settings),
make_shared<ArcheryCommandFactory>(settings),
make_shared<BlowBridgeCommandFactory>(settings),
make_shared<BraveWaterCommandFactory>(settings),
make_shared<BuildBridgeCommandFactory>(settings),
make_shared<ChallengeDuelCommandFactory>(settings),
make_shared<ChargeCommandFactory>(settings),
make_shared<EvacuatePrisonersCommandFactory>(settings),
make_shared<ExtinguishFireCommandFactory>(settings),
make_shared<FearCommandFactory>(settings),
make_shared<FleeCommandFactory>(settings),
make_shared<FortifyCommandFactory>(settings),
make_shared<FreezeWaterCommandFactory>(settings),
make_shared<HideCommandFactory>(settings),
make_shared<HolyWaveCommandFactory>(settings),
make_shared<LightningBoltCommandFactory>(settings),
make_shared<MeleeCommandFactory>(settings),
make_shared<MeteorStartCommandFactory>(settings),
make_shared<RaiseDeadCommandFactory>(settings),
make_shared<ReduceCommandFactory>(settings),
make_shared<ReinforceCommandFactory>(settings),
make_shared<RepairCommandFactory>(settings),
make_shared<RetreatCommandFactory>(settings),
make_shared<ScoutCommandFactory>(settings),
make_shared<StartFireCommandFactory>(settings),
make_shared<MoveCommandFactory>(settings)};
}
auto CommandFactoriesList::GetFactories() const
@@ -19,7 +19,7 @@ private:
public:
explicit CommandFactoriesList(const SettingsGetter& settings);
[[nodiscard]] auto GetFactories() const -> std::vector<std::shared_ptr<const CommandFactory>>;
auto GetFactories() const -> std::vector<std::shared_ptr<const CommandFactory>>;
};
} // namespace shardok
@@ -41,7 +41,7 @@ public:
virtual void AddAvailableCommands(CommandList& commands, const CommandParams& params) const = 0;
[[nodiscard]] virtual auto IncludeInFollowUps() const -> bool { return true; }
virtual auto IncludeInFollowUps() const -> bool { return true; }
};
} // namespace shardok
@@ -33,7 +33,6 @@ namespace {
const PlayerId actorPlayerId,
const vector<PlayerId>& allyPids) -> std::vector<UnitId> {
std::vector<UnitId> captiveUnitIds;
captiveUnitIds.reserve(units->size());
for (const auto* unit : *units) {
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_CAPTURED_UNIT &&
unit->has_attached_hero() && IsHostileToActor(unit, actorPlayerId, allyPids)) {
@@ -27,7 +27,7 @@ void FearCommandFactory::AddAvailableFearCommands(
if (costForFear.IsPossible(remainingActionPoints)) {
const int range = settings.Backing().fear_range();
for (int distance = 1; distance <= range; ++distance) {
for (int distance = 1; distance <= range; distance++) {
const auto coordsSet = TilesWithExactDistance(hexMap, position, distance);
for (const Coords &fearCoords : coordsSet) {
@@ -9,7 +9,6 @@
#include "FleeCommandFactory.hpp"
#include <algorithm>
#include <array>
#include <ranges>
#include "src/main/cpp/net/eagle0/shardok/library/commands/BecomeOutlawCommand.hpp"
@@ -68,11 +67,15 @@ void FleeCommandFactory::AddAvailableFleeCommands(
}
}
const std::array otherFactors = {
MakeOtherFactor(adjacentFriendliesMod, "adjacentFriendlies"),
MakeOtherFactor(adjacentEnemiesMod, "adjacentEnemies"),
MakeOtherFactor(adjacentImpassableMod, "adjacentImpassable")};
odds = MakeOdds(settings.Backing().flee_success_base_chance(), 0, 0, 0, 0, otherFactors);
odds = MakeOdds(
settings.Backing().flee_success_base_chance(),
0,
0,
0,
0,
{MakeOtherFactor(adjacentFriendliesMod, "adjacentFriendlies"),
MakeOtherFactor(adjacentEnemiesMod, "adjacentEnemies"),
MakeOtherFactor(adjacentImpassableMod, "adjacentImpassable")});
}
if (unit->can_flee()) {

Some files were not shown because too many files have changed in this diff Show More