Compare commits

..
Author SHA1 Message Date
admin 78937263a8 Remove broken random battle runner 2026-06-21 08:27:03 -07:00
552 changed files with 4620 additions and 20925 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" \
-6
View File
@@ -20,7 +20,6 @@ on:
- 'ci/github_actions/ensure_bazel_installed.sh'
- 'scripts/build_eagle_ci.sh'
- 'scripts/build_shardok_ci.sh'
- 'scripts/build_shardok_linux_arm64_ci.sh'
- '.github/actions/setup-bazel/**'
- '.github/workflows/server_build.yml'
@@ -84,7 +83,6 @@ jobs:
'src/main/cpp/',
'src/main/protobuf/net/eagle0/shardok/',
'scripts/build_shardok_ci.sh',
'scripts/build_shardok_linux_arm64_ci.sh',
];
const shared = matches(sharedPatterns);
@@ -108,10 +106,6 @@ jobs:
name: 'Shardok server',
command: './scripts/build_shardok_ci.sh',
});
include.push({
name: 'Shardok Linux ARM64 server',
command: './scripts/build_shardok_linux_arm64_ci.sh',
});
}
core.setOutput('build_matrix', JSON.stringify({include}));
+1 -3
View File
@@ -196,9 +196,7 @@ 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
-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
+1 -1
View File
@@ -282,7 +282,7 @@ oci.pull(
# Base image for Admin Server (Alpine for lightweight Go binary)
oci.pull(
name = "alpine_linux",
digest = "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
digest = "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
image = "docker.io/library/alpine",
platforms = ["linux/amd64"],
tag = "3.24",
+8 -8
View File
@@ -904,7 +904,7 @@
"@@rules_oci+//oci:extensions.bzl%oci": {
"general": {
"bzlTransitiveDigest": "H23W9dvwCQM6h+Wt8PjIahx08EkAU5eSbUxAyx9Mopc=",
"usagesDigest": "k6DBHAkb5br/tpwCPUMJgqwb+7U4DXyCmBZQBgLnHJ8=",
"usagesDigest": "tbQ7wHHObzzrBzSRv652104GY3xxiaQDK5OUp2YeEbg=",
"recordedInputs": [
"REPO_MAPPING:aspect_bazel_lib+,bazel_tools bazel_tools",
"REPO_MAPPING:bazel_features+,bazel_tools bazel_tools",
@@ -922,7 +922,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/eclipse-temurin",
"identifier": "sha256:0beb258e10ad28d2bae0f74349a170bdfa67da0ef87c028cbf3d95c3e1170120",
"identifier": "sha256:db3011b348ad87ced055afe5145238d87a3d8f0e8f23eb4d0cd8cecb4659b085",
"platform": "linux/amd64",
"target_name": "eclipse_temurin_25_jre_linux_amd64",
"bazel_tags": []
@@ -936,7 +936,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/eclipse-temurin",
"identifier": "sha256:0beb258e10ad28d2bae0f74349a170bdfa67da0ef87c028cbf3d95c3e1170120",
"identifier": "sha256:db3011b348ad87ced055afe5145238d87a3d8f0e8f23eb4d0cd8cecb4659b085",
"platforms": {
"@@platforms//cpu:x86_64": "@eclipse_temurin_25_jre_linux_amd64"
},
@@ -980,7 +980,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/ubuntu",
"identifier": "26.04",
"identifier": "24.04",
"platform": "linux/amd64",
"target_name": "ubuntu_24_04_linux_amd64",
"bazel_tags": []
@@ -993,7 +993,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/ubuntu",
"identifier": "26.04",
"identifier": "24.04",
"platform": "linux/arm64/v8",
"target_name": "ubuntu_24_04_linux_arm64_v8",
"bazel_tags": []
@@ -1007,7 +1007,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/ubuntu",
"identifier": "26.04",
"identifier": "24.04",
"platforms": {
"@@platforms//cpu:x86_64": "@ubuntu_24_04_linux_amd64",
"@@platforms//cpu:arm64": "@ubuntu_24_04_linux_arm64_v8"
@@ -1023,7 +1023,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/alpine",
"identifier": "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
"identifier": "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
"platform": "linux/amd64",
"target_name": "alpine_linux_linux_amd64",
"bazel_tags": []
@@ -1037,7 +1037,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/alpine",
"identifier": "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
"identifier": "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
"platforms": {
"@@platforms//cpu:x86_64": "@alpine_linux_linux_amd64"
},
+3 -1
View File
@@ -5,7 +5,8 @@ set -x
COMMAND_LOG="$(bazel info command_log)"
bazel build \
//src/main/cpp/net/eagle0/shardok:shardok-server
//src/main/cpp/net/eagle0/shardok:shardok-server \
//src/main/cpp/net/eagle0/shardok:shardok-inspector
STATUS="$?"
if [ $STATUS -eq 0 ]
@@ -25,6 +26,7 @@ echo "Setting up Shardok files..."
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server ./shardok/
/bin/mkdir -p ./shardok/shardok-server.runfiles/net_eagle0/src/main/
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server.runfiles/net_eagle0/src/main/resources ./shardok/shardok-server.runfiles/net_eagle0/src/main/
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-inspector ./shardok/
echo "tar & zip shardok files..."
/bin/tar cfh - shardok | /usr/bin/pigz > ./deploy/shardok.tar.gz
+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.
-17
View File
@@ -1,17 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
bazel_cmd=(bazel)
if [ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]; then
bazel_cmd+=("--output_user_root=${BAZEL_OUTPUT_USER_ROOT}")
fi
if [ -n "${BAZEL_OUTPUT_BASE:-}" ]; then
bazel_cmd+=("--output_base=${BAZEL_OUTPUT_BASE}")
fi
"${bazel_cmd[@]}" build \
--platforms=//:linux_arm64 \
--extra_toolchains=@llvm_toolchain_linux_arm64//:cc-toolchain-aarch64-linux \
"$@" \
//src/main/cpp/net/eagle0/shardok:shardok-server
@@ -14,7 +14,6 @@
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <mutex>
#include "tools/cpp/runfiles/runfiles.h"
@@ -38,7 +37,7 @@ auto rloc(const string& execPath) -> string {
const std::unique_ptr<Runfiles> runfiles(Runfiles::Create(execPath, &error));
if (runfiles == nullptr) {
std::cerr << "Error! " << error << '\n';
fprintf(stderr, "Error! %s\n", error.c_str());
abort();
// error handling
}
@@ -79,9 +78,9 @@ auto FilesystemUtils::MapFilesDirectory() -> string {
void FilesystemUtils::MakeDirectoryIfNecessary(const string& directoryPath) {
if (fs::create_directories(directoryPath))
std::cerr << "Directory " << directoryPath << " created\n";
fprintf(stderr, "Directory %s created\n", directoryPath.c_str());
else
std::cerr << "No new directory created for " << directoryPath << '\n';
fprintf(stderr, "No new directory created for %s\n", directoryPath.c_str());
}
auto FilesystemUtils::SaveFilesDirectory() -> string {
@@ -141,11 +140,11 @@ auto FilesystemUtils::AtomicallySaveToPath(const string& path, const byte_vector
if (ostr.good()) {
const int err = rename(tempPath.c_str(), path.c_str());
if (err == -1) {
std::cerr << "Failed to move file to " << path << "! Errno " << errno << '\n';
fprintf(stderr, "Failed to move file to %s! Errno %d\n", path.c_str(), errno);
return false;
}
} else {
std::cerr << "Failed writing to " << tempPath << "!\n";
fprintf(stderr, "Failed writing to %s!\n", tempPath.c_str());
return false;
}
@@ -80,7 +80,7 @@ void EmitShardokLatencyTrace(
out << "}";
std::scoped_lock lock(LogMutex());
std::cerr << out.str() << '\n';
std::cerr << out.str() << std::endl;
}
ScopedShardokLatencyTrace::ScopedShardokLatencyTrace(
@@ -10,7 +10,6 @@
#include <condition_variable>
#include <functional>
#include <future>
#include <iostream>
#include <memory>
#include <mutex>
#include <queue>
@@ -172,7 +171,7 @@ public:
// Get detailed queue information for debugging
void debug_queue_state() const {
std::unique_lock<std::mutex> lock(const_cast<std::mutex&>(queue_mutex));
std::cout << "ThreadPool: Queue size: " << tasks.size() << '\n';
printf("ThreadPool: Queue size: %zu\n", tasks.size());
if (!tasks.empty()) {
// Create a copy to inspect priorities without modifying queue
auto queue_copy = tasks;
@@ -181,9 +180,9 @@ public:
priorities.push_back(queue_copy.top().priority);
queue_copy.pop();
}
std::cout << "ThreadPool: Priorities in queue: ";
for (int p : priorities) { std::cout << p << ' '; }
std::cout << '\n';
printf("ThreadPool: Priorities in queue: ");
for (int p : priorities) { printf("%d ", p); }
printf("\n");
}
}
@@ -198,4 +197,4 @@ public:
} // namespace eagle0::common
#endif // EAGLE0_THREADPOOL_HPP
#endif // EAGLE0_THREADPOOL_HPP
@@ -9,11 +9,9 @@
#include <fstream>
#include <future>
#include <iomanip>
#include <iostream>
#include <limits>
#include <mutex>
#include <random>
#include <sstream>
#include <stdexcept>
#include <thread>
@@ -864,12 +862,12 @@ auto AbstractMCTSAI::LogSearchResults(
// Log selected command
const std::string selectedDesc =
bestChild->action ? bestChild->action->getDescription() : "Unknown";
std::ostringstream selectedLine;
selectedLine << std::fixed << std::setprecision(2) << "MCTS: Selected " << selectedDesc
<< " (visit:" << bestChild->visitCount << ", reward:" << bestChild->averageReward
<< ", lookahead:" << bestChild->lookaheadScore << ") from "
<< rootNode->children.size() << " options";
std::cout << selectedLine.str() << '\n';
printf("MCTS: Selected %s (visit:%d, reward:%.2f, lookahead:%.2f) from %zu options\n",
selectedDesc.c_str(),
bestChild->visitCount,
bestChild->averageReward,
bestChild->lookaheadScore,
rootNode->children.size());
// Log top 3 actions by visits
std::vector<const MCTSNode*> sortedChildren;
@@ -881,17 +879,20 @@ auto AbstractMCTSAI::LogSearchResults(
// Show all actions if there are <= 10, otherwise top 5
const size_t numToShow = sortedChildren.size() <= 10 ? sortedChildren.size() : 5;
std::cout << "MCTS: Top " << numToShow << " actions by visits (out of " << sortedChildren.size()
<< " total):\n";
printf("MCTS: Top %zu actions by visits (out of %zu total):\n",
numToShow,
sortedChildren.size());
for (size_t i = 0; i < numToShow; ++i) {
const auto* child = sortedChildren[i];
std::ostringstream line;
line << std::fixed << std::setprecision(2) << " [" << i << "] visits:" << child->visitCount
<< " avgReward:" << child->averageReward << " immediate:" << child->immediateScore
<< " lookahead:" << child->lookaheadScore;
printf(" [%zu] visits:%d avgReward:%.2f immediate:%.2f lookahead:%.2f",
i,
child->visitCount,
child->averageReward,
child->immediateScore,
child->lookaheadScore);
// Show the action's own description
if (child->action) { line << ' ' << child->action->getDescription(); }
if (child->action) { printf(" %s", child->action->getDescription().c_str()); }
// Show sequence preview
if (!child->children.empty()) {
@@ -904,15 +905,16 @@ auto AbstractMCTSAI::LogSearchResults(
}
}
if (bestNext && bestNext->action) {
line << " -> " << bestNext->action->getDescription();
printf(" -> %s", bestNext->action->getDescription().c_str());
}
}
std::cout << line.str() << '\n';
printf("\n");
}
std::cout << "MCTS: Tree stats - max depth:" << result.searchDepth
<< ", total nodes:" << result.nodesEvaluated
<< ", root visits:" << rootNode->visitCount << '\n';
printf("MCTS: Tree stats - max depth:%d, total nodes:%d, root visits:%d\n",
result.searchDepth,
result.nodesEvaluated,
rootNode->visitCount);
// Log best sequence from chosen action (following most-visited path)
std::vector<const MCTSNode*> bestSequence;
@@ -938,10 +940,7 @@ auto AbstractMCTSAI::LogSearchResults(
}
if (!bestSequence.empty()) {
std::ostringstream sequenceLine;
sequenceLine << std::fixed << std::setprecision(2)
<< "MCTS: Best sequence from chosen action (final: " << sequenceScore << "):";
std::cout << sequenceLine.str() << '\n';
printf("MCTS: Best sequence from chosen action (final: %.2f):\n", sequenceScore);
int displayedStep = 0;
for (size_t i = 0; i < bestSequence.size(); ++i) {
const auto* node = bestSequence[i];
@@ -950,31 +949,29 @@ auto AbstractMCTSAI::LogSearchResults(
if (i > 0 && node->parent && node->parent->IsChanceNode()) { continue; }
displayedStep++;
std::ostringstream line;
line << " " << displayedStep << ".";
if (node->action) { line << ' ' << node->action->getDescription(); }
printf(" %d.", displayedStep);
if (node->action) { printf(" %s", node->action->getDescription().c_str()); }
// If this is a chance node, display outcome probabilities and scores
if (node->IsChanceNode() && !node->outcomeProbabilities.empty()) {
line << " [";
printf(" [");
for (size_t j = 0; j < node->outcomeProbabilities.size(); ++j) {
if (j > 0) { line << ", "; }
if (j > 0) printf(", ");
const double prob = node->outcomeProbabilities[j] * 100;
// Show lookahead score for each outcome if child exists
if (j < node->children.size() && node->children[j]->visitCount > 0) {
line << std::fixed << std::setprecision(0) << prob << "%->"
<< std::setprecision(1) << node->children[j]->lookaheadScore;
printf("%.0f%%->%.1f", prob, node->children[j]->lookaheadScore);
} else {
line << std::fixed << std::setprecision(0) << prob << "%->?";
printf("%.0f%%->?", prob);
}
}
line << "]";
printf("]");
}
line << std::fixed << std::setprecision(2) << " (visits:" << node->visitCount
<< ", immediate:" << node->immediateScore << ", lookahead:" << node->lookaheadScore
<< ")";
std::cout << line.str() << '\n';
printf(" (visits:%d, immediate:%.2f, lookahead:%.2f)\n",
node->visitCount,
node->immediateScore,
node->lookaheadScore);
// For non-root nodes in the sequence, show what the top alternatives were
// Skip showing alternatives for chance nodes (they have outcome children, not action
@@ -994,17 +991,17 @@ auto AbstractMCTSAI::LogSearchResults(
});
// Show top 3 alternatives at this decision point
std::cout << " Alternatives at this node (" << siblings.size() << " total):\n";
printf(" Alternatives at this node (%zu total):\n", siblings.size());
const size_t topN = std::min(siblings.size(), size_t(3));
for (size_t j = 0; j < topN; ++j) {
const auto* alt = siblings[j];
std::ostringstream altLine;
altLine << std::fixed << std::setprecision(2) << " [" << j
<< "] visits:" << alt->visitCount
<< " immediate:" << alt->immediateScore
<< " lookahead:" << alt->lookaheadScore;
if (alt->action) { altLine << ' ' << alt->action->getDescription(); }
std::cout << altLine.str() << '\n';
printf(" [%zu] visits:%d immediate:%.2f lookahead:%.2f",
j,
alt->visitCount,
alt->immediateScore,
alt->lookaheadScore);
if (alt->action) { printf(" %s", alt->action->getDescription().c_str()); }
printf("\n");
}
}
}
@@ -1016,7 +1013,7 @@ auto AbstractMCTSAI::DumpTreeToFile(const MCTSNode* root, const std::string& fil
std::ofstream out(filepath);
if (!out) {
std::cerr << "Failed to open dump file: " << filepath << '\n';
fprintf(stderr, "Failed to open dump file: %s\n", filepath.c_str());
return;
}
@@ -1045,7 +1042,7 @@ auto AbstractMCTSAI::DumpTreeToFile(const MCTSNode* root, const std::string& fil
out << "\n=== End of Tree Dump ===\n";
out.close();
std::cout << "MCTS: Tree dumped to " << filepath << '\n';
printf("MCTS: Tree dumped to %s\n", filepath.c_str());
}
auto AbstractMCTSAI::DumpNodeRecursive(
@@ -6,11 +6,8 @@
#define EAGLE0_ABSTRACT_MCTSNODE_HPP
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <memory>
#include <sstream>
#include <vector>
#include "MCTSAction.hpp"
@@ -208,13 +205,13 @@ struct MCTSNode {
isMaximizingPlayer ? child->lookaheadScore : -child->lookaheadScore;
const double explorationValue =
explorationConstant * std::sqrt(std::log(visitCount) / child->visitCount);
std::ostringstream line;
line << std::fixed << std::setprecision(2)
<< " UCB: " << (isMaximizingPlayer ? "MAX" : "MIN")
<< " lookahead=" << child->lookaheadScore << " expl=" << exploitationValue
<< " (+" << explorationValue << ") = " << value << " ["
<< (child->action ? child->action->getDescription() : "root") << "]";
std::cout << line.str() << '\n';
printf(" UCB: %s lookahead=%.2f expl=%.2f (+%.2f) = %.2f [%s]\n",
isMaximizingPlayer ? "MAX" : "MIN",
child->lookaheadScore,
exploitationValue,
explorationValue,
value,
child->action ? child->action->getDescription().c_str() : "root");
}
if (value > bestValue) {
+30 -19
View File
@@ -28,6 +28,36 @@ cc_binary(
deps = [":shardok-server-lib"],
)
cc_binary(
name = "shardok-inspector",
srcs = ["shardok_inspector.cpp"],
copts = COPTS,
data = [
"//src/main/resources/net/eagle0/shardok:battalion_types",
"//src/main/resources/net/eagle0/shardok:settings",
"//src/main/resources/net/eagle0/shardok/maps",
],
visibility = ["//visibility:public"],
deps = [
"//src/main/cpp/net/eagle0/common:byte_vector",
"//src/main/cpp/net/eagle0/common:filesystem_utils",
"//src/main/cpp/net/eagle0/common:tsv_parser",
"//src/main/cpp/net/eagle0/shardok/library:engine",
],
)
cc_binary(
name = "corrupt-game-checker",
srcs = ["CorruptGameChecker.cpp"],
copts = COPTS,
deps = [
"//src/main/cpp/net/eagle0/common:byte_vector",
"//src/main/cpp/net/eagle0/common:filesystem_utils",
"//src/main/cpp/net/eagle0/shardok/library:game_state_w",
"//src/main/protobuf/net/eagle0/common:shardok_internal_interface_cc_grpc",
],
)
cc_binary(
name = "hex-map-migrator",
srcs = ["HexMapMigrator.cpp"],
@@ -39,25 +69,6 @@ cc_binary(
],
)
cc_binary(
name = "random_battle_runner",
srcs = ["RandomBattleRunner.cpp"],
copts = COPTS,
data = [
"//src/main/resources/net/eagle0/shardok:battalion_types",
"//src/main/resources/net/eagle0/shardok:settings",
"//src/main/resources/net/eagle0/shardok/maps",
],
deps = [
"//src/main/cpp/net/eagle0/common:byte_vector",
"//src/main/cpp/net/eagle0/common:filesystem_utils",
"//src/main/cpp/net/eagle0/common:unit_conversions",
"//src/main/cpp/net/eagle0/shardok/library:shardok_c_types",
"//src/main/cpp/net/eagle0/shardok/server:games_manager",
"//src/main/protobuf/net/eagle0/common:random_units_cc_proto",
],
)
cc_library(
name = "map_info_calculator",
srcs = ["MapInfoCalculator.cpp"],
@@ -0,0 +1,46 @@
//
// Created by Dan Crosby on 1/9/22.
//
#include "src/main/cpp/net/eagle0/common/byte_vector.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
#include "src/main/protobuf/net/eagle0/common/shardok_internal_interface.pb.h"
#include "src/main/protobuf/net/eagle0/shardok/storage/game.pb.h"
auto main(int argc, char** argv) -> int {
char* path = argv[1];
printf("Checking file %s\n", path);
auto bytes = byte_vector::FromPath(path);
net::eagle0::shardok::storage::Game game;
game.ParseFromArray(bytes.data(), bytes.size());
printf("The game ID is %s\n", game.game_id().c_str());
printf("The size is %lu\n", bytes.size());
const int arCount = game.action_result_size();
printf("There are %d results\n", arCount);
for (int arIndex = 0; arIndex < arCount; arIndex++) {
shardok::GameStateW gameState =
shardok::GameStateW::FromByteString(game.action_result(arIndex).state_after_fb());
const auto* hexMap = gameState->hex_map();
for (int terrainIndex = 0; terrainIndex < hexMap->terrain()->size(); terrainIndex++) {
const auto* terrain = hexMap->terrain()->Get(terrainIndex);
if (terrain->type() == net::eagle0::shardok::storage::fb::Terrain_::Type_STILL_WATER)
continue;
if (terrain->type() == net::eagle0::shardok::storage::fb::Terrain_::Type_RIVER)
continue;
if (terrain->modifier().bridge().present()) {
printf("Caught one! arIndex %d, terrainIndex %d\n", arIndex, terrainIndex);
printf("Action result was %s\n",
game.action_result(arIndex).action_result().DebugString().c_str());
exit(0);
}
}
}
}
@@ -2,7 +2,7 @@
// Created by Dan Crosby on 3/19/20.
//
#include <iostream>
#include <cstdio>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/common/byte_vector.hpp"
@@ -14,7 +14,7 @@ void ModifyMap(HexMapProto& map);
int main(const int argc, char** argv) {
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " input_path output_path\n";
printf("Usage: %s input_path output_path\n", argv[0]);
return -1;
}
@@ -22,22 +22,18 @@ int main(const int argc, char** argv) {
const std::string outputPath = argv[2];
const byte_vector mapProtoBytes = byte_vector::FromPath(inputPath);
net::eagle0::shardok::common::HexMap mapProto;
if (!mapProto.ParseFromArray(mapProtoBytes.data(), static_cast<int>(mapProtoBytes.size()))) {
std::cerr << "Failed to parse hex map from " << inputPath << '\n';
return -1;
}
mapProto.ParseFromArray(mapProtoBytes.data(), static_cast<int>(mapProtoBytes.size()));
ModifyMap(mapProto);
const std::string outputString = mapProto.SerializeAsString();
if (FilesystemUtils::AtomicallySaveToPath(outputPath, byte_vector(outputString))) return 0;
std::cerr << "Failed to save.\n";
printf("Failed to save.\n");
return -1;
}
void ModifyMap(HexMapProto& map) {
const int terrainCount = map.terrain_size();
for (int i = 0; i < terrainCount; i++) {
for (int i = 0; i < map.terrain_size(); i++) {
const auto existingTerrainType = map.terrain(i).type();
const auto newTerrain =
static_cast<net::eagle0::shardok::common::Terrain_Type>(existingTerrainType + 2);
@@ -4,7 +4,6 @@
#include "MapInfoCalculator.hpp"
#include <cstddef>
#include <cstdlib>
#include <iostream>
@@ -13,43 +12,44 @@
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/MapLoader.hpp"
auto LoadMapFb(const std::string& mapName) -> shardok::Wrapper<shardok::HexMap> {
using namespace shardok;
auto LoadMapFb(const string& mapName) -> Wrapper<HexMap> {
const HexMapProto mapProto = LoadMap(mapName);
flatbuffers::FlatBufferBuilder fbb{};
const auto hexMapOffset = shardok::fb::ConvertHexMapProto(fbb, mapProto);
const auto hexMapOffset = fb::ConvertHexMapProto(fbb, mapProto);
fbb.Finish(hexMapOffset);
return shardok::Wrapper<shardok::HexMap>(fbb);
return Wrapper<HexMap>(fbb);
}
auto CalculateMap(
const std::string& mapName,
const std::shared_ptr<shardok::ActionPointDistancesCache>& apdCache,
const shardok::BattalionTypeSPtr& battalionType) -> OneMapInfo {
const string& mapName,
const std::shared_ptr<ActionPointDistancesCache>& apdCache,
const BattalionTypeSPtr& battalionType) -> OneMapInfo {
auto hexMap = LoadMapFb(mapName);
const auto mapId = shardok::ActionPointDistancesCache::GetMapId(hexMap);
const auto mapId = ActionPointDistancesCache::GetMapId(hexMap);
const shardok::CoordsSet criticalTileLocations = shardok::GetCriticalTileLocations(hexMap);
const CoordsSet criticalTileLocations = GetCriticalTileLocations(hexMap);
OneMapInfo info{
.castleCount = criticalTileLocations.size(),
.name = mapName,
.positionsRequiringCrossing = {}};
for (std::size_t i = 0; i < hexMap->attacker_starting_positions()->size(); i++) {
for (unsigned int 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()->size() < 1) continue;
if (positionList->positions()->size() != 10) {
std::cerr << "Should have 10 starting positions!" << '\n';
std::abort();
abort();
}
int positionsRequiringCrossing = 0;
for (const auto* startingPosition : *positions) {
for (const shardok::Coords& castlePosition : criticalTileLocations) {
if (!shardok::CanReach(
for (const auto* startingPosition : *positionList->positions()) {
for (const Coords& castlePosition : criticalTileLocations) {
if (!CanReach(
*startingPosition,
castlePosition,
hexMap,
@@ -58,10 +58,10 @@ auto CalculateMap(
battalionType)) {
++positionsRequiringCrossing;
break;
}
};
}
}
info.positionsRequiringCrossing[static_cast<int>(i)] = positionsRequiringCrossing;
info.positionsRequiringCrossing[i] = positionsRequiringCrossing;
}
return info;
@@ -12,15 +12,19 @@
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistancesCache.hpp"
using namespace shardok;
using std::map;
using std::string;
struct alignas(64) OneMapInfo {
uint32_t castleCount;
std::string name;
std::map<int, int> positionsRequiringCrossing;
string name;
map<int, int> positionsRequiringCrossing;
};
auto CalculateMap(
const std::string& mapName,
const std::shared_ptr<shardok::ActionPointDistancesCache>& apdCache,
const shardok::BattalionTypeSPtr& battalionType) -> OneMapInfo;
const string& mapName,
const std::shared_ptr<ActionPointDistancesCache>& apdCache,
const BattalionTypeSPtr& battalionType) -> OneMapInfo;
#endif // EAGLE0_MAPINFOCALCULATOR_HPP
@@ -5,39 +5,36 @@
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "MapInfoCalculator.hpp"
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/BattalionTypeRegistrar.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/MapLoader.hpp"
using namespace shardok;
auto main(const int argc, char** argv) -> int {
FilesystemUtils::SetExecPath(argv[0]);
std::ostream* outputStreamPtr = &std::cout;
std::ofstream outputFile{};
if (argc > 1) {
const std::string outputPath = argv[1];
const string outputPath = argv[1];
outputFile.open(outputPath);
outputStreamPtr = &outputFile;
}
std::ostream& outputStream = *outputStreamPtr;
const auto gameSettings = std::make_shared<shardok::GameSettings>();
const auto gameSettings = std::make_shared<GameSettings>();
auto setter = gameSettings->GetSetter();
shardok::BattalionTypeRegistrar::RegisterBattalionTypes(setter);
const shardok::BattalionTypeSPtr& battalionType = gameSettings->GetGetter().GetBattalionType(
BattalionTypeRegistrar::RegisterBattalionTypes(setter);
const 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) {
std::shared_ptr<shardok::ActionPointDistancesCache> apdCache =
std::make_shared<shardok::ActionPointDistancesCache>();
vector<OneMapInfo> mapInfos{};
for (const string& mapName : GetMapNames()) {
std::shared_ptr<ActionPointDistancesCache> apdCache =
std::make_shared<ActionPointDistancesCache>();
mapInfos.push_back(CalculateMap(mapName, apdCache, battalionType));
}
@@ -1,166 +0,0 @@
//
// Created by Dan Crosby on 3/29/21.
//
#include <algorithm>
#include <array>
#include <climits>
#include <iostream>
#include <memory>
#include <mutex>
#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"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCTypes.h"
#include "src/main/cpp/net/eagle0/shardok/server/ShardokGamesManager.hpp"
#include "src/main/protobuf/net/eagle0/common/random_units.pb.h"
using net::eagle0::common::RandomUnits;
constexpr int THREAD_COUNT = 16;
#define FIXED_SEED true
#if FIXED_SEED
std::mt19937 g;
#else
std::random_device rd;
std::mt19937 g(rd());
#endif
std::mutex randomMutex;
std::vector<std::string> fileNames{};
std::unique_ptr<shardok::ShardokGamesManager> manager;
auto randInt(const int lowInc, const int hiInc) {
std::lock_guard<std::mutex> lock(randomMutex);
return std::uniform_int_distribution(lowInc, hiInc)(g);
}
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);
const auto maxP1UnitCount = std::min<size_t>(20, units.size() - 1);
const auto p1UnitCount = randInt(1, static_cast<int>(maxP1UnitCount));
std::vector<shardok::Unit> p1Units{};
p1Units.reserve(p1UnitCount);
for (int i = 0; i < p1UnitCount; i++) {
auto unit = units[i];
unit.mutate_player_id(0);
p1Units.push_back(std::move(unit));
}
const auto maxP2UnitCount = std::min<size_t>(20, units.size() - p1UnitCount);
const auto p2UnitCount = randInt(1, static_cast<int>(maxP2UnitCount));
std::vector<shardok::Unit> p2Units{};
p2Units.reserve(p2UnitCount);
for (int i = p1UnitCount; i < p1UnitCount + p2UnitCount; i++) {
auto unit = units[i];
unit.mutate_player_id(1);
p2Units.push_back(std::move(unit));
}
std::cout << "map name is " << mapName << ", " << p1UnitCount << " attacking " << p2UnitCount
<< '\n';
std::vector<VictoryConditionProto> attVictoryConditions{
VictoryConditionProto::VICTORY_CONDITION_HOLDS_CRITICAL_TILES,
VictoryConditionProto::VICTORY_CONDITION_LAST_PLAYER_STANDING};
std::vector<VictoryConditionProto> defVictoryConditions{
VictoryConditionProto::VICTORY_CONDITION_LAST_PLAYER_STANDING,
VictoryConditionProto::VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS};
std::vector<shardok::PlayerInfoWithUnits> playerInfos;
playerInfos.reserve(2);
playerInfos.emplace_back(
0,
true,
5,
false,
10000,
attVictoryConditions,
std::vector<shardok::PlayerId>{},
p1Units,
false);
playerInfos.emplace_back(
1,
true,
7,
true,
10000,
defVictoryConditions,
std::vector<shardok::PlayerId>{},
p2Units,
false);
const std::string gameId = std::to_string(randInt(INT_MIN, INT_MAX));
manager->UnlockedCreateSpecifiedGame(
gameId,
playerInfos,
mapName,
"",
month,
TutorialBattleConfigProto{},
"");
}
auto LoadUnits() -> std::vector<shardok::Unit> {
const auto bytes = byte_vector::FromPath("/tmp/random_heroes.bin");
RandomUnits commonUnits;
if (!commonUnits.ParseFromArray(bytes.data(), static_cast<int>(bytes.size()))) {
throw std::runtime_error("Failed to parse /tmp/random_heroes.bin");
}
std::cout << "Loaded " << commonUnits.units_size() << " units\n";
std::vector<shardok::Unit> units{};
units.reserve(commonUnits.units_size());
for (const auto& commonUnit : commonUnits.units()) {
// FIXME: assumes 2 players
units.push_back(shardok::ConvertUnit(commonUnit, 0, {0, 1}));
}
std::shuffle(units.begin(), units.end(), g);
return units;
}
[[noreturn]] void runBattlesThread(const std::vector<shardok::Unit>& units) {
while (true) { runBattle(units); }
}
auto main(int /*argc*/, char** argv) -> int {
#if FIXED_SEED
g.seed(378473);
#endif
FilesystemUtils::SetExecPath(argv[0]);
auto randomUnits = LoadUnits();
std::cout << "Map file directory is " << FilesystemUtils::MapFilesDirectory() << '\n';
fileNames = FilesystemUtils::FilesInDirectory(FilesystemUtils::MapFilesDirectory(), ".e0m");
for (auto& fileName : fileNames) { fileName.resize(fileName.size() - 4); }
manager = std::make_unique<shardok::ShardokGamesManager>(std::vector<std::string>());
std::array<std::thread, THREAD_COUNT> threads;
for (int i = 0; i < THREAD_COUNT; i++) {
threads[i] = std::thread(runBattlesThread, randomUnits);
}
for (auto& thread : threads) { thread.join(); }
}
@@ -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
@@ -20,6 +20,7 @@ namespace shardok {
using GameState = net::eagle0::shardok::storage::fb::GameState;
using Unit = net::eagle0::shardok::storage::fb::Unit;
using net::eagle0::shardok::storage::fb::PlayerInfo;
using std::vector;
using BattalionTypeGetter = std::function<BattalionTypeSPtr(BattalionTypeId)>;
@@ -32,9 +33,9 @@ struct TargetAndAttackLocations {
struct TargetPriorityList {
UnitId attackingUnitId;
std::vector<TargetAndAttackLocations> priorityOrder;
vector<TargetAndAttackLocations> priorityOrder;
TargetPriorityList(const UnitId uid, const std::vector<TargetAndAttackLocations>& po)
TargetPriorityList(const UnitId uid, const vector<TargetAndAttackLocations>& po)
: attackingUnitId(uid),
priorityOrder(po) {}
};
@@ -63,16 +64,16 @@ auto EffectiveDistance(
// Chooses a list of targets in priority order for each unit.
auto GenerateTargetPriorities(
const std::vector<const Unit*>& occupants,
const vector<const Unit*>& occupants,
const HexMap* map,
const CoordsSet& targets,
PlayerId attackerId,
const std::vector<const Unit*>& remainingUnits,
const vector<const Unit*>& remainingUnits,
const APDCache& apdCache,
const ALCache& alCache,
const BattalionTypeGetter& battalionTypeGetter,
ActionPoints braveWaterCost,
bool isLateGame = false) -> std::vector<TargetPriorityList>;
bool isLateGame = false) -> vector<TargetPriorityList>;
} // namespace shardok
@@ -17,6 +17,8 @@
namespace shardok {
using Unit = net::eagle0::shardok::storage::fb::Unit;
using std::shared_ptr;
using std::vector;
class alignas(128) AttackLocations {
private:
@@ -163,7 +165,7 @@ public:
auto CachedLocations(const Coords& singleTarget, bool lateGame) -> const AttackLocations&;
auto CachedLocations(const CoordsSet& targets, bool lateGame) -> AttackLocations;
auto CachedLocations(const std::vector<const Unit*>& enemies, bool lateGame) -> AttackLocations;
auto CachedLocations(const vector<const Unit*>& enemies, bool lateGame) -> AttackLocations;
};
using ALCache = std::unique_ptr<AttackLocationsCache>;
@@ -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
@@ -8,8 +8,6 @@
#include <chrono>
#include <cmath>
#include <future>
#include <iomanip>
#include <iostream>
#include <limits>
#include "AICommandFilter.hpp"
@@ -311,25 +309,25 @@ auto AICommandEvaluator::FindBestCommand(
const auto filteredCommandCount = filteredIndices.size();
const int currentRound = gameState->current_round();
const auto flags = std::cout.flags();
const auto precision = std::cout.precision();
std::cout << "AI_COMMAND_COUNT: Round " << currentRound << ", Player "
<< static_cast<int>(pid) << ", Defender " << (isDefender ? 1 : 0) << ", MinDist "
<< std::fixed << std::setprecision(1) << minDistToEnemies << ", Commands "
<< allCommandCount << " -> " << filteredCommandCount << " ("
<< 100.0 * (allCommandCount - filteredCommandCount) / allCommandCount
<< "% filtered)\n";
std::cout.flags(flags);
std::cout.precision(precision);
printf("AI_COMMAND_COUNT: Round %d, Player %d, Defender %d, MinDist %.1f, Commands %zu -> "
"%zu "
"(%.1f%% filtered)\n",
currentRound,
static_cast<int>(pid),
isDefender ? 1 : 0,
minDistToEnemies,
allCommandCount,
filteredCommandCount,
100.0 * (allCommandCount - filteredCommandCount) / allCommandCount);
}
const auto commandCount = filteredIndices.size();
// 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;
};
@@ -422,7 +420,6 @@ 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++) {
// In each iteration, use a double from [0, 1] as the random roll
auto sequence =
@@ -433,7 +430,7 @@ auto AICommandEvaluator::FindBestCommand(
originalIndex,
remainingLookahead,
maxRepeatCount,
std::make_shared<SequenceRandomGenerator>(std::move(sequence)),
std::make_shared<SequenceRandomGenerator>(sequence),
guessedEngine,
attackerStrategy,
allCastleCoords,
@@ -512,15 +509,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,
@@ -533,9 +528,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;
@@ -580,47 +573,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(
@@ -81,8 +81,8 @@ private:
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(
@@ -13,11 +13,14 @@
namespace shardok {
using std::shared_ptr;
using std::vector;
auto DefenderDistanceBuf(
const Coords &defenderLocation,
const HexMap *hexMap,
const MapId &mapId,
const std::vector<const Unit *> &attackerUnits,
const vector<const Unit *> &attackerUnits,
const APDCache &apdCache,
const ALCache &alCache,
const BattalionTypeGetter &battalionTypeGetter,
@@ -5,27 +5,15 @@
#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 {
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);
}
inline auto IsAIExperiment(const int id) -> bool { return CurrentAIExperimentId() == id; }
@@ -8,29 +8,12 @@
#include "AIFleeDecisionCalculator.hpp"
#include <iomanip>
#include <iostream>
#include "src/main/cpp/net/eagle0/shardok/ai/AIScoreUtilities.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIUnitScoreCalculator.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
namespace shardok {
namespace {
void WritePercent(std::ostream& output, const double value) {
const auto flags = output.flags();
const auto precision = output.precision();
output << std::fixed << std::setprecision(1) << value;
output.flags(flags);
output.precision(precision);
}
} // namespace
auto AIFleeDecisionCalculator::GetFleeCommandIndex(
const CommandList::const_iterator& fleeCommand,
const CommandListSPtr& availableCommands) -> size_t {
@@ -161,14 +144,15 @@ auto AIFleeDecisionCalculator::EvaluateFleeVsFight(
const int fleeSuccessChance = (*fleeCommand)->GetOddsPercentile();
if (enableDebugLogging) {
std::cout << "AI FinalRound: Evaluating flee (odds=" << fleeSuccessChance << "%)...\n";
printf("AI FinalRound: Evaluating flee (odds=%d%%)...\n", fleeSuccessChance);
}
// Check if flee odds are good enough to attempt
if (fleeSuccessChance >= minimumFleeOddsThreshold) {
if (enableDebugLogging) {
std::cout << "AI FinalRound: Good flee odds (" << fleeSuccessChance
<< "% >= " << minimumFleeOddsThreshold << "%), choosing flee\n";
printf("AI FinalRound: Good flee odds (%d%% >= %d%%), choosing flee\n",
fleeSuccessChance,
minimumFleeOddsThreshold);
}
return FleeDecision{
true,
@@ -182,9 +166,9 @@ auto AIFleeDecisionCalculator::EvaluateFleeVsFight(
// If combat situation is hopeless, even bad flee odds are better than certain death
if (combatWinChance <= 0.05 && fleeSuccessChance >= desperateFleeThreshold) {
if (enableDebugLogging) {
std::cout << "AI FinalRound: Combat hopeless (";
WritePercent(std::cout, combatWinChance * 100);
std::cout << "%), desperate flee attempt (" << fleeSuccessChance << "%)\n";
printf("AI FinalRound: Combat hopeless (%.1f%%), desperate flee attempt (%d%%)\n",
combatWinChance * 100,
fleeSuccessChance);
}
return FleeDecision{
true,
@@ -204,21 +188,20 @@ auto AIFleeDecisionCalculator::EvaluateFleeVsFight(
const double adjustedCombatThreshold = combatWinChance * FLEE_VS_COMBAT_MARGIN;
if (enableDebugLogging) {
std::cout << "AI FinalRound: Flee=" << fleeSuccessChance << "%, Combat=";
WritePercent(std::cout, combatWinChance * 100);
std::cout << "%, Threshold=";
WritePercent(std::cout, adjustedCombatThreshold * 100);
std::cout << "% -> ";
printf("AI FinalRound: Flee=%d%%, Combat=%.1f%%, Threshold=%.1f%% -> ",
fleeSuccessChance,
combatWinChance * 100,
adjustedCombatThreshold * 100);
}
if (fleeChance > adjustedCombatThreshold) {
if (enableDebugLogging) { std::cout << "FLEE (better odds)\n"; }
if (enableDebugLogging) { printf("FLEE (better odds)\n"); }
return FleeDecision{
true,
GetFleeCommandIndex(fleeCommand, availableCommands),
"Flee has better expected outcome"};
} else {
if (enableDebugLogging) { std::cout << "FIGHT (better expected outcome)\n"; }
if (enableDebugLogging) { printf("FIGHT (better expected outcome)\n"); }
// Return 0 to indicate we should use standard command selection
return FleeDecision{
false,
@@ -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,6 +15,7 @@
namespace shardok {
using net::eagle0::shardok::storage::fb::Coords;
using std::shared_ptr;
struct CoordsAndDistance {
Coords coords;
@@ -16,6 +16,8 @@ namespace shardok {
using ScoreValue = double;
using std::vector;
using GameState = net::eagle0::shardok::storage::fb::GameState;
using PlayerInfo = net::eagle0::shardok::storage::fb::PlayerInfo;
using Unit = net::eagle0::shardok::storage::fb::Unit;
@@ -116,6 +116,15 @@ auto CalculateTimeBudget(
const auto clampedBudgetMs = std::clamp(budgetMs, 200.0, maxBudgetMs);
const auto remainingBudget = std::chrono::milliseconds(static_cast<int64_t>(clampedBudgetMs));
// TEMPORARY DEBUG OUTPUT
printf("[DEBUG CalculateTimeBudget] numCommands=%zu, msPerCommand=%.2f, budgetMs=%.2f, "
"clampedBudgetMs=%.2f, isClose=%d\n",
numCommands,
msPerCommand,
budgetMs,
clampedBudgetMs,
isClose);
// Get minimum depth requirement
const size_t minDepth = settingsGetter.Backing().min_lookahead_turns();
@@ -125,4 +134,4 @@ auto CalculateTimeBudget(
.isCloseToEnemy = isClose};
}
} // namespace shardok
} // namespace shardok
@@ -25,11 +25,6 @@ class AIEvaluationCounter {
public:
AIEvaluationCounter();
~AIEvaluationCounter();
AIEvaluationCounter(const AIEvaluationCounter&) = delete;
auto operator=(const AIEvaluationCounter&) -> AIEvaluationCounter& = delete;
AIEvaluationCounter(AIEvaluationCounter&&) = delete;
auto operator=(AIEvaluationCounter&&) -> AIEvaluationCounter& = delete;
static int GetCurrentCount();
};
@@ -46,8 +41,8 @@ struct AITimeBudget {
// If isAllAiBattle is true, uses allAiBattleTimeBudgetMaximum instead of the normal maximum.
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(
@@ -14,6 +14,9 @@
namespace shardok {
using net::eagle0::shardok::storage::fb::Unit;
using std::shared_ptr;
using std::vector;
using ScoreValue = double;
class AttackLocations;
@@ -26,8 +29,8 @@ auto GetRangedAttackBonus(
const Terrain *terrain,
const AttackLocations &attackLocations,
int roundsRemaining,
const std::vector<const Unit *> &attackerUnits,
const std::vector<const Unit *> &defenderUnits,
const vector<const Unit *> &attackerUnits,
const vector<const Unit *> &defenderUnits,
int meteorRange,
double minVigorToCast,
ActionPoints archeryActionPointCost = 0) -> double;
@@ -35,10 +38,10 @@ auto GetRangedAttackBonus(
auto UnitValue(
const Unit *unit,
bool isAttacker,
const std::vector<const Unit *> &attackerUnits,
const vector<const Unit *> &attackerUnits,
bool attackerWantsCastles,
bool includeCastleBonus,
const std::vector<const Unit *> &defenderUnits,
const vector<const Unit *> &defenderUnits,
const HexMap *map,
int roundsRemaining,
const AttackLocations &locationsThisSideCanAttackFrom,
@@ -5,8 +5,6 @@
#include "IterativeDeepeningAI.hpp"
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <limits>
#include <numeric>
#include <utility>
@@ -23,20 +21,6 @@ namespace shardok {
constexpr bool kDebugIterativeDeepeningTimings = true;
namespace {
void WriteFixed(std::ostream& output, const double value, const int precision) {
const auto flags = output.flags();
const auto originalPrecision = output.precision();
output << std::fixed << std::setprecision(precision) << value;
output.flags(flags);
output.precision(originalPrecision);
}
} // namespace
IterativeDeepeningAI::IterativeDeepeningAI(
const PlayerId playerId,
const bool isDefender,
@@ -71,7 +55,7 @@ auto IterativeDeepeningAI::IterativeSearch(
// g_transpositionTable.clear(); // Uncomment to test without cross-search caching
if (commands->empty()) {
if (kDebugIterativeDeepeningTimings) {
std::cout << "ID AI: Commands are empty, returning early\n";
printf("ID AI: Commands are empty, returning early\n");
}
result.searchCompleted = true;
return result;
@@ -185,21 +169,21 @@ auto IterativeDeepeningAI::IterativeSearch(
// Log if best command changed from previous depth
if (currentDepth > 1 && currentBestCommand != previousBestCommand) {
if (kDebugIterativeDeepeningTimings) {
std::cout << "ID AI: Best command changed at depth " << currentDepth << ":\n"
<< " Depth " << currentDepth - 1 << " best: command "
<< previousBestCommand << " (score ";
WriteFixed(std::cout, scoresByDepth[previousBestCommand][currentDepth - 1], 2);
std::cout << ") - type: "
<< net::eagle0::shardok::common::CommandType_Name(
(*commands)[previousBestCommand]->GetCommandType())
<< '\n'
<< " Depth " << currentDepth << " best: command "
<< currentBestCommand << " (score ";
WriteFixed(std::cout, currentBestScore, 2);
std::cout << ") - type: "
<< net::eagle0::shardok::common::CommandType_Name(
(*commands)[currentBestCommand]->GetCommandType())
<< '\n';
printf("ID AI: Best command changed at depth %lu:\n", currentDepth);
printf(" Depth %lu best: command %zu (score %.2f) - type: %s\n",
currentDepth - 1,
previousBestCommand,
scoresByDepth[previousBestCommand][currentDepth - 1],
net::eagle0::shardok::common::CommandType_Name(
(*commands)[previousBestCommand]->GetCommandType())
.c_str());
printf(" Depth %lu best: command %zu (score %.2f) - type: %s\n",
currentDepth,
currentBestCommand,
currentBestScore,
net::eagle0::shardok::common::CommandType_Name(
(*commands)[currentBestCommand]->GetCommandType())
.c_str());
}
}
@@ -255,9 +239,9 @@ auto IterativeDeepeningAI::IterativeSearch(
static_cast<double>(initialBudgetMs.count());
if (budgetUsedPercent > 0.5) {
std::cout << "ID AI: Stopping after depth " << currentDepth << " - used ";
WriteFixed(std::cout, budgetUsedPercent * 100, 1);
std::cout << "% of time budget\n";
printf("ID AI: Stopping after depth %lu - used %.1f%% of time budget\n",
currentDepth,
budgetUsedPercent * 100);
completionReason = EvaluationCompletionReason::NOT_ENOUGH_TIME_TO_CONTINUE;
break;
}
@@ -287,9 +271,9 @@ auto IterativeDeepeningAI::IterativeSearch(
// Validation: if completion reason is RAN_OUT_OF_COMMANDS, evaluation should be 100%
if (completionReason == EvaluationCompletionReason::RAN_OUT_OF_COMMANDS &&
result.commandCountEvaluated < result.availableCommandCount) {
std::cerr << "ERROR: Completion reason RAN_OUT_OF_COMMANDS but evaluation "
<< result.commandCountEvaluated << '/' << result.availableCommandCount
<< " < 100%\n";
printf("ERROR: Completion reason RAN_OUT_OF_COMMANDS but evaluation %lu/%zu < 100%%\n",
result.commandCountEvaluated,
result.availableCommandCount);
}
// Print TranspositionTable statistics
@@ -15,12 +15,9 @@
#include <chrono>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#endif
#include <iomanip>
#include <iostream>
#include <set>
#include <unordered_map>
@@ -177,20 +174,25 @@ auto ShardokAIClient::StandardChooseCommandIndex(
// Verify that the AI's guessed state produces the same available commands as reality
if (commandCount != realAvailableCommands->size()) {
std::cerr << "AI command count mismatch: guessed=" << commandCount
<< " real=" << realAvailableCommands->size() << " player=" << playerId
<< " round=" << guessedState->current_round() << "\n";
fprintf(stderr,
"AI command count mismatch: guessed=%zu real=%zu player=%d round=%d\n",
commandCount,
realAvailableCommands->size(),
playerId,
guessedState->current_round());
const auto maxDump = std::max(commandCount, realAvailableCommands->size());
for (size_t i = 0; i < maxDump; i++) {
const auto realName = i < realAvailableCommands->size()
? net::eagle0::shardok::common::CommandType_Name(
(*realAvailableCommands)[i]->GetCommandType())
: "(none)";
const auto guessedName = i < commandCount
? net::eagle0::shardok::common::CommandType_Name(
(*guessedCommands)[i]->GetCommandType())
: "(none)";
const auto *realName = i < realAvailableCommands->size()
? net::eagle0::shardok::common::CommandType_Name(
(*realAvailableCommands)[i]->GetCommandType())
.c_str()
: "(none)";
const auto *guessedName = i < commandCount
? net::eagle0::shardok::common::CommandType_Name(
(*guessedCommands)[i]->GetCommandType())
.c_str()
: "(none)";
const int realActor = i < realAvailableCommands->size()
? (*realAvailableCommands)[i]->GetActorUnitId()
: -1;
@@ -204,10 +206,17 @@ auto ShardokAIClient::StandardChooseCommandIndex(
: -1;
const int guessedRow = i < commandCount ? (*guessedCommands)[i]->GetTargetRow() : -1;
const int guessedCol = i < commandCount ? (*guessedCommands)[i]->GetTargetColumn() : -1;
std::cerr << " [" << i << "] real: " << realName << " unit=" << realActor << " ("
<< realRow << "," << realCol << ") guessed: " << guessedName
<< " unit=" << guessedActor << " (" << guessedRow << "," << guessedCol
<< ")\n";
fprintf(stderr,
" [%zu] real: %s unit=%d (%d,%d) guessed: %s unit=%d (%d,%d)\n",
i,
realName,
realActor,
realRow,
realCol,
guessedName,
guessedActor,
guessedRow,
guessedCol);
}
// Find positions in real commands that are missing from guessed commands
@@ -229,34 +238,45 @@ auto ShardokAIClient::StandardChooseCommandIndex(
for (const auto &[unitId, positions] : realPositions) {
for (const auto &[row, col] : positions) {
if (!guessedPositions[unitId].contains({row, col})) {
std::cerr << " Missing from guessed: unit=" << unitId << " pos=(" << row << ","
<< col << ")";
fprintf(stderr,
" Missing from guessed: unit=%d pos=(%d,%d)",
unitId,
row,
col);
const Coords missingCoords(row, col);
const auto *occupant = guessedState.GetOccupant(missingCoords);
if (occupant) {
std::cerr << " -> guessed occupant: unit_id=" << occupant->unit_id()
<< " player=" << occupant->player_id()
<< " status=" << static_cast<int>(occupant->status()) << " loc=("
<< occupant->location().row() << ","
<< occupant->location().column()
<< ") hidden=" << occupant->hidden()
<< " battalion_size=" << occupant->battalion().size() << "\n";
fprintf(stderr,
" -> guessed occupant: unit_id=%d player=%d status=%d "
"loc=(%d,%d) hidden=%d battalion_size=%d\n",
occupant->unit_id(),
occupant->player_id(),
static_cast<int>(occupant->status()),
occupant->location().row(),
occupant->location().column(),
occupant->hidden(),
occupant->battalion().size());
} else {
std::cerr << " -> no occupant in guessed state\n";
fprintf(stderr, " -> no occupant in guessed state\n");
}
}
}
}
// Dump all guessed state units for full picture
std::cerr << " Guessed state units (" << guessedState->units()->size() << " total):\n";
fprintf(stderr, " Guessed state units (%u total):\n", guessedState->units()->size());
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=("
<< u->location().row() << "," << u->location().column()
<< ") hidden=" << u->hidden()
<< " starting_pos_idx=" << u->starting_position_index() << "\n";
fprintf(stderr,
" unit_id=%d player=%d status=%d loc=(%d,%d) hidden=%d "
"starting_pos_idx=%d\n",
u->unit_id(),
u->player_id(),
static_cast<int>(u->status()),
u->location().row(),
u->location().column(),
u->hidden(),
u->starting_position_index());
}
throw ShardokInternalErrorException(
@@ -336,9 +356,9 @@ auto ShardokAIClient::StandardChooseCommandIndex(
// Write the flatbuffer game state to file using SaveTo method
if (guessedState.SaveTo(statePath)) {
std::cout << "Game state dumped to: " << statePath << '\n';
printf("Game state dumped to: %s\n", statePath.c_str());
} else {
std::cout << "Failed to dump game state to: " << statePath << '\n';
printf("Failed to dump game state to: %s\n", statePath.c_str());
}
#endif // ENABLE_MCTS_DEBUG_DUMP
@@ -376,17 +396,19 @@ auto ShardokAIClient::StandardChooseCommandIndex(
if constexpr (kPerformanceLogging) {
if (result.commandCountEvaluated < result.availableCommandCount) {
std::cout << "ID AI: Depth " << result.depthAchieved << " - evaluated "
<< result.commandCountEvaluated << '/' << result.availableCommandCount
<< " commands\n";
printf("ID AI: Depth %d - evaluated %lu/%zu commands\n",
result.depthAchieved,
result.commandCountEvaluated,
result.availableCommandCount);
}
const auto chosenCommandType =
(*realAvailableCommands)[result.chosenIndex]->GetCommandType();
std::cout << "ID AI: Search complete - achieved depth " << result.depthAchieved
<< " for best command " << result.chosenIndex << " ("
<< net::eagle0::shardok::common::CommandType_Name(chosenCommandType) << ")\n";
printf("ID AI: Search complete - achieved depth %d for best command %zu (%s)\n",
result.depthAchieved,
result.chosenIndex,
net::eagle0::shardok::common::CommandType_Name(chosenCommandType).c_str());
std::cout << std::flush;
fflush(stdout);
}
return result;
@@ -488,7 +510,7 @@ auto ShardokAIClient::ChooseCommandIndex(
totalChoices++;
if (totalChoices % 1000 == 0) {
std::cout << "TYPES CHOSEN:\n";
printf("TYPES CHOSEN:\n");
vector<std::pair<int, CommandType>> choices{};
for (int i = 0; i <= net::eagle0::shardok::common::CommandType_MAX; i++) {
if (typeChosenCount[i] != 0) {
@@ -499,9 +521,9 @@ auto ShardokAIClient::ChooseCommandIndex(
std::ranges::sort(choices);
std::ranges::reverse(choices);
for (const auto &[index, choice] : choices) {
std::cout << std::setw(5) << index << ' ' << CommandType_Name(choice) << '\n';
printf("%5d %s\n", index, CommandType_Name(choice).c_str());
}
std::cout << "\n\n";
printf("\n\n");
}
return results;
@@ -511,7 +533,7 @@ auto ShardokAIClient::ChooseCommandIndex(const ShardokEngine &engine) const
-> CommandChoiceResults {
if (const auto &availableCommands = engine.GetAvailableCommandsForAIPlayer(playerId);
availableCommands->empty()) {
std::cout << "no commands for player " << playerId << '\n';
printf("no commands for player %d\n", playerId);
throw ShardokInternalErrorException(
"Asked to choose a command, but there are none available");
} else {
@@ -4,9 +4,9 @@
#include "TranspositionTable.hpp"
#include <cinttypes>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
namespace shardok {
@@ -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
@@ -100,23 +100,15 @@ void TranspositionTable::clear() {
current_age = 0;
}
void TranspositionTable::printStats() const { printStats(std::cout); }
void TranspositionTable::printStats(std::ostream& output) const {
const auto flags = output.flags();
const auto precision = output.precision();
output << "TranspositionTable Stats:\n"
<< " Probes: " << stats.probes.load() << '\n'
<< " Hits: " << stats.hits.load() << " (" << std::fixed << std::setprecision(1)
<< stats.hitRate() << "%)\n"
<< " Stores: " << stats.stores.load() << '\n'
<< " Collisions: " << stats.collisions.load() << '\n'
<< " Table size: " << TABLE_SIZE << " entries ("
<< (TABLE_SIZE * sizeof(TTEntry)) / (1024.0 * 1024.0) << " MB)\n";
output.flags(flags);
output.precision(precision);
void TranspositionTable::printStats() const {
printf("TranspositionTable Stats:\n");
printf(" Probes: %" PRIu64 "\n", stats.probes.load());
printf(" Hits: %" PRIu64 " (%.1f%%)\n", stats.hits.load(), stats.hitRate());
printf(" Stores: %" PRIu64 "\n", stats.stores.load());
printf(" Collisions: %" PRIu64 "\n", stats.collisions.load());
printf(" Table size: %zu entries (%.1f MB)\n",
TABLE_SIZE,
(TABLE_SIZE * sizeof(TTEntry)) / (1024.0 * 1024.0));
}
} // namespace shardok
} // namespace shardok
@@ -8,7 +8,6 @@
#include <atomic>
#include <cstdint>
#include <optional>
#include <ostream>
#include <vector>
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
@@ -28,8 +27,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 +59,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,11 +77,10 @@ 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;
void printStats(std::ostream& output) const;
};
// Global instance for the AI to use
@@ -6,8 +6,6 @@
#include <algorithm>
#include <chrono>
#include <iomanip>
#include <iostream>
#include <numeric>
#include "ShardokAction.hpp"
@@ -37,23 +35,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::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;
}
} // namespace
// Shared cache for legal actions (uses lock-free parallel hash map for thread safety)
@@ -299,7 +280,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 +353,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(
@@ -503,9 +509,7 @@ size_t ShardokGameEngine::mapFilteredIndexToOriginal(
return shardokAction->getIndex();
}
void ShardokGameEngine::reportCacheStatistics() const { reportCacheStatistics(std::cout); }
void ShardokGameEngine::reportCacheStatistics(std::ostream& output) const {
void ShardokGameEngine::reportCacheStatistics() const {
const uint64_t hits = cacheHits_.load(std::memory_order_relaxed);
const uint64_t misses = cacheMisses_.load(std::memory_order_relaxed);
const uint64_t hashTime = timeInHashComputation_.load(std::memory_order_relaxed);
@@ -519,29 +523,27 @@ void ShardokGameEngine::reportCacheStatistics(std::ostream& output) const {
const double avgActionsTimeUs =
misses > 0 ? static_cast<double>(actionsTime) / static_cast<double>(misses) : 0.0;
printf("Legal Actions Cache Stats:\n");
printf(" Lookups: %llu hits, %llu misses, %.1f%% hit rate, %zu entries\n",
static_cast<unsigned long long>(hits),
static_cast<unsigned long long>(misses),
hitRate * 100.0,
legalActionsCache_.size());
printf(" Timing: %.2f us avg hash, %.2f us avg actions (on miss)\n",
avgHashTimeUs,
avgActionsTimeUs);
printf(" Total time: %.2f ms in hash, %.2f ms in actions\n",
hashTime / 1000.0,
actionsTime / 1000.0);
// Calculate if transposition table is worth it
const double timeWithCache = hashTime + actionsTime;
const double timeWithoutCache =
avgActionsTimeUs * static_cast<double>(totalLookups); // All lookups recompute
const double savings = (timeWithoutCache - timeWithCache) / timeWithoutCache * 100.0;
const auto flags = output.flags();
const auto precision = output.precision();
output << "Legal Actions Cache Stats:\n"
<< " Lookups: " << hits << " hits, " << misses << " misses, " << std::fixed
<< std::setprecision(1) << hitRate * 100.0 << "% hit rate, "
<< legalActionsCache_.size() << " entries\n"
<< " Timing: " << std::setprecision(2) << avgHashTimeUs << " us avg hash, "
<< avgActionsTimeUs << " us avg actions (on miss)\n"
<< " Total time: " << hashTime / 1000.0 << " ms in hash, " << actionsTime / 1000.0
<< " ms in actions\n"
<< " Cache savings: " << std::setprecision(1) << savings << "% vs. no cache ("
<< std::setprecision(2) << (timeWithoutCache - timeWithCache) / 1000.0
<< " ms saved)\n";
output.flags(flags);
output.precision(precision);
printf(" Cache savings: %.1f%% vs. no cache (%.2f ms saved)\n",
savings,
(timeWithoutCache - timeWithCache) / 1000.0);
}
}
@@ -9,7 +9,6 @@
#include <functional>
#include <gtl/phmap.hpp>
#include <memory>
#include <ostream>
#include <vector>
#include "src/main/cpp/net/eagle0/common/mcts/abstract/MCTSGameEngine.hpp"
@@ -93,7 +92,6 @@ public:
// Report transposition table statistics
void reportCacheStatistics() const;
void reportCacheStatistics(std::ostream& output) const;
// Reset cache statistics
void resetCacheStatistics();
@@ -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,8 @@
namespace shardok {
using shardok::PlayerId;
using std::future;
using std::vector;
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;
@@ -20,6 +20,7 @@ namespace shardok {
using GameState = net::eagle0::shardok::storage::fb::GameState;
using net::eagle0::shardok::storage::fb::PlayerInfo;
using std::vector;
using ScoreValue = double;
@@ -8,9 +8,6 @@
#include <atomic>
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <unordered_map>
#include "private/AIScoreCalculatorSharedUtilities.hpp"
@@ -60,12 +57,11 @@ struct AttackerScorePerformanceLogger {
if (callCount.load() % LOG_INTERVAL == 0) {
double intervalAvg = intervalTime.load() / LOG_INTERVAL;
double overallAvg = totalTime.load() / callCount.load();
std::ostringstream message;
message << std::fixed << std::setprecision(1)
<< "AttackerScoreForState: " << callCount.load() << " calls, last "
<< LOG_INTERVAL << " avg: " << intervalAvg * 1000000.0
<< " µs, overall avg: " << overallAvg * 1000000.0 << " µs\n";
std::cout << message.str();
printf("AttackerScoreForState: %d calls, last %d avg: %.1f µs, overall avg: %.1f µs\n",
callCount.load(),
LOG_INTERVAL,
intervalAvg * 1000000.0,
overallAvg * 1000000.0);
intervalTime.store(0.0); // Reset for next interval
}
}
@@ -144,10 +140,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();
@@ -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) {
@@ -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"
@@ -333,7 +332,6 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
// Create player info protos
std::vector<net::eagle0::shardok::common::PlayerInfo> playerInfoProtos;
playerInfoProtos.reserve(2);
// Attacker info
net::eagle0::shardok::common::PlayerInfo attackerInfo;
@@ -344,7 +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(std::move(attackerInfo));
playerInfoProtos.push_back(attackerInfo);
// Defender info
net::eagle0::shardok::common::PlayerInfo defenderInfo;
@@ -355,15 +353,13 @@ 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);
playerInfoProtos.push_back(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();
for (int i = 0; i < attackerUnitCount; ++i) {
for (int i = 0; i < config_.attacker().units_size(); ++i) {
const auto& unitConfig = config_.attacker().units(i);
net::eagle0::shardok::storage::fb::Unit unit{};
@@ -453,13 +449,12 @@ 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
const int defenderUnitIdStart = attackerUnitCount;
const int defenderUnitCount = config_.defender().units_size();
for (int i = 0; i < defenderUnitCount; ++i) {
int defenderUnitIdStart = config_.attacker().units_size();
for (int i = 0; i < config_.defender().units_size(); ++i) {
const auto& unitConfig = config_.defender().units(i);
net::eagle0::shardok::storage::fb::Unit unit{};
@@ -549,7 +544,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
@@ -683,9 +678,8 @@ BattleResult AiBattleSimulator::RunConfiguredSetupPhase(ShardokEngine& engine) {
playerConfig,
int unitIdOffset) {
std::vector<UnitPlacementInfo> placements;
const int unitCount = playerConfig.units_size();
placements.reserve(unitCount);
for (int i = 0; i < unitCount; ++i) {
placements.reserve(playerConfig.units_size());
for (int i = 0; i < playerConfig.units_size(); ++i) {
const auto& unitConfig = playerConfig.units(i);
if (!unitConfig.has_fixed_position()) {
throw std::invalid_argument(
@@ -917,7 +911,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; }
@@ -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; }
@@ -4,10 +4,9 @@
#include "AIPerformanceRunner.hpp"
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include "PerformanceTestGameStateBuilder.hpp"
@@ -19,15 +18,7 @@
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/command_type.pb.h"
using shardok::AIAlgorithmType;
using shardok::AIPerformanceMetrics;
using shardok::EvaluationCompletionReason;
using shardok::PerformanceTestConfig;
using shardok::PerformanceTestGameStateBuilder;
using shardok::PlayerId;
using shardok::ShardokAIClient;
using shardok::ShardokEngine;
namespace ai_testing_common = shardok::ai_testing_common;
using namespace shardok;
namespace {
@@ -48,7 +39,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 +55,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 +66,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 +88,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 +155,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
@@ -311,4 +300,4 @@ int main(int argc, char* argv[]) {
}
return 0;
}
}
@@ -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
@@ -5,15 +5,13 @@
#ifndef EAGLE0_SHARDOK_CONTROLLER_GAME_UPDATE_RECEIVER_HPP
#define EAGLE0_SHARDOK_CONTROLLER_GAME_UPDATE_RECEIVER_HPP
#include <atomic>
#include <vector>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-redundant-constexpr-static-def"
#include "src/main/protobuf/net/eagle0/shardok/api/action_result_view.pb.h"
#pragma GCC diagnostic pop
namespace shardok {
using std::vector;
class GameUpdateReceiver {
private:
@@ -27,7 +25,7 @@ public:
virtual ~GameUpdateReceiver() = default;
virtual void ReceiveActionResult(const net::eagle0::shardok::api::ActionResultView& result) = 0;
virtual void ReceiveActionResults(
const std::vector<net::eagle0::shardok::api::ActionResultView>& results) = 0;
const vector<net::eagle0::shardok::api::ActionResultView>& results) = 0;
[[nodiscard]] auto GetIdentifier() const -> long { return identifier; }
};
@@ -24,6 +24,12 @@
namespace shardok {
using net::eagle0::common::ScopedShardokLatencyTrace;
using std::condition_variable;
using std::mutex;
using std::scoped_lock;
using std::string;
using std::unique_lock;
using std::weak_ptr;
using net::eagle0::shardok::common::GameStatus;
@@ -34,7 +40,7 @@ private:
PlayerId shardokPlayerId;
int eagleFactionId;
std::string message;
string message;
public:
MismatchedPlayerIdException(const PlayerId shardokPid, const int eagleFid)
@@ -51,7 +57,7 @@ class MissingPlayerIdException : public std::exception {
private:
PlayerId shardokPlayerId;
std::string message;
string message;
public:
MissingPlayerIdException(const PlayerId shardokPid) : shardokPlayerId(shardokPid) {
@@ -61,9 +67,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 +79,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 +108,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 +121,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 +129,7 @@ void ShardokGameController::DoAIThread() {
size_t expectedHistoryCount;
{
std::unique_lock lk(masterLock);
unique_lock lk(masterLock);
if (engine->GameIsOver()) {
aiThreadKeepGoing = false;
@@ -168,7 +173,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 +229,7 @@ void ShardokGameController::DoAIThread() {
ShardokGameController::~ShardokGameController() {
{
std::unique_lock lk(masterLock);
unique_lock lk(masterLock);
aiThreadKeepGoing = false;
aiCondition.notify_one();
}
@@ -232,7 +237,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 +257,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 +265,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 +304,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 +313,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 +357,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 +369,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 +401,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 +412,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 +431,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 +484,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 +505,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 +557,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,8 +9,6 @@
#include "AvailableCommandsFactory.hpp"
#include <algorithm>
#include <iterator>
#include <memory>
#include <ranges>
#include "src/main/cpp/net/eagle0/shardok/library/FireUtils.hpp"
@@ -23,6 +21,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 {
@@ -33,25 +33,25 @@ private:
if (pid == UNCONTROLLED_PLAYER_ID) return false;
const auto &player = std::find_if(
std::begin(*gameState->player_infos()),
std::end(*gameState->player_infos()),
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::find_if(
std::begin(*gameState->player_infos()),
std::end(*gameState->player_infos()),
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;
const vector<shared_ptr<const CommandFactory>> commandFactories;
const PlayerSetupCommandFactory playerSetupCommandFactory;
// AvailableCommands helpers for a single unit
@@ -97,7 +97,7 @@ auto AvailableCommandsFactoryImpl::GetPlayerSetupCommands(
gameState,
reinforcementPositions);
return std::make_shared<CommandList>(previewCommands);
return make_shared<CommandList>(previewCommands);
}
void AvailableCommandsFactoryImpl::AddAvailableCommandsForOneUnit(
@@ -116,8 +116,7 @@ void AvailableCommandsFactoryImpl::AddAvailableCommandsForOneUnit(
const Coords &currentCoords = unit->location();
const bool isEligibleCharger = unit->unit_id() == gameState->eligible_charger_id();
std::vector<UnitId> possibleChargees{};
possibleChargees.reserve(gameState->possible_chargee_ids()->size());
vector<UnitId> possibleChargees{};
for (const UnitId cid : *gameState->possible_chargee_ids()) {
if (cid != -1) { possibleChargees.push_back(cid); }
}
@@ -196,9 +195,11 @@ auto AvailableCommandsFactoryImpl::GetAvailableCommands(
const std::optional<std::vector<Coords>> &reinforcementPositions) const -> CommandListSPtr {
CommandList commands{};
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;
@@ -238,7 +239,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());
@@ -251,7 +251,7 @@ auto AvailableCommandsFactoryImpl::GetAvailableCommands(
}
if (commands.empty()) return nullptr;
return std::make_shared<CommandList>(commands);
return make_shared<CommandList>(commands);
}
} // namespace shardok
@@ -9,7 +9,6 @@
#ifndef EAGLE0_SHARDOK_LIBRARY_AVAILABLE_COMMANDS_FACTORY_HPP
#define EAGLE0_SHARDOK_LIBRARY_AVAILABLE_COMMANDS_FACTORY_HPP
#include <memory>
#include <optional>
#include <vector>
@@ -21,12 +20,14 @@
namespace shardok {
using UnitIdOptional = std::optional<UnitId>;
using std::optional;
using std::unique_ptr;
using UnitIdOptional = optional<UnitId>;
class AvailableCommandsFactory {
public:
static auto MakeAvailableCommandsFactory(const SettingsGetter &settingsGetter)
-> std::unique_ptr<AvailableCommandsFactory>;
-> unique_ptr<AvailableCommandsFactory>;
virtual ~AvailableCommandsFactory() = default;
@@ -8,18 +8,17 @@
#include "BattalionType.hpp"
#include <iostream>
#include <mutex>
#include <unordered_map>
#include "src/main/cpp/net/eagle0/common/MapUtils.hpp"
namespace shardok {
using std::unordered_map;
using BattalionTypeId = net::eagle0::shardok::storage::fb::BattalionTypeId;
ActionCost actionCostForKey(
const std::unordered_map<std::string, std::string> &map,
const std::string &key) {
ActionCost actionCostForKey(const unordered_map<string, string> &map, const string &key) {
const int pointCost = IntForKey(map, key);
return pointCost == -1 ? IMPOSSIBLE_ACTION_COST : ActionCost::StandardActionCost(pointCost);
@@ -28,7 +27,7 @@ ActionCost actionCostForKey(
std::shared_ptr<const BattalionType> BattalionType::NewBattalionTypeFromMap(
const std::unordered_map<std::string, std::string> &map) {
try {
return std::make_shared<const BattalionType>(BattalionType{
auto *newType = new BattalionType{
map.at("TypeName"),
BattalionTypeId(std::stoi(map.at("TypeId"))),
IntForKey(map, "Capacity"),
@@ -172,9 +171,11 @@ std::shared_ptr<const BattalionType> BattalionType::NewBattalionTypeFromMap(
DoubleForKey(map, "AmbushOddsWithoutHero"),
DoubleForKey(map, "BaseStunChanceOnCharge"),
DoubleForKey(map, "TrainingDifferenceMultiplierForStunChanceOnCharge"),
DoubleForKey(map, "MonthlyFoodCost")});
DoubleForKey(map, "MonthlyFoodCost")};
return std::shared_ptr<const BattalionType>(newType);
} catch (const std::out_of_range &e) {
std::cerr << "Failed to parse file map; caught exception " << e.what() << '\n';
printf("Failed to parse file map; caught exception %s\n", e.what());
return nullptr;
}
}
@@ -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 {
@@ -13,8 +13,7 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <iomanip>
#include <sstream>
#include <cstdio>
#include <string>
#include <vector>
@@ -245,27 +244,33 @@ public:
}
[[nodiscard]] auto ToString() const -> std::string {
std::ostringstream out;
out << std::fixed << std::setprecision(6)
<< "slashing=" << GetNormalDamageOfType(DamageType_slashing) << " / "
<< GetPenetratingDamageOfType(DamageType_slashing)
<< ", puncturing=" << GetNormalDamageOfType(DamageType_puncturing) << " / "
<< GetPenetratingDamageOfType(DamageType_puncturing)
<< ", stabbing=" << GetNormalDamageOfType(DamageType_stabbing) << " / "
<< GetPenetratingDamageOfType(DamageType_stabbing)
<< ", crushing=" << GetNormalDamageOfType(DamageType_crushing) << " / "
<< GetPenetratingDamageOfType(DamageType_crushing)
<< ", fire=" << GetNormalDamageOfType(DamageType_fire) << " / "
<< GetPenetratingDamageOfType(DamageType_fire)
<< ", cold=" << GetNormalDamageOfType(DamageType_cold) << " / "
<< GetPenetratingDamageOfType(DamageType_cold)
<< ", lightning=" << GetNormalDamageOfType(DamageType_lightning) << " / "
<< GetPenetratingDamageOfType(DamageType_lightning)
<< ", aessence=" << GetNormalDamageOfType(DamageType_aessence) << " / "
<< GetPenetratingDamageOfType(DamageType_aessence)
<< ", impact=" << GetNormalDamageOfType(DamageType_impact) << " / "
<< GetPenetratingDamageOfType(DamageType_impact);
return out.str();
std::array<char, 1024> buffer{};
snprintf(
buffer.data(),
buffer.size(),
"slashing=%f / %f, puncturing=%f / %f, stabbing=%f / %f, crushing=%f / %f, "
"fire=%f / "
"%f, cold=%f / %f, lightning=%f / %f, aessence=%f / %f, impact=%f / %f",
GetNormalDamageOfType(DamageType_slashing),
GetPenetratingDamageOfType(DamageType_slashing),
GetNormalDamageOfType(DamageType_puncturing),
GetPenetratingDamageOfType(DamageType_puncturing),
GetNormalDamageOfType(DamageType_stabbing),
GetPenetratingDamageOfType(DamageType_stabbing),
GetNormalDamageOfType(DamageType_crushing),
GetPenetratingDamageOfType(DamageType_crushing),
GetNormalDamageOfType(DamageType_fire),
GetPenetratingDamageOfType(DamageType_fire),
GetNormalDamageOfType(DamageType_cold),
GetPenetratingDamageOfType(DamageType_cold),
GetNormalDamageOfType(DamageType_lightning),
GetPenetratingDamageOfType(DamageType_lightning),
GetNormalDamageOfType(DamageType_aessence),
GetPenetratingDamageOfType(DamageType_aessence),
GetNormalDamageOfType(DamageType_impact),
GetPenetratingDamageOfType(DamageType_impact));
return {buffer.data()};
}
class Builder {
@@ -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
@@ -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);
@@ -10,10 +10,12 @@
namespace shardok {
using std::vector;
auto ShardokAction::Execute(
const GameStateW& currentState,
const std::shared_ptr<RandomGenerator>& generator) const -> std::vector<ActionResult> {
std::vector<ActionResult> results = InternalExecute(currentState, generator);
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
vector<ActionResult> results = InternalExecute(currentState, generator);
return results;
}
@@ -21,10 +23,10 @@ auto ShardokAction::Execute(
auto ShardokAction::ExecuteWithRoll(
const GameStateW& currentState,
const std::shared_ptr<RandomGenerator>& generator,
const std::optional<int32_t> roll) const -> std::vector<ActionResult> {
std::vector<ActionResult> results = InternalExecuteWithRoll(currentState, generator, roll);
const std::optional<int32_t> roll) const -> vector<ActionResult> {
vector<ActionResult> results = InternalExecuteWithRoll(currentState, generator, roll);
return results;
}
} // namespace shardok
} // namespace shardok
@@ -9,9 +9,6 @@
#ifndef EAGLE0_SHARDOK_LIBRARY_SHARDOK_ACTION_HPP
#define EAGLE0_SHARDOK_LIBRARY_SHARDOK_ACTION_HPP
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "ShardokException.hpp"
@@ -25,6 +22,8 @@
namespace shardok {
using net::eagle0::shardok::storage::ActionResult;
using std::shared_ptr;
using std::vector;
using PercentileRollOdds = net::eagle0::shardok::storage::Odds;
class IllegalActionException : public ShardokException {
@@ -57,9 +57,9 @@ public:
}
};
using CommandSPtr = std::shared_ptr<ShardokCommand>;
using CommandList = std::vector<CommandSPtr>;
using CommandListSPtr = std::shared_ptr<const CommandList>;
using CommandSPtr = shared_ptr<ShardokCommand>;
using CommandList = vector<CommandSPtr>;
using CommandListSPtr = shared_ptr<const CommandList>;
} // namespace shardok
@@ -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!");
@@ -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));
}
@@ -14,11 +14,12 @@
#include <utility>
namespace shardok {
using std::string;
class ShardokException : public std::exception {
protected:
std::string description;
explicit ShardokException(std::string desc) : description(std::move(desc)) {}
string description;
explicit ShardokException(string desc) : description(std::move(desc)) {}
public:
[[nodiscard]] auto what() const noexcept -> const char* override {
@@ -29,13 +30,13 @@ public:
// Thrown when the client has provided us with bad info. The game state should still be OK.
class ShardokClientErrorException : public ShardokException {
public:
explicit ShardokClientErrorException(const std::string& desc) : ShardokException(desc) {}
explicit ShardokClientErrorException(const string& desc) : ShardokException(desc) {}
};
// Thrown when we have some bad internal state. Probably not recoverable.
class ShardokInternalErrorException : public ShardokException {
public:
explicit ShardokInternalErrorException(const std::string& desc) : ShardokException(desc) {}
explicit ShardokInternalErrorException(const string& desc) : ShardokException(desc) {}
};
static inline void internalAssert(const bool condition) {
@@ -48,13 +49,13 @@ static inline void internalAssert(const bool condition, const std::string& messa
class ShardokNotImplementedException : public ShardokException {
public:
explicit ShardokNotImplementedException(const std::string& operation)
explicit ShardokNotImplementedException(const string& operation)
: ShardokException("Operation " + operation + " is not yet implemented.") {}
};
class ShardokEagleInterfaceException : public ShardokException {
public:
explicit ShardokEagleInterfaceException(const std::string& desc) : ShardokException(desc) {}
explicit ShardokEagleInterfaceException(const string& desc) : ShardokException(desc) {}
};
} // namespace shardok
@@ -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,9 @@
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
namespace shardok {
using std::begin;
using std::end;
using std::vector;
struct ActionPointDistances::BraveableTileInfo {
struct BraveableFromInfo {
@@ -19,7 +22,7 @@ struct ActionPointDistances::BraveableTileInfo {
};
CoordsSet cs;
std::vector<BraveableFromInfo> details;
vector<BraveableFromInfo> details;
explicit BraveableTileInfo(const HexMap* hexMap) : cs(hexMap) {}
};
@@ -29,7 +32,6 @@ 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++) {
if (const Terrain* terrain = hexMap->terrain()->Get(i);
@@ -66,10 +68,10 @@ auto ActionPointDistances::BraveWaterPossibleCoords(const HexMap* hexMap) const
return info;
}
auto ActionPointDistances::CreateIndexToCoords(const HexMap* hexMap) -> std::vector<Coords> {
auto ActionPointDistances::CreateIndexToCoords(const HexMap* hexMap) -> vector<Coords> {
const int8_t columnCount = hexMap->column_count();
const int indexCount = hexMap->row_count() * columnCount;
std::vector<Coords> indexToCoords;
vector<Coords> indexToCoords;
indexToCoords.reserve(indexCount);
for (int i = 0; i < indexCount; i++) {
indexToCoords.emplace_back(
@@ -80,10 +82,10 @@ auto ActionPointDistances::CreateIndexToCoords(const HexMap* hexMap) -> std::vec
}
auto ActionPointDistances::CreateAdjacencyTable(const HexMap* hexMap)
-> std::vector<std::array<int, 6>> {
-> vector<std::array<int, 6>> {
const int8_t columnCount = hexMap->column_count();
const int indexCount = hexMap->row_count() * columnCount;
std::vector<std::array<int, 6>> adjacencyTable;
vector<std::array<int, 6>> adjacencyTable;
adjacencyTable.reserve(indexCount);
for (int i = 0; i < indexCount; i++) {
const Coords coords(
@@ -103,23 +105,22 @@ auto ActionPointDistances::CreateAdjacencyTable(const HexMap* hexMap)
}
void ActionPointDistances::PopulateOne(
std::vector<DIST_T>& ds,
vector<DIST_T>& ds,
const HexMap* hexMap,
const bool includeBravingWater,
const int braveWaterCost,
const BattalionTypeSPtr& battalionType,
const std::shared_ptr<BraveableTileInfo>& braveWaterPossibleCoords,
const std::vector<Coords>& indexToCoords,
const std::vector<std::array<int, 6>>& adjacencyTable) {
std::vector<uint8_t> visited(ds.size());
const vector<Coords>& indexToCoords,
const vector<std::array<int, 6>>& adjacencyTable) {
vector<uint8_t> visited(ds.size());
// Priority queue for efficient minimum selection: {distance, index}
std::priority_queue<std::pair<DIST_T, int>, std::vector<std::pair<DIST_T, int>>, std::greater<>>
pq;
// 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 < static_cast<int>(ds.size()); i++) {
if (ds[i] == 0) {
pq.emplace(0, i);
break;
@@ -205,9 +206,9 @@ auto ActionPointDistances::GenerateDistances(
const bool includeBravingWater,
const int braveWaterCost,
const BattalionTypeSPtr& battalionType,
const std::shared_ptr<BraveableTileInfo>& braveWaterPossibleCoords) -> std::vector<DIST_T> {
const std::shared_ptr<BraveableTileInfo>& braveWaterPossibleCoords) -> vector<DIST_T> {
const int indexCount = hexMap->row_count() * hexMap->column_count();
auto ds = std::vector(indexCount, IMPOSSIBLE);
auto ds = vector(indexCount, IMPOSSIBLE);
ds[fromIndex] = 0;
@@ -240,6 +241,7 @@ 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;
@@ -257,4 +259,4 @@ OnDemandActionPointDistances::OnDemandActionPointDistances(
}
}
} // namespace shardok
} // namespace shardok
@@ -5,11 +5,8 @@
#ifndef EAGLE0_SHARDOK_LIBRARY_ACTION_POINT_DISTANCES_ACTION_POINT_DISTANCES_HPP
#define EAGLE0_SHARDOK_LIBRARY_ACTION_POINT_DISTANCES_ACTION_POINT_DISTANCES_HPP
#include <array>
#include <future>
#include <limits>
#include <memory>
#include <vector>
#include <optional>
#include "src/main/cpp/net/eagle0/shardok/library/BattalionType.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/FlatbufferWrapper.hpp"
@@ -24,6 +21,9 @@ namespace shardok {
using HexMap = net::eagle0::shardok::storage::fb::HexMap;
using HexMapW = Wrapper<HexMap>;
using Coords = net::eagle0::shardok::storage::fb::Coords;
using std::optional;
using std::shared_future;
using std::vector;
using DIST_T = int16_t;
@@ -35,29 +35,28 @@ protected:
struct BraveableTileInfo;
static void PopulateOne(
std::vector<DIST_T> &ds,
vector<DIST_T> &ds,
const net::eagle0::shardok::storage::fb::HexMap *hexMap,
bool includeBravingWater,
int braveWaterCost,
const BattalionTypeSPtr &battalionType,
const std::shared_ptr<BraveableTileInfo> &braveWaterPossibleCoords,
const std::vector<Coords> &indexToCoords,
const std::vector<std::array<int, 6>> &adjacencyTable);
const vector<Coords> &indexToCoords,
const vector<std::array<int, 6>> &adjacencyTable);
static auto GenerateDistances(
int fromIndex,
const HexMap *hexMap,
bool includeBravingWater,
int braveWaterCost,
const BattalionTypeSPtr &battalionType,
const std::shared_ptr<BraveableTileInfo> &braveWaterPossibleCoords)
-> std::vector<DIST_T>;
const std::shared_ptr<BraveableTileInfo> &braveWaterPossibleCoords) -> vector<DIST_T>;
auto BraveWaterPossibleCoords(const HexMap *hexMap) const -> std::shared_ptr<BraveableTileInfo>;
// Create coordinate lookup table for efficient index->coords conversion
static auto CreateIndexToCoords(const HexMap *hexMap) -> std::vector<Coords>;
static auto CreateIndexToCoords(const HexMap *hexMap) -> vector<Coords>;
// Create adjacency lookup table for efficient neighbor access
static auto CreateAdjacencyTable(const HexMap *hexMap) -> std::vector<std::array<int, 6>>;
static auto CreateAdjacencyTable(const HexMap *hexMap) -> vector<std::array<int, 6>>;
[[nodiscard]] auto ToIndex(const Coords &coords) const -> int {
return coords.row() * column_count + coords.column();
@@ -80,7 +79,7 @@ private:
const HexMapW hexMap;
const BattalionTypeSPtr battalionType;
std::vector<std::shared_future<std::vector<int16_t>>> distances;
vector<shared_future<vector<int16_t>>> distances;
public:
explicit OnDemandActionPointDistances(
@@ -6,8 +6,6 @@
#include <algorithm>
#include <chrono>
#include <iostream>
#include <ostream>
#include <unordered_map>
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/FixedActionPointDistances.hpp"
@@ -34,21 +32,22 @@ thread_local struct {
} cacheStats;
// Helper function to print stats periodically
static void MaybePrintCacheStats(std::ostream& output) {
static void MaybePrintCacheStats() {
auto now = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::seconds>(now - cacheStats.lastReportTime).count() >=
kCacheStatsFrequencySeconds) {
output << "Thread cache stats: " << cacheStats.persistentHits << " persistent hits, "
<< cacheStats.persistentMisses << " persistent misses, " << cacheStats.localHits
<< " local hits, " << cacheStats.localMisses << " local misses, "
<< cacheStats.sharedAccesses << " shared accesses, " << cacheStats.evictionEvents
<< " eviction events\n";
printf("Thread cache stats: %d persistent hits, %d persistent misses, %d local hits, "
"%d local misses, %d shared accesses, %d eviction events\n",
cacheStats.persistentHits,
cacheStats.persistentMisses,
cacheStats.localHits,
cacheStats.localMisses,
cacheStats.sharedAccesses,
cacheStats.evictionEvents);
cacheStats.lastReportTime = now;
}
}
static void MaybePrintCacheStats() { MaybePrintCacheStats(std::cout); }
class BadHashException final : public std::exception {
public:
BadHashException() = default;
@@ -155,7 +154,7 @@ auto ActionPointDistancesCache::GetRaw(
if (kCacheStatsLogging) { cacheStats.localMisses++; }
// Check shared cache before expensive ice-clearing operation
std::shared_ptr<ActionPointDistances> sharedResult;
shared_ptr<ActionPointDistances> sharedResult;
if (sharedDistances.if_contains(cacheKey, [&sharedResult](const auto& kv) {
sharedResult = kv.second;
})) {
@@ -5,7 +5,6 @@
#ifndef EAGLE0_SHARDOK_LIBRARY_ACTION_POINT_DISTANCES_ACTION_POINT_DISTANCES_CACHE_HPP
#define EAGLE0_SHARDOK_LIBRARY_ACTION_POINT_DISTANCES_ACTION_POINT_DISTANCES_CACHE_HPP
#include <memory>
#include <shared_mutex>
#include <unordered_map>
@@ -18,6 +17,8 @@
namespace shardok {
using std::shared_ptr;
struct MapId {
uint64_t terrainTypesId;
uint64_t modifierId;
@@ -57,10 +58,9 @@ struct FullCacheKeyHash {
class ActionPointDistancesCache {
private:
struct CacheEntry {
std::shared_ptr<ActionPointDistances> sharedPtr;
shared_ptr<ActionPointDistances> sharedPtr;
explicit CacheEntry(std::shared_ptr<ActionPointDistances> ptr)
: sharedPtr(std::move(ptr)) {}
explicit CacheEntry(shared_ptr<ActionPointDistances> ptr) : sharedPtr(std::move(ptr)) {}
};
// Tier 1: persistent map. This is NOT safe to write to while reads may be happening.
@@ -70,10 +70,10 @@ private:
using APDMap = gtl::parallel_flat_hash_map<
FullCacheKey,
std::shared_ptr<ActionPointDistances>,
shared_ptr<ActionPointDistances>,
FullCacheKeyHash,
std::equal_to<FullCacheKey>,
std::allocator<std::pair<const FullCacheKey, std::shared_ptr<ActionPointDistances>>>,
std::allocator<std::pair<const FullCacheKey, shared_ptr<ActionPointDistances>>>,
6,
std::mutex>;
@@ -119,7 +119,7 @@ public:
static size_t GetThreadLocalCacheSize();
};
using APDCache = std::shared_ptr<ActionPointDistancesCache>;
using APDCache = shared_ptr<ActionPointDistancesCache>;
} // namespace shardok
@@ -54,3 +54,31 @@ cc_library(
"//src/main/cpp/net/eagle0/shardok/library/fb_helpers:hex_map_helpers",
],
)
cc_library(
name = "fixed_action_point_distances_timer",
srcs = ["FixedActionPointDistancesTimer.cpp"],
copts = COPTS,
data = [
"//src/main/resources/net/eagle0/shardok/maps",
],
deps = [
"//src/main/cpp/net/eagle0/common:sequence_random_generator",
"//src/main/cpp/net/eagle0/shardok/library/action_point_distances:fixed_action_point_distances",
"//src/main/cpp/net/eagle0/shardok/library/settings_loader",
"//src/main/cpp/net/eagle0/shardok/util:map_loader",
"@googletest//:gtest",
"@googletest//:gtest_main",
],
)
cc_binary(
name = "fixed_action_point_distances_timer_app",
copts = COPTS,
data = [
"//src/main/resources/net/eagle0/shardok/maps",
],
deps = [
":fixed_action_point_distances_timer",
],
)
@@ -4,7 +4,6 @@
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/FixedActionPointDistances.hpp"
#include <iostream>
#include <thread>
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
@@ -12,10 +11,12 @@
// 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";
fprintf(stderr,
"ActionPointDistances using %d threads (detected %d cores)\n",
threadCount,
cores);
return threadCount;
}();
@@ -35,7 +36,7 @@ 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;
@@ -13,11 +13,12 @@
namespace shardok {
using std::vector;
using BattalionTypeSPtr = std::shared_ptr<const BattalionType>;
class FixedActionPointDistances final : public ActionPointDistances {
private:
std::vector<std::vector<DIST_T>> distances;
vector<vector<DIST_T>> distances;
// Private constructor - use Create factory method instead
explicit FixedActionPointDistances(const HexMap *map, int columnCount);
@@ -0,0 +1,48 @@
//
// Created by Dan Crosby on 11/26/22.
//
#include <include/gtest/gtest.h>
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistances.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/FixedActionPointDistances.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/HexMapHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/settings_loader/SettingsLoader.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/MapLoader.hpp"
using namespace std::chrono;
using namespace shardok;
auto main(int argc, char** argv) -> int {
FilesystemUtils::SetExecPath(
"/Users/dancrosby/CodingProjects/github/eagle0/bazel-bin/src/main/cpp/net/eagle0/"
"shardok/library/action_point_distances/fixed_action_point_distances_timer_app");
const auto gameSettings = SettingsLoader::LoadSettings();
const vector<string> mapNames = GetMapNames();
vector<HexMapW> hexMaps;
for (const string& name : mapNames) {
auto hexMapProto = LoadMap(name);
flatbuffers::FlatBufferBuilder fbb;
const auto offset = shardok::fb::ConvertHexMapProto(fbb, hexMapProto);
fbb.Finish(offset);
hexMaps.emplace_back(fbb);
}
const auto& battalionType = gameSettings->GetGetter().GetBattalionType(
net::eagle0::shardok::storage::fb::BattalionTypeId_LIGHT_INFANTRY);
const auto start = system_clock::now();
for (int i = 0; i < 100; i++) {
for (const HexMapW& hexMap : hexMaps) {
shardok::FixedActionPointDistances
distances(hexMap, 0x1234, 0xABCD, battalionType, true, 5);
}
}
const auto end = system_clock::now();
std::cerr << duration_cast<microseconds>(end - start).count() << " µs" << '\n';
}
@@ -19,6 +19,8 @@
namespace shardok {
using std::unique_ptr;
using ResolvedUnitProto = net::eagle0::shardok::storage::ResolvedUnit;
using GameState = net::eagle0::shardok::storage::fb::GameState;
using GameStateT = net::eagle0::shardok::storage::fb::GameStateT;
@@ -247,13 +249,12 @@ void MutatingApplyResult(
internalAssert(mutatingGameState->mutable_status()->mutate_state(
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 < result.game_status().winning_shardok_ids_size(); 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 = result.game_status().winning_shardok_ids_size(); i < 10; i++) {
mutatingGameState->mutable_status()->mutable_winning_shardok_ids()->Mutate(i, -1);
}
@@ -301,18 +302,23 @@ void MutatingApplyResult(
for (const auto &changedUnitBytes : result.changed_units_fb()) {
const Unit *changedUnit = &UnitFromBytes(changedUnitBytes);
if (changedUnit->battalion().type() ==
net::eagle0::shardok::storage::fb::BattalionTypeId_UNDEAD &&
changedUnit->battalion().morale() != 50.0) {
printf("bad morale\n");
}
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)) {
@@ -384,11 +390,10 @@ void MutatingApplyResult(
} else {
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 < result.possible_chargees_size(); i++) {
mutatingGameState->mutable_possible_chargee_ids()->Mutate(i, result.possible_chargees(i));
}
for (int i = possibleChargeeCount; i < 6; i++) {
for (int i = result.possible_chargees_size(); i < 6; i++) {
mutatingGameState->mutable_possible_chargee_ids()->Mutate(i, -1);
}
@@ -5,7 +5,6 @@
#include "src/main/cpp/net/eagle0/shardok/library/action_result_applier/GameStateCopier.hpp"
#include <cstring>
#include <utility>
namespace shardok {
@@ -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)
@@ -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)) {
@@ -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
@@ -19,6 +19,8 @@ using net::eagle0::shardok::storage::fb::VictoryCondition;
using PlayerInfoFb = net::eagle0::shardok::storage::fb::PlayerInfo;
using GameStatusFb = net::eagle0::shardok::storage::fb::GameStatus;
using GameStatusProto = net::eagle0::shardok::common::GameStatus;
using std::optional;
using std::unordered_set;
static inline auto GameIsOver(const GameStatusFb* status) -> bool {
return (status->state() == net::eagle0::shardok::storage::fb::GameStatus_::State_VICTORY);
@@ -39,19 +41,17 @@ static inline auto HasVictoryCondition(const PlayerInfoFb* pi, const VictoryCond
return false;
}
auto UpdateGameStatusAction::SurvivingPlayers() const -> std::unordered_set<PlayerId> {
auto UpdateGameStatusAction::SurvivingPlayers() const -> unordered_set<PlayerId> {
if (gameState->status()->state() ==
net::eagle0::shardok::storage::fb::GameStatus_::State_SET_UP)
return std::unordered_set<PlayerId>{};
return unordered_set<PlayerId>{};
std::unordered_set<PlayerId> survivingPids{};
unordered_set<PlayerId> survivingPids{};
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))) {
@@ -288,7 +288,7 @@ auto UpdateGameStatusAction::InternalExecute(
}
// Second check: allied players collectively holding all critical tiles
std::unordered_set<PlayerId> occupantPlayerIds{};
unordered_set<PlayerId> occupantPlayerIds{};
bool allTilesOccupiedWithHeroes = true;
for (const auto& criticalTile : criticalTileLocations) {
const auto* possibleOccupant = currentState.GetOccupant(criticalTile);
@@ -356,4 +356,4 @@ auto UpdateGameStatusAction::InternalExecute(
// If we got all the way through, keep the status as it is
return results;
}
} // namespace shardok
} // namespace shardok
@@ -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,
@@ -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
@@ -16,6 +16,7 @@
#include "src/main/protobuf/net/eagle0/shardok/api/unit_view.pb.h"
namespace shardok {
using std::vector;
class ChallengeDuelCommandFactory : public CommandFactory {
private:
@@ -31,7 +32,7 @@ public:
ActionPoints remainingActionPoints,
const HexMap *map,
const Units *units,
const std::vector<PlayerId> &allyPids) const;
const vector<PlayerId> &allyPids) const;
void AddAvailableCommands(CommandList &commands, const CommandParams &params) const override;
};
@@ -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,42 +34,42 @@
namespace shardok {
auto MakeFactories(const SettingsGetter& settings)
-> std::vector<std::shared_ptr<const CommandFactory>> {
using std::make_shared;
auto MakeFactories(const SettingsGetter& settings) -> vector<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
-> std::vector<std::shared_ptr<const CommandFactory>> {
auto CommandFactoriesList::GetFactories() const -> vector<shared_ptr<const CommandFactory>> {
return factories;
}

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