mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-29 13:15:42 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f200b4cc4f | ||
|
|
6141a27eed | ||
|
|
bf7e576bf3 | ||
|
|
dd1882967a |
@@ -84,16 +84,6 @@ find . -name "*.cpp" -o -name "*.hpp" | xargs clang-format -i
|
||||
find . -name "*.cs" | xargs clang-format -i
|
||||
```
|
||||
|
||||
### Static Analysis
|
||||
```bash
|
||||
# Run clang-tidy static analysis on C++ files
|
||||
# Note: This may show some header include errors but will still analyze the main file
|
||||
bazel run @llvm_toolchain//:clang-tidy -- --checks='readability-*,bugprone-*,clang-analyzer-*' <file_path> -- -I/Users/dancrosby/CodingProjects/github/eagle0 -std=c++20
|
||||
|
||||
# Example for AI files:
|
||||
bazel run @llvm_toolchain//:clang-tidy -- --checks='readability-*,bugprone-*,clang-analyzer-*' /Users/dancrosby/CodingProjects/github/eagle0/src/main/cpp/net/eagle0/shardok/ai/AIUnitScoreCalculator.cpp -- -I/Users/dancrosby/CodingProjects/github/eagle0 -std=c++20
|
||||
```
|
||||
|
||||
## Language-Specific Patterns
|
||||
|
||||
**Scala (Strategic Layer):**
|
||||
|
||||
+7
-7
@@ -4,14 +4,14 @@ bazel_dep(name = "apple_support", repo_name = "build_bazel_apple_support", versi
|
||||
# bazel-toolchain
|
||||
#
|
||||
|
||||
bazel_dep(name = "toolchains_llvm", version = "1.4.0")
|
||||
bazel_dep(name = "toolchains_llvm", version = "1.2.0")
|
||||
|
||||
# Configure and register the toolchain.
|
||||
llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm")
|
||||
|
||||
llvm.toolchain(
|
||||
name = "llvm_toolchain",
|
||||
llvm_version = "20.1.2",
|
||||
llvm_version = "19.1.0",
|
||||
)
|
||||
|
||||
use_repo(llvm, "llvm_toolchain")
|
||||
@@ -22,14 +22,14 @@ register_toolchains(
|
||||
dev_dependency = True,
|
||||
)
|
||||
|
||||
bazel_dep(name = "rules_pkg", version = "1.1.0")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.8.1")
|
||||
bazel_dep(name = "rules_pkg", version = "1.0.1")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.7.1")
|
||||
bazel_dep(name = "protobuf", repo_name = "com_google_protobuf", version = "29.2")
|
||||
bazel_dep(name = "grpc", version = "1.71.0")
|
||||
bazel_dep(name = "grpc-java", version = "1.71.0")
|
||||
bazel_dep(name = "googletest", version = "1.17.0")
|
||||
bazel_dep(name = "rules_go", repo_name = "io_bazel_rules_go", version = "0.56.1")
|
||||
bazel_dep(name = "gazelle", repo_name = "bazel_gazelle", version = "0.45.0")
|
||||
bazel_dep(name = "googletest", version = "1.15.2")
|
||||
bazel_dep(name = "rules_go", repo_name = "io_bazel_rules_go", version = "0.50.1")
|
||||
bazel_dep(name = "gazelle", repo_name = "bazel_gazelle", version = "0.40.0")
|
||||
|
||||
go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
|
||||
|
||||
|
||||
Generated
+35
-61
@@ -13,8 +13,7 @@
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20240722.0/MODULE.bazel": "88668a07647adbdc14cb3a7cd116fb23c9dda37a90a1681590b6c9d8339a5b84",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20250127.1/source.json": "03c90ee57977264436d3231676dcddae116c4769a5d02b6fc16c2c9e019b583a",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20240722.0/source.json": "59af9f8a8a4817092624e21263fe1fb7d7951a3b06f0570c610c7e5a9caf5f29",
|
||||
"https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896",
|
||||
"https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85",
|
||||
"https://bcr.bazel.build/modules/apple_support/1.17.1/MODULE.bazel": "655c922ab1209978a94ef6ca7d9d43e940cd97d9c172fb55f94d91ac53f8610b",
|
||||
@@ -41,7 +40,6 @@
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.27.0/source.json": "ed8cf0ef05c858dce3661689d0a2b110ff398e63994e178e4f1f7555a8067fed",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9",
|
||||
@@ -59,8 +57,7 @@
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.8.1/source.json": "7ebaefba0b03efe59cac88ed5bbc67bcf59a3eff33af937345ede2a38b2d368a",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953",
|
||||
"https://bcr.bazel.build/modules/boringssl/0.0.0-20211025-d4f1ab9/MODULE.bazel": "6ee6353f8b1a701fe2178e1d925034294971350b6d3ac37e67e5a7d463267834",
|
||||
"https://bcr.bazel.build/modules/boringssl/0.0.0-20230215-5c22014/MODULE.bazel": "4b03dc0d04375fa0271174badcd202ed249870c8e895b26664fd7298abea7282",
|
||||
"https://bcr.bazel.build/modules/boringssl/0.0.0-20240530-2db0eb3/MODULE.bazel": "d0405b762c5e87cd445b7015f2b8da5400ef9a8dbca0bfefa6c1cea79d528a97",
|
||||
@@ -94,8 +91,8 @@
|
||||
"https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.37.0/MODULE.bazel": "d1327ba0907d0275ed5103bfbbb13518f6c04955b402213319d0d6c0ce9839d4",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.45.0/MODULE.bazel": "ecd19ebe9f8e024e1ccffb6d997cc893a974bcc581f1ae08f386bdd448b10687",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.45.0/source.json": "111d182facc5f5e80f0b823d5f077b74128f40c3fd2eccc89a06f34191bd3392",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.40.0/MODULE.bazel": "42ba5378ebe845fca43989a53186ab436d956db498acde790685fe0e8f9c6146",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.40.0/source.json": "1e5ef6e4d8b9b6836d93273c781e78ff829ea2e077afef7a57298040fa4f010a",
|
||||
"https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb",
|
||||
"https://bcr.bazel.build/modules/google_benchmark/1.8.4/MODULE.bazel": "c6d54a11dcf64ee63545f42561eda3fd94c1b5f5ebe1357011de63ae33739d5e",
|
||||
"https://bcr.bazel.build/modules/google_benchmark/1.8.5/MODULE.bazel": "9ba9b31b984022828a950e3300410977eda2e35df35584c6b0b2d0c2e52766b7",
|
||||
@@ -107,8 +104,7 @@
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6",
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
|
||||
"https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108",
|
||||
"https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46",
|
||||
"https://bcr.bazel.build/modules/googletest/1.17.0/source.json": "38e4454b25fc30f15439c0378e57909ab1fd0a443158aa35aec685da727cd713",
|
||||
"https://bcr.bazel.build/modules/googletest/1.15.2/source.json": "dbdda654dcb3a0d7a8bc5d0ac5fc7e150b58c2a986025ae5bc634bb2cb61f470",
|
||||
"https://bcr.bazel.build/modules/grpc-java/1.62.2/MODULE.bazel": "99b8771e8c7cacb130170fed2a10c9e8fed26334a93e73b42d2953250885a158",
|
||||
"https://bcr.bazel.build/modules/grpc-java/1.66.0/MODULE.bazel": "86ff26209fac846adb89db11f3714b3dc0090fb2fb81575673cc74880cda4e7e",
|
||||
"https://bcr.bazel.build/modules/grpc-java/1.69.0/MODULE.bazel": "53887af6a00b3b406d70175d3d07e84ea9362016ff55ea90b9185f0227bfaf98",
|
||||
@@ -150,18 +146,14 @@
|
||||
"https://bcr.bazel.build/modules/opentelemetry-proto/1.5.0/source.json": "046b721ce203e88cdaad44d7dd17a86b7200eab9388b663b234e72e13ff7b143",
|
||||
"https://bcr.bazel.build/modules/opentracing-cpp/1.6.0/MODULE.bazel": "b3925269f63561b8b880ae7cf62ccf81f6ece55b62cd791eda9925147ae116ec",
|
||||
"https://bcr.bazel.build/modules/opentracing-cpp/1.6.0/source.json": "da1cb1add160f5e5074b7272e9db6fd8f1b3336c15032cd0a653af9d2f484aed",
|
||||
"https://bcr.bazel.build/modules/package_metadata/0.0.5/MODULE.bazel": "ef4f9439e3270fdd6b9fd4dbc3d2f29d13888e44c529a1b243f7a31dfbc2e8e4",
|
||||
"https://bcr.bazel.build/modules/package_metadata/0.0.5/source.json": "2326db2f6592578177751c3e1f74786b79382cd6008834c9d01ec865b9126a85",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.10/source.json": "f22828ff4cf021a6b577f1bf6341cb9dcd7965092a439f64fc1bb3b7a5ae4bd5",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc",
|
||||
"https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580",
|
||||
"https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96",
|
||||
"https://bcr.bazel.build/modules/prometheus-cpp/1.2.4/MODULE.bazel": "0fbe5dcff66311947a3f6b86ebc6a6d9328e31a28413ca864debc4a043f371e5",
|
||||
"https://bcr.bazel.build/modules/prometheus-cpp/1.3.0/MODULE.bazel": "ce82e086bbc0b60267e970f6a54b2ca6d0f22d3eb6633e00e2cc2899c700f3d8",
|
||||
"https://bcr.bazel.build/modules/prometheus-cpp/1.3.0/source.json": "8cb66b4e535afc718e9d104a3db96ccb71a42ee816a100e50fd0d5ac843c0606",
|
||||
@@ -174,9 +166,7 @@
|
||||
"https://bcr.bazel.build/modules/protobuf/27.0-rc2/MODULE.bazel": "b2b0dbafd57b6bec0ca9b251da02e628c357dab53a097570aa7d79d020f107cf",
|
||||
"https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c",
|
||||
"https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0-rc2.bcr.1/MODULE.bazel": "52f4126f63a2f0bbf36b99c2a87648f08467a4eaf92ba726bc7d6a500bbf770c",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.2/MODULE.bazel": "5435497c190d86f79b0568698c45044df7c8d97692886cda9fe9cf9053aea712",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.2/source.json": "fe7090cc34072609b26d9beafb122916dabc1d47ba61b242c26c4b06c51384ab",
|
||||
@@ -195,9 +185,8 @@
|
||||
"https://bcr.bazel.build/modules/re2/2021-09-01/MODULE.bazel": "bcb6b96f3b071e6fe2d8bed9cc8ada137a105f9d2c5912e91d27528b3d123833",
|
||||
"https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206",
|
||||
"https://bcr.bazel.build/modules/re2/2024-05-01/MODULE.bazel": "55a3f059538f381107824e7d00df5df6d061ba1fb80e874e4909c0f0549e8f3e",
|
||||
"https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a",
|
||||
"https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/source.json": "2ff292be6ef3340325ce8a045ecc326e92cbfab47c7cbab4bd85d28971b97ac4",
|
||||
"https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa",
|
||||
"https://bcr.bazel.build/modules/re2/2024-07-02/source.json": "547d0111a9d4f362db32196fef805abbf3676e8d6afbe44d395d87816c1130ca",
|
||||
"https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8",
|
||||
"https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e",
|
||||
"https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a",
|
||||
@@ -231,9 +220,7 @@
|
||||
"https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.48.0/MODULE.bazel": "d00ebcae0908ee3f5e6d53f68677a303d6d59a77beef879598700049c3980a03",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.50.1/MODULE.bazel": "b91a308dc5782bb0a8021ad4330c81fea5bda77f96b9e4c117b9b9c8f6665ee0",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.53.0/MODULE.bazel": "a4ed760d3ac0dbc0d7b967631a9a3fd9100d28f7d9fcf214b4df87d4bfff5f9a",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.56.1/MODULE.bazel": "d5b835c548ac917345f1780cd2da52edc1130a908fe091c92096895303ae78a0",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.56.1/source.json": "0c902f7272e8d4e47e459af97be472bc19dadbbe6023a0719d1adce8483ac75a",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.50.1/source.json": "205765fd30216c70321f84c9a967267684bdc74350af3f3c46c857d9f80a4fa2",
|
||||
"https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74",
|
||||
"https://bcr.bazel.build/modules/rules_java/5.1.0/MODULE.bazel": "324b6478b0343a3ce7a9add8586ad75d24076d6d43d2f622990b9c1cfd8a1b15",
|
||||
"https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86",
|
||||
@@ -245,14 +232,12 @@
|
||||
"https://bcr.bazel.build/modules/rules_java/7.1.0/MODULE.bazel": "30d9135a2b6561c761bd67bd4990da591e6bdc128790ce3e7afd6a3558b2fb64",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.12.2/source.json": "b0890f9cda8ff1b8e691a3ac6037b5c14b7fd4134765a3946b89f31ea02e5884",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.4.0/MODULE.bazel": "a592852f8a3dd539e82ee6542013bf2cadfc4c6946be8941e189d224500a8934",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.5.1/source.json": "db1a77d81b059e0f84985db67a22f3f579a529a86b7997605be3d214a0abe38e",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036",
|
||||
@@ -274,8 +259,7 @@
|
||||
"https://bcr.bazel.build/modules/rules_nodejs/6.3.3/source.json": "45bd343155bdfed2543f0e39b80ff3f6840efc31975da4b5795797f4c94147ad",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.1.0/MODULE.bazel": "9db8031e71b6ef32d1846106e10dd0ee2deac042bd9a2de22b4761b0c3036453",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.1.0/source.json": "fef768df13a92ce6067e1cd0cdc47560dace01354f1d921cfb1d632511f7d608",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a",
|
||||
"https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06",
|
||||
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7",
|
||||
"https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483",
|
||||
@@ -297,8 +281,7 @@
|
||||
"https://bcr.bazel.build/modules/rules_python/0.37.1/MODULE.bazel": "3faeb2d9fa0a81f8980643ee33f212308f4d93eea4b9ce6f36d0b742e71e9500",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7",
|
||||
"https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43",
|
||||
"https://bcr.bazel.build/modules/rules_python/1.0.0/source.json": "b0162a65c6312e45e7912e39abd1a7f8856c2c7e41ecc9b6dc688a6f6400a917",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.40.0/source.json": "939d4bd2e3110f27bfb360292986bb79fd8dcefb874358ccd6cdaa7bda029320",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3",
|
||||
@@ -314,12 +297,11 @@
|
||||
"https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.1/source.json": "b6500ffcd7b48cd72c29bb67bcac781e12701cc0d6d55d266a652583cfcdab01",
|
||||
"https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91",
|
||||
"https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb",
|
||||
"https://bcr.bazel.build/modules/toolchains_llvm/1.4.0/MODULE.bazel": "05239402b7374293359c2f22806f420b75aa5d6f4b15a2eaa809a2c214d58b31",
|
||||
"https://bcr.bazel.build/modules/toolchains_llvm/1.4.0/source.json": "229a516d282b17a82be54c6e3ae220a1b750fb55a8495567e5c7a9d09423f3e2",
|
||||
"https://bcr.bazel.build/modules/toolchains_llvm/1.2.0/MODULE.bazel": "7b271b71e50de47fa47159a7f58165e80fcebe1196c014a14af0a08a867d1635",
|
||||
"https://bcr.bazel.build/modules/toolchains_llvm/1.2.0/source.json": "0328cfc67075d6a016980be4011bbc1dcfba933e357568002542dff22abdd3a1",
|
||||
"https://bcr.bazel.build/modules/upb/0.0.0-20211020-160625a/MODULE.bazel": "6cced416be2dc5b9c05efd5b997049ba795e5e4e6fafbe1624f4587767638928",
|
||||
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
|
||||
"https://bcr.bazel.build/modules/upb/0.0.0-20230516-61a97ef/MODULE.bazel": "c0df5e35ad55e264160417fd0875932ee3c9dda63d9fccace35ac62f45e1b6f9",
|
||||
@@ -681,6 +663,23 @@
|
||||
"recordedRepoMappingEntries": []
|
||||
}
|
||||
},
|
||||
"@@platforms//host:extension.bzl%host_platform": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=",
|
||||
"usagesDigest": "ibwLwKXW2CiKpWzexj5KO6SHidEgCwJvu+ebsPxirn4=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"host_platform": {
|
||||
"bzlFile": "@@platforms//host:extension.bzl",
|
||||
"ruleClassName": "host_platform_repo",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": []
|
||||
}
|
||||
},
|
||||
"@@rules_foreign_cc~//foreign_cc:extensions.bzl%tools": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "a7qnESofmIRYId6wwGNPJ9kvExU80KrkxL281P3+lBE=",
|
||||
@@ -1021,29 +1020,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_java~//java:rules_java_deps.bzl%compatibility_proxy": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "KIX40nDfygEWbU+rq3nYpt3tVgTK/iO8PKh5VMBlN7M=",
|
||||
"usagesDigest": "pwHZ+26iLgQdwvdZeA5wnAjKnNI3y6XO2VbhOTeo5h8=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"compatibility_proxy": {
|
||||
"bzlFile": "@@rules_java~//java:rules_java_deps.bzl",
|
||||
"ruleClassName": "_compatibility_proxy_repo_rule",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_java~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_kotlin~//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "fus14IFJ/1LGWWGKPH/U18VnJCoMjfDt1ckahqCnM0A=",
|
||||
@@ -1413,8 +1389,8 @@
|
||||
},
|
||||
"@@toolchains_llvm~//toolchain/extensions:llvm.bzl%llvm": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "afRF0aFOIUrkYl3o040WQ606ep1qciEXzjnAxT3Kek8=",
|
||||
"usagesDigest": "wl2njI0HdPm89XuKGMWIa1gyCAQ5gvOs+ksjFd4QbZs=",
|
||||
"bzlTransitiveDigest": "dZkcElkfice3BvKPXtFwrvPABm8ac+SwfynSinwK4dQ=",
|
||||
"usagesDigest": "Phpqeo3bkW3l2tD5aOSKApubwu0KPfl0KuPgjY6+ueM=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
@@ -1428,9 +1404,8 @@
|
||||
"distribution": "auto",
|
||||
"exec_arch": "",
|
||||
"exec_os": "",
|
||||
"libclang_rt": {},
|
||||
"llvm_mirror": "",
|
||||
"llvm_version": "20.1.2",
|
||||
"llvm_version": "19.1.0",
|
||||
"llvm_versions": {},
|
||||
"netrc": "",
|
||||
"sha256": {},
|
||||
@@ -1445,7 +1420,6 @@
|
||||
"absolute_paths": false,
|
||||
"archive_flags": {},
|
||||
"compile_flags": {},
|
||||
"conly_flags": {},
|
||||
"coverage_compile_flags": {},
|
||||
"coverage_link_flags": {},
|
||||
"cxx_builtin_include_directories": {},
|
||||
@@ -1459,7 +1433,7 @@
|
||||
"link_flags": {},
|
||||
"link_libs": {},
|
||||
"llvm_versions": {
|
||||
"": "20.1.2"
|
||||
"": "19.1.0"
|
||||
},
|
||||
"opt_compile_flags": {},
|
||||
"opt_link_flags": {},
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
# Race Condition Analysis for Eagle0
|
||||
|
||||
## Summary
|
||||
This document provides an analysis of potential race conditions in the Eagle0 codebase after merging the `fix-race` branch and re-adding ThreadPool functionality from PRs 4340, 4342, and 4343.
|
||||
|
||||
## Fixed Issues
|
||||
|
||||
### ✅ ShardokEngine Shared State Issue (FIXED by fix-race branch)
|
||||
The `fix-race` branch successfully addressed a major race condition by changing `ShardokEngine` from being passed as `shared_ptr<ShardokEngine>` to being passed by value (copy).
|
||||
|
||||
**Key changes:**
|
||||
- `BasicLookaheadCalculator` now takes `const ShardokEngine innerEngine` (by value)
|
||||
- `CalcOne` creates a local copy: `auto innerEngine = ShardokEngine(guessedEngine, false)`
|
||||
- All engine method calls changed from `innerEngine->` to `innerEngine.`
|
||||
|
||||
This ensures each thread works with its own independent copy of the engine state, eliminating concurrent access to shared mutable state.
|
||||
|
||||
## Remaining Potential Race Conditions
|
||||
|
||||
### 1. Transposition Table Global Access (HIGH RISK)
|
||||
**Location:** `TranspositionTable.cpp`, global instance `g_transpositionTable`
|
||||
|
||||
**Issues:**
|
||||
- Global shared state accessed by multiple threads simultaneously
|
||||
- Hash collisions possible under high concurrency
|
||||
- Depth-based storage logic when threads work at different depths
|
||||
- Potential ABA problems in compare-and-swap operations
|
||||
|
||||
**Impact:** Most likely culprit for remaining crashes given it was added recently
|
||||
|
||||
### 2. Global Random Generator (MEDIUM RISK)
|
||||
**Location:** `AIScoreCalculator.cpp`
|
||||
```cpp
|
||||
static const auto _averageGenerator = std::make_shared<SequenceRandomGenerator>(_averageSequence);
|
||||
```
|
||||
|
||||
**Issues:**
|
||||
- Shared across all threads
|
||||
- If `SequenceRandomGenerator` isn't thread-safe internally, concurrent access could corrupt state
|
||||
- No synchronization around access to this shared generator
|
||||
|
||||
### 3. ActionPointDistances Cache (MEDIUM RISK)
|
||||
**Location:** `ActionPointDistancesCache.cpp`
|
||||
|
||||
**Issues:**
|
||||
- Thread-local caching mechanism may have synchronization issues
|
||||
- Cache invalidation across threads could be problematic
|
||||
- Global cache updates might race with thread-local cache access
|
||||
|
||||
### 4. Performance Logging Atomics (LOW RISK)
|
||||
**Location:** `AIScoreCalculator.cpp`, `AttackerScorePerformanceLogger`
|
||||
```cpp
|
||||
intervalTime.fetch_add(duration); // duration is a double
|
||||
```
|
||||
|
||||
**Issues:**
|
||||
- Atomic operations on doubles aren't guaranteed lock-free on all platforms
|
||||
- Could cause performance degradation or incorrect metrics
|
||||
|
||||
### 5. ThreadPool Task Ordering (MEDIUM RISK)
|
||||
**Location:** `ThreadPool.hpp`
|
||||
|
||||
**Issues:**
|
||||
- FIFO queue (deque) could have ordering dependencies
|
||||
- Deadline handling might create races if tasks timeout while processing
|
||||
- Session-based metrics collection adds new shared state
|
||||
- Task cancellation and cleanup could race with execution
|
||||
|
||||
### 6. Future Aggregation (LOW RISK)
|
||||
**Location:** `AIScoreCalculator.cpp`, `BestCommandIndex` function
|
||||
|
||||
**Issues:**
|
||||
- Futures collected and results aggregated
|
||||
- Unexpected completion order could cause issues
|
||||
- Error states might not be handled consistently
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Immediate Actions
|
||||
|
||||
1. **Make Random Generators Thread-Local**
|
||||
```cpp
|
||||
thread_local auto t_randomGenerator =
|
||||
std::make_shared<SequenceRandomGenerator>(_averageSequence);
|
||||
```
|
||||
|
||||
2. **Add Defensive Checks**
|
||||
- Validate game state after engine operations
|
||||
- Check for NaN/infinity before storing scores
|
||||
- Assert transposition table entry consistency
|
||||
|
||||
3. **Improve Transposition Table Locking**
|
||||
- Consider sharding with separate locks per shard
|
||||
- Investigate lock-free data structures
|
||||
- Add more granular locking around critical sections
|
||||
|
||||
### Investigation Steps
|
||||
|
||||
1. **Run Thread Sanitizer**
|
||||
```bash
|
||||
bazel test --config=tsan //src/test/cpp/net/eagle0/shardok/ai:all
|
||||
```
|
||||
|
||||
2. **Add Detailed Logging**
|
||||
- Log all transposition table store/probe operations with thread IDs
|
||||
- Track random generator access patterns
|
||||
- Monitor ThreadPool task lifecycle
|
||||
|
||||
3. **Verify Thread Safety**
|
||||
- Audit `SequenceRandomGenerator` for thread safety
|
||||
- Review `ShardokEngine` copy constructor for deep copy completeness
|
||||
- Check all global/static variables for proper synchronization
|
||||
|
||||
### Long-term Improvements
|
||||
|
||||
1. **Redesign Transposition Table**
|
||||
- Implement thread-local transposition tables with periodic merging
|
||||
- Use concurrent hash map implementation (e.g., Intel TBB concurrent_hash_map)
|
||||
- Add versioning to prevent ABA problems
|
||||
|
||||
2. **Eliminate Global State**
|
||||
- Pass random generators explicitly rather than using globals
|
||||
- Consider dependency injection for caches and tables
|
||||
- Make performance loggers thread-local
|
||||
|
||||
3. **Improve ThreadPool Robustness**
|
||||
- Add task dependency tracking
|
||||
- Implement proper cancellation tokens
|
||||
- Add timeout recovery mechanisms
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
1. **Stress Testing**
|
||||
- Run AI calculations with many threads simultaneously
|
||||
- Use different random seeds to expose timing-dependent bugs
|
||||
- Test with various game states and board configurations
|
||||
|
||||
2. **Reproducibility**
|
||||
- Add deterministic mode with fixed seeds
|
||||
- Log thread scheduling information
|
||||
- Create minimal test cases that reproduce crashes
|
||||
|
||||
3. **Monitoring**
|
||||
- Add metrics for lock contention
|
||||
- Track task completion times and timeout rates
|
||||
- Monitor memory usage patterns
|
||||
|
||||
## Conclusion
|
||||
|
||||
While the `fix-race` branch addressed the critical ShardokEngine shared state issue, several race condition risks remain. The transposition table and global random generator are the most likely culprits for any remaining crashes. Implementing the recommended immediate actions should significantly improve stability, while the long-term improvements will make the system more robust and maintainable.
|
||||
@@ -22,6 +22,13 @@ cc_library(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "container_utils",
|
||||
hdrs = ["ContainerUtils.hpp"],
|
||||
copts = COPTS,
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "filesystem_utils",
|
||||
srcs = ["FilesystemUtils.cpp"],
|
||||
|
||||
@@ -7,23 +7,12 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// FNV-1a 64-bit constants
|
||||
constexpr uint64_t FNV_PRIME = 0x00000100000001B3ULL;
|
||||
constexpr uint64_t FNV_OFFSET_BASIS = 0xcbf29ce484222325ULL;
|
||||
constexpr uint64_t FNV_PRIME = 0x100000001b3;
|
||||
constexpr uint64_t FNV_OFFSET_BASIS = 0xcbf29ce484222325;
|
||||
|
||||
// FNV-1a algorithm: XOR first, then multiply
|
||||
static inline auto MixIn(uint64_t& hash, const uint8_t byte) {
|
||||
hash ^= byte;
|
||||
hash *= FNV_PRIME;
|
||||
}
|
||||
|
||||
// Hash an entire buffer using FNV-1a
|
||||
static inline auto HashBuffer(const uint8_t* data, size_t size) -> uint64_t {
|
||||
uint64_t hash = FNV_OFFSET_BASIS;
|
||||
if (data != nullptr) {
|
||||
for (size_t i = 0; i < size; ++i) { MixIn(hash, data[i]); }
|
||||
}
|
||||
return hash;
|
||||
hash = hash * FNV_PRIME;
|
||||
hash = hash ^ byte;
|
||||
}
|
||||
|
||||
#endif // EAGLE0_BYTEHASHER_HPP
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
//
|
||||
// Created by Dan Crosby on 12/25/20.
|
||||
//
|
||||
|
||||
#ifndef EAGLE0_CONTAINERUTILS_HPP
|
||||
#define EAGLE0_CONTAINERUTILS_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
namespace common {
|
||||
|
||||
using std::allocator;
|
||||
using std::back_inserter;
|
||||
using std::begin;
|
||||
using std::copy_if;
|
||||
using std::count_if;
|
||||
using std::end;
|
||||
using std::find;
|
||||
using std::find_if;
|
||||
using std::function;
|
||||
using std::optional;
|
||||
using std::remove_if;
|
||||
using std::vector;
|
||||
|
||||
template<class T, class Container>
|
||||
auto Contains(const Container& container, const T& elt) -> bool {
|
||||
return find(begin(container), end(container), elt) != end(container);
|
||||
}
|
||||
|
||||
template<class Container, class Func>
|
||||
auto CountIf(const Container& container, Func fn) -> size_t {
|
||||
Container result{};
|
||||
return count_if(begin(container), end(container), fn);
|
||||
}
|
||||
|
||||
template<class Container, class Func>
|
||||
void FilterInPlace(Container& container, Func fn) {
|
||||
container.erase(
|
||||
remove_if(begin(container), end(container), [fn](const auto& elt) { return !fn(elt); }),
|
||||
end(container));
|
||||
}
|
||||
|
||||
template<class Container, class Func>
|
||||
auto Filtered(const Container& container, Func fn) -> Container {
|
||||
Container result{};
|
||||
copy_if(begin(container), end(container), back_inserter(result), fn);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<class Container, class Func>
|
||||
auto FilteredToVector(const Container& container, Func fn) -> decltype(auto) {
|
||||
typedef typename Container::value_type value_type;
|
||||
vector<value_type> result{};
|
||||
copy_if(begin(container), end(container), back_inserter(result), fn);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename Container, typename Func>
|
||||
auto FindIf(const Container& container, Func fn) -> optional<typename Container::value_type> {
|
||||
const auto& t = find_if(begin(container), end(container), fn);
|
||||
|
||||
if (t == end(container)) {
|
||||
return {};
|
||||
} else {
|
||||
return optional<typename Container::value_type>(*t);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Container, typename Func>
|
||||
auto ContainsWhere(const Container& container, Func fn) -> bool {
|
||||
return find_if(begin(container), end(container), fn) != end(container);
|
||||
}
|
||||
|
||||
template<
|
||||
template<typename, typename>
|
||||
class TwoTypeContainer,
|
||||
typename T,
|
||||
typename Allocator = allocator<T>,
|
||||
typename Func>
|
||||
auto Map(const TwoTypeContainer<T, Allocator>& input, Func fn) -> decltype(auto) {
|
||||
typedef typename decltype(function(fn))::result_type result_type;
|
||||
|
||||
TwoTypeContainer<result_type, allocator<result_type>> result{};
|
||||
result.reserve(input.size());
|
||||
|
||||
transform(begin(input), end(input), back_inserter(result), fn);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<template<typename> class OneTypeContainer, typename T, typename Func>
|
||||
auto Map(const OneTypeContainer<T>& input, Func fn) -> decltype(auto) {
|
||||
typedef typename decltype(function(fn))::result_type result_type;
|
||||
|
||||
OneTypeContainer<result_type> result{};
|
||||
result.reserve(input.size());
|
||||
|
||||
transform(begin(input), end(input), back_inserter(result), fn);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename Container, typename Func>
|
||||
auto MapToVector(const Container& input, Func fn) -> decltype(auto) {
|
||||
typedef typename decltype(function(fn))::result_type result_type;
|
||||
|
||||
vector<result_type> result{};
|
||||
|
||||
transform(begin(input), end(input), back_inserter(result), fn);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<
|
||||
template<typename, typename>
|
||||
class TwoTypeContainer,
|
||||
typename T,
|
||||
typename Allocator = allocator<T>,
|
||||
typename Func>
|
||||
auto FlatMap(const TwoTypeContainer<T, Allocator>& input, Func fn) -> decltype(auto) {
|
||||
typedef typename decltype(function(fn))::result_type::value_type result_value_type;
|
||||
|
||||
TwoTypeContainer<result_value_type, allocator<result_value_type>> result{};
|
||||
|
||||
for (const auto& elt : input) {
|
||||
const auto& outContainer = fn(elt);
|
||||
for (const auto& outElt : outContainer) { result.push_back(outElt); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<template<typename> class OneTypeContainer, typename T, typename Func>
|
||||
auto FlatMap(const OneTypeContainer<T>& input, Func fn) -> decltype(auto) {
|
||||
typedef typename decltype(function(fn))::result_type::value_type result_value_type;
|
||||
|
||||
OneTypeContainer<result_value_type> result{};
|
||||
|
||||
for (const auto& elt : input) {
|
||||
const auto& outContainer = fn(elt);
|
||||
for (const auto& outElt : outContainer) { result.push_back(outElt); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename Container, typename Func>
|
||||
auto FlatMapToVector(const Container& input, Func fn) -> decltype(auto) {
|
||||
typedef typename decltype(function(fn))::result_type::value_type value_type;
|
||||
|
||||
vector<value_type> result{};
|
||||
|
||||
for (const auto& elt : input) {
|
||||
const auto& outContainer = fn(elt);
|
||||
for (const auto& outElt : outContainer) { result.push_back(outElt); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename Container>
|
||||
auto ToVector(const Container& input) -> decltype(auto) {
|
||||
typedef typename Container::value_type value_type;
|
||||
return vector<value_type>(begin(input), end(input));
|
||||
}
|
||||
|
||||
template<typename C1, typename C2>
|
||||
auto Append(C1& recipient, const C2& newItems) -> C1& {
|
||||
recipient.insert(end(recipient), begin(newItems), end(newItems));
|
||||
return recipient;
|
||||
}
|
||||
|
||||
} // namespace common
|
||||
|
||||
#endif // EAGLE0_CONTAINERUTILS_HPP
|
||||
@@ -145,7 +145,7 @@ auto FilesystemUtils::LoadFromPath(const string& path) -> byte_vector {
|
||||
const std::streamsize size = inputFileStream.tellg();
|
||||
inputFileStream.seekg(0, std::ios::beg);
|
||||
|
||||
auto bv = byte_vector(static_cast<size_t>(size));
|
||||
auto bv = byte_vector(size);
|
||||
inputFileStream.read((char*)bv.data(), size);
|
||||
|
||||
return bv;
|
||||
|
||||
@@ -84,9 +84,7 @@ auto RandomGenerator::ChanceOpenEndedPercentileAtOrAbove(const double value) ->
|
||||
|
||||
auto StdLibraryGenerator::DoubleZeroToOne() -> double { return unifDouble(engine); }
|
||||
|
||||
StdLibraryGenerator::StdLibraryGenerator() : RandomGenerator() {
|
||||
engine.seed(static_cast<std::mt19937_64::result_type>(std::time(nullptr)));
|
||||
}
|
||||
StdLibraryGenerator::StdLibraryGenerator() : RandomGenerator() { engine.seed(std::time(nullptr)); }
|
||||
|
||||
auto StdLibraryGenerator::IntBetween(const int min, const int max) -> int {
|
||||
std::uniform_int_distribution<int> unifInt(min, max - 1);
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
//
|
||||
// TaskResult.hpp - Result wrapper for task execution with status information
|
||||
//
|
||||
|
||||
#ifndef EAGLE0_TASK_RESULT_HPP
|
||||
#define EAGLE0_TASK_RESULT_HPP
|
||||
|
||||
namespace eagle0::common {
|
||||
|
||||
enum class TaskStatus { SUCCESS = 0, DEADLINE_EXCEEDED = 1, CANCELLED = 2 };
|
||||
|
||||
template<typename T>
|
||||
struct TaskResult {
|
||||
T value;
|
||||
TaskStatus status;
|
||||
|
||||
TaskResult() : value{}, status(TaskStatus::SUCCESS) {}
|
||||
TaskResult(T val) : value(std::move(val)), status(TaskStatus::SUCCESS) {}
|
||||
TaskResult(T val, TaskStatus stat) : value(std::move(val)), status(stat) {}
|
||||
|
||||
// Convenience methods for checking status
|
||||
T get() const { return value; }
|
||||
bool succeeded() const { return status == TaskStatus::SUCCESS; }
|
||||
bool deadlineExceeded() const { return status == TaskStatus::DEADLINE_EXCEEDED; }
|
||||
bool cancelled() const { return status == TaskStatus::CANCELLED; }
|
||||
|
||||
// Factory methods for cleaner construction
|
||||
static TaskResult Success(T val) { return TaskResult(std::move(val), TaskStatus::SUCCESS); }
|
||||
static TaskResult DeadlineExceeded(T val = T{}) {
|
||||
return TaskResult(std::move(val), TaskStatus::DEADLINE_EXCEEDED);
|
||||
}
|
||||
static TaskResult Cancelled(T val = T{}) {
|
||||
return TaskResult(std::move(val), TaskStatus::CANCELLED);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace eagle0::common
|
||||
|
||||
#endif // EAGLE0_TASK_RESULT_HPP
|
||||
@@ -8,11 +8,11 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
@@ -45,18 +45,38 @@ public:
|
||||
private:
|
||||
struct Task {
|
||||
std::function<void()> function;
|
||||
int priority;
|
||||
TimePoint deadline;
|
||||
bool has_deadline;
|
||||
|
||||
Task(std::function<void()> f, TimePoint d, bool has_d)
|
||||
Task(std::function<void()> f, int p, TimePoint d, bool has_d)
|
||||
: function(std::move(f)),
|
||||
priority(p),
|
||||
deadline(d),
|
||||
has_deadline(has_d) {}
|
||||
|
||||
// Higher priority values and earlier deadlines have higher priority
|
||||
bool operator<(const Task& other) const {
|
||||
if (priority != other.priority) {
|
||||
return priority < other.priority; // Lower priority values have lower priority in
|
||||
// priority_queue
|
||||
}
|
||||
if (has_deadline && other.has_deadline) {
|
||||
return deadline > other.deadline; // Later deadlines have lower priority
|
||||
}
|
||||
if (has_deadline && !other.has_deadline) {
|
||||
return false; // Tasks with deadlines have higher priority
|
||||
}
|
||||
if (!has_deadline && other.has_deadline) {
|
||||
return true; // Tasks without deadlines have lower priority
|
||||
}
|
||||
return false; // Equal priority, no preference
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::thread> workers;
|
||||
std::deque<Task> tasks; // Simple FIFO queue instead of priority queue
|
||||
mutable std::mutex queue_mutex; // mutable for const methods like queue_size()
|
||||
std::priority_queue<Task> tasks;
|
||||
std::mutex queue_mutex;
|
||||
std::condition_variable condition;
|
||||
std::atomic<bool> stop{false};
|
||||
|
||||
@@ -65,7 +85,7 @@ public:
|
||||
for (size_t i = 0; i < num_threads; ++i) {
|
||||
workers.emplace_back([this] {
|
||||
while (true) {
|
||||
Task task{nullptr, TimePoint{}, false};
|
||||
Task task{nullptr, 0, TimePoint{}, false};
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queue_mutex);
|
||||
condition.wait(lock, [this] { return stop.load() || !tasks.empty(); });
|
||||
@@ -73,8 +93,8 @@ public:
|
||||
if (stop.load() && tasks.empty()) { return; }
|
||||
|
||||
if (!tasks.empty()) {
|
||||
task = std::move(tasks.front());
|
||||
tasks.pop_front();
|
||||
task = std::move(const_cast<Task&>(tasks.top()));
|
||||
tasks.pop();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
@@ -87,9 +107,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue a task without deadline
|
||||
// Enqueue a task with priority only
|
||||
template<class F, class... Args>
|
||||
auto enqueue(F&& f, Args&&... args)
|
||||
auto enqueue(F&& f, Args&&... args, int priority = 0)
|
||||
-> std::future<TaskResult<std::invoke_result_t<F, Args...>>> {
|
||||
using return_type = std::invoke_result_t<F, Args...>;
|
||||
using result_type = TaskResult<return_type>;
|
||||
@@ -106,21 +126,21 @@ public:
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queue_mutex);
|
||||
if (stop.load()) { throw std::runtime_error("enqueue on stopped ThreadPool"); }
|
||||
tasks.emplace_back([task]() { (*task)(); }, TimePoint{}, false);
|
||||
tasks.emplace([task]() { (*task)(); }, priority, TimePoint{}, false);
|
||||
}
|
||||
|
||||
condition.notify_one();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Enqueue a task with deadline
|
||||
template<class F>
|
||||
auto enqueue_with_deadline(F&& f, TimePoint deadline)
|
||||
-> std::future<TaskResult<std::invoke_result_t<F>>> {
|
||||
using return_type = std::invoke_result_t<F>;
|
||||
// Enqueue a task with priority and deadline
|
||||
template<class F, class... Args>
|
||||
auto enqueue_with_deadline(F&& f, Args&&... args, int priority, TimePoint deadline)
|
||||
-> std::future<TaskResult<std::invoke_result_t<F, Args...>>> {
|
||||
using return_type = std::invoke_result_t<F, Args...>;
|
||||
using result_type = TaskResult<return_type>;
|
||||
|
||||
auto actualTask = std::forward<F>(f);
|
||||
auto actualTask = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
|
||||
|
||||
auto task = std::make_shared<std::packaged_task<result_type()>>(
|
||||
[actualTask = std::move(actualTask), deadline]() mutable -> result_type {
|
||||
@@ -135,7 +155,7 @@ public:
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queue_mutex);
|
||||
if (stop.load()) { throw std::runtime_error("enqueue on stopped ThreadPool"); }
|
||||
tasks.emplace_back([task]() { (*task)(); }, deadline, true);
|
||||
tasks.emplace([task]() { (*task)(); }, priority, deadline, true);
|
||||
}
|
||||
|
||||
condition.notify_one();
|
||||
@@ -144,27 +164,25 @@ public:
|
||||
|
||||
// Get current queue size (approximate, for monitoring)
|
||||
size_t queue_size() const {
|
||||
std::unique_lock<std::mutex> lock(queue_mutex);
|
||||
std::unique_lock<std::mutex> lock(const_cast<std::mutex&>(queue_mutex));
|
||||
return tasks.size();
|
||||
}
|
||||
|
||||
// Get detailed queue information for debugging
|
||||
void debug_queue_state() const {
|
||||
std::unique_lock<std::mutex> lock(queue_mutex);
|
||||
std::unique_lock<std::mutex> lock(const_cast<std::mutex&>(queue_mutex));
|
||||
printf("ThreadPool: Queue size: %zu\n", tasks.size());
|
||||
if (!tasks.empty()) {
|
||||
int with_deadline = 0;
|
||||
int without_deadline = 0;
|
||||
for (const auto& task : tasks) {
|
||||
if (task.has_deadline) {
|
||||
with_deadline++;
|
||||
} else {
|
||||
without_deadline++;
|
||||
}
|
||||
// Create a copy to inspect priorities without modifying queue
|
||||
auto queue_copy = tasks;
|
||||
std::vector<int> priorities;
|
||||
while (!queue_copy.empty()) {
|
||||
priorities.push_back(queue_copy.top().priority);
|
||||
queue_copy.pop();
|
||||
}
|
||||
printf("ThreadPool: Tasks with deadline: %d, without deadline: %d\n",
|
||||
with_deadline,
|
||||
without_deadline);
|
||||
printf("ThreadPool: Priorities in queue: ");
|
||||
for (int p : priorities) { printf("%d ", p); }
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ namespace shardok {
|
||||
|
||||
using Coords = net::eagle0::shardok::storage::fb::Coords;
|
||||
|
||||
constexpr double kDefaultMorale = 50.0;
|
||||
|
||||
auto ConvertBattalion(const net::eagle0::common::CommonBattalion &battalion) -> Battalion {
|
||||
Battalion shardokBattalion{};
|
||||
|
||||
@@ -17,9 +15,9 @@ auto ConvertBattalion(const net::eagle0::common::CommonBattalion &battalion) ->
|
||||
shardokBattalion.mutate_size(battalion.size());
|
||||
shardokBattalion.mutate_type(
|
||||
static_cast<net::eagle0::shardok::storage::fb::BattalionTypeId>(battalion.type()));
|
||||
shardokBattalion.mutate_morale(kDefaultMorale);
|
||||
shardokBattalion.mutate_armament(static_cast<float>(battalion.armament()));
|
||||
shardokBattalion.mutate_training(static_cast<float>(battalion.training()));
|
||||
shardokBattalion.mutate_morale(battalion.morale());
|
||||
shardokBattalion.mutate_armament(battalion.armament());
|
||||
shardokBattalion.mutate_training(battalion.training());
|
||||
|
||||
return shardokBattalion;
|
||||
}
|
||||
@@ -39,28 +37,28 @@ auto ConvertHero(const net::eagle0::common::CommonHero &hero) -> Hero {
|
||||
shardokHero.mutable_control_info().mutate_controlled_unit_id(-1);
|
||||
shardokHero.mutable_control_info().mutate_controlled_this_round(false);
|
||||
|
||||
shardokHero.mutate_strength(static_cast<int8_t>(hero.strength()));
|
||||
shardokHero.mutate_strength_xp(static_cast<int16_t>(hero.strength_xp()));
|
||||
shardokHero.mutate_strength(hero.strength());
|
||||
shardokHero.mutate_strength_xp(hero.strength_xp());
|
||||
|
||||
shardokHero.mutate_agility(static_cast<int8_t>(hero.agility()));
|
||||
shardokHero.mutate_agility_xp(static_cast<int16_t>(hero.agility_xp()));
|
||||
shardokHero.mutate_agility(hero.agility());
|
||||
shardokHero.mutate_agility_xp(hero.agility_xp());
|
||||
|
||||
shardokHero.mutate_constitution(static_cast<int8_t>(hero.constitution()));
|
||||
shardokHero.mutate_constitution_xp(static_cast<int16_t>(hero.constitution_xp()));
|
||||
shardokHero.mutate_constitution(hero.constitution());
|
||||
shardokHero.mutate_constitution_xp(hero.constitution_xp());
|
||||
|
||||
shardokHero.mutate_charisma(static_cast<int8_t>(hero.charisma()));
|
||||
shardokHero.mutate_charisma_xp(static_cast<int16_t>(hero.charisma_xp()));
|
||||
shardokHero.mutate_charisma(hero.charisma());
|
||||
shardokHero.mutate_charisma_xp(hero.charisma_xp());
|
||||
|
||||
shardokHero.mutate_wisdom(static_cast<int8_t>(hero.wisdom()));
|
||||
shardokHero.mutate_wisdom_xp(static_cast<int16_t>(hero.wisdom_xp()));
|
||||
shardokHero.mutate_wisdom(hero.wisdom());
|
||||
shardokHero.mutate_wisdom_xp(hero.wisdom_xp());
|
||||
|
||||
shardokHero.mutate_integrity(static_cast<int8_t>(hero.integrity()));
|
||||
shardokHero.mutate_ambition(static_cast<int8_t>(hero.ambition()));
|
||||
shardokHero.mutate_gregariousness(static_cast<int8_t>(hero.gregariousness()));
|
||||
shardokHero.mutate_bravery(static_cast<int8_t>(hero.bravery()));
|
||||
shardokHero.mutate_integrity(hero.integrity());
|
||||
shardokHero.mutate_ambition(hero.ambition());
|
||||
shardokHero.mutate_gregariousness(hero.gregariousness());
|
||||
shardokHero.mutate_bravery(hero.bravery());
|
||||
|
||||
shardokHero.mutate_vigor(static_cast<float>(hero.vigor()));
|
||||
shardokHero.mutate_starting_vigor(static_cast<float>(hero.vigor()));
|
||||
shardokHero.mutate_vigor(hero.vigor());
|
||||
shardokHero.mutate_starting_vigor(hero.vigor());
|
||||
|
||||
return shardokHero;
|
||||
}
|
||||
@@ -95,22 +93,19 @@ auto ConvertUnit(
|
||||
shardokUnit.mutate_stun_rounds_remaining(0);
|
||||
|
||||
for (const PlayerId pid : allPlayerIds) {
|
||||
shardokUnit.mutable_opponent_knowledge()->Mutate(
|
||||
static_cast<flatbuffers::uoffset_t>(pid),
|
||||
0);
|
||||
shardokUnit.mutable_opponent_knowledge()->Mutate(pid, 0);
|
||||
}
|
||||
|
||||
shardokUnit.mutate_has_moved_in_zoc(false);
|
||||
shardokUnit.mutate_targeted_unit(-1);
|
||||
shardokUnit.mutate_volleys_remaining(0);
|
||||
shardokUnit.mutate_food_remaining(static_cast<float>(unit.food()));
|
||||
shardokUnit.mutate_food_remaining(unit.food());
|
||||
shardokUnit.mutate_can_flee(unit.can_flee());
|
||||
shardokUnit.mutate_can_archery(unit.can_archery());
|
||||
shardokUnit.mutate_can_start_fire(unit.can_start_fire());
|
||||
|
||||
if (unit.has_starting_position_index()) {
|
||||
shardokUnit.mutate_starting_position_index(
|
||||
static_cast<int8_t>(unit.starting_position_index().value()));
|
||||
shardokUnit.mutate_starting_position_index(unit.starting_position_index().value());
|
||||
} else {
|
||||
shardokUnit.mutate_starting_position_index(-1);
|
||||
}
|
||||
|
||||
@@ -9,10 +9,7 @@
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCTypes.h"
|
||||
#include "src/main/flatbuffer/net/eagle0/shardok/storage/unit.hpp"
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-redundant-constexpr-static-def"
|
||||
#include "src/main/protobuf/net/eagle0/common/common_unit.pb.h"
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
namespace shardok {
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ auto CalculateMap(
|
||||
.name = mapName,
|
||||
.positionsRequiringCrossing = {}};
|
||||
|
||||
for (unsigned int i = 0; i < hexMap->attacker_starting_positions()->size(); i++) {
|
||||
for (int i = 0; i < hexMap->attacker_starting_positions()->size(); i++) {
|
||||
const auto* positionList = hexMap->attacker_starting_positions()->Get(i);
|
||||
if (positionList->positions()->size() < 1) continue;
|
||||
if (positionList->positions()->size() != 10) {
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
#ifndef EAGLE0_MAPINFOCALCULATOR_HPP
|
||||
#define EAGLE0_MAPINFOCALCULATOR_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistancesCache.hpp"
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "MapInfoCalculator.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"
|
||||
@@ -53,7 +52,7 @@ auto main(const int argc, char** argv) -> int {
|
||||
outputStream << " \"positions\": {";
|
||||
|
||||
bool firstPosition = true;
|
||||
for (const auto& [position, count] : mapInfo.positionsRequiringCrossing) {
|
||||
for (const auto& kv : mapInfo.positionsRequiringCrossing) {
|
||||
if (firstPosition) {
|
||||
outputStream << endl;
|
||||
firstPosition = false;
|
||||
@@ -61,7 +60,7 @@ auto main(const int argc, char** argv) -> int {
|
||||
outputStream << "," << endl;
|
||||
}
|
||||
|
||||
outputStream << " \"" << position << "\": " << count;
|
||||
outputStream << " \"" << kv.first << "\": " << kv.second;
|
||||
}
|
||||
outputStream << endl << " }" << endl << " }";
|
||||
}
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
|
||||
#include "AIAttackGroups.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include <ranges>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"
|
||||
@@ -224,15 +221,11 @@ auto GenerateTargetPriorities(
|
||||
Power(unit);
|
||||
}
|
||||
|
||||
tpl.priorityOrder.reserve(targetsWithDistance.size());
|
||||
std::ranges::transform(
|
||||
targetsWithDistance,
|
||||
std::back_inserter(tpl.priorityOrder),
|
||||
[](const TargetAndDistance& tad) {
|
||||
return TargetAndAttackLocations{
|
||||
.target = tad.target,
|
||||
.attackLocations = tad.attackLocations};
|
||||
});
|
||||
tpl.priorityOrder = common::Map(targetsWithDistance, [](const TargetAndDistance& tad) {
|
||||
return TargetAndAttackLocations{
|
||||
.target = tad.target,
|
||||
.attackLocations = tad.attackLocations};
|
||||
});
|
||||
}
|
||||
|
||||
return allTargetsUnitsAndDistances;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "AIAttackerStrategySelector.hpp"
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIFleeDecisionCalculator.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIScoreUtilities.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
|
||||
|
||||
@@ -12,21 +11,21 @@ namespace shardok {
|
||||
|
||||
using Unit = net::eagle0::shardok::storage::fb::Unit;
|
||||
|
||||
// Combat success threshold below which we should consider fleeing
|
||||
// This replaces the simple troop ratio check with sophisticated probability estimation
|
||||
constexpr double FLEE_CONSIDERATION_THRESHOLD = 0.25;
|
||||
constexpr double MAXIMUM_RATIO_FOR_ATTACKER_TO_FLEE = 0.50;
|
||||
|
||||
auto AIAttackerStrategySelector::BestAttackerStrategy(
|
||||
const PlayerId attackerPid,
|
||||
const GameStateW& gameState,
|
||||
const net::eagle0::shardok::storage::fb::GameState* gameState,
|
||||
const CoordsSet& criticalTileCoords,
|
||||
const APDCache& apdCache,
|
||||
const ALCache& alCache,
|
||||
const SettingsGetter& settings,
|
||||
const AIWaterCrossingCommandChooser& waterCrossingCommandChooser,
|
||||
const vector<CommandProto>& /*availableCommands*/) -> AIStrategy {
|
||||
const vector<CommandProto>& availableCommands) -> AIStrategy {
|
||||
uint32_t attackerUnitCount = 0;
|
||||
int defenderOccupiedCriticalTileCount = 0;
|
||||
int attackerTroops = 0;
|
||||
int defenderTroops = 0;
|
||||
bool canFlee = false;
|
||||
|
||||
vector<const Unit*> attackerUnits{};
|
||||
@@ -41,6 +40,8 @@ auto AIAttackerStrategySelector::BestAttackerStrategy(
|
||||
if (pi != nullptr) {
|
||||
if (pi->is_defender()) {
|
||||
if (unit->location().row() >= 0) {
|
||||
defenderTroops += unit->battalion().size();
|
||||
|
||||
if (criticalTileCoords.Contains(unit->location())) {
|
||||
++defenderOccupiedCriticalTileCount;
|
||||
}
|
||||
@@ -49,6 +50,7 @@ auto AIAttackerStrategySelector::BestAttackerStrategy(
|
||||
}
|
||||
} else if (unit->player_id() == attackerPid) {
|
||||
++attackerUnitCount;
|
||||
attackerTroops += unit->battalion().size();
|
||||
if (unit->can_flee()) canFlee = true;
|
||||
attackerUnits.push_back(unit);
|
||||
} else {
|
||||
@@ -58,13 +60,7 @@ auto AIAttackerStrategySelector::BestAttackerStrategy(
|
||||
}
|
||||
|
||||
AIStrategy chosenStrategy;
|
||||
|
||||
// Use sophisticated combat success estimation instead of simple troop ratio
|
||||
if (canFlee && AIFleeDecisionCalculator::ShouldConsiderFleeing(
|
||||
attackerPid,
|
||||
gameState,
|
||||
settings,
|
||||
FLEE_CONSIDERATION_THRESHOLD)) {
|
||||
if (canFlee && attackerTroops < MAXIMUM_RATIO_FOR_ATTACKER_TO_FLEE * defenderTroops) {
|
||||
chosenStrategy = FleeStrategy;
|
||||
} else if (const CoordsSet startCrossingLocations =
|
||||
waterCrossingCommandChooser
|
||||
|
||||
@@ -8,16 +8,18 @@
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIStrategy.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIWaterCrossingCommandChooser.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
|
||||
#include "src/main/flatbuffer/net/eagle0/shardok/storage/game_state.hpp"
|
||||
|
||||
namespace shardok {
|
||||
|
||||
using GameState = net::eagle0::shardok::storage::fb::GameState;
|
||||
|
||||
class AIAttackerStrategySelector {
|
||||
public:
|
||||
static auto BestAttackerStrategy(
|
||||
PlayerId attackerPid,
|
||||
const GameStateW& gameState,
|
||||
const GameState* gameState,
|
||||
const CoordsSet& criticalTileCoords,
|
||||
const APDCache& apdCache,
|
||||
const ALCache& alCache,
|
||||
|
||||
@@ -20,8 +20,8 @@ CoordsSet AICommandFilter::BuildEnemyLocations(const GameStateW& gameState, Play
|
||||
CoordsSet enemyLocations(gameState->hex_map());
|
||||
const auto* units = gameState->units();
|
||||
|
||||
for (size_t i = 0; i < units->size(); ++i) {
|
||||
const auto* unit = units->Get(static_cast<unsigned int>(i));
|
||||
for (int i = 0; i < units->size(); ++i) {
|
||||
const auto* unit = units->Get(i);
|
||||
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
|
||||
unit->player_id() != pid && !unit->hidden() && unit->location().column() != -1) {
|
||||
enemyLocations.Add(unit->location());
|
||||
@@ -486,12 +486,12 @@ bool AICommandFilter::IsWastefulMovement(
|
||||
}
|
||||
|
||||
bool AICommandFilter::IsStrategicBlunder(
|
||||
const ShardokCommand& /*cmd*/,
|
||||
PlayerId /*pid*/,
|
||||
bool /*isDefender*/,
|
||||
const GameStateW& /*gameState*/,
|
||||
const SettingsGetter& /*settings*/,
|
||||
double /*minDistToEnemies*/) {
|
||||
const ShardokCommand& cmd,
|
||||
PlayerId pid,
|
||||
bool isDefender,
|
||||
const GameStateW& gameState,
|
||||
const SettingsGetter& settings,
|
||||
double minDistToEnemies) {
|
||||
// Simplified strategic blunder detection for now
|
||||
// TODO: Implement proper castle abandonment detection
|
||||
// TODO: Use minDistToEnemies for strategic blunder logic
|
||||
@@ -506,8 +506,8 @@ double AICommandFilter::MinDistanceToEnemyUnits(
|
||||
double minDistance = std::numeric_limits<double>::max();
|
||||
const auto* units = gameState->units();
|
||||
|
||||
for (size_t i = 0; i < units->size(); ++i) {
|
||||
const auto* playerUnit = units->Get(static_cast<unsigned int>(i));
|
||||
for (int i = 0; i < units->size(); ++i) {
|
||||
const auto* playerUnit = units->Get(i);
|
||||
if (playerUnit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
|
||||
playerUnit->player_id() == pid) {
|
||||
const auto& playerCoords = playerUnit->location();
|
||||
@@ -537,8 +537,8 @@ double AICommandFilter::MinDistanceToCastles(
|
||||
}
|
||||
|
||||
// Find minimum hex distance from any player unit to any castle
|
||||
for (size_t i = 0; i < units->size(); ++i) {
|
||||
const auto* unit = units->Get(static_cast<unsigned int>(i));
|
||||
for (int i = 0; i < units->size(); ++i) {
|
||||
const auto* unit = units->Get(i);
|
||||
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
|
||||
unit->player_id() == pid) {
|
||||
const auto& unitCoords = unit->location();
|
||||
@@ -572,8 +572,8 @@ int AICommandFilter::CountPlayerUnits(const GameStateW& gameState, PlayerId pid)
|
||||
int count = 0;
|
||||
const auto* units = gameState->units();
|
||||
|
||||
for (size_t i = 0; i < units->size(); ++i) {
|
||||
const auto* unit = units->Get(static_cast<unsigned int>(i));
|
||||
for (int i = 0; i < units->size(); ++i) {
|
||||
const auto* unit = units->Get(i);
|
||||
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
|
||||
unit->player_id() == pid) {
|
||||
count++;
|
||||
@@ -584,9 +584,9 @@ int AICommandFilter::CountPlayerUnits(const GameStateW& gameState, PlayerId pid)
|
||||
}
|
||||
|
||||
bool AICommandFilter::WouldAbandonCriticalCastle(
|
||||
const ShardokCommand& /*cmd*/,
|
||||
PlayerId /*pid*/,
|
||||
const GameStateW& /*gameState*/) {
|
||||
const ShardokCommand& cmd,
|
||||
PlayerId pid,
|
||||
const GameStateW& gameState) {
|
||||
// Simplified implementation - return false for now
|
||||
// TODO: Implement proper castle abandonment detection when API is available
|
||||
return false;
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
|
||||
#include "AIDefenderStrategySelector.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIScoreUtilities.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIWaterCrossingCalculator.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
|
||||
@@ -17,7 +14,7 @@ constexpr double MAXIMUM_RATIO_FOR_DEFENDER_TO_FLEE = 0.15;
|
||||
constexpr double MINIMUM_RATIO_FOR_DEFENDER_TO_HOLD = 0.60;
|
||||
|
||||
auto AIDefenderStrategySelector::BestDefenderStrategy(
|
||||
const GameStateW& gameState,
|
||||
const GameState* gameState,
|
||||
const CoordsSet& criticalTileCoords,
|
||||
const APDCache& apdCache,
|
||||
const SettingsGetter& settings) -> AIStrategy {
|
||||
@@ -60,9 +57,7 @@ auto AIDefenderStrategySelector::BestDefenderStrategy(
|
||||
net::eagle0::shardok::storage::fb::BattalionTypeId_UNDEAD) {
|
||||
++attackerNonUndeadUnitCount;
|
||||
|
||||
if (!std::ranges::contains(
|
||||
attackerUnitIdsRequiringWaterCrossing,
|
||||
unit->unit_id())) {
|
||||
if (!common::Contains(attackerUnitIdsRequiringWaterCrossing, unit->unit_id())) {
|
||||
++attackerNonUndeadUnitNotRequiringWaterCrossingCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,16 @@
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIStrategy.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
|
||||
#include "src/main/flatbuffer/net/eagle0/shardok/storage/game_state.hpp"
|
||||
|
||||
namespace shardok {
|
||||
class AIDefenderStrategySelector {
|
||||
using GameState = net::eagle0::shardok::storage::fb::GameState;
|
||||
|
||||
public:
|
||||
static auto BestDefenderStrategy(
|
||||
const GameStateW& gameState,
|
||||
const GameState* gameState,
|
||||
const CoordsSet& criticalTileCoords,
|
||||
const APDCache& apdCache,
|
||||
const SettingsGetter& settings) -> AIStrategy;
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
//
|
||||
// AIFleeDecisionCalculator.cpp
|
||||
// eagle0
|
||||
//
|
||||
// Handles AI flee decision logic including combat success estimation
|
||||
// and flee vs fight evaluation for final round scenarios
|
||||
//
|
||||
|
||||
#include "AIFleeDecisionCalculator.hpp"
|
||||
|
||||
#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 {
|
||||
|
||||
auto AIFleeDecisionCalculator::GetFleeCommandIndex(
|
||||
const vector<CommandProto>::const_iterator& fleeCommand,
|
||||
const vector<CommandProto>& availableCommands) -> size_t {
|
||||
return static_cast<size_t>(std::distance(availableCommands.begin(), fleeCommand));
|
||||
}
|
||||
|
||||
auto AIFleeDecisionCalculator::EstimateCombatSuccess(
|
||||
PlayerId attackerPlayerId,
|
||||
const GameStateW& gameState,
|
||||
const SettingsGetter& settings) -> double {
|
||||
if (gameState->status() == nullptr ||
|
||||
gameState->status()->state() !=
|
||||
net::eagle0::shardok::storage::fb::GameStatus_::State_GAME_RUNNING) {
|
||||
return 1.0; // we're still in set_up so we can't really evaluate
|
||||
}
|
||||
|
||||
// Combat success estimation based on unit power, heroes, and capture dynamics
|
||||
double attackerPower = 0.0;
|
||||
double defenderPower = 0.0;
|
||||
int attackerTroops = 0; // Still track raw troops for special cases
|
||||
int defenderTroops = 0;
|
||||
int attackerUnits = 0;
|
||||
int defenderUnits = 0;
|
||||
int attackerHeroes = 0;
|
||||
int defenderHeroes = 0;
|
||||
bool defenderHasVips = false;
|
||||
|
||||
// Calculate total power and count units/heroes for each side
|
||||
for (const auto* unit : *gameState->units()) {
|
||||
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
|
||||
|
||||
const auto* pi = PlayerInfoForPid(gameState, unit->player_id());
|
||||
if (pi == nullptr) continue;
|
||||
|
||||
const int unitTroops = unit->battalion().size();
|
||||
const bool hasHero = unit->has_attached_hero();
|
||||
const double unitPower = ContextFreeUnitValue(unit);
|
||||
|
||||
if (pi->is_defender()) {
|
||||
defenderPower += unitPower;
|
||||
defenderTroops += unitTroops;
|
||||
defenderUnits++;
|
||||
if (hasHero) {
|
||||
defenderHeroes++;
|
||||
if (unit->attached_hero().is_vip()) { defenderHasVips = true; }
|
||||
}
|
||||
} else if (unit->player_id() == attackerPlayerId) {
|
||||
attackerPower += unitPower;
|
||||
attackerTroops += unitTroops;
|
||||
attackerUnits++;
|
||||
if (hasHero) { attackerHeroes++; }
|
||||
}
|
||||
}
|
||||
|
||||
const int roundsRemaining = settings.Backing().max_rounds() - gameState->current_round();
|
||||
|
||||
// Special case: Attacker has no heroes - automatic loss
|
||||
if (attackerHeroes == 0) {
|
||||
return 0.0; // Cannot win without heroes
|
||||
}
|
||||
|
||||
// Special case: Defender has no heroes - automatic win for attacker
|
||||
if (defenderHeroes == 0) {
|
||||
return 1.0; // Guaranteed win
|
||||
}
|
||||
|
||||
// Special case: Attacker has no troops (but has heroes)
|
||||
if (attackerTroops == 0) {
|
||||
// Very difficult to win with heroes alone
|
||||
return 0.05; // Extremely low chance
|
||||
}
|
||||
|
||||
// Special case: Defender has no troops but has heroes
|
||||
if (defenderTroops == 0) {
|
||||
// Defenders with only heroes are vulnerable to capture
|
||||
// Only truly difficult if time is extremely limited
|
||||
if (roundsRemaining <= 1) {
|
||||
// Last round - very hard to capture all heroes
|
||||
return 0.3; // Low but not impossible
|
||||
} else if (roundsRemaining <= 2) {
|
||||
return 0.6; // Still achievable
|
||||
} else {
|
||||
// With 3+ rounds, capturing defenseless heroes is quite feasible
|
||||
return 0.85; // High probability of success
|
||||
}
|
||||
}
|
||||
|
||||
// Normal case: Both sides have troops
|
||||
// Base probability from power ratio (accounts for unit quality, not just quantity)
|
||||
const double powerRatio = attackerPower / std::max(1.0, defenderPower);
|
||||
double baseProbability = std::min(0.95, std::max(0.05, powerRatio * 0.5));
|
||||
|
||||
// Adjust for time pressure - attackers need to win before time runs out
|
||||
if (roundsRemaining <= 1) {
|
||||
baseProbability *= 0.6; // Severe penalty for last round
|
||||
} else if (roundsRemaining <= 3) {
|
||||
baseProbability *= 0.8; // Moderate penalty
|
||||
}
|
||||
|
||||
// Adjust for unit count (more units = better tactical flexibility)
|
||||
const double unitRatio =
|
||||
static_cast<double>(attackerUnits) / std::max(1.0, static_cast<double>(defenderUnits));
|
||||
if (unitRatio < 0.5) {
|
||||
baseProbability *= 0.8;
|
||||
} else if (unitRatio > 1.5) {
|
||||
baseProbability *= 1.15;
|
||||
}
|
||||
|
||||
// Adjust for hero presence
|
||||
if (defenderHeroes > attackerHeroes && defenderHasVips) {
|
||||
// Defender has more heroes including VIPs - harder to capture
|
||||
baseProbability *= 0.85;
|
||||
}
|
||||
|
||||
return std::min(0.95, std::max(0.05, baseProbability));
|
||||
}
|
||||
|
||||
auto AIFleeDecisionCalculator::EvaluateFleeVsFight(
|
||||
PlayerId playerId,
|
||||
const SettingsGetter& settingsGetter,
|
||||
const GameStateW& guessedState,
|
||||
const vector<CommandProto>& availableCommands,
|
||||
const vector<CommandProto>::const_iterator& fleeCommand,
|
||||
bool enableDebugLogging) -> FleeDecision {
|
||||
// Get flee success odds
|
||||
const int fleeSuccessChance = fleeCommand->odds().success_chance();
|
||||
|
||||
// Get thresholds from settings
|
||||
const int minimumFleeOddsThreshold = settingsGetter.Backing().ai_minimum_flee_odds_threshold();
|
||||
const int desperateFleeThreshold = settingsGetter.Backing().ai_desperate_flee_threshold();
|
||||
|
||||
if (enableDebugLogging) {
|
||||
printf("AI FinalRound: Evaluating flee (odds=%d%%)...\n", fleeSuccessChance);
|
||||
}
|
||||
|
||||
// Check if flee odds are good enough to attempt
|
||||
if (fleeSuccessChance >= minimumFleeOddsThreshold) {
|
||||
if (enableDebugLogging) {
|
||||
printf("AI FinalRound: Good flee odds (%d%% >= %d%%), choosing flee\n",
|
||||
fleeSuccessChance,
|
||||
minimumFleeOddsThreshold);
|
||||
}
|
||||
return FleeDecision{
|
||||
true,
|
||||
GetFleeCommandIndex(fleeCommand, availableCommands),
|
||||
"Good flee odds"};
|
||||
}
|
||||
|
||||
// Low flee odds - evaluate if fighting might be better
|
||||
const double combatWinChance = EstimateCombatSuccess(playerId, guessedState, settingsGetter);
|
||||
|
||||
// If combat situation is hopeless, even bad flee odds are better than certain death
|
||||
if (combatWinChance <= 0.05 && fleeSuccessChance >= desperateFleeThreshold) {
|
||||
if (enableDebugLogging) {
|
||||
printf("AI FinalRound: Combat hopeless (%.1f%%), desperate flee attempt (%d%%)\n",
|
||||
combatWinChance * 100,
|
||||
fleeSuccessChance);
|
||||
}
|
||||
return FleeDecision{
|
||||
true,
|
||||
GetFleeCommandIndex(fleeCommand, availableCommands),
|
||||
"Combat hopeless, desperate flee"};
|
||||
}
|
||||
|
||||
// Detailed flee vs fight comparison
|
||||
const double fleeChance = static_cast<double>(fleeSuccessChance) / 100.0;
|
||||
|
||||
// Compare expected outcomes:
|
||||
// - Flee: fleeChance of survival (not victory, but avoiding loss)
|
||||
// - Fight: combatWinChance of victory (better than survival)
|
||||
|
||||
constexpr double FLEE_VS_COMBAT_MARGIN =
|
||||
0.8; // Require 80% of combat chance to prefer fighting
|
||||
const double adjustedCombatThreshold = combatWinChance * FLEE_VS_COMBAT_MARGIN;
|
||||
|
||||
if (enableDebugLogging) {
|
||||
printf("AI FinalRound: Flee=%d%%, Combat=%.1f%%, Threshold=%.1f%% -> ",
|
||||
fleeSuccessChance,
|
||||
combatWinChance * 100,
|
||||
adjustedCombatThreshold * 100);
|
||||
}
|
||||
|
||||
if (fleeChance > adjustedCombatThreshold) {
|
||||
if (enableDebugLogging) { printf("FLEE (better odds)\n"); }
|
||||
return FleeDecision{
|
||||
true,
|
||||
GetFleeCommandIndex(fleeCommand, availableCommands),
|
||||
"Flee has better expected outcome"};
|
||||
} else {
|
||||
if (enableDebugLogging) { printf("FIGHT (better expected outcome)\n"); }
|
||||
// Return 0 to indicate we should use standard command selection
|
||||
return FleeDecision{
|
||||
false,
|
||||
0, // Will be replaced by StandardChooseCommandIndex
|
||||
"Fighting has better expected outcome"};
|
||||
}
|
||||
}
|
||||
|
||||
auto AIFleeDecisionCalculator::ShouldConsiderFleeing(
|
||||
PlayerId attackerPlayerId,
|
||||
const GameStateW& guessedState,
|
||||
const SettingsGetter& settings,
|
||||
double fleeConsiderationThreshold) -> bool {
|
||||
// Get combat success probability
|
||||
const double combatSuccessChance =
|
||||
EstimateCombatSuccess(attackerPlayerId, guessedState, settings);
|
||||
|
||||
// Consider fleeing if combat success chance is below threshold
|
||||
return combatSuccessChance < fleeConsiderationThreshold;
|
||||
}
|
||||
|
||||
} // namespace shardok
|
||||
@@ -1,67 +0,0 @@
|
||||
//
|
||||
// AIFleeDecisionCalculator.hpp
|
||||
// eagle0
|
||||
//
|
||||
// Handles AI flee decision logic including combat success estimation
|
||||
// and flee vs fight evaluation for final round scenarios
|
||||
//
|
||||
|
||||
#ifndef AIFleeDecisionCalculator_hpp
|
||||
#define AIFleeDecisionCalculator_hpp
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"
|
||||
#include "src/main/protobuf/net/eagle0/shardok/api/command_descriptor.pb.h"
|
||||
|
||||
namespace shardok {
|
||||
|
||||
using CommandProto = net::eagle0::shardok::api::CommandDescriptor;
|
||||
|
||||
class AIFleeDecisionCalculator {
|
||||
public:
|
||||
// Configuration for flee decision thresholds
|
||||
struct FleeThresholds {
|
||||
int minimumFleeOddsThreshold; // Minimum flee success odds to consider fleeing
|
||||
int desperateFleeThreshold; // Flee threshold when combat is hopeless
|
||||
};
|
||||
|
||||
// Result of flee vs fight evaluation
|
||||
struct FleeDecision {
|
||||
bool shouldFlee;
|
||||
size_t commandIndex; // Index of command to execute (flee or fight)
|
||||
const char* reasoning; // Debug explanation of decision
|
||||
};
|
||||
|
||||
// Evaluate whether to flee or fight in the final round
|
||||
[[nodiscard]] static auto EvaluateFleeVsFight(
|
||||
PlayerId playerId,
|
||||
const SettingsGetter& settings,
|
||||
const GameStateW& guessedState,
|
||||
const vector<CommandProto>& availableCommands,
|
||||
const vector<CommandProto>::const_iterator& fleeCommand,
|
||||
bool enableDebugLogging = false) -> FleeDecision;
|
||||
|
||||
// Estimate probability of combat success for the attacker
|
||||
[[nodiscard]] static auto EstimateCombatSuccess(
|
||||
PlayerId attackerPlayerId,
|
||||
const GameStateW& guessedState,
|
||||
const SettingsGetter& settings) -> double;
|
||||
|
||||
// Determine if the attacker should consider fleeing based on combat odds
|
||||
// Returns true if fleeing should be considered as an option
|
||||
[[nodiscard]] static auto ShouldConsiderFleeing(
|
||||
PlayerId attackerPlayerId,
|
||||
const GameStateW& guessedState,
|
||||
const SettingsGetter& settings,
|
||||
double fleeConsiderationThreshold = 0.5) -> bool;
|
||||
|
||||
private:
|
||||
// Helper to get flee command index
|
||||
[[nodiscard]] static auto GetFleeCommandIndex(
|
||||
const vector<CommandProto>::const_iterator& fleeCommand,
|
||||
const vector<CommandProto>& availableCommands) -> size_t;
|
||||
};
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
#endif /* AIFleeDecisionCalculator_hpp */
|
||||
@@ -6,11 +6,8 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <future>
|
||||
|
||||
#include "TranspositionTable.hpp"
|
||||
#include "src/main/cpp/net/eagle0/common/SequenceRandomGenerator.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackGroups.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
|
||||
@@ -28,117 +25,6 @@
|
||||
|
||||
namespace shardok {
|
||||
|
||||
struct IndexAndScore {
|
||||
size_t index;
|
||||
CommandType type;
|
||||
ScoreValue lookaheadScore;
|
||||
ScoreValue immediateScore;
|
||||
};
|
||||
|
||||
struct ImmediateAndLookaheadScore {
|
||||
ScoreValue immediateScore;
|
||||
future<ScoreValue> lookaheadScore;
|
||||
};
|
||||
|
||||
[[nodiscard]] static auto BasicLookaheadCalculator(
|
||||
PlayerId pid,
|
||||
bool isDefender,
|
||||
int remainingLookahead,
|
||||
int maxRepeatCount,
|
||||
const ShardokEngine innerEngine,
|
||||
ScoreValue currentUtility,
|
||||
const AIStrategy &attackerStrategy,
|
||||
const SettingsGetter &settingsGetter,
|
||||
const CoordsSet &allCastleCoords,
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
std::chrono::steady_clock::time_point deadline) -> std::future<ScoreValue>;
|
||||
|
||||
[[nodiscard]] static auto BestCommandIndex(
|
||||
PlayerId pid,
|
||||
bool isDefender,
|
||||
int remainingLookahead,
|
||||
int maxRepeatCount,
|
||||
const ShardokEngine &guessedEngine,
|
||||
const AIStrategy &attackerStrategy,
|
||||
ScoreValue currentUtility,
|
||||
const SettingsGetter &settingsGetter,
|
||||
const CoordsSet &allCastleCoords,
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
std::chrono::steady_clock::time_point deadline) -> std::future<IndexAndScore>;
|
||||
|
||||
[[nodiscard]] static auto CalcOne(
|
||||
PlayerId pid,
|
||||
bool isDefender,
|
||||
uint32_t commandIndex,
|
||||
int remainingLookahead,
|
||||
int maxRepeatCount,
|
||||
const std::shared_ptr<RandomGenerator> &randomGenerator,
|
||||
const ShardokEngine &guessedEngine,
|
||||
const AIStrategy &attackerStrategy,
|
||||
const SettingsGetter &settingsGetter,
|
||||
const CoordsSet &allCastleCoords,
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
std::chrono::steady_clock::time_point deadline) -> ImmediateAndLookaheadScore;
|
||||
|
||||
[[nodiscard]] static auto DefenderScatterStrategyScoreForState(
|
||||
const GameStateW &gameState,
|
||||
int roundsRemaining,
|
||||
const SettingsGetter &settings,
|
||||
const ALCache &alCache,
|
||||
const APDCache &apdCache) -> ScoreValue;
|
||||
|
||||
[[nodiscard]] static auto DefenderHoldCastlesStrategyScoreForState(
|
||||
const GameStateW &gameState,
|
||||
const CoordsSet &castleCoords,
|
||||
int roundsRemaining,
|
||||
const SettingsGetter &settings,
|
||||
const ALCache &alCache,
|
||||
const APDCache &apdCache) -> ScoreValue;
|
||||
|
||||
[[nodiscard]] static auto FleeStrategyScoreForState(const GameStateW &gameState, PlayerId playerId)
|
||||
-> ScoreValue;
|
||||
|
||||
[[nodiscard]] static auto DefenderScoreForState(
|
||||
const GameStateW &gameState,
|
||||
const AIStrategy &defenderStrategy,
|
||||
const CoordsSet &castleCoords,
|
||||
int roundsRemaining,
|
||||
const SettingsGetter &settings,
|
||||
const ALCache &alCache,
|
||||
const APDCache &apdCache) -> ScoreValue;
|
||||
|
||||
[[nodiscard]] static auto AttackerScoreForState(
|
||||
const GameStateW &gameState,
|
||||
const AIStrategy &attackerStrategy,
|
||||
const CoordsSet &castleCoords,
|
||||
int roundsRemaining,
|
||||
const SettingsGetter &settings,
|
||||
const ALCache &alCache,
|
||||
const APDCache &apdCache) -> ScoreValue;
|
||||
|
||||
struct CommandEvaluationResult {
|
||||
ScoreValue immediateScore;
|
||||
future<ScoreValue> lookaheadScore;
|
||||
};
|
||||
|
||||
[[nodiscard]] static auto EvaluateCommand(
|
||||
PlayerId pid,
|
||||
bool isDefender,
|
||||
uint32_t commandIndex,
|
||||
int remainingLookahead,
|
||||
int maxRepeatCount,
|
||||
const ShardokEngine &guessedEngine,
|
||||
const AIStrategy &attackerStrategy,
|
||||
ScoreValue currentUtility,
|
||||
const SettingsGetter &settingsGetter,
|
||||
const CoordsSet &allCastleCoords,
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
std::chrono::steady_clock::time_point deadline) -> CommandEvaluationResult;
|
||||
|
||||
#define LOGGING_ 0
|
||||
#define PERFORMANCE_LOGGING_ 0
|
||||
|
||||
@@ -251,7 +137,9 @@ using Unit = fb::Unit;
|
||||
static const std::vector _averageSequence = {0.5};
|
||||
static const auto _averageGenerator = std::make_shared<SequenceRandomGenerator>(_averageSequence);
|
||||
|
||||
static auto CommandSorter(const IndexAndScore &l, const IndexAndScore &r) -> bool {
|
||||
static auto CommandSorter(
|
||||
const AIScoreCalculator::IndexAndScore &l,
|
||||
const AIScoreCalculator::IndexAndScore &r) -> bool {
|
||||
if (l.lookaheadScore < r.lookaheadScore) return true;
|
||||
if (l.lookaheadScore > r.lookaheadScore) return false;
|
||||
|
||||
@@ -373,13 +261,12 @@ auto AttackerUnitsScore(
|
||||
const APDCache &apdCache,
|
||||
const MapId &mapId) -> ScoreValue {
|
||||
// Cache frequently accessed FlatBuffer fields to avoid repeated offset calculations
|
||||
const auto *gameStateRawPtr = gameState.Get();
|
||||
const auto *cachedUnits = gameStateRawPtr->units();
|
||||
const auto *cachedHexMap = gameStateRawPtr->hex_map();
|
||||
|
||||
const auto *cachedGameState = gameState.Get();
|
||||
const auto *cachedUnits = cachedGameState->units();
|
||||
const auto *cachedHexMap = cachedGameState->hex_map();
|
||||
const int16_t cachedRowCount = cachedHexMap->row_count();
|
||||
const int16_t cachedColumnCount = cachedHexMap->column_count();
|
||||
const int cachedCurrentRound = gameStateRawPtr->current_round();
|
||||
const int cachedCurrentRound = cachedGameState->current_round();
|
||||
|
||||
bool isLateGame = cachedCurrentRound > 18; // Inline IsLateGame for efficiency
|
||||
|
||||
@@ -402,7 +289,7 @@ auto AttackerUnitsScore(
|
||||
auto occupants = Occupants(*cachedUnits, cachedRowCount, cachedColumnCount);
|
||||
|
||||
for (const Unit *unit : *cachedUnits) {
|
||||
const auto *pi = PlayerInfoForPid(gameState, unit->player_id());
|
||||
const auto *pi = PlayerInfoForPid(cachedGameState, unit->player_id());
|
||||
if (pi == nullptr) continue;
|
||||
|
||||
switch (unit->status()) {
|
||||
@@ -571,7 +458,9 @@ auto AttackerUnitsScore(
|
||||
|
||||
// If the best we can do puts us very close to the enemy, and the unit is almost
|
||||
// destroyed, return a negative value; better to flee
|
||||
if (unit->can_flee() && closestDistanceToEnemy < 5 && unit->battalion().size() < 10) {
|
||||
if (unit->can_flee() && closestDistanceToEnemy < 5 &&
|
||||
closestDistanceToEnemy != ActionPointDistances::IMPOSSIBLE &&
|
||||
unit->battalion().size() < 10) {
|
||||
distanceMultiplier = -1;
|
||||
} else {
|
||||
DIST_T closestDistanceToFriendly = 1;
|
||||
@@ -621,13 +510,12 @@ auto AttackerUnitsScore(
|
||||
return attackerUnitsValue - defenderUnitsValue;
|
||||
}
|
||||
|
||||
auto FleeStrategyScoreForState(const GameStateW &gameState, const PlayerId playerId) -> ScoreValue {
|
||||
auto AIScoreCalculator::FleeStrategyScoreForState(
|
||||
const GameStateW &gameState,
|
||||
const PlayerId playerId) -> ScoreValue {
|
||||
ScoreValue scoreValue = 0.0;
|
||||
|
||||
const auto *gameStatePtr = gameState.Get();
|
||||
const auto *units = gameStatePtr->units();
|
||||
|
||||
for (const auto *unit : *units) {
|
||||
for (const auto *unit : *gameState->units()) {
|
||||
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
|
||||
|
||||
if (unit->player_id() == playerId &&
|
||||
@@ -644,31 +532,27 @@ auto FleeStrategyScoreForState(const GameStateW &gameState, const PlayerId playe
|
||||
return scoreValue;
|
||||
}
|
||||
|
||||
auto DefenderScatterStrategyScoreForState(
|
||||
auto AIScoreCalculator::DefenderScatterStrategyScoreForState(
|
||||
const GameStateW &gameState,
|
||||
const int roundsRemaining,
|
||||
const SettingsGetter &settings,
|
||||
const ALCache &alCache,
|
||||
const APDCache &apdCache) -> ScoreValue {
|
||||
const auto *gameStatePtr = gameState.Get();
|
||||
const auto *status = gameStatePtr->status();
|
||||
|
||||
if (status->state() == net::eagle0::shardok::storage::fb::GameStatus_::State_VICTORY) {
|
||||
const auto *winningIds = status->winning_shardok_ids();
|
||||
const auto *playerInfos = gameStatePtr->player_infos();
|
||||
|
||||
for (const PlayerId winningPid : *winningIds) {
|
||||
if (gameState->status()->state() ==
|
||||
net::eagle0::shardok::storage::fb::GameStatus_::State_VICTORY) {
|
||||
for (const PlayerId winningPid : *gameState->status()->winning_shardok_ids()) {
|
||||
if (winningPid < 0) continue;
|
||||
if (playerInfos->Get(winningPid)->is_defender()) return INT_MAX;
|
||||
if (gameState->player_infos()->Get(winningPid)->is_defender()) return INT_MAX;
|
||||
return INT_MIN;
|
||||
}
|
||||
return INT_MAX;
|
||||
}
|
||||
if (status->state() == net::eagle0::shardok::storage::fb::GameStatus_::State_DRAW) { return 0; }
|
||||
if (gameState->status()->state() ==
|
||||
net::eagle0::shardok::storage::fb::GameStatus_::State_DRAW) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto *hexMap = gameStatePtr->hex_map();
|
||||
|
||||
const auto mapId = ActionPointDistancesCache::GetMapId(hexMap);
|
||||
const auto mapId = ActionPointDistancesCache::GetMapId(gameState->hex_map());
|
||||
|
||||
const auto unitsTotal = -AttackerUnitsScore(
|
||||
gameState,
|
||||
@@ -684,7 +568,7 @@ auto DefenderScatterStrategyScoreForState(
|
||||
return unitsTotal;
|
||||
}
|
||||
|
||||
auto DefenderHoldCastlesStrategyScoreForState(
|
||||
auto AIScoreCalculator::DefenderHoldCastlesStrategyScoreForState(
|
||||
const GameStateW &gameState,
|
||||
const CoordsSet &castleCoords,
|
||||
const int roundsRemaining,
|
||||
@@ -724,7 +608,7 @@ auto DefenderHoldCastlesStrategyScoreForState(
|
||||
return UNITS_BASE_MULTIPLIER * unitsMultiplier * unitsTotal + victoryConditionTotal;
|
||||
}
|
||||
|
||||
auto DefenderScoreForState(
|
||||
auto AIScoreCalculator::DefenderScoreForState(
|
||||
const GameStateW &gameState,
|
||||
const AIStrategy &defenderStrategy,
|
||||
const CoordsSet &castleCoords,
|
||||
@@ -780,7 +664,7 @@ auto DefenderScoreForState(
|
||||
throw ShardokInternalErrorException("Escaped AIStrategy switch");
|
||||
}
|
||||
|
||||
auto AttackerScoreForState(
|
||||
auto AIScoreCalculator::AttackerScoreForState(
|
||||
const GameStateW &gameState,
|
||||
const AIStrategy &attackerStrategy,
|
||||
const CoordsSet &castleCoords,
|
||||
@@ -908,102 +792,47 @@ void PrintCommand(
|
||||
printf("u%f\n", utility);
|
||||
}
|
||||
|
||||
auto BasicLookaheadCalculator(
|
||||
auto AIScoreCalculator::BasicLookaheadCalculator(
|
||||
const PlayerId pid,
|
||||
const bool isDefender,
|
||||
const int remainingLookahead,
|
||||
const int maxRepeatCount,
|
||||
const ShardokEngine innerEngine,
|
||||
const shared_ptr<ShardokEngine> &innerEngine,
|
||||
const ScoreValue currentUtility,
|
||||
const AIStrategy &attackerStrategy,
|
||||
const SettingsGetter &settingsGetter,
|
||||
const CoordsSet &allCastleCoords,
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
std::chrono::steady_clock::time_point deadline) -> std::future<ScoreValue> {
|
||||
// Check transposition table before expensive computation
|
||||
auto cachedScore =
|
||||
g_transpositionTable.probe(innerEngine.GetCurrentGameState(), remainingLookahead, pid);
|
||||
|
||||
if (cachedScore.has_value()) {
|
||||
// Return cached result immediately
|
||||
std::promise<ScoreValue> p;
|
||||
p.set_value(*cachedScore);
|
||||
return p.get_future();
|
||||
}
|
||||
const AITimeBudget *timeBudget) -> ScoreValue {
|
||||
const auto nextUtility = currentUtility;
|
||||
|
||||
// Check if we've reached the depth limit before making recursive calls
|
||||
if (remainingLookahead <= 0) {
|
||||
// Store the current utility in the transposition table and return it
|
||||
// Note: Store with depth 1 since depth 0 indicates an empty entry in the transposition
|
||||
// table
|
||||
g_transpositionTable.store(innerEngine.GetCurrentGameState(), 1, pid, nextUtility);
|
||||
|
||||
std::promise<ScoreValue> p;
|
||||
p.set_value(nextUtility);
|
||||
return p.get_future();
|
||||
}
|
||||
|
||||
if (const CommandListSPtr nextCommands = innerEngine.GetAvailableCommandsForAIPlayer(pid);
|
||||
if (const CommandListSPtr nextCommands = innerEngine->GetAvailableCommandsForAIPlayer(pid);
|
||||
nextCommands && !nextCommands->empty()) {
|
||||
// Get the future from BestCommandIndex without calling .get()
|
||||
auto bestCommandFuture = BestCommandIndex(
|
||||
const auto [index, type, lookaheadScore, immediateScore] = BestCommandIndex(
|
||||
pid,
|
||||
isDefender,
|
||||
remainingLookahead - 1,
|
||||
maxRepeatCount,
|
||||
innerEngine,
|
||||
*innerEngine,
|
||||
attackerStrategy,
|
||||
nextUtility,
|
||||
settingsGetter,
|
||||
allCastleCoords,
|
||||
apdCache,
|
||||
alCache,
|
||||
deadline);
|
||||
timeBudget);
|
||||
|
||||
// Return a future that chains the best command evaluation
|
||||
return std::async(
|
||||
std::launch::deferred,
|
||||
[bestCommandFuture = std::move(bestCommandFuture),
|
||||
innerEngine,
|
||||
pid,
|
||||
nextUtility,
|
||||
remainingLookahead]() mutable -> ScoreValue {
|
||||
const auto [index, type, lookaheadScore, immediateScore] =
|
||||
bestCommandFuture.get();
|
||||
|
||||
ScoreValue resultScore;
|
||||
if (auto &nextCommand =
|
||||
innerEngine.GetAvailableCommandsForAIPlayer(pid)->at(index);
|
||||
nextCommand->GetCommandType() !=
|
||||
net::eagle0::shardok::common::END_TURN_COMMAND) {
|
||||
resultScore = immediateScore;
|
||||
} else {
|
||||
resultScore = nextUtility;
|
||||
}
|
||||
|
||||
// Store in transposition table before returning
|
||||
g_transpositionTable.store(
|
||||
innerEngine.GetCurrentGameState(),
|
||||
remainingLookahead,
|
||||
pid,
|
||||
resultScore);
|
||||
|
||||
return resultScore;
|
||||
});
|
||||
if (auto &nextCommand = innerEngine->GetAvailableCommandsForAIPlayer(pid)->at(index);
|
||||
nextCommand->GetCommandType() != net::eagle0::shardok::common::END_TURN_COMMAND) {
|
||||
return immediateScore;
|
||||
}
|
||||
}
|
||||
|
||||
// No commands available, store and return the current utility as a future
|
||||
g_transpositionTable
|
||||
.store(innerEngine.GetCurrentGameState(), remainingLookahead, pid, nextUtility);
|
||||
|
||||
std::promise<ScoreValue> p;
|
||||
p.set_value(nextUtility);
|
||||
return p.get_future();
|
||||
return nextUtility;
|
||||
}
|
||||
|
||||
auto CalcOne(
|
||||
auto AIScoreCalculator::CalcOne(
|
||||
PlayerId pid,
|
||||
bool isDefender,
|
||||
uint32_t commandIndex,
|
||||
@@ -1016,25 +845,15 @@ auto CalcOne(
|
||||
const CoordsSet &allCastleCoords,
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
std::chrono::steady_clock::time_point deadline) -> ImmediateAndLookaheadScore {
|
||||
const AITimeBudget *timeBudget) -> ImmediateAndLookaheadScore {
|
||||
ImmediateAndLookaheadScore returnValue{};
|
||||
|
||||
// Check if we've exceeded the deadline
|
||||
if (std::chrono::steady_clock::now() > deadline) {
|
||||
// Return with a default score and an empty future that resolves immediately
|
||||
std::promise<ScoreValue> p;
|
||||
p.set_value(0.0); // Default timeout score
|
||||
returnValue.immediateScore = 0.0;
|
||||
returnValue.lookaheadScore = p.get_future();
|
||||
return returnValue;
|
||||
}
|
||||
auto innerEngine = std::make_shared<ShardokEngine>(guessedEngine, false);
|
||||
innerEngine->PostCommand(pid, commandIndex, randomGenerator);
|
||||
|
||||
auto innerEngine = ShardokEngine(guessedEngine, false);
|
||||
innerEngine.PostCommand(pid, commandIndex, randomGenerator);
|
||||
|
||||
auto innerUtility = AIScoreCalculator::GuessedStateScore(
|
||||
auto innerUtility = GuessedStateScore(
|
||||
isDefender,
|
||||
innerEngine.GetCurrentGameState(),
|
||||
innerEngine->GetCurrentGameState(),
|
||||
attackerStrategy,
|
||||
allCastleCoords,
|
||||
settingsGetter,
|
||||
@@ -1044,9 +863,10 @@ auto CalcOne(
|
||||
returnValue.immediateScore = innerUtility;
|
||||
|
||||
if (remainingLookahead <= 0) {
|
||||
std::promise<ScoreValue> p;
|
||||
returnValue.lookaheadScore = p.get_future();
|
||||
p.set_value(innerUtility);
|
||||
// Create a promise that will be moved, not destroyed
|
||||
auto p = std::make_shared<std::promise<eagle0::common::TaskResult<ScoreValue>>>();
|
||||
returnValue.lookaheadScore = p->get_future();
|
||||
p->set_value(eagle0::common::TaskResult<ScoreValue>(innerUtility));
|
||||
} else {
|
||||
auto lookaheadLambda = [pid,
|
||||
isDefender,
|
||||
@@ -1059,8 +879,8 @@ auto CalcOne(
|
||||
&allCastleCoords,
|
||||
&apdCache,
|
||||
&alCache,
|
||||
deadline]() -> ScoreValue {
|
||||
auto lookaheadFuture = BasicLookaheadCalculator(
|
||||
timeBudget]() -> ScoreValue {
|
||||
return BasicLookaheadCalculator(
|
||||
pid,
|
||||
isDefender,
|
||||
remainingLookahead,
|
||||
@@ -1072,25 +892,35 @@ auto CalcOne(
|
||||
allCastleCoords,
|
||||
apdCache,
|
||||
alCache,
|
||||
deadline);
|
||||
return lookaheadFuture.get();
|
||||
timeBudget);
|
||||
};
|
||||
|
||||
#if MULTITHREAD
|
||||
auto launchPolicy = remainingLookahead == 1 ? std::launch::async : std::launch::deferred;
|
||||
returnValue.lookaheadScore = std::async(launchPolicy, lookaheadLambda);
|
||||
// Priority = remainingLookahead (higher depth = higher priority)
|
||||
const int priority = remainingLookahead;
|
||||
|
||||
if (timeBudget && timeBudget->remainingBudget.count() > 0) {
|
||||
const auto now = eagle0::common::ThreadPool::Clock::now();
|
||||
const auto timeForThisDepth = timeBudget->remainingBudget / (remainingLookahead + 1);
|
||||
const auto deadline = now + timeForThisDepth;
|
||||
|
||||
returnValue.lookaheadScore =
|
||||
threadPool.enqueue_with_deadline(lookaheadLambda, priority, deadline);
|
||||
} else {
|
||||
returnValue.lookaheadScore = threadPool.enqueue(lookaheadLambda, priority);
|
||||
}
|
||||
#else
|
||||
std::promise<ScoreValue> p;
|
||||
returnValue.lookaheadScore = p.get_future();
|
||||
auto p = std::make_shared<std::promise<eagle0::common::TaskResult<ScoreValue>>>();
|
||||
returnValue.lookaheadScore = p->get_future();
|
||||
auto lambdaResult = lookaheadLambda();
|
||||
p.set_value(lambdaResult);
|
||||
p->set_value(eagle0::common::TaskResult<ScoreValue>(lambdaResult));
|
||||
#endif
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto BestCommandIndex(
|
||||
[[nodiscard]] auto AIScoreCalculator::BestCommandIndex(
|
||||
const PlayerId pid,
|
||||
const bool isDefender,
|
||||
const int remainingLookahead,
|
||||
@@ -1102,7 +932,7 @@ auto CalcOne(
|
||||
const CoordsSet &allCastleCoords,
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
std::chrono::steady_clock::time_point deadline) -> std::future<IndexAndScore> {
|
||||
const AITimeBudget *timeBudget) -> IndexAndScore {
|
||||
const CommandListSPtr guessedDescriptors = guessedEngine.GetAvailableCommandsForAIPlayer(pid);
|
||||
|
||||
// Filter out obviously bad commands to reduce search space
|
||||
@@ -1119,14 +949,12 @@ auto CalcOne(
|
||||
double minDistToEnemies = std::numeric_limits<double>::max();
|
||||
const auto *units = gameState->units();
|
||||
|
||||
for (size_t i = 0; i < units->size(); ++i) {
|
||||
if (const auto *playerUnit = units->Get(static_cast<unsigned int>(i));
|
||||
playerUnit->player_id() == pid) {
|
||||
for (int i = 0; i < units->size(); ++i) {
|
||||
if (const auto *playerUnit = units->Get(i); playerUnit->player_id() == pid) {
|
||||
const auto &playerCoords = playerUnit->location();
|
||||
|
||||
for (size_t j = 0; j < units->size(); ++j) {
|
||||
if (const auto *enemyUnit = units->Get(static_cast<unsigned int>(j));
|
||||
enemyUnit->player_id() != pid) {
|
||||
for (int j = 0; j < units->size(); ++j) {
|
||||
if (const auto *enemyUnit = units->Get(j); enemyUnit->player_id() != pid) {
|
||||
const auto &enemyCoords = enemyUnit->location();
|
||||
|
||||
// Proper hex distance calculation using cube coordinates
|
||||
@@ -1163,29 +991,24 @@ auto CalcOne(
|
||||
|
||||
const auto commandCount = filteredIndices.size();
|
||||
|
||||
// Structure to hold all command evaluation data
|
||||
struct CommandEvaluation {
|
||||
size_t index;
|
||||
CommandType type;
|
||||
ScoreValue immediateScore;
|
||||
std::vector<std::future<ScoreValue>> lookaheadFutures;
|
||||
};
|
||||
vector<IndexAndScore> allIndices(commandCount);
|
||||
|
||||
std::vector<CommandEvaluation> commandEvaluations(commandCount);
|
||||
// Primary index is the command index; vector may contain repeated attempts
|
||||
vector<vector<future<eagle0::common::TaskResult<ScoreValue>>>> scoreFutures(commandCount);
|
||||
|
||||
for (uint32_t index = 0; index < commandCount; index++) {
|
||||
const auto originalIndex = filteredIndices[index];
|
||||
const auto &guessedDescriptor = guessedDescriptors->at(originalIndex);
|
||||
const auto guessedCommandType = guessedDescriptor->GetCommandType();
|
||||
|
||||
commandEvaluations[index].index = originalIndex;
|
||||
commandEvaluations[index].type = guessedCommandType;
|
||||
allIndices[index].index = originalIndex;
|
||||
allIndices[index].type = guessedCommandType;
|
||||
|
||||
if (guessedCommandType == net::eagle0::shardok::common::END_TURN_COMMAND) {
|
||||
std::promise<ScoreValue> p;
|
||||
commandEvaluations[index].lookaheadFutures.push_back(p.get_future());
|
||||
p.set_value(currentUtility);
|
||||
commandEvaluations[index].immediateScore = currentUtility;
|
||||
std::promise<eagle0::common::TaskResult<ScoreValue>> p;
|
||||
scoreFutures[index].push_back(p.get_future());
|
||||
p.set_value(eagle0::common::TaskResult<ScoreValue>(currentUtility));
|
||||
allIndices[index].immediateScore = currentUtility;
|
||||
} else if (IsDeterministic(guessedCommandType)) {
|
||||
auto [immediateScore, lookaheadScore] =
|
||||
CalcOne(pid,
|
||||
@@ -1200,10 +1023,10 @@ auto CalcOne(
|
||||
allCastleCoords,
|
||||
apdCache,
|
||||
alCache,
|
||||
deadline);
|
||||
timeBudget);
|
||||
|
||||
commandEvaluations[index].immediateScore = immediateScore;
|
||||
commandEvaluations[index].lookaheadFutures.push_back(std::move(lookaheadScore));
|
||||
allIndices[index].immediateScore = immediateScore;
|
||||
scoreFutures[index].push_back(std::move(lookaheadScore));
|
||||
} else if (guessedDescriptor->HasOdds()) {
|
||||
const auto successChancePercentile = guessedDescriptor->GetOddsPercentile();
|
||||
const double successChance = static_cast<double>(successChancePercentile) / 100.0;
|
||||
@@ -1224,7 +1047,7 @@ auto CalcOne(
|
||||
allCastleCoords,
|
||||
apdCache,
|
||||
alCache,
|
||||
deadline);
|
||||
timeBudget);
|
||||
|
||||
// second attempt uses the average of (1 - successChance) and 0 as the roll (so 30%
|
||||
// chance -> rolling 15)
|
||||
@@ -1242,17 +1065,21 @@ auto CalcOne(
|
||||
allCastleCoords,
|
||||
apdCache,
|
||||
alCache,
|
||||
deadline);
|
||||
timeBudget);
|
||||
|
||||
commandEvaluations[index].immediateScore =
|
||||
allIndices[index].immediateScore =
|
||||
std::lerp(failureImmediateScore, successImmediateScore, successChance);
|
||||
|
||||
auto successSF = successLookaheadScore.share();
|
||||
auto failureSF = failureLookaheadScore.share();
|
||||
commandEvaluations[index].lookaheadFutures.push_back(std::async(
|
||||
scoreFutures[index].push_back(std::async(
|
||||
std::launch::deferred,
|
||||
[successSF, failureSF, successChance]() -> double {
|
||||
return std::lerp(failureSF.get(), successSF.get(), successChance);
|
||||
[successSF, failureSF, successChance]() -> eagle0::common::TaskResult<double> {
|
||||
auto successResult = successSF.get();
|
||||
auto failureResult = failureSF.get();
|
||||
double finalScore =
|
||||
std::lerp(failureResult.value, successResult.value, successChance);
|
||||
return eagle0::common::TaskResult<double>(finalScore);
|
||||
}));
|
||||
} else {
|
||||
ScoreValue sum = 0.0;
|
||||
@@ -1274,46 +1101,66 @@ auto CalcOne(
|
||||
allCastleCoords,
|
||||
apdCache,
|
||||
alCache,
|
||||
deadline);
|
||||
timeBudget);
|
||||
|
||||
sum += immediateScore;
|
||||
commandEvaluations[index].lookaheadFutures.push_back(std::move(lookaheadScore));
|
||||
scoreFutures[index].push_back(std::move(lookaheadScore));
|
||||
}
|
||||
commandEvaluations[index].immediateScore = sum / maxRepeatCount;
|
||||
allIndices[index].immediateScore = sum / maxRepeatCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a future that will wait for all evaluations and find the best one
|
||||
return std::async(
|
||||
std::launch::deferred,
|
||||
[commandEvaluations = std::move(commandEvaluations)]() mutable -> IndexAndScore {
|
||||
std::vector<IndexAndScore> allResults;
|
||||
allResults.reserve(commandEvaluations.size());
|
||||
printf("AIScoreCalculator: BestCommandIndex starting with %zu commands, budget: %lldms\n",
|
||||
(size_t)commandCount,
|
||||
timeBudget ? timeBudget->remainingBudget.count() : -1LL);
|
||||
|
||||
// Wait for all futures and compute final scores
|
||||
for (auto &eval : commandEvaluations) {
|
||||
ScoreValue totalLookaheadScore = 0.0;
|
||||
for (auto &future : eval.lookaheadFutures) {
|
||||
totalLookaheadScore += future.get();
|
||||
}
|
||||
ScoreValue avgLookaheadScore =
|
||||
eval.lookaheadFutures.empty()
|
||||
? eval.immediateScore
|
||||
: totalLookaheadScore / eval.lookaheadFutures.size();
|
||||
for (uint32_t i = 0; i < commandCount; i++) {
|
||||
ScoreValue total = 0.0;
|
||||
size_t validResults = 0;
|
||||
|
||||
allResults.push_back(IndexAndScore{
|
||||
.index = eval.index,
|
||||
.type = eval.type,
|
||||
.lookaheadScore = avgLookaheadScore,
|
||||
.immediateScore = eval.immediateScore});
|
||||
}
|
||||
// Find the best command using the existing sorter
|
||||
auto bestIt = std::ranges::max_element(allResults, CommandSorter);
|
||||
return *bestIt;
|
||||
});
|
||||
for (size_t j = 0; j < scoreFutures[i].size(); j++) {
|
||||
// Calculate timeout based on remaining time budget with small buffer
|
||||
auto timeout = std::chrono::steady_clock::now() +
|
||||
std::chrono::milliseconds(100); // Default fallback
|
||||
if (timeBudget && timeBudget->remainingBudget.count() > 0) {
|
||||
timeout = std::chrono::steady_clock::now() + timeBudget->remainingBudget +
|
||||
std::chrono::milliseconds(100);
|
||||
}
|
||||
|
||||
auto future_status = scoreFutures[i][j].wait_until(timeout);
|
||||
|
||||
if (future_status == std::future_status::timeout) {
|
||||
printf("AIScoreCalculator: TIMEOUT on command %u, future %zu (budget: %lldms)\n",
|
||||
i,
|
||||
j,
|
||||
timeBudget ? timeBudget->remainingBudget.count() : -1LL);
|
||||
// Future didn't complete within timeout - treat as deadline exceeded
|
||||
// Don't increment validResults so we fall back to immediate score if needed
|
||||
continue; // Skip this result
|
||||
}
|
||||
|
||||
auto result = scoreFutures[i][j].get();
|
||||
if (result.succeeded()) {
|
||||
total += result.value;
|
||||
validResults++;
|
||||
} else if (result.deadlineExceeded()) {
|
||||
// For deadline exceeded, we skip the result and rely on other futures
|
||||
// or fallback to immediate score if all tasks failed
|
||||
}
|
||||
}
|
||||
|
||||
if (validResults > 0) {
|
||||
allIndices[i].lookaheadScore = total / static_cast<ScoreValue>(validResults);
|
||||
} else {
|
||||
// All tasks exceeded deadline, fall back to immediate score
|
||||
allIndices[i].lookaheadScore = allIndices[i].immediateScore;
|
||||
}
|
||||
}
|
||||
|
||||
return *std::ranges::max_element(allIndices, CommandSorter);
|
||||
}
|
||||
|
||||
auto EvaluateCommand(
|
||||
auto AIScoreCalculator::EvaluateCommand(
|
||||
const PlayerId pid,
|
||||
const bool isDefender,
|
||||
const uint32_t commandIndex,
|
||||
@@ -1326,22 +1173,16 @@ auto EvaluateCommand(
|
||||
const CoordsSet &allCastleCoords,
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
std::chrono::steady_clock::time_point deadline) -> CommandEvaluationResult {
|
||||
const AITimeBudget *timeBudget) -> CommandEvaluationResult {
|
||||
const CommandListSPtr guessedDescriptors = guessedEngine.GetAvailableCommandsForAIPlayer(pid);
|
||||
|
||||
if (commandIndex >= guessedDescriptors->size()) {
|
||||
std::promise<ScoreValue> p;
|
||||
p.set_value(currentUtility);
|
||||
return {currentUtility, p.get_future()};
|
||||
}
|
||||
if (commandIndex >= guessedDescriptors->size()) { return {currentUtility, currentUtility}; }
|
||||
|
||||
const auto &guessedDescriptor = guessedDescriptors->at(commandIndex);
|
||||
|
||||
if (const auto guessedCommandType = guessedDescriptor->GetCommandType();
|
||||
guessedCommandType == net::eagle0::shardok::common::END_TURN_COMMAND) {
|
||||
std::promise<ScoreValue> p;
|
||||
p.set_value(currentUtility);
|
||||
return {currentUtility, p.get_future()};
|
||||
return {currentUtility, currentUtility};
|
||||
} else if (IsDeterministic(guessedCommandType)) {
|
||||
auto [immediateScore, lookaheadScore] =
|
||||
CalcOne(pid,
|
||||
@@ -1356,8 +1197,15 @@ auto EvaluateCommand(
|
||||
allCastleCoords,
|
||||
apdCache,
|
||||
alCache,
|
||||
deadline);
|
||||
return {immediateScore, std::move(lookaheadScore)};
|
||||
timeBudget);
|
||||
// Use timeout to avoid hanging on future (conservative timeout for EvaluateCommand)
|
||||
auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||
if (lookaheadScore.wait_until(timeout) == std::future_status::timeout) {
|
||||
return {immediateScore, immediateScore}; // Fall back to immediate score
|
||||
}
|
||||
auto lookaheadResult = lookaheadScore.get();
|
||||
return {immediateScore,
|
||||
lookaheadResult.succeeded() ? lookaheadResult.value : immediateScore};
|
||||
} else if (guessedDescriptor->HasOdds()) {
|
||||
const auto successChancePercentile = guessedDescriptor->GetOddsPercentile();
|
||||
const double successChance = static_cast<double>(successChancePercentile) / 100.0;
|
||||
@@ -1376,7 +1224,7 @@ auto EvaluateCommand(
|
||||
allCastleCoords,
|
||||
apdCache,
|
||||
alCache,
|
||||
deadline);
|
||||
timeBudget);
|
||||
|
||||
// Failure attempt
|
||||
auto [failureImmediateScore, failureLookaheadScore] = CalcOne(
|
||||
@@ -1392,23 +1240,31 @@ auto EvaluateCommand(
|
||||
allCastleCoords,
|
||||
apdCache,
|
||||
alCache,
|
||||
deadline);
|
||||
timeBudget);
|
||||
|
||||
// Return weighted average of success and failure
|
||||
auto successSF = successLookaheadScore.share();
|
||||
auto failureSF = failureLookaheadScore.share();
|
||||
// Use timeout to avoid hanging on futures (conservative timeout for EvaluateCommand)
|
||||
auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||
|
||||
ScoreValue failureLookahead = failureImmediateScore; // Default fallback
|
||||
if (failureLookaheadScore.wait_until(timeout) != std::future_status::timeout) {
|
||||
auto failureResult = failureLookaheadScore.get();
|
||||
failureLookahead =
|
||||
failureResult.succeeded() ? failureResult.value : failureImmediateScore;
|
||||
}
|
||||
|
||||
ScoreValue successLookahead = successImmediateScore; // Default fallback
|
||||
if (successLookaheadScore.wait_until(timeout) != std::future_status::timeout) {
|
||||
auto successResult = successLookaheadScore.get();
|
||||
successLookahead =
|
||||
successResult.succeeded() ? successResult.value : successImmediateScore;
|
||||
}
|
||||
return {std::lerp(failureImmediateScore, successImmediateScore, successChance),
|
||||
std::async(
|
||||
std::launch::deferred,
|
||||
[successSF, failureSF, successChance]() -> double {
|
||||
return std::lerp(failureSF.get(), successSF.get(), successChance);
|
||||
})};
|
||||
std::lerp(failureLookahead, successLookahead, successChance)};
|
||||
} else {
|
||||
// For non-deterministic commands without odds, use multiple attempts
|
||||
ScoreValue totalImmediateScore = 0.0;
|
||||
std::vector<std::future<ScoreValue>> lookaheadFutures;
|
||||
lookaheadFutures.reserve(maxRepeatCount);
|
||||
|
||||
ScoreValue totalLookaheadScore = 0.0;
|
||||
for (int repeatIteration = 0; repeatIteration < maxRepeatCount; repeatIteration++) {
|
||||
auto sequence = std::vector{
|
||||
static_cast<double>(repeatIteration) / static_cast<double>(maxRepeatCount - 1)};
|
||||
@@ -1425,22 +1281,21 @@ auto EvaluateCommand(
|
||||
allCastleCoords,
|
||||
apdCache,
|
||||
alCache,
|
||||
deadline);
|
||||
timeBudget);
|
||||
|
||||
totalImmediateScore += immediateScore;
|
||||
lookaheadFutures.push_back(std::move(lookaheadScore));
|
||||
}
|
||||
|
||||
// Return a future that computes the average when needed
|
||||
return {totalImmediateScore / maxRepeatCount,
|
||||
std::async(
|
||||
std::launch::deferred,
|
||||
[lookaheadFutures = std::move(lookaheadFutures),
|
||||
maxRepeatCount]() mutable -> double {
|
||||
ScoreValue total = 0.0;
|
||||
for (auto &future : lookaheadFutures) { total += future.get(); }
|
||||
return total / maxRepeatCount;
|
||||
})};
|
||||
// Use timeout to avoid hanging on future (conservative timeout for EvaluateCommand)
|
||||
auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||
if (lookaheadScore.wait_until(timeout) == std::future_status::timeout) {
|
||||
totalLookaheadScore += immediateScore; // Fall back to immediate score
|
||||
} else {
|
||||
auto lookaheadResult = lookaheadScore.get();
|
||||
totalLookaheadScore +=
|
||||
lookaheadResult.succeeded() ? lookaheadResult.value : immediateScore;
|
||||
}
|
||||
}
|
||||
return {totalImmediateScore / maxRepeatCount, totalLookaheadScore / maxRepeatCount};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1457,8 +1312,8 @@ auto EvaluateCommand(
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
const size_t commandIndex,
|
||||
std::chrono::steady_clock::time_point deadline) -> std::future<ScoreValue> {
|
||||
auto result = EvaluateCommand(
|
||||
const AITimeBudget *timeBudget) -> ScoreValue {
|
||||
const auto result = EvaluateCommand(
|
||||
pid,
|
||||
isDefender,
|
||||
commandIndex,
|
||||
@@ -1471,8 +1326,8 @@ auto EvaluateCommand(
|
||||
allCastleCoords,
|
||||
apdCache,
|
||||
alCache,
|
||||
deadline);
|
||||
return std::move(result.lookaheadScore);
|
||||
timeBudget);
|
||||
return result.lookaheadScore;
|
||||
}
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
#ifndef EAGLE0_AISCORECALCULATOR_HPP
|
||||
#define EAGLE0_AISCORECALCULATOR_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/common/ThreadPool.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIStrategy.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AITimeBudget.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCTypes.h"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistancesCache.hpp"
|
||||
@@ -30,9 +31,111 @@ using ScoreValue = double;
|
||||
using CommandProto = net::eagle0::shardok::api::CommandDescriptor;
|
||||
|
||||
class AIScoreCalculator {
|
||||
private:
|
||||
// Static thread pool with 32 threads for parallel AI calculations
|
||||
static inline eagle0::common::ThreadPool threadPool{32};
|
||||
|
||||
public:
|
||||
struct IndexAndScore {
|
||||
size_t index;
|
||||
CommandType type;
|
||||
ScoreValue lookaheadScore;
|
||||
ScoreValue immediateScore;
|
||||
};
|
||||
|
||||
private:
|
||||
[[nodiscard]] static auto DefenderScatterStrategyScoreForState(
|
||||
const GameStateW &gameState,
|
||||
int roundsRemaining,
|
||||
const SettingsGetter &settings,
|
||||
const ALCache &alCache,
|
||||
const APDCache &apdCache) -> ScoreValue;
|
||||
|
||||
[[nodiscard]] static auto DefenderHoldCastlesStrategyScoreForState(
|
||||
const GameStateW &gameState,
|
||||
const CoordsSet &castleCoords,
|
||||
int roundsRemaining,
|
||||
const SettingsGetter &settings,
|
||||
const ALCache &alCache,
|
||||
const APDCache &apdCache) -> ScoreValue;
|
||||
|
||||
[[nodiscard]] static auto FleeStrategyScoreForState(
|
||||
const GameStateW &gameState,
|
||||
PlayerId playerId) -> ScoreValue;
|
||||
|
||||
[[nodiscard]] static auto DefenderScoreForState(
|
||||
const GameStateW &gameState,
|
||||
const AIStrategy &defenderStrategy,
|
||||
const CoordsSet &castleCoords,
|
||||
int roundsRemaining,
|
||||
const SettingsGetter &settings,
|
||||
const ALCache &alCache,
|
||||
const APDCache &apdCache) -> ScoreValue;
|
||||
|
||||
[[nodiscard]] static auto AttackerScoreForState(
|
||||
const GameStateW &gameState,
|
||||
const AIStrategy &attackerStrategy,
|
||||
const CoordsSet &castleCoords,
|
||||
int roundsRemaining,
|
||||
const SettingsGetter &settings,
|
||||
const ALCache &alCache,
|
||||
const APDCache &apdCache) -> ScoreValue;
|
||||
|
||||
struct ImmediateAndLookaheadScore {
|
||||
ScoreValue immediateScore;
|
||||
future<eagle0::common::TaskResult<ScoreValue>> lookaheadScore;
|
||||
};
|
||||
|
||||
static auto BasicLookaheadCalculator(
|
||||
PlayerId pid,
|
||||
bool isDefender,
|
||||
int remainingLookahead,
|
||||
int maxRepeatCount,
|
||||
const shared_ptr<ShardokEngine> &innerEngine,
|
||||
ScoreValue currentUtility,
|
||||
const AIStrategy &attackerStrategy,
|
||||
const SettingsGetter &settingsGetter,
|
||||
const CoordsSet &allCastleCoords,
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
const AITimeBudget *timeBudget = nullptr) -> ScoreValue;
|
||||
|
||||
static auto CalcOne(
|
||||
PlayerId pid,
|
||||
bool isDefender,
|
||||
uint32_t commandIndex,
|
||||
int remainingLookahead,
|
||||
int maxRepeatCount,
|
||||
const std::shared_ptr<RandomGenerator> &randomGenerator,
|
||||
const ShardokEngine &guessedEngine,
|
||||
const AIStrategy &attackerStrategy,
|
||||
const SettingsGetter &settingsGetter,
|
||||
const CoordsSet &allCastleCoords,
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
const AITimeBudget *timeBudget = nullptr) -> ImmediateAndLookaheadScore;
|
||||
|
||||
struct CommandEvaluationResult {
|
||||
ScoreValue immediateScore;
|
||||
ScoreValue lookaheadScore;
|
||||
};
|
||||
|
||||
static auto EvaluateCommand(
|
||||
PlayerId pid,
|
||||
bool isDefender,
|
||||
uint32_t commandIndex,
|
||||
int remainingLookahead,
|
||||
int maxRepeatCount,
|
||||
const ShardokEngine &guessedEngine,
|
||||
const AIStrategy &attackerStrategy,
|
||||
ScoreValue currentUtility,
|
||||
const SettingsGetter &settingsGetter,
|
||||
const CoordsSet &allCastleCoords,
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
const AITimeBudget *timeBudget = nullptr) -> CommandEvaluationResult;
|
||||
|
||||
public:
|
||||
// Evaluate the score of a guessed game state based on the current AI strategy. DOES NOT perform
|
||||
// or evaluate any commands.
|
||||
[[nodiscard]] static auto GuessedStateScore(
|
||||
bool isDefender,
|
||||
const GameStateW &state,
|
||||
@@ -42,7 +145,20 @@ public:
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache) -> ScoreValue;
|
||||
|
||||
// Evaluates the score for a particular command index for the given player, using lookahead.
|
||||
[[nodiscard]] static auto BestCommandIndex(
|
||||
PlayerId pid,
|
||||
bool isDefender,
|
||||
int remainingLookahead,
|
||||
int maxRepeatCount,
|
||||
const ShardokEngine &guessedEngine,
|
||||
const AIStrategy &attackerStrategy,
|
||||
ScoreValue currentUtility,
|
||||
const SettingsGetter &settingsGetter,
|
||||
const CoordsSet &allCastleCoords,
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
const AITimeBudget *timeBudget = nullptr) -> IndexAndScore;
|
||||
|
||||
[[nodiscard]] static auto CommandScore(
|
||||
PlayerId pid,
|
||||
bool isDefender,
|
||||
@@ -56,7 +172,7 @@ public:
|
||||
const APDCache &apdCache,
|
||||
const ALCache &alCache,
|
||||
size_t commandIndex,
|
||||
std::chrono::steady_clock::time_point deadline) -> std::future<ScoreValue>;
|
||||
const AITimeBudget *timeBudget = nullptr) -> ScoreValue;
|
||||
};
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
@@ -16,7 +16,7 @@ auto HasAttachedHeroWithProfession(
|
||||
unit->attached_hero().profession_info().profession() == profession;
|
||||
}
|
||||
|
||||
auto CastleClaimCapableAttackerUnitCount(const GameStateW &gameState) -> int {
|
||||
auto CastleClaimCapableAttackerUnitCount(const GameState *gameState) -> int {
|
||||
int count = 0;
|
||||
|
||||
for (const auto *unit : *gameState->units()) {
|
||||
@@ -32,7 +32,7 @@ auto CastleClaimCapableAttackerUnitCount(const GameStateW &gameState) -> int {
|
||||
return count;
|
||||
}
|
||||
|
||||
auto PlayerInfoForPid(const GameStateW &gs, const PlayerId pid) -> const PlayerInfo * {
|
||||
auto PlayerInfoForPid(const GameState *gs, const PlayerId pid) -> const PlayerInfo * {
|
||||
if (gs->player_infos()) {
|
||||
for (const auto &pi : *gs->player_infos()) {
|
||||
if (pi->player_id() == pid) return pi;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCTypes.h"
|
||||
#include "src/main/flatbuffer/net/eagle0/shardok/storage/game_state.hpp"
|
||||
#include "src/main/flatbuffer/net/eagle0/shardok/storage/unit.hpp"
|
||||
@@ -26,8 +25,8 @@ auto HasAttachedHeroWithProfession(
|
||||
const Unit *unit,
|
||||
net::eagle0::shardok::storage::fb::Profession profession) -> bool;
|
||||
|
||||
auto CastleClaimCapableAttackerUnitCount(const GameStateW &gameState) -> int;
|
||||
auto PlayerInfoForPid(const GameStateW &, PlayerId pid) -> const PlayerInfo *;
|
||||
auto CastleClaimCapableAttackerUnitCount(const GameState *gameState) -> int;
|
||||
auto PlayerInfoForPid(const GameState *gs, PlayerId pid) -> const PlayerInfo *;
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ auto CalculateTimeBudget(
|
||||
bool isClose = false;
|
||||
const auto *units = state->units();
|
||||
|
||||
for (size_t i = 0; i < units->size() && !isClose; ++i) {
|
||||
const auto *myUnit = units->Get(static_cast<unsigned int>(i));
|
||||
for (int i = 0; i < units->size() && !isClose; ++i) {
|
||||
const auto *myUnit = units->Get(i);
|
||||
if (myUnit->player_id() != playerId) continue;
|
||||
|
||||
const auto &myCoords = myUnit->location();
|
||||
@@ -43,8 +43,8 @@ auto CalculateTimeBudget(
|
||||
const Cube myCube = OffsetToCube(myCoords);
|
||||
|
||||
// Check distance to enemy units
|
||||
for (size_t j = 0; j < units->size(); ++j) {
|
||||
const auto *enemyUnit = units->Get(static_cast<unsigned int>(j));
|
||||
for (int j = 0; j < units->size(); ++j) {
|
||||
const auto *enemyUnit = units->Get(j);
|
||||
if (enemyUnit->player_id() == playerId) continue;
|
||||
|
||||
const auto &enemyCoords = enemyUnit->location();
|
||||
@@ -80,7 +80,7 @@ auto CalculateTimeBudget(
|
||||
const auto remainingBudget = std::chrono::duration_cast<std::chrono::milliseconds>(budget);
|
||||
|
||||
// Get minimum depth requirement
|
||||
const size_t minDepth = settingsGetter.Backing().min_lookahead_turns();
|
||||
const int minDepth = settingsGetter.Backing().min_lookahead_turns();
|
||||
|
||||
return AITimeBudget{
|
||||
.remainingBudget = remainingBudget,
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
// Configuration structure for iterative deepening time budget
|
||||
struct AITimeBudget {
|
||||
std::chrono::milliseconds remainingBudget; // Time budget remaining (decremented as used)
|
||||
size_t minDepthRequired; // Minimum depth from minLookaheadTurns
|
||||
int minDepthRequired; // Minimum depth from minLookaheadTurns
|
||||
bool isCloseToEnemy; // Proximity flag for budget selection
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "AIUnitScoreCalculator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "AIAttackLocations.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
|
||||
@@ -99,7 +98,7 @@ auto ContextFreeUnitValue(const Unit *unit) -> ScoreValue {
|
||||
return battalionValue + heroValue;
|
||||
}
|
||||
|
||||
auto archeryValue(const Unit * /*unit*/) -> double {
|
||||
auto archeryValue(const Unit *unit) -> double {
|
||||
// TODO: make this depend on the value of the targets
|
||||
return kArcheryPossibleValue;
|
||||
}
|
||||
@@ -114,7 +113,7 @@ auto reduceValue(const Unit *unit, const Terrain *unitTerrain) -> double {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
auto fearValue(const Unit * /*unit*/) -> double {
|
||||
auto fearValue(const Unit *unit) -> double {
|
||||
// TODO: make this depend on the value of the targets
|
||||
return kFearPossibleValue;
|
||||
}
|
||||
@@ -343,8 +342,7 @@ auto UnitValue(
|
||||
unit->battalion().type() == net::eagle0::shardok::storage::fb::BattalionTypeId_UNDEAD;
|
||||
|
||||
const int coordsIndex = location.row() * map->column_count() + location.column();
|
||||
const auto *terrain = map->terrain()->Get(coordsIndex);
|
||||
|
||||
const auto &terrain = map->terrain()->Get(coordsIndex);
|
||||
double castleMultiplier = 1.0;
|
||||
// Only give a multiplier for being in a castle if the castle is useful, and the unit is not
|
||||
// undead
|
||||
@@ -360,8 +358,8 @@ auto UnitValue(
|
||||
{
|
||||
for (const auto adjacentCoords = HexMapUtils::GetAdjacentCoords(map, location);
|
||||
const auto &c : adjacentCoords) {
|
||||
if (const auto *adjTerrain = GetTerrain(map, c);
|
||||
adjTerrain && adjTerrain->modifier().fire().present()) {
|
||||
if (const auto &adjTerrain = GetTerrain(map, c);
|
||||
adjTerrain->modifier().fire().present()) {
|
||||
onFireMultiplier *= kAdjacentFireMultiplier;
|
||||
}
|
||||
}
|
||||
@@ -416,7 +414,7 @@ auto UnitValue(
|
||||
if (const auto commandingUnitId = unit->commanding_unit_id(); commandingUnitId != -1) {
|
||||
const Unit *commandingUnit = nullptr;
|
||||
for (const Unit *attackerUnit : attackerUnits) {
|
||||
if (attackerUnit && attackerUnit->unit_id() == commandingUnitId) {
|
||||
if (attackerUnit->unit_id() == commandingUnitId) {
|
||||
commandingUnit = attackerUnit;
|
||||
break;
|
||||
}
|
||||
@@ -424,7 +422,7 @@ auto UnitValue(
|
||||
|
||||
if (commandingUnit == nullptr) {
|
||||
for (const Unit *defenderUnit : defenderUnits) {
|
||||
if (defenderUnit && defenderUnit->unit_id() == commandingUnitId) {
|
||||
if (defenderUnit->unit_id() == commandingUnitId) {
|
||||
commandingUnit = defenderUnit;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
|
||||
#include "AIVictoryConditionScoreCalculator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "AIAttackLocations.hpp"
|
||||
#include "AIDistanceDebuf.hpp"
|
||||
#include "src/main/cpp/net/eagle0/common/ContainerUtils.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackGroups.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
|
||||
#include "src/main/flatbuffer/net/eagle0/shardok/storage/victory_condition.hpp"
|
||||
@@ -124,12 +122,12 @@ auto AttackerDebufForDefenderOccupiedCriticalTile(
|
||||
}
|
||||
|
||||
auto DefenderHoldsCriticalTilesVictoryScore(
|
||||
const GameStateW& gameState,
|
||||
const net::eagle0::shardok::storage::fb::GameState* gameState,
|
||||
const CoordsSet& criticalTileLocations,
|
||||
const PlayerInfo* player,
|
||||
const APDCache& /*apdCache*/,
|
||||
const ALCache& /*alCache*/,
|
||||
const SettingsGetter& /*settings*/) -> ScoreValue {
|
||||
const APDCache& apdCache,
|
||||
const ALCache& alCache,
|
||||
const SettingsGetter& settings) -> ScoreValue {
|
||||
ScoreValue total = 0.0;
|
||||
|
||||
const auto rc = gameState->hex_map()->row_count();
|
||||
@@ -154,7 +152,7 @@ auto DefenderHoldsCriticalTilesVictoryScore(
|
||||
}
|
||||
|
||||
auto AttackerHoldsCriticalTilesVictoryScore(
|
||||
const GameStateW& gameState,
|
||||
const net::eagle0::shardok::storage::fb::GameState* gameState,
|
||||
const CoordsSet& criticalTileLocations,
|
||||
const PlayerInfo* player,
|
||||
const APDCache& apdCache,
|
||||
@@ -248,12 +246,12 @@ auto AttackerHoldsCriticalTilesVictoryScore(
|
||||
}
|
||||
|
||||
auto LastPlayerStandingVictoryScore(
|
||||
const GameStateW& gameState,
|
||||
const GameState* gameState,
|
||||
const PlayerInfo* player,
|
||||
const APDCache& apdCache,
|
||||
const ALCache& alCache,
|
||||
const SettingsGetter& settings) -> ScoreValue {
|
||||
if (!std::ranges::contains(
|
||||
if (!common::Contains(
|
||||
*player->victory_conditions(),
|
||||
net::eagle0::shardok::storage::fb::
|
||||
VictoryCondition_VICTORY_CONDITION_LAST_PLAYER_STANDING)) {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackGroups.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistancesCache.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"
|
||||
@@ -24,7 +23,7 @@ using std::vector;
|
||||
using ScoreValue = double;
|
||||
|
||||
auto AttackerHoldsCriticalTilesVictoryScore(
|
||||
const GameStateW& gameState,
|
||||
const net::eagle0::shardok::storage::fb::GameState* gameState,
|
||||
const CoordsSet& criticalTileLocations,
|
||||
const PlayerInfo* player,
|
||||
const APDCache& apdCache,
|
||||
@@ -32,7 +31,7 @@ auto AttackerHoldsCriticalTilesVictoryScore(
|
||||
const SettingsGetter& settings) -> ScoreValue;
|
||||
|
||||
auto DefenderHoldsCriticalTilesVictoryScore(
|
||||
const GameStateW& gameState,
|
||||
const net::eagle0::shardok::storage::fb::GameState* gameState,
|
||||
const CoordsSet& criticalTileLocations,
|
||||
const PlayerInfo* player,
|
||||
const APDCache& apdCache,
|
||||
@@ -40,7 +39,7 @@ auto DefenderHoldsCriticalTilesVictoryScore(
|
||||
const SettingsGetter& settings) -> ScoreValue;
|
||||
|
||||
auto LastPlayerStandingVictoryScore(
|
||||
const GameStateW& gameState,
|
||||
const GameState* gameState,
|
||||
const PlayerInfo* player,
|
||||
const APDCache& apdCache,
|
||||
const ALCache& alCache,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace shardok {
|
||||
|
||||
auto UnitIdsRequiringWaterCrossing(
|
||||
const GameStateW &gameState,
|
||||
const GameState *gameState,
|
||||
const PlayerId pid,
|
||||
const CoordsSet &destinations,
|
||||
const APDCache &apdCache,
|
||||
@@ -74,9 +74,9 @@ auto UnitIdsRequiringWaterCrossing(
|
||||
}
|
||||
|
||||
auto UnitIdsToCreateWaterCrossing(
|
||||
const GameStateW &gameState,
|
||||
const GameState *gameState,
|
||||
const PlayerId pid,
|
||||
const APDCache & /*apdCache*/,
|
||||
const APDCache &apdCache,
|
||||
const SettingsGetter &settings) -> vector<UnitId> {
|
||||
vector<UnitId> unitIds{};
|
||||
|
||||
@@ -196,7 +196,7 @@ auto WaterCrossingTiles(
|
||||
|
||||
// Returns the set of tiles that the attacker should try to approach in order to bridge/freeze
|
||||
auto IntendedCrossingStarts(
|
||||
const GameStateW &gameState,
|
||||
const GameState *gameState,
|
||||
const vector<UnitId> &unitIdsCreatingCrossing,
|
||||
const CoordsSet &tilesToStartCrossingFrom,
|
||||
const MapId &mapId,
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#ifndef EAGLE0_AIWATERCROSSINGCALCULATOR_HPP
|
||||
#define EAGLE0_AIWATERCROSSINGCALCULATOR_HPP
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistancesCache.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"
|
||||
@@ -30,7 +29,7 @@ static inline void AssertValid(const Coords& c, const HexMap* hexMap) {
|
||||
|
||||
// Units that need a water crossing to reach at least one of the destinations
|
||||
auto UnitIdsRequiringWaterCrossing(
|
||||
const GameStateW& gameState,
|
||||
const GameState* gameState,
|
||||
PlayerId pid,
|
||||
const CoordsSet& destinations,
|
||||
const APDCache& apdCache,
|
||||
@@ -38,7 +37,7 @@ auto UnitIdsRequiringWaterCrossing(
|
||||
|
||||
// Units belonging to the player that are capable of creating water crossings
|
||||
auto UnitIdsToCreateWaterCrossing(
|
||||
const GameStateW& gameState,
|
||||
const GameState* gameState,
|
||||
PlayerId pid,
|
||||
const APDCache& apdCache,
|
||||
const SettingsGetter& settings) -> vector<UnitId>;
|
||||
@@ -68,7 +67,7 @@ auto WaterCrossingTiles(
|
||||
|
||||
// Returns the set of tiles that the attacker should try to approach in order to bridge/freeze
|
||||
auto IntendedCrossingStarts(
|
||||
const GameStateW& gameState,
|
||||
const GameState* gameState,
|
||||
const vector<UnitId>& unitIdsCreatingCrossing,
|
||||
const CoordsSet& tilesToStartCrossingFrom,
|
||||
const MapId& mapId,
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
|
||||
#include "AIWaterCrossingCommandChooser.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "AIMinimumDistanceAndTarget.hpp"
|
||||
#include "src/main/cpp/net/eagle0/common/ContainerUtils.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIWaterCrossingCalculator.hpp"
|
||||
|
||||
namespace shardok {
|
||||
@@ -19,10 +17,10 @@ constexpr ScoreValue kNoCrossingCreatorsScore = std::numeric_limits<ScoreValue>:
|
||||
|
||||
[[nodiscard]] auto AIWaterCrossingCommandChooser::WaterCrossingScore(
|
||||
const SettingsGetter &settingsGetter,
|
||||
const GameStateW &gameState,
|
||||
const GameState *gameState,
|
||||
const CoordsSet &castleCoords,
|
||||
const CoordsSet &startCrossingFrom) const -> ScoreValue {
|
||||
uint32_t castleClaimCount = 0;
|
||||
int castleClaimCount = 0;
|
||||
for (const auto *unit : *gameState->units()) {
|
||||
if (unit->player_id() != playerId) continue;
|
||||
const auto status = unit->status();
|
||||
@@ -85,7 +83,7 @@ constexpr ScoreValue kNoCrossingCreatorsScore = std::numeric_limits<ScoreValue>:
|
||||
// a large penalty
|
||||
for (const UnitId uid : unitIdsRequiringCrossing) {
|
||||
// If this unit ID can also create a crossing, we already handled it
|
||||
if (std::ranges::contains(unitIdsCreatingCrossing, uid)) continue;
|
||||
if (common::Contains(unitIdsCreatingCrossing, uid)) continue;
|
||||
|
||||
const Unit *unit = gameState->units()->Get(uid);
|
||||
const auto &battalionType = settingsGetter.GetBattalionType(unit->battalion().type());
|
||||
@@ -121,11 +119,11 @@ constexpr ScoreValue kNoCrossingCreatorsScore = std::numeric_limits<ScoreValue>:
|
||||
|
||||
auto AIWaterCrossingCommandChooser::StartCrossingFrom(
|
||||
const SettingsGetter &settingsGetter,
|
||||
const GameStateW &gameState,
|
||||
const GameState *gameState,
|
||||
const CoordsSet &castleCoords) const -> CoordsSet {
|
||||
CoordsSet startCrossingFrom(gameState->hex_map());
|
||||
|
||||
uint32_t castleClaimCount = 0;
|
||||
int castleClaimCount = 0;
|
||||
for (const auto *unit : *gameState->units()) {
|
||||
if (unit->player_id() != playerId) continue;
|
||||
const auto status = unit->status();
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistancesCache.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/FlatbufferWrapper.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
|
||||
@@ -35,12 +34,12 @@ public:
|
||||
|
||||
auto StartCrossingFrom(
|
||||
const SettingsGetter &settingsGetter,
|
||||
const GameStateW &gameState,
|
||||
const GameState *gameState,
|
||||
const CoordsSet &castleCoords) const -> CoordsSet;
|
||||
|
||||
[[nodiscard]] auto WaterCrossingScore(
|
||||
const SettingsGetter &settingsGetter,
|
||||
const GameStateW &gameState,
|
||||
const GameState *gameState,
|
||||
const CoordsSet &castleCoords,
|
||||
const CoordsSet &startCrossingFrom) const -> ScoreValue;
|
||||
};
|
||||
|
||||
@@ -11,7 +11,6 @@ cc_library(
|
||||
],
|
||||
deps = [
|
||||
":ai_attack_locations",
|
||||
":ai_flee_decision_calculator",
|
||||
":ai_score_utilities",
|
||||
":ai_strategy",
|
||||
":ai_water_crossing_command_chooser",
|
||||
@@ -71,7 +70,6 @@ cc_library(
|
||||
":ai_score_utilities",
|
||||
":ai_strategy",
|
||||
":ai_water_crossing_calculator",
|
||||
"//src/main/cpp/net/eagle0/shardok/library:game_state_w",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/map:coords_set",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/util:hex_map_utils",
|
||||
"//src/main/flatbuffer/net/eagle0/shardok/storage:game_state_cc_fbs",
|
||||
@@ -122,32 +120,12 @@ cc_library(
|
||||
"//src/test/cpp/net/eagle0/shardok/ai:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/cpp/net/eagle0/shardok/library:game_state_w",
|
||||
"//src/main/cpp/net/eagle0/shardok/library:shardok_c_types",
|
||||
"//src/main/flatbuffer/net/eagle0/shardok/storage:game_state_cc_fbs",
|
||||
"//src/main/flatbuffer/net/eagle0/shardok/storage:unit_cc_fbs",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ai_flee_decision_calculator",
|
||||
srcs = ["AIFleeDecisionCalculator.cpp"],
|
||||
hdrs = ["AIFleeDecisionCalculator.hpp"],
|
||||
copts = COPTS,
|
||||
visibility = [
|
||||
"//src/main/cpp/net/eagle0/shardok/ai_performance_runner:__pkg__",
|
||||
"//src/test/cpp/net/eagle0/shardok/ai:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
":ai_score_utilities",
|
||||
":ai_unit_score_calculator",
|
||||
"//src/main/cpp/net/eagle0/shardok/library:engine",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/settings:game_settings",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/util:hex_map_utils",
|
||||
"//src/main/protobuf/net/eagle0/shardok/api:command_descriptor_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ai_command_filter",
|
||||
srcs = ["AICommandFilter.cpp"],
|
||||
@@ -168,20 +146,6 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "transposition_table",
|
||||
srcs = ["TranspositionTable.cpp"],
|
||||
hdrs = ["TranspositionTable.hpp"],
|
||||
copts = COPTS,
|
||||
visibility = [
|
||||
"//src/main/cpp/net/eagle0/shardok/ai_performance_runner:__pkg__",
|
||||
"//src/test/cpp/net/eagle0/shardok/ai:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/cpp/net/eagle0/shardok/library:game_state_w",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ai_score_calculator",
|
||||
srcs = ["AIScoreCalculator.cpp"],
|
||||
@@ -194,10 +158,11 @@ cc_library(
|
||||
deps = [
|
||||
":ai_attacker_strategy_selector",
|
||||
":ai_command_filter",
|
||||
":ai_time_budget",
|
||||
":ai_unit_score_calculator",
|
||||
":ai_victory_condition_score_calculator",
|
||||
":transposition_table",
|
||||
"//src/main/cpp/net/eagle0/common:sequence_random_generator",
|
||||
"//src/main/cpp/net/eagle0/common:thread_pool",
|
||||
"//src/main/cpp/net/eagle0/shardok/library:engine",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/view_filters:game_state_guesser",
|
||||
],
|
||||
@@ -247,7 +212,6 @@ cc_library(
|
||||
":ai_attack_locations",
|
||||
":ai_distance_debuf",
|
||||
":ai_score_utilities",
|
||||
"//src/main/cpp/net/eagle0/shardok/library:game_state_w",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/action_point_distances",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/action_point_distances:action_point_distances_cache",
|
||||
"//src/main/flatbuffer/net/eagle0/shardok/storage:game_state_cc_fbs",
|
||||
@@ -265,7 +229,6 @@ cc_library(
|
||||
],
|
||||
deps = [
|
||||
":ai_minimum_distance_and_target",
|
||||
"//src/main/cpp/net/eagle0/shardok/library:game_state_w",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/action_point_distances",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/action_point_distances:action_point_distances_cache",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/fb_helpers:hex_map_helpers",
|
||||
@@ -283,7 +246,6 @@ cc_library(
|
||||
deps = [
|
||||
":ai_minimum_distance_and_target",
|
||||
":ai_water_crossing_calculator",
|
||||
"//src/main/cpp/net/eagle0/shardok/library:game_state_w",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/action_point_distances",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/action_point_distances:action_point_distances_cache",
|
||||
"//src/main/protobuf/net/eagle0/shardok/api:command_descriptor_cc_proto",
|
||||
@@ -339,7 +301,6 @@ cc_library(
|
||||
deps = [
|
||||
":ai_attacker_strategy_selector",
|
||||
":ai_defender_strategy_selector",
|
||||
":ai_flee_decision_calculator",
|
||||
":ai_iterative_deepening",
|
||||
":ai_score_calculator",
|
||||
":ai_time_budget",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#include "AIAttackerStrategySelector.hpp"
|
||||
#include "AIScoreCalculator.hpp"
|
||||
#include "TranspositionTable.hpp"
|
||||
#include "src/main/cpp/net/eagle0/common/TimeUtils.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
|
||||
|
||||
namespace shardok {
|
||||
@@ -43,11 +43,6 @@ auto IterativeDeepeningAI::IterativeSearch(
|
||||
const auto initialBudgetMs = initialBudget.remainingBudget;
|
||||
SearchResult result;
|
||||
|
||||
// Increment TT age for replacement strategy (new search)
|
||||
g_transpositionTable.incrementAge();
|
||||
|
||||
// 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 DEBUG_ITERATIVE_DEEPENING_TIMINGS
|
||||
printf("ID AI: Commands are empty, returning early\n");
|
||||
@@ -56,12 +51,11 @@ auto IterativeDeepeningAI::IterativeSearch(
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check if we're in SET_UP phase and enforce maximum depth limit
|
||||
// Check if we're in SET_UP phase
|
||||
bool isSetupPhase =
|
||||
(state->status()->state() ==
|
||||
net::eagle0::shardok::storage::fb::GameStatus_::State_SET_UP);
|
||||
// Limit depth to prevent thread pool exhaustion and keep search reasonable
|
||||
size_t maxDepth = isSetupPhase ? 2 : 8;
|
||||
int maxDepth = isSetupPhase ? 2 : std::numeric_limits<int>::max();
|
||||
|
||||
// Calculate current utility and create engine once for all command evaluations
|
||||
const auto& settingsGetter = settings->GetGetter();
|
||||
@@ -82,10 +76,10 @@ auto IterativeDeepeningAI::IterativeSearch(
|
||||
highestDepthCompleted.clear();
|
||||
highestDepthCompleted.resize(commands.size(), 0);
|
||||
|
||||
size_t currentDepth = 1;
|
||||
int currentDepth = 1;
|
||||
size_t previousBestCommand = 0; // Track best command from previous depth
|
||||
size_t evaluatedCountAtHighestDepth = 0;
|
||||
auto completionReason = EvaluationCompletionReason::RAN_OUT_OF_TIME;
|
||||
EvaluationCompletionReason completionReason = EvaluationCompletionReason::RAN_OUT_OF_TIME;
|
||||
|
||||
// Main iterative deepening loop
|
||||
while ((currentDepth == 1 || !IsTimeExpired(timeBudget)) && currentDepth <= maxDepth) {
|
||||
@@ -95,37 +89,27 @@ auto IterativeDeepeningAI::IterativeSearch(
|
||||
scoresByDepth,
|
||||
highestDepthCompleted);
|
||||
|
||||
size_t evaluatedCount = 0;
|
||||
int evaluatedCount = 0;
|
||||
bool allEvaluated = true;
|
||||
bool allEndTurnCommands = true; // Track if all commands are END_TURN
|
||||
|
||||
// Start all command evaluations for this depth
|
||||
std::vector<std::pair<size_t, std::future<SearchResult>>> futures;
|
||||
futures.reserve(sortedIndices.size());
|
||||
|
||||
// Try to evaluate all commands at this depth, within budget constraints
|
||||
for (size_t cmdIndex : sortedIndices) {
|
||||
if (currentDepth > 1 && IsTimeExpired(timeBudget)) {
|
||||
allEvaluated = false;
|
||||
break;
|
||||
}
|
||||
|
||||
auto future = SearchCommandAtDepthWithEngine(
|
||||
auto cmdResult = SearchCommandAtDepthWithEngine(
|
||||
guessedEngine,
|
||||
settingsGetter,
|
||||
maxRepeatCount,
|
||||
commands,
|
||||
cmdIndex,
|
||||
currentDepth, // Pass current iteration depth as desired search depth
|
||||
currentDepth,
|
||||
currentUtility,
|
||||
timeBudget);
|
||||
|
||||
futures.emplace_back(cmdIndex, std::move(future));
|
||||
}
|
||||
|
||||
// Now wait for all futures and collect results
|
||||
for (auto& [cmdIndex, future] : futures) {
|
||||
auto cmdResult = future.get();
|
||||
|
||||
// Ensure scoresByDepth[cmdIndex] has enough space
|
||||
if (scoresByDepth[cmdIndex].size() <= currentDepth) {
|
||||
scoresByDepth[cmdIndex].resize(currentDepth + 1);
|
||||
@@ -158,13 +142,13 @@ auto IterativeDeepeningAI::IterativeSearch(
|
||||
// Log if best command changed from previous depth
|
||||
if (currentDepth > 1 && currentBestCommand != previousBestCommand) {
|
||||
#if DEBUG_ITERATIVE_DEEPENING_TIMINGS
|
||||
printf("ID AI: Best command changed at depth %lu:\n", currentDepth);
|
||||
printf(" Depth %lu best: command %zu (score %.2f) - %s\n",
|
||||
printf("ID AI: Best command changed at depth %d:\n", currentDepth);
|
||||
printf(" Depth %d best: command %zu (score %.2f) - %s\n",
|
||||
currentDepth - 1,
|
||||
previousBestCommand,
|
||||
scoresByDepth[previousBestCommand][currentDepth - 1],
|
||||
commands[previousBestCommand].DebugString().c_str());
|
||||
printf(" Depth %lu best: command %zu (score %.2f) - %s\n",
|
||||
printf(" Depth %d best: command %zu (score %.2f) - %s\n",
|
||||
currentDepth,
|
||||
currentBestCommand,
|
||||
currentBestScore,
|
||||
@@ -191,12 +175,12 @@ auto IterativeDeepeningAI::IterativeSearch(
|
||||
// This indicates we've hit END_TURN in the lookahead
|
||||
if (currentDepth > 1 && evaluatedCount > 0) {
|
||||
bool scoresUnchanged = true;
|
||||
size_t unchangedCount = 0;
|
||||
int unchangedCount = 0;
|
||||
|
||||
for (size_t i = 0; i < sortedIndices.size() && i < evaluatedCount; ++i) {
|
||||
size_t cmdIndex = sortedIndices[i];
|
||||
// This command was evaluated at both current and previous depth
|
||||
if (size_t cmdIndex = sortedIndices[i];
|
||||
scoresByDepth[cmdIndex].size() > currentDepth &&
|
||||
if (scoresByDepth[cmdIndex].size() > currentDepth &&
|
||||
scoresByDepth[cmdIndex].size() > currentDepth - 1) {
|
||||
// Check if score changed between depth N-1 and depth N
|
||||
if (std::abs(
|
||||
@@ -220,11 +204,10 @@ auto IterativeDeepeningAI::IterativeSearch(
|
||||
// Check if we've used more than 50% of total budget
|
||||
auto totalElapsed = std::chrono::steady_clock::now() - startTime;
|
||||
auto totalElapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(totalElapsed);
|
||||
double budgetUsedPercent = static_cast<double>(totalElapsedMs.count()) /
|
||||
static_cast<double>(initialBudgetMs.count());
|
||||
double budgetUsedPercent = (double)totalElapsedMs.count() / initialBudgetMs.count();
|
||||
|
||||
if (budgetUsedPercent > 0.5) {
|
||||
printf("ID AI: Stopping after depth %lu - used %.1f%% of time budget\n",
|
||||
printf("ID AI: Stopping after depth %d - used %.1f%% of time budget\n",
|
||||
currentDepth,
|
||||
budgetUsedPercent * 100);
|
||||
completionReason = EvaluationCompletionReason::NOT_ENOUGH_TIME_TO_CONTINUE;
|
||||
@@ -259,8 +242,6 @@ auto IterativeDeepeningAI::IterativeSearch(
|
||||
result.availableCommandCount);
|
||||
}
|
||||
|
||||
// Print TranspositionTable statistics
|
||||
g_transpositionTable.printStats();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -274,12 +255,12 @@ auto IterativeDeepeningAI::SearchCommandAtDepthWithEngine(
|
||||
const int maxRepeatCount,
|
||||
const std::vector<CommandProto>& commands,
|
||||
const size_t commandIndex,
|
||||
const int desiredDepth,
|
||||
const int depth,
|
||||
const ScoreValue currentUtility,
|
||||
AITimeBudget& timeBudget) const -> std::future<SearchResult> {
|
||||
AITimeBudget& timeBudget) const -> SearchResult {
|
||||
SearchResult result;
|
||||
result.bestCommandIndex = commandIndex;
|
||||
result.depthAchieved = desiredDepth;
|
||||
result.depthAchieved = depth;
|
||||
result.searchCompleted = true;
|
||||
result.minimumDepthCompleted = true;
|
||||
result.availableCommandCount = commands.size();
|
||||
@@ -287,9 +268,7 @@ auto IterativeDeepeningAI::SearchCommandAtDepthWithEngine(
|
||||
|
||||
if (commandIndex >= commands.size()) {
|
||||
result.bestScore = 0.0;
|
||||
std::promise<SearchResult> p;
|
||||
p.set_value(result);
|
||||
return p.get_future();
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -297,18 +276,11 @@ auto IterativeDeepeningAI::SearchCommandAtDepthWithEngine(
|
||||
AIEvaluationCounter counter;
|
||||
const auto startTime = std::chrono::steady_clock::now();
|
||||
|
||||
// Calculate deadline from remaining time budget
|
||||
const auto deadline = startTime + timeBudget.remainingBudget;
|
||||
|
||||
// Get the future from CommandScore - don't wait yet
|
||||
// Note: CommandScore expects remainingLookahead, not desiredDepth
|
||||
// desiredDepth 1 = evaluate immediate (remainingLookahead 0)
|
||||
// desiredDepth 2 = look 1 move ahead (remainingLookahead 1)
|
||||
// desiredDepth N = look N-1 moves ahead (remainingLookahead N-1)
|
||||
auto commandScoreFuture = AIScoreCalculator::CommandScore(
|
||||
// Use CommandScore to evaluate the specific command at the given depth
|
||||
const auto commandScore = AIScoreCalculator::CommandScore(
|
||||
playerId,
|
||||
isDefender,
|
||||
desiredDepth - 1, // Convert desiredDepth to remainingLookahead
|
||||
depth,
|
||||
maxRepeatCount,
|
||||
guessedEngine,
|
||||
strategy,
|
||||
@@ -317,15 +289,11 @@ auto IterativeDeepeningAI::SearchCommandAtDepthWithEngine(
|
||||
castleCoords,
|
||||
apdCache,
|
||||
alCache,
|
||||
commandIndex,
|
||||
deadline);
|
||||
|
||||
// Calculate time and adjust budget before waiting
|
||||
// This is needed because we need to update timeBudget synchronously
|
||||
const auto commandScore = commandScoreFuture.get();
|
||||
commandIndex);
|
||||
|
||||
// Calculate time used and adjust based on concurrent evaluations
|
||||
const auto elapsed = std::chrono::steady_clock::now() - startTime;
|
||||
const int concurrentCount = AIEvaluationCounter::GetCurrentCount();
|
||||
const int concurrentCount = counter.GetCurrentCount();
|
||||
const auto adjustedElapsed = elapsed / std::max(1, concurrentCount);
|
||||
const auto adjustedElapsedMs =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(adjustedElapsed);
|
||||
@@ -342,15 +310,13 @@ auto IterativeDeepeningAI::SearchCommandAtDepthWithEngine(
|
||||
result.bestScore = 0.0;
|
||||
}
|
||||
|
||||
std::promise<SearchResult> p;
|
||||
p.set_value(result);
|
||||
return p.get_future();
|
||||
return result;
|
||||
}
|
||||
|
||||
auto IterativeDeepeningAI::GetCommandsSortedByPreviousDepth(
|
||||
const size_t currentDepth,
|
||||
int currentDepth,
|
||||
const std::vector<std::vector<ScoreValue>>& scoresByDepth,
|
||||
const std::vector<size_t>& highestDepthCompleted) -> std::vector<size_t> {
|
||||
const std::vector<int>& highestDepthCompleted) const -> std::vector<size_t> {
|
||||
std::vector<size_t> indices(scoresByDepth.size());
|
||||
std::iota(indices.begin(), indices.end(), 0);
|
||||
|
||||
@@ -360,21 +326,11 @@ auto IterativeDeepeningAI::GetCommandsSortedByPreviousDepth(
|
||||
}
|
||||
|
||||
// Sort by score at previous depth
|
||||
const size_t prevDepth = currentDepth - 1;
|
||||
std::ranges::sort(indices, [&](const size_t a, const size_t b) {
|
||||
// Bounds check - if indices are out of range, or inner vectors are too small, treat as not
|
||||
// evaluated
|
||||
if (a >= scoresByDepth.size() || b >= scoresByDepth.size() ||
|
||||
a >= highestDepthCompleted.size() || b >= highestDepthCompleted.size()) {
|
||||
return a < b; // Maintain stable order for out-of-bounds indices
|
||||
}
|
||||
|
||||
// Check if the scores for previous depth exist
|
||||
int prevDepth = currentDepth - 1;
|
||||
std::sort(indices.begin(), indices.end(), [&](size_t a, size_t b) {
|
||||
// Only consider commands that were evaluated at previous depth
|
||||
if (highestDepthCompleted[a] >= prevDepth && highestDepthCompleted[b] >= prevDepth) {
|
||||
// Additional safety check for inner vector size
|
||||
if (scoresByDepth[a].size() > prevDepth && scoresByDepth[b].size() > prevDepth) {
|
||||
return scoresByDepth[a][prevDepth] > scoresByDepth[b][prevDepth];
|
||||
}
|
||||
return scoresByDepth[a][prevDepth] > scoresByDepth[b][prevDepth];
|
||||
}
|
||||
// Commands not evaluated at prev depth go to the end
|
||||
return highestDepthCompleted[a] >= prevDepth;
|
||||
@@ -385,7 +341,7 @@ auto IterativeDeepeningAI::GetCommandsSortedByPreviousDepth(
|
||||
|
||||
auto IterativeDeepeningAI::SelectBestResult(
|
||||
const std::vector<std::vector<ScoreValue>>& scoresByDepth,
|
||||
const std::vector<size_t>& highestDepthCompleted) -> SearchResult {
|
||||
const std::vector<int>& highestDepthCompleted) const -> SearchResult {
|
||||
SearchResult result;
|
||||
result.bestScore = -std::numeric_limits<ScoreValue>::infinity();
|
||||
result.searchCompleted = false;
|
||||
@@ -393,8 +349,9 @@ auto IterativeDeepeningAI::SelectBestResult(
|
||||
// Find the command with best score at its highest evaluated depth
|
||||
for (size_t i = 0; i < scoresByDepth.size(); ++i) {
|
||||
if (highestDepthCompleted[i] > 0) {
|
||||
const size_t depth = highestDepthCompleted[i];
|
||||
if (ScoreValue score = scoresByDepth[i][depth]; score > result.bestScore) {
|
||||
int depth = highestDepthCompleted[i];
|
||||
ScoreValue score = scoresByDepth[i][depth];
|
||||
if (score > result.bestScore) {
|
||||
result.bestScore = score;
|
||||
result.bestCommandIndex = i;
|
||||
result.depthAchieved = depth;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#define EAGLE0_ITERATIVEDEEPENINGAI_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <vector>
|
||||
|
||||
#include "AIStrategy.hpp"
|
||||
@@ -36,7 +35,7 @@ public:
|
||||
struct SearchResult {
|
||||
size_t bestCommandIndex;
|
||||
ScoreValue bestScore;
|
||||
size_t depthAchieved;
|
||||
int depthAchieved;
|
||||
std::chrono::milliseconds timeUsed;
|
||||
bool minimumDepthCompleted;
|
||||
bool searchCompleted;
|
||||
@@ -68,7 +67,7 @@ public:
|
||||
const GameSettingsSPtr& settings,
|
||||
const GameStateW& state,
|
||||
const std::vector<CommandProto>& commands,
|
||||
const AITimeBudget& initialBudget) const;
|
||||
const AITimeBudget& timeBudget) const;
|
||||
|
||||
private:
|
||||
PlayerId playerId;
|
||||
@@ -80,29 +79,29 @@ private:
|
||||
|
||||
// Reusable vectors to reduce memory allocations
|
||||
mutable std::vector<std::vector<ScoreValue>> scoresByDepth;
|
||||
mutable std::vector<size_t> highestDepthCompleted;
|
||||
mutable std::vector<int> highestDepthCompleted;
|
||||
mutable std::vector<size_t> reusableSortedIndices;
|
||||
|
||||
[[nodiscard]] static bool IsTimeExpired(const AITimeBudget& budget);
|
||||
|
||||
[[nodiscard]] std::future<SearchResult> SearchCommandAtDepthWithEngine(
|
||||
[[nodiscard]] SearchResult SearchCommandAtDepthWithEngine(
|
||||
const ShardokEngine& guessedEngine,
|
||||
const GameSettings::Getter& settingsGetter,
|
||||
int maxRepeatCount,
|
||||
const std::vector<CommandProto>& commands,
|
||||
size_t commandIndex,
|
||||
int desiredDepth,
|
||||
int depth,
|
||||
ScoreValue currentUtility,
|
||||
AITimeBudget& timeBudget) const;
|
||||
|
||||
[[nodiscard]] static std::vector<size_t> GetCommandsSortedByPreviousDepth(
|
||||
size_t currentDepth,
|
||||
[[nodiscard]] std::vector<size_t> GetCommandsSortedByPreviousDepth(
|
||||
int currentDepth,
|
||||
const std::vector<std::vector<ScoreValue>>& scoresByDepth,
|
||||
const std::vector<size_t>& highestDepthCompleted);
|
||||
const std::vector<int>& highestDepthCompleted) const;
|
||||
|
||||
[[nodiscard]] static SearchResult SelectBestResult(
|
||||
[[nodiscard]] SearchResult SelectBestResult(
|
||||
const std::vector<std::vector<ScoreValue>>& scoresByDepth,
|
||||
const std::vector<size_t>& highestDepthCompleted);
|
||||
const std::vector<int>& highestDepthCompleted) const;
|
||||
};
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
@@ -8,14 +8,10 @@
|
||||
|
||||
#include "ShardokAIClient.hpp"
|
||||
|
||||
#define DEBUG_FLEE_DECISIONS
|
||||
|
||||
#include <google/protobuf/util/message_differencer.h>
|
||||
|
||||
#include "AIAttackerStrategySelector.hpp"
|
||||
#include "AIDefenderStrategySelector.hpp"
|
||||
#include "AIFleeDecisionCalculator.hpp"
|
||||
#include "AIScoreUtilities.hpp"
|
||||
#include "AITimeBudget.hpp"
|
||||
#include "IterativeDeepeningAI.hpp"
|
||||
#include "src/main/cpp/net/eagle0/common/TimeUtils.hpp"
|
||||
@@ -30,7 +26,7 @@ using net::eagle0::shardok::api::GameStateView;
|
||||
|
||||
static constexpr bool kPerformanceLogging = true;
|
||||
|
||||
void ApplyUpdate(GameStateView & /*currentView*/, const ActionResultView & /*update*/) {}
|
||||
void ApplyUpdate(GameStateView ¤tView, const ActionResultView &update) {}
|
||||
|
||||
auto RoundsRemaining(const GameSettingsSPtr &settings, const GameStateView &gsv) -> int {
|
||||
const int maxRounds = settings->GetGetter().Backing().max_rounds();
|
||||
@@ -100,7 +96,7 @@ auto ShardokAIClient::StandardChooseCommandIndex(
|
||||
const auto commandCount = guessedCommands.size();
|
||||
|
||||
assert(commandCount == realAvailableCommands.size());
|
||||
for (size_t i = 0; i < commandCount; i++) {
|
||||
for (int i = 0; i < commandCount; i++) {
|
||||
CheckCommand(realAvailableCommands[i], guessedCommands[i]);
|
||||
}
|
||||
|
||||
@@ -179,41 +175,23 @@ auto ShardokAIClient::FinalRoundAttackerChooseCommandIndex(
|
||||
const GameSettingsSPtr &settings,
|
||||
const GameStateW &guessedState,
|
||||
const vector<CommandProto> &realAvailableCommands) const -> CommandChoiceResults {
|
||||
const auto fleeCommand = std::ranges::find_if(
|
||||
realAvailableCommands,
|
||||
[](const net::eagle0::shardok::api::CommandDescriptor &cmd) {
|
||||
return cmd.type() == net::eagle0::shardok::common::FLEE_COMMAND;
|
||||
});
|
||||
|
||||
if (fleeCommand == realAvailableCommands.end()) {
|
||||
if (const auto fleeCommand = std::ranges::find_if(
|
||||
realAvailableCommands,
|
||||
[](const net::eagle0::shardok::api::CommandDescriptor &cmd) {
|
||||
return cmd.type() == net::eagle0::shardok::common::FLEE_COMMAND;
|
||||
});
|
||||
fleeCommand == realAvailableCommands.end()) {
|
||||
return LateRoundAttackerChooseCommandIndex(settings, guessedState, realAvailableCommands);
|
||||
}
|
||||
|
||||
// Use the flee decision calculator
|
||||
const auto fleeDecision = AIFleeDecisionCalculator::EvaluateFleeVsFight(
|
||||
playerId,
|
||||
settings->GetGetter(),
|
||||
guessedState,
|
||||
realAvailableCommands,
|
||||
fleeCommand,
|
||||
#ifdef DEBUG_FLEE_DECISIONS
|
||||
true // Enable debug logging
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
);
|
||||
|
||||
if (fleeDecision.shouldFlee) {
|
||||
CommandChoiceResults results{};
|
||||
results.chosenIndex = fleeDecision.commandIndex;
|
||||
results.availableCommandCount = realAvailableCommands.size();
|
||||
results.depthAchieved = 1; // Heuristic choice
|
||||
results.commandCountEvaluated = 1; // Only evaluated one command type
|
||||
results.completionReason = EvaluationCompletionReason::RAN_OUT_OF_COMMANDS;
|
||||
return results;
|
||||
} else {
|
||||
// Fight instead of flee
|
||||
return StandardChooseCommandIndex(settings, guessedState, realAvailableCommands);
|
||||
CommandChoiceResults results{};
|
||||
results.chosenIndex =
|
||||
static_cast<size_t>(std::distance(realAvailableCommands.begin(), fleeCommand));
|
||||
results.availableCommandCount = realAvailableCommands.size();
|
||||
results.depthAchieved = 1; // Simple heuristic choice
|
||||
results.commandCountEvaluated = 1; // Only evaluated one command type
|
||||
results.completionReason =
|
||||
EvaluationCompletionReason::RAN_OUT_OF_COMMANDS; // Heuristic choice
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ private:
|
||||
const GameSettingsSPtr& settings,
|
||||
const GameStateW& guessedState,
|
||||
const vector<CommandProto>& realAvailableCommands) const -> CommandChoiceResults;
|
||||
|
||||
[[nodiscard]] auto ChooseCommandIndex(
|
||||
const GameSettingsSPtr& settings,
|
||||
const net::eagle0::shardok::api::GameStateView& gsv,
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
//
|
||||
// TranspositionTable.cpp - Implementation of game state evaluation cache
|
||||
//
|
||||
|
||||
#include "TranspositionTable.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace shardok {
|
||||
|
||||
// Global instance
|
||||
TranspositionTable g_transpositionTable;
|
||||
|
||||
TranspositionTable::TranspositionTable() : table(TABLE_SIZE) {
|
||||
// Initialize all entries to zero
|
||||
clear();
|
||||
}
|
||||
|
||||
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
|
||||
return state.ComputeFNV1aHash();
|
||||
}
|
||||
|
||||
std::optional<ScoreValue>
|
||||
TranspositionTable::probe(const GameStateW& state, int depth, PlayerId player) {
|
||||
stats.probes++;
|
||||
|
||||
uint64_t hash = hashGameState(state);
|
||||
size_t index = hash & INDEX_MASK;
|
||||
|
||||
const auto& entry = table[index];
|
||||
|
||||
// Check if this entry matches our position using FULL hash
|
||||
uint64_t stored_hash = entry.hash_full.load(std::memory_order_relaxed);
|
||||
uint8_t stored_depth = entry.depth.load(std::memory_order_relaxed);
|
||||
uint8_t stored_player = entry.player_id.load(std::memory_order_relaxed);
|
||||
|
||||
if (stored_hash == hash && stored_depth >= depth && stored_player == player) {
|
||||
stats.hits++;
|
||||
float score = entry.score.load(std::memory_order_relaxed);
|
||||
return static_cast<ScoreValue>(score);
|
||||
}
|
||||
|
||||
// Track collisions (different position mapped to same index)
|
||||
// Note: We use depth==0 to indicate empty entries, not hash==0
|
||||
if (stored_depth != 0 && stored_hash != hash) { stats.collisions++; }
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void TranspositionTable::store(
|
||||
const GameStateW& state,
|
||||
int depth,
|
||||
PlayerId player,
|
||||
ScoreValue score) {
|
||||
stats.stores++;
|
||||
|
||||
uint64_t hash = hashGameState(state);
|
||||
size_t index = hash & INDEX_MASK;
|
||||
|
||||
auto& entry = table[index];
|
||||
|
||||
// Simple replacement strategy: always replace if:
|
||||
// 1. Entry is from an older search (different age)
|
||||
// 2. New search is deeper
|
||||
// 3. Entry is empty (depth == 0)
|
||||
|
||||
uint16_t stored_age = entry.age.load(std::memory_order_relaxed);
|
||||
uint8_t stored_depth = entry.depth.load(std::memory_order_relaxed);
|
||||
|
||||
bool should_replace = (stored_depth == 0) || // Empty entry (depth 0 means unused)
|
||||
(stored_age != current_age) || // Old entry
|
||||
(depth >= stored_depth); // Deeper or equal search
|
||||
|
||||
if (should_replace) {
|
||||
// Store all fields with relaxed ordering (TT races are benign)
|
||||
entry.hash_full.store(hash, std::memory_order_relaxed);
|
||||
entry.score.store(static_cast<float>(score), std::memory_order_relaxed);
|
||||
entry.depth.store(static_cast<uint8_t>(depth), std::memory_order_relaxed);
|
||||
entry.player_id.store(static_cast<uint8_t>(player), std::memory_order_relaxed);
|
||||
entry.age.store(current_age, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
void TranspositionTable::clear() {
|
||||
// Reset all entries
|
||||
for (auto& entry : table) {
|
||||
entry.hash_full.store(0, std::memory_order_relaxed);
|
||||
entry.score.store(0.0f, std::memory_order_relaxed);
|
||||
entry.depth.store(0, std::memory_order_relaxed);
|
||||
entry.player_id.store(0, std::memory_order_relaxed);
|
||||
entry.age.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
stats.reset();
|
||||
current_age = 0;
|
||||
}
|
||||
|
||||
void TranspositionTable::printStats() const {
|
||||
printf("TranspositionTable Stats:\n");
|
||||
printf(" Probes: %llu\n", stats.probes.load());
|
||||
printf(" Hits: %llu (%.1f%%)\n", stats.hits.load(), stats.hitRate());
|
||||
printf(" Stores: %llu\n", stats.stores.load());
|
||||
printf(" Collisions: %llu\n", stats.collisions.load());
|
||||
printf(" Table size: %zu entries (%.1f MB)\n",
|
||||
TABLE_SIZE,
|
||||
(TABLE_SIZE * sizeof(TTEntry)) / (1024.0 * 1024.0));
|
||||
}
|
||||
|
||||
} // namespace shardok
|
||||
@@ -1,91 +0,0 @@
|
||||
//
|
||||
// TranspositionTable.hpp - Cache for game state evaluations to avoid redundant calculations
|
||||
//
|
||||
|
||||
#ifndef EAGLE0_TRANSPOSITIONTABLE_HPP
|
||||
#define EAGLE0_TRANSPOSITIONTABLE_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCTypes.h"
|
||||
|
||||
namespace shardok {
|
||||
|
||||
using ScoreValue = double;
|
||||
// PlayerId already defined in ShardokCTypes.h
|
||||
|
||||
class TranspositionTable {
|
||||
public:
|
||||
// Statistics for monitoring effectiveness
|
||||
struct Stats {
|
||||
std::atomic<uint64_t> probes{0};
|
||||
std::atomic<uint64_t> hits{0};
|
||||
std::atomic<uint64_t> stores{0};
|
||||
std::atomic<uint64_t> collisions{0};
|
||||
|
||||
double hitRate() const {
|
||||
uint64_t p = probes.load();
|
||||
return p > 0 ? (100.0 * hits.load() / p) : 0.0;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
probes = 0;
|
||||
hits = 0;
|
||||
stores = 0;
|
||||
collisions = 0;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
// Compact entry structure (actual size is greater than 16 bytes due to atomics and alignment)
|
||||
struct TTEntry {
|
||||
std::atomic<uint64_t> hash_full; // Full hash for validation
|
||||
std::atomic<float> score; // Score as float to save space
|
||||
std::atomic<uint8_t> depth; // Search depth (0-255)
|
||||
std::atomic<uint8_t> player_id; // Player who is to move
|
||||
std::atomic<uint16_t> age; // For replacement strategy
|
||||
};
|
||||
|
||||
static constexpr size_t TABLE_SIZE_BITS = 22; // 2^22 entries
|
||||
static constexpr size_t TABLE_SIZE = 1ULL << TABLE_SIZE_BITS; // 4M entries = 64MB
|
||||
static constexpr size_t INDEX_MASK = TABLE_SIZE - 1;
|
||||
|
||||
std::vector<TTEntry> table;
|
||||
Stats stats;
|
||||
std::atomic<uint16_t> current_age{0};
|
||||
|
||||
// Hash function for FlatBuffer game state
|
||||
uint64_t hashGameState(const GameStateW& state) const;
|
||||
|
||||
public:
|
||||
TranspositionTable();
|
||||
|
||||
// Probe the table for a cached evaluation
|
||||
std::optional<ScoreValue> probe(const GameStateW& state, int depth, PlayerId player);
|
||||
|
||||
// Store an evaluation in the table
|
||||
void store(const GameStateW& state, int depth, PlayerId player, ScoreValue score);
|
||||
|
||||
// Clear the entire table
|
||||
void clear();
|
||||
|
||||
// Increment age for replacement strategy (call at start of each search)
|
||||
void incrementAge() { current_age++; }
|
||||
|
||||
// Get statistics
|
||||
const Stats& getStats() const { return stats; }
|
||||
|
||||
// Print statistics to stdout
|
||||
void printStats() const;
|
||||
};
|
||||
|
||||
// Global instance for the AI to use
|
||||
extern TranspositionTable g_transpositionTable;
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
#endif // EAGLE0_TRANSPOSITIONTABLE_HPP
|
||||
@@ -0,0 +1,304 @@
|
||||
# AIScoreCalculator ThreadPool Migration Plan
|
||||
|
||||
## Overview
|
||||
This document outlines the plan to migrate AIScoreCalculator from using std::async to using the new ThreadPool class with priority-based scheduling and deadline support.
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### std::async Usage
|
||||
1. **CalcOne method (line 893)**: Uses `std::async(std::launch::async, lookaheadLambda)` to asynchronously calculate lookahead scores
|
||||
2. **BestCommandIndex method (line 1053)**: Uses `std::async(std::launch::deferred, ...)` for deferred calculation of weighted scores
|
||||
3. **Future resolution (lines 1086-1091)**: Waits on all futures using `.get()` in a loop
|
||||
|
||||
### Time Management
|
||||
- IterativeDeepeningAI manages time budgets via AITimeBudget structure
|
||||
- Time budget contains `remainingBudget` (std::chrono::milliseconds) that gets decremented
|
||||
- No explicit deadline passing to AIScoreCalculator currently
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### 1. ThreadPool Integration
|
||||
- Create a static thread pool instance with 32 threads in AIScoreCalculator
|
||||
- Thread pool will be shared across all AIScoreCalculator operations
|
||||
|
||||
### 2. Priority Assignment
|
||||
- Priority = current lookahead depth (passed via `remainingLookahead` parameter)
|
||||
- Higher depth = higher priority (deeper searches are more valuable)
|
||||
- This ensures shallow searches complete first, allowing iterative deepening to work effectively
|
||||
|
||||
### 3. Deadline Support
|
||||
- Calculate deadline based on remaining time budget from IterativeDeepeningAI
|
||||
- Pass deadline to thread pool using `enqueue_with_deadline` for time-critical tasks
|
||||
- Tasks that exceed deadline will be automatically skipped by the thread pool
|
||||
|
||||
### 4. Implementation Details
|
||||
|
||||
#### Changes to CalcOne:
|
||||
```cpp
|
||||
// OLD:
|
||||
returnValue.lookaheadScore = std::async(std::launch::async, lookaheadLambda);
|
||||
|
||||
// NEW:
|
||||
// Priority = remainingLookahead (higher depth = higher priority)
|
||||
// Deadline = current_time + remaining_budget_fraction
|
||||
returnValue.lookaheadScore = threadPool.enqueue_with_deadline(
|
||||
lookaheadLambda,
|
||||
remainingLookahead, // priority
|
||||
deadline // calculated from time budget
|
||||
);
|
||||
```
|
||||
|
||||
#### Changes to BestCommandIndex:
|
||||
```cpp
|
||||
// Deferred calculations stay as-is (no change needed)
|
||||
// They're already using std::launch::deferred which is appropriate
|
||||
|
||||
// The .get() loop (lines 1086-1091) can be moved to a lower priority task:
|
||||
auto futureResolutionTask = [&]() {
|
||||
for (uint32_t i = 0; i < commandCount; i++) {
|
||||
const auto count = static_cast<ScoreValue>(scoreFutures[i].size());
|
||||
ScoreValue total = 0.0;
|
||||
for (auto &oneFuture : scoreFutures[i]) {
|
||||
total += oneFuture.get();
|
||||
}
|
||||
allIndices[i].lookaheadScore = total / count;
|
||||
}
|
||||
};
|
||||
|
||||
// Enqueue with lower priority (0 or negative) to ensure all calculations complete first
|
||||
threadPool.enqueue(futureResolutionTask, 0);
|
||||
```
|
||||
|
||||
### 5. Thread Pool Lifecycle
|
||||
- Initialize as static member: `static inline ThreadPool threadPool{32};`
|
||||
- Destruction handled automatically by ThreadPool destructor
|
||||
- No explicit cleanup needed
|
||||
|
||||
### 6. Deadline Calculation
|
||||
- Need to pass time budget information down from IterativeDeepeningAI
|
||||
- Add optional `AITimeBudget*` parameter to CalcOne and BestCommandIndex
|
||||
- Calculate deadline as: `now() + (remainingBudget * depth_fraction)`
|
||||
- Deeper searches get proportionally less time
|
||||
|
||||
### 7. Benefits
|
||||
1. **Better CPU utilization**: 32 threads vs unbounded std::async
|
||||
2. **Priority scheduling**: Deeper searches get higher priority
|
||||
3. **Deadline enforcement**: Automatic timeout handling
|
||||
4. **Resource control**: Fixed thread pool prevents thread explosion
|
||||
5. **Performance**: Thread reuse avoids creation/destruction overhead
|
||||
|
||||
### 8. Testing Plan
|
||||
1. Verify thread pool initialization
|
||||
2. Test priority ordering (shallow searches complete first)
|
||||
3. Test deadline enforcement (expired tasks are skipped)
|
||||
4. Compare performance with existing implementation
|
||||
5. Stress test with multiple concurrent AI calculations
|
||||
|
||||
### 9. Rollback Plan
|
||||
- Keep MULTITHREAD macro to allow switching between implementations
|
||||
- Add THREAD_POOL macro to conditionally compile new implementation
|
||||
- Allows A/B testing and gradual migration
|
||||
|
||||
## Implementation Status
|
||||
|
||||
### ✅ Completed
|
||||
1. **ThreadPool Integration** - Added static 32-thread pool to AIScoreCalculator
|
||||
2. **Priority Assignment** - Priority = current lookahead depth (higher depth = higher priority)
|
||||
3. **Deadline Support** - Calculate deadline based on remaining time budget from IterativeDeepeningAI
|
||||
4. **Method Signatures Updated** - Added `AITimeBudget* timeBudget` parameter to CalcOne and BestCommandIndex
|
||||
5. **std::async Replacement** - Replaced `std::async(std::launch::async)` with `threadPool.enqueue_with_deadline()`
|
||||
6. **Time Budget Integration** - IterativeDeepeningAI now passes timeBudget to AIScoreCalculator methods
|
||||
7. **Build System Updates** - Added thread_pool dependency to BUILD.bazel files
|
||||
8. **Build Verification** - Successfully builds with `bazel build //src/main/cpp/net/eagle0/shardok/ai:ai_score_calculator`
|
||||
|
||||
### Implementation Details
|
||||
- **Priority Calculation**: `priority = remainingLookahead` (deeper searches get higher priority)
|
||||
- **Deadline Calculation**: `deadline = now + (remainingBudget / (remainingLookahead + 1))`
|
||||
- **Thread Pool**: Static 32-thread pool shared across all AIScoreCalculator operations
|
||||
- **Backward Compatibility**: MULTITHREAD macro preserved for rollback capability
|
||||
- **Deferred Tasks**: std::launch::deferred calls remain unchanged as planned
|
||||
|
||||
### 🔧 Fixed Issues
|
||||
|
||||
#### Build Issues
|
||||
- **SearchAtDepth method**: Fixed missing timeBudget parameter - now passes nullptr since this method doesn't have access to time budget
|
||||
- **AI Performance Runner**: Successfully builds and runs with `bazel build //src/main/cpp/net/eagle0/shardok/ai_performance_runner:ai_performance_runner`
|
||||
|
||||
#### Runtime Issues
|
||||
- **Hanging AI Performance Test**: ✅ FIXED WITH PROPER DEADLINE SUPPORT
|
||||
- **Root Cause**: Deadline expiration in ThreadPool was skipping task execution, leaving futures unresolved
|
||||
- **Symptom**: `./scripts/ai_perf_test.sh` would hang indefinitely when calling `oneFuture.get()` in BestCommandIndex
|
||||
- **Solution**: Implemented proper status code system with `TaskResult<T>` wrapper
|
||||
- Created `TaskStatus` enum (SUCCESS, DEADLINE_EXCEEDED, CANCELLED)
|
||||
- Updated ThreadPool to return `TaskResult<T>` instead of raw `T`
|
||||
- Tasks that exceed deadline return `TaskResult` with `DEADLINE_EXCEEDED` status
|
||||
- AIScoreCalculator checks status codes and handles expired tasks gracefully
|
||||
- **Additional Fix**: Lambda capture issue in `enqueue_with_deadline`
|
||||
- **Problem**: Complex lambda capture syntax `[f = std::forward<F>(f), args..., deadline]` was causing compilation/runtime issues
|
||||
- **Solution**: Used `std::bind` to create callable object: `auto actualTask = std::bind(std::forward<F>(f), std::forward<Args>(args)...);`
|
||||
- **Result**: Cleaner, more reliable task capture and execution
|
||||
- **Final Result**: Full deadline functionality restored, AI performance test runs correctly without hanging
|
||||
|
||||
### Implementation Details - TaskResult System
|
||||
```cpp
|
||||
// TaskResult wrapper with status code
|
||||
template<typename T>
|
||||
struct TaskResult {
|
||||
T value;
|
||||
TaskStatus status;
|
||||
|
||||
bool succeeded() const { return status == TaskStatus::SUCCESS; }
|
||||
bool deadlineExceeded() const { return status == TaskStatus::DEADLINE_EXCEEDED; }
|
||||
operator T() const { return value; } // Implicit conversion for compatibility
|
||||
};
|
||||
|
||||
// Usage in AIScoreCalculator
|
||||
for (auto &oneFuture : scoreFutures[i]) {
|
||||
auto result = oneFuture.get();
|
||||
if (result.succeeded()) {
|
||||
total += result.value;
|
||||
validResults++;
|
||||
} else if (result.deadlineExceeded()) {
|
||||
// Skip deadline-exceeded results, fall back to immediate score
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Status: ✅ COMPLETE AND FULLY FUNCTIONAL
|
||||
All ThreadPool migration work is complete with proper deadline support. The implementation:
|
||||
- ✅ Builds successfully
|
||||
- ✅ Runs correctly with full deadline functionality
|
||||
- ✅ Handles deadline expiration gracefully without hanging
|
||||
- ✅ AI performance test works perfectly
|
||||
- ✅ Fixed infinite loop issue caused by TaskResult implicit conversion
|
||||
|
||||
### 🔧 Final Issue Resolution - Infinite Loop Bug
|
||||
|
||||
#### Problem: TaskResult Implicit Conversion Causing Infinite Recursion
|
||||
- **Root Cause**: TaskResult<T> had an implicit conversion operator that was interfering with AI search control flow
|
||||
- **Symptom**: AI performance test hanging in infinite loop, processing same set of futures repeatedly
|
||||
- **Evidence**: Debug output showed endless cycle processing futures 0-45
|
||||
|
||||
#### Solution: Remove Implicit Conversion Operator
|
||||
- **Change**: Removed `operator T() const { return value; }` from TaskResult<T>
|
||||
- **Replacement**: Added explicit `.get()` method: `T get() const { return value; }`
|
||||
- **Code Updates**: Updated all AIScoreCalculator usage to explicitly call `.value` or handle TaskResult properly
|
||||
|
||||
#### Updated TaskResult Implementation
|
||||
```cpp
|
||||
template<typename T>
|
||||
struct TaskResult {
|
||||
T value;
|
||||
TaskStatus status;
|
||||
|
||||
// NO implicit conversion - this was causing infinite recursion
|
||||
T get() const { return value; }
|
||||
|
||||
bool succeeded() const { return status == TaskStatus::SUCCESS; }
|
||||
bool deadlineExceeded() const { return status == TaskStatus::DEADLINE_EXCEEDED; }
|
||||
};
|
||||
```
|
||||
|
||||
#### Final Result
|
||||
- **AI Performance Test**: ✅ Now runs successfully, no more hanging
|
||||
- **ThreadPool Usage**: ✅ Correctly using 32-thread pool with priority-based scheduling
|
||||
- **Deadline Support**: ✅ Full deadline functionality with proper status codes
|
||||
- **Performance**: ✅ AI achieves depth 2 searches consistently across turns
|
||||
|
||||
### 🔧 Critical Bug Fix - Promise Lifetime Issue
|
||||
|
||||
#### Problem: Stack-Allocated Promise Destruction
|
||||
The final and most critical bug was in the immediate path (no lookahead) promise handling:
|
||||
|
||||
**Broken Code:**
|
||||
```cpp
|
||||
if (remainingLookahead <= 0) {
|
||||
std::promise<TaskResult<ScoreValue>> p; // Stack allocated!
|
||||
returnValue.lookaheadScore = p.get_future();
|
||||
p.set_value(TaskResult<ScoreValue>(innerUtility));
|
||||
// BUG: 'p' destructor runs here, invalidating the future!
|
||||
}
|
||||
```
|
||||
|
||||
**Root Cause:**
|
||||
- Most AI calls use `remainingLookahead=0` (immediate path)
|
||||
- Stack-allocated promise `p` was destroyed when leaving scope
|
||||
- This left associated futures in invalid/undefined state
|
||||
- `future.get()` calls on invalid futures hang indefinitely
|
||||
- ThreadPool tasks (priority 1,2) would queue up waiting for invalid futures
|
||||
|
||||
**Fixed Code:**
|
||||
```cpp
|
||||
if (remainingLookahead <= 0) {
|
||||
auto p = std::make_shared<std::promise<TaskResult<ScoreValue>>>(); // Heap allocated!
|
||||
returnValue.lookaheadScore = p->get_future();
|
||||
p->set_value(TaskResult<ScoreValue>(innerUtility));
|
||||
// Promise stays alive through shared_ptr reference counting
|
||||
}
|
||||
```
|
||||
|
||||
#### Impact of Fix
|
||||
- **Before**: AI hanging indefinitely on invalid futures, ThreadPool backing up with 271+ queued tasks
|
||||
- **After**: AI runs smoothly with proper ThreadPool execution, normal performance restored
|
||||
- **Key Insight**: The ThreadPool itself was working correctly - the hang was caused by invalid futures from destroyed promises
|
||||
|
||||
### 🔧 Additional Defensive Improvement - Future Timeout Protection
|
||||
|
||||
Added `wait_until()` timeout protection before all `future.get()` calls with budget-aware timeouts:
|
||||
|
||||
```cpp
|
||||
// Before: Direct .get() call could hang indefinitely
|
||||
auto result = future.get();
|
||||
|
||||
// After: Budget-aware timeout protection with fallback
|
||||
auto timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(100); // Default
|
||||
if (timeBudget && timeBudget->remainingBudget.count() > 0) {
|
||||
timeout = std::chrono::steady_clock::now() + timeBudget->remainingBudget + std::chrono::milliseconds(100);
|
||||
}
|
||||
if (future.wait_until(timeout) == std::future_status::timeout) {
|
||||
// Fall back to immediate score or skip result
|
||||
return immediateScore;
|
||||
}
|
||||
auto result = future.get();
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- **Prevents hanging**: Even if ThreadPool has issues, system won't freeze
|
||||
- **Budget-aware**: Uses actual time budget + 100ms buffer instead of arbitrary timeouts
|
||||
- **Graceful degradation**: Falls back to immediate scores when tasks timeout
|
||||
- **User experience driven**: Respects the original deadline constraints for responsiveness
|
||||
- **Multi-layered protection**: ThreadPool deadline + budget-aware future timeout
|
||||
- **Conservative fallback**: Uses 2-second timeout in contexts without time budget
|
||||
|
||||
### Status: ✅ FULLY WORKING THREADPOOL MIGRATION - COMPLETE
|
||||
ThreadPool migration is complete and production-ready:
|
||||
- ✅ 32-thread pool with priority-based scheduling (higher depth = higher priority)
|
||||
- ✅ Deadline support with graceful timeout handling
|
||||
- ✅ TaskResult wrapper with explicit status checking (no implicit conversion)
|
||||
- ✅ Proper promise/future lifetime management
|
||||
- ✅ Defensive timeout protection on all future.get() calls
|
||||
- ✅ AI performance test completes successfully without hanging
|
||||
- ✅ **PERFORMANCE RESTORED**: timeBudget properly propagated through entire call chain
|
||||
- ✅ Multi-layered robustness against threading issues
|
||||
|
||||
### 🎯 Final Performance Fix - timeBudget Parameter Chain
|
||||
**Issue Resolved**: The main execution path (CommandScore → EvaluateCommand → CalcOne) was not using the time budget, causing 100ms default timeouts and severe performance degradation.
|
||||
|
||||
**Root Cause**: Missing timeBudget parameter in key call sites:
|
||||
- IterativeDeepeningAI → CommandScore (missing timeBudget parameter)
|
||||
- EvaluateCommand → CalcOne (passing nullptr instead of timeBudget)
|
||||
- BasicLookaheadCalculator → BestCommandIndex (missing timeBudget parameter)
|
||||
|
||||
**Solution Implemented**:
|
||||
1. **Updated method signatures**: Added `const AITimeBudget *timeBudget = nullptr` to BasicLookaheadCalculator
|
||||
2. **Fixed all CalcOne calls in EvaluateCommand**: Changed from `nullptr` to `timeBudget` (3 call sites)
|
||||
3. **Updated lambda capture in CalcOne**: Added timeBudget to capture list and pass to BasicLookaheadCalculator
|
||||
4. **Fixed IterativeDeepeningAI**: Added `&timeBudget` parameter to CommandScore call
|
||||
5. **Verified build and performance**: AI now properly uses time budget, reaches depth 2, shows correct budget values
|
||||
|
||||
**Performance Test Results**:
|
||||
- **Before**: Severe performance degradation, limited depth achievement
|
||||
- **After**: Normal performance restored, proper depth 2 searches, budget-aware timeouts working
|
||||
- **Evidence**: Logs show correct budget usage: `budget: 1500ms`, `budget: 1068ms`, `achieved depth 2`
|
||||
|
||||
The ThreadPool migration is now **FULLY COMPLETE** with all performance issues resolved.
|
||||
@@ -5,10 +5,7 @@
|
||||
#ifndef EAGLE0_GAMEUPDATERECEIVER_HPP
|
||||
#define EAGLE0_GAMEUPDATERECEIVER_HPP
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-redundant-constexpr-static-def"
|
||||
#include "src/main/protobuf/net/eagle0/shardok/api/action_result_view.pb.h"
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
namespace shardok {
|
||||
using std::vector;
|
||||
|
||||
@@ -8,11 +8,9 @@
|
||||
|
||||
#include "ShardokGameController.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <ranges>
|
||||
#include <thread>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/common/ContainerUtils.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
|
||||
#include "src/main/protobuf/net/eagle0/shardok/api/game_state_view.pb.h"
|
||||
@@ -87,10 +85,10 @@ void ShardokGameController::LockedNotifyClients() const { updateCondition.notify
|
||||
|
||||
auto ShardokGameController::LockedAIClientForPid(PlayerId pid) const
|
||||
-> shared_ptr<ShardokAIClient> {
|
||||
const auto it = std::ranges::find_if(aiClients, [pid](const auto &client) {
|
||||
return client->GetPlayerId() == pid;
|
||||
});
|
||||
return (it != aiClients.end()) ? *it : nullptr;
|
||||
return common::FindIf(
|
||||
aiClients,
|
||||
[pid](const auto &client) { return client->GetPlayerId() == pid; })
|
||||
.value_or(nullptr);
|
||||
}
|
||||
|
||||
void ShardokGameController::DoAIThread() {
|
||||
@@ -167,7 +165,7 @@ void ShardokGameController::PostCommand(
|
||||
|
||||
CheckFactionId(engine, shardokPlayerId, eagleFactionId);
|
||||
|
||||
const auto expectedToken = static_cast<int64_t>(engine->GetUnfilteredHistoryCount());
|
||||
const auto expectedToken = engine->GetUnfilteredHistoryCount();
|
||||
if (token < expectedToken) {
|
||||
printf("Double token in postCommand\n");
|
||||
// The client is missing some updates; probably it's a double-submit
|
||||
@@ -195,7 +193,7 @@ void ShardokGameController::PostPlacementCommands(
|
||||
|
||||
CheckFactionId(engine, shardokPlayerId, eagleFactionId);
|
||||
|
||||
const auto expectedToken = static_cast<int64_t>(engine->GetUnfilteredHistoryCount());
|
||||
const auto expectedToken = engine->GetUnfilteredHistoryCount();
|
||||
if (token < expectedToken) {
|
||||
printf("Double token in postPlacementCommands\n");
|
||||
// The client is missing some updates; probably it's a double-submit
|
||||
@@ -242,11 +240,9 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
|
||||
incomingRegistrations--;
|
||||
}
|
||||
|
||||
updates.mainResults.reserve(awrs.size());
|
||||
std::ranges::transform(
|
||||
awrs,
|
||||
std::back_inserter(updates.mainResults),
|
||||
[](const ShardokActionWithResultingState &a) { return a.action_result(); });
|
||||
updates.mainResults = common::Map(awrs, [](const ShardokActionWithResultingState &a) {
|
||||
return a.action_result();
|
||||
});
|
||||
|
||||
const auto playerInfos = engine->GetPlayerInfos();
|
||||
updates.filteredResults.reserve(playerInfos.size() + 1);
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
|
||||
#include "AvailableCommandsFactory.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/FireUtils.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/action_factories/PlayerSetupCommandFactory.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/action_result_applier/ActionResultApplier.hpp"
|
||||
@@ -135,30 +132,30 @@ void AvailableCommandsFactoryImpl::AddAvailableCommandsForOneUnit(
|
||||
}
|
||||
if (battType->adjustsMorale &&
|
||||
unit->battalion().morale() < settings.Backing().minimum_morale_to_act()) {
|
||||
std::erase_if(oneUnitCommands, [](const CommandSPtr &cmd) {
|
||||
return !cmd->CanDoWithLowMorale();
|
||||
common::FilterInPlace(oneUnitCommands, [](const CommandSPtr &cmd) {
|
||||
return cmd->CanDoWithLowMorale();
|
||||
});
|
||||
}
|
||||
if (unit->stun_rounds_remaining() > 0) {
|
||||
std::erase_if(oneUnitCommands, [](const CommandSPtr &cmd) {
|
||||
return !cmd->CanDoWhileStunned();
|
||||
common::FilterInPlace(oneUnitCommands, [](const CommandSPtr &cmd) {
|
||||
return cmd->CanDoWhileStunned();
|
||||
});
|
||||
}
|
||||
if (hasHero && unit->attached_hero().vigor() < settings.Backing().minimum_vigor_to_act()) {
|
||||
std::erase_if(oneUnitCommands, [](const CommandSPtr &cmd) {
|
||||
return !cmd->CanDoWithLowVigor();
|
||||
common::FilterInPlace(oneUnitCommands, [](const CommandSPtr &cmd) {
|
||||
return cmd->CanDoWithLowVigor();
|
||||
});
|
||||
}
|
||||
if (unitMovedIntoZoc) {
|
||||
std::erase_if(oneUnitCommands, [](const CommandSPtr &cmd) {
|
||||
return !cmd->CanDoAfterMovingIntoZoc();
|
||||
common::FilterInPlace(oneUnitCommands, [](const CommandSPtr &cmd) {
|
||||
return cmd->CanDoAfterMovingIntoZoc();
|
||||
});
|
||||
}
|
||||
if (std::ranges::any_of(oneUnitCommands, [](const CommandSPtr &cmd) {
|
||||
if (common::ContainsWhere(oneUnitCommands, [](const CommandSPtr &cmd) {
|
||||
return cmd->IsRequiredToEndTurn();
|
||||
})) {
|
||||
std::erase_if(oneUnitCommands, [](const CommandSPtr &cmd) {
|
||||
return !cmd->IsRequiredToEndTurn();
|
||||
common::FilterInPlace(oneUnitCommands, [](const CommandSPtr &cmd) {
|
||||
return cmd->IsRequiredToEndTurn();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -186,7 +183,7 @@ auto AvailableCommandsFactoryImpl::GetAvailableCommands(
|
||||
/* onlyFollowUps=*/false);
|
||||
}
|
||||
|
||||
if (!std::ranges::any_of(commands, [](const CommandSPtr &command) {
|
||||
if (!common::ContainsWhere(commands, [](const CommandSPtr &command) {
|
||||
return command->IsRequiredToEndTurn();
|
||||
})) {
|
||||
commands.push_back(std::make_shared<EndTurnCommand>(playerId, gameState, settings));
|
||||
|
||||
@@ -8,6 +8,7 @@ cc_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":shardok_c_types",
|
||||
"//src/main/cpp/net/eagle0/common:container_utils",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/fb_helpers:flatbuffer_wrapper",
|
||||
"//src/main/flatbuffer/net/eagle0/shardok/storage:game_state_cc_fbs",
|
||||
"//src/main/flatbuffer/net/eagle0/shardok/storage:unit_cc_fbs",
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
#include "GameStateW.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include "src/main/cpp/net/eagle0/common/ContainerUtils.hpp"
|
||||
|
||||
namespace shardok {
|
||||
|
||||
@@ -25,17 +24,13 @@ auto GameStateW::GetOccupant(const net::eagle0::shardok::storage::fb::Coords& co
|
||||
|
||||
// Fast path: use bitfield cache if available
|
||||
if (state->occupied_tiles() && !state->occupied_tiles()->empty()) {
|
||||
const size_t tileIndex =
|
||||
static_cast<size_t>(coords.row()) * static_cast<size_t>(columnCount) +
|
||||
static_cast<size_t>(coords.column());
|
||||
const size_t expectedBitfieldSize =
|
||||
(static_cast<size_t>(rowCount) * static_cast<size_t>(columnCount) + 7) /
|
||||
8; // Ceiling division
|
||||
const size_t tileIndex = coords.row() * columnCount + coords.column();
|
||||
const size_t expectedBitfieldSize = (rowCount * columnCount + 7) / 8; // Ceiling division
|
||||
|
||||
if (state->occupied_tiles()->size() == expectedBitfieldSize) {
|
||||
const size_t byteIndex = tileIndex / 8;
|
||||
const size_t bitOffset = tileIndex % 8;
|
||||
const uint8_t byte = state->occupied_tiles()->Get(static_cast<unsigned int>(byteIndex));
|
||||
const uint8_t byte = state->occupied_tiles()->Get(byteIndex);
|
||||
const bool isOccupied = (byte & (1 << bitOffset)) != 0;
|
||||
|
||||
if (!isOccupied) {
|
||||
@@ -48,8 +43,8 @@ auto GameStateW::GetOccupant(const net::eagle0::shardok::storage::fb::Coords& co
|
||||
// Used when bitfield not available OR when bitfield indicates occupation
|
||||
if (!state->units()) { return nullptr; }
|
||||
|
||||
for (size_t i = 0; i < state->units()->size(); ++i) {
|
||||
const auto* unit = state->units()->Get(static_cast<unsigned int>(i));
|
||||
for (int i = 0; i < state->units()->size(); ++i) {
|
||||
const auto* unit = state->units()->Get(i);
|
||||
if (unit && unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
|
||||
unit->location().row() == coords.row() &&
|
||||
unit->location().column() == coords.column()) {
|
||||
@@ -67,7 +62,7 @@ auto GameStateW::GetKnownEnemyOccupant(
|
||||
const auto* occupant = GetOccupant(coords);
|
||||
if (occupant) {
|
||||
if (!occupant->hidden() && occupant->player_id() != playerId &&
|
||||
!std::ranges::contains(allyPids, occupant->player_id())) {
|
||||
!common::Contains(allyPids, occupant->player_id())) {
|
||||
return occupant;
|
||||
}
|
||||
}
|
||||
@@ -87,30 +82,26 @@ void GameStateW::UpdateOccupiedTile(
|
||||
// Clear old position in bitfield
|
||||
if (oldCoords.row() >= 0 && oldCoords.row() < rowCount && oldCoords.column() >= 0 &&
|
||||
oldCoords.column() < columnCount) {
|
||||
const size_t tileIndex =
|
||||
static_cast<size_t>(oldCoords.row()) * static_cast<size_t>(columnCount) +
|
||||
static_cast<size_t>(oldCoords.column());
|
||||
const size_t tileIndex = oldCoords.row() * columnCount + oldCoords.column();
|
||||
const size_t byteIndex = tileIndex / 8;
|
||||
const size_t bitOffset = tileIndex % 8;
|
||||
if (byteIndex < mutableOccupiedTiles->size()) {
|
||||
uint8_t byte = mutableOccupiedTiles->Get(static_cast<unsigned int>(byteIndex));
|
||||
uint8_t byte = mutableOccupiedTiles->Get(byteIndex);
|
||||
byte &= ~(1 << bitOffset); // Clear the bit
|
||||
mutableOccupiedTiles->Mutate(static_cast<unsigned int>(byteIndex), byte);
|
||||
mutableOccupiedTiles->Mutate(byteIndex, byte);
|
||||
}
|
||||
}
|
||||
|
||||
// Set new position in bitfield
|
||||
if (newCoords.row() >= 0 && newCoords.row() < rowCount && newCoords.column() >= 0 &&
|
||||
newCoords.column() < columnCount) {
|
||||
const size_t tileIndex =
|
||||
static_cast<size_t>(newCoords.row()) * static_cast<size_t>(columnCount) +
|
||||
static_cast<size_t>(newCoords.column());
|
||||
const size_t tileIndex = newCoords.row() * columnCount + newCoords.column();
|
||||
const size_t byteIndex = tileIndex / 8;
|
||||
const size_t bitOffset = tileIndex % 8;
|
||||
if (byteIndex < mutableOccupiedTiles->size()) {
|
||||
uint8_t byte = mutableOccupiedTiles->Get(static_cast<unsigned int>(byteIndex));
|
||||
uint8_t byte = mutableOccupiedTiles->Get(byteIndex);
|
||||
byte |= (1 << bitOffset); // Set the bit
|
||||
mutableOccupiedTiles->Mutate(static_cast<unsigned int>(byteIndex), byte);
|
||||
mutableOccupiedTiles->Mutate(byteIndex, byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,8 +115,7 @@ auto GameStateW::GetOccupiedTilesBitfield() const -> const flatbuffers::Vector<u
|
||||
// Verify the bitfield size matches expected map size
|
||||
const int16_t rowCount = state->hex_map()->row_count();
|
||||
const int16_t columnCount = state->hex_map()->column_count();
|
||||
const size_t expectedBitfieldSize =
|
||||
(static_cast<size_t>(rowCount) * static_cast<size_t>(columnCount) + 7) / 8;
|
||||
const size_t expectedBitfieldSize = (rowCount * columnCount + 7) / 8;
|
||||
|
||||
if (state->occupied_tiles()->size() != expectedBitfieldSize) { return nullptr; }
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
#ifndef EAGLE0_GAMESTATEW_HPP
|
||||
#define EAGLE0_GAMESTATEW_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCTypes.h"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/FlatbufferWrapper.hpp"
|
||||
#include "src/main/flatbuffer/net/eagle0/shardok/storage/game_state.hpp"
|
||||
|
||||
@@ -12,10 +12,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-redundant-constexpr-static-def"
|
||||
#include "src/main/protobuf/net/eagle0/shardok/storage/odds.pb.h"
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
namespace shardok {
|
||||
typedef net::eagle0::shardok::storage::Odds PercentileRollOdds;
|
||||
|
||||
@@ -14,10 +14,7 @@
|
||||
#include "ShardokException.hpp"
|
||||
#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-redundant-constexpr-static-def"
|
||||
#include "src/main/protobuf/net/eagle0/shardok/storage/action_result.pb.h"
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
namespace shardok {
|
||||
|
||||
@@ -45,9 +42,9 @@ private:
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual auto InternalExecuteWithRoll(
|
||||
const GameStateW& /*currentState*/,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/,
|
||||
std::optional<int32_t> /*roll*/) const -> std::vector<ActionResult> {
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& generator,
|
||||
std::optional<int32_t> roll) const -> std::vector<ActionResult> {
|
||||
throw ShardokClientErrorException("Roll not supported");
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
[[nodiscard]] virtual auto HasOdds() const -> bool { return false; }
|
||||
[[nodiscard]] virtual auto GetOddsPercentile() const -> int32_t { return 0; }
|
||||
|
||||
virtual void AddFollowUpCommandTypes(const std::unordered_set<CommandType>& /*newTypes*/) {
|
||||
virtual void AddFollowUpCommandTypes(const std::unordered_set<CommandType>& newTypes) {
|
||||
throw ShardokInternalErrorException("Can't add follow up commands to this type");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
#include "ShardokEngine.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -312,19 +310,21 @@ void ShardokEngine::PostPlacementCommands(
|
||||
availableCommandsFactory->GetPlayerSetupCommands(gameState, player);
|
||||
|
||||
// first make sure they're all valid and there are no duplicates
|
||||
for (size_t i = 0; i < placementInfos.size(); i++) {
|
||||
for (int i = 0; i < placementInfos.size(); i++) {
|
||||
const UnitPlacementInfo &pi = placementInfos[i];
|
||||
|
||||
const auto it = std::ranges::find_if(*placementCommands, [pi](const CommandSPtr &cmd) {
|
||||
return cmd->GetCommandProto().actor().value() == pi.unitId &&
|
||||
cmd->GetCommandProto().target() == pi.location;
|
||||
});
|
||||
if (it == placementCommands->end()) {
|
||||
if (auto command = common::FindIf(
|
||||
*placementCommands,
|
||||
[pi](const CommandSPtr &cmd) {
|
||||
return cmd->GetCommandProto().actor().value() == pi.unitId &&
|
||||
cmd->GetCommandProto().target() == pi.location;
|
||||
});
|
||||
!command.has_value()) {
|
||||
throw ShardokClientErrorException("No such placement info found");
|
||||
}
|
||||
|
||||
// check that we're not double-filling any location or double-placing any unit
|
||||
for (size_t j = i + 1; j < placementInfos.size(); j++) {
|
||||
for (int j = i + 1; j < placementInfos.size(); j++) {
|
||||
const UnitPlacementInfo &other = placementInfos[j];
|
||||
|
||||
if (pi.unitId == other.unitId)
|
||||
@@ -339,11 +339,12 @@ void ShardokEngine::PostPlacementCommands(
|
||||
|
||||
// now execute
|
||||
for (const auto &pi : placementInfos) {
|
||||
const auto it = std::ranges::find_if(*placementCommands, [pi](const CommandSPtr &cmd) {
|
||||
auto command = common::FindIf(*placementCommands, [pi](const CommandSPtr &cmd) {
|
||||
return cmd->GetCommandProto().actor().value() == pi.unitId &&
|
||||
cmd->GetCommandProto().target() == pi.location;
|
||||
});
|
||||
for (vector<ActionResult> onePlacementResults = (*it)->Execute(gameState, randomGenerator);
|
||||
for (vector<ActionResult> onePlacementResults =
|
||||
(*command)->Execute(gameState, randomGenerator);
|
||||
const ActionResultProto &oneResult : onePlacementResults) {
|
||||
HandleActionResult(oneResult, randomGenerator);
|
||||
}
|
||||
@@ -375,17 +376,17 @@ void ShardokEngine::PostFinishedPlacementCommand(
|
||||
const auto placementCommands =
|
||||
availableCommandsFactory->GetPlayerSetupCommands(gameState, player);
|
||||
|
||||
const auto it = std::ranges::find_if(*placementCommands, [](const CommandSPtr &cmd) {
|
||||
const auto command = common::FindIf(*placementCommands, [](const CommandSPtr &cmd) {
|
||||
return cmd->GetCommandProto().type() ==
|
||||
net::eagle0::shardok::common::END_PLAYER_SETUP_COMMAND;
|
||||
});
|
||||
|
||||
if (it == placementCommands->end()) {
|
||||
if (!command.has_value()) {
|
||||
throw ShardokClientErrorException("No finish placement command found");
|
||||
}
|
||||
cachedAvailableCommands = nullptr;
|
||||
|
||||
PostActionUnchecked(*it, randomGenerator, std::nullopt);
|
||||
PostActionUnchecked(command.value(), randomGenerator, std::nullopt);
|
||||
}
|
||||
|
||||
void ShardokEngine::PostCommand(
|
||||
|
||||
+2
-2
@@ -87,11 +87,11 @@ auto PlayerSetupCommandFactory::AddAvailablePlayerSetupCommands(
|
||||
if (placedUnits.size() >= 10) return;
|
||||
if (unplacedUnits.empty()) return;
|
||||
|
||||
for (const auto &[unitId, unit] : unplacedUnits) {
|
||||
for (const auto &kv : unplacedUnits) {
|
||||
AddAvailablePlaceAndHideUnitCommandsForOneUnit(
|
||||
existingCommands,
|
||||
isDefender,
|
||||
unit,
|
||||
kv.second,
|
||||
gameState);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-9
@@ -135,14 +135,14 @@ auto ActionPointDistancesCache::GetRaw(
|
||||
MakeCacheKey(mapId, battalionType, includeBravingWater, braveWaterActionPointCost);
|
||||
|
||||
// Check the persistent map first
|
||||
if (persistentCache.contains(cacheKey)) {
|
||||
if (auto persistentIt = persistentCache.find(cacheKey); persistentIt != persistentCache.end()) {
|
||||
#if CACHE_STATS_LOGGING_
|
||||
cacheStats.persistentHits++;
|
||||
MaybePrintCacheStats();
|
||||
#endif
|
||||
// Return directly from persistent cache without TLS insertion
|
||||
// This avoids the overhead of thread-local storage operations on hot path
|
||||
return persistentCache.at(cacheKey).rawPtr;
|
||||
return persistentIt->second.rawPtr;
|
||||
}
|
||||
|
||||
#if CACHE_STATS_LOGGING_
|
||||
@@ -150,12 +150,12 @@ auto ActionPointDistancesCache::GetRaw(
|
||||
#endif
|
||||
|
||||
// Check thread-local cache first (no locks needed!)
|
||||
if (tlsCache.contains(cacheKey)) {
|
||||
if (auto localIt = tlsCache.find(cacheKey); localIt != tlsCache.end()) {
|
||||
#if CACHE_STATS_LOGGING_
|
||||
cacheStats.localHits++;
|
||||
MaybePrintCacheStats();
|
||||
#endif
|
||||
return tlsCache.at(cacheKey).rawPtr; // Raw pointer - zero overhead access!
|
||||
return localIt->second.rawPtr; // Raw pointer - zero overhead access!
|
||||
}
|
||||
|
||||
#if CACHE_STATS_LOGGING_
|
||||
@@ -184,13 +184,10 @@ auto ActionPointDistancesCache::GetRaw(
|
||||
// Declaring here to keep the copied map in scope
|
||||
const HexMap* mapToUse = map;
|
||||
|
||||
// ReSharper disable once CppTooWideScope
|
||||
// ReSharper disable once CppJoinDeclarationAndAssignment
|
||||
fb::HexMapW iceClearedMap;
|
||||
if (hasIce) {
|
||||
// Create ice-cleared map for pathfinding
|
||||
// This prevents AI from considering ice as a valid path toward enemies
|
||||
iceClearedMap = CreateIceClearedMap(map);
|
||||
fb::HexMapW iceClearedMap = CreateIceClearedMap(map);
|
||||
mapToUse = iceClearedMap.Get();
|
||||
}
|
||||
|
||||
@@ -217,7 +214,7 @@ auto ActionPointDistancesCache::GetRaw(
|
||||
// Store in shared cache
|
||||
sharedDistances.lazy_emplace_l(
|
||||
cacheKey,
|
||||
[](const auto& /*kv*/) { /* already checked above */ },
|
||||
[](const auto& kv) { /* already checked above */ },
|
||||
[=](const auto& ctor) { ctor(cacheKey, result); });
|
||||
|
||||
// Cache result locally for future lookups by this thread
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ void FixedActionPointDistances::SetCacheDirectory(const string& newDir) {
|
||||
|
||||
static thread_local byte_vector _scratch;
|
||||
|
||||
FixedActionPointDistances::FixedActionPointDistances(const HexMap* /*map*/, int columnCount)
|
||||
FixedActionPointDistances::FixedActionPointDistances(const HexMap* map, int columnCount)
|
||||
: ActionPointDistances(columnCount) {}
|
||||
|
||||
auto FixedActionPointDistances::Create(
|
||||
|
||||
+3
-3
@@ -130,8 +130,8 @@ void ApplyResolvedUnit(
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(inoutState.units, [unitId](const auto &unit) {
|
||||
return unit.unit_id() == unitId;
|
||||
common::FilterInPlace(inoutState.units, [unitId](const auto &unit) {
|
||||
return unit.unit_id() != unitId;
|
||||
});
|
||||
inoutState.units[unitId] = *((Unit *)resolvedUnit.unit_bytes().data());
|
||||
inoutState.units[unitId].mutate_status(
|
||||
@@ -161,7 +161,7 @@ void MutatingAddUnits(GameStateW &mutatingState, const ActionResultProto &result
|
||||
const auto *unit = (Unit *)unitBytes.data();
|
||||
maxChangedUnitId = std::max(maxChangedUnitId, unit->unit_id());
|
||||
|
||||
if (static_cast<unsigned int>(unit->unit_id()) >= mutatingState->units()->size()) {
|
||||
if (unit->unit_id() >= mutatingState->units()->size()) {
|
||||
// Unit ID beyond vector size - must expand
|
||||
needsVectorExpansion = true;
|
||||
break; // No point checking further
|
||||
|
||||
@@ -48,7 +48,7 @@ auto CopyWithExtraUnits(const GameStateW& original, int additionalCount) -> Game
|
||||
// Fallback: create new bitfield only if original doesn't have one
|
||||
const int16_t rowCount = endGST.hex_map->row_count;
|
||||
const int16_t columnCount = endGST.hex_map->column_count;
|
||||
const size_t mapSize = static_cast<size_t>(rowCount) * static_cast<size_t>(columnCount);
|
||||
const size_t mapSize = rowCount * columnCount;
|
||||
const size_t bitfieldSize = (mapSize + 7) / 8; // Ceiling division
|
||||
endGST.occupied_tiles.resize(bitfieldSize, 0); // Initialize all bits to 0 (empty)
|
||||
|
||||
@@ -58,9 +58,7 @@ auto CopyWithExtraUnits(const GameStateW& original, int additionalCount) -> Game
|
||||
const auto& location = unit.location();
|
||||
if (location.row() >= 0 && location.row() < rowCount && location.column() >= 0 &&
|
||||
location.column() < columnCount) {
|
||||
const size_t tileIndex =
|
||||
static_cast<size_t>(location.row()) * static_cast<size_t>(columnCount) +
|
||||
static_cast<size_t>(location.column());
|
||||
const size_t tileIndex = location.row() * columnCount + location.column();
|
||||
const size_t byteIndex = tileIndex / 8;
|
||||
const size_t bitOffset = tileIndex % 8;
|
||||
endGST.occupied_tiles[byteIndex] |= (1 << bitOffset); // Set the bit
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace shardok {
|
||||
auto DefensiveAmbushAction::InternalExecute(
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
const auto results = CombatUtils::InternalPerformMelee(
|
||||
ActionCost(ActionCost::standard, 0),
|
||||
currentState->units()->Get(ambusherId),
|
||||
|
||||
@@ -23,7 +23,7 @@ FireOutAction::FireOutAction(
|
||||
fireOutOdds(std::move(odds)) {}
|
||||
|
||||
auto FireOutAction::InternalExecute(
|
||||
const GameStateW& /*currentState*/,
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
const auto fireOutRoll = generator->Percentile();
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ FireSpreadAction::FireSpreadAction(
|
||||
fireSpreadOdds(std::move(odds)) {}
|
||||
|
||||
auto FireSpreadAction::InternalExecute(
|
||||
const GameStateW& /*currentState*/,
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
const auto fireSpreadRoll = generator->Percentile();
|
||||
|
||||
|
||||
@@ -71,8 +71,8 @@ public:
|
||||
};
|
||||
|
||||
auto MeteorUnitDamageAction::InternalExecute(
|
||||
const GameStateW & /*currentState*/,
|
||||
const std::shared_ptr<RandomGenerator> & /*generator*/) const -> vector<ActionResultProto> {
|
||||
const GameStateW ¤tState,
|
||||
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResultProto> {
|
||||
CombatDamage attackerDamage =
|
||||
CombatDamage::Builder()
|
||||
.SetFire(attackerIntelligence * baseDamage * damageMultiplier)
|
||||
@@ -131,8 +131,8 @@ public:
|
||||
};
|
||||
|
||||
auto MeteorTileDamageAction::InternalExecute(
|
||||
const GameStateW & /*currentState*/,
|
||||
const std::shared_ptr<RandomGenerator> & /*generator*/) const -> vector<ActionResultProto> {
|
||||
const GameStateW ¤tState,
|
||||
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResultProto> {
|
||||
auto tm = fb::ToTileModifierProto(terrain->modifier());
|
||||
|
||||
MutatingAdjustBridgeIntegrity(&tm, integrityAdjustment);
|
||||
@@ -154,7 +154,7 @@ auto MeteorTileDamageAction::InternalExecute(
|
||||
}
|
||||
|
||||
auto MeteorCastAction::InternalExecute(
|
||||
const GameStateW & /*currentState*/,
|
||||
const GameStateW ¤tState,
|
||||
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResultProto> {
|
||||
vector<ActionResultProto> allResults{};
|
||||
auto runningGameState = startingGameState;
|
||||
|
||||
@@ -97,7 +97,7 @@ auto BurnStructuresResult(const GameStateW &gameState, const SettingsGetter &set
|
||||
auto NewWeather(
|
||||
const net::eagle0::shardok::storage::fb::MonthlyWeather &monthlyWeather,
|
||||
const WeatherFb &oldWeather,
|
||||
const SettingsGetter & /*settings*/,
|
||||
const SettingsGetter &settings,
|
||||
const std::shared_ptr<RandomGenerator> &randomGenerator) -> Weather {
|
||||
const Weather::Conditions newConditions =
|
||||
ConditionsByMonth(monthlyWeather, randomGenerator->Percentile());
|
||||
|
||||
@@ -21,7 +21,7 @@ auto ChooseUndeadCommand(
|
||||
const std::shared_ptr<RandomGenerator> &randomGenerator) -> CommandSPtr;
|
||||
|
||||
auto PerformUndeadCommandsAction::InternalExecute(
|
||||
const GameStateW & /*currentState*/,
|
||||
const GameStateW ¤tState,
|
||||
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResultProto> {
|
||||
GameStateW runningGameState = startingGameState;
|
||||
vector<ActionResultProto> allResults{};
|
||||
|
||||
@@ -14,7 +14,7 @@ using net::eagle0::shardok::common::GameStatus;
|
||||
|
||||
[[nodiscard]] auto PlaceHiddenUnitCommand::InternalExecute(
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
auto actorAfter = *currentState->units()->Get(actorId);
|
||||
actorAfter.mutable_location() = target;
|
||||
actorAfter.mutate_hidden(true);
|
||||
|
||||
@@ -12,8 +12,8 @@ using net::eagle0::shardok::common::ActionType;
|
||||
using net::eagle0::shardok::common::GameStatus;
|
||||
|
||||
auto PlaceUnitCommand::InternalExecute(
|
||||
const GameStateW& /*currentState*/,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
auto actorAfter = *actor;
|
||||
actorAfter.mutable_location() = target;
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ auto effectiveSnow(const TerrainProto& terr) -> double {
|
||||
}
|
||||
|
||||
auto SnowAdjustmentAction::InternalExecute(
|
||||
const GameStateW& /*currentState*/,
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
vector<ActionResult> results{};
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ static auto RequiredDailyFood(const Unit& unit, const SettingsGetter& settingsGe
|
||||
}
|
||||
|
||||
auto StartPlayerTurnAction::InternalExecute(
|
||||
const GameStateW& /*currentState*/,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
ActionResult result{};
|
||||
result.set_type(net::eagle0::shardok::common::ActionType::PLAYER_TURN_START);
|
||||
result.mutable_next_player()->set_value(newFactionId);
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace shardok {
|
||||
|
||||
auto UndeadChangeAction::InternalExecute(
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
if (undeadUnitIds.empty()) return {};
|
||||
|
||||
const int maxGrowthPer = (int)(reinforceRate * bodyCount / (double)undeadUnitIds.size());
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
namespace shardok {
|
||||
|
||||
auto UndeadFrozenAction::InternalExecute(
|
||||
const GameStateW& /*currentState*/,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
if (occupant->battalion().type() != net::eagle0::shardok::storage::fb::BattalionTypeId_UNDEAD)
|
||||
return {};
|
||||
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
|
||||
#include "UpdateGameStatusAction.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/ActionResultFlatbufferHelpers.hpp"
|
||||
#include "src/main/flatbuffer/net/eagle0/shardok/storage/game_status.hpp"
|
||||
#include "src/main/flatbuffer/net/eagle0/shardok/storage/player_info.hpp"
|
||||
@@ -68,7 +65,7 @@ void UpdateGameStatusAction::ResolveHiddenLosers(ActionResult& actionResult) con
|
||||
for (const auto* unit : *gameState->units()) {
|
||||
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
|
||||
|
||||
if (!std::ranges::contains(winningIds, unit->player_id()) && unit->hidden() &&
|
||||
if (!common::Contains(winningIds, unit->player_id()) && unit->hidden() &&
|
||||
unit->can_flee() && unit->has_attached_hero() &&
|
||||
unit->attached_hero().vigor() >= settingsGetter.Backing().minimum_vigor_to_act()) {
|
||||
net::eagle0::shardok::storage::ResolvedUnit* fledRanger =
|
||||
@@ -83,7 +80,7 @@ void UpdateGameStatusAction::ResolveHiddenLosers(ActionResult& actionResult) con
|
||||
|
||||
auto UpdateGameStatusAction::InternalExecute(
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
vector<ActionResult> results{};
|
||||
|
||||
// If the game has already ended, just return that state
|
||||
@@ -151,7 +148,7 @@ auto UpdateGameStatusAction::InternalExecute(
|
||||
|
||||
for (const PlayerId pid2 : survivors) {
|
||||
if (pid1 == pid2) continue;
|
||||
if (!std::ranges::any_of(
|
||||
if (!common::ContainsWhere(
|
||||
*p1Info->allies(),
|
||||
[pid2](const net::eagle0::shardok::storage::fb::AlliedPlayer*
|
||||
alliedPlayer) {
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace shardok {
|
||||
|
||||
auto UpdateOpponentKnowledgeAction::InternalExecute(
|
||||
const GameStateW ¤tState,
|
||||
const std::shared_ptr<RandomGenerator> & /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResult> {
|
||||
ActionResult result{};
|
||||
result.set_type(net::eagle0::shardok::common::ActionType::KNOWLEDGE_UPDATED);
|
||||
|
||||
@@ -31,7 +31,7 @@ auto UpdateOpponentKnowledgeAction::InternalExecute(
|
||||
|
||||
for (PlayerId pid = 0; pid < 10; pid++) {
|
||||
if (pid == unitPid) continue;
|
||||
if (static_cast<unsigned int>(pid) >= playerCount) continue;
|
||||
if (pid >= playerCount) continue;
|
||||
int bump = PlayerIsDefender(currentState, pid) ? defenderKnowledgeGain
|
||||
: attackerKnowledgeGain;
|
||||
MutatingBumpOpponentKnowledge(&unitAfter, pid, bump);
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
|
||||
#include "FleeCommandFactory.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/commands/BecomeOutlawCommand.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/commands/FleeCommand.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
|
||||
@@ -44,7 +41,7 @@ void FleeCommandFactory::AddAvailableFleeCommands(
|
||||
for (const auto &adjTile : HexMapUtils::GetAdjacentTiles(map, unit->location())) {
|
||||
const auto *occupant = Occupant(allUnits, adjTile.coords);
|
||||
if (occupant && !occupant->hidden()) {
|
||||
if (std::ranges::contains(allyPids, occupant->player_id())) {
|
||||
if (common::Contains(allyPids, occupant->player_id())) {
|
||||
adjacentFriendliesMod += perAdjacentFriendly;
|
||||
} else {
|
||||
adjacentEnemiesMod += perAdjacentEnemy;
|
||||
@@ -58,7 +55,7 @@ void FleeCommandFactory::AddAvailableFleeCommands(
|
||||
for (const auto &twoAwayCoords : TilesWithExactDistance(map, unit->location(), 2)) {
|
||||
const auto *occupant = Occupant(allUnits, twoAwayCoords);
|
||||
if (occupant && !occupant->hidden()) {
|
||||
if (std::ranges::contains(allyPids, occupant->player_id())) {
|
||||
if (common::Contains(allyPids, occupant->player_id())) {
|
||||
adjacentFriendliesMod += int32_t(perAdjacentFriendly * adjustmentForTwoAway);
|
||||
} else {
|
||||
adjacentEnemiesMod += int32_t(perAdjacentEnemy * adjustmentForTwoAway);
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
|
||||
#include "HideCommandFactory.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/commands/HideCommand.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/ZoneOfControlCalculator.hpp"
|
||||
@@ -29,7 +26,7 @@ auto HideCommandFactory::MakeHideCommand(const Unit *actor, const Coords &target
|
||||
|
||||
auto HideCommandFactory::PositionIsHideable(
|
||||
const Unit *actor,
|
||||
CommandList & /*existingCommands*/,
|
||||
CommandList &existingCommands,
|
||||
const HexMap *hexMap,
|
||||
const Coords &target,
|
||||
const Units *units,
|
||||
@@ -39,7 +36,7 @@ auto HideCommandFactory::PositionIsHideable(
|
||||
const auto occupant = Occupant(units, target);
|
||||
if (occupant) {
|
||||
if (occupant->player_id() == actor->player_id()) return false;
|
||||
if (std::ranges::contains(allyPids, occupant->player_id())) return false;
|
||||
if (common::Contains(allyPids, occupant->player_id())) return false;
|
||||
if (!occupant->hidden()) return false;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "HolyWaveCommandFactory.hpp"
|
||||
|
||||
#include "src/main/cpp/net/eagle0/common/ContainerUtils.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/commands/HolyWaveCommand.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
|
||||
|
||||
@@ -45,7 +46,7 @@ void HolyWaveCommandFactory::AddAvailableCommands(
|
||||
AddAvailableHolyWaveCommands(commands, params.unit, params.remainingActionPoints);
|
||||
}
|
||||
|
||||
auto CanHolyWave(const SettingsGetter & /*settings*/, const Unit *unit) -> bool {
|
||||
auto CanHolyWave(const SettingsGetter &settings, const Unit *unit) -> bool {
|
||||
if (!unit->has_attached_hero()) return false;
|
||||
|
||||
return unit->attached_hero().profession_info().profession() ==
|
||||
|
||||
@@ -21,9 +21,9 @@ void ScoutCommandFactory::AddAvailableScoutCommands(
|
||||
CommandList& commands,
|
||||
const Unit* unit,
|
||||
const ActionPoints remainingActionPoints,
|
||||
const Coords& /*position*/,
|
||||
const Coords& position,
|
||||
const HexMap* hexMap,
|
||||
const Units* /*units*/) const {
|
||||
const Units* units) const {
|
||||
if (CanScout(settings, unit)) {
|
||||
const auto& hero = unit->attached_hero();
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ using net::eagle0::shardok::common::ActionType;
|
||||
|
||||
auto ControlCommand::InternalExecute(
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
const Unit* actor = currentState->units()->Get(actorId);
|
||||
if (!actor->has_attached_hero() ||
|
||||
actor->attached_hero().control_info().controlled_unit_id() == -1) {
|
||||
|
||||
@@ -38,7 +38,7 @@ auto ApplyAndAdd(
|
||||
}
|
||||
|
||||
auto EndTurnCommand::InternalExecute(
|
||||
const GameStateW & /*currentState*/,
|
||||
const GameStateW ¤tState,
|
||||
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResultProto> {
|
||||
if (GetPlayerId() != gameState->current_player()) {
|
||||
throw ShardokInternalErrorException(
|
||||
@@ -73,8 +73,8 @@ auto EndTurnCommand::InternalExecute(
|
||||
// Any heroes in fire get burninated
|
||||
// Decrement stun counters, increment cast state
|
||||
const auto beforeFireUnitsCount = runningGameState->units()->size();
|
||||
for (size_t i = 0; i < beforeFireUnitsCount; i++) {
|
||||
const auto *beforeUnit = runningGameState->units()->Get(static_cast<unsigned int>(i));
|
||||
for (int i = 0; i < beforeFireUnitsCount; i++) {
|
||||
const auto *beforeUnit = runningGameState->units()->Get(i);
|
||||
if (beforeUnit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT)
|
||||
continue;
|
||||
if (beforeUnit->player_id() != GetPlayerId()) continue;
|
||||
@@ -112,10 +112,10 @@ auto EndTurnCommand::InternalExecute(
|
||||
endTurnResult.mutable_next_player()->set_value(NextPlayerId(gameState, GetPlayerId()));
|
||||
|
||||
const auto unitsAtEndOfRoundCount = runningGameState->units()->size();
|
||||
for (size_t i = 0; i < unitsAtEndOfRoundCount; i++) {
|
||||
for (int i = 0; i < unitsAtEndOfRoundCount; i++) {
|
||||
// Grab the unit fresh because it might have been modified by a previous iteration of the
|
||||
// loop
|
||||
const auto *eorUnit = runningGameState->units()->Get(static_cast<unsigned int>(i));
|
||||
const auto *eorUnit = runningGameState->units()->Get(i);
|
||||
if (eorUnit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT)
|
||||
continue;
|
||||
if (eorUnit->player_id() != GetPlayerId()) continue;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace shardok {
|
||||
|
||||
auto FortifyCommand::InternalExecute(
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
ActionResult fortifyResult{};
|
||||
fortifyResult.set_type(net::eagle0::shardok::common::FORTIFIED);
|
||||
fortifyResult.mutable_player()->set_value(GetPlayerId());
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#include "HideCommand.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include <utility>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/unit/Unit.hpp"
|
||||
@@ -31,7 +29,7 @@ HideCommand::HideCommand(
|
||||
|
||||
auto HideCommand::InternalExecute(
|
||||
const GameStateW ¤tState,
|
||||
const std::shared_ptr<RandomGenerator> & /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResult> {
|
||||
auto actorAfter = *currentState->units()->Get(actorId);
|
||||
MutatingSpendActionPoints(&actorAfter, cost);
|
||||
|
||||
@@ -62,7 +60,7 @@ auto HideCommand::InternalExecute(
|
||||
if (!oneOverOccupant) continue;
|
||||
PlayerId occupantPid = oneOverOccupant->player_id();
|
||||
if (occupantPid == GetPlayerId()) continue;
|
||||
if (std::ranges::contains(alliedPids, occupantPid)) continue;
|
||||
if (common::Contains(alliedPids, occupantPid)) continue;
|
||||
|
||||
if (!oneOverOccupant->has_attached_hero()) continue;
|
||||
if (oneOverOccupant->attached_hero().profession_info().profession() ==
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "HolyWaveCommand.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include <utility>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/action_result_applier/ActionResultApplier.hpp"
|
||||
@@ -51,7 +50,7 @@ public:
|
||||
|
||||
auto HolyWaveDamageAction::InternalExecute(
|
||||
const GameStateW ¤tState,
|
||||
const std::shared_ptr<RandomGenerator> & /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResult> {
|
||||
ActionResult resultProto;
|
||||
resultProto.mutable_player()->set_value(playerId);
|
||||
resultProto.mutable_actor()->set_value(actingUnitId);
|
||||
@@ -132,7 +131,7 @@ public:
|
||||
|
||||
auto HolyWaveInspireAction::InternalExecute(
|
||||
const GameStateW ¤tState,
|
||||
const std::shared_ptr<RandomGenerator> & /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResult> {
|
||||
ActionResult resultProto;
|
||||
resultProto.mutable_player()->set_value(playerId);
|
||||
resultProto.mutable_actor()->set_value(actingUnitId);
|
||||
@@ -198,7 +197,7 @@ HolyWaveCommand::HolyWaveCommand(
|
||||
|
||||
auto HolyWaveCommand::InternalExecute(
|
||||
const GameStateW ¤tState,
|
||||
const std::shared_ptr<RandomGenerator> & /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResult> {
|
||||
vector<ActionResult> results;
|
||||
|
||||
const Unit *actorBefore = currentState->units()->Get(actorId);
|
||||
@@ -238,7 +237,7 @@ auto HolyWaveCommand::InternalExecute(
|
||||
undeadIds.push_back(occupant->unit_id());
|
||||
} else if (
|
||||
occupant->player_id() == GetPlayerId() ||
|
||||
std::ranges::contains(allyPids, GetPlayerId())) {
|
||||
common::Contains(allyPids, GetPlayerId())) {
|
||||
allFriendlyUnitIds.push_back(occupant->unit_id());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ auto LightningBoltCommand::InternalExecute(
|
||||
return ExecuteWithRoll(currentState, attackerRoll);
|
||||
}
|
||||
|
||||
auto LightningBoltCommand::ExecuteWithRoll(const GameStateW& currentState, double /*attackerRoll*/)
|
||||
auto LightningBoltCommand::ExecuteWithRoll(const GameStateW& currentState, double attackerRoll)
|
||||
const -> vector<ActionResult> {
|
||||
const Unit* attackerBefore = currentState->units()->Get(attackerId);
|
||||
if (!attackerBefore->has_attached_hero()) {
|
||||
|
||||
@@ -24,7 +24,7 @@ MeteorCancelCommand::MeteorCancelCommand(
|
||||
|
||||
auto MeteorCancelCommand::InternalExecute(
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
const Unit* caster = currentState->units()->Get(casterId);
|
||||
auto casterAfter = *caster;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ auto MeteorStartCommand::GetCommandProto() const -> CommandProto {
|
||||
|
||||
auto MeteorStartCommand::InternalExecute(
|
||||
const GameStateW ¤tState,
|
||||
const std::shared_ptr<RandomGenerator> & /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResult> {
|
||||
const auto *caster = currentState->units()->Get(casterId);
|
||||
|
||||
auto casterAfter = *caster;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace shardok {
|
||||
|
||||
auto shardok::MeteorTargetCommand::InternalExecute(
|
||||
const GameStateW ¤tState,
|
||||
const std::shared_ptr<RandomGenerator> & /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResult> {
|
||||
const Unit *caster = currentState->units()->Get(casterId);
|
||||
auto casterAfter = *caster;
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#include "MoveCommand.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include <utility>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/action_result_applier/ActionResultApplier.hpp"
|
||||
@@ -99,7 +97,7 @@ auto shardok::MoveCommand::InternalExecute(
|
||||
const auto* occupant = currentState.GetOccupant(adj);
|
||||
if (!occupant) continue;
|
||||
if (occupant->player_id() == mover.player_id()) continue;
|
||||
if (std::ranges::contains(allyPids, occupant->player_id())) continue;
|
||||
if (common::Contains(allyPids, occupant->player_id())) continue;
|
||||
if (!occupant->has_attached_hero()) continue;
|
||||
if (occupant->attached_hero().profession_info().profession() ==
|
||||
net::eagle0::shardok::storage::fb::Profession_RANGER) {
|
||||
@@ -115,7 +113,7 @@ auto shardok::MoveCommand::InternalExecute(
|
||||
auto occupant = currentState.GetOccupant(adj);
|
||||
if (!occupant) continue;
|
||||
if (occupant->player_id() == mover.player_id()) continue;
|
||||
if (std::ranges::contains(allyPids, occupant->player_id())) continue;
|
||||
if (common::Contains(allyPids, occupant->player_id())) continue;
|
||||
if (occupant->hidden()) {
|
||||
auto occupantAfter = *occupant;
|
||||
occupantAfter.mutate_hidden(false);
|
||||
|
||||
@@ -17,7 +17,7 @@ using net::eagle0::shardok::common::ActionType;
|
||||
|
||||
auto ReinforceCommand::InternalExecute(
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
const Unit* actingUnit = currentState->units()->Get(actingUnitId);
|
||||
auto actorAfter = *actingUnit;
|
||||
MutatingSpendActionPoints(&actorAfter, cost);
|
||||
|
||||
@@ -21,7 +21,7 @@ auto ReleaseUnitCommand::GetCommandProto() const -> CommandProto {
|
||||
|
||||
auto ReleaseUnitCommand::InternalExecute(
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
auto controllingUnitAfter = *currentState->units()->Get(actingUnitId);
|
||||
controllingUnitAfter.mutate_remaining_action_points(0);
|
||||
controllingUnitAfter.mutable_attached_hero().mutable_control_info().mutate_controlled_unit_id(
|
||||
|
||||
@@ -18,7 +18,7 @@ using net::eagle0::shardok::common::ActionType::REPAIR_FAILED;
|
||||
|
||||
auto RepairCommand::InternalExecute(
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
const auto* actor = currentState->units()->Get(actorId);
|
||||
|
||||
if (!actor->has_attached_hero()) { throw ActionRequiresHeroException("repair"); }
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
namespace shardok {
|
||||
auto RetreatCommand::InternalExecute(
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
ActionResult result{};
|
||||
result.set_type(net::eagle0::shardok::common::ActionType::RETREATED);
|
||||
result.mutable_player()->set_value(GetPlayerId());
|
||||
|
||||
@@ -26,7 +26,7 @@ StartFireCommand::StartFireCommand(
|
||||
odds(std::move(odds)){};
|
||||
|
||||
auto StartFireCommand::InternalExecute(
|
||||
const GameStateW& /*currentState*/,
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
return ExecuteWithRoll(generator->Percentile());
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace shardok {
|
||||
|
||||
auto UnitRestCommand::InternalExecute(
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
auto actorAfter = *currentState->units()->Get(actorId);
|
||||
|
||||
if (actorAfter.has_attached_hero()) {
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace shardok {
|
||||
|
||||
auto UnitStopCommand::InternalExecute(
|
||||
const GameStateW& currentState,
|
||||
const std::shared_ptr<RandomGenerator>& /*generator*/) const -> vector<ActionResult> {
|
||||
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
|
||||
ActionResult result{};
|
||||
result.set_type(net::eagle0::shardok::common::ActionType::UNIT_STOP);
|
||||
result.mutable_player()->set_value(GetPlayerId());
|
||||
|
||||
@@ -10,7 +10,6 @@ cc_library(
|
||||
"//src/test/cpp/net/eagle0/shardok/library:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/cpp/net/eagle0/common:byte_hasher",
|
||||
"//src/main/cpp/net/eagle0/common:filesystem_utils",
|
||||
"@flatbuffers",
|
||||
],
|
||||
|
||||
@@ -7,12 +7,6 @@
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
namespace shardok {
|
||||
|
||||
// Uses mmap and munmap to directly allocate pages
|
||||
@@ -50,7 +44,7 @@ public:
|
||||
}
|
||||
return b;
|
||||
}
|
||||
auto deallocate(uint8_t* buf, const size_t /*size*/) -> void override { free(buf); }
|
||||
auto deallocate(uint8_t* buf, const size_t size) -> void override { free(buf); }
|
||||
|
||||
~MallocAllocator() override = default;
|
||||
};
|
||||
@@ -60,9 +54,9 @@ class ReusingAllocator final : public WrapperAllocator {
|
||||
private:
|
||||
std::unique_ptr<WrapperAllocator> fallbackAllocator;
|
||||
|
||||
const size_t pageSize = static_cast<size_t>(getpagesize());
|
||||
const size_t pageSize = getpagesize();
|
||||
|
||||
[[nodiscard]] auto RoundUpToPage(const size_t size) const -> size_t {
|
||||
auto RoundUpToPage(const size_t size) const -> size_t {
|
||||
return (size + pageSize - 1) & ~(pageSize - 1);
|
||||
}
|
||||
|
||||
@@ -144,7 +138,7 @@ public:
|
||||
auto WrapperAllocator::GetDefaultAllocator() -> WrapperAllocator* {
|
||||
static pthread_once_t once = PTHREAD_ONCE_INIT;
|
||||
static WrapperAllocator* allocator = nullptr;
|
||||
pthread_once(&once, [] {
|
||||
pthread_once(&once, []() {
|
||||
// Change this to use one of the other allocators
|
||||
allocator = new MallocAllocator();
|
||||
});
|
||||
@@ -152,4 +146,4 @@ auto WrapperAllocator::GetDefaultAllocator() -> WrapperAllocator* {
|
||||
return allocator;
|
||||
}
|
||||
|
||||
} // namespace shardok
|
||||
} // namespace shardok
|
||||
@@ -7,10 +7,8 @@
|
||||
|
||||
#include <flatbuffers/flatbuffers.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/common/ByteHasher.hpp"
|
||||
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
|
||||
|
||||
namespace shardok {
|
||||
@@ -71,7 +69,7 @@ public:
|
||||
Wrapper(const Wrapper& copiedFrom)
|
||||
: size(copiedFrom.size),
|
||||
offset(copiedFrom.offset),
|
||||
buffer(CopyBuffer(copiedFrom)) {}
|
||||
buffer(CopyBuffer(copiedFrom)){};
|
||||
|
||||
auto operator=(const Wrapper& other) -> Wrapper& {
|
||||
if (&other != this) {
|
||||
@@ -80,9 +78,10 @@ public:
|
||||
WrapperAllocator::GetDefaultAllocator()->deallocate(buffer, size);
|
||||
}
|
||||
buffer = CopyBuffer(other.buffer, other.size);
|
||||
} else if (buffer != nullptr && other.buffer != nullptr) {
|
||||
} else {
|
||||
memcpy(buffer, other.buffer, other.size);
|
||||
}
|
||||
memcpy(buffer, other.buffer, other.size);
|
||||
|
||||
size = other.size;
|
||||
offset = other.offset;
|
||||
@@ -104,9 +103,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once CppNonExplicitConversionOperator
|
||||
operator FB*() { return Get(); } // NOLINT(*-explicit-constructor)
|
||||
// ReSharper disable once CppNonExplicitConversionOperator
|
||||
operator FB*() { return Get(); } // NOLINT(*-explicit-constructor)
|
||||
operator const FB*() const { return Get(); } // NOLINT(*-explicit-constructor)
|
||||
|
||||
auto Get() const -> const FB* { return flatbuffers::GetRoot<FB>(buffer + offset); }
|
||||
@@ -122,47 +119,27 @@ public:
|
||||
}
|
||||
|
||||
[[nodiscard]] auto ToByteString() const -> string {
|
||||
string result;
|
||||
result.reserve(sizeof(size_t) + size);
|
||||
result.append(reinterpret_cast<const char*>(&offset), sizeof(size_t));
|
||||
result.append(reinterpret_cast<const char*>(buffer), size);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Compute FNV-1a hash directly on the buffer content without copying
|
||||
// Note: Offset is not included in hash as it's just an implementation detail
|
||||
[[nodiscard]] auto ComputeFNV1aHash() const -> uint64_t {
|
||||
// Hash only the actual FlatBuffer content, not the offset
|
||||
return HashBuffer(buffer, size);
|
||||
char* str = (char*)malloc(sizeof(size_t) + size);
|
||||
memcpy(str, &offset, sizeof(size_t));
|
||||
memcpy(str + sizeof(size_t), reinterpret_cast<char const*>(buffer), size);
|
||||
return string(str, sizeof(size_t) + size);
|
||||
}
|
||||
static auto FromByteString(const string& str) -> Wrapper {
|
||||
if (str.size() < sizeof(size_t)) {
|
||||
return Wrapper(); // Return empty wrapper for invalid data
|
||||
}
|
||||
|
||||
size_t offset;
|
||||
memcpy(&offset, str.data(), sizeof(size_t));
|
||||
|
||||
size_t size = str.size() - sizeof(size_t);
|
||||
auto toReturn = Wrapper(
|
||||
const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(str.data())) + sizeof(size_t),
|
||||
offset,
|
||||
size);
|
||||
auto toReturn = Wrapper((uint8_t*)str.data() + sizeof(size_t), offset, size);
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto ToByteVector() const -> byte_vector { return byte_vector(ToByteString()); }
|
||||
static auto FromByteVector(const byte_vector& bv) -> Wrapper {
|
||||
if (bv.size() < sizeof(size_t)) {
|
||||
return Wrapper(); // Return empty wrapper for invalid data
|
||||
}
|
||||
|
||||
size_t offset;
|
||||
memcpy(&offset, bv.data(), sizeof(size_t));
|
||||
|
||||
size_t size = bv.size() - sizeof(size_t);
|
||||
// Note: CopyBuffer will handle const_cast safely by making a copy
|
||||
auto toReturn = Wrapper(const_cast<uint8_t*>(bv.data()) + sizeof(size_t), offset, size);
|
||||
|
||||
return toReturn;
|
||||
|
||||
@@ -9,10 +9,7 @@
|
||||
|
||||
#include "FlatbufferWrapper.hpp"
|
||||
#include "src/main/flatbuffer/net/eagle0/shardok/storage/hex_map.hpp"
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-redundant-constexpr-static-def"
|
||||
#include "src/main/protobuf/net/eagle0/shardok/common/hex_map.pb.h"
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
namespace shardok::fb {
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@
|
||||
#define EAGLE0_TERRAINHELPERS_HPP
|
||||
|
||||
#include "src/main/flatbuffer/net/eagle0/shardok/storage/terrain.hpp"
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-redundant-constexpr-static-def"
|
||||
#include "src/main/protobuf/net/eagle0/shardok/common/terrain.pb.h"
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
namespace shardok::fb {
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user