mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-29 09:55:41 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78937263a8 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" \
|
||||
|
||||
@@ -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}));
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
Generated
+8
-8
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
+3
-2
@@ -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"); }
|
||||
|
||||
-4
@@ -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;
|
||||
}
|
||||
}
|
||||
+6
-12
@@ -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 ¤tCoords = 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 ¤tGameState = 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)) {
|
||||
|
||||
+17
-15
@@ -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
|
||||
+11
-12
@@ -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(
|
||||
|
||||
+10
-11
@@ -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;
|
||||
})) {
|
||||
|
||||
+7
-7
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
+6
-5
@@ -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;
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+48
@@ -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
-14
@@ -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 ¤tState,
|
||||
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 ¤tState,
|
||||
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 ¤tState,
|
||||
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
-6
@@ -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
-13
@@ -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
-6
@@ -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
|
||||
|
||||
+2
-1
@@ -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 ¶ms) const override;
|
||||
};
|
||||
|
||||
+31
-33
@@ -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
Reference in New Issue
Block a user