mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-29 06:35:41 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
944ed19e3b |
@@ -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
@@ -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'
|
||||
@@ -255,7 +255,7 @@ jobs:
|
||||
echo "=== All images pushed successfully ==="
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: [self-hosted, bazel]
|
||||
needs: [build-all]
|
||||
if: needs.build-all.outputs.skip_build != 'true' && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true'))
|
||||
environment: production
|
||||
@@ -334,11 +334,25 @@ jobs:
|
||||
ssh-keyscan -H "$DO_DROPLET_IP" >> ~/.ssh/known_hosts 2>/dev/null || true
|
||||
|
||||
- name: Download warmup binary
|
||||
id: download-warmup
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: warmup-binary
|
||||
path: scripts/bin/
|
||||
|
||||
- name: Build warmup binary fallback
|
||||
if: steps.download-warmup.outcome != 'success'
|
||||
run: |
|
||||
echo "::warning::warmup-binary artifact was unavailable; rebuilding warmup binary before deploy"
|
||||
./ci/github_actions/ensure_bazel_installed.sh
|
||||
bazel build \
|
||||
--platforms=//:linux_x86_64 \
|
||||
--extra_toolchains=@llvm_toolchain_linux//:cc-toolchain-x86_64-linux \
|
||||
//ci:warmup_tar
|
||||
mkdir -p scripts/bin
|
||||
tar -xf bazel-bin/ci/warmup_tar.tar -C scripts/bin --strip-components=1
|
||||
|
||||
- name: Copy config files to droplet
|
||||
run: |
|
||||
# Create directory structure on remote
|
||||
|
||||
@@ -177,12 +177,10 @@ jobs:
|
||||
env:
|
||||
DO_CDN_PAT: ${{ secrets.DO_CDN_PAT }}
|
||||
run: |
|
||||
UNITY_VERSION=$(grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //')
|
||||
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
|
||||
curl -s -X DELETE "https://api.digitalocean.com/v2/cdn/endpoints/8c98df29-6f0c-4704-8e82-ca40a2b81aa8/cache" \
|
||||
-H "Authorization: Bearer $DO_CDN_PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"files\": [\"addressables/iOS/$UNITY_MAJOR_MINOR/*\"]}" \
|
||||
-d '{"files": ["addressables/iOS/*"]}' \
|
||||
--fail || echo "Warning: CDN purge failed (non-fatal)"
|
||||
|
||||
- name: Archive Build Log
|
||||
|
||||
@@ -50,11 +50,9 @@ on:
|
||||
- "scripts/notarize_wait.sh"
|
||||
- "ci/github_actions/build_mac.sh"
|
||||
- "ci/github_actions/build_unity_mac.sh"
|
||||
- "ci/github_actions/ensure_unity_installed.sh"
|
||||
- "ci/github_actions/ensure_bazel_installed.sh"
|
||||
- ".github/actions/setup-bazel/**"
|
||||
- "ci/github_actions/detect_addressables_changes.sh"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectSettings.asset"
|
||||
- ".bazelrc"
|
||||
- "ci/mac/**"
|
||||
@@ -125,25 +123,11 @@ jobs:
|
||||
- name: Clean stale files
|
||||
run: |
|
||||
git clean -ffd
|
||||
LIBRARY_DIR="src/main/csharp/net/eagle0/clients/unity/eagle0/Library"
|
||||
VERSION_CACHE="$LIBRARY_DIR/.last_unity_version"
|
||||
PROJECT_VERSION_FILE="src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt"
|
||||
CURRENT_VERSION=$(grep "m_EditorVersion:" "$PROJECT_VERSION_FILE" | head -1 | sed 's/m_EditorVersion: //')
|
||||
|
||||
# Nuke Library/ when Unity version changes to avoid import loops
|
||||
if [ -f "$VERSION_CACHE" ]; then
|
||||
CACHED_VERSION=$(cat "$VERSION_CACHE")
|
||||
if [ "$CACHED_VERSION" != "$CURRENT_VERSION" ]; then
|
||||
echo "Unity version changed ($CACHED_VERSION -> $CURRENT_VERSION) -- clearing Library/"
|
||||
rm -rf "$LIBRARY_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Only clear Bee/ when C# files were added/deleted/renamed (structural
|
||||
# changes that stale the DAG). Content-only modifications are handled by
|
||||
# Bee's incremental compilation. See persist_library.sh for background.
|
||||
BEE_DIR="$LIBRARY_DIR/Bee"
|
||||
SHA_FILE="$LIBRARY_DIR/.last_built_sha"
|
||||
BEE_DIR="src/main/csharp/net/eagle0/clients/unity/eagle0/Library/Bee"
|
||||
SHA_FILE="src/main/csharp/net/eagle0/clients/unity/eagle0/Library/.last_built_sha"
|
||||
if [ -f "$SHA_FILE" ] && [ -d "$BEE_DIR" ]; then
|
||||
LAST_SHA=$(cat "$SHA_FILE")
|
||||
if git diff --diff-filter=ADR --name-only "$LAST_SHA" HEAD -- '*.cs' '*.csproj' '*.asmdef' 2>/dev/null | grep -q .; then
|
||||
@@ -200,12 +184,6 @@ jobs:
|
||||
mkdir -p src/main/csharp/net/eagle0/clients/unity/eagle0/Library
|
||||
git rev-parse HEAD > src/main/csharp/net/eagle0/clients/unity/eagle0/Library/.last_built_sha
|
||||
|
||||
- name: Save Unity version for Library/ cache invalidation
|
||||
if: success()
|
||||
run: |
|
||||
mkdir -p src/main/csharp/net/eagle0/clients/unity/eagle0/Library
|
||||
grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //' > src/main/csharp/net/eagle0/clients/unity/eagle0/Library/.last_unity_version
|
||||
|
||||
- name: Check if should deploy
|
||||
id: check-deploy
|
||||
run: |
|
||||
@@ -267,7 +245,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 +473,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 +493,7 @@ jobs:
|
||||
curl -s -X DELETE "https://api.digitalocean.com/v2/cdn/endpoints/8c98df29-6f0c-4704-8e82-ca40a2b81aa8/cache" \
|
||||
-H "Authorization: Bearer $DO_CDN_PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"files": ["mac/*", "addressables/StandaloneOSX/${{ needs.deploy-mac.outputs.deployed_unity_major_minor }}/*"]}' \
|
||||
-d '{"files": ["mac/*", "addressables/StandaloneOSX/*"]}' \
|
||||
--fail || echo "Warning: CDN purge failed (non-fatal)"
|
||||
|
||||
curl -X POST "https://admin.eagle0.net/notify-update?platform=mac&version=${{ needs.deploy-mac.outputs.deployed_version }}&required=false" \
|
||||
|
||||
@@ -20,7 +20,6 @@ on:
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- 'scripts/build_eagle_ci.sh'
|
||||
- 'scripts/build_shardok_ci.sh'
|
||||
- 'scripts/build_shardok_linux_arm64_ci.sh'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- '.github/workflows/server_build.yml'
|
||||
|
||||
@@ -84,7 +83,6 @@ jobs:
|
||||
'src/main/cpp/',
|
||||
'src/main/protobuf/net/eagle0/shardok/',
|
||||
'scripts/build_shardok_ci.sh',
|
||||
'scripts/build_shardok_linux_arm64_ci.sh',
|
||||
];
|
||||
|
||||
const shared = matches(sharedPatterns);
|
||||
@@ -108,10 +106,6 @@ jobs:
|
||||
name: 'Shardok server',
|
||||
command: './scripts/build_shardok_ci.sh',
|
||||
});
|
||||
include.push({
|
||||
name: 'Shardok Linux ARM64 server',
|
||||
command: './scripts/build_shardok_linux_arm64_ci.sh',
|
||||
});
|
||||
}
|
||||
core.setOutput('build_matrix', JSON.stringify({include}));
|
||||
|
||||
|
||||
@@ -196,9 +196,7 @@ jobs:
|
||||
echo "Also tagged as: registry.digitalocean.com/eagle0/shardok-server:arm64-latest"
|
||||
|
||||
deploy-hetzner:
|
||||
# Keep this on a self-hosted Mac runner for IPv6 reachability to Hetzner,
|
||||
# but do not require a Bazel runner slot for artifact transport/deploy work.
|
||||
runs-on: [self-hosted, macOS, ARM64]
|
||||
runs-on: [self-hosted, bazel]
|
||||
needs: [build-shardok-arm64]
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
environment: production
|
||||
|
||||
@@ -16,6 +16,8 @@ on:
|
||||
- "scripts/build_windows_plugin.sh"
|
||||
- "ci/github_actions/build_unity.sh"
|
||||
- "ci/github_actions/test_unity_editmode.sh"
|
||||
- "ci/github_actions/detect_unity_proto_changes.sh"
|
||||
- "ci/github_actions/ensure_unity_protos.sh"
|
||||
- "ci/github_actions/ensure_bazel_installed.sh"
|
||||
- ".github/actions/setup-bazel/**"
|
||||
- "ci/github_actions/detect_addressables_changes.sh"
|
||||
@@ -41,6 +43,8 @@ on:
|
||||
- "scripts/build_windows_plugin.sh"
|
||||
- "ci/github_actions/build_unity.sh"
|
||||
- "ci/github_actions/test_unity_editmode.sh"
|
||||
- "ci/github_actions/detect_unity_proto_changes.sh"
|
||||
- "ci/github_actions/ensure_unity_protos.sh"
|
||||
- "ci/github_actions/ensure_bazel_installed.sh"
|
||||
- ".github/actions/setup-bazel/**"
|
||||
- "ci/github_actions/detect_addressables_changes.sh"
|
||||
@@ -103,15 +107,34 @@ jobs:
|
||||
clean: false # Library/ persists between runs on self-hosted runners
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Detect Unity proto changes
|
||||
id: unity-protos
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
||||
else
|
||||
BASE_SHA="${{ github.event.before }}"
|
||||
fi
|
||||
|
||||
./ci/github_actions/detect_unity_proto_changes.sh "$BASE_SHA" "${{ github.sha }}"
|
||||
|
||||
- name: Clean stale files
|
||||
run: |
|
||||
git clean -ffd
|
||||
UNITY_ROOT="src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
# These ignored proto outputs are mutually exclusive across proto-generation
|
||||
# layouts. Clean both before regenerating to avoid stale DLL/source duplicates
|
||||
# when a self-hosted runner switches between main and PR branches.
|
||||
# layouts. Always clean generated source protos; clean the DLL only when
|
||||
# proto inputs changed so unchanged runs can reuse the previous DLL
|
||||
# without paying Bazel analysis/startup cost.
|
||||
rm -rf "$UNITY_ROOT/Assets/GeneratedProtos"
|
||||
rm -rf "$UNITY_ROOT/Assets/Plugins/Eagle0Protos"
|
||||
if [ "${{ steps.unity-protos.outputs.changed }}" = "true" ]; then
|
||||
rm -rf "$UNITY_ROOT/Assets/Plugins/Eagle0Protos"
|
||||
else
|
||||
echo "Keeping existing Eagle0Protos DLL because Unity proto inputs are unchanged."
|
||||
fi
|
||||
LIBRARY_DIR="src/main/csharp/net/eagle0/clients/unity/eagle0/Library"
|
||||
VERSION_CACHE="$LIBRARY_DIR/.last_unity_version"
|
||||
PROJECT_VERSION_FILE="src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt"
|
||||
@@ -158,6 +181,8 @@ jobs:
|
||||
uses: ./.github/actions/setup-bazel
|
||||
|
||||
- name: Run Unity EditMode tests
|
||||
env:
|
||||
UNITY_PROTOS_CHANGED: ${{ steps.unity-protos.outputs.changed }}
|
||||
run: ./ci/github_actions/test_unity_editmode.sh
|
||||
|
||||
- name: Archive EditMode test artifacts
|
||||
@@ -218,15 +243,34 @@ jobs:
|
||||
clean: false # Library/ persists between runs on self-hosted runners
|
||||
fetch-depth: 2 # Keep the previous main commit available for Addressables change detection
|
||||
|
||||
- name: Detect Unity proto changes
|
||||
id: unity-protos
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
||||
else
|
||||
BASE_SHA="${{ github.event.before }}"
|
||||
fi
|
||||
|
||||
./ci/github_actions/detect_unity_proto_changes.sh "$BASE_SHA" "${{ github.sha }}"
|
||||
|
||||
- name: Clean stale files
|
||||
run: |
|
||||
git clean -ffd
|
||||
UNITY_ROOT="src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
# These ignored proto outputs are mutually exclusive across proto-generation
|
||||
# layouts. Clean both before regenerating to avoid stale DLL/source duplicates
|
||||
# when a self-hosted runner switches between main and PR branches.
|
||||
# layouts. Always clean generated source protos; clean the DLL only when
|
||||
# proto inputs changed so unchanged runs can reuse the previous DLL
|
||||
# without paying Bazel analysis/startup cost.
|
||||
rm -rf "$UNITY_ROOT/Assets/GeneratedProtos"
|
||||
rm -rf "$UNITY_ROOT/Assets/Plugins/Eagle0Protos"
|
||||
if [ "${{ steps.unity-protos.outputs.changed }}" = "true" ]; then
|
||||
rm -rf "$UNITY_ROOT/Assets/Plugins/Eagle0Protos"
|
||||
else
|
||||
echo "Keeping existing Eagle0Protos DLL because Unity proto inputs are unchanged."
|
||||
fi
|
||||
LIBRARY_DIR="src/main/csharp/net/eagle0/clients/unity/eagle0/Library"
|
||||
VERSION_CACHE="$LIBRARY_DIR/.last_unity_version"
|
||||
PROJECT_VERSION_FILE="src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt"
|
||||
@@ -287,7 +331,9 @@ 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 }}"
|
||||
env:
|
||||
UNITY_PROTOS_CHANGED: ${{ steps.unity-protos.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()
|
||||
|
||||
@@ -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
|
||||
@@ -313,7 +294,7 @@ to be used for different players or game situations within the same server proce
|
||||
**C# (Unity Client):**
|
||||
|
||||
- Located in `/src/main/csharp/net/eagle0/clients/unity/eagle0/`
|
||||
- Uses Unity 6.5 (6000.5.0f1) with comprehensive protobuf integration (100+ .proto files)
|
||||
- Uses Unity 6 (6000.4.10f1) with comprehensive protobuf integration (100+ .proto files)
|
||||
- Key components: `EagleConnection.cs` (gRPC client), `EagleGameController.cs` (main game logic)
|
||||
- Real-time bidirectional streaming with server via `PersistentClientConnection.cs`
|
||||
- Strategic map UI in `Assets/Eagle/`, tactical battle UI in `Assets/Shardok/`
|
||||
|
||||
+1
-1
@@ -282,7 +282,7 @@ oci.pull(
|
||||
# Base image for Admin Server (Alpine for lightweight Go binary)
|
||||
oci.pull(
|
||||
name = "alpine_linux",
|
||||
digest = "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
|
||||
digest = "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
|
||||
image = "docker.io/library/alpine",
|
||||
platforms = ["linux/amd64"],
|
||||
tag = "3.24",
|
||||
|
||||
Generated
+8
-8
@@ -904,7 +904,7 @@
|
||||
"@@rules_oci+//oci:extensions.bzl%oci": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "H23W9dvwCQM6h+Wt8PjIahx08EkAU5eSbUxAyx9Mopc=",
|
||||
"usagesDigest": "k6DBHAkb5br/tpwCPUMJgqwb+7U4DXyCmBZQBgLnHJ8=",
|
||||
"usagesDigest": "tbQ7wHHObzzrBzSRv652104GY3xxiaQDK5OUp2YeEbg=",
|
||||
"recordedInputs": [
|
||||
"REPO_MAPPING:aspect_bazel_lib+,bazel_tools bazel_tools",
|
||||
"REPO_MAPPING:bazel_features+,bazel_tools bazel_tools",
|
||||
@@ -922,7 +922,7 @@
|
||||
"scheme": "https",
|
||||
"registry": "index.docker.io",
|
||||
"repository": "library/eclipse-temurin",
|
||||
"identifier": "sha256:0beb258e10ad28d2bae0f74349a170bdfa67da0ef87c028cbf3d95c3e1170120",
|
||||
"identifier": "sha256:db3011b348ad87ced055afe5145238d87a3d8f0e8f23eb4d0cd8cecb4659b085",
|
||||
"platform": "linux/amd64",
|
||||
"target_name": "eclipse_temurin_25_jre_linux_amd64",
|
||||
"bazel_tags": []
|
||||
@@ -936,7 +936,7 @@
|
||||
"scheme": "https",
|
||||
"registry": "index.docker.io",
|
||||
"repository": "library/eclipse-temurin",
|
||||
"identifier": "sha256:0beb258e10ad28d2bae0f74349a170bdfa67da0ef87c028cbf3d95c3e1170120",
|
||||
"identifier": "sha256:db3011b348ad87ced055afe5145238d87a3d8f0e8f23eb4d0cd8cecb4659b085",
|
||||
"platforms": {
|
||||
"@@platforms//cpu:x86_64": "@eclipse_temurin_25_jre_linux_amd64"
|
||||
},
|
||||
@@ -980,7 +980,7 @@
|
||||
"scheme": "https",
|
||||
"registry": "index.docker.io",
|
||||
"repository": "library/ubuntu",
|
||||
"identifier": "26.04",
|
||||
"identifier": "24.04",
|
||||
"platform": "linux/amd64",
|
||||
"target_name": "ubuntu_24_04_linux_amd64",
|
||||
"bazel_tags": []
|
||||
@@ -993,7 +993,7 @@
|
||||
"scheme": "https",
|
||||
"registry": "index.docker.io",
|
||||
"repository": "library/ubuntu",
|
||||
"identifier": "26.04",
|
||||
"identifier": "24.04",
|
||||
"platform": "linux/arm64/v8",
|
||||
"target_name": "ubuntu_24_04_linux_arm64_v8",
|
||||
"bazel_tags": []
|
||||
@@ -1007,7 +1007,7 @@
|
||||
"scheme": "https",
|
||||
"registry": "index.docker.io",
|
||||
"repository": "library/ubuntu",
|
||||
"identifier": "26.04",
|
||||
"identifier": "24.04",
|
||||
"platforms": {
|
||||
"@@platforms//cpu:x86_64": "@ubuntu_24_04_linux_amd64",
|
||||
"@@platforms//cpu:arm64": "@ubuntu_24_04_linux_arm64_v8"
|
||||
@@ -1023,7 +1023,7 @@
|
||||
"scheme": "https",
|
||||
"registry": "index.docker.io",
|
||||
"repository": "library/alpine",
|
||||
"identifier": "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
|
||||
"identifier": "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
|
||||
"platform": "linux/amd64",
|
||||
"target_name": "alpine_linux_linux_amd64",
|
||||
"bazel_tags": []
|
||||
@@ -1037,7 +1037,7 @@
|
||||
"scheme": "https",
|
||||
"registry": "index.docker.io",
|
||||
"repository": "library/alpine",
|
||||
"identifier": "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
|
||||
"identifier": "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
|
||||
"platforms": {
|
||||
"@@platforms//cpu:x86_64": "@alpine_linux_linux_amd64"
|
||||
},
|
||||
|
||||
@@ -5,7 +5,8 @@ set -x
|
||||
|
||||
COMMAND_LOG="$(bazel info command_log)"
|
||||
bazel build \
|
||||
//src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
//src/main/cpp/net/eagle0/shardok:shardok-server \
|
||||
//src/main/cpp/net/eagle0/shardok:shardok-inspector
|
||||
STATUS="$?"
|
||||
|
||||
if [ $STATUS -eq 0 ]
|
||||
@@ -25,6 +26,7 @@ echo "Setting up Shardok files..."
|
||||
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server ./shardok/
|
||||
/bin/mkdir -p ./shardok/shardok-server.runfiles/net_eagle0/src/main/
|
||||
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server.runfiles/net_eagle0/src/main/resources ./shardok/shardok-server.runfiles/net_eagle0/src/main/
|
||||
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-inspector ./shardok/
|
||||
|
||||
echo "tar & zip shardok files..."
|
||||
/bin/tar cfh - shardok | /usr/bin/pigz > ./deploy/shardok.tar.gz
|
||||
|
||||
@@ -40,6 +40,19 @@ if [ -f "$INFO_PLIST" ]; then
|
||||
/usr/libexec/PlistBuddy -c "Set :ITSAppUsesNonExemptEncryption false" "$INFO_PLIST" 2>/dev/null \
|
||||
|| /usr/libexec/PlistBuddy -c "Add :ITSAppUsesNonExemptEncryption bool false" "$INFO_PLIST"
|
||||
echo "Set ITSAppUsesNonExemptEncryption=false in Info.plist"
|
||||
|
||||
# Remove UIApplicationSceneManifest if present.
|
||||
# Unity 6 generates this key, which opts the app into the iOS scene-based
|
||||
# lifecycle. This breaks Application.deepLinkActivated because iOS routes
|
||||
# deep link URLs through UISceneDelegate instead of UIApplicationDelegate,
|
||||
# and Unity doesn't implement the scene delegate path.
|
||||
# See: https://issuetracker.unity3d.com/issues/in-135632
|
||||
if /usr/libexec/PlistBuddy -c "Print :UIApplicationSceneManifest" "$INFO_PLIST" 2>/dev/null; then
|
||||
echo "Found UIApplicationSceneManifest in Info.plist — removing to fix deep link handling"
|
||||
/usr/libexec/PlistBuddy -c "Delete :UIApplicationSceneManifest" "$INFO_PLIST"
|
||||
else
|
||||
echo "No UIApplicationSceneManifest in Info.plist (deep links should work)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Unity always generates "Unity-iPhone" as the main app scheme
|
||||
|
||||
@@ -8,7 +8,7 @@ BUILD_BASE="${EAGLE0_BUILD_DIR:-/tmp/eagle0}"
|
||||
COMMIT=$(/usr/bin/git rev-parse --short HEAD)
|
||||
|
||||
/bin/echo "build protos"
|
||||
./scripts/build_protos.sh
|
||||
./ci/github_actions/ensure_unity_protos.sh
|
||||
|
||||
git log -3
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BASE_SHA=${1:-}
|
||||
HEAD_SHA=${2:-HEAD}
|
||||
OUTPUT_FILE=${GITHUB_OUTPUT:-}
|
||||
|
||||
if [ -z "$BASE_SHA" ] || [[ "$BASE_SHA" =~ ^0+$ ]]; then
|
||||
echo "No usable base SHA for this run; building Unity protos as a safe default."
|
||||
changed=true
|
||||
elif ! git cat-file -e "$BASE_SHA^{commit}"; then
|
||||
echo "Base SHA $BASE_SHA is not available locally; building Unity protos as a safe default."
|
||||
changed=true
|
||||
else
|
||||
UNITY_PROTO_INPUT_PREFIXES=(
|
||||
"src/main/protobuf/"
|
||||
"src/main/csharp/net/eagle0/clients/unity/eagle0/BUILD.bazel"
|
||||
"scripts/build_protos.sh"
|
||||
"ci/github_actions/detect_unity_proto_changes.sh"
|
||||
"ci/github_actions/ensure_unity_protos.sh"
|
||||
"MODULE.bazel"
|
||||
"MODULE.bazel.lock"
|
||||
".bazelrc"
|
||||
"WORKSPACE"
|
||||
)
|
||||
|
||||
is_unity_proto_input() {
|
||||
local file="$1"
|
||||
for prefix in "${UNITY_PROTO_INPUT_PREFIXES[@]}"; do
|
||||
if [[ "$file" == "$prefix"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
changed=false
|
||||
while IFS= read -r file; do
|
||||
if is_unity_proto_input "$file"; then
|
||||
echo "Unity-proto-impacting change: $file"
|
||||
changed=true
|
||||
break
|
||||
fi
|
||||
done < <(git diff --name-only "$BASE_SHA" "$HEAD_SHA")
|
||||
fi
|
||||
|
||||
if [ "$changed" = "true" ]; then
|
||||
echo "Building Unity protos because their inputs changed."
|
||||
else
|
||||
echo "Skipping Unity proto build; no Unity proto inputs changed."
|
||||
fi
|
||||
|
||||
if [ -n "$OUTPUT_FILE" ]; then
|
||||
echo "changed=$changed" >> "$OUTPUT_FILE"
|
||||
else
|
||||
echo "changed=$changed"
|
||||
fi
|
||||
@@ -49,7 +49,7 @@ check_modules_installed() {
|
||||
fi
|
||||
;;
|
||||
mac)
|
||||
if ! has_mac_il2cpp_support "$unity_path" "$unity_app_contents_path"; then
|
||||
if ! has_il2cpp_variation "${unity_app_contents_path}/PlaybackEngines/MacStandaloneSupport/Variations"; then
|
||||
echo "✗ Mac IL2CPP module not installed for Unity ${UNITY_VERSION}"
|
||||
return 1
|
||||
fi
|
||||
@@ -67,7 +67,7 @@ check_modules_installed() {
|
||||
echo "✗ iOS module missing"
|
||||
missing=1
|
||||
fi
|
||||
if ! has_mac_il2cpp_support "$unity_path" "$unity_app_contents_path"; then
|
||||
if ! has_il2cpp_variation "${unity_app_contents_path}/PlaybackEngines/MacStandaloneSupport/Variations"; then
|
||||
echo "✗ Mac IL2CPP module missing"
|
||||
missing=1
|
||||
fi
|
||||
@@ -84,14 +84,6 @@ check_modules_installed() {
|
||||
return 0
|
||||
}
|
||||
|
||||
has_mac_il2cpp_support() {
|
||||
local unity_path="$1"
|
||||
local unity_app_contents_path="$2"
|
||||
|
||||
has_il2cpp_variation "${unity_app_contents_path}/PlaybackEngines/MacStandaloneSupport/Variations" ||
|
||||
has_il2cpp_variation "${unity_path}/PlaybackEngines/MacStandaloneSupport/Variations"
|
||||
}
|
||||
|
||||
has_il2cpp_variation() {
|
||||
local variations_path="$1"
|
||||
local variation
|
||||
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
UNITY_ROOT="src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
PLUGIN_DIR="$UNITY_ROOT/Assets/Plugins/Eagle0Protos"
|
||||
GENERATED_SOURCES_DIR="$UNITY_ROOT/Assets/GeneratedProtos"
|
||||
PROTO_DLL="$PLUGIN_DIR/Eagle0Protos.dll"
|
||||
PROTO_XML="$PLUGIN_DIR/Eagle0Protos.xml"
|
||||
UNITY_PROTOS_CHANGED=${UNITY_PROTOS_CHANGED:-true}
|
||||
|
||||
if [ "$UNITY_PROTOS_CHANGED" = "false" ] && [ -s "$PROTO_DLL" ] && [ -s "$PROTO_XML" ]; then
|
||||
echo "Skipping Unity proto build; existing Eagle0Protos DLL/XML are present and proto inputs are unchanged."
|
||||
rm -rf "$GENERATED_SOURCES_DIR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$UNITY_PROTOS_CHANGED" = "false" ]; then
|
||||
echo "Unity proto inputs are unchanged, but generated DLL/XML are missing; rebuilding."
|
||||
fi
|
||||
|
||||
./scripts/build_protos.sh
|
||||
@@ -16,7 +16,7 @@ RESULTS_PATH="${BUILD_BASE}/editmode-test-results.xml"
|
||||
mkdir -p "$BUILD_BASE"
|
||||
|
||||
echo "Building protos"
|
||||
./scripts/build_protos.sh
|
||||
./ci/github_actions/ensure_unity_protos.sh
|
||||
|
||||
echo "Running Unity EditMode tests"
|
||||
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -103,18 +103,6 @@ and protect fragile specialists.
|
||||
a prisoner, add the Warden custody bonus to that prisoner's `factionBiases(rulingFactionId)`.
|
||||
- For Shardok, start with a single-turn guard status before attempting more complex interception chains
|
||||
|
||||
**Current Follow-Up Priorities:**
|
||||
- Teach the Shardok AI when to use the Warden evacuation command. The command is valuable when the Warden's side has
|
||||
captured enemy heroes and can secure them by leaving the battle, especially when the battle outcome is uncertain or
|
||||
trending against that side.
|
||||
- Add the strategic passive custody bonus: each round, prisoners in a province guarded by a ruling-faction Warden should
|
||||
get a small accumulated positive bias toward joining that faction. Prefer storing this as an addition to the existing
|
||||
`factionBiases` entry rather than changing the global `roundsInType` multiplier, so the bonus only reflects rounds
|
||||
actually spent under Warden custody.
|
||||
- Make tactical AI more protective of heroes at risk of capture when a Warden may be present. Warden evacuation makes
|
||||
captured heroes harder to recover, so the AI should treat exposing high-value heroes to capture as more dangerous than
|
||||
before. This matters even when the AI does not have perfect knowledge that a Warden is in the battle.
|
||||
|
||||
### 2. HERALD (Morale & Communication Specialist)
|
||||
|
||||
**Fantasy:** The inspiring leader who rallies troops and carries messages across the battlefield.
|
||||
|
||||
Binary file not shown.
@@ -1,17 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
bazel_cmd=(bazel)
|
||||
if [ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]; then
|
||||
bazel_cmd+=("--output_user_root=${BAZEL_OUTPUT_USER_ROOT}")
|
||||
fi
|
||||
if [ -n "${BAZEL_OUTPUT_BASE:-}" ]; then
|
||||
bazel_cmd+=("--output_base=${BAZEL_OUTPUT_BASE}")
|
||||
fi
|
||||
|
||||
"${bazel_cmd[@]}" build \
|
||||
--platforms=//:linux_arm64 \
|
||||
--extra_toolchains=@llvm_toolchain_linux_arm64//:cc-toolchain-aarch64-linux \
|
||||
"$@" \
|
||||
//src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
@@ -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" "$@"
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/bin/zsh
|
||||
|
||||
/opt/homebrew/bin/s3cmd sync \
|
||||
--acl-public \
|
||||
--exclude="*dream_log.txt" \
|
||||
--exclude="*.DS_Store" \
|
||||
--delete-removed \
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
@@ -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
-3
@@ -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;
|
||||
|
||||
+3
-2
@@ -2,6 +2,7 @@
|
||||
// Converts saved Eagle ShardokBattle/GameState protobuf blobs into AI battle configs.
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
@@ -145,9 +146,9 @@ int main(int argc, char *argv[]) {
|
||||
} else if (arg.starts_with("--output=")) {
|
||||
outputPath = arg.substr(9);
|
||||
} else if (arg.starts_with("--seed=")) {
|
||||
seed = std::stoi(arg.substr(7));
|
||||
seed = std::atoi(arg.substr(7).c_str());
|
||||
} else if (arg.starts_with("--max-rounds=")) {
|
||||
maxRounds = std::stoi(arg.substr(13));
|
||||
maxRounds = std::atoi(arg.substr(13).c_str());
|
||||
} else if (arg.starts_with("--attacker-scoring=")) {
|
||||
const auto scoring = ParseScoring(arg.substr(19));
|
||||
if (!scoring.has_value()) { throw std::runtime_error("Unknown attacker scoring"); }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user