Compare commits

..
Author SHA1 Message Date
admin 5c66c6d4e4 Remove Scala asInstanceOf usage 2026-06-19 22:23:05 -07:00
780 changed files with 8698 additions and 24617 deletions
-26
View File
@@ -22,32 +22,6 @@ common --per_file_copt=src/test/cpp/.*@-Wno-sign-compare
common --per_file_copt=src/test/cpp/.*@-Wno-unknown-warning-option
common --host_cxxopt="--std=c++23"
# C++ sanitizer configs for targeted Shardok safety checks.
# Example: bazel test --config=asan //src/test/cpp/net/eagle0/shardok/library/...
build:asan --compilation_mode=dbg
build:asan --strip=never
build:asan --copt=-fno-omit-frame-pointer
build:asan --copt=-Wno-macro-redefined
build:asan --copt=-fsanitize=address
build:asan --linkopt=-fsanitize=address
test:asan --test_env=ASAN_OPTIONS=detect_leaks=0:strict_init_order=1
build:ubsan --compilation_mode=dbg
build:ubsan --strip=never
build:ubsan --copt=-fno-omit-frame-pointer
build:ubsan --copt=-Wno-macro-redefined
build:ubsan --copt=-fsanitize=undefined
build:ubsan --linkopt=-fsanitize=undefined
test:ubsan --test_env=UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1
build:tsan --compilation_mode=dbg
build:tsan --strip=never
build:tsan --copt=-fno-omit-frame-pointer
build:tsan --copt=-Wno-macro-redefined
build:tsan --copt=-fsanitize=thread
build:tsan --linkopt=-fsanitize=thread
test:tsan --test_env=TSAN_OPTIONS=halt_on_error=1
common --javacopt="-Xlint:-options"
# Prefer protobuf's prebuilt protoc toolchain instead of building protoc from
-32
View File
@@ -1,32 +0,0 @@
Checks: >
-*,
bugprone-*,
performance-*,
readability-*,
modernize-*,
cppcoreguidelines-*,
-bugprone-easily-swappable-parameters,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-type-const-cast,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-modernize-use-trailing-return-type,
-readability-convert-member-functions-to-static,
-readability-function-cognitive-complexity,
-readability-identifier-length,
-readability-magic-numbers
WarningsAsErrors: ''
HeaderFilterRegex: 'src/(main|test)/cpp/net/eagle0/(shardok|common)/.*'
FormatStyle: file
CheckOptions:
readability-braces-around-statements.ShortStatementLines: '1'
readability-function-size.LineThreshold: '160'
readability-function-size.StatementThreshold: '80'
readability-function-size.BranchThreshold: '20'
modernize-use-nullptr.NullMacros: 'NULL'
+15 -1
View File
@@ -255,7 +255,7 @@ jobs:
echo "=== All images pushed successfully ==="
deploy:
runs-on: ubuntu-latest
runs-on: [self-hosted, bazel]
needs: [build-all]
if: needs.build-all.outputs.skip_build != 'true' && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true'))
environment: production
@@ -334,11 +334,25 @@ jobs:
ssh-keyscan -H "$DO_DROPLET_IP" >> ~/.ssh/known_hosts 2>/dev/null || true
- name: Download warmup binary
id: download-warmup
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: warmup-binary
path: scripts/bin/
- name: Build warmup binary fallback
if: steps.download-warmup.outcome != 'success'
run: |
echo "::warning::warmup-binary artifact was unavailable; rebuilding warmup binary before deploy"
./ci/github_actions/ensure_bazel_installed.sh
bazel build \
--platforms=//:linux_x86_64 \
--extra_toolchains=@llvm_toolchain_linux//:cc-toolchain-x86_64-linux \
//ci:warmup_tar
mkdir -p scripts/bin
tar -xf bazel-bin/ci/warmup_tar.tar -C scripts/bin --strip-components=1
- name: Copy config files to droplet
run: |
# Create directory structure on remote
+1 -3
View File
@@ -177,12 +177,10 @@ jobs:
env:
DO_CDN_PAT: ${{ secrets.DO_CDN_PAT }}
run: |
UNITY_VERSION=$(grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //')
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
curl -s -X DELETE "https://api.digitalocean.com/v2/cdn/endpoints/8c98df29-6f0c-4704-8e82-ca40a2b81aa8/cache" \
-H "Authorization: Bearer $DO_CDN_PAT" \
-H "Content-Type: application/json" \
-d "{\"files\": [\"addressables/iOS/$UNITY_MAJOR_MINOR/*\"]}" \
-d '{"files": ["addressables/iOS/*"]}' \
--fail || echo "Warning: CDN purge failed (non-fatal)"
- name: Archive Build Log
+1 -5
View File
@@ -267,7 +267,6 @@ jobs:
runs-on: [self-hosted, macOS, notarize]
outputs:
deployed_version: ${{ steps.deploy-mac.outputs.deployed_version }}
deployed_unity_major_minor: ${{ steps.deploy-mac.outputs.deployed_unity_major_minor }}
concurrency:
group: mac-deploy-${{ github.ref }}
cancel-in-progress: false
@@ -496,9 +495,6 @@ jobs:
rm "$SPARKLE_PRIVATE_KEY_PATH"
echo "deployed_version=$VERSION" >> $GITHUB_OUTPUT
UNITY_VERSION=$(grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //')
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
echo "deployed_unity_major_minor=$UNITY_MAJOR_MINOR" >> $GITHUB_OUTPUT
# Artifact cleanup is handled by the cleanup job on ubuntu-latest
- name: Cleanup build directory
@@ -519,7 +515,7 @@ jobs:
curl -s -X DELETE "https://api.digitalocean.com/v2/cdn/endpoints/8c98df29-6f0c-4704-8e82-ca40a2b81aa8/cache" \
-H "Authorization: Bearer $DO_CDN_PAT" \
-H "Content-Type: application/json" \
-d '{"files": ["mac/*", "addressables/StandaloneOSX/${{ needs.deploy-mac.outputs.deployed_unity_major_minor }}/*"]}' \
-d '{"files": ["mac/*", "addressables/StandaloneOSX/*"]}' \
--fail || echo "Warning: CDN purge failed (non-fatal)"
curl -X POST "https://admin.eagle0.net/notify-update?platform=mac&version=${{ needs.deploy-mac.outputs.deployed_version }}&required=false" \
-6
View File
@@ -20,7 +20,6 @@ on:
- 'ci/github_actions/ensure_bazel_installed.sh'
- 'scripts/build_eagle_ci.sh'
- 'scripts/build_shardok_ci.sh'
- 'scripts/build_shardok_linux_arm64_ci.sh'
- '.github/actions/setup-bazel/**'
- '.github/workflows/server_build.yml'
@@ -84,7 +83,6 @@ jobs:
'src/main/cpp/',
'src/main/protobuf/net/eagle0/shardok/',
'scripts/build_shardok_ci.sh',
'scripts/build_shardok_linux_arm64_ci.sh',
];
const shared = matches(sharedPatterns);
@@ -108,10 +106,6 @@ jobs:
name: 'Shardok server',
command: './scripts/build_shardok_ci.sh',
});
include.push({
name: 'Shardok Linux ARM64 server',
command: './scripts/build_shardok_linux_arm64_ci.sh',
});
}
core.setOutput('build_matrix', JSON.stringify({include}));
+1 -3
View File
@@ -196,9 +196,7 @@ jobs:
echo "Also tagged as: registry.digitalocean.com/eagle0/shardok-server:arm64-latest"
deploy-hetzner:
# Keep this on a self-hosted Mac runner for IPv6 reachability to Hetzner,
# but do not require a Bazel runner slot for artifact transport/deploy work.
runs-on: [self-hosted, macOS, ARM64]
runs-on: [self-hosted, bazel]
needs: [build-shardok-arm64]
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
environment: production
+1 -1
View File
@@ -287,7 +287,7 @@ jobs:
./ci/github_actions/detect_addressables_changes.sh "$BASE_SHA" "${{ github.sha }}"
- name: Build Windows unity
run: ./ci/github_actions/build_unity.sh "${{ env.EAGLE0_BUILD_DIR }}/eagle0WIN" true "${{ steps.addressables.outputs.changed }}"
run: ./ci/github_actions/build_unity.sh "${{ env.EAGLE0_BUILD_DIR }}/eagle0WIN" "${{ steps.addressables.outputs.changed }}" "${{ steps.addressables.outputs.changed }}"
- name: Save build SHA for Bee/ cache invalidation
if: success()
-19
View File
@@ -29,16 +29,6 @@ If you catch yourself about to run `git push origin main` or `git push origin <b
If you catch yourself about to run `gh pr merge`, STOP. Only the user merges PRs.
## Commit History During Review
Prefer new follow-up commits over amending existing commits once a PR is open and the user is actively reviewing or
testing it. This preserves working interim states so the user can inspect, compare, or return to a known-good point
while improvements continue.
Use `git commit --amend` and force-push only when the user explicitly asks for history cleanup, when fixing metadata
before review has started, or when repairing a local commit that has not been pushed. If you believe squashing is the
right choice despite an open review, explain why and ask first.
## Worktree Usage
Prefer using the current worktree for small requested fixes. Create a separate worktree only when the user asks for one,
@@ -68,15 +58,6 @@ For PRs expected to trigger substantial Bazel work in CI, run the relevant Bazel
workstation is fast, and local Bazel runs can help hydrate the remote cache for the CI builders while still giving
earlier signal on failures.
## Cleanup PR Batching
When making mechanical cleanup changes, group 3-5 files per PR when the files are receiving the same kind of change
and can be reviewed as one pattern. Do not open one PR per file for nearly identical few-line cleanups, such as replacing
the same unsafe accessor pattern with the same contextual failure pattern across multiple Scala files.
Keep separate PRs for changes that involve different semantics, materially different risk, unrelated subsystems, or
files whose tests/validation strategy makes them better reviewed independently.
## GitHub CLI PR Bodies
When creating or editing PR descriptions with multiline bodies, write the body to a temporary markdown file and pass it
+9
View File
@@ -47,3 +47,12 @@ sh_test(
"no-sandbox",
],
)
sh_test(
name = "no_scala_asinstanceof_test",
srcs = ["scripts/check_no_scala_asinstanceof.sh"],
tags = [
"local",
"no-sandbox",
],
)
+1 -1
View File
@@ -282,7 +282,7 @@ oci.pull(
# Base image for Admin Server (Alpine for lightweight Go binary)
oci.pull(
name = "alpine_linux",
digest = "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
digest = "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
image = "docker.io/library/alpine",
platforms = ["linux/amd64"],
tag = "3.24",
+8 -8
View File
@@ -904,7 +904,7 @@
"@@rules_oci+//oci:extensions.bzl%oci": {
"general": {
"bzlTransitiveDigest": "H23W9dvwCQM6h+Wt8PjIahx08EkAU5eSbUxAyx9Mopc=",
"usagesDigest": "k6DBHAkb5br/tpwCPUMJgqwb+7U4DXyCmBZQBgLnHJ8=",
"usagesDigest": "tbQ7wHHObzzrBzSRv652104GY3xxiaQDK5OUp2YeEbg=",
"recordedInputs": [
"REPO_MAPPING:aspect_bazel_lib+,bazel_tools bazel_tools",
"REPO_MAPPING:bazel_features+,bazel_tools bazel_tools",
@@ -922,7 +922,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/eclipse-temurin",
"identifier": "sha256:0beb258e10ad28d2bae0f74349a170bdfa67da0ef87c028cbf3d95c3e1170120",
"identifier": "sha256:db3011b348ad87ced055afe5145238d87a3d8f0e8f23eb4d0cd8cecb4659b085",
"platform": "linux/amd64",
"target_name": "eclipse_temurin_25_jre_linux_amd64",
"bazel_tags": []
@@ -936,7 +936,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/eclipse-temurin",
"identifier": "sha256:0beb258e10ad28d2bae0f74349a170bdfa67da0ef87c028cbf3d95c3e1170120",
"identifier": "sha256:db3011b348ad87ced055afe5145238d87a3d8f0e8f23eb4d0cd8cecb4659b085",
"platforms": {
"@@platforms//cpu:x86_64": "@eclipse_temurin_25_jre_linux_amd64"
},
@@ -980,7 +980,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/ubuntu",
"identifier": "26.04",
"identifier": "24.04",
"platform": "linux/amd64",
"target_name": "ubuntu_24_04_linux_amd64",
"bazel_tags": []
@@ -993,7 +993,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/ubuntu",
"identifier": "26.04",
"identifier": "24.04",
"platform": "linux/arm64/v8",
"target_name": "ubuntu_24_04_linux_arm64_v8",
"bazel_tags": []
@@ -1007,7 +1007,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/ubuntu",
"identifier": "26.04",
"identifier": "24.04",
"platforms": {
"@@platforms//cpu:x86_64": "@ubuntu_24_04_linux_amd64",
"@@platforms//cpu:arm64": "@ubuntu_24_04_linux_arm64_v8"
@@ -1023,7 +1023,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/alpine",
"identifier": "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
"identifier": "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
"platform": "linux/amd64",
"target_name": "alpine_linux_linux_amd64",
"bazel_tags": []
@@ -1037,7 +1037,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/alpine",
"identifier": "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
"identifier": "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
"platforms": {
"@@platforms//cpu:x86_64": "@alpine_linux_linux_amd64"
},
+3 -1
View File
@@ -5,7 +5,8 @@ set -x
COMMAND_LOG="$(bazel info command_log)"
bazel build \
//src/main/cpp/net/eagle0/shardok:shardok-server
//src/main/cpp/net/eagle0/shardok:shardok-server \
//src/main/cpp/net/eagle0/shardok:shardok-inspector
STATUS="$?"
if [ $STATUS -eq 0 ]
@@ -25,6 +26,7 @@ echo "Setting up Shardok files..."
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server ./shardok/
/bin/mkdir -p ./shardok/shardok-server.runfiles/net_eagle0/src/main/
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server.runfiles/net_eagle0/src/main/resources ./shardok/shardok-server.runfiles/net_eagle0/src/main/
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-inspector ./shardok/
echo "tar & zip shardok files..."
/bin/tar cfh - shardok | /usr/bin/pigz > ./deploy/shardok.tar.gz
+13
View File
@@ -40,6 +40,19 @@ if [ -f "$INFO_PLIST" ]; then
/usr/libexec/PlistBuddy -c "Set :ITSAppUsesNonExemptEncryption false" "$INFO_PLIST" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :ITSAppUsesNonExemptEncryption bool false" "$INFO_PLIST"
echo "Set ITSAppUsesNonExemptEncryption=false in Info.plist"
# Remove UIApplicationSceneManifest if present.
# Unity 6 generates this key, which opts the app into the iOS scene-based
# lifecycle. This breaks Application.deepLinkActivated because iOS routes
# deep link URLs through UISceneDelegate instead of UIApplicationDelegate,
# and Unity doesn't implement the scene delegate path.
# See: https://issuetracker.unity3d.com/issues/in-135632
if /usr/libexec/PlistBuddy -c "Print :UIApplicationSceneManifest" "$INFO_PLIST" 2>/dev/null; then
echo "Found UIApplicationSceneManifest in Info.plist — removing to fix deep link handling"
/usr/libexec/PlistBuddy -c "Delete :UIApplicationSceneManifest" "$INFO_PLIST"
else
echo "No UIApplicationSceneManifest in Info.plist (deep links should work)"
fi
fi
# Unity always generates "Unity-iPhone" as the main app scheme
-14
View File
@@ -59,20 +59,6 @@ if [ $UNITY_EXIT_CODE -ne 0 ]; then
exit $UNITY_EXIT_CODE
fi
if [ ! -f "$LOG_PATH" ]; then
echo ""
echo "ERROR: Unity exited successfully but did not write an editor log at $LOG_PATH"
exit 1
fi
if [ ! -f "$BUILD_PATH/Data/Raw/aa/settings.json" ]; then
echo ""
echo "ERROR: iOS player is missing Addressables runtime data at:"
echo " $BUILD_PATH/Data/Raw/aa/settings.json"
echo "Build Addressables before packaging the player so Addressables can initialize at runtime."
exit 1
fi
# Fail the build if any prefab references are broken — this produces a player
# that launches but has null Inspector fields, which is hard to debug.
if grep -q "Missing Prefab" "$LOG_PATH"; then
-8
View File
@@ -57,14 +57,6 @@ if [ ! -f "$LOG_PATH" ]; then
exit 1
fi
if [ ! -f "$BUILD_DIR/eagle0_Data/StreamingAssets/aa/settings.json" ]; then
echo ""
echo "ERROR: Windows player is missing Addressables runtime data at:"
echo " $BUILD_DIR/eagle0_Data/StreamingAssets/aa/settings.json"
echo "Build Addressables before packaging the player so Addressables can initialize at runtime."
exit 1
fi
# Fail the build if any prefab references are broken — this produces a player
# that launches but has null Inspector fields, which is hard to debug.
if grep -q "Missing Prefab" "$LOG_PATH"; then
+6 -9
View File
@@ -13,10 +13,7 @@ set -euxo pipefail
BUILD_TARGET=$1
WORKSPACE=$(pwd)
UNITY_PROJECT="$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
UNITY_VERSION=$(grep "m_EditorVersion:" "$UNITY_PROJECT/ProjectSettings/ProjectVersion.txt" | head -1 | sed 's/m_EditorVersion: //')
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
ADDRESSABLES_PREFIX="addressables/$BUILD_TARGET/$UNITY_MAJOR_MINOR"
SERVER_DATA="$UNITY_PROJECT/ServerData/$BUILD_TARGET/$UNITY_MAJOR_MINOR"
SERVER_DATA="$UNITY_PROJECT/ServerData/$BUILD_TARGET"
# DigitalOcean Spaces configuration (same region as other eagle0 buckets)
DO_ENDPOINT="https://sfo3.digitaloceanspaces.com"
@@ -78,7 +75,7 @@ summarize_pending_uploads() {
sorted_uploads_file=$(mktemp)
trap 'rm -f "$sync_args_file" "$uploads_file" "$sorted_uploads_file"' RETURN
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
--endpoint-url "$DO_ENDPOINT" \
--acl public-read \
--size-only \
@@ -181,7 +178,7 @@ if [ ! -d "$SERVER_DATA" ]; then
fi
echo "Uploading Addressables bundles from $SERVER_DATA"
echo "Target: s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/"
echo "Target: s3://$DO_BUCKET/addressables/$BUILD_TARGET/"
# Configure AWS CLI for DigitalOcean Spaces
export AWS_ACCESS_KEY_ID="$ACCESS_KEY_ID"
@@ -196,13 +193,13 @@ summarize_pending_uploads
#
# --delete removes files in destination that don't exist in source.
# --acl public-read makes files publicly accessible.
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
--endpoint-url "$DO_ENDPOINT" \
--acl public-read \
--size-only \
--delete
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
--endpoint-url "$DO_ENDPOINT" \
--acl public-read \
--exclude "*" \
@@ -211,4 +208,4 @@ aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
--include "*.json"
echo "Addressables upload complete"
echo "Files available at: https://assets.eagle0.net/$ADDRESSABLES_PREFIX/"
echo "Files available at: https://assets.eagle0.net/addressables/$BUILD_TARGET/"
Binary file not shown.
-17
View File
@@ -1,17 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
bazel_cmd=(bazel)
if [ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]; then
bazel_cmd+=("--output_user_root=${BAZEL_OUTPUT_USER_ROOT}")
fi
if [ -n "${BAZEL_OUTPUT_BASE:-}" ]; then
bazel_cmd+=("--output_base=${BAZEL_OUTPUT_BASE}")
fi
"${bazel_cmd[@]}" build \
--platforms=//:linux_arm64 \
--extra_toolchains=@llvm_toolchain_linux_arm64//:cc-toolchain-aarch64-linux \
"$@" \
//src/main/cpp/net/eagle0/shardok:shardok-server
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
matches="$(
grep -R -n "asInstanceOf" \
"${REPO_ROOT}/src/main/scala" \
"${REPO_ROOT}/src/test/scala" || true
)"
if [[ -n "${matches}" ]]; then
echo "Scala asInstanceOf usage is not allowed:" >&2
echo "${matches}" >&2
exit 1
fi
-33
View File
@@ -1,33 +0,0 @@
#!/bin/bash
# Runs the repository-pinned clang-tidy binary from the workspace root.
#
# Bazel's `run` command executes clang-tidy from its runfiles tree, which makes
# workspace-relative source paths and .clang-tidy discovery fail. This wrapper
# builds the tool and then invokes the produced binary directly.
set -e
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
repo_root="$(git rev-parse --show-toplevel)"
if [ "$#" -eq 0 ]; then
echo "Usage: scripts/run-clang-tidy.sh [clang-tidy args] <files> [-- <compiler args>]"
echo ""
echo "Examples:"
echo " scripts/run-clang-tidy.sh --verify-config"
echo " scripts/run-clang-tidy.sh src/main/cpp/net/eagle0/shardok/library/CombatDamage.hpp -- -I$repo_root -std=c++23"
exit 2
fi
bazel build @llvm_toolchain//:clang-tidy
bazel_bin="$(bazel info bazel-bin)"
clang_tidy="$bazel_bin/external/toolchains_llvm++llvm+llvm_toolchain/clang-tidy"
if [ ! -x "$clang_tidy" ]; then
echo "Unable to find clang-tidy binary at $clang_tidy"
exit 1
fi
"$clang_tidy" --config-file="$repo_root/.clang-tidy" "$@"
@@ -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;
}
@@ -6,8 +6,8 @@
// Copyright © 2017 none. All rights reserved.
//
#ifndef EAGLE0_COMMON_FILESYSTEM_UTILS_HPP
#define EAGLE0_COMMON_FILESYSTEM_UTILS_HPP
#ifndef FilesystemUtils_hpp
#define FilesystemUtils_hpp
#include <string>
#include <vector>
@@ -36,4 +36,4 @@ public:
static auto LoadFromPath(const string& path) -> byte_vector;
};
#endif // EAGLE0_COMMON_FILESYSTEM_UTILS_HPP
#endif /* FilesystemUtils_hpp */
+3 -3
View File
@@ -6,8 +6,8 @@
// Copyright © 2018 none. All rights reserved.
//
#ifndef EAGLE0_COMMON_MAP_UTILS_HPP
#define EAGLE0_COMMON_MAP_UTILS_HPP
#ifndef MapUtils_hpp
#define MapUtils_hpp
#include <string>
#include <unordered_map>
@@ -19,4 +19,4 @@ auto IntForKey(const std::unordered_map<std::string, std::string>& map, const st
auto BoolForKey(const std::unordered_map<std::string, std::string>& map, const std::string& key)
-> bool;
#endif // EAGLE0_COMMON_MAP_UTILS_HPP
#endif /* MapUtils_hpp */
@@ -6,8 +6,8 @@
// Copyright © 2019 none. All rights reserved.
//
#ifndef EAGLE0_COMMON_PROTOBUF_WARNING_SUPPRESSION_HPP
#define EAGLE0_COMMON_PROTOBUF_WARNING_SUPPRESSION_HPP
#ifndef ProtobufWarningSuppression_h
#define ProtobufWarningSuppression_h
#define SUPPRESS_PROTOBUF_WARNINGS \
_Pragma("GCC diagnostic ignored \"-Wpragmas\"") \
@@ -19,4 +19,4 @@
"GCC diagnostic ignored \"-Wshorten-64-to-32\"") \
_Pragma("GCC diagnostic ignored \"-Wnested-anon-types\"")
#endif // EAGLE0_COMMON_PROTOBUF_WARNING_SUPPRESSION_HPP
#endif /* ProtobufWarningSuppression_h */
@@ -6,13 +6,11 @@
// Copyright (c) 2015 none. All rights reserved.
//
#ifndef EAGLE0_COMMON_RANDOM_GENERATOR_HPP
#define EAGLE0_COMMON_RANDOM_GENERATOR_HPP
#ifndef __eagle0__ShardokRandomGenerator__
#define __eagle0__ShardokRandomGenerator__
#include <cstddef>
#include <memory>
#include <random>
#include <vector>
class RandomGenerator;
@@ -24,10 +22,6 @@ private:
public:
RandomGenerator() = default;
RandomGenerator(const RandomGenerator&) = default;
auto operator=(const RandomGenerator&) -> RandomGenerator& = default;
RandomGenerator(RandomGenerator&&) = default;
auto operator=(RandomGenerator&&) -> RandomGenerator& = default;
virtual ~RandomGenerator() = default;
@@ -41,7 +35,7 @@ public:
template<class T>
auto RandomElement(const std::vector<T>& vec) -> T {
return vec.at(static_cast<std::size_t>(IntBelow(static_cast<int>(vec.size()))));
return vec[IntBelow((int)vec.size())];
}
static auto ChanceOpenEndedPercentileAtOrAbove(double value) -> double;
@@ -62,4 +56,4 @@ public:
auto IntBetween(int min, int max) -> int override; // inclusive, exclusive (min<=return<max)
};
#endif // EAGLE0_COMMON_RANDOM_GENERATOR_HPP
#endif /* defined(__eagle0__ShardokRandomGenerator__) */
@@ -6,11 +6,10 @@
// Copyright © 2018 none. All rights reserved.
//
#ifndef EAGLE0_COMMON_SEQUENCE_RANDOM_GENERATOR_HPP
#define EAGLE0_COMMON_SEQUENCE_RANDOM_GENERATOR_HPP
#ifndef SequenceRandomGenerator_h
#define SequenceRandomGenerator_h
#include <memory>
#include <utility>
#include <vector>
#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
@@ -25,22 +24,22 @@
// which produces: initial=2 (triggers open-ended), accumulated=52, final=2-52=-50
class SequenceRandomGenerator : public ::RandomGenerator {
private:
std::vector<double> sequence;
const std::vector<double> sequence;
size_t position = 0;
auto DoubleZeroToOne() -> double override {
const double nextVal = sequence.at(position);
const double nextVal = sequence[position];
position++;
if (position >= sequence.size()) { position = 0; }
return nextVal;
}
public:
explicit SequenceRandomGenerator(std::vector<double> s) : sequence(std::move(s)) {}
explicit SequenceRandomGenerator(std::vector<double> s) : sequence(std::move(s)), position(0) {}
static auto WithSequence(const std::vector<double>& s)
-> std::shared_ptr<SequenceRandomGenerator> {
return std::make_shared<SequenceRandomGenerator>(s);
}
};
#endif // EAGLE0_COMMON_SEQUENCE_RANDOM_GENERATOR_HPP
#endif /* SequenceRandomGenerator_h */
@@ -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
+4 -4
View File
@@ -6,14 +6,14 @@
// Copyright © 2018 none. All rights reserved.
//
#ifndef EAGLE0_COMMON_TSV_PARSER_HPP
#define EAGLE0_COMMON_TSV_PARSER_HPP
#ifndef TsvParser_hpp
#define TsvParser_hpp
#include <string>
#include <unordered_map>
#include <vector>
using StringMap = std::unordered_map<std::string, std::string>;
typedef std::unordered_map<std::string, std::string> StringMap;
// Parses a TSV, with the first column becoming map keys and subsequent columns becoming values
// for different maps in the returned vector.
@@ -23,4 +23,4 @@ public:
std::vector<StringMap> ParseColumnEntryTsv(std::string tsv);
};
#endif // EAGLE0_COMMON_TSV_PARSER_HPP
#endif /* TsvParser_hpp */
@@ -6,8 +6,8 @@
// Copyright © 2017 none. All rights reserved.
//
#ifndef EAGLE0_COMMON_BYTE_VECTOR_HPP
#define EAGLE0_COMMON_BYTE_VECTOR_HPP
#ifndef byte_vector_h
#define byte_vector_h
#include <cstdint>
#include <cstring>
@@ -184,4 +184,4 @@ inline auto operator<<(std::ostream& ostr, byte_vector vec) -> std::ostream& {
return ostr;
}
#endif // EAGLE0_COMMON_BYTE_VECTOR_HPP
#endif /* byte_vector_h */
@@ -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(
@@ -141,8 +141,9 @@ public:
// Default implementation: no filtering, so filtered index = original index
[[nodiscard]] virtual size_t mapFilteredIndexToOriginal(
size_t filteredIndex,
const MCTSGameState& /*state*/) const {
const MCTSGameState& state) const {
// Default: no filtering, index stays the same
(void)state; // Suppress unused parameter warning
return filteredIndex;
}
@@ -157,4 +158,4 @@ public:
} // namespace mcts
} // namespace shardok
#endif // EAGLE0_MCTS_GAME_ENGINE_HPP
#endif // EAGLE0_MCTS_GAME_ENGINE_HPP
@@ -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"],
@@ -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,25 +22,21 @@ 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(), (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);
(net::eagle0::shardok::common::Terrain_Type)(existingTerrainType + 2);
map.mutable_terrain(i)->set_type(newTerrain);
}
}
@@ -4,52 +4,50 @@
#include "MapInfoCalculator.hpp"
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include "src/main/cpp/net/eagle0/shardok/ai/AIWaterCrossingCalculator.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/HexMapHelpers.hpp"
#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;
using std::endl;
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) {
std::cerr << "Should have 10 starting positions!" << '\n';
std::abort();
if (positionList->positions()->size() < 1) continue;
if (positionList->positions()->size() != 10) {
printf("Should have 10 starting positions!");
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,11 +56,11 @@ 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"
struct alignas(64) OneMapInfo {
using namespace shardok;
using std::map;
using std::string;
struct OneMapInfo {
uint32_t castleCount;
std::string name;
std::map<int, int> positionsRequiringCrossing;
};
string name;
map<int, int> positionsRequiringCrossing;
} __attribute__((aligned(64)));
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
@@ -2,42 +2,37 @@
// Created by Dan Crosby on 4/18/22.
//
#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;
using std::endl;
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];
outputFile.open(outputPath);
outputStreamPtr = &outputFile;
const string outputPath = argv[1];
outputStreamPtr = new std::ofstream(outputPath);
}
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));
}
@@ -46,29 +41,29 @@ auto main(const int argc, char** argv) -> int {
bool firstMap = true;
for (const OneMapInfo& mapInfo : mapInfos) {
if (firstMap) {
outputStream << '\n';
outputStream << endl;
firstMap = false;
} else {
outputStream << "," << '\n';
outputStream << "," << endl;
}
outputStream << " {" << '\n';
outputStream << R"( "name": ")" << mapInfo.name << "\"," << '\n';
outputStream << " {" << endl;
outputStream << R"( "name": ")" << mapInfo.name << "\"," << endl;
outputStream << " \"castleCount\": " << mapInfo.castleCount << "," << '\n';
outputStream << " \"castleCount\": " << mapInfo.castleCount << "," << endl;
outputStream << " \"positions\": {";
bool firstPosition = true;
for (const auto& [position, count] : mapInfo.positionsRequiringCrossing) {
if (firstPosition) {
outputStream << '\n';
outputStream << endl;
firstPosition = false;
} else {
outputStream << "," << '\n';
outputStream << "," << endl;
}
outputStream << " \"" << position << "\": " << count;
}
outputStream << '\n' << " }" << '\n' << " }";
outputStream << endl << " }" << endl << " }";
}
outputStream << '\n' << "]" << '\n';
outputStream << endl << "]" << endl;
}
@@ -2,17 +2,10 @@
// Created by Dan Crosby on 3/29/21.
//
#include <algorithm>
#include <array>
#include <climits>
#include <iostream>
#include <memory>
#include <mutex>
#include <numeric>
#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"
@@ -21,6 +14,8 @@
#include "src/main/protobuf/net/eagle0/common/random_units.pb.h"
using net::eagle0::common::RandomUnits;
using std::shared_ptr;
using std::vector;
constexpr int THREAD_COUNT = 16;
@@ -33,130 +28,96 @@ std::random_device rd;
std::mt19937 g(rd());
#endif
std::mutex randomMutex;
std::vector<std::string> fileNames{};
vector<string> fileNames{};
std::unique_ptr<shardok::ShardokGamesManager> manager;
using namespace 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"); }
void runBattle(const vector<shared_ptr<const UnitProto>>& units) {
auto mapName = fileNames[randInt(0, (int)fileNames.size() - 1)];
auto month = randInt(1, 12);
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);
auto p1UnitCount = randInt(1, 20);
vector<UnitProto> p1Units{};
for (int i = 0; i < p1UnitCount; i++) {
auto unit = units[i];
unit.mutate_player_id(0);
p1Units.push_back(std::move(unit));
auto unit = *units[i];
unit.set_player_id(0);
p1Units.push_back(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);
auto p2UnitCount = randInt(1, 20);
vector<UnitProto> p2Units{};
for (int i = p1UnitCount; i < p1UnitCount + p2UnitCount; i++) {
auto unit = units[i];
unit.mutate_player_id(1);
p2Units.push_back(std::move(unit));
auto unit = *units[i];
unit.set_player_id(1);
p2Units.push_back(unit);
}
std::cout << "map name is " << mapName << ", " << p1UnitCount << " attacking " << p2UnitCount
<< '\n';
printf("map name is %s, %d attacking %d\n", mapName.c_str(), p1UnitCount, p2UnitCount);
std::vector<VictoryConditionProto> attVictoryConditions{
vector<VictoryConditionProto> attVictoryConditions{
VictoryConditionProto::VICTORY_CONDITION_HOLDS_CRITICAL_TILES,
VictoryConditionProto::VICTORY_CONDITION_LAST_PLAYER_STANDING};
std::vector<VictoryConditionProto> defVictoryConditions{
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);
auto p1 = PlayerInfoWithUnits(0, true, 5, false, 10000, attVictoryConditions, {}, p1Units);
auto p2 = PlayerInfoWithUnits(1, false, 7, false, 10000, defVictoryConditions, {}, p2Units);
const std::string gameId = std::to_string(randInt(INT_MIN, INT_MAX));
string gameId = std::to_string(randInt(INT_MIN, INT_MAX));
manager->UnlockedCreateSpecifiedGame(
gameId,
playerInfos,
mapName,
"",
month,
TutorialBattleConfigProto{},
"");
manager->LockedCreateSpecifiedGame(gameId, {p1, p2}, mapName, "", month, "");
}
auto LoadUnits() -> std::vector<shardok::Unit> {
auto LoadUnits() -> vector<shared_ptr<const UnitProto>> {
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";
commonUnits.ParseFromArray(bytes.data(), (int)bytes.size());
printf("Loaded %d units\n", commonUnits.units_size());
std::vector<shardok::Unit> units{};
vector<shared_ptr<const UnitProto>> 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}));
units.push_back(std::make_shared<const UnitProto>(ConvertUnit(commonUnit, 0, {0, 1})));
}
std::shuffle(units.begin(), units.end(), g);
return units;
}
[[noreturn]] void runBattlesThread(const std::vector<shardok::Unit>& units) {
[[noreturn]] void runBattlesThread(const vector<shared_ptr<const UnitProto>>& units) {
while (true) { runBattle(units); }
}
auto main(int /*argc*/, char** argv) -> int {
auto main(int argc, char** argv) -> int {
#if FIXED_SEED
std::srand(4564564);
g.seed(378473);
#else
std::srand(std::time(nullptr));
#endif
FilesystemUtils::SetExecPath(argv[0]);
auto randomUnits = LoadUnits();
std::cout << "Map file directory is " << FilesystemUtils::MapFilesDirectory() << '\n';
printf("Map file directory is %s\n", FilesystemUtils::MapFilesDirectory().c_str());
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>());
manager = new ShardokGamesManager(nullptr, std::vector<std::string>());
std::array<std::thread, THREAD_COUNT> threads;
std::thread threads[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
threads[i] = std::thread(runBattlesThread, randomUnits);
@@ -16,6 +16,9 @@ namespace shardok {
constexpr double kOverpowerRatio = 2.0;
constexpr double kBraveWaterCostMultiplier = 1.2;
using std::pair;
using std::shared_ptr;
DIST_T NormalizedCostWhenBraving(const DIST_T cost) {
if (cost >= static_cast<double>(ActionPointDistances::IMPOSSIBLE) / kBraveWaterCostMultiplier)
return ActionPointDistances::IMPOSSIBLE;
@@ -153,7 +156,6 @@ auto GenerateTargetPriorities(
vector<TargetAndAttackLocations>{});
vector<TargetAndDistance> targetsWithDistance;
targetsWithDistance.reserve(targets.size());
// Get APDs directly from cache (now with built-in thread-local optimization)
const auto& battType = battalionTypeGetter(unit->battalion().type());
@@ -233,4 +235,4 @@ auto GenerateTargetPriorities(
return allTargetsUnitsAndDistances;
}
} // namespace shardok
} // namespace shardok
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 6/19/23.
//
#ifndef EAGLE0_SHARDOK_AI_AI_ATTACK_GROUPS_HPP
#define EAGLE0_SHARDOK_AI_AI_ATTACK_GROUPS_HPP
#ifndef EAGLE0_AIATTACKGROUPS_HPP
#define EAGLE0_AIATTACKGROUPS_HPP
#include <functional>
#include <vector>
@@ -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,17 +64,17 @@ 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
#endif // EAGLE0_SHARDOK_AI_AI_ATTACK_GROUPS_HPP
#endif // EAGLE0_AIATTACKGROUPS_HPP
@@ -17,8 +17,10 @@
namespace shardok {
using Unit = net::eagle0::shardok::storage::fb::Unit;
using std::shared_ptr;
using std::vector;
class alignas(128) AttackLocations {
class AttackLocations {
private:
const MapIndex rowCount;
const MapIndex columnCount;
@@ -145,9 +147,9 @@ public:
adjacentLocations += cs;
allLocations += cs;
}
};
} __attribute__((aligned(128)));
struct alignas(128) AttackLocationsCache {
struct AttackLocationsCache {
private:
const MapIndex rowCount;
const MapIndex columnCount;
@@ -163,8 +165,8 @@ 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;
} __attribute__((aligned(128)));
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
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 1/9/22.
//
#ifndef EAGLE0_SHARDOK_AI_AI_ATTACKER_STRATEGY_SELECTOR_HPP
#define EAGLE0_SHARDOK_AI_AI_ATTACKER_STRATEGY_SELECTOR_HPP
#ifndef EAGLE0_AIATTACKERSTRATEGYSELECTOR_HPP
#define EAGLE0_AIATTACKERSTRATEGYSELECTOR_HPP
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AICommonTypes.hpp"
@@ -32,4 +32,4 @@ public:
};
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_ATTACKER_STRATEGY_SELECTOR_HPP
#endif // EAGLE0_AIATTACKERSTRATEGYSELECTOR_HPP
@@ -8,8 +8,6 @@
#include <chrono>
#include <cmath>
#include <future>
#include <iomanip>
#include <iostream>
#include <limits>
#include "AICommandFilter.hpp"
@@ -24,11 +22,11 @@ namespace shardok {
// No need to forward declare internal functions - use the public interface instead
// Helper constants and static variables
static const std::vector<double> kAverageSequence = {0.5};
static const auto kAverageGenerator = std::make_shared<SequenceRandomGenerator>(kAverageSequence);
static const std::vector<double> _averageSequence = {0.5};
static const auto _averageGenerator = std::make_shared<SequenceRandomGenerator>(_averageSequence);
constexpr bool kMultithread = true;
constexpr bool kLogging = false;
#define MULTITHREAD true
#define LOGGING_ 0
// Helper function to determine if a command type is deterministic
static auto IsDeterministic(const CommandType type) -> bool {
@@ -239,16 +237,15 @@ auto AICommandEvaluator::EvaluateWithRandomness(
return lookaheadFuture.get();
};
if constexpr (kMultithread) {
auto launchPolicy =
remainingLookahead == 1 ? std::launch::async : std::launch::deferred;
returnValue.lookaheadScore = std::async(launchPolicy, lookaheadLambda);
} else {
std::promise<EvaluationResult> p;
returnValue.lookaheadScore = p.get_future();
auto lambdaResult = lookaheadLambda();
p.set_value(lambdaResult);
}
#if MULTITHREAD
auto launchPolicy = remainingLookahead == 1 ? std::launch::async : std::launch::deferred;
returnValue.lookaheadScore = std::async(launchPolicy, lookaheadLambda);
#else
std::promise<EvaluationResult> p;
returnValue.lookaheadScore = p.get_future();
auto lambdaResult = lookaheadLambda();
p.set_value(lambdaResult);
#endif
}
return returnValue;
@@ -305,31 +302,30 @@ auto AICommandEvaluator::FindBestCommand(
minDistToEnemies = 0.0; // No enemies found
}
if constexpr (kLogging) {
// Log command count and distance metrics for performance analysis
const auto allCommandCount = guessedDescriptors->size();
const auto filteredCommandCount = filteredIndices.size();
const int currentRound = gameState->current_round();
#if LOGGING_
// Log command count and distance metrics for performance analysis
const auto allCommandCount = guessedDescriptors->size();
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);
#endif
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;
};
@@ -356,7 +352,7 @@ auto AICommandEvaluator::FindBestCommand(
originalIndex,
remainingLookahead,
maxRepeatCount,
kAverageGenerator,
_averageGenerator,
guessedEngine,
attackerStrategy,
allCastleCoords,
@@ -422,7 +418,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 +428,7 @@ auto AICommandEvaluator::FindBestCommand(
originalIndex,
remainingLookahead,
maxRepeatCount,
std::make_shared<SequenceRandomGenerator>(std::move(sequence)),
std::make_shared<SequenceRandomGenerator>(sequence),
guessedEngine,
attackerStrategy,
allCastleCoords,
@@ -512,30 +507,26 @@ 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,
commandIndex,
remainingLookahead,
maxRepeatCount,
kAverageGenerator,
_averageGenerator,
guessedEngine,
attackerStrategy,
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 +571,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
@@ -3,8 +3,8 @@
// Separated from AIScoreCalculator to isolate pure state scoring from lookahead logic.
//
#ifndef EAGLE0_SHARDOK_AI_AI_COMMAND_EVALUATOR_HPP
#define EAGLE0_SHARDOK_AI_AI_COMMAND_EVALUATOR_HPP
#ifndef EAGLE0_AICOMMANDEVALUATOR_HPP
#define EAGLE0_AICOMMANDEVALUATOR_HPP
#include <chrono>
#include <future>
@@ -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;
};
@@ -114,4 +114,4 @@ private:
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_COMMAND_EVALUATOR_HPP
#endif // EAGLE0_AICOMMANDEVALUATOR_HPP
@@ -2,8 +2,8 @@
// Filter obviously bad commands to reduce search space for AI
//
#ifndef EAGLE0_SHARDOK_AI_AI_COMMAND_FILTER_HPP
#define EAGLE0_SHARDOK_AI_AI_COMMAND_FILTER_HPP
#ifndef EAGLE0_AICOMMANDFILTER_HPP
#define EAGLE0_AICOMMANDFILTER_HPP
#include <memory>
#include <vector>
@@ -116,4 +116,4 @@ private:
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_COMMAND_FILTER_HPP
#endif // EAGLE0_AICOMMANDFILTER_HPP
@@ -3,8 +3,8 @@
// Common type definitions used across AI utility functions
//
#ifndef EAGLE0_SHARDOK_AI_AI_COMMON_TYPES_HPP
#define EAGLE0_SHARDOK_AI_AI_COMMON_TYPES_HPP
#ifndef EAGLE0_AICOMMONTYPES_HPP
#define EAGLE0_AICOMMONTYPES_HPP
#include <functional>
@@ -20,4 +20,4 @@ using BattalionTypeGetter = std::function<BattalionTypeSPtr(BattalionTypeId)>;
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_COMMON_TYPES_HPP
#endif // EAGLE0_AICOMMONTYPES_HPP
@@ -2,8 +2,8 @@
// AI System Types and Configuration
//
#ifndef EAGLE0_SHARDOK_AI_AI_CONFIG_HPP
#define EAGLE0_SHARDOK_AI_AI_CONFIG_HPP
#ifndef EAGLE0_AI_CONFIG_HPP
#define EAGLE0_AI_CONFIG_HPP
namespace shardok {
@@ -22,4 +22,4 @@ enum class ScoringCalculatorType {
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_CONFIG_HPP
#endif // EAGLE0_AI_CONFIG_HPP
@@ -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(
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 8/30/23.
//
#ifndef EAGLE0_SHARDOK_AI_AI_DEFENDER_STRATEGY_SELECTOR_HPP
#define EAGLE0_SHARDOK_AI_AI_DEFENDER_STRATEGY_SELECTOR_HPP
#ifndef EAGLE0_AIDEFENDERSTRATEGYSELECTOR_HPP
#define EAGLE0_AIDEFENDERSTRATEGYSELECTOR_HPP
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AICommonTypes.hpp"
@@ -26,4 +26,4 @@ public:
};
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_DEFENDER_STRATEGY_SELECTOR_HPP
#endif // EAGLE0_AIDEFENDERSTRATEGYSELECTOR_HPP
@@ -55,12 +55,12 @@ auto DefenderDistanceBuf(
const bool includeUndead) -> double {
const auto &locationsToAttackMe = alCache->CachedLocations(defenderLocation, lateGame);
struct alignas(8) WithoutAndWith {
struct WithoutAndWith {
int without;
int with;
WithoutAndWith(const int a, const int b) : without(a), with(b) {}
};
} __attribute__((aligned(8)));
vector<WithoutAndWith> pointCosts{};
pointCosts.reserve(attackerUnits.size());
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 5/10/21.
//
#ifndef EAGLE0_SHARDOK_AI_AI_DISTANCE_DEBUF_HPP
#define EAGLE0_SHARDOK_AI_AI_DISTANCE_DEBUF_HPP
#ifndef EAGLE0_AIDISTANCEDEBUF_HPP
#define EAGLE0_AIDISTANCEDEBUF_HPP
#include "AIAttackLocations.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AICommonTypes.hpp"
@@ -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,
@@ -26,4 +29,4 @@ auto DefenderDistanceBuf(
bool includeUndead) -> double;
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_DISTANCE_DEBUF_HPP
#endif // EAGLE0_AIDISTANCEDEBUF_HPP
@@ -2,34 +2,22 @@
// Temporary AI experiment switches used by benchmark-only EXPERIMENTAL runs.
//
#ifndef EAGLE0_SHARDOK_AI_AI_EXPERIMENT_CONFIG_HPP
#define EAGLE0_SHARDOK_AI_AI_EXPERIMENT_CONFIG_HPP
#ifndef EAGLE0_AI_EXPERIMENT_CONFIG_HPP
#define EAGLE0_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; }
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_EXPERIMENT_CONFIG_HPP
#endif // EAGLE0_AI_EXPERIMENT_CONFIG_HPP
@@ -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,
@@ -6,8 +6,8 @@
// and flee vs fight evaluation for final round scenarios
//
#ifndef EAGLE0_SHARDOK_AI_AI_FLEE_DECISION_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_AI_FLEE_DECISION_CALCULATOR_HPP
#ifndef AIFleeDecisionCalculator_hpp
#define AIFleeDecisionCalculator_hpp
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
@@ -63,4 +63,4 @@ private:
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_FLEE_DECISION_CALCULATOR_HPP
#endif /* AIFleeDecisionCalculator_hpp */
@@ -3,8 +3,8 @@
// Provides O(1) weights based on command type and context
//
#ifndef EAGLE0_SHARDOK_AI_AI_HEURISTIC_WEIGHTING_HPP
#define EAGLE0_SHARDOK_AI_AI_HEURISTIC_WEIGHTING_HPP
#ifndef EAGLE0_AI_HEURISTIC_WEIGHTING_HPP
#define EAGLE0_AI_HEURISTIC_WEIGHTING_HPP
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-redundant-constexpr-static-def"
@@ -37,4 +37,4 @@ public:
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_HEURISTIC_WEIGHTING_HPP
#endif // EAGLE0_AI_HEURISTIC_WEIGHTING_HPP
@@ -2,8 +2,11 @@
// Created by Dan Crosby on 8/31/23.
//
#ifndef EAGLE0_SHARDOK_AI_AI_MINIMUM_DISTANCE_AND_TARGET_HPP
#define EAGLE0_SHARDOK_AI_AI_MINIMUM_DISTANCE_AND_TARGET_HPP
#ifndef EAGLE0_AIMINIMUMDISTANCEANDTARGET_HPP
#define EAGLE0_AIMINIMUMDISTANCEANDTARGET_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"
@@ -12,6 +15,7 @@
namespace shardok {
using net::eagle0::shardok::storage::fb::Coords;
using std::shared_ptr;
struct CoordsAndDistance {
Coords coords;
@@ -30,4 +34,4 @@ auto MinimumDistance(
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_MINIMUM_DISTANCE_AND_TARGET_HPP
#endif // EAGLE0_AIMINIMUMDISTANCEANDTARGET_HPP
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 4/7/21.
//
#ifndef EAGLE0_SHARDOK_AI_AI_SCORE_UTILITIES_HPP
#define EAGLE0_SHARDOK_AI_AI_SCORE_UTILITIES_HPP
#ifndef EAGLE0_AISCOREUTILITIES_HPP
#define EAGLE0_AISCOREUTILITIES_HPP
#include <vector>
@@ -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;
@@ -29,4 +31,4 @@ auto PlayerInfoForPid(const GameStateW &, PlayerId pid) -> const PlayerInfo *;
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_SCORE_UTILITIES_HPP
#endif // EAGLE0_AISCOREUTILITIES_HPP
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 1/10/22.
//
#ifndef EAGLE0_SHARDOK_AI_AI_STRATEGY_HPP
#define EAGLE0_SHARDOK_AI_AI_STRATEGY_HPP
#ifndef EAGLE0_AISTRATEGY_HPP
#define EAGLE0_AISTRATEGY_HPP
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackGroups.hpp"
@@ -34,4 +34,4 @@ auto CrossRiversStrategy(const CoordsSet& targetLocations) -> AIStrategy;
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_STRATEGY_HPP
#endif // EAGLE0_AISTRATEGY_HPP
@@ -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
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 07/04/25.
//
#ifndef EAGLE0_SHARDOK_AI_AI_TIME_BUDGET_HPP
#define EAGLE0_SHARDOK_AI_AI_TIME_BUDGET_HPP
#ifndef EAGLE0_AITIMEBUDGET_HPP
#define EAGLE0_AITIMEBUDGET_HPP
#include <atomic>
#include <chrono>
@@ -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,11 +41,11 @@ 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;
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_TIME_BUDGET_HPP
#endif // EAGLE0_AITIMEBUDGET_HPP
@@ -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(
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 4/7/21.
//
#ifndef EAGLE0_SHARDOK_AI_AI_UNIT_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_AI_UNIT_SCORE_CALCULATOR_HPP
#ifndef EAGLE0_AIUNITSCORECALCULATOR_HPP
#define EAGLE0_AIUNITSCORECALCULATOR_HPP
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistances.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
@@ -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,
@@ -50,4 +53,4 @@ auto UnitValue(
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_UNIT_SCORE_CALCULATOR_HPP
#endif // EAGLE0_AIUNITSCORECALCULATOR_HPP
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 12/29/21.
//
#ifndef EAGLE0_SHARDOK_AI_AI_WATER_CROSSING_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_AI_WATER_CROSSING_CALCULATOR_HPP
#ifndef EAGLE0_AIWATERCROSSINGCALCULATOR_HPP
#define EAGLE0_AIWATERCROSSINGCALCULATOR_HPP
#include "src/main/cpp/net/eagle0/shardok/ai/AICommonTypes.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
@@ -23,8 +23,7 @@ static inline void AssertValid(const Coords& c, const HexMap* hexMap) {
if (!valid) {
std::stringstream str;
str << "Invalid coords " << static_cast<int>(c.row()) << ", "
<< static_cast<int>(c.column());
str << "Invalid coords " << (int)c.row() << ", " << (int)c.column();
throw ShardokInternalErrorException(str.str());
}
@@ -86,4 +85,4 @@ auto WaterCrossingScore(
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_WATER_CROSSING_CALCULATOR_HPP
#endif // EAGLE0_AIWATERCROSSINGCALCULATOR_HPP
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 1/5/22.
//
#ifndef EAGLE0_SHARDOK_AI_AI_WATER_CROSSING_COMMAND_CHOOSER_HPP
#define EAGLE0_SHARDOK_AI_AI_WATER_CROSSING_COMMAND_CHOOSER_HPP
#ifndef EAGLE0_AIWATERCROSSINGCOMMANDCHOOSER_HPP
#define EAGLE0_AIWATERCROSSINGCOMMANDCHOOSER_HPP
#include <utility>
@@ -44,4 +44,4 @@ public:
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_AI_WATER_CROSSING_COMMAND_CHOOSER_HPP
#endif // EAGLE0_AIWATERCROSSINGCOMMANDCHOOSER_HPP
@@ -5,8 +5,6 @@
#include "IterativeDeepeningAI.hpp"
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <limits>
#include <numeric>
#include <utility>
@@ -21,21 +19,7 @@
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
#define DEBUG_ITERATIVE_DEEPENING_TIMINGS 1
IterativeDeepeningAI::IterativeDeepeningAI(
const PlayerId playerId,
@@ -70,9 +54,9 @@ auto IterativeDeepeningAI::IterativeSearch(
// DEBUG: Clear TT to see if that's causing the suspicious depth reaching
// 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";
}
#if DEBUG_ITERATIVE_DEEPENING_TIMINGS
printf("ID AI: Commands are empty, returning early\n");
#endif
result.searchCompleted = true;
return result;
}
@@ -184,23 +168,23 @@ 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';
}
#if DEBUG_ITERATIVE_DEEPENING_TIMINGS
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());
#endif
}
previousBestCommand = currentBestCommand;
@@ -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
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 07/04/25.
//
#ifndef EAGLE0_SHARDOK_AI_ITERATIVE_DEEPENING_AI_HPP
#define EAGLE0_SHARDOK_AI_ITERATIVE_DEEPENING_AI_HPP
#ifndef EAGLE0_ITERATIVEDEEPENINGAI_HPP
#define EAGLE0_ITERATIVEDEEPENINGAI_HPP
#include <chrono>
#include <future>
@@ -111,4 +111,4 @@ private:
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_ITERATIVE_DEEPENING_AI_HPP
#endif // EAGLE0_ITERATIVEDEEPENINGAI_HPP
@@ -8,6 +8,8 @@
#include "ShardokAIClient.hpp"
#define DEBUG_FLEE_DECISIONS
// Enable to dump game state and debug tree to /tmp for debugging
// #define ENABLE_MCTS_DEBUG_DUMP
@@ -15,12 +17,9 @@
#include <chrono>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#endif
#include <iomanip>
#include <iostream>
#include <set>
#include <unordered_map>
@@ -44,7 +43,6 @@ using net::eagle0::shardok::api::ActionResultView;
using net::eagle0::shardok::api::GameStateView;
static constexpr bool kPerformanceLogging = true;
static constexpr bool kDebugFleeDecisions = true;
void ApplyUpdate(GameStateView & /*currentView*/, const ActionResultView & /*update*/) {}
@@ -177,20 +175,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 +207,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 +239,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 +357,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 +397,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;
@@ -445,7 +468,12 @@ auto ShardokAIClient::FinalRoundAttackerChooseCommandIndex(
maxRounds,
minimumFleeOddsThreshold,
desperateFleeThreshold,
kDebugFleeDecisions);
#ifdef DEBUG_FLEE_DECISIONS
true // Enable debug logging
#else
false
#endif
);
if (fleeDecision.shouldFlee) {
CommandChoiceResults results{};
@@ -488,7 +516,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 +527,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 +539,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 {
@@ -6,8 +6,8 @@
// Copyright © 2017 none. All rights reserved.
//
#ifndef EAGLE0_SHARDOK_AI_SHARDOK_AI_CLIENT_HPP
#define EAGLE0_SHARDOK_AI_SHARDOK_AI_CLIENT_HPP
#ifndef ShardokAIClient_hpp
#define ShardokAIClient_hpp
#include <vector>
@@ -95,4 +95,4 @@ public:
};
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_SHARDOK_AI_CLIENT_HPP
#endif /* ShardokAIClient_hpp */
@@ -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
@@ -2,13 +2,12 @@
// TranspositionTable.hpp - Cache for game state evaluations to avoid redundant calculations
//
#ifndef EAGLE0_SHARDOK_AI_TRANSPOSITION_TABLE_HPP
#define EAGLE0_SHARDOK_AI_TRANSPOSITION_TABLE_HPP
#ifndef EAGLE0_TRANSPOSITIONTABLE_HPP
#define EAGLE0_TRANSPOSITIONTABLE_HPP
#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
@@ -90,4 +88,4 @@ extern TranspositionTable g_transpositionTable;
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_TRANSPOSITION_TABLE_HPP
#endif // EAGLE0_TRANSPOSITIONTABLE_HPP
@@ -2,8 +2,8 @@
// Shardok-specific MCTS AI that wraps the abstract implementation
//
#ifndef EAGLE0_SHARDOK_AI_MCTS_SHARDOK_MCTSAI_HPP
#define EAGLE0_SHARDOK_AI_MCTS_SHARDOK_MCTSAI_HPP
#ifndef EAGLE0_SHARDOK_MCTSAI_HPP
#define EAGLE0_SHARDOK_MCTSAI_HPP
#include <memory>
#include <vector>
@@ -68,4 +68,4 @@ private:
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_MCTS_SHARDOK_MCTSAI_HPP
#endif // EAGLE0_SHARDOK_MCTSAI_HPP
@@ -2,8 +2,8 @@
// Shardok-specific action adapter for MCTS
//
#ifndef EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_ACTION_HPP
#define EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_ACTION_HPP
#ifndef EAGLE0_SHARDOK_ACTION_HPP
#define EAGLE0_SHARDOK_ACTION_HPP
#include <memory>
#include <string>
@@ -58,4 +58,4 @@ private:
} // namespace shardok::mcts
#endif // EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_ACTION_HPP
#endif // EAGLE0_SHARDOK_ACTION_HPP
@@ -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);
}
}
@@ -2,14 +2,13 @@
// Shardok-specific game engine adapter for MCTS
//
#ifndef EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_GAME_ENGINE_HPP
#define EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_GAME_ENGINE_HPP
#ifndef EAGLE0_SHARDOK_GAME_ENGINE_HPP
#define EAGLE0_SHARDOK_GAME_ENGINE_HPP
#include <atomic>
#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();
@@ -143,4 +141,4 @@ public:
} // namespace mcts
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_GAME_ENGINE_HPP
#endif // EAGLE0_SHARDOK_GAME_ENGINE_HPP
@@ -2,8 +2,8 @@
// Shardok-specific game state adapter for MCTS
//
#ifndef EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_GAME_STATE_HPP
#define EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_GAME_STATE_HPP
#ifndef EAGLE0_SHARDOK_GAME_STATE_HPP
#define EAGLE0_SHARDOK_GAME_STATE_HPP
#include <memory>
@@ -80,4 +80,4 @@ private:
} // namespace mcts
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_GAME_STATE_HPP
#endif // EAGLE0_SHARDOK_GAME_STATE_HPP
@@ -2,8 +2,8 @@
// Factory for creating Shardok-specific MCTS components
//
#ifndef EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_MCTS_FACTORY_HPP
#define EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_MCTS_FACTORY_HPP
#ifndef EAGLE0_SHARDOK_MCTS_FACTORY_HPP
#define EAGLE0_SHARDOK_MCTS_FACTORY_HPP
#include <functional>
#include <memory>
@@ -67,4 +67,4 @@ public:
} // namespace mcts
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_MCTS_FACTORY_HPP
#endif // EAGLE0_SHARDOK_MCTS_FACTORY_HPP
@@ -2,8 +2,10 @@
// Created by dancrosby on 3/4/20.
//
#ifndef EAGLE0_SHARDOK_AI_SCORE_AI_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_SCORE_AI_SCORE_CALCULATOR_HPP
#ifndef EAGLE0_AISCORECALCULATOR_HPP
#define EAGLE0_AISCORECALCULATOR_HPP
#include <future>
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
@@ -14,6 +16,8 @@
namespace shardok {
using shardok::PlayerId;
using std::future;
using std::vector;
using ScoreValue = double;
@@ -49,4 +53,4 @@ public:
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_SCORE_AI_SCORE_CALCULATOR_HPP
#endif // EAGLE0_AISCORECALCULATOR_HPP
@@ -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;
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 4/11/21.
//
#ifndef EAGLE0_SHARDOK_AI_SCORE_AI_VICTORY_CONDITION_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_SCORE_AI_VICTORY_CONDITION_SCORE_CALCULATOR_HPP
#ifndef EAGLE0_AIVICTORYCONDITIONSCORECALCULATOR_HPP
#define EAGLE0_AIVICTORYCONDITIONSCORECALCULATOR_HPP
#include <vector>
@@ -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;
@@ -47,4 +48,4 @@ auto LastPlayerStandingVictoryScore(
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_SCORE_AI_VICTORY_CONDITION_SCORE_CALCULATOR_HPP
#endif // EAGLE0_AIVICTORYCONDITIONSCORECALCULATOR_HPP
@@ -3,8 +3,8 @@
// Uses bounded linear scoring tuned for MCTS exploration/exploitation balance
//
#ifndef EAGLE0_SHARDOK_AI_SCORE_MCTS_OPTIMIZED_AI_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_SCORE_MCTS_OPTIMIZED_AI_SCORE_CALCULATOR_HPP
#ifndef EAGLE0_MCTSOPTIMIZEDAISCORECALCULATOR_HPP
#define EAGLE0_MCTSOPTIMIZEDAISCORECALCULATOR_HPP
#include <memory>
@@ -29,4 +29,4 @@ using ALCache = std::unique_ptr<AttackLocationsCache>;
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_SCORE_MCTS_OPTIMIZED_AI_SCORE_CALCULATOR_HPP
#endif // EAGLE0_MCTSOPTIMIZEDAISCORECALCULATOR_HPP
@@ -8,9 +8,6 @@
#include <atomic>
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <unordered_map>
#include "private/AIScoreCalculatorSharedUtilities.hpp"
@@ -44,6 +41,9 @@ class StandardAIScoreCalculator;
// Anonymous namespace for helper functions that don't need access to scorer
namespace {
#define LOGGING_ 0
#define PERFORMANCE_LOGGING_ 0
// Performance logging for AttackerScoreForState
struct AttackerScorePerformanceLogger {
static constexpr int LOG_INTERVAL = 100000;
@@ -60,12 +60,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 +143,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();
@@ -2,8 +2,8 @@
// Standard implementation of AIScoreCalculator
//
#ifndef EAGLE0_SHARDOK_AI_SCORE_STANDARD_AI_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_SCORE_STANDARD_AI_SCORE_CALCULATOR_HPP
#ifndef EAGLE0_STANDARDAISCORECALCULATOR_HPP
#define EAGLE0_STANDARDAISCORECALCULATOR_HPP
#include <memory>
@@ -34,4 +34,4 @@ using ALCache = std::unique_ptr<AttackLocationsCache>;
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_SCORE_STANDARD_AI_SCORE_CALCULATOR_HPP
#endif // EAGLE0_STANDARDAISCORECALCULATOR_HPP
@@ -3,8 +3,8 @@
// This file is private to the ai/score package
//
#ifndef EAGLE0_SHARDOK_AI_SCORE_PRIVATE_AI_SCORE_CALCULATOR_SHARED_UTILITIES_HPP
#define EAGLE0_SHARDOK_AI_SCORE_PRIVATE_AI_SCORE_CALCULATOR_SHARED_UTILITIES_HPP
#ifndef EAGLE0_AI_SCORE_CALCULATOR_SHARED_UTILITIES_HPP
#define EAGLE0_AI_SCORE_CALCULATOR_SHARED_UTILITIES_HPP
#include <vector>
@@ -92,4 +92,4 @@ auto AttackerMultiplierForTargetDistance(
} // namespace score_calculator_internal
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_SCORE_PRIVATE_AI_SCORE_CALCULATOR_SHARED_UTILITIES_HPP
#endif // EAGLE0_AI_SCORE_CALCULATOR_SHARED_UTILITIES_HPP
@@ -47,19 +47,19 @@ auto AbstractAIScoreCalculator::CalculateUnitsScoreComponents(
// Memoization cache for EffectiveDistance calls
EffectiveDistanceCache distanceCache;
// Early return for empty game states
const size_t estimatedUnitCount = cachedUnits->size();
if (estimatedUnitCount == 0) { return UnitsScoreComponents{0.0, 0.0}; }
std::vector<const Unit *> attackerUnits{};
std::vector<const Unit *> defenderUnits{};
// Pre-allocate vectors based on estimated unit ratios to avoid reallocations
attackerUnits.reserve(estimatedUnitCount);
defenderUnits.reserve(estimatedUnitCount);
const size_t estimatedUnitCount = cachedUnits->size();
attackerUnits.reserve(estimatedUnitCount - 1);
defenderUnits.reserve(estimatedUnitCount - 1);
double attackerUnitsValue = 0;
double defenderUnitsValue = 0;
// Early return for empty game states
if (cachedUnits->size() == 0) { return UnitsScoreComponents{0.0, 0.0}; }
auto occupants = Occupants(*cachedUnits, cachedRowCount, cachedColumnCount);
for (const Unit *unit : *cachedUnits) {
@@ -3,8 +3,8 @@
// This file is private to the ai/score package
//
#ifndef EAGLE0_SHARDOK_AI_SCORE_PRIVATE_ABSTRACT_AI_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_SCORE_PRIVATE_ABSTRACT_AI_SCORE_CALCULATOR_HPP
#ifndef EAGLE0_ABSTRACT_AI_SCORE_CALCULATOR_HPP
#define EAGLE0_ABSTRACT_AI_SCORE_CALCULATOR_HPP
#include <memory>
#include <unordered_map>
@@ -178,4 +178,4 @@ private:
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_SCORE_PRIVATE_ABSTRACT_AI_SCORE_CALCULATOR_HPP
#endif // EAGLE0_ABSTRACT_AI_SCORE_CALCULATOR_HPP
@@ -21,7 +21,7 @@ std::unique_ptr<BattleConfigProto> AiBattleConfigLoader::LoadFromJsonFile(
const std::string& filepath) {
std::ifstream file(filepath);
if (!file.is_open()) {
std::cerr << "Failed to open config file: " << filepath << '\n';
std::cerr << "Failed to open config file: " << filepath << std::endl;
return nullptr;
}
@@ -41,7 +41,7 @@ std::unique_ptr<BattleConfigProto> AiBattleConfigLoader::LoadFromJsonString(
google::protobuf::util::JsonStringToMessage(jsonString, config.get(), options);
if (!status.ok()) {
std::cerr << "Failed to parse JSON config: " << status.message() << '\n';
std::cerr << "Failed to parse JSON config: " << status.message() << std::endl;
return nullptr;
}
@@ -103,7 +103,7 @@ std::string AiBattleConfigLoader::ToJsonString(const BattleConfigProto& config)
const auto status = google::protobuf::util::MessageToJsonString(config, &jsonString, options);
if (!status.ok()) {
std::cerr << "Failed to convert config to JSON: " << status.message() << '\n';
std::cerr << "Failed to convert config to JSON: " << status.message() << std::endl;
return "";
}
@@ -2,8 +2,8 @@
// AI Battle Simulator Configuration Loader
//
#ifndef EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_CONFIG_HPP
#define EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_CONFIG_HPP
#ifndef EAGLE0_AI_BATTLE_CONFIG_HPP
#define EAGLE0_AI_BATTLE_CONFIG_HPP
#include <memory>
#include <string>
@@ -44,4 +44,4 @@ public:
} // namespace ai_battle_simulator
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_CONFIG_HPP
#endif // EAGLE0_AI_BATTLE_CONFIG_HPP
@@ -8,7 +8,6 @@
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <utility>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
@@ -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(
@@ -750,8 +744,7 @@ BattleResult AiBattleSimulator::RunBattlePhase(
auto availableCommands = engine.GetAvailableCommandProtos(currentPlayer, false);
if (availableCommands.empty()) {
std::cout << "No commands available for player " << static_cast<int>(currentPlayer)
<< "\n";
std::cout << "No commands available for player " << (int)currentPlayer << "\n";
break;
}
@@ -917,7 +910,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; }
@@ -2,8 +2,8 @@
// AI Battle Simulator - Runs AI vs AI battles to completion
//
#ifndef EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_SIMULATOR_HPP
#define EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_SIMULATOR_HPP
#ifndef EAGLE0_AI_BATTLE_SIMULATOR_HPP
#define EAGLE0_AI_BATTLE_SIMULATOR_HPP
#include <memory>
#include <optional>
@@ -149,4 +149,4 @@ private:
} // namespace ai_battle_simulator
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_SIMULATOR_HPP
#endif // EAGLE0_AI_BATTLE_SIMULATOR_HPP
@@ -88,7 +88,8 @@ void GenerateConfigFile(const std::string& outputPath) {
// Output to file
std::ofstream outFile(outputPath);
if (!outFile.is_open()) {
throw std::runtime_error("Failed to open output file: " + outputPath);
std::cerr << "Error: Failed to open output file: " << outputPath << "\n";
std::exit(1);
}
outFile << jsonString << "\n";
std::cout << "Sample config written to: " << outputPath << "\n";
@@ -294,7 +295,8 @@ BattleResult RunBattle(
if (!config) {
restoreOutput();
throw std::runtime_error("Failed to load config file");
std::cerr << "Error: Failed to load config file\n";
std::exit(1);
}
// Create simulator
@@ -326,8 +328,6 @@ int main(int argc, char* argv[]) {
std::vector<std::string> configPaths;
std::vector<std::string> statePaths;
configPaths.reserve(argc - 1);
statePaths.reserve(argc - 1);
std::string outputPath;
std::string tracePath;
std::string runLabel;
@@ -2,6 +2,7 @@
// Converts saved Eagle ShardokBattle/GameState protobuf blobs into AI battle configs.
//
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <optional>
@@ -145,9 +146,9 @@ int main(int argc, char *argv[]) {
} else if (arg.starts_with("--output=")) {
outputPath = arg.substr(9);
} else if (arg.starts_with("--seed=")) {
seed = std::stoi(arg.substr(7));
seed = std::atoi(arg.substr(7).c_str());
} else if (arg.starts_with("--max-rounds=")) {
maxRounds = std::stoi(arg.substr(13));
maxRounds = std::atoi(arg.substr(13).c_str());
} else if (arg.starts_with("--attacker-scoring=")) {
const auto scoring = ParseScoring(arg.substr(19));
if (!scoring.has_value()) { throw std::runtime_error("Unknown attacker scoring"); }
@@ -115,7 +115,6 @@ int main(int argc, char *argv[]) {
std::string resultPath;
std::string stateFbPath;
std::vector<std::string> actionViewPaths;
actionViewPaths.reserve(argc - 1);
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
@@ -146,9 +145,6 @@ int main(int argc, char *argv[]) {
std::vector<int> attackerFactions;
std::vector<int> defenderFactions;
std::vector<std::string> playerSides;
attackerFactions.reserve(battle.players_size());
defenderFactions.reserve(battle.players_size());
playerSides.reserve(battle.players_size());
bool aiVsAi = true;
for (const auto &player : battle.players()) {
if (player.eagle_fid() == 3 || player.eagle_fid() == 4) { aiVsAi = false; }
@@ -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);
}
}
@@ -86,7 +78,7 @@ auto ParseCommandLineArgs(int argc, char* argv[]) -> std::optional<PerformanceTe
} // namespace
int main(int argc, char* argv[]) {
std::cout << "Starting AI Performance Runner..." << '\n';
std::cout << "Starting AI Performance Runner..." << std::endl;
// Set exec path so FilesystemUtils can find resource files
FilesystemUtils::SetExecPath(argv[0]);
@@ -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;
}
}
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 2025-01-15.
//
#ifndef EAGLE0_SHARDOK_AI_PERFORMANCE_RUNNER_AI_PERFORMANCE_RUNNER_HPP
#define EAGLE0_SHARDOK_AI_PERFORMANCE_RUNNER_AI_PERFORMANCE_RUNNER_HPP
#ifndef EAGLE0_AIPERFORMANCERUNNER_HPP
#define EAGLE0_AIPERFORMANCERUNNER_HPP
#include <chrono>
#include <map>
@@ -52,4 +52,4 @@ struct PerformanceTestConfig {
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_PERFORMANCE_RUNNER_AI_PERFORMANCE_RUNNER_HPP
#endif // EAGLE0_AIPERFORMANCERUNNER_HPP
@@ -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
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 2025-01-15.
//
#ifndef EAGLE0_SHARDOK_AI_PERFORMANCE_RUNNER_PERFORMANCE_TEST_GAME_STATE_BUILDER_HPP
#define EAGLE0_SHARDOK_AI_PERFORMANCE_RUNNER_PERFORMANCE_TEST_GAME_STATE_BUILDER_HPP
#ifndef EAGLE0_PERFORMANCETESTGAMESTATEBUILDER_HPP
#define EAGLE0_PERFORMANCETESTGAMESTATEBUILDER_HPP
#include <flatbuffers/flatbuffers.h>
@@ -85,4 +85,4 @@ private:
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_PERFORMANCE_RUNNER_PERFORMANCE_TEST_GAME_STATE_BUILDER_HPP
#endif // EAGLE0_PERFORMANCETESTGAMESTATEBUILDER_HPP

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