Compare commits

...
Author SHA1 Message Date
admin d5d0730c28 Use direct combat factory vector declarations 2026-06-23 13:03:15 -07:00
adminandGitHub 549cf37127 Require eagle map on GameState (#7924) 2026-06-23 12:44:12 -07:00
adminandGitHub 237d5ce7c3 Tidy transposition table accessors (#7918) 2026-06-23 12:25:57 -07:00
adminandGitHub 05fc72c611 Tidy AI command evaluator result state (#7917) 2026-06-23 12:25:36 -07:00
adminandGitHub 5d889b71aa Remove GameState bootstrap date helper (#7916) 2026-06-23 12:24:57 -07:00
adminandGitHub 8641d15fc7 Remove single-future reserve hints (#7915) 2026-06-23 12:24:00 -07:00
adminandGitHub e4fc742990 Make GameState currentDate required (#7913) 2026-06-23 12:10:56 -07:00
adminandGitHub a27dfe895f Use span for odds other factors (#7914) 2026-06-23 12:07:11 -07:00
adminandGitHub 098d4b4dcc Tidy random battle runner setup (#7903) 2026-06-23 11:52:39 -07:00
adminandGitHub 765ca02255 Use clamped perf test unit counts (#7902) 2026-06-23 11:52:13 -07:00
adminandGitHub 186dec0ad9 Reserve controller update fields (#7901) 2026-06-23 11:50:01 -07:00
adminandGitHub 14a6d143bc Reserve odds helper factors (#7904) 2026-06-23 11:48:42 -07:00
adminandGitHub ade5a4fa10 Size game state guesser buffers first (#7905) 2026-06-23 11:47:38 -07:00
adminandGitHub 85d3ae8fce Tidy action point distance setup (#7906) 2026-06-23 11:47:22 -07:00
adminandGitHub fb200278ae Tidy available command assembly (#7907) 2026-06-23 11:47:02 -07:00
adminandGitHub 34da29bef0 Reserve reinforcement setup commands (#7908) 2026-06-23 11:46:47 -07:00
adminandGitHub f164fbb150 Add GameState required current date helper (#7910) 2026-06-23 11:46:03 -07:00
adminandGitHub fe1537bb12 Trim map names by extension (#7909) 2026-06-23 11:45:06 -07:00
adminandGitHub 69cc336cef Tidy map info position loop (#7911) 2026-06-23 11:44:48 -07:00
adminandGitHub d05030f99a Improve remaining iPad touch targets (#7900) 2026-06-23 11:03:39 -07:00
adminandGitHub 9699d095c5 Reserve command execution result buffers (#7883) 2026-06-23 11:03:01 -07:00
adminandGitHub 6ca2d7a37c Reserve action execution buffers (#7884) 2026-06-23 11:02:40 -07:00
adminandGitHub b0c4944a6c Remove Postgres text store early returns (#7881) 2026-06-23 11:02:13 -07:00
adminandGitHub d25925d525 Avoid implicit food lookup insertion (#7885) 2026-06-23 11:01:38 -07:00
adminandGitHub 07a6834216 Handle zero-length cube lines (#7880) 2026-06-23 11:00:47 -07:00
adminandGitHub 69f1f51460 Tighten shardok server request conversion (#7879) 2026-06-23 11:00:14 -07:00
adminandGitHub 8b30a2c5f2 Reserve tutorial reinforcement units (#7886) 2026-06-23 10:59:49 -07:00
adminandGitHub 65a534ebae Remove SQLite text store early returns (#7887) 2026-06-23 10:59:31 -07:00
adminandGitHub ac0a154fc4 Reserve game state view fields (#7888) 2026-06-23 10:59:10 -07:00
adminandGitHub 1b510007fd Use Await.result in server setup (#7889) 2026-06-23 10:58:23 -07:00
adminandGitHub 692c363264 Guard action filter actor lookup (#7890) 2026-06-23 10:58:02 -07:00
adminandGitHub cf5d88c69d Avoid copying filtered history (#7892) 2026-06-23 10:57:36 -07:00
adminandGitHub 109f49e522 Clarify custom battle player view lookup (#7891) 2026-06-23 10:57:05 -07:00
adminandGitHub 53e3f7a1b0 Use fixed adjacent tile offsets (#7893) 2026-06-23 10:56:51 -07:00
adminandGitHub c7d3e162b9 Clarify ResolveBattle required winners (#7894) 2026-06-23 10:56:23 -07:00
adminandGitHub 7b2bb34a36 Clarify mid-game AI required values (#7895) 2026-06-23 10:56:07 -07:00
adminandGitHub c7b1082954 Clarify GamesManager required loads (#7896) 2026-06-23 10:54:42 -07:00
adminandGitHub aa18d0dd1a Clarify chronicle required leaders (#7897) 2026-06-23 10:54:22 -07:00
adminandGitHub 711d0382f3 Make province distance lookup explicit (#7898) 2026-06-23 10:53:55 -07:00
adminandGitHub 7b02c39172 Make tutorial loyalty lookups explicit (#7899) 2026-06-23 10:53:04 -07:00
adminandGitHub b178f11bea Improve iPad touch targets in popup UI (#7882) 2026-06-23 10:49:29 -07:00
adminandGitHub 42da8a7d15 Clarify great person faction requirement (#7870) 2026-06-23 10:08:24 -07:00
adminandGitHub bd9b62d7de Reserve AI evaluator lookahead vectors (#7869) 2026-06-23 10:08:07 -07:00
adminandGitHub 98fcd94614 Reserve engine output vectors (#7868) 2026-06-23 10:07:49 -07:00
adminandGitHub 06e7112db1 Clarify battle participant lookups (#7847) 2026-06-23 10:07:32 -07:00
adminandGitHub 9ba023b0fd Reserve hex map proto fields (#7871) 2026-06-23 10:07:08 -07:00
adminandGitHub ff63c0fff2 Reserve game state helper collections (#7872) 2026-06-23 10:06:52 -07:00
adminandGitHub 421dbcae80 Clarify required quest request fields (#7873) 2026-06-23 10:06:34 -07:00
adminandGitHub 4c321967f4 Reserve geometry utility vectors (#7874) 2026-06-23 10:06:18 -07:00
adminandGitHub b3c0138074 Clarify required proto submessages (#7875) 2026-06-23 10:05:53 -07:00
adminandGitHub 4b7c862ac8 Clarify service invariant lookups (#7876) 2026-06-23 10:05:31 -07:00
adminandGitHub cf4b99710c Clarify Shardok interface required fields (#7877) 2026-06-23 10:05:11 -07:00
adminandGitHub d25e0121a6 Clarify required hero library conjunctions (#7878) 2026-06-23 10:04:52 -07:00
adminandGitHub 213a339d34 Delete stale editor log zip (#7845) 2026-06-23 08:38:58 -07:00
adminandGitHub d2205dc361 Reserve Shardok test helper vectors (#7846) 2026-06-23 08:38:46 -07:00
adminandGitHub 21e0586add Reserve AI test helper vectors (#7848) 2026-06-23 08:38:07 -07:00
adminandGitHub cbbe200c98 Avoid raw battle battalion options (#7862) 2026-06-23 08:37:50 -07:00
adminandGitHub fbeddc7b19 Avoid raw heads in string parser (#7849) 2026-06-23 08:29:08 -07:00
adminandGitHub 1ed6c85b5d Improve iPad touch targets in Eagle UI (#7850) 2026-06-23 08:28:25 -07:00
adminandGitHub c7f10dbcbc Clarify required engine history result (#7851) 2026-06-23 08:28:08 -07:00
adminandGitHub 81f747c75b Reserve simulator setup vectors (#7852) 2026-06-23 08:27:41 -07:00
adminandGitHub a76dc0515c Clarify required dates in round advancement (#7853) 2026-06-23 08:26:39 -07:00
adminandGitHub bb77ecbd73 Clarify province resource date requirement (#7854) 2026-06-23 08:26:21 -07:00
adminandGitHub 5d35e7d78b Extract MCTS action sorting helper (#7855) 2026-06-23 08:25:56 -07:00
adminandGitHub e20c4e7027 Avoid vector allocation in ranged bonus scoring (#7856) 2026-06-23 08:25:25 -07:00
adminandGitHub 3977df70f4 Clarify more current date requirements (#7857) 2026-06-23 08:24:52 -07:00
adminandGitHub 7f72a8bb39 Reserve AI strategy working vectors (#7858) 2026-06-23 08:24:26 -07:00
adminandGitHub de27e9fcb8 Right-size APD working vectors (#7859) 2026-06-23 08:24:05 -07:00
adminandGitHub 76bd94ddb5 Clarify config parse failures (#7860) 2026-06-23 08:23:35 -07:00
adminandGitHub 651e209f07 Move completed action results into vectors (#7861) 2026-06-23 08:23:03 -07:00
adminandGitHub 448ec1e0ee Reserve simulator CLI path vectors (#7863) 2026-06-23 08:21:32 -07:00
adminandGitHub 6e2afeee3c Clarify required battle ended result (#7864) 2026-06-23 08:21:08 -07:00
adminandGitHub edce49467c Reserve map info results (#7865) 2026-06-23 08:20:51 -07:00
adminandGitHub 2934027437 Clarify battle unit return lookups (#7866) 2026-06-23 08:20:35 -07:00
adminandGitHub b2250cda79 Reserve tutorial controller result vectors (#7867) 2026-06-23 08:20:17 -07:00
adminandGitHub 96d69b349f Reserve Shardok filter result vectors (#7839) 2026-06-23 07:49:53 -07:00
adminandGitHub 4a6ae0380a Clarify current date requirements in command helpers (#7840) 2026-06-23 07:49:38 -07:00
adminandGitHub e339ddd227 Require factions for leader-aware command choices (#7841) 2026-06-23 07:49:18 -07:00
adminandGitHub afaf6c9e19 Move Shardok vector temporaries (#7842) 2026-06-23 07:49:03 -07:00
adminandGitHub e21eaf8606 Add narrow March command flow (#7747) 2026-06-23 07:48:32 -07:00
adminandGitHub a43a650d17 Document PR review commit history preference (#7844) 2026-06-23 07:48:17 -07:00
adminandGitHub 17cb742964 Clarify province conquest required values (#7843) 2026-06-23 07:40:12 -07:00
adminandGitHub 9d8231b253 Clarify current date requirements in aftermath phases (#7836) 2026-06-23 07:00:16 -07:00
adminandGitHub 5bf2ea95e5 Clarify current date requirements in resolution events (#7837) 2026-06-23 07:00:02 -07:00
adminandGitHub 2f48330d74 Document cleanup PR batching guidance (#7838) 2026-06-23 06:59:31 -07:00
adminandGitHub b0b516a990 Clarify current date requirements in event phases (#7835) 2026-06-23 06:59:05 -07:00
adminandGitHub 1057271163 Reserve move destination vector (#7830) 2026-06-23 06:58:47 -07:00
adminandGitHub 296d5df16f Reserve fall into water helper vectors (#7829) 2026-06-23 06:58:33 -07:00
adminandGitHub b2abe00b14 Reserve possible chargee ids (#7828) 2026-06-23 06:58:20 -07:00
adminandGitHub 793d665d80 Reserve allied player id vector (#7826) 2026-06-23 06:58:07 -07:00
adminandGitHub bbbe596e72 Reserve game state guesser vectors (#7825) 2026-06-23 06:57:54 -07:00
adminandGitHub c3c148f5db Reserve Shardok game setup vectors (#7823) 2026-06-23 06:57:33 -07:00
adminandGitHub 4b8f1d2d20 Reserve hex cube result vectors (#7821) 2026-06-23 06:57:18 -07:00
adminandGitHub 8a3222ed54 Reserve map name vector capacity (#7819) 2026-06-23 06:56:56 -07:00
adminandGitHub 9f82af2e12 Make remaining Shardok test casts explicit (#7818) 2026-06-23 06:56:35 -07:00
adminandGitHub 2536e5146c Fix empty AI score reserve underflow (#7832) 2026-06-23 06:54:09 -07:00
adminandGitHub 98b0502e20 Use reinterpret cast for resolved unit bytes (#7811) 2026-06-23 06:08:33 -07:00
adminandGitHub 25c1198c0a Clarify captured hero command acting hero (#7790) 2026-06-23 06:05:58 -07:00
adminandGitHub a6e31596d6 Qualify flatbuffer wrapper memcpy calls (#7791) 2026-06-23 06:05:42 -07:00
adminandGitHub c9b3cd6fc0 Clarify beast info requirement (#7792) 2026-06-23 06:05:26 -07:00
adminandGitHub 2c18d1d39b Move AI command evaluator sample sequence (#7794) 2026-06-23 06:05:09 -07:00
adminandGitHub 28895e3e04 Clarify captured hero command choice requirement (#7793) 2026-06-23 06:04:54 -07:00
adminandGitHub f8ef044226 Include cstring in unit helpers (#7789) 2026-06-23 06:04:36 -07:00
adminandGitHub b8da5750c7 Clarify please recruit command choice requirements (#7795) 2026-06-23 06:02:59 -07:00
adminandGitHub fbdc0f79a8 Move AI battle simulator setup objects (#7796) 2026-06-23 06:02:35 -07:00
adminandGitHub ea7f666e36 Move game state helper unit structs (#7797) 2026-06-23 06:02:18 -07:00
adminandGitHub 48ff8a7e1b Bind march origin command choice (#7798) 2026-06-23 06:02:00 -07:00
adminandGitHub 033a378e14 Use static cast for game status conversion (#7799) 2026-06-23 06:01:41 -07:00
adminandGitHub a3d19f246e Move random battle runner units (#7800) 2026-06-23 06:01:25 -07:00
adminandGitHub ec67215bc2 Clean up percentile roll casts (#7801) 2026-06-23 06:00:59 -07:00
adminandGitHub f207bef4a8 Clarify armament cost lookup (#7802) 2026-06-23 06:00:34 -07:00
adminandGitHub 41bb944c26 Use static casts for action result sizes (#7803) 2026-06-23 06:00:19 -07:00
adminandGitHub 38eb33367d Bind attack command current date (#7804) 2026-06-23 05:59:45 -07:00
adminandGitHub 5ae59006fb Bind alms command current date (#7805) 2026-06-23 05:59:26 -07:00
adminandGitHub e66e7d4a03 Clarify update game status player id check (#7806) 2026-06-23 05:59:05 -07:00
adminandGitHub 34bbfaf256 Clarify backstory update counter parsing (#7807) 2026-06-23 05:58:30 -07:00
adminandGitHub abd323a232 Use static casts in hex map utilities (#7808) 2026-06-23 05:58:00 -07:00
adminandGitHub 97ba1d0be1 Use static casts in hex cube fixup (#7809) 2026-06-23 05:57:43 -07:00
adminandGitHub deb211a420 Clarify new round current date requirement (#7810) 2026-06-23 05:57:19 -07:00
adminandGitHub 9481eed7b8 Use static cast for new wind direction (#7812) 2026-06-23 05:51:51 -07:00
adminandGitHub 50c457d877 Use static cast for battalion type size check (#7813) 2026-06-23 05:51:34 -07:00
adminandGitHub a544a0d15e Use static casts in hex map direction arithmetic (#7814) 2026-06-22 23:52:15 -07:00
adminandGitHub 8c26ac5fca Clarify expand selector required hero checks (#7777) 2026-06-22 21:19:09 -07:00
adminandGitHub d56e0de9fd Clarify new hero province selection (#7778) 2026-06-22 21:18:40 -07:00
adminandGitHub 5132152334 Catch Shardok server exceptions by const reference (#7779) 2026-06-22 21:18:11 -07:00
adminandGitHub ef6ba7e585 Throw battle simulator CLI helper errors (#7780) 2026-06-22 21:17:51 -07:00
adminandGitHub 695a9d19d4 Return from AI performance argument parsing (#7781) 2026-06-22 21:17:30 -07:00
adminandGitHub 45682dff9b Parse real battle extractor integers with stoi (#7782) 2026-06-22 21:17:08 -07:00
adminandGitHub 47c5653bc0 Parse AI experiment id with from_chars (#7783) 2026-06-22 21:16:43 -07:00
adminandGitHub 033ebc802a Parse Shardok server config without strtok (#7784) 2026-06-22 21:16:12 -07:00
adminandGitHub 14e0fb66fb Drop unused random battle srand calls (#7785) 2026-06-22 21:15:28 -07:00
adminandGitHub 18d9756fc5 Make AI evaluation counter noncopyable (#7786) 2026-06-22 21:15:15 -07:00
adminandGitHub f976548fa1 Make attacker score timer noncopyable (#7787) 2026-06-22 21:14:58 -07:00
adminandGitHub a3fed6836d Include cstring in flatbuffer wrapper (#7788) 2026-06-22 21:14:38 -07:00
adminandGitHub 78d89f83da Scope remaining Shardok cpp tests to namespace (#7776) 2026-06-22 17:04:14 -07:00
adminandGitHub 66b93b0c83 Clarify tribute demand fallback (#7775) 2026-06-22 17:03:21 -07:00
adminandGitHub fe06ead2e2 Scope flee command tests to shardok namespace (#7767) 2026-06-22 17:03:09 -07:00
adminandGitHub 71e5b1b866 Clarify faction change victor lookups (#7774) 2026-06-22 17:02:38 -07:00
adminandGitHub 35ac2d6297 Scope ice action tests to shardok namespace (#7764) 2026-06-22 17:02:00 -07:00
adminandGitHub ff33d13822 Scope snow action tests to shardok namespace (#7762) 2026-06-22 17:01:47 -07:00
adminandGitHub ffb02d1d6c Scope build bridge command tests to shardok namespace (#7771) 2026-06-22 17:01:34 -07:00
adminandGitHub 86f7e85ce9 Clarify prisoner move required hero lookup (#7770) 2026-06-22 17:01:20 -07:00
adminandGitHub cd6318ba84 Clarify faction change required lookups (#7772) 2026-06-22 17:01:07 -07:00
adminandGitHub 7385b8ced2 Scope evacuate prisoners command tests to shardok namespace (#7773) 2026-06-22 17:00:54 -07:00
adminandGitHub a36ccd4157 Clarify captured hero recruitment requirements (#7765) 2026-06-22 17:00:40 -07:00
adminandGitHub 29a3459649 Scope blow bridge command tests to shardok namespace (#7766) 2026-06-22 17:00:27 -07:00
adminandGitHub 8dd7df989d Bind selected captured hero options once (#7763) 2026-06-22 17:00:09 -07:00
adminandGitHub d40b9a9ba6 Clarify captured hero return destination lookup (#7768) 2026-06-22 16:59:49 -07:00
adminandGitHub dd5d34f7aa Scope holy wave command tests to shardok namespace (#7769) 2026-06-22 16:58:50 -07:00
adminandGitHub 5685c8430f Clarify required CommandFactory province fields (#7761) 2026-06-22 16:58:37 -07:00
adminandGitHub 723a195d89 Scope ambush odds tests to shardok namespace (#7760) 2026-06-22 16:58:22 -07:00
adminandGitHub 68a20cba27 Scope percentile roll tests to shardok namespace (#7759) 2026-06-22 16:53:03 -07:00
adminandGitHub 27802e54e6 Move game settings test include inside guard (#7758) 2026-06-22 16:49:18 -07:00
adminandGitHub 3e91ca3bc3 Qualify standard library types in ShardokGameController (#7756) 2026-06-22 16:49:07 -07:00
adminandGitHub 5a4e98bd71 Remove unsafe runtime validator lookups (#7752) 2026-06-22 16:44:57 -07:00
adminandGitHub 225fe66afa Qualify standard library types in ShardokEngine (#7753) 2026-06-22 16:44:27 -07:00
adminandGitHub 619f0ca3e9 Report alliance destination lookup failures explicitly (#7754) 2026-06-22 16:44:09 -07:00
adminandGitHub fa3f4cd06b Report chronicle attack faction name lookup failures explicitly (#7755) 2026-06-22 16:43:54 -07:00
adminandGitHub 7694d2e676 Handle empty conjunction results without unsafe get (#7757) 2026-06-22 16:43:24 -07:00
adminandGitHub ea6792f21b Report quest creation lookup failures explicitly (#7751) 2026-06-22 16:42:10 -07:00
adminandGitHub 5e4dace755 Report battalion suitability type lookup failures explicitly (#7742) 2026-06-22 16:33:14 -07:00
adminandGitHub 276dbf5d42 Qualify vectors in hex map helpers (#7743)
* Qualify vectors in hex map helpers

* Qualify ZOC vector parameters
2026-06-22 16:32:57 -07:00
adminandGitHub 0ca2685bf3 Use province ruling faction helper for command availability (#7736) 2026-06-22 16:32:33 -07:00
adminandGitHub 4bab97ec62 Report arm troops availability cost lookup failures explicitly (#7741) 2026-06-22 16:32:11 -07:00
adminandGitHub 47bd5f6291 Qualify std iterators in Eagle gRPC server (#7740) 2026-06-22 16:31:57 -07:00
adminandGitHub 7ee1441b8e Report march neighbor lookup failures explicitly (#7739) 2026-06-22 16:31:43 -07:00
adminandGitHub 034270a119 Use province ruling faction helper for diplomacy availability (#7738) 2026-06-22 16:31:29 -07:00
adminandGitHub 9e4bff03d9 Qualify string constant in FearCommand (#7737) 2026-06-22 16:31:11 -07:00
adminandGitHub 29b9954954 Qualify std algorithms in AI unit scoring (#7735) 2026-06-22 16:30:48 -07:00
adminandGitHub 3aa62b9eeb Report quest fulfillment lookup failures explicitly (#7734) 2026-06-22 16:30:28 -07:00
adminandGitHub d8bd976204 Report unaffiliated hero province lookup failures explicitly (#7744) 2026-06-22 16:30:07 -07:00
adminandGitHub 3ccb8f5dc8 Qualify shared pointers in Eagle gRPC server (#7745) 2026-06-22 16:29:54 -07:00
adminandGitHub cb05990260 Report hero utils faction lookup failures explicitly (#7746) 2026-06-22 16:29:20 -07:00
adminandGitHub 8a2c21c88c Report combat unit suitability lookup failures explicitly (#7748) 2026-06-22 16:28:47 -07:00
adminandGitHub 204cd81462 Remove stale std aliases from Unit header (#7749) 2026-06-22 16:28:30 -07:00
adminandGitHub 71505b5419 Report manage prisoners return province lookup failures explicitly (#7750) 2026-06-22 16:27:59 -07:00
adminandGitHub 50e958e2e3 Use province ruling faction helper in actions (#7724) 2026-06-22 14:58:25 -07:00
adminandGitHub 3ffab58cbf Report LLM lookup failures explicitly (#7723) 2026-06-22 14:58:12 -07:00
adminandGitHub febb9e2fe0 Report organize troops lookup failures explicitly (#7722) 2026-06-22 14:57:52 -07:00
adminandGitHub b1af1b7254 Report quest ruling faction lookup failures explicitly (#7725) 2026-06-22 14:57:07 -07:00
adminandGitHub 32a4da1826 Report alliance resolution lookup failures explicitly (#7726) 2026-06-22 14:56:31 -07:00
adminandGitHub cc44eef174 Report invitation resolution lookup failures explicitly (#7727) 2026-06-22 14:55:41 -07:00
adminandGitHub c608a5890a Report diplomacy origin province lookup failures explicitly (#7728) 2026-06-22 14:53:43 -07:00
adminandGitHub 52253908eb Report AI diplomacy faction lookup failures explicitly (#7729) 2026-06-22 14:53:27 -07:00
adminandGitHub a50956cdb0 Report AI ransom hero lookup failures explicitly (#7730) 2026-06-22 14:53:14 -07:00
adminandGitHub ee55e4b216 Move deploy transport off Bazel runners (#7732) 2026-06-22 14:52:27 -07:00
adminandGitHub 9d041926d7 Preserve iOS scene manifest for TestFlight builds (#7733) 2026-06-22 14:51:36 -07:00
371 changed files with 17658 additions and 1956 deletions
+1 -15
View File
@@ -255,7 +255,7 @@ jobs:
echo "=== All images pushed successfully ==="
deploy:
runs-on: [self-hosted, bazel]
runs-on: ubuntu-latest
needs: [build-all]
if: needs.build-all.outputs.skip_build != 'true' && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true'))
environment: production
@@ -334,25 +334,11 @@ jobs:
ssh-keyscan -H "$DO_DROPLET_IP" >> ~/.ssh/known_hosts 2>/dev/null || true
- name: Download warmup binary
id: download-warmup
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: warmup-binary
path: scripts/bin/
- name: Build warmup binary fallback
if: steps.download-warmup.outcome != 'success'
run: |
echo "::warning::warmup-binary artifact was unavailable; rebuilding warmup binary before deploy"
./ci/github_actions/ensure_bazel_installed.sh
bazel build \
--platforms=//:linux_x86_64 \
--extra_toolchains=@llvm_toolchain_linux//:cc-toolchain-x86_64-linux \
//ci:warmup_tar
mkdir -p scripts/bin
tar -xf bazel-bin/ci/warmup_tar.tar -C scripts/bin --strip-components=1
- name: Copy config files to droplet
run: |
# Create directory structure on remote
+3 -1
View File
@@ -196,7 +196,9 @@ jobs:
echo "Also tagged as: registry.digitalocean.com/eagle0/shardok-server:arm64-latest"
deploy-hetzner:
runs-on: [self-hosted, bazel]
# Keep this on a self-hosted Mac runner for IPv6 reachability to Hetzner,
# but do not require a Bazel runner slot for artifact transport/deploy work.
runs-on: [self-hosted, macOS, ARM64]
needs: [build-shardok-arm64]
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
environment: production
+19
View File
@@ -29,6 +29,16 @@ If you catch yourself about to run `git push origin main` or `git push origin <b
If you catch yourself about to run `gh pr merge`, STOP. Only the user merges PRs.
## Commit History During Review
Prefer new follow-up commits over amending existing commits once a PR is open and the user is actively reviewing or
testing it. This preserves working interim states so the user can inspect, compare, or return to a known-good point
while improvements continue.
Use `git commit --amend` and force-push only when the user explicitly asks for history cleanup, when fixing metadata
before review has started, or when repairing a local commit that has not been pushed. If you believe squashing is the
right choice despite an open review, explain why and ask first.
## Worktree Usage
Prefer using the current worktree for small requested fixes. Create a separate worktree only when the user asks for one,
@@ -58,6 +68,15 @@ For PRs expected to trigger substantial Bazel work in CI, run the relevant Bazel
workstation is fast, and local Bazel runs can help hydrate the remote cache for the CI builders while still giving
earlier signal on failures.
## Cleanup PR Batching
When making mechanical cleanup changes, group 3-5 files per PR when the files are receiving the same kind of change
and can be reviewed as one pattern. Do not open one PR per file for nearly identical few-line cleanups, such as replacing
the same unsafe accessor pattern with the same contextual failure pattern across multiple Scala files.
Keep separate PRs for changes that involve different semantics, materially different risk, unrelated subsystems, or
files whose tests/validation strategy makes them better reviewed independently.
## GitHub CLI PR Bodies
When creating or editing PR descriptions with multiline bodies, write the body to a temporary markdown file and pass it
-13
View File
@@ -40,19 +40,6 @@ if [ -f "$INFO_PLIST" ]; then
/usr/libexec/PlistBuddy -c "Set :ITSAppUsesNonExemptEncryption false" "$INFO_PLIST" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :ITSAppUsesNonExemptEncryption bool false" "$INFO_PLIST"
echo "Set ITSAppUsesNonExemptEncryption=false in Info.plist"
# Remove UIApplicationSceneManifest if present.
# Unity 6 generates this key, which opts the app into the iOS scene-based
# lifecycle. This breaks Application.deepLinkActivated because iOS routes
# deep link URLs through UISceneDelegate instead of UIApplicationDelegate,
# and Unity doesn't implement the scene delegate path.
# See: https://issuetracker.unity3d.com/issues/in-135632
if /usr/libexec/PlistBuddy -c "Print :UIApplicationSceneManifest" "$INFO_PLIST" 2>/dev/null; then
echo "Found UIApplicationSceneManifest in Info.plist — removing to fix deep link handling"
/usr/libexec/PlistBuddy -c "Delete :UIApplicationSceneManifest" "$INFO_PLIST"
else
echo "No UIApplicationSceneManifest in Info.plist (deep links should work)"
fi
fi
# Unity always generates "Unity-iPhone" as the main app scheme
Binary file not shown.
@@ -39,14 +39,15 @@ auto CalculateMap(
for (std::size_t i = 0; i < hexMap->attacker_starting_positions()->size(); i++) {
const auto* positionList = hexMap->attacker_starting_positions()->Get(i);
if (positionList->positions()->empty()) continue;
if (positionList->positions()->size() != 10) {
const auto* positions = positionList->positions();
if (positions->empty()) continue;
if (positions->size() != 10) {
std::cerr << "Should have 10 starting positions!" << '\n';
std::abort();
}
int positionsRequiringCrossing = 0;
for (const auto* startingPosition : *positionList->positions()) {
for (const auto* startingPosition : *positions) {
for (const shardok::Coords& castlePosition : criticalTileLocations) {
if (!shardok::CanReach(
*startingPosition,
@@ -57,7 +58,7 @@ auto CalculateMap(
battalionType)) {
++positionsRequiringCrossing;
break;
};
}
}
}
info.positionsRequiringCrossing[static_cast<int>(i)] = positionsRequiringCrossing;
@@ -32,8 +32,10 @@ auto main(const int argc, char** argv) -> int {
const shardok::BattalionTypeSPtr& battalionType = gameSettings->GetGetter().GetBattalionType(
net::eagle0::shardok::storage::fb::BattalionTypeId_LIGHT_INFANTRY);
const std::vector<std::string> mapNames = GetMapNames();
std::vector<OneMapInfo> mapInfos{};
for (const std::string& mapName : GetMapNames()) {
mapInfos.reserve(mapNames.size());
for (const std::string& mapName : mapNames) {
std::shared_ptr<shardok::ActionPointDistancesCache> apdCache =
std::make_shared<shardok::ActionPointDistancesCache>();
@@ -3,6 +3,7 @@
//
#include <algorithm>
#include <array>
#include <climits>
#include <iostream>
#include <memory>
@@ -10,6 +11,8 @@
#include <random>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/common/UnitConversions.hpp"
@@ -44,25 +47,27 @@ void runBattle(const std::vector<shardok::Unit>& units) {
if (fileNames.empty()) { throw std::runtime_error("No map files found"); }
if (units.size() < 2) { throw std::runtime_error("Need at least two units to run a battle"); }
auto mapName = fileNames[randInt(0, (int)fileNames.size() - 1)];
auto month = randInt(1, 12);
const auto mapName = fileNames[randInt(0, static_cast<int>(fileNames.size()) - 1)];
const auto month = randInt(1, 12);
const auto maxP1UnitCount = std::min<size_t>(20, units.size() - 1);
auto p1UnitCount = randInt(1, static_cast<int>(maxP1UnitCount));
const auto p1UnitCount = randInt(1, static_cast<int>(maxP1UnitCount));
std::vector<shardok::Unit> p1Units{};
p1Units.reserve(p1UnitCount);
for (int i = 0; i < p1UnitCount; i++) {
auto unit = units[i];
unit.mutate_player_id(0);
p1Units.push_back(unit);
p1Units.push_back(std::move(unit));
}
const auto maxP2UnitCount = std::min<size_t>(20, units.size() - p1UnitCount);
auto p2UnitCount = randInt(1, static_cast<int>(maxP2UnitCount));
const auto p2UnitCount = randInt(1, static_cast<int>(maxP2UnitCount));
std::vector<shardok::Unit> p2Units{};
p2Units.reserve(p2UnitCount);
for (int i = p1UnitCount; i < p1UnitCount + p2UnitCount; i++) {
auto unit = units[i];
unit.mutate_player_id(1);
p2Units.push_back(unit);
p2Units.push_back(std::move(unit));
}
std::cout << "map name is " << mapName << ", " << p1UnitCount << " attacking " << p2UnitCount
@@ -75,32 +80,34 @@ void runBattle(const std::vector<shardok::Unit>& units) {
VictoryConditionProto::VICTORY_CONDITION_LAST_PLAYER_STANDING,
VictoryConditionProto::VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS};
auto p1 = shardok::PlayerInfoWithUnits(
std::vector<shardok::PlayerInfoWithUnits> playerInfos;
playerInfos.reserve(2);
playerInfos.emplace_back(
0,
true,
5,
false,
10000,
attVictoryConditions,
{},
std::vector<shardok::PlayerId>{},
p1Units,
false);
auto p2 = shardok::PlayerInfoWithUnits(
playerInfos.emplace_back(
1,
true,
7,
true,
10000,
defVictoryConditions,
{},
std::vector<shardok::PlayerId>{},
p2Units,
false);
std::string gameId = std::to_string(randInt(INT_MIN, INT_MAX));
const std::string gameId = std::to_string(randInt(INT_MIN, INT_MAX));
manager->UnlockedCreateSpecifiedGame(
gameId,
{p1, p2},
playerInfos,
mapName,
"",
month,
@@ -135,10 +142,7 @@ auto LoadUnits() -> std::vector<shardok::Unit> {
auto main(int /*argc*/, char** argv) -> int {
#if FIXED_SEED
std::srand(4564564);
g.seed(378473);
#else
std::srand(std::time(nullptr));
#endif
FilesystemUtils::SetExecPath(argv[0]);
@@ -152,7 +156,7 @@ auto main(int /*argc*/, char** argv) -> int {
manager = std::make_unique<shardok::ShardokGamesManager>(std::vector<std::string>());
std::thread threads[THREAD_COUNT];
std::array<std::thread, THREAD_COUNT> threads;
for (int i = 0; i < THREAD_COUNT; i++) {
threads[i] = std::thread(runBattlesThread, randomUnits);
@@ -153,6 +153,7 @@ auto GenerateTargetPriorities(
vector<TargetAndAttackLocations>{});
vector<TargetAndDistance> targetsWithDistance;
targetsWithDistance.reserve(targets.size());
// Get APDs directly from cache (now with built-in thread-local optimization)
const auto& battType = battalionTypeGetter(unit->battalion().type());
@@ -33,6 +33,7 @@ auto AIAttackerStrategySelector::BestAttackerStrategy(
bool canFlee = false;
vector<const Unit*> attackerUnits{};
attackerUnits.reserve(gameState->units()->size());
CoordsSet defenderPositions(gameState->hex_map());
for (const Unit* unit : *gameState->units()) {
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
@@ -131,4 +132,4 @@ auto AIAttackerStrategySelector::BestAttackerStrategy(
return chosenStrategy;
}
} // namespace shardok
} // namespace shardok
@@ -327,9 +327,9 @@ auto AICommandEvaluator::FindBestCommand(
// Structure to hold all command evaluation data
struct CommandEvaluation {
size_t index;
CommandType type;
ScoreValue immediateScore;
size_t index{};
CommandType type{};
ScoreValue immediateScore{};
bool immediateCompleted = true;
std::vector<std::future<EvaluationResult>> lookaheadFutures;
};
@@ -422,6 +422,7 @@ auto AICommandEvaluator::FindBestCommand(
} else {
ScoreValue sum = 0.0;
const int sampleCount = std::max(1, maxRepeatCount);
commandEvaluations[index].lookaheadFutures.reserve(sampleCount);
for (int repeatIteration = 0; repeatIteration < sampleCount; repeatIteration++) {
// In each iteration, use a double from [0, 1] as the random roll
auto sequence =
@@ -432,7 +433,7 @@ auto AICommandEvaluator::FindBestCommand(
originalIndex,
remainingLookahead,
maxRepeatCount,
std::make_shared<SequenceRandomGenerator>(sequence),
std::make_shared<SequenceRandomGenerator>(std::move(sequence)),
guessedEngine,
attackerStrategy,
allCastleCoords,
@@ -511,13 +512,15 @@ auto AICommandEvaluator::EvaluateCommand(
}
const auto& guessedDescriptor = guessedDescriptors->at(commandIndex);
const auto guessedCommandType = guessedDescriptor->GetCommandType();
if (const auto guessedCommandType = guessedDescriptor->GetCommandType();
guessedCommandType == net::eagle0::shardok::common::END_TURN_COMMAND) {
if (guessedCommandType == net::eagle0::shardok::common::END_TURN_COMMAND) {
std::promise<EvaluationResult> p;
p.set_value(EvaluationResult{.score = currentUtility, .completed = true});
return p.get_future();
} else if (IsDeterministic(guessedCommandType)) {
}
if (IsDeterministic(guessedCommandType)) {
auto evaluation = EvaluateWithRandomness(
pid,
isDefender,
@@ -530,7 +533,9 @@ auto AICommandEvaluator::EvaluateCommand(
allCastleCoords,
deadline);
return std::move(evaluation.lookaheadScore);
} else if (guessedDescriptor->HasOdds()) {
}
if (guessedDescriptor->HasOdds()) {
const auto successChancePercentile = guessedDescriptor->GetOddsPercentile();
const double successChance = static_cast<double>(successChancePercentile) / 100.0;
@@ -575,47 +580,47 @@ auto AICommandEvaluator::EvaluateCommand(
.score = std::lerp(failure.score, success.score, successChance),
.completed = true};
});
} else {
// For non-deterministic commands without odds, use multiple attempts
std::vector<std::future<EvaluationResult>> lookaheadFutures;
const int sampleCount = std::max(1, maxRepeatCount);
lookaheadFutures.reserve(sampleCount);
for (int repeatIteration = 0; repeatIteration < sampleCount; repeatIteration++) {
auto sequence = std::vector{RandomnessSampleForRepeat(repeatIteration, sampleCount)};
auto evaluation = EvaluateWithRandomness(
pid,
isDefender,
commandIndex,
remainingLookahead,
maxRepeatCount,
std::make_shared<SequenceRandomGenerator>(sequence),
guessedEngine,
attackerStrategy,
allCastleCoords,
deadline);
lookaheadFutures.push_back(std::move(evaluation.lookaheadScore));
}
// Return a future that computes the average when needed
return std::async(
std::launch::deferred,
[lookaheadFutures = std::move(lookaheadFutures),
sampleCount]() mutable -> EvaluationResult {
ScoreValue total = 0.0;
bool completed = true;
for (auto& future : lookaheadFutures) {
const auto result = future.get();
if (!result.completed) { completed = false; }
total += result.score;
}
if (!completed) { return EvaluationResult{.score = 0.0, .completed = false}; }
return EvaluationResult{
.score = total / static_cast<ScoreValue>(sampleCount),
.completed = true};
});
}
// For non-deterministic commands without odds, use multiple attempts
std::vector<std::future<EvaluationResult>> lookaheadFutures;
const int sampleCount = std::max(1, maxRepeatCount);
lookaheadFutures.reserve(sampleCount);
for (int repeatIteration = 0; repeatIteration < sampleCount; repeatIteration++) {
auto sequence = std::vector{RandomnessSampleForRepeat(repeatIteration, sampleCount)};
auto evaluation = EvaluateWithRandomness(
pid,
isDefender,
commandIndex,
remainingLookahead,
maxRepeatCount,
std::make_shared<SequenceRandomGenerator>(std::move(sequence)),
guessedEngine,
attackerStrategy,
allCastleCoords,
deadline);
lookaheadFutures.push_back(std::move(evaluation.lookaheadScore));
}
// Return a future that computes the average when needed
return std::async(
std::launch::deferred,
[lookaheadFutures = std::move(lookaheadFutures),
sampleCount]() mutable -> EvaluationResult {
ScoreValue total = 0.0;
bool completed = true;
for (auto& future : lookaheadFutures) {
const auto result = future.get();
if (!result.completed) { completed = false; }
total += result.score;
}
if (!completed) { return EvaluationResult{.score = 0.0, .completed = false}; }
return EvaluationResult{
.score = total / static_cast<ScoreValue>(sampleCount),
.completed = true};
});
}
} // namespace shardok
@@ -57,11 +57,11 @@ public:
/// Find the best command among all available commands at the given depth.
struct IndexAndScore {
size_t index;
CommandType type;
ScoreValue lookaheadScore;
ScoreValue immediateScore;
bool completed;
size_t index{};
CommandType type{};
ScoreValue lookaheadScore{};
ScoreValue immediateScore{};
bool completed{};
};
[[nodiscard]] auto FindBestCommand(
@@ -81,8 +81,8 @@ private:
BattalionTypeGetter battalionTypeGetter_; // Store by value
struct ImmediateAndLookaheadScore {
ScoreValue immediateScore;
bool completed;
ScoreValue immediateScore{};
bool completed{};
std::future<EvaluationResult> lookaheadScore;
};
@@ -32,6 +32,7 @@ auto AIDefenderStrategySelector::BestDefenderStrategy(
bool canFlee = false;
vector<UnitId> attackerUnitIdsRequiringWaterCrossing{};
attackerUnitIdsRequiringWaterCrossing.reserve(gameState->units()->size());
for (const auto& player : *gameState->player_infos()) {
if (!player->is_defender()) {
const auto& unitIdsRequiringWaterCrossing = UnitIdsRequiringWaterCrossing(
@@ -5,15 +5,27 @@
#ifndef EAGLE0_SHARDOK_AI_AI_EXPERIMENT_CONFIG_HPP
#define EAGLE0_SHARDOK_AI_AI_EXPERIMENT_CONFIG_HPP
#include <charconv>
#include <cstdlib>
#include <string>
#include <string_view>
#include <system_error>
namespace shardok {
inline auto CurrentAIExperimentId() -> int {
const char* rawValue = std::getenv("SHARDOK_SCORING_EXPERIMENT_ID");
if (rawValue == nullptr || std::string(rawValue).empty()) { return 0; }
return std::atoi(rawValue);
if (rawValue == nullptr) { return 0; }
const std::string_view value(rawValue);
if (value.empty()) { return 0; }
int experimentId = 0;
const auto parseResult =
std::from_chars(value.data(), value.data() + value.size(), experimentId);
if (parseResult.ec != std::errc{} || parseResult.ptr != value.data() + value.size()) {
return 0;
}
return experimentId;
}
inline auto IsAIExperiment(const int id) -> bool { return CurrentAIExperimentId() == id; }
@@ -25,6 +25,11 @@ class AIEvaluationCounter {
public:
AIEvaluationCounter();
~AIEvaluationCounter();
AIEvaluationCounter(const AIEvaluationCounter&) = delete;
auto operator=(const AIEvaluationCounter&) -> AIEvaluationCounter& = delete;
AIEvaluationCounter(AIEvaluationCounter&&) = delete;
auto operator=(AIEvaluationCounter&&) -> AIEvaluationCounter& = delete;
static int GetCurrentCount();
};
@@ -41,8 +46,8 @@ struct AITimeBudget {
// If isAllAiBattle is true, uses allAiBattleTimeBudgetMaximum instead of the normal maximum.
auto CalculateTimeBudget(
PlayerId playerId,
const GameSettingsSPtr &settings,
const GameStateW &state,
const GameSettingsSPtr& settings,
const GameStateW& state,
size_t numCommands,
bool isAllAiBattle) -> AITimeBudget;
@@ -11,10 +11,6 @@
namespace shardok {
using std::begin;
using std::end;
using std::shared_ptr;
constexpr double kProfessionValue = 200;
constexpr double kVigorScoreMultiplier = 5.0;
constexpr double kCastleMultiplierBonus = 1.0;
@@ -280,7 +276,7 @@ auto GetRangedAttackBonus(
const int meteorRange,
const double minVigorToCast,
const ActionPoints archeryActionPointCost) -> double {
vector<double> rangedAttackValues{};
double bestRangedAttackValue = 0.0;
const auto &enemyUnits = isAttacker ? defenderUnits : attackerUnits;
const auto &friendlyUnits = isAttacker ? attackerUnits : defenderUnits;
@@ -296,7 +292,7 @@ auto GetRangedAttackBonus(
net::eagle0::shardok::storage::fb::MultiroundMagicState_NONE)) {
if (unit->attached_hero().profession_info().meteor_cast_state() ==
net::eagle0::shardok::storage::fb::MultiroundMagicState_NONE) {
rangedAttackValues.push_back(lightningValue(unit));
bestRangedAttackValue = std::max(bestRangedAttackValue, lightningValue(unit));
} else {
const double mv = meteorValue(
unit,
@@ -306,14 +302,14 @@ auto GetRangedAttackBonus(
friendlyUnits,
meteorRange,
minVigorToCast);
rangedAttackValues.push_back(mv);
bestRangedAttackValue = std::max(bestRangedAttackValue, mv);
}
}
if (attackLocations.EngineerLocations().Contains(unitLocation) && unit->has_attached_hero() &&
unit->attached_hero().profession_info().profession() ==
net::eagle0::shardok::storage::fb::Profession_ENGINEER) {
rangedAttackValues.push_back(reduceValue(unit, terrain));
bestRangedAttackValue = std::max(bestRangedAttackValue, reduceValue(unit, terrain));
}
if (attackLocations.NecromancerLocations().Contains(unitLocation) &&
@@ -321,23 +317,24 @@ auto GetRangedAttackBonus(
unit->attached_hero().profession_info().profession() ==
net::eagle0::shardok::storage::fb::Profession_NECROMANCER &&
unit->attached_hero().control_info().controlled_unit_id() != -1) {
rangedAttackValues.push_back(fearValue(enemyUnits));
bestRangedAttackValue = std::max(bestRangedAttackValue, fearValue(enemyUnits));
}
if (attackLocations.ArcheryLocations().Contains(unitLocation) &&
unit->volleys_remaining() > 0) {
rangedAttackValues.push_back(archeryValue(enemyUnits, canShootNow));
bestRangedAttackValue =
std::max(bestRangedAttackValue, archeryValue(enemyUnits, canShootNow));
}
// Longbowmen in castle can archer adjacent
if (attackLocations.AdjacentLocations().Contains(unitLocation) &&
unit->volleys_remaining() > 0 && terrain->modifier().castle().present() &&
unit->battalion().type() == net::eagle0::shardok::storage::fb::BattalionTypeId_LONGBOWMEN) {
rangedAttackValues.push_back(archeryValue(enemyUnits, canShootNow));
bestRangedAttackValue =
std::max(bestRangedAttackValue, archeryValue(enemyUnits, canShootNow));
}
if (rangedAttackValues.empty()) return 0.0;
return *std::max_element(begin(rangedAttackValues), end(rangedAttackValues));
return bestRangedAttackValue;
}
auto UnitValue(
@@ -18,7 +18,7 @@ TranspositionTable::TranspositionTable() : table(TABLE_SIZE) {
clear();
}
uint64_t TranspositionTable::hashGameState(const GameStateW& state) const {
auto TranspositionTable::hashGameState(const GameStateW& state) const -> uint64_t {
// 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
@@ -28,8 +28,8 @@ public:
std::atomic<uint64_t> stores{0};
std::atomic<uint64_t> collisions{0};
double hitRate() const {
uint64_t p = probes.load();
[[nodiscard]] auto hitRate() const -> double {
const uint64_t p = probes.load();
return p > 0 ? (100.0 * hits.load() / p) : 0.0;
}
@@ -60,7 +60,7 @@ private:
std::atomic<uint16_t> current_age{0};
// Hash function for FlatBuffer game state
uint64_t hashGameState(const GameStateW& state) const;
[[nodiscard]] auto hashGameState(const GameStateW& state) const -> uint64_t;
public:
TranspositionTable();
@@ -78,7 +78,7 @@ public:
void incrementAge() { current_age++; }
// Get statistics
const Stats& getStats() const { return stats; }
[[nodiscard]] auto getStats() const -> const Stats& { return stats; }
// Print statistics to stdout
void printStats() const;
@@ -37,6 +37,23 @@ auto LegalActionsCacheKey(const uint64_t stateHash, const bool rootIsDefender) -
return rootIsDefender ? stateHash ^ kDefenderRoleHash : stateHash;
}
auto SortActionsByWeight(
std::vector<std::unique_ptr<MCTSAction>> actions,
const std::vector<double>& weights) -> std::vector<std::unique_ptr<MCTSAction>> {
std::vector<size_t> sortedIndices(actions.size());
std::iota(sortedIndices.begin(), sortedIndices.end(), 0);
std::sort(sortedIndices.begin(), sortedIndices.end(), [&weights](size_t a, size_t b) {
return weights[a] > weights[b];
});
std::vector<std::unique_ptr<MCTSAction>> sortedActions;
sortedActions.reserve(actions.size());
for (size_t idx : sortedIndices) { sortedActions.push_back(std::move(actions[idx])); }
return sortedActions;
}
} // namespace
// Shared cache for legal actions (uses lock-free parallel hash map for thread safety)
@@ -282,19 +299,7 @@ std::vector<std::unique_ptr<MCTSAction>> ShardokGameEngine::getLegalActions(
// Sort actions by weight (descending) to ensure MCTS explores high-value actions first
const std::vector<double> weights = getActionWeights(actions, state);
std::vector<size_t> sortedIndices(actions.size());
std::iota(sortedIndices.begin(), sortedIndices.end(), 0);
std::sort(sortedIndices.begin(), sortedIndices.end(), [&weights](size_t a, size_t b) {
return weights[a] > weights[b];
});
std::vector<std::unique_ptr<MCTSAction>> sortedActions;
sortedActions.reserve(actions.size());
for (size_t idx : sortedIndices) { sortedActions.push_back(std::move(actions[idx])); }
return sortedActions;
return SortActionsByWeight(std::move(actions), weights);
}
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
@@ -355,20 +360,7 @@ std::vector<std::unique_ptr<MCTSAction>> ShardokGameEngine::getLegalActions(
// get explored deeply. Original indices are preserved in ShardokAction::getIndex()
const std::vector<double> weights = getActionWeights(actions, state);
// Create index vector for sorting
std::vector<size_t> sortedIndices(actions.size());
std::iota(sortedIndices.begin(), sortedIndices.end(), 0);
// Sort indices by weight (descending)
std::sort(sortedIndices.begin(), sortedIndices.end(), [&weights](size_t a, size_t b) {
return weights[a] > weights[b];
});
// Reorder actions according to sorted indices
std::vector<std::unique_ptr<MCTSAction>> sortedActions;
sortedActions.reserve(actions.size());
for (size_t idx : sortedIndices) { sortedActions.push_back(std::move(actions[idx])); }
actions = std::move(sortedActions);
actions = SortActionsByWeight(std::move(actions), weights);
const auto actionsEnd = std::chrono::high_resolution_clock::now();
timeInLegalActionsComputation_.fetch_add(
@@ -160,6 +160,8 @@ auto AttackerHoldsCriticalTilesVictoryScore(
ActionPoints braveWaterCost) -> ScoreValue {
vector<const Unit*> playerUnits{};
vector<const Unit*> claimablePlayerUnits{};
playerUnits.reserve(gameState->units()->size());
claimablePlayerUnits.reserve(gameState->units()->size());
for (const Unit* unit : *gameState->units()) {
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
unit->player_id() == player->player_id()) {
@@ -260,6 +262,8 @@ auto LastPlayerStandingVictoryScore(
vector<const Unit*> playerUnits{};
vector<const Unit*> otherUnits{};
playerUnits.reserve(gameState->units()->size());
otherUnits.reserve(gameState->units()->size());
for (const Unit* unit : *gameState->units()) {
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
@@ -144,6 +144,10 @@ private:
public:
AttackerScoreTimer() : startTime(std::chrono::high_resolution_clock::now()) {}
AttackerScoreTimer(const AttackerScoreTimer &) = delete;
auto operator=(const AttackerScoreTimer &) -> AttackerScoreTimer & = delete;
AttackerScoreTimer(AttackerScoreTimer &&) = delete;
auto operator=(AttackerScoreTimer &&) -> AttackerScoreTimer & = delete;
~AttackerScoreTimer() {
auto endTime = std::chrono::high_resolution_clock::now();
@@ -47,19 +47,19 @@ auto AbstractAIScoreCalculator::CalculateUnitsScoreComponents(
// Memoization cache for EffectiveDistance calls
EffectiveDistanceCache distanceCache;
// Early return for empty game states
const size_t estimatedUnitCount = cachedUnits->size();
if (estimatedUnitCount == 0) { return UnitsScoreComponents{0.0, 0.0}; }
std::vector<const Unit *> attackerUnits{};
std::vector<const Unit *> defenderUnits{};
// Pre-allocate vectors based on estimated unit ratios to avoid reallocations
const size_t estimatedUnitCount = cachedUnits->size();
attackerUnits.reserve(estimatedUnitCount - 1);
defenderUnits.reserve(estimatedUnitCount - 1);
attackerUnits.reserve(estimatedUnitCount);
defenderUnits.reserve(estimatedUnitCount);
double attackerUnitsValue = 0;
double defenderUnitsValue = 0;
// Early return for empty game states
if (cachedUnits->size() == 0) { return UnitsScoreComponents{0.0, 0.0}; }
auto occupants = Occupants(*cachedUnits, cachedRowCount, cachedColumnCount);
for (const Unit *unit : *cachedUnits) {
@@ -8,6 +8,7 @@
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <utility>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
@@ -332,6 +333,7 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
// Create player info protos
std::vector<net::eagle0::shardok::common::PlayerInfo> playerInfoProtos;
playerInfoProtos.reserve(2);
// Attacker info
net::eagle0::shardok::common::PlayerInfo attackerInfo;
@@ -342,7 +344,7 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
net::eagle0::shardok::common::VICTORY_CONDITION_LAST_PLAYER_STANDING);
attackerInfo.add_victory_conditions(
net::eagle0::shardok::common::VICTORY_CONDITION_HOLDS_CRITICAL_TILES);
playerInfoProtos.push_back(attackerInfo);
playerInfoProtos.push_back(std::move(attackerInfo));
// Defender info
net::eagle0::shardok::common::PlayerInfo defenderInfo;
@@ -353,10 +355,11 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
net::eagle0::shardok::common::VICTORY_CONDITION_LAST_PLAYER_STANDING);
defenderInfo.add_victory_conditions(
net::eagle0::shardok::common::VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS);
playerInfoProtos.push_back(defenderInfo);
playerInfoProtos.push_back(std::move(defenderInfo));
// Create units from config
std::vector<net::eagle0::shardok::storage::fb::Unit> units;
units.reserve(config_.attacker().units_size() + config_.defender().units_size());
// Attacker units
const int attackerUnitCount = config_.attacker().units_size();
@@ -450,7 +453,7 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
unit.mutable_opponent_knowledge()->Mutate(0, 0);
unit.mutable_opponent_knowledge()->Mutate(1, 0);
units.push_back(unit);
units.push_back(std::move(unit));
}
// Defender units
@@ -546,7 +549,7 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
unit.mutable_opponent_knowledge()->Mutate(0, 0);
unit.mutable_opponent_knowledge()->Mutate(1, 0);
units.push_back(unit);
units.push_back(std::move(unit));
}
// Create game state
@@ -914,6 +917,7 @@ BattleResult AiBattleSimulator::CreateResultFromGameState(
// Collect surviving units (for winner, or all units if draw)
const auto* units = state->units();
if (units) {
result.survivingUnits.reserve(units->size());
for (const auto* unit : *units) {
if (!unit) { continue; }
@@ -88,8 +88,7 @@ void GenerateConfigFile(const std::string& outputPath) {
// Output to file
std::ofstream outFile(outputPath);
if (!outFile.is_open()) {
std::cerr << "Error: Failed to open output file: " << outputPath << "\n";
std::exit(1);
throw std::runtime_error("Failed to open output file: " + outputPath);
}
outFile << jsonString << "\n";
std::cout << "Sample config written to: " << outputPath << "\n";
@@ -295,8 +294,7 @@ BattleResult RunBattle(
if (!config) {
restoreOutput();
std::cerr << "Error: Failed to load config file\n";
std::exit(1);
throw std::runtime_error("Failed to load config file");
}
// Create simulator
@@ -328,6 +326,8 @@ int main(int argc, char* argv[]) {
std::vector<std::string> configPaths;
std::vector<std::string> statePaths;
configPaths.reserve(argc - 1);
statePaths.reserve(argc - 1);
std::string outputPath;
std::string tracePath;
std::string runLabel;
@@ -2,7 +2,6 @@
// Converts saved Eagle ShardokBattle/GameState protobuf blobs into AI battle configs.
//
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <optional>
@@ -146,9 +145,9 @@ int main(int argc, char *argv[]) {
} else if (arg.starts_with("--output=")) {
outputPath = arg.substr(9);
} else if (arg.starts_with("--seed=")) {
seed = std::atoi(arg.substr(7).c_str());
seed = std::stoi(arg.substr(7));
} else if (arg.starts_with("--max-rounds=")) {
maxRounds = std::atoi(arg.substr(13).c_str());
maxRounds = std::stoi(arg.substr(13));
} else if (arg.starts_with("--attacker-scoring=")) {
const auto scoring = ParseScoring(arg.substr(19));
if (!scoring.has_value()) { throw std::runtime_error("Unknown attacker scoring"); }
@@ -115,6 +115,7 @@ int main(int argc, char *argv[]) {
std::string resultPath;
std::string stateFbPath;
std::vector<std::string> actionViewPaths;
actionViewPaths.reserve(argc - 1);
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
@@ -145,6 +146,9 @@ int main(int argc, char *argv[]) {
std::vector<int> attackerFactions;
std::vector<int> defenderFactions;
std::vector<std::string> playerSides;
attackerFactions.reserve(battle.players_size());
defenderFactions.reserve(battle.players_size());
playerSides.reserve(battle.players_size());
bool aiVsAi = true;
for (const auto &player : battle.players()) {
if (player.eagle_fid() == 3 || player.eagle_fid() == 4) { aiVsAi = false; }
@@ -4,9 +4,10 @@
#include "AIPerformanceRunner.hpp"
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include "PerformanceTestGameStateBuilder.hpp"
@@ -47,7 +48,7 @@ auto CompletionReasonToString(EvaluationCompletionReason reason) -> std::string
/**
* Parse command line arguments into a configuration struct.
*/
auto ParseCommandLineArgs(int argc, char* argv[]) -> PerformanceTestConfig {
auto ParseCommandLineArgs(int argc, char* argv[]) -> std::optional<PerformanceTestConfig> {
PerformanceTestConfig config;
for (int i = 1; i < argc; ++i) {
@@ -63,7 +64,7 @@ auto ParseCommandLineArgs(int argc, char* argv[]) -> PerformanceTestConfig {
<< " --defender=BOOL AI is defender (default: false)\n"
<< " --verbose Enable verbose output\n"
<< " --help, -h Show this help message\n";
std::exit(0);
return std::nullopt;
} else if (arg.starts_with("--map=")) {
config.mapName = arg.substr(6);
} else if (arg.starts_with("--turns=")) {
@@ -74,9 +75,8 @@ auto ParseCommandLineArgs(int argc, char* argv[]) -> PerformanceTestConfig {
} else if (arg == "--verbose") {
config.verbose = true;
} else {
std::cerr << "Unknown argument: " << arg << "\n";
std::cerr << "Use --help for usage information.\n";
std::exit(1);
throw std::invalid_argument(
"Unknown argument: " + arg + "\nUse --help for usage information.");
}
}
@@ -96,7 +96,9 @@ int main(int argc, char* argv[]) {
std::cout << "==============================\n";
// Parse command line arguments
auto config = ParseCommandLineArgs(argc, argv);
const auto parsedConfig = ParseCommandLineArgs(argc, argv);
if (!parsedConfig.has_value()) { return 0; }
const auto& config = parsedConfig.value();
if (config.verbose) {
std::cout << "Configuration:\n";
@@ -163,6 +165,7 @@ int main(int argc, char* argv[]) {
std::cout << "Running AI performance test for " << config.numTurns << " turns...\n";
std::vector<AIPerformanceMetrics> metrics;
if (config.numTurns > 0) { metrics.reserve(config.numTurns); }
for (int turn = 0; turn < config.numTurns; ++turn) {
// Check if AI can make a move
@@ -4,7 +4,9 @@
#include "PerformanceTestGameStateBuilder.hpp"
#include <algorithm>
#include <filesystem>
#include <utility>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai_testing_common/GameSettingsFactory.hpp"
@@ -53,6 +55,7 @@ auto PerformanceTestGameStateBuilder::CreateCustomTestGameState(
// Create player info protos
std::vector<net::eagle0::shardok::common::PlayerInfo> playerInfoProtos;
playerInfoProtos.reserve(2);
// AI player
net::eagle0::shardok::common::PlayerInfo aiPlayerInfo;
@@ -68,7 +71,7 @@ auto PerformanceTestGameStateBuilder::CreateCustomTestGameState(
aiPlayerInfo.add_victory_conditions(
net::eagle0::shardok::common::VICTORY_CONDITION_HOLDS_CRITICAL_TILES);
}
playerInfoProtos.push_back(aiPlayerInfo);
playerInfoProtos.push_back(std::move(aiPlayerInfo));
// Human player
net::eagle0::shardok::common::PlayerInfo humanPlayerInfo;
@@ -84,13 +87,16 @@ auto PerformanceTestGameStateBuilder::CreateCustomTestGameState(
humanPlayerInfo.add_victory_conditions(
net::eagle0::shardok::common::VICTORY_CONDITION_HOLDS_CRITICAL_TILES);
}
playerInfoProtos.push_back(humanPlayerInfo);
playerInfoProtos.push_back(std::move(humanPlayerInfo));
// Create units
const int clampedAiUnitCount = std::clamp(aiUnitCount, 0, 6);
const int clampedHumanUnitCount = std::clamp(humanUnitCount, 0, 6);
std::vector<net::eagle0::shardok::storage::fb::Unit> units;
units.reserve(clampedAiUnitCount + clampedHumanUnitCount);
// Create AI units in reserve (location -1, -1)
for (int i = 0; i < aiUnitCount && i < 6; ++i) {
for (int i = 0; i < clampedAiUnitCount; ++i) {
units.push_back(AddGenericUnit(
AI_PLAYER_ID,
i, // Unit ID
@@ -101,10 +107,10 @@ auto PerformanceTestGameStateBuilder::CreateCustomTestGameState(
}
// Create human units in reserve (location -1, -1)
for (int i = 0; i < humanUnitCount && i < 6; ++i) {
for (int i = 0; i < clampedHumanUnitCount; ++i) {
units.push_back(AddGenericUnit(
HUMAN_PLAYER_ID,
aiUnitCount + i, // Unit ID starting aiUnitCount
clampedAiUnitCount + i, // Unit ID starting after generated AI units
net::eagle0::shardok::storage::fb::Coords(-1, -1), // Reserve location
NO_PROFESSION,
HEAVY_INFANTRY_BATTALION_TYPE,
@@ -235,4 +241,4 @@ auto PerformanceTestGameStateBuilder::AddGenericUnit(
return unit;
}
} // namespace shardok
} // namespace shardok
@@ -24,11 +24,6 @@
namespace shardok {
using net::eagle0::common::ScopedShardokLatencyTrace;
using std::condition_variable;
using std::mutex;
using std::scoped_lock;
using std::unique_lock;
using std::weak_ptr;
using net::eagle0::shardok::common::GameStatus;
@@ -66,9 +61,9 @@ public:
[[nodiscard]] auto what() const noexcept -> const char * override { return message.c_str(); }
};
vector<shared_ptr<ShardokAIClient>> ShardokGameController::MakeAIClients(
const unique_ptr<ShardokEngine> &e) {
vector<shared_ptr<ShardokAIClient>> aic;
std::vector<std::shared_ptr<ShardokAIClient>> ShardokGameController::MakeAIClients(
const std::unique_ptr<ShardokEngine> &e) {
std::vector<std::shared_ptr<ShardokAIClient>> aic;
mcts::MCTSConfig mctsConfig;
mctsConfig.backpropagationPolicy = mcts::MCTSBackpropagationPolicy::MINIMAX;
@@ -78,6 +73,7 @@ vector<shared_ptr<ShardokAIClient>> ShardokGameController::MakeAIClients(
// Check if all players are AI - if so, use faster time budgets
const auto &playerInfos = e->GetPlayerInfos();
aic.reserve(playerInfos.size());
const bool isAllAiBattle =
std::ranges::all_of(playerInfos, [](const auto &pi) { return pi.is_ai(); });
@@ -107,7 +103,7 @@ vector<shared_ptr<ShardokAIClient>> ShardokGameController::MakeAIClients(
void ShardokGameController::LockedNotifyClients() const { updateCondition.notify_all(); }
auto ShardokGameController::LockedAIClientForPid(PlayerId pid) const
-> shared_ptr<ShardokAIClient> {
-> std::shared_ptr<ShardokAIClient> {
const auto it = std::ranges::find_if(aiClients, [pid](const auto &client) {
return client->GetPlayerId() == pid;
});
@@ -120,7 +116,7 @@ void ShardokGameController::DoAIThread() {
while (aiThreadKeepGoing) {
// Phase 1: Gather data for AI decision (brief lock)
shared_ptr<ShardokAIClient> aiClient;
std::shared_ptr<ShardokAIClient> aiClient;
PlayerId playerId;
GameSettingsSPtr settings;
net::eagle0::shardok::api::GameStateView gsv;
@@ -128,7 +124,7 @@ void ShardokGameController::DoAIThread() {
size_t expectedHistoryCount;
{
unique_lock lk(masterLock);
std::unique_lock lk(masterLock);
if (engine->GameIsOver()) {
aiThreadKeepGoing = false;
@@ -172,7 +168,7 @@ void ShardokGameController::DoAIThread() {
// Phase 3: Post the command (brief lock)
{
unique_lock lk(masterLock);
std::unique_lock lk(masterLock);
// Verify state hasn't changed while we were thinking
if (engine->GetUnfilteredHistoryCount() != expectedHistoryCount) {
@@ -228,7 +224,7 @@ void ShardokGameController::DoAIThread() {
ShardokGameController::~ShardokGameController() {
{
unique_lock lk(masterLock);
std::unique_lock lk(masterLock);
aiThreadKeepGoing = false;
aiCondition.notify_one();
}
@@ -236,7 +232,7 @@ ShardokGameController::~ShardokGameController() {
}
void CheckFactionId(
const unique_ptr<ShardokEngine> &engine,
const std::unique_ptr<ShardokEngine> &engine,
const PlayerId shardokPlayerId,
const int eagleFactionId) {
bool found = false;
@@ -256,7 +252,7 @@ void ShardokGameController::PostCommand(
const int eagleFactionId,
const int64_t token,
const int64_t commandIndex,
const optional<int32_t> roll) {
const std::optional<int32_t> roll) {
ScopedShardokLatencyTrace totalTrace(
"controller_post_command_total",
cachedGameId,
@@ -264,7 +260,7 @@ void ShardokGameController::PostCommand(
{"eagle_faction_id", std::to_string(eagleFactionId)},
{"token", std::to_string(token)},
{"command_index", std::to_string(commandIndex)}});
scoped_lock<mutex> guard(masterLock);
std::scoped_lock<std::mutex> guard(masterLock);
CheckFactionId(engine, shardokPlayerId, eagleFactionId);
@@ -303,7 +299,7 @@ void ShardokGameController::PostPlacementCommands(
const PlayerId shardokPlayerId,
const int eagleFactionId,
const int64_t token,
const vector<UnitPlacementInfo> &infos) {
const std::vector<UnitPlacementInfo> &infos) {
ScopedShardokLatencyTrace totalTrace(
"controller_post_placement_commands_total",
cachedGameId,
@@ -312,7 +308,7 @@ void ShardokGameController::PostPlacementCommands(
{"token", std::to_string(token)},
{"placement_count", std::to_string(infos.size())}});
{
scoped_lock<mutex> guard(masterLock);
std::scoped_lock<std::mutex> guard(masterLock);
CheckFactionId(engine, shardokPlayerId, eagleFactionId);
@@ -356,7 +352,7 @@ void ShardokGameController::PostPlacementCommands(
}
auto ShardokGameController::GetCurrentGameStateBytes() -> byte_vector {
scoped_lock<mutex> guard(masterLock);
std::scoped_lock<std::mutex> guard(masterLock);
return engine->GetCurrentGameStateBytes();
}
@@ -368,9 +364,9 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
AllUpdates updates{};
incomingRegistrations++;
vector<net::eagle0::shardok::storage::ShardokActionWithResultingState> awrs;
std::vector<net::eagle0::shardok::storage::ShardokActionWithResultingState> awrs;
{
unique_lock<mutex> guard(masterLock);
std::unique_lock<std::mutex> guard(masterLock);
updates.newUnfilteredCount = engine->GetUnfilteredHistoryCount();
@@ -400,7 +396,7 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
auto fid = pi.eagle_faction_id();
vector<net::eagle0::shardok::api::ActionResultView> actionResultViews;
std::vector<net::eagle0::shardok::api::ActionResultView> actionResultViews;
{
ScopedShardokLatencyTrace trace(
"engine_filter_new_results",
@@ -411,7 +407,7 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
actionResultViews = engine->FilterNewResults(pi.player_id(), startingActionId);
}
shared_ptr<AvailableCommands> acs = nullptr;
std::shared_ptr<AvailableCommands> acs = nullptr;
const auto availableCommands = [&] {
ScopedShardokLatencyTrace trace(
@@ -430,9 +426,11 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
if (!availableCommands.empty() || !previewCommands.empty()) {
acs = std::make_shared<AvailableCommands>();
acs->mutable_current_command()->Reserve(availableCommands.size());
*acs->mutable_current_command() = {
std::begin(availableCommands),
std::end(availableCommands)};
acs->mutable_preview_command()->Reserve(previewCommands.size());
*acs->mutable_preview_command() = {
std::begin(previewCommands),
std::end(previewCommands)};
@@ -483,6 +481,7 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
// Extract battle progress data from the FlatBuffer game state
const auto *gs = engine->GetCurrentGameState().Get();
updates.currentRound = gs->current_round();
updates.playerTroopCounts.reserve(gs->player_infos()->size());
for (const auto *player : *gs->player_infos()) {
int32_t troopCount = 0;
for (const auto *unit : *gs->units()) {
@@ -504,36 +503,37 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
}
auto ShardokGameController::GameIsOver() -> bool {
scoped_lock<mutex> guard(masterLock);
std::scoped_lock<std::mutex> guard(masterLock);
const auto &gameStatus = engine->GetGameStatus();
return ShardokEngine::GameIsOver(gameStatus);
}
auto ShardokGameController::GameOverStatus() -> net::eagle0::shardok::common::GameStatus {
scoped_lock<mutex> guard(masterLock);
std::scoped_lock<std::mutex> guard(masterLock);
return fb::ToProto(engine->GetGameStatus());
}
auto ShardokGameController::EndGameUnits() -> vector<net::eagle0::shardok::storage::ResolvedUnit> {
scoped_lock<mutex> guard(masterLock);
auto ShardokGameController::EndGameUnits()
-> std::vector<net::eagle0::shardok::storage::ResolvedUnit> {
std::scoped_lock<std::mutex> guard(masterLock);
return engine->EndGameUnits();
}
auto ShardokGameController::ResolvedPlayerInfos()
-> vector<net::eagle0::shardok::common::PlayerInfo> {
scoped_lock<mutex> guard(masterLock);
-> std::vector<net::eagle0::shardok::common::PlayerInfo> {
std::scoped_lock<std::mutex> guard(masterLock);
return engine->GetPlayerInfos();
}
void ShardokGameController::RegisterSubscriber(std::shared_ptr<StreamSubscriber> subscriber) {
scoped_lock<mutex> guard(subscriberLock);
std::scoped_lock<std::mutex> guard(subscriberLock);
subscribers.push_back(subscriber);
}
void ShardokGameController::UnregisterSubscriber(const StreamSubscriber *subscriber) {
scoped_lock<mutex> guard(subscriberLock);
std::scoped_lock<std::mutex> guard(subscriberLock);
subscribers.erase(
std::remove_if(
subscribers.begin(),
@@ -556,7 +556,7 @@ auto ShardokGameController::WaitForUpdatesAndPush(
GameOverInfo gameOverInfo{};
{
unique_lock<mutex> guard(masterLock);
std::unique_lock<std::mutex> guard(masterLock);
// Wait for updates, game over, or AI thread failure
updateCondition.wait(guard, [this, lastPushedActionId] {
@@ -10,9 +10,14 @@
#define EAGLE0_SHARDOK_CONTROLLER_SHARDOK_GAME_CONTROLLER_HPP
#include <atomic>
#include <condition_variable>
#include <ctime>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <utility>
#include <vector>
@@ -26,31 +31,27 @@
namespace shardok {
using net::eagle0::shardok::api::ActionResultView;
using net::eagle0::shardok::api::AvailableCommands;
using std::optional;
using std::shared_ptr;
using std::unique_ptr;
using std::weak_ptr;
// Forward declaration
class ShardokGameController;
/// Info about a game that has ended, for notifying subscribers
struct GameOverInfo {
vector<net::eagle0::shardok::common::PlayerInfo> playerInfos;
vector<net::eagle0::shardok::storage::ResolvedUnit> endGameUnits;
std::vector<net::eagle0::shardok::common::PlayerInfo> playerInfos;
std::vector<net::eagle0::shardok::storage::ResolvedUnit> endGameUnits;
net::eagle0::shardok::common::GameStatus gameStatus;
};
/// Updates for a single player (includes faction ID for client routing)
struct OnePlayerUpdates {
int32_t eagleFactionId;
vector<ActionResultView> resultViews;
shared_ptr<AvailableCommands> availableCommands;
std::vector<ActionResultView> resultViews;
std::shared_ptr<AvailableCommands> availableCommands;
OnePlayerUpdates(
const int32_t fid,
const vector<ActionResultView>& arvs,
const shared_ptr<AvailableCommands>& acs)
const std::vector<ActionResultView>& arvs,
const std::shared_ptr<AvailableCommands>& acs)
: eagleFactionId(fid),
resultViews(arvs),
availableCommands(acs) {}
@@ -70,12 +71,12 @@ public:
/// Called when new game updates are available
virtual void OnUpdate(
const vector<ActionResult>& mainResults,
const vector<OnePlayerUpdates>& filteredResults,
const std::vector<ActionResult>& mainResults,
const std::vector<OnePlayerUpdates>& filteredResults,
int32_t newUnfilteredCount,
const byte_vector& currentGameState,
int32_t currentRound,
const vector<std::pair<int32_t, int32_t>>& playerTroopCounts) = 0;
const std::vector<std::pair<int32_t, int32_t>>& playerTroopCounts) = 0;
/// Called when the game ends
virtual void OnGameOver(const GameOverInfo& info) = 0;
@@ -101,51 +102,52 @@ private:
mutable std::mutex subscriberLock{};
std::vector<std::weak_ptr<StreamSubscriber>> subscribers{};
string serializedRequest;
std::string serializedRequest;
unique_ptr<ShardokEngine> engine;
std::unique_ptr<ShardokEngine> engine;
// Cached immutable data - safe to access without lock since it never changes after construction
const GameId cachedGameId;
std::atomic_int incomingRegistrations = 0;
const string mapName;
const string logFilePath;
const std::string mapName;
const std::string logFilePath;
// AI thread error state - written once by AI thread before setting atomic flag
std::atomic<bool> aiThreadFailed{false};
std::string aiThreadErrorMessage;
vector<shared_ptr<ShardokAIClient>> aiClients;
std::vector<std::shared_ptr<ShardokAIClient>> aiClients;
std::thread aiThread;
// Each entry maps an Eagle faction id of a non-participant watcher to the
// Shardok pids of participants whose allies list named this watcher. Used
// when generating per-watcher filtered views in GetUpdates.
const vector<std::pair<int32_t, vector<PlayerId>>> watcherAllies;
const std::vector<std::pair<int32_t, std::vector<PlayerId>>> watcherAllies;
static auto MakeLogFilePath() -> string {
const time_t timer = time(nullptr);
static auto MakeLogFilePath() -> std::string {
const std::time_t timer = std::time(nullptr);
char buf[255];
strftime(buf, 255, "/tmp/playthrough_%Y_%m_%d_%H%M%S.log", localtime(&timer));
return string(buf);
std::strftime(buf, 255, "/tmp/playthrough_%Y_%m_%d_%H%M%S.log", std::localtime(&timer));
return std::string(buf);
}
void LockedNotifyClients() const;
auto LockedAIClientForPid(PlayerId pid) const -> shared_ptr<ShardokAIClient>;
auto LockedAIClientForPid(PlayerId pid) const -> std::shared_ptr<ShardokAIClient>;
static vector<shared_ptr<ShardokAIClient>> MakeAIClients(const unique_ptr<ShardokEngine>& e);
static std::vector<std::shared_ptr<ShardokAIClient>> MakeAIClients(
const std::unique_ptr<ShardokEngine>& e);
void DoAIThread();
public:
ShardokGameController(
unique_ptr<ShardokEngine> e,
string mapName,
string serializedRequest = "",
vector<std::pair<int32_t, vector<PlayerId>>> watcherAllies = {})
std::unique_ptr<ShardokEngine> e,
std::string mapName,
std::string serializedRequest = "",
std::vector<std::pair<int32_t, std::vector<PlayerId>>> watcherAllies = {})
: serializedRequest(std::move(serializedRequest)),
engine(std::move(e)),
cachedGameId(engine->GetGameId()),
@@ -160,40 +162,40 @@ public:
~ShardokGameController();
auto SerializedRequest() const -> string { return serializedRequest; }
auto SerializedRequest() const -> std::string { return serializedRequest; }
void PostCommand(
PlayerId shardokPlayerId,
int eagleFactionId,
int64_t token,
int64_t commandIndex,
optional<int32_t> roll);
std::optional<int32_t> roll);
void PostPlacementCommands(
PlayerId shardokPlayerId,
int eagleFactionId,
int64_t token,
const vector<UnitPlacementInfo>& infos);
const std::vector<UnitPlacementInfo>& infos);
struct AllUpdates {
vector<ActionResult> mainResults;
vector<OnePlayerUpdates> filteredResults;
std::vector<ActionResult> mainResults;
std::vector<OnePlayerUpdates> filteredResults;
int32_t newUnfilteredCount;
byte_vector currentGameState;
int32_t currentRound = 0;
vector<std::pair<int32_t, int32_t>> playerTroopCounts; // (playerId, troopCount)
std::vector<std::pair<int32_t, int32_t>> playerTroopCounts; // (playerId, troopCount)
};
auto GetUpdates(int64_t startingActionId) -> AllUpdates;
auto GetCurrentGameStateBytes() -> byte_vector;
auto GameIsOver() -> bool;
auto GameOverStatus() -> net::eagle0::shardok::common::GameStatus;
auto ResolvedPlayerInfos() -> vector<net::eagle0::shardok::common::PlayerInfo>;
auto EndGameUnits() -> vector<net::eagle0::shardok::storage::ResolvedUnit>;
auto ResolvedPlayerInfos() -> std::vector<net::eagle0::shardok::common::PlayerInfo>;
auto EndGameUnits() -> std::vector<net::eagle0::shardok::storage::ResolvedUnit>;
[[nodiscard]] auto GetGameId() const -> GameId { return cachedGameId; }
[[nodiscard]] auto GetLogFilePath() const -> string { return logFilePath; }
[[nodiscard]] auto GetLogFilePath() const -> std::string { return logFilePath; }
[[nodiscard]] auto HasAIThreadError() const -> bool { return aiThreadFailed.load(); }
[[nodiscard]] auto GetAIThreadError() const -> std::string { return aiThreadErrorMessage; }
@@ -9,6 +9,7 @@
#include "AvailableCommandsFactory.hpp"
#include <algorithm>
#include <iterator>
#include <memory>
#include <ranges>
@@ -32,22 +33,22 @@ private:
if (pid == UNCONTROLLED_PLAYER_ID) return false;
const auto &player = std::find_if(
begin(*gameState->player_infos()),
end(*gameState->player_infos()),
std::begin(*gameState->player_infos()),
std::end(*gameState->player_infos()),
[pid](const PlayerInfoFb *pi) { return pi->player_id() == pid; });
return (player != end(*gameState->player_infos()) && player->cannot_become_outlaw());
return (player != std::end(*gameState->player_infos()) && player->cannot_become_outlaw());
}
static auto IsAttacker(const GameStateW &gameState, PlayerId pid) -> bool {
if (pid == UNCONTROLLED_PLAYER_ID) return false;
const auto &player = std::find_if(
begin(*gameState->player_infos()),
end(*gameState->player_infos()),
std::begin(*gameState->player_infos()),
std::end(*gameState->player_infos()),
[pid](const PlayerInfoFb *pi) { return pi->player_id() == pid; });
return (player == end(*gameState->player_infos()) || !player->is_defender());
return (player == std::end(*gameState->player_infos()) || !player->is_defender());
}
const std::vector<std::shared_ptr<const CommandFactory>> commandFactories;
@@ -116,6 +117,7 @@ void AvailableCommandsFactoryImpl::AddAvailableCommandsForOneUnit(
const bool isEligibleCharger = unit->unit_id() == gameState->eligible_charger_id();
std::vector<UnitId> possibleChargees{};
possibleChargees.reserve(gameState->possible_chargee_ids()->size());
for (const UnitId cid : *gameState->possible_chargee_ids()) {
if (cid != -1) { possibleChargees.push_back(cid); }
}
@@ -194,8 +196,6 @@ auto AvailableCommandsFactoryImpl::GetAvailableCommands(
const std::optional<std::vector<Coords>> &reinforcementPositions) const -> CommandListSPtr {
CommandList commands{};
std::vector<AdjacentTile> adjacentTiles;
playerSetupCommandFactory
.AddAvailablePlayerSetupCommands(commands, playerId, gameState, reinforcementPositions);
if (!commands.empty()) return std::make_shared<CommandList>(commands);
@@ -238,6 +238,7 @@ auto AvailableCommandsFactoryImpl::GetAvailableCommands(
/* onlyFollowUps=*/true);
std::unordered_set<CommandType> followUpTypes{};
followUpTypes.reserve(followUpCommands.size());
for (const auto &followUpCmd : followUpCommands) {
if (followUpCmd->CanBeFollowUp()) {
followUpTypes.insert(followUpCmd->GetCommandType());
@@ -261,13 +261,8 @@ struct BattalionType {
trainingDifferenceMultiplierForStunChanceOnCharge * trainingDifference;
OtherFactor trainingFactor =
MakeOtherFactor(static_cast<int16_t>(trainingValue), "training difference");
return MakeOdds(
static_cast<int16_t>(baseStunChanceOnCharge),
0,
0,
0,
{},
{trainingFactor});
const std::array otherFactors = {trainingFactor};
return MakeOdds(static_cast<int16_t>(baseStunChanceOnCharge), 0, 0, 0, {}, otherFactors);
}
[[nodiscard]] auto GetDailyFoodCostPerTroop() const -> double {
@@ -9,6 +9,7 @@
#include "FireUtils.hpp"
#include <algorithm>
#include <array>
#include "src/main/cpp/net/eagle0/shardok/library/map/TileModifier.hpp"
@@ -103,14 +104,14 @@ auto GetExtinguishOdds(
const int16_t windFactor = -weather->wind().speed_in_mph();
std::vector<OtherFactor> others;
const std::vector<double> stats = {agility, agility, intelligence};
if (ownTile) {
others.push_back(MakeOtherFactor(settings.Backing().extinguish_own_tile_buf(), "ownTile"));
const std::array others = {
MakeOtherFactor(settings.Backing().extinguish_own_tile_buf(), "ownTile")};
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, others);
}
const std::vector<double> stats = {agility, agility, intelligence};
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, others);
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats);
}
auto GetStartFireOdds(
@@ -128,12 +129,13 @@ auto GetStartFireOdds(
const int16_t windFactor = -currentWeather.wind().speed_in_mph();
std::vector<OtherFactor> otherFactors;
if (isMage) { otherFactors.push_back(MakeOtherFactor(+20, "mage")); }
const std::vector<double> stats = {agility, intelligence, intelligence};
if (isMage) {
const std::array otherFactors = {MakeOtherFactor(+20, "mage")};
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
}
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats);
}
} // namespace shardok
@@ -13,7 +13,7 @@
#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
namespace shardok {
double BonusFromStat(const double stat) { return pow(((double)stat - 50.0), 3.0) / 5000.0; }
double BonusFromStat(const double stat) { return pow(stat - 50.0, 3.0) / 5000.0; }
int16_t AverageBonusFromStats(const std::vector<double> &stats) {
double totalBonus = 0.0;
@@ -23,7 +23,7 @@ int16_t AverageBonusFromStats(const std::vector<double> &stats) {
}
int16_t GetSuccessChance(const PercentileRollOdds &odds) {
return int16_t(std::floor(
return static_cast<int16_t>(std::floor(
100.0 * RandomGenerator::ChanceOpenEndedPercentileAtOrAbove(100 - GetTotalOdds(odds)) +
0.5));
}
@@ -42,7 +42,7 @@ PercentileRollOdds MakeOdds(
const int weath,
const int wind,
const int statF,
const std::vector<OtherFactor> &others) {
const std::span<const OtherFactor> others) {
PercentileRollOdds odds;
odds.set_base_difficulty(base);
@@ -61,7 +61,7 @@ PercentileRollOdds MakeOdds(
const int weath,
const int wind,
const std::vector<double> &stats,
const std::vector<OtherFactor> &others) {
const std::span<const OtherFactor> others) {
return MakeOdds(base, terr, weath, wind, AverageBonusFromStats(stats), others);
}
@@ -9,6 +9,7 @@
#ifndef EAGLE0_SHARDOK_LIBRARY_PERCENTILE_ROLL_ODDS_HPP
#define EAGLE0_SHARDOK_LIBRARY_PERCENTILE_ROLL_ODDS_HPP
#include <span>
#include <string>
#include <vector>
@@ -33,7 +34,7 @@ PercentileRollOdds MakeOdds(
int weath = 0,
int wind = 0,
int statF = 0,
const std::vector<OtherFactor> &others = std::vector<OtherFactor>());
std::span<const OtherFactor> others = {});
PercentileRollOdds MakeOdds(
int base,
@@ -41,7 +42,7 @@ PercentileRollOdds MakeOdds(
int weath,
int wind,
const std::vector<double> &stats,
const std::vector<OtherFactor> &others = std::vector<OtherFactor>());
std::span<const OtherFactor> others = {});
int16_t GetTotalOdds(const PercentileRollOdds &odds);
@@ -77,11 +77,11 @@ ShardokEngine::ShardokEngine(
ShardokEngine::ShardokEngine(
const GameSettingsSPtr &settings,
vector<Unit> &&unplacedUnorderedUnits,
std::vector<Unit> &&unplacedUnorderedUnits,
const HexMapProto &map,
const int month,
const GameId &existingGameId,
const vector<PlayerInfoProto> &playerInfos,
const std::vector<PlayerInfoProto> &playerInfos,
const bool trackHistory)
: gameSettings(settings),
settingsGetter(settings->GetGetter()),
@@ -106,7 +106,7 @@ ShardokEngine::ShardokEngine(
ApplyAndAddActionResult(setupStartAction);
}
void ShardokEngine::ApplyAndAddActionResults(const vector<ActionResultProto> &results) {
void ShardokEngine::ApplyAndAddActionResults(const std::vector<ActionResultProto> &results) {
for (const ActionResultProto &result : results) { ApplyAndAddActionResult(result); }
}
@@ -122,7 +122,7 @@ void ShardokEngine::ApplyAndAddActionResult(const ActionResultProto &result) {
ShardokEngine::ShardokEngine(
const GameSettingsSPtr &settings,
const vector<ShardokActionWithResultingState> &history,
const std::vector<ShardokActionWithResultingState> &history,
const bool trackHistory)
: gameSettings(settings),
settingsGetter(settings->GetGetter()),
@@ -156,11 +156,11 @@ auto ShardokEngine::GetGameStateAtStartOfAction(const ActionId startingActionId)
}
auto ShardokEngine::GetGameHistory(const ActionId lastUpdatedActionId) const
-> vector<ShardokActionWithResultingState> {
-> std::vector<ShardokActionWithResultingState> {
if (lastUpdatedActionId >= static_cast<int64_t>(actionHistory.size()) + startingHistoryCount) {
return vector<ShardokActionWithResultingState>();
return std::vector<ShardokActionWithResultingState>();
}
return vector<ShardokActionWithResultingState>(
return std::vector<ShardokActionWithResultingState>(
std::begin(actionHistory) + lastUpdatedActionId - startingHistoryCount,
std::end(actionHistory));
}
@@ -172,14 +172,17 @@ auto ShardokEngine::GetGameStateView(const PlayerId askingPlayer) const
[[nodiscard]] auto ShardokEngine::FilterNewResults(
PlayerId askingPlayer,
int64_t previousActionCount) const -> vector<net::eagle0::shardok::api::ActionResultView> {
int64_t previousActionCount) const
-> std::vector<net::eagle0::shardok::api::ActionResultView> {
if (previousActionCount < startingHistoryCount) {
throw ShardokInternalErrorException("Unable to fetch history before startingHistoryCount");
}
vector<ShardokActionWithResultingState> unfilteredHistory = GetGameHistory(previousActionCount);
std::vector<ShardokActionWithResultingState> unfilteredHistory =
GetGameHistory(previousActionCount);
vector<net::eagle0::shardok::api::ActionResultView> filteredHistory{};
std::vector<net::eagle0::shardok::api::ActionResultView> filteredHistory{};
filteredHistory.reserve(unfilteredHistory.size());
GameStateW startingState = (previousActionCount == 0)
? GameStateW{}
@@ -193,9 +196,7 @@ auto ShardokEngine::GetGameStateView(const PlayerId askingPlayer) const
GameState *previousStatePtr = nullptr;
GameStateView &previousView = startingView;
for (vector<ShardokActionWithResultingState> newHistory =
{unfilteredHistory.begin(), unfilteredHistory.end()};
const ShardokActionWithResultingState &awrs : newHistory) {
for (const ShardokActionWithResultingState &awrs : unfilteredHistory) {
GameStateView viewAfter = GameStateFilteredForPlayer(
settingsGetter,
GameStateW::FromByteString(awrs.state_after_fb()),
@@ -220,16 +221,18 @@ auto ShardokEngine::GetGameStateView(const PlayerId askingPlayer) const
[[nodiscard]] auto ShardokEngine::FilterNewResultsForWatcher(
const PlayerId askingPlayer,
const vector<PlayerId> &alliedPids,
const std::vector<PlayerId> &alliedPids,
const int64_t previousActionCount) const
-> vector<net::eagle0::shardok::api::ActionResultView> {
-> std::vector<net::eagle0::shardok::api::ActionResultView> {
if (previousActionCount < startingHistoryCount) {
throw ShardokInternalErrorException("Unable to fetch history before startingHistoryCount");
}
vector<ShardokActionWithResultingState> unfilteredHistory = GetGameHistory(previousActionCount);
std::vector<ShardokActionWithResultingState> unfilteredHistory =
GetGameHistory(previousActionCount);
vector<net::eagle0::shardok::api::ActionResultView> filteredHistory{};
std::vector<net::eagle0::shardok::api::ActionResultView> filteredHistory{};
filteredHistory.reserve(unfilteredHistory.size());
GameStateW startingState = (previousActionCount == 0)
? GameStateW{}
@@ -271,7 +274,7 @@ auto ShardokEngine::GetGameStateView(const PlayerId askingPlayer) const
}
auto ShardokEngine::GetFilteredGameHistory(const PlayerId askingPlayer) const
-> vector<net::eagle0::shardok::api::ActionResultView> {
-> std::vector<net::eagle0::shardok::api::ActionResultView> {
return FilterNewResults(askingPlayer, 0);
}
@@ -423,7 +426,7 @@ void ShardokEngine::ResumeAfterReinforcementPlacement(
void ShardokEngine::PostPlacementCommands(
const PlayerId player,
const vector<UnitPlacementInfo> &placementInfos,
const std::vector<UnitPlacementInfo> &placementInfos,
const std::shared_ptr<RandomGenerator> &randomGenerator) {
if (GetCurrentPlayerId() != player) {
throw ShardokClientErrorException("Posting for a player who's not the current player ID!");
@@ -471,7 +474,8 @@ void ShardokEngine::PostPlacementCommands(
return cmd->GetCommandProto().actor().value() == pi.unitId &&
cmd->GetCommandProto().target() == pi.location;
});
for (vector<ActionResult> onePlacementResults = (*it)->Execute(gameState, randomGenerator);
for (std::vector<ActionResult> onePlacementResults =
(*it)->Execute(gameState, randomGenerator);
const ActionResultProto &oneResult : onePlacementResults) {
HandleActionResult(oneResult, randomGenerator);
}
@@ -564,7 +568,7 @@ void ShardokEngine::PostActionUnchecked(
const CommandSPtr &command,
const std::shared_ptr<RandomGenerator> &randomGenerator,
const std::optional<int32_t> roll) {
for (const vector<ActionResultProto> results =
for (const std::vector<ActionResultProto> results =
command->CanUseClientRoll()
? command->ExecuteWithRoll(gameState, randomGenerator, roll)
: command->Execute(gameState, randomGenerator);
@@ -614,7 +618,7 @@ void ShardokEngine::HandleActionResult(
auto ShardokEngine::HandleUnitFallingIntoWater(
const Terrain *terrain,
const Unit *unit,
std::shared_ptr<RandomGenerator> randomGenerator) const -> vector<ActionResultProto> {
std::shared_ptr<RandomGenerator> randomGenerator) const -> std::vector<ActionResultProto> {
// Do nothing if this unit can travel across water
if (const auto &battType = settingsGetter.GetBattalionType(unit->battalion().type());
battType->GetCostToEnterTerrain(terrain).type != ActionCost::impossible) {
@@ -629,8 +633,9 @@ auto ShardokEngine::HandleUnitFallingIntoWater(
return fallAction.Execute(gameState, std::move(randomGenerator));
}
auto CommandProtosFromActionList(const CommandList &actionList) -> vector<CommandProto> {
vector<CommandProto> protos;
auto CommandProtosFromActionList(const CommandList &actionList) -> std::vector<CommandProto> {
std::vector<CommandProto> protos;
protos.reserve(actionList.size());
std::transform(
std::begin(actionList),
@@ -641,7 +646,7 @@ auto CommandProtosFromActionList(const CommandList &actionList) -> vector<Comman
return protos;
}
auto ShardokEngine::GetPreviewCommands(const PlayerId playerId) const -> vector<CommandProto> {
auto ShardokEngine::GetPreviewCommands(const PlayerId playerId) const -> std::vector<CommandProto> {
if (GameIsOver(GetGameStatus())) {
// The game is over
return {};
@@ -670,7 +675,7 @@ auto ShardokEngine::GetAvailableCommandsForAIPlayer(const PlayerId playerId) con
}
auto ShardokEngine::GetAvailableCommandProtos(const PlayerId playerId, const bool includeFollowUps)
const -> vector<CommandProto> {
const -> std::vector<CommandProto> {
if (GameIsOver(GetGameStatus())) {
// The game is over
return {};
@@ -703,7 +708,8 @@ auto ShardokEngine::UncachedGetAvailableCommands(
using SecuredByPlayerByUnitId = std::unordered_map<UnitId, PlayerId>;
auto SecuredByPlayerByUnitIdFromHistory(
const vector<ShardokActionWithResultingState> &actionHistory) -> SecuredByPlayerByUnitId {
const std::vector<ShardokActionWithResultingState> &actionHistory)
-> SecuredByPlayerByUnitId {
SecuredByPlayerByUnitId securedByPlayerByUnitId;
for (const auto &actionWithResultingState : actionHistory) {
for (const auto &resolvedUnit : actionWithResultingState.action_result().resolved_units()) {
@@ -720,7 +726,7 @@ auto SecuredByPlayerByUnitIdFromHistory(
}
void AddUnits(
vector<net::eagle0::shardok::storage::ResolvedUnit> &to,
std::vector<net::eagle0::shardok::storage::ResolvedUnit> &to,
const Units &from,
const SecuredByPlayerByUnitId &securedByPlayerByUnitId) {
for (const auto &unit : from) {
@@ -769,11 +775,12 @@ void AddUnits(
}
AddUnitToResolved(ru, *unit);
to.push_back(ru);
to.push_back(std::move(ru));
}
}
auto ShardokEngine::EndGameUnits() const -> vector<net::eagle0::shardok::storage::ResolvedUnit> {
auto ShardokEngine::EndGameUnits() const
-> std::vector<net::eagle0::shardok::storage::ResolvedUnit> {
if (const auto state = GetGameStatus()->state();
state != net::eagle0::shardok::storage::fb::GameStatus_::State_VICTORY) {
throw ShardokInternalErrorException(
@@ -782,7 +789,8 @@ auto ShardokEngine::EndGameUnits() const -> vector<net::eagle0::shardok::storage
const auto &gs = GetCurrentGameState();
vector<net::eagle0::shardok::storage::ResolvedUnit> endgameUnits;
std::vector<net::eagle0::shardok::storage::ResolvedUnit> endgameUnits;
endgameUnits.reserve(gs->units()->size());
AddUnits(endgameUnits, *gs->units(), SecuredByPlayerByUnitIdFromHistory(actionHistory));
return endgameUnits;
@@ -11,7 +11,9 @@
#include <flatbuffers/flatbuffers.h>
#include <memory>
#include <optional>
#include <vector>
#include "ShardokAction.hpp"
#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
@@ -27,12 +29,6 @@
#include "src/main/protobuf/net/eagle0/shardok/storage/action_with_resulting_state.pb.h"
namespace shardok {
using std::begin;
using std::end;
using std::optional;
using std::shared_ptr;
using std::vector;
using net::eagle0::shardok::api::UnitView;
using PlayerInfoProto = net::eagle0::shardok::common::PlayerInfo;
using net::eagle0::shardok::storage::ShardokActionWithResultingState;
@@ -43,14 +39,14 @@ class ShardokEngine {
private:
const GameSettingsSPtr gameSettings;
const SettingsGetter settingsGetter;
const shared_ptr<AvailableCommandsFactory> availableCommandsFactory;
const std::shared_ptr<AvailableCommandsFactory> availableCommandsFactory;
GameStateW gameState;
const bool trackHistory;
const int32_t startingHistoryCount = 0;
const GameStateW startingHistoryState;
mutable vector<ShardokActionWithResultingState> actionHistory;
mutable std::vector<ShardokActionWithResultingState> actionHistory;
const CoordsSet criticalTileCoords;
@@ -61,12 +57,12 @@ private:
void ApplyAndAddActionResult(const ActionResult &result);
void ApplyAndAddActionResults(const vector<ActionResult> &results);
void ApplyAndAddActionResults(const std::vector<ActionResult> &results);
[[nodiscard]] auto HandleUnitFallingIntoWater(
const Terrain *terrain,
const fb::Unit *unit,
std::shared_ptr<RandomGenerator> randomGenerator) const -> vector<ActionResult>;
std::shared_ptr<RandomGenerator> randomGenerator) const -> std::vector<ActionResult>;
void HandleActionResult(
const ActionResult &actionResult,
@@ -105,17 +101,17 @@ public:
// Constructor for a brand new game
ShardokEngine(
const GameSettingsSPtr &settings,
vector<Unit> &&unplacedUnorderedUnits,
std::vector<Unit> &&unplacedUnorderedUnits,
const HexMapProto &map,
int month,
const GameId &existingGameId,
const vector<PlayerInfoProto> &playerInfos,
const std::vector<PlayerInfoProto> &playerInfos,
bool trackHistory = true);
// Constructor for a game with history
ShardokEngine(
const GameSettingsSPtr &settings,
const vector<ShardokActionWithResultingState> &history,
const std::vector<ShardokActionWithResultingState> &history,
bool trackHistory = true);
// Copy constructor
@@ -134,12 +130,12 @@ public:
}
[[nodiscard]] auto EndGameUnits() const
-> vector<net::eagle0::shardok::storage::ResolvedUnit>; // Throws if the game is not
// over
-> std::vector<net::eagle0::shardok::storage::ResolvedUnit>; // Throws if the game is
// not over
// Controller API
[[nodiscard]] auto GetGameHistory(ActionId lastUpdatedActionId) const
-> vector<ShardokActionWithResultingState>;
-> std::vector<ShardokActionWithResultingState>;
[[nodiscard]] auto GetUnfilteredHistoryCount() const -> size_t {
return actionHistory.size() + startingHistoryCount;
@@ -150,7 +146,7 @@ public:
-> net::eagle0::shardok::api::GameStateView;
[[nodiscard]] auto FilterNewResults(PlayerId askingPlayer, int64_t previousActionCount) const
-> vector<net::eagle0::shardok::api::ActionResultView>;
-> std::vector<net::eagle0::shardok::api::ActionResultView>;
// Variant for non-participant watchers: askingPlayer is a sentinel (not in
// player_infos) and alliedPids names the Shardok pids whose units the
@@ -158,18 +154,18 @@ public:
// and hidden actions.
[[nodiscard]] auto FilterNewResultsForWatcher(
PlayerId askingPlayer,
const vector<PlayerId> &alliedPids,
const std::vector<PlayerId> &alliedPids,
int64_t previousActionCount) const
-> vector<net::eagle0::shardok::api::ActionResultView>;
-> std::vector<net::eagle0::shardok::api::ActionResultView>;
[[nodiscard]] auto GetFilteredGameHistory(PlayerId askingPlayer) const
-> vector<net::eagle0::shardok::api::ActionResultView>;
-> std::vector<net::eagle0::shardok::api::ActionResultView>;
[[nodiscard]] auto GetUnitById(PlayerId askingPlayer, UnitId unitId) const -> UnitView;
void PostPlacementCommands(
PlayerId player,
const vector<UnitPlacementInfo> &placementInfos,
const std::vector<UnitPlacementInfo> &placementInfos,
const std::shared_ptr<RandomGenerator> &randomGenerator = nullptr);
void PostFinishedPlacementCommand(
PlayerId player,
@@ -186,11 +182,11 @@ public:
PlayerId playerId,
std::shared_ptr<RandomGenerator> randomGenerator) -> int;
[[nodiscard]] auto GetPreviewCommands(PlayerId playerId) const -> vector<CommandProto>;
[[nodiscard]] auto GetPreviewCommands(PlayerId playerId) const -> std::vector<CommandProto>;
[[nodiscard]] auto GetAvailableCommandsForAIPlayer(PlayerId playerId) const -> CommandListSPtr;
[[nodiscard]] auto GetAvailableCommandProtos(PlayerId playerId, bool includeFollowUps) const
-> vector<CommandProto>;
-> std::vector<CommandProto>;
[[nodiscard]] auto GetGameId() const -> GameId {
return GetCurrentGameState()->game_id()->str();
@@ -198,9 +194,9 @@ public:
[[nodiscard]] auto GetMonth() const -> int { return GetCurrentGameState()->month(); }
[[nodiscard]] auto GetPlayerInfos() const -> vector<PlayerInfoProto> {
[[nodiscard]] auto GetPlayerInfos() const -> std::vector<PlayerInfoProto> {
const auto &currentGameState = GetCurrentGameState();
vector<PlayerInfoProto> protos{};
std::vector<PlayerInfoProto> protos{};
for (const auto *const piFB : *currentGameState->player_infos()) {
protos.push_back(fb::ToPlayerInfoProto(piFB));
}
@@ -79,6 +79,8 @@ auto PlayerSetupCommandFactory::AddAvailablePlayerSetupCommands(
const auto unplacedUnits = ReserveUnitsForPlayer(gameState->units(), playerId);
if (unplacedUnits.empty()) return;
existingCommands.reserve(
existingCommands.size() + (unplacedUnits.size() * reinforcementPositions->size()));
for (const auto &[unitId, unit] : unplacedUnits) {
for (const Coords &position : *reinforcementPositions) {
if (!gameState.GetOccupant(position)) {
@@ -29,6 +29,7 @@ auto ActionPointDistances::BraveWaterPossibleCoords(const HexMap* hexMap) const
std::shared_ptr<BraveableTileInfo> info = std::make_shared<BraveableTileInfo>(hexMap);
CoordsSet& braveWaterPossibleCoords = info->cs;
const int indexCount = hexMap->row_count() * hexMap->column_count();
info->details.reserve(indexCount);
const auto indexToCoords = CreateIndexToCoords(hexMap);
for (int i = 0; i < indexCount; i++) {
if (const Terrain* terrain = hexMap->terrain()->Get(i);
@@ -239,7 +240,6 @@ OnDemandActionPointDistances::OnDemandActionPointDistances(
battalionType(std::move(battTp)),
distances(static_cast<size_t>(map->row_count() * map->column_count())) {
const int indexCount = map->row_count() * map->column_count();
distances.resize(indexCount);
auto braveWaterPossibleCoords = includeBravingWater ? BraveWaterPossibleCoords(map) : nullptr;
@@ -12,7 +12,7 @@
// Dynamic thread count based on hardware capabilities
static const int ASYNC_COUNT = []() {
const int cores = static_cast<int>(std::thread::hardware_concurrency());
// Use cores-2 to leave room for OS and other processes, minimum 4 threads
// Use cores-4 to leave room for OS and other processes, minimum 4 threads
const int threadCount = std::max(4, cores - 4);
std::cerr << "ActionPointDistances using " << threadCount << " threads (detected " << cores
<< " cores)\n";
@@ -35,7 +35,7 @@ auto FixedActionPointDistances::Create(
const int indexCount = map->row_count() * map->column_count();
vector<std::future<vector<vector<DIST_T>>>> futures(indexCount);
vector<std::future<vector<vector<DIST_T>>>> futures(ASYNC_COUNT);
auto braveWaterPossibleCoords =
includeBravingWater ? apd->BraveWaterPossibleCoords(map) : nullptr;
@@ -302,16 +302,17 @@ void MutatingApplyResult(
const Unit *changedUnit = &UnitFromBytes(changedUnitBytes);
internalAssert(
changedUnit->unit_id() < (int32_t)mutatingGameState->units()->size(),
changedUnit->unit_id() < static_cast<int32_t>(mutatingGameState->units()->size()),
"Got a changed unit outside acceptable range");
const auto battalionSizeAfter = changedUnit->battalion().size();
const int battalionSizeBefore = (changedUnit->unit_id() < (int32_t)initialUnitCount)
? mutatingGameState->units()
->Get(changedUnit->unit_id())
->battalion()
.size()
: changedUnit->battalion().size();
const int battalionSizeBefore =
(changedUnit->unit_id() < static_cast<int32_t>(initialUnitCount))
? mutatingGameState->units()
->Get(changedUnit->unit_id())
->battalion()
.size()
: changedUnit->battalion().size();
auto status = changedUnit->status();
if (IsDestroyed(*changedUnit)) {
@@ -5,6 +5,7 @@
#include "src/main/cpp/net/eagle0/shardok/library/action_result_applier/GameStateCopier.hpp"
#include <cstring>
#include <utility>
namespace shardok {
@@ -31,7 +32,7 @@ auto CopyWithExtraUnits(const GameStateW& original, int additionalCount) -> Game
unit.mutable_location().mutate_row(-1);
unit.mutable_location().mutate_column(-1);
}
endGST.units.push_back(unit);
endGST.units.push_back(std::move(unit));
}
// Copy occupied tiles bitfield from original GameState (much faster than O(n) rebuild)
@@ -4,6 +4,8 @@
#include "src/main/cpp/net/eagle0/shardok/library/action_result_applier/UnitHelpers.hpp"
#include <cstring>
namespace shardok::fb {
using namespace net::eagle0::shardok::storage::fb;
@@ -27,7 +29,7 @@ auto ApplyUnit(
}
}
memcpy(toUnit, fromUnit, sizeof(Unit));
std::memcpy(toUnit, fromUnit, sizeof(Unit));
toUnit->mutate_status(status);
if (toUnit->has_attached_hero()) {
@@ -4,6 +4,8 @@
#include "EndPlayerSetupCommand.hpp"
#include <utility>
#include "src/main/cpp/net/eagle0/shardok/library/action_result_applier/ActionResultApplier.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/actions/NewRoundAction.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/actions/StartPlayerTurnAction.hpp"
@@ -33,9 +35,9 @@ auto EndPlayerSetupCommand::InternalExecute(
gameStartResult.mutable_game_status()->set_state(
GameStatus::State::GameStatus_State_GAME_RUNNING);
gameStartResult.mutable_game_status()->set_description("The game has begun!");
allResults.push_back(gameStartResult);
startingGameState = ApplyResult(startingGameState, gameStartResult, settings);
allResults.push_back(std::move(gameStartResult));
auto newRoundResults =
NewRoundAction(startingGameState, settings).Execute(currentState, generator);
@@ -61,4 +63,4 @@ auto EndPlayerSetupCommand::GetCommandProto() const -> shardok::CommandProto {
return proto;
}
} // namespace shardok
} // namespace shardok
@@ -8,6 +8,7 @@
#include "FallIntoWaterAction.hpp"
#include <array>
#include <utility>
#include "src/main/cpp/net/eagle0/shardok/library/map/Coordinates.hpp"
@@ -55,16 +56,17 @@ auto EscapeChance(
const int16_t windFactor = -currentWeather.wind().speed_in_mph() * 2;
const std::vector<double> stats = {intelligence, agility};
std::vector<OtherFactor> otherFactors;
if (terrain.modifier().fire().present()) {
otherFactors.push_back(MakeOtherFactor(-50, "fire"));
const std::array otherFactors = {MakeOtherFactor(-50, "fire")};
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
}
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats);
}
auto ToVector(const Units &units) -> vector<const Unit *> {
auto v = vector<const Unit *>{};
v.reserve(units.size());
for (const Unit *u : units) { v.push_back(u); }
return v;
}
@@ -76,6 +78,7 @@ auto FallIntoWaterAction::AdjacentsWithTerrain(
const CoordsSet adjacentCoords = HexMapUtils::GetAdjacentCoords(gameState->hex_map(), location);
vector<AdjacentWithTerrain> vec{};
vec.reserve(adjacentCoords.size());
for (const Coords &adjCoords : adjacentCoords) {
const auto *possibleOccupant = Occupant(gameState->units(), adjCoords);
@@ -103,6 +106,7 @@ auto FallIntoWaterAction::InternalExecute(
const GameStateW &currentState,
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResult> {
vector<ActionResult> results{};
results.reserve(2);
auto fallerTerrain =
*GetTerrain(currentState->hex_map(), currentState->units()->Get(fallerId)->location());
@@ -56,7 +56,7 @@ auto IceAdjustmentAction::InternalExecute(
*resultProto.mutable_target_coords() = ToCoordsProto(coords);
*resultProto.add_changed_tile_modifiers() = MakeTmc(coords, newTerrain.modifier());
results.push_back(resultProto);
results.push_back(std::move(resultProto));
if (terrain.modifier().ice().present() && !IsFrozen(newTerrain.modifier()) &&
fallAction != nullptr) {
@@ -71,4 +71,4 @@ auto IceAdjustmentAction::InternalExecute(
return results;
}
} // namespace shardok
} // namespace shardok
@@ -160,6 +160,7 @@ auto MeteorCastAction::InternalExecute(
const GameStateW &currentState,
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResultProto> {
vector<ActionResultProto> allResults{};
allResults.reserve(actorIds.size() * 16);
auto runningGameState = currentState;
for (const UnitId &actorId : actorIds) {
const Unit *actorBefore = currentState->units()->Get(actorId);
@@ -183,7 +184,7 @@ auto MeteorCastAction::PerformOneActorCast(
mainResult.mutable_actor()->set_value(actorBefore->unit_id());
*mainResult.mutable_target_coords() =
ToCoordsProto(actorBefore->attached_hero().profession_info().cast_target());
results.push_back(mainResult);
results.push_back(std::move(mainResult));
const Coords target = actorBefore->attached_hero().profession_info().cast_target();
if (const Unit *possibleOccupant = runningGameState.GetOccupant(target)) {
@@ -151,7 +151,7 @@ auto NewWeather(
return MakeWeather(
newConditions,
MakeWind(
(net::eagle0::shardok::common::HexMapDirection)newWindDirection,
static_cast<net::eagle0::shardok::common::HexMapDirection>(newWindDirection),
newWindSpeed));
}
@@ -159,6 +159,7 @@ auto NewRoundAction::InternalExecute(
const GameStateW &currentState,
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResultProto> {
vector<ActionResultProto> results{};
results.reserve(4);
GameStateW runningGameState = startingGameState;
@@ -177,6 +178,7 @@ auto NewRoundAction::InternalExecute(
ActionList fireSpreadAction = fireSpreadActionFactory->MakeFireSpreadActions(
runningGameState->hex_map(),
runningGameState->weather());
results.reserve(results.size() + fireSpreadAction.size() + 1);
for (const ActionSPtr &action : fireSpreadAction) {
auto fireSpreadResults = action->Execute(currentState, generator);
for (const auto &result : fireSpreadResults) {
@@ -191,6 +193,7 @@ auto NewRoundAction::InternalExecute(
*runningGameState->units(),
runningGameState->month(),
runningGameState->weather());
results.reserve(results.size() + iceAdjustmentAction.size());
for (const ActionSPtr &action : iceAdjustmentAction) {
auto iceAdjustmentResults = action->Execute(currentState, generator);
for (const auto &result : iceAdjustmentResults) {
@@ -220,6 +223,7 @@ auto NewRoundAction::InternalExecute(
ActionList fireOutActions = fireOutActionFactory->MakeFireOutActions(
runningGameState->hex_map(),
runningGameState->weather());
results.reserve(results.size() + fireOutActions.size());
for (const ActionSPtr &action : fireOutActions) {
auto fireOutResults = action->Execute(currentState, generator);
for (const auto &result : fireOutResults) {
@@ -25,6 +25,7 @@ auto PerformUndeadCommandsAction::InternalExecute(
const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResultProto> {
GameStateW runningGameState = startingGameState;
vector<ActionResultProto> allResults{};
allResults.reserve(runningGameState->units()->size());
while (runningGameState->current_player() == UNCONTROLLED_PLAYER_ID) {
auto commands = availableCommandsFactory->GetAvailableCommands(
@@ -56,6 +57,7 @@ auto ChooseUndeadCommand(
const HexMap *hexMap,
const std::shared_ptr<RandomGenerator> &randomGenerator) -> CommandSPtr {
CommandList attackCommands;
attackCommands.reserve(commands->size());
for (const auto &command : *commands) {
const CommandProto &possibleAttackCommandProto = command->GetCommandProto();
@@ -76,6 +78,7 @@ auto ChooseUndeadCommand(
}
CommandList moveCommands;
moveCommands.reserve(commands->size());
CommandSPtr bestMoveCommand = nullptr;
for (const auto &command : *commands) {
@@ -4,6 +4,8 @@
#include "SnowAdjustmentAction.hpp"
#include <utility>
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/TileModifierHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/TileModifier.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/TileModifierWithCoords.hpp"
@@ -102,8 +104,8 @@ auto SnowAdjustmentAction::InternalExecute(
*resultProto.mutable_target_coords() = ToCoordsProto(coords);
*resultProto.add_changed_tile_modifiers() = MakeTmc(coords, newTerrain.modifier());
results.push_back(resultProto);
results.push_back(std::move(resultProto));
return results;
}
} // namespace shardok
} // namespace shardok
@@ -49,7 +49,9 @@ auto UpdateGameStatusAction::SurvivingPlayers() const -> std::unordered_set<Play
for (const Unit* unit : *gameState->units()) {
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
if ((uint32_t)unit->player_id() >= gameState->player_infos()->size()) continue;
if (unit->player_id() < 0 ||
static_cast<uint32_t>(unit->player_id()) >= gameState->player_infos()->size())
continue;
if (!unit->hidden() && (unit->battalion().size() > 0 ||
(unit->has_attached_hero() && unit->attached_hero().vigor() > 0))) {
@@ -4,6 +4,8 @@
#include "BlowBridgeCommandFactory.hpp"
#include <array>
#include "src/main/cpp/net/eagle0/shardok/library/commands/BlowBridgeCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
@@ -92,13 +94,13 @@ auto GetBlowBridgeOdds(
constexpr int16_t weatherFactor = 0;
constexpr int16_t windFactor = 0;
const std::vector stats = {strength, agility, intelligence};
std::vector<OtherFactor> otherFactors;
if (hasForestAccess) {
otherFactors.push_back(
MakeOtherFactor(settings.Backing().blow_bridge_forest_bonus(), "forest nearby"));
const std::array otherFactors = {
MakeOtherFactor(settings.Backing().blow_bridge_forest_bonus(), "forest nearby")};
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
}
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats);
}
} // namespace shardok
@@ -4,6 +4,8 @@
#include "BraveWaterCommandFactory.hpp"
#include <array>
#include "src/main/cpp/net/eagle0/shardok/library/commands/BraveWaterCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
@@ -39,13 +41,20 @@ PercentileRollOdds GetBraveWaterOdds(
wisdom * settings.Backing().brave_water_wisdom_factor());
const double armamentFactor = armament * settings.Backing().brave_water_armament_factor();
auto otherFactors = vector<OtherFactor>{MakeOtherFactor(armamentFactor, "Armament")};
if (isRanger) {
otherFactors.emplace_back(
MakeOtherFactor(settings.Backing().brave_water_ranger_bonus(), "Ranger"));
const std::array otherFactors = {
MakeOtherFactor(armamentFactor, "Armament"),
MakeOtherFactor(settings.Backing().brave_water_ranger_bonus(), "Ranger")};
return MakeOdds(
/*base*/ baseOdds,
/*terrain*/ 0,
weatherFactor,
/*wind*/ 0,
statFactor,
otherFactors);
}
const std::array otherFactors = {MakeOtherFactor(armamentFactor, "Armament")};
return MakeOdds(
/*base*/ baseOdds,
/*terrain*/ 0,
@@ -4,6 +4,8 @@
#include "BuildBridgeCommandFactory.hpp"
#include <array>
#include "src/main/cpp/net/eagle0/shardok/library/commands/BuildBridgeCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
@@ -81,13 +83,13 @@ auto GetBuildBridgeOdds(
constexpr int16_t weatherFactor = 0;
constexpr int16_t windFactor = 0;
const std::vector stats = {strength, agility, intelligence};
std::vector<OtherFactor> otherFactors;
if (hasForestAccess) {
otherFactors.push_back(
MakeOtherFactor(settings.Backing().build_bridge_forest_bonus(), "forest nearby"));
const std::array otherFactors = {
MakeOtherFactor(settings.Backing().build_bridge_forest_bonus(), "forest nearby")};
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
}
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats, otherFactors);
return MakeOdds(base, terrainFactor, weatherFactor, windFactor, stats);
}
} // namespace shardok
@@ -33,6 +33,7 @@ namespace {
const PlayerId actorPlayerId,
const vector<PlayerId>& allyPids) -> std::vector<UnitId> {
std::vector<UnitId> captiveUnitIds;
captiveUnitIds.reserve(units->size());
for (const auto* unit : *units) {
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_CAPTURED_UNIT &&
unit->has_attached_hero() && IsHostileToActor(unit, actorPlayerId, allyPids)) {
@@ -9,6 +9,7 @@
#include "FleeCommandFactory.hpp"
#include <algorithm>
#include <array>
#include <ranges>
#include "src/main/cpp/net/eagle0/shardok/library/commands/BecomeOutlawCommand.hpp"
@@ -67,15 +68,11 @@ void FleeCommandFactory::AddAvailableFleeCommands(
}
}
odds = MakeOdds(
settings.Backing().flee_success_base_chance(),
0,
0,
0,
0,
{MakeOtherFactor(adjacentFriendliesMod, "adjacentFriendlies"),
MakeOtherFactor(adjacentEnemiesMod, "adjacentEnemies"),
MakeOtherFactor(adjacentImpassableMod, "adjacentImpassable")});
const std::array otherFactors = {
MakeOtherFactor(adjacentFriendliesMod, "adjacentFriendlies"),
MakeOtherFactor(adjacentEnemiesMod, "adjacentEnemies"),
MakeOtherFactor(adjacentImpassableMod, "adjacentImpassable")};
odds = MakeOdds(settings.Backing().flee_success_base_chance(), 0, 0, 0, 0, otherFactors);
}
if (unit->can_flee()) {
@@ -209,6 +209,7 @@ auto ConstructMoveDestinations(
const auto occupants = Occupants(*units, hexMap->row_count(), hexMap->column_count());
std::vector<AccumulatedMoveInfo> allAmis{};
allAmis.reserve(hexMap->row_count() * hexMap->column_count());
CoordsSet checkedDestinations(hexMap);
checkedDestinations.Add(startingLocation);
const AccumulatedMoveInfo baseInfo(startingLocation, startingLocation, 0, false, {});
@@ -4,6 +4,7 @@
#include "BraveWaterCommand.hpp"
#include <optional>
#include <utility>
#include "src/main/cpp/net/eagle0/shardok/library/actions/DefensiveAmbushAction.hpp"
@@ -17,7 +18,7 @@ using net::eagle0::shardok::common::ActionType;
vector<ActionResult> BraveWaterCommand::InternalExecuteWithRoll(
const GameStateW &currentState,
const std::shared_ptr<RandomGenerator> &generator,
const optional<int32_t> roll) const {
const std::optional<int32_t> roll) const {
// Succeeds on high roll
const auto successRoll = 101 - roll.value_or(generator->Percentile());
const auto lossesRoll = generator->Percentile();
@@ -54,13 +55,14 @@ vector<ActionResult> BraveWaterCommand::InternalExecuteWithRoll(
crossingResult.mutable_actor()->set_value(actorAfterSpend.unit_id());
vector<ActionResult> allResults{};
allResults.reserve(ambusherId.has_value() ? 2 : 1);
if (PercentileRollSucceeds(successOdds, successRoll)) {
if (ambusherId.has_value()) {
// Tile is occupied -- ambush!
crossingResult.set_type(ActionType::CROSSED_WATER);
AddChangedUnit(crossingResult, actorAfterSpend);
allResults.push_back(crossingResult);
allResults.push_back(std::move(crossingResult));
auto ambushResults =
DefensiveAmbushAction(ambusherId.value(), actorId, ambusherRoll, settings)
@@ -73,13 +75,13 @@ vector<ActionResult> BraveWaterCommand::InternalExecuteWithRoll(
crossingResult.set_type(ActionType::CROSSED_WATER);
actorAfterSpend.mutable_location() = target;
AddChangedUnit(crossingResult, actorAfterSpend);
allResults.push_back(crossingResult);
allResults.push_back(std::move(crossingResult));
}
} else {
crossingResult.set_type(ActionType::CROSS_WATER_FAILED);
AddChangedUnit(crossingResult, actorAfterSpend);
// location doesn't change
allResults.push_back(crossingResult);
allResults.push_back(std::move(crossingResult));
}
return allResults;
@@ -5,6 +5,8 @@
#ifndef EAGLE0_SHARDOK_LIBRARY_COMMANDS_BRAVE_WATER_COMMAND_HPP
#define EAGLE0_SHARDOK_LIBRARY_COMMANDS_BRAVE_WATER_COMMAND_HPP
#include <optional>
#include "src/main/cpp/net/eagle0/shardok/library/ActionCost.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/Coordinates.hpp"
@@ -16,7 +18,7 @@ protected:
[[nodiscard]] auto InternalExecuteWithRoll(
const GameStateW &currentState,
const std::shared_ptr<RandomGenerator> &generator,
optional<int32_t> roll) const -> vector<ActionResult> override;
std::optional<int32_t> roll) const -> vector<ActionResult> override;
const ActionCost cost;
const UnitId actorId;
@@ -48,8 +48,10 @@ auto EndTurnCommand::InternalExecute(
GameStateW runningGameState = currentState;
vector<ActionResultProto> allResults{};
allResults.reserve(runningGameState->units()->size() + 2);
vector<UnitId> castingUnitIds{};
castingUnitIds.reserve(runningGameState->units()->size());
for (const auto &unit : *runningGameState->units()) {
if (unit->player_id() != GetPlayerId()) continue;
@@ -4,6 +4,9 @@
#include "FearCommand.hpp"
#include <array>
#include <string>
#include "src/main/cpp/net/eagle0/shardok/library/actions/ActionRequiresHeroException.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/unit/Unit.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/ActionResultFlatbufferHelpers.hpp"
@@ -13,7 +16,7 @@ namespace shardok {
using net::eagle0::shardok::common::ActionType;
static const string opponentCharismaString = "opponentCharisma";
static const std::string opponentCharismaString = "opponentCharisma";
auto FearCommand::InternalExecute(
const GameStateW& currentState,
@@ -81,13 +84,15 @@ auto FearCommand::GetFearOdds(
double attackerCharisma,
double attackerWisdom,
const double defenderCharisma) -> PercentileRollOdds {
const std::array otherFactors = {
MakeOtherFactor(int(50.0 - defenderCharisma), opponentCharismaString)};
return MakeOdds(
fearBaseOdds,
0,
0,
0,
{attackerCharisma, attackerCharisma, attackerWisdom},
{MakeOtherFactor(int(50.0 - defenderCharisma), opponentCharismaString)});
otherFactors);
}
int32_t FearCommand::GetOddsPercentile() const {
@@ -4,6 +4,8 @@
#include "FreezeWaterCommand.hpp"
#include <utility>
#include "src/main/cpp/net/eagle0/shardok/library/actions/ActionRequiresHeroException.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/actions/UndeadFrozenAction.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/TileModifierHelpers.hpp"
@@ -45,6 +47,7 @@ auto FreezeWaterCommand::InternalExecute(
*freezeResult.mutable_odds() = odds;
vector<ActionResult> allResults{};
allResults.reserve(occupantId != -1 ? 2 : 1);
if (PercentileRollSucceeds(odds, roll)) {
freezeResult.set_type(ActionType::FREEZE_WATER);
@@ -56,7 +59,7 @@ auto FreezeWaterCommand::InternalExecute(
freezeResult.set_type(ActionType::FREEZE_WATER_FAILED);
}
allResults.push_back(freezeResult);
allResults.push_back(std::move(freezeResult));
if (occupantId != -1 && PercentileRollSucceeds(odds, roll)) {
auto destroyedResults = UndeadFrozenAction(currentState->units()->Get(occupantId))
.Execute(currentState, generator);
@@ -56,6 +56,7 @@ auto HideCommand::InternalExecute(
auto alliedPids = AlliedPids(currentState, GetPlayerId());
vector<Unit> adjacentEnemyRangers{};
adjacentEnemyRangers.reserve(6);
for (const auto &adjCoords :
HexMapUtils::GetAdjacentCoords(currentState->hex_map(), target)) {
const auto *oneOverOccupant = currentState.GetOccupant(adjCoords);
@@ -77,6 +77,7 @@ auto HolyWaveDamageAction::MakeDamageActions(
const vector<UnitId> &damagedUnitIds,
double damagePercentage) -> ActionList {
ActionList actions;
actions.reserve(damagedUnitIds.size());
std::transform(
std::begin(damagedUnitIds),
std::end(damagedUnitIds),
@@ -169,6 +170,7 @@ auto HolyWaveInspireAction::MakeInspireActions(
const double maxInspireOverBase,
const double vigorBuf) -> ActionList {
ActionList actions;
actions.reserve(friendlyUnitIds.size());
std::transform(
std::begin(friendlyUnitIds),
std::end(friendlyUnitIds),
@@ -200,6 +202,7 @@ auto HolyWaveCommand::InternalExecute(
const GameStateW &currentState,
const std::shared_ptr<RandomGenerator> & /*generator*/) const -> vector<ActionResult> {
vector<ActionResult> results;
results.reserve(1 + 6 + 1);
const Unit *actorBefore = currentState->units()->Get(actorId);
@@ -228,6 +231,8 @@ auto HolyWaveCommand::InternalExecute(
vector<UnitId> undeadIds{};
vector<UnitId> allFriendlyUnitIds{};
undeadIds.reserve(6);
allFriendlyUnitIds.reserve(7);
auto allyPids = AlliedPids(runningState, GetPlayerId());
for (const auto &coords :
HexMapUtils::GetAdjacentCoords(runningState->hex_map(), actorBefore->location())) {
@@ -8,6 +8,7 @@
#include "MeleeCommand.hpp"
#include <optional>
#include <utility>
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/TerrainHelpers.hpp"
@@ -36,7 +37,7 @@ MeleeCommand::MeleeCommand(
auto MeleeCommand::InternalExecuteWithRoll(
const GameStateW& currentState,
const std::shared_ptr<RandomGenerator>& generator,
const optional<int32_t> roll) const -> vector<ActionResult> {
const std::optional<int32_t> roll) const -> vector<ActionResult> {
const auto ambushRoll = generator->Percentile();
const auto attackerRoll = roll.value_or(generator->OpenEndedPercentile());
const auto defenderRoll = generator->OpenEndedPercentile();
@@ -8,6 +8,8 @@
#include "src/main/cpp/net/eagle0/shardok/library/commands/challenge_duel_command/DuelUtils.hpp"
#include <array>
#include "src/main/cpp/net/eagle0/shardok/library/unit/Unit.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/CombatUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/view_filters/UnitFilter.hpp"
@@ -27,7 +29,6 @@ auto GetAcceptOdds(
net::eagle0::shardok::api::UnitView filteredView =
UnitFilteredForPlayer(settings, challenger, defender->player_id(), {});
std::vector<double> stats = {static_cast<double>(defender->attached_hero().bravery())};
std::vector<OtherFactor> otherFactors{};
Unit believedChallenger = *challenger;
MutatingSpendActionPoints(
@@ -100,8 +101,6 @@ auto GetAcceptOdds(
double battalionStrengthSwing = (NUM_RUNS - 2.0 * defenderWins) / NUM_RUNS;
double battalionStrengthFactor =
BATTALION_STRENGTH_WEIGHT * battalionStrengthSwing * defender->attached_hero().wisdom();
otherFactors.push_back(
MakeOtherFactor(static_cast<int>(battalionStrengthFactor), "battalion strength"));
bool heroStatsKnown = filteredView.attached_hero().has_stats();
@@ -144,8 +143,10 @@ auto GetAcceptOdds(
double duelOddsSwing = (2.0 * defenderWins - NUM_RUNS) / NUM_RUNS;
double expectedDuelOutcomeFactor =
HERO_STRENGTH_WEIGHT * duelOddsSwing * defender->attached_hero().wisdom();
otherFactors.push_back(
MakeOtherFactor(static_cast<int>(expectedDuelOutcomeFactor), "expected duel outcome"));
const std::array otherFactors = {
MakeOtherFactor(static_cast<int>(battalionStrengthFactor), "battalion strength"),
MakeOtherFactor(static_cast<int>(expectedDuelOutcomeFactor), "expected duel outcome")};
return MakeOdds(settings.Backing().duel_base_accept_odds(), 0, 0, 0, stats, otherFactors);
}
@@ -8,6 +8,7 @@
#include <flatbuffers/flatbuffers.h>
#include <cstdlib>
#include <cstring>
#include <utility>
#include "src/main/cpp/net/eagle0/common/ByteHasher.hpp"
@@ -34,7 +35,7 @@ private:
static auto CopyBuffer(const uint8_t* data, const size_t size) -> uint8_t* {
auto* b = WrapperAllocator::GetDefaultAllocator()->allocate(size);
memcpy(b, data, size);
std::memcpy(b, data, size);
return b;
}
@@ -81,7 +82,7 @@ public:
}
buffer = CopyBuffer(other.buffer, other.size);
} else if (buffer != nullptr && other.buffer != nullptr) {
memcpy(buffer, other.buffer, other.size);
std::memcpy(buffer, other.buffer, other.size);
}
size = other.size;
@@ -141,7 +142,7 @@ public:
}
size_t offset;
memcpy(&offset, str.data(), sizeof(size_t));
std::memcpy(&offset, str.data(), sizeof(size_t));
size_t size = str.size() - sizeof(size_t);
auto toReturn = Wrapper(
@@ -159,7 +160,7 @@ public:
}
size_t offset;
memcpy(&offset, bv.data(), sizeof(size_t));
std::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
@@ -7,6 +7,7 @@
#include <flatbuffers/flatbuffers.h>
#include <unordered_map>
#include <utility>
#include <vector>
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/HexMapHelpers.hpp"
@@ -46,6 +47,7 @@ auto ConvertVictoryCondition(const net::eagle0::shardok::common::VictoryConditio
auto FromPlayerInfoProto(flatbuffers::FlatBufferBuilder& fbb, const PlayerInfoProto& piProto)
-> flatbuffers::Offset<net::eagle0::shardok::storage::fb::PlayerInfo> {
auto alliesVec = std::vector<AlliedPlayer>();
alliesVec.reserve(piProto.allies_size());
for (const auto& apProto : piProto.allies()) { alliesVec.emplace_back(apProto.player_id()); }
const auto alliesOffset = fbb.CreateVectorOfStructs(alliesVec);
@@ -80,18 +82,17 @@ auto FoodPercentageByPid(
const std::vector<Unit>& units,
const SettingsGetter& settings) -> std::unordered_map<int, double> {
std::unordered_map<int, double> foodRequirementsByPid;
foodRequirementsByPid.reserve(playerInfoProtos.size());
for (const auto& unit : units) {
const auto cost = RequiredMonthlyFood(unit, settings);
if (foodRequirementsByPid.count(unit.player_id()) > 0) {
foodRequirementsByPid[unit.player_id()] += cost;
} else {
foodRequirementsByPid[unit.player_id()] = cost;
}
foodRequirementsByPid[unit.player_id()] += RequiredMonthlyFood(unit, settings);
}
std::unordered_map<int, double> foodPercentByPid;
foodPercentByPid.reserve(playerInfoProtos.size());
for (const auto& pi : playerInfoProtos) {
const double required = foodRequirementsByPid[pi.player_id()];
const auto requirementIt = foodRequirementsByPid.find(pi.player_id());
const double required =
requirementIt == foodRequirementsByPid.end() ? 0.0 : requirementIt->second;
const double available = pi.starting_food();
foodPercentByPid[pi.player_id()] =
required == 0 ? 1.0 : std::max(1.0, available / required);
@@ -142,6 +143,7 @@ auto SetupInitialGameState(
auto gameIdOffset = fbb.CreateString(gameId);
auto playerInfoVec = std::vector<Offset<PlayerInfo>>();
playerInfoVec.reserve(playerInfoProtos.size());
for (const auto& piProto : playerInfoProtos) {
playerInfoVec.push_back(FromPlayerInfoProto(fbb, piProto));
}
@@ -158,7 +160,7 @@ auto SetupInitialGameState(
}
// Add slack units: 3 per necromancer, minimum 5 for general use
int slackUnits = std::max(5, necromancerCount * 3);
const int slackUnits = std::max(5, necromancerCount * 3);
auto unitsVec = std::vector<Unit>();
unitsVec.reserve(units.size() + slackUnits);
@@ -185,7 +187,7 @@ auto SetupInitialGameState(
modifiedUnit.mutate_status(net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT);
}
unitsVec.push_back(modifiedUnit);
unitsVec.push_back(std::move(modifiedUnit));
}
// Add slack units for future expansion (e.g., necromancer raise dead)
@@ -198,7 +200,7 @@ auto SetupInitialGameState(
slackUnit.mutate_eagle_player_id(-1);
slackUnit.mutable_location().mutate_row(-1);
slackUnit.mutable_location().mutate_column(-1);
unitsVec.push_back(slackUnit);
unitsVec.push_back(std::move(slackUnit));
}
auto unitsOffset = fbb.CreateVectorOfSortedStructs(&unitsVec);
@@ -229,9 +231,11 @@ auto SetupInitialGameState(
auto ToProto(const GameStatus* fbStatus) -> GameStatusProto {
GameStatusProto protoStatus{};
protoStatus.set_state((net::eagle0::shardok::common::GameStatus_State)fbStatus->state());
protoStatus.set_state(
static_cast<net::eagle0::shardok::common::GameStatus_State>(fbStatus->state()));
if (fbStatus->description()) { protoStatus.set_description(fbStatus->description()->str()); }
if (fbStatus->winning_shardok_ids()) {
protoStatus.mutable_winning_shardok_ids()->Reserve(fbStatus->winning_shardok_ids()->size());
for (const auto pid : *fbStatus->winning_shardok_ids()) {
if (pid != -1) { protoStatus.add_winning_shardok_ids(pid); }
}
@@ -274,6 +278,7 @@ auto ToPlayerInfoProto(const PlayerInfo* fbPI) -> PlayerInfoProto {
fbPI->victory_conditions()->end()};
if (fbPI->allies()) {
piProto.mutable_allies()->Reserve(fbPI->allies()->size());
for (const auto& ally : *fbPI->allies()) {
piProto.add_allies()->set_player_id(ally->player_id());
}
@@ -65,6 +65,7 @@ auto ToProto(const HexMap* hexMap) -> HexMapProto {
hexMapProto.set_column_count(hexMap->column_count());
if (hexMap->monthly_weather()) {
hexMapProto.mutable_monthly_weather()->Reserve(hexMap->monthly_weather()->size());
for (const auto& mw : *hexMap->monthly_weather()) {
auto* mwProto = hexMapProto.add_monthly_weather();
mwProto->set_is_winter(mw->is_winter());
@@ -79,12 +80,15 @@ auto ToProto(const HexMap* hexMap) -> HexMapProto {
}
}
hexMapProto.mutable_terrain()->Reserve(hexMap->terrain()->size());
for (const auto& terr : *hexMap->terrain()) {
*hexMapProto.add_terrain() = ToTerrainProto(terr);
}
if (hexMap->defender_starting_positions() &&
hexMap->defender_starting_positions()->positions()) {
hexMapProto.mutable_defender_starting_positions()->mutable_positions()->Reserve(
hexMap->defender_starting_positions()->positions()->size());
for (const auto& dsp : *hexMap->defender_starting_positions()->positions()) {
auto* pos = hexMapProto.mutable_defender_starting_positions()->add_positions();
pos->set_row(dsp->row());
@@ -93,10 +97,13 @@ auto ToProto(const HexMap* hexMap) -> HexMapProto {
}
if (hexMap->attacker_starting_positions()) {
hexMapProto.mutable_attacker_starting_positions()->Reserve(
hexMap->attacker_starting_positions()->size());
for (const auto& aspl : *hexMap->attacker_starting_positions()) {
auto* list = hexMapProto.add_attacker_starting_positions();
if (aspl->positions()) {
list->mutable_positions()->Reserve(aspl->positions()->size());
for (const auto& asp : *aspl->positions()) {
auto* pos = list->add_positions();
pos->set_row(asp->row());
@@ -59,7 +59,9 @@ private:
public:
CoordsSet(int w, int h) : mapWidth(w), mapHeight(h), indexCount(w * h) {
if (kCoordsSetIndexChecks) { assert(w * h < STANDARD_SIZE); }
if (kCoordsSetIndexChecks) {
assert(static_cast<size_t>(w) * static_cast<size_t>(h) < STANDARD_SIZE);
}
clear();
}
explicit CoordsSet(const HexMap* map) : CoordsSet(map->column_count(), map->row_count()) {}
@@ -28,11 +28,11 @@ using net::eagle0::shardok::common::HexMapDirection::SOUTHWEST;
using net::eagle0::shardok::common::HexMapDirection::WEST;
inline HexMapDirection operator+(const HexMapDirection& dir, const int delta) {
return HexMapDirection((int(dir) + delta) % 6);
return static_cast<HexMapDirection>((static_cast<int>(dir) + delta) % 6);
}
inline HexMapDirection operator-(const HexMapDirection& dir, const int delta) {
return HexMapDirection((int(dir) - delta) % 6);
return static_cast<HexMapDirection>((static_cast<int>(dir) - delta) % 6);
}
inline HexMapDirection& operator++(HexMapDirection& dir) {
@@ -96,7 +96,7 @@ void GameSettings::Setter::SetRandomGenerator(std::shared_ptr<RandomGenerator> g
}
void GameSettings::Setter::RegisterBattalionType(const BattalionTypeSPtr& newType) {
if ((int64_t)settings.battalionTypes.size() <= newType->typeId) {
if (static_cast<int64_t>(settings.battalionTypes.size()) <= newType->typeId) {
settings.battalionTypes.resize(newType->typeId + 1);
}
settings.battalionTypes[newType->typeId] = newType;
@@ -9,6 +9,7 @@
#include "TutorialBattleController.hpp"
#include <ranges>
#include <utility>
#include <vector>
#include "src/main/cpp/net/eagle0/shardok/library/util/ActionResultFlatbufferHelpers.hpp"
@@ -38,6 +39,7 @@ auto TutorialBattleController::CheckAndExecuteEvents(
if (!enabled_) { return {}; }
TutorialEventResults combined;
combined.actionResults.reserve(config_.events_size());
// Check events in config order
for (const auto& event : config_.events()) {
@@ -195,12 +197,13 @@ auto TutorialBattleController::ExecuteFleeAction(
const FleeActionProto& fleeAction,
const GameStateW& state) const -> std::vector<ActionResult> {
std::vector<ActionResult> results;
results.reserve(state->units()->size() + 1);
const PlayerId fleeingPlayerId = fleeAction.player_id();
// First, create a "tutorial enemy fled" notification
ActionResult fleeNotification{};
fleeNotification.set_type(ActionType::TUTORIAL_ENEMY_FLED);
results.push_back(fleeNotification);
results.push_back(std::move(fleeNotification));
// Determine which units should flee
std::set<int32_t> specificUnitIds(fleeAction.unit_ids().begin(), fleeAction.unit_ids().end());
@@ -233,7 +236,7 @@ auto TutorialBattleController::ExecuteFleeAction(
AddUnitToResolved(*resolvedUnit, *unit);
resolvedUnit->set_status(net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_FLED_UNIT);
results.push_back(fleeResult);
results.push_back(std::move(fleeResult));
}
return results;
@@ -255,7 +258,9 @@ auto TutorialBattleController::ExecuteReinforcementsAction(
const auto* attackerPositions = state->hex_map()->attacker_starting_positions();
if (startingPosGroupIndex >= 0 &&
startingPosGroupIndex < static_cast<int>(attackerPositions->size())) {
for (const auto* pos : *attackerPositions->Get(startingPosGroupIndex)->positions()) {
const auto* startingPositions = attackerPositions->Get(startingPosGroupIndex)->positions();
availablePositions.reserve(startingPositions->size());
for (const auto* pos : *startingPositions) {
if (!state.GetOccupant(*pos)) { availablePositions.push_back(*pos); }
}
}
@@ -286,7 +291,7 @@ auto TutorialBattleController::ExecuteReinforcementsAction(
AddChangedUnit(reinforcementsResult, changedUnit);
}
eventResults.actionResults.push_back(reinforcementsResult);
eventResults.actionResults.push_back(std::move(reinforcementsResult));
return eventResults;
}
@@ -19,12 +19,6 @@
namespace shardok {
using std::begin;
using std::end;
using std::find_if;
using std::optional;
using std::remove_if;
using Unit = net::eagle0::shardok::storage::fb::Unit;
using Units = flatbuffers::Vector<const Unit *>;
@@ -21,7 +21,8 @@ static inline void AddChangedUnit(ActionResult& result, const Unit& unit) {
}
static inline void AddUnitToResolved(ResolvedUnit& resolvedUnit, const Unit& unit) {
*resolvedUnit.mutable_unit_bytes() = std::string((char*)&unit, sizeof(Unit));
*resolvedUnit.mutable_unit_bytes() =
std::string(reinterpret_cast<const char*>(&unit), sizeof(Unit));
}
} // namespace shardok
@@ -28,6 +28,7 @@ auto CubeToOffset(const Cube &cube) -> Coords {
auto CubesWithinDistance(const Cube &c1, const int distance) -> std::vector<Cube> {
std::vector<Cube> cubes{};
cubes.reserve(1 + 3 * distance * (distance + 1));
for (int x = -distance; x <= distance; x++) {
for (int y = std::max(-distance, -x - distance); y <= std::min(distance, -x + distance);
@@ -42,6 +43,7 @@ auto CubesWithinDistance(const Cube &c1, const int distance) -> std::vector<Cube
auto CubesWithExactDistance(const Cube &c1, const int distance) -> std::vector<Cube> {
std::vector<Cube> cubes{};
cubes.reserve(distance == 0 ? 1 : 6 * distance);
for (int x = -distance; x <= distance; x++) {
for (int y = std::max(-distance, -x - distance); y <= std::min(distance, -x + distance);
@@ -70,7 +72,7 @@ auto FixupCube(
newZ = -newY - newX;
}
return Cube(int16_t(newX), int16_t(newY), int16_t(newZ));
return Cube(static_cast<int16_t>(newX), static_cast<int16_t>(newY), static_cast<int16_t>(newZ));
}
// Always rounds half up, instead of away from zero.
@@ -90,6 +92,7 @@ auto CubeLerp(const Cube &c1, const Cube &c2, const double t) -> std::vector<Cub
const auto zDiff = abs(newZ - floatZ);
std::vector<Cube> cubes{};
cubes.reserve(2);
cubes.push_back(FixupCube(newX, xDiff, newY, yDiff, newZ, zDiff));
// The 0.5 case represents a situation where the line passes exactly between two hexes. In that
@@ -111,6 +114,9 @@ auto CubeLineInclusive(const Cube &c1, const Cube &c2) -> std::vector<Cube> {
std::vector<Cube> cubes;
const auto dist = CubeDistance(c1, c2);
if (dist == 0) { return {c1}; }
cubes.reserve(2 * (dist + 1));
const double reciprocal = 1.0 / dist;
for (int i = 0; i <= dist; i++) {
@@ -9,6 +9,7 @@
#include "HexMapUtils.hpp"
#include <algorithm>
#include <array>
#include <memory>
#include <ranges>
@@ -20,10 +21,10 @@ namespace shardok {
using Terrain = net::eagle0::shardok::storage::fb::Terrain;
const vector<Coords> ADJACENT_TILE_MODS_EVEN_ROW =
const std::array<Coords, 6> ADJACENT_TILE_MODS_EVEN_ROW =
{Coords(-1, 0), Coords(0, 1), Coords(1, 0), Coords(1, -1), Coords(0, -1), Coords(-1, -1)};
const vector<Coords> ADJACENT_TILE_MODS_ODD_ROW =
const std::array<Coords, 6> ADJACENT_TILE_MODS_ODD_ROW =
{Coords(-1, 1), Coords(0, 1), Coords(1, 1), Coords(1, 0), Coords(0, -1), Coords(-1, 0)};
auto AllCastleCoords(const HexMap *hexMap) -> CoordsSet {
@@ -78,7 +79,7 @@ auto HasForestAccess(
const Coords &coords,
const Units *units,
const HexMap *hexMap,
const vector<PlayerId> &allyPids,
const std::vector<PlayerId> &allyPids,
const PlayerId player) -> bool {
if (GetTerrain(hexMap, coords)->type() ==
net::eagle0::shardok::storage::fb::Terrain_::Type_FOREST)
@@ -176,12 +177,13 @@ auto HexMapUtils::GetAdjacentCoords(const HexMap *map, const Coords &origin) ->
}
auto HexMapUtils::GetAdjacentTiles(const HexMap *map, const Coords &origin)
-> vector<AdjacentTile> {
vector<AdjacentTile> adjacentTiles{};
-> std::vector<AdjacentTile> {
std::vector<AdjacentTile> adjacentTiles{};
adjacentTiles.reserve(6);
const auto &modifierSet =
(origin.row() % 2 ? ADJACENT_TILE_MODS_ODD_ROW : ADJACENT_TILE_MODS_EVEN_ROW);
auto dir = HexMapDirection(0);
auto dir = static_cast<HexMapDirection>(0);
for (const Coords &modifier : modifierSet) {
Coords adjacentCoords =
Coords(origin.row() + modifier.row(), origin.column() + modifier.column());
@@ -196,17 +198,17 @@ auto HexMapUtils::GetAdjacentTiles(const HexMap *map, const Coords &origin)
}
[[nodiscard]] auto GetTilesAcrossWater(const HexMap *map, const Coords &coords) -> CoordsSet {
const vector<AdjacentTile> adjacentTiles = HexMapUtils::GetAdjacentTiles(map, coords);
const std::vector<AdjacentTile> adjacentTiles = HexMapUtils::GetAdjacentTiles(map, coords);
CoordsSet braveWaterTargets(map);
for (const AdjacentTile &adjacentTile : adjacentTiles) {
const auto *terrain = GetTerrain(map, adjacentTile.coords);
if (IsWater((net::eagle0::shardok::common::Terrain_Type)terrain->type())) {
if (IsWater(static_cast<net::eagle0::shardok::common::Terrain_Type>(terrain->type()))) {
if (terrain->modifier().bridge().present()) continue;
if (terrain->modifier().ice().present()) continue;
for (int i = -1; i <= 1; i++) {
const auto nextDirection =
(HexMapDirection)((adjacentTile.directionTo + i + 6) % 6);
static_cast<HexMapDirection>((adjacentTile.directionTo + i + 6) % 6);
const auto &maybeNext =
MaybeTileInDirection(map, adjacentTile.coords, nextDirection);
@@ -678,11 +680,13 @@ auto KnownAdjacentEnemies(
const PlayerId playerId,
const Units *units,
const HexMap *map,
const vector<PlayerId> &allyPids,
const Coords &coords) -> vector<const Unit *> {
vector<const Unit *> knownAdjacentEnemies{};
const std::vector<PlayerId> &allyPids,
const Coords &coords) -> std::vector<const Unit *> {
std::vector<const Unit *> knownAdjacentEnemies{};
const CoordsSet &adjacentCoords = HexMapUtils::GetAdjacentCoords(map, coords);
knownAdjacentEnemies.reserve(adjacentCoords.size());
for (const Coords &adjCoords : HexMapUtils::GetAdjacentCoords(map, coords)) {
for (const Coords &adjCoords : adjacentCoords) {
const auto *possibleEnemy = KnownEnemyOccupant(playerId, units, allyPids, adjCoords);
if (possibleEnemy) { knownAdjacentEnemies.push_back(possibleEnemy); }
}
@@ -693,7 +697,7 @@ auto KnownAdjacentEnemies(
auto KnownEnemyOccupant(
const PlayerId playerId,
const Units *units,
const vector<PlayerId> &allyPids,
const std::vector<PlayerId> &allyPids,
const Coords &coords) -> const Unit * {
const auto *occupant = Occupant(units, coords);
if (occupant) {
@@ -29,7 +29,6 @@ using Terrain = net::eagle0::shardok::storage::fb::Terrain;
using UnitView = net::eagle0::shardok::api::UnitView;
using Unit = net::eagle0::shardok::storage::fb::Unit;
using Units = flatbuffers::Vector<const Unit *>;
using std::vector;
struct alignas(16) AdjacentTile {
HexMapDirection directionTo;
@@ -59,7 +58,7 @@ public:
-> const CoordsSet &;
[[nodiscard]] static auto GetAdjacentTiles(const HexMap *map, const Coords &coords)
-> vector<AdjacentTile>;
-> std::vector<AdjacentTile>;
[[nodiscard]] static auto LineIsBlockedByMountains(
const HexMap *map,
@@ -78,7 +77,7 @@ auto HasForestAccess(
const Coords &coords,
const Units *units,
const HexMap *hexMap,
const vector<PlayerId> &allyPids,
const std::vector<PlayerId> &allyPids,
PlayerId player) -> bool;
auto GetTerrain(const HexMap *map, const Coords &coords) -> const Terrain *;
@@ -124,10 +123,10 @@ static inline auto Occupant(const Units &units, const Coords &coords) -> const U
}
static inline auto Occupants(
const vector<const Unit *> &units,
const std::vector<const Unit *> &units,
const int rowCount,
const int columnCount) -> vector<const Unit *> {
vector<const Unit *> positions(rowCount * columnCount);
const int columnCount) -> std::vector<const Unit *> {
std::vector<const Unit *> positions(rowCount * columnCount);
for (const auto &unit : units) {
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) {
@@ -142,8 +141,8 @@ static inline auto Occupants(
static inline auto Occupants(
const flatbuffers::Vector<const Unit *> &units,
const int rowCount,
const int columnCount) -> vector<const Unit *> {
vector<const Unit *> positions(rowCount * columnCount);
const int columnCount) -> std::vector<const Unit *> {
std::vector<const Unit *> positions(rowCount * columnCount);
for (const auto &unit : units) {
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) {
@@ -155,7 +154,7 @@ static inline auto Occupants(
return positions;
}
static inline auto Occupant(const vector<const Unit *> &units, const Coords &coords)
static inline auto Occupant(const std::vector<const Unit *> &units, const Coords &coords)
-> const Unit * {
for (const auto &unit : units) {
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
@@ -208,7 +207,7 @@ auto Occupant(const UnitContainer &units, const Coords &coords) -> std::optional
static inline auto FriendlyOccupant(
const PlayerId pid,
const Units *units,
const vector<PlayerId> &allyPids,
const std::vector<PlayerId> &allyPids,
const Coords &coords) -> const Unit * {
const auto *occupant = Occupant(units, coords);
if (occupant) {
@@ -225,7 +224,7 @@ template<class U, class UnitContainer>
auto FriendlyOccupant(
PlayerId pid,
const UnitContainer &units,
const vector<PlayerId> &allyPids,
const std::vector<PlayerId> &allyPids,
const Coords &coords) -> std::optional<U> {
auto occupantOptional = Occupant(units, coords);
if (occupantOptional.has_value()) {
@@ -241,14 +240,14 @@ auto FriendlyOccupant(
auto KnownEnemyOccupant(
PlayerId playerId,
const Units *units,
const vector<PlayerId> &allyPids,
const std::vector<PlayerId> &allyPids,
const Coords &coords) -> const Unit *;
template<class UnitContainer>
auto KnownEnemyOccupant(
PlayerId playerId,
const UnitContainer &units,
const vector<PlayerId> &allyPids,
const std::vector<PlayerId> &allyPids,
const Coords &coords) -> std::optional<typename UnitContainer::value_type> {
auto occupantOptional = Occupant(units, coords);
if (occupantOptional.has_value()) {
@@ -265,7 +264,7 @@ auto KnownEnemyOccupant(
[[nodiscard]] static inline auto KnownOccupant(
const PlayerId playerId,
const Units *units,
const vector<PlayerId> &allyPids,
const std::vector<PlayerId> &allyPids,
const Coords &coords) -> const Unit * {
const auto *occupant = Occupant(units, coords);
if (occupant) {
@@ -282,8 +281,8 @@ auto KnownAdjacentEnemies(
PlayerId playerId,
const Units *units,
const HexMap *map,
const vector<PlayerId> &allyPids,
const Coords &coords) -> vector<const Unit *>;
const std::vector<PlayerId> &allyPids,
const Coords &coords) -> std::vector<const Unit *>;
auto DirectionsTo(int columnCount, const Coords &origin, const Coords &destination)
-> HexMapDirectionsTo;
@@ -41,6 +41,7 @@ auto AlliedPids(const GameState *gameState, int playerId) -> std::vector<PlayerI
std::vector<PlayerId> allyPids{};
if (pi->allies()) {
allyPids.reserve(pi->allies()->size());
for (const auto &ally : *pi->allies()) { allyPids.push_back(ally->player_id()); }
}
return allyPids;
@@ -121,7 +121,7 @@ auto AttackOrientationForAttack(
const PlayerId playerId,
const Units *units,
const HexMap *hexMap,
const vector<PlayerId> &allyPids) -> CoordsSet {
const std::vector<PlayerId> &allyPids) -> CoordsSet {
CoordsSet cs(hexMap); // NOLINT(readability-identifier-length)
for (const Unit *unit : *units) {
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
@@ -139,7 +139,7 @@ auto AttackOrientationForAttack(
const PlayerId playerId,
const Units *units,
const HexMap *hexMap,
const vector<PlayerId> &allyPids,
const std::vector<PlayerId> &allyPids,
const Coords &coords) -> bool {
CoordsSet adjCoords = HexMapUtils::GetAdjacentCoords(hexMap, coords);
return std::any_of(std::begin(adjCoords), std::end(adjCoords), [&](const Coords &adjCoords) {
@@ -27,14 +27,14 @@ ExertsZoneOfControl(const SettingsGetter &settings, const Unit *unit, PlayerId t
PlayerId playerId,
const Units *units,
const HexMap *hexMap,
const vector<PlayerId> &allyPids) -> CoordsSet;
const std::vector<PlayerId> &allyPids) -> CoordsSet;
[[nodiscard]] auto IsInEnemyZoc(
const SettingsGetter &settings,
PlayerId playerId,
const Units *units,
const HexMap *hexMap,
const vector<PlayerId> &allyPids,
const std::vector<PlayerId> &allyPids,
const Coords &coords) -> bool;
[[nodiscard]] auto AttackOrientationForAttack(
@@ -93,9 +93,9 @@ auto TargetIsHiddenFromOpponents(const ActionResultType type) -> bool {
hiddenActor = unit.hidden();
}
}
wasHidden = beforeState &&
wasHidden = beforeState && actorId >= 0 &&
beforeState->units()->size() > static_cast<unsigned int>(actorId) &&
beforeState->units()->Get(result.actor().value())->hidden();
beforeState->units()->Get(actorId)->hidden();
}
const PlayerId actorPlayer = result.has_player() ? result.player().value() : -1;
const bool actorIsSelfOrAlly =
@@ -38,10 +38,41 @@ auto GameStateFilteredForPlayerWithAllies(
gsv.set_game_id(gameState->game_id()->str());
gsv.set_month(gameState->month());
gsv.mutable_player_infos()->Reserve(gameState->player_infos()->size());
for (const auto& info : *gameState->player_infos()) {
*gsv.add_player_infos() = fb::ToPlayerInfoProto(info);
}
int reserveUnitCount = 0;
int normalUnitCount = 0;
for (const auto& unit : *gameState->units()) {
switch (unit->status()) {
case net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT:
case net::eagle0::shardok::storage::fb::UnitStatus_NEVER_ENTERED_UNIT:
++reserveUnitCount;
break;
case net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT:
++normalUnitCount;
break;
case net::eagle0::shardok::storage::fb::UnitStatus_DESTROYED_SUMMONED_UNIT:
case net::eagle0::shardok::storage::fb::UnitStatus_CAPTURED_UNIT:
case net::eagle0::shardok::storage::fb::UnitStatus_FLED_UNIT:
case net::eagle0::shardok::storage::fb::UnitStatus_RETREATED_UNIT:
case net::eagle0::shardok::storage::fb::UnitStatus_OUTLAWED_UNIT:
case net::eagle0::shardok::storage::fb::UnitStatus_EVACUATED_UNIT:
case net::eagle0::shardok::storage::fb::UnitStatus_SECURED_UNIT:
case net::eagle0::shardok::storage::fb::UnitStatus_RESERVED_SLOT:
case net::eagle0::shardok::storage::fb::UnitStatus_PENDING_REINFORCEMENT: break;
case net::eagle0::shardok::storage::fb::UnitStatus_UNKNOWN_UNIT:
throw ShardokInternalErrorException("Unknown unit status");
}
}
gsv.mutable_reserve_units()->Reserve(reserveUnitCount);
gsv.mutable_units()->Reserve(normalUnitCount);
for (const auto& unit : *gameState->units()) {
switch (unit->status()) {
case net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT:
@@ -80,12 +111,14 @@ auto GameStateFilteredForPlayerWithAllies(
if (gameState->eligible_charger_id() > -1) {
gsv.mutable_eligible_charger_id()->set_value(gameState->eligible_charger_id());
}
gsv.mutable_possible_chargee_ids()->Reserve(gameState->possible_chargee_ids()->size());
gsv.mutable_possible_chargee_ids()->Add(
gameState->possible_chargee_ids()->begin(),
gameState->possible_chargee_ids()->end());
*gsv.mutable_status() = fb::ToProto(gameState->status());
gsv.mutable_player_totals()->Reserve(gameState->player_infos()->size());
for (const auto* player : *gameState->player_infos()) {
auto* newTotal = gsv.add_player_totals();
newTotal->set_player_id(player->player_id());
@@ -59,6 +59,7 @@ auto GuessMeteorTarget(
// Build a set of valid coords within meteor range
std::vector<Coords> validCoordsInRange;
validCoordsInRange.reserve(1 + 3 * meteorRange * (meteorRange + 1));
for (const Cube &cube : CubesWithinDistance(mageCube, meteorRange)) {
Coords coords = CubeToOffset(cube);
if (CoordsAreValidProto(hexMap, coords) && coords != mageLocation) {
@@ -94,6 +95,7 @@ auto GuessMeteorTarget(
// Priority 3: Find a castle not occupied by the caster's player
// Build set of occupied coords by caster's player
std::unordered_set<int> casterOccupiedIndices;
casterOccupiedIndices.reserve(units.size());
for (const auto &unit : units) {
if (unit.player_id() == casterPlayerId) {
Coords loc = FromCoordsProto(unit.location());
@@ -154,6 +156,7 @@ auto GameStateGuesser::GuessedState(
auto gameIdOffset = fbb.CreateString(gameStateView.game_id());
std::vector<Offset<net::eagle0::shardok::storage::fb::PlayerInfo>> piVec{};
piVec.reserve(gameStateView.player_infos_size());
for (const PlayerInfoProto &pi : gameStateView.player_infos()) {
piVec.push_back(fb::FromPlayerInfoProto(fbb, pi));
}
@@ -161,9 +164,12 @@ auto GameStateGuesser::GuessedState(
auto statusDescription = fbb.CreateString(gameStateView.status().description());
std::vector<PlayerId> winningShardokIdsVec = {
std::vector<PlayerId> winningShardokIdsVec;
winningShardokIdsVec.reserve(10);
winningShardokIdsVec.insert(
winningShardokIdsVec.end(),
std::begin(gameStateView.status().winning_shardok_ids()),
std::end(gameStateView.status().winning_shardok_ids())};
std::end(gameStateView.status().winning_shardok_ids()));
for (int i = gameStateView.status().winning_shardok_ids_size(); i < 10; i++) {
winningShardokIdsVec.push_back(-1);
}
@@ -210,6 +216,9 @@ auto GameStateGuesser::GuessedState(
std::vector<UnitId> foundUnits{};
std::vector<Unit> unitsVec{};
const int knownUnitCount = gameStateView.units_size() + gameStateView.reserve_units_size();
foundUnits.reserve(knownUnitCount);
unitsVec.reserve(knownUnitCount + 6);
for (const auto &uv : gameStateView.units()) {
unitsVec.push_back(GuessedUnit(
settings,
@@ -344,7 +353,10 @@ auto GameStateGuesser::GuessedState(
auto unitsOffset = fbb.CreateVectorOfSortedStructs(&unitsVec);
auto chargeeIdsVec = std::vector<UnitId>(
std::vector<UnitId> chargeeIdsVec;
chargeeIdsVec.reserve(6);
chargeeIdsVec.insert(
chargeeIdsVec.end(),
std::begin(gameStateView.possible_chargee_ids()),
std::end(gameStateView.possible_chargee_ids()));
for (int i = gameStateView.possible_chargee_ids_size(); i < 6; i++) {
@@ -35,8 +35,6 @@ using net::eagle0::common::ShardokLatencyFields;
using net::eagle0::common::VictoryCondition;
using net::eagle0::shardok::api::PlacementCommand;
using net::eagle0::shardok::common::EndGameCondition;
using std::begin;
using std::end;
class NewGameException : public std::exception {
private:
@@ -87,7 +85,7 @@ auto EagleInterfaceImpl::ControllerForGame(const GameId &gameId, const GameSetup
}
EagleInterfaceImpl::EagleInterfaceImpl(
shared_ptr<ShardokGamesManager> manager,
std::shared_ptr<ShardokGamesManager> manager,
std::string authToken)
: gamesManager(std::move(manager)),
tokenValidator_(std::move(authToken)) {
@@ -119,7 +117,7 @@ auto ConvertPlayerInfo(
std::ranges::transform(
pi.units(),
std::back_inserter(shardokUnits),
[shardokPid, allPids](const net::eagle0::common::CommonUnit &unit) {
[shardokPid, &allPids](const net::eagle0::common::CommonUnit &unit) {
return ConvertUnit(unit, shardokPid, allPids);
});
@@ -221,6 +219,7 @@ void EagleInterfaceImpl::StartGame(const NewGameRequest &request) {
watcherAllies.reserve(request.watcher_eagle_faction_ids_size());
for (const int32_t watcherFid : request.watcher_eagle_faction_ids()) {
std::vector<PlayerId> allyPids;
allyPids.reserve(userInfoCount);
for (int i = 0; i < userInfoCount; i++) {
for (const auto &ally : request.user_infos(i).allies()) {
if (ally.eagle_faction_id() == watcherFid) {
@@ -260,10 +259,10 @@ auto EagleInterfaceImpl::PostCommand(
// Validate auth token
if (auto authStatus = tokenValidator_.ValidateOrStatus(context)) { return *authStatus; }
shared_ptr<ShardokGameController> controller;
std::shared_ptr<ShardokGameController> controller;
try {
controller = ControllerForGame(request->game_id(), request->game_setup_info());
} catch (NewGameException &e) { return e.GetStatus(); }
} catch (const NewGameException &e) { return e.GetStatus(); }
try {
{
ScopedShardokLatencyTrace trace(
@@ -291,13 +290,13 @@ auto EagleInterfaceImpl::PostCommand(
request->game_setup_info().known_result_count(),
response);
}
} catch (ShardokClientErrorException &e) {
} catch (const ShardokClientErrorException &e) {
std::cerr << "Bad request from client: " << e.what() << '\n';
return Status(StatusCode::INVALID_ARGUMENT, e.what());
} catch (ShardokInternalErrorException &e) {
} catch (const ShardokInternalErrorException &e) {
std::cerr << "Internal error: " << e.what() << '\n';
return Status(StatusCode::INTERNAL, e.what());
} catch (std::exception &e) {
} catch (const std::exception &e) {
std::cerr << "Unknown exception: " << e.what() << '\n';
return Status(StatusCode::INTERNAL, e.what());
}
@@ -321,10 +320,10 @@ auto EagleInterfaceImpl::PostPlacementCommands(
// Validate auth token
if (auto authStatus = tokenValidator_.ValidateOrStatus(context)) { return *authStatus; }
shared_ptr<ShardokGameController> controller;
std::shared_ptr<ShardokGameController> controller;
try {
controller = ControllerForGame(request->game_id(), request->game_setup_info());
} catch (NewGameException &e) { return e.GetStatus(); }
} catch (const NewGameException &e) { return e.GetStatus(); }
try {
vector<UnitPlacementInfo> upis;
upis.reserve(request->placement_commands().size());
@@ -360,13 +359,13 @@ auto EagleInterfaceImpl::PostPlacementCommands(
request->game_setup_info().known_result_count(),
response);
}
} catch (ShardokClientErrorException &e) {
} catch (const ShardokClientErrorException &e) {
std::cerr << "Bad request from client: " << e.what() << '\n';
return Status(StatusCode::INVALID_ARGUMENT, e.what());
} catch (ShardokInternalErrorException &e) {
} catch (const ShardokInternalErrorException &e) {
std::cerr << "Internal error: " << e.what() << '\n';
return Status(StatusCode::INTERNAL, e.what());
} catch (std::exception &e) {
} catch (const std::exception &e) {
std::cerr << "Unknown exception: " << e.what() << '\n';
return Status(StatusCode::INTERNAL, e.what());
}
@@ -375,7 +374,7 @@ auto EagleInterfaceImpl::PostPlacementCommands(
}
void EagleInterfaceImpl::PopulateGameStatusResponse(
shared_ptr<ShardokGameController> &controller,
std::shared_ptr<ShardokGameController> &controller,
const int64_t startingActionId,
GameStatusResponse *response) {
ScopedShardokLatencyTrace trace(
@@ -400,15 +399,15 @@ void EagleInterfaceImpl::PopulateGameStatusResponse(
}
} else {
response->mutable_game_update_response()->mutable_update_responses()->Add(
begin(results.mainResults),
end(results.mainResults));
std::begin(results.mainResults),
std::end(results.mainResults));
for (const auto &op : results.filteredResults) {
auto *newFilteredResponse =
response->mutable_game_update_response()->add_filtered_update_responses();
newFilteredResponse->set_eagle_faction_id(op.eagleFactionId);
newFilteredResponse->mutable_action_result_views()->Add(
begin(op.resultViews),
end(op.resultViews));
std::begin(op.resultViews),
std::end(op.resultViews));
if (op.availableCommands) {
*newFilteredResponse->mutable_available_commands() = *op.availableCommands;
@@ -467,16 +466,18 @@ auto EagleInterfaceImpl::SubscribeToGame(
// Validate auth token
if (auto authStatus = tokenValidator_.ValidateOrStatus(context)) { return *authStatus; }
shared_ptr<ShardokGameController> controller;
std::shared_ptr<ShardokGameController> controller;
try {
controller = ControllerForGame(request->game_id(), request->game_setup_info());
} catch (NewGameException &e) { return e.GetStatus(); } catch (ShardokClientErrorException &e) {
} catch (const NewGameException &e) {
return e.GetStatus();
} catch (const ShardokClientErrorException &e) {
std::cerr << "Bad request from client: " << e.what() << '\n';
return Status(StatusCode::INVALID_ARGUMENT, e.what());
} catch (ShardokInternalErrorException &e) {
} catch (const ShardokInternalErrorException &e) {
std::cerr << "Internal error: " << e.what() << '\n';
return Status(StatusCode::INTERNAL, e.what());
} catch (std::exception &e) {
} catch (const std::exception &e) {
std::cerr << "Unknown exception in SubscribeToGame: " << e.what() << '\n';
return Status(StatusCode::INTERNAL, e.what());
}
@@ -545,8 +546,8 @@ auto EagleInterfaceImpl::SubscribeToGame(
response.set_game_id(gameId_);
response.mutable_game_update_response()->mutable_update_responses()->Add(
begin(mainResults),
end(mainResults));
std::begin(mainResults),
std::end(mainResults));
// Add filtered results for each player with their faction IDs
for (const auto &playerUpdate : filteredResults) {
@@ -554,8 +555,8 @@ auto EagleInterfaceImpl::SubscribeToGame(
response.mutable_game_update_response()->add_filtered_update_responses();
filtered->set_eagle_faction_id(playerUpdate.eagleFactionId);
filtered->mutable_action_result_views()->Add(
begin(playerUpdate.resultViews),
end(playerUpdate.resultViews));
std::begin(playerUpdate.resultViews),
std::end(playerUpdate.resultViews));
if (playerUpdate.availableCommands) {
*filtered->mutable_available_commands() = *playerUpdate.availableCommands;
}
@@ -42,7 +42,6 @@ using net::eagle0::common::NewGameRequest;
using net::eagle0::common::PlacementCommandsRequest;
using net::eagle0::common::PostCommandRequest;
using net::eagle0::common::ShardokInternalInterface;
using std::shared_ptr;
class EagleInterfaceImpl final : public ShardokInternalInterface::Service {
private:
@@ -54,7 +53,7 @@ private:
void StartGame(const NewGameRequest& newGameRequest);
void PopulateGameStatusResponse(
shared_ptr<ShardokGameController>& controller,
std::shared_ptr<ShardokGameController>& controller,
int64_t startingActionId,
GameStatusResponse* response);
@@ -8,10 +8,11 @@
#include "ServerConfiguration.hpp"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <sstream>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
@@ -50,12 +51,14 @@ ServerConfiguration::ServerConfiguration(const std::string& filePath) {
}
std::ifstream istr(filePath);
char line[256];
while (!istr.eof()) {
istr.getline(line, 256);
if (strnlen(line, 256) < 3) continue;
std::string key = strtok(line, " =");
std::string value = strtok(nullptr, " =");
std::string line;
while (std::getline(istr, line)) {
std::ranges::replace(line, '=', ' ');
std::istringstream lineStream(line);
std::string key;
std::string value;
if (!(lineStream >> key >> value)) { continue; }
configValues[key] = value;
}
@@ -16,7 +16,6 @@
#include "ServerConfiguration.hpp"
#include "src/main/cpp/net/eagle0/common/UnitConversions.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.hpp"
#include "src/main/cpp/net/eagle0/shardok/controller/ShardokGameController.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/settings_loader/SettingsLoader.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/MapLoader.hpp"
@@ -52,6 +51,21 @@ ShardokGamesManager::ShardokGamesManager(const std::vector<std::string> &extraSe
}
}
auto TutorialReinforcementUnitCount(const TutorialBattleConfigProto &tutorialConfig) -> size_t {
if (!tutorialConfig.enabled()) { return 0; }
size_t unitCount = 0;
for (const auto &event : tutorialConfig.events()) {
if (!event.has_action() || event.action().action_type_case() !=
net::eagle0::common::TutorialAction::kReinforcements) {
continue;
}
unitCount += event.action().reinforcements().units_size();
}
return unitCount;
}
auto ShardokGamesManager::GetController(const GameId &gameId)
-> std::shared_ptr<ShardokGameController> {
std::shared_lock<std::shared_mutex> lk(runningControllersLock);
@@ -105,6 +119,7 @@ auto ShardokGamesManager::UnlockedCreateSpecifiedGame(
}
std::vector<PlayerInfoProto> playerInfos{};
playerInfos.reserve(playerSetupInfos.size());
std::transform(
std::begin(playerSetupInfos),
std::end(playerSetupInfos),
@@ -112,6 +127,9 @@ auto ShardokGamesManager::UnlockedCreateSpecifiedGame(
[](const PlayerInfoWithUnits &psi) { return psi.playerInfo; });
std::vector<Unit> units{};
size_t unitCount = 0;
for (const auto &info : playerSetupInfos) { unitCount += info.units.size(); }
units.reserve(unitCount);
for (const auto &info : playerSetupInfos) {
units.insert(std::end(units), std::begin(info.units), std::end(info.units));
}
@@ -135,8 +153,6 @@ auto ShardokGamesManager::LockedCreateGame(
const std::shared_ptr<ShardokGameController> &controller,
const std::vector<PlayerInfoWithUnits> & /*playerInfos*/,
const std::string & /*serializedRequest*/) -> GameId {
std::vector<std::shared_ptr<ShardokAIClient>> aiClients{};
GameId newGameId = controller->GetGameId();
runningControllers[newGameId] = controller;
@@ -157,6 +173,7 @@ auto SetUpController(
const std::vector<std::pair<int32_t, std::vector<PlayerId>>> &watcherAllies)
-> std::shared_ptr<ShardokGameController> {
auto unplacedUnits = std::vector<Unit>();
unplacedUnits.reserve(units.size() + TutorialReinforcementUnitCount(tutorialConfig));
UnitId nextUnitId = 0;
for (auto up : units) {
@@ -180,7 +197,7 @@ auto SetUpController(
}
}
unplacedUnits.push_back(up);
unplacedUnits.push_back(std::move(up));
}
// Add reinforcement units from tutorial config as PENDING_REINFORCEMENT.
@@ -188,6 +205,7 @@ auto SetUpController(
// until the TutorialBattleController activates them at the triggered round.
if (tutorialConfig.enabled()) {
std::vector<PlayerId> allPlayerIds;
allPlayerIds.reserve(playerInfos.size());
for (size_t i = 0; i < playerInfos.size(); i++) {
allPlayerIds.push_back(static_cast<PlayerId>(i));
}
@@ -206,7 +224,7 @@ auto SetUpController(
unit.mutate_unit_id(nextUnitId++);
unit.mutate_status(
net::eagle0::shardok::storage::fb::UnitStatus_PENDING_REINFORCEMENT);
unplacedUnits.push_back(unit);
unplacedUnits.push_back(std::move(unit));
}
}
}
@@ -218,7 +236,7 @@ auto SetUpController(
effectiveSettings->GetSetter().SetInt(kSettingsKeys.duelBaseAcceptOdds, 200);
}
unique_ptr<ShardokEngine> engine = std::make_unique<ShardokEngine>(
std::unique_ptr<ShardokEngine> engine = std::make_unique<ShardokEngine>(
effectiveSettings,
std::move(unplacedUnits),
customMapBytes.empty() ? LoadMap(mapName) : LoadMapFromBytes(customMapBytes),
@@ -38,10 +38,10 @@ auto GetMapNames() -> std::vector<std::string> {
kShardokMapExtension);
std::vector<std::string> names{};
names.reserve(mapFileNames.size());
for (const auto& fileName : mapFileNames) {
const size_t lastindex = fileName.find_last_of('.');
names.push_back(fileName.substr(0, lastindex));
names.push_back(fileName.substr(0, fileName.size() - kShardokMapExtension.size()));
}
return names;
@@ -38,8 +38,10 @@ public class AvailableGameItem : MonoBehaviour {
}
}
// Use this for initialization
void Start() {}
private void Awake() {
TouchTargetUtils.EnsureMinimumTouchTargets(joinButton, dropButton);
TouchTargetUtils.EnsureMinimumTouchHeight(heroDropdownController);
}
public void JoinClicked() {
// Track multiplayer status for tutorial system
@@ -206,6 +206,7 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
void Start() {
MainQueue.EnsureExists();
EnsureOAuthManager();
EnsureConnectionTouchTargets();
var currentResolution = Screen.currentResolution;
var resolutions = Screen.resolutions;
@@ -239,6 +240,24 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
TryRestoreSession();
}
private void EnsureConnectionTouchTargets() {
TouchTargetUtils.EnsureMinimumTouchHeight(resolutionDropdown);
TouchTargetUtils.EnsureMinimumTouchTargets(
discordLoginButton,
googleLoginButton,
githubLoginButton,
appleLoginButton,
steamLoginButton,
twitchLoginButton,
connectToQAButton,
logoutButton,
customBattleButton,
cancelCustomBattleButton,
lobbyConnectToQAButton,
startTutorialButton);
TouchTargetUtils.EnsureMinimumTouchHeight(tutorialPhaseDropdown);
}
private static void EnsureOAuthManager() {
if (OAuthManager.Instance != null) return;
@@ -22,6 +22,12 @@ public class CreateGameItem : MonoBehaviour {
private List<AvailableLeader> availableLeaders;
private void Awake() {
TouchTargetUtils.EnsureMinimumTouchTarget(createButton);
TouchTargetUtils.EnsureMinimumTouchHeight(heroDropdownController);
TouchTargetUtils.EnsureMinimumTouchHeight(totalHumansDropdown);
}
public string SelectedLeaderTextId {
get {
if (heroDropdownController.SelectedHeroId == 0) {
@@ -38,6 +38,11 @@ public class CustomBattleHandler : MonoBehaviour, IClientConnectionSubscriber {
private ShardokGameModel _shardokModel;
private void Awake() {
TouchTargetUtils.EnsureMinimumTouchTarget(defenderToggle);
TouchTargetUtils.EnsureMinimumTouchHeights(mapDropdown, monthDropdown);
}
public void SetActive(
EagleConnection conn,
PersistentClientConnection persistentClientConnection) {
@@ -20,8 +20,7 @@ public class RunningGameItem : MonoBehaviour {
private long gameId;
// Use this for initialization
void Start() {}
private void Awake() { TouchTargetUtils.EnsureMinimumTouchTarget(dropButton); }
public void GoClicked() { GoCallback(gameId); }
@@ -5,4 +5,9 @@ public class StoredAccountButton : MonoBehaviour {
public TMP_Text label;
public Image icon;
public Button deleteButton;
}
private void Awake() {
TouchTargetUtils.EnsureMinimumTouchTarget(this);
TouchTargetUtils.EnsureMinimumTouchTarget(deleteButton);
}
}
@@ -22,6 +22,8 @@ namespace eagle {
private bool _registeredAsFightButton;
private void Awake() { TouchTargetUtils.EnsureMinimumTouchTarget(actionButton); }
public void Populate(
ShardokBattleView battle,
IGameModel model,
@@ -22,6 +22,10 @@ namespace eagle {
private ChronicleEntry CurrentEntry => _entries[_currentIndex];
private void Awake() {
TouchTargetUtils.EnsureMinimumTouchTargets(previousButton, nextButton);
}
public void OnTextUpdate(string text, bool completed) { SetUp(); }
public string TextId() { return _entries.Count > 0 ? CurrentEntry.GeneratedTextId : null; }
@@ -538,6 +538,7 @@ RectTransform:
- {fileID: 3165093937138047963}
- {fileID: 3759813680990936597}
- {fileID: 8750533615234724169}
- {fileID: 5362531688911303417}
- {fileID: 4376065723316188052}
- {fileID: 2798378402887572584}
- {fileID: 6305990270828882927}
@@ -3291,6 +3292,130 @@ RectTransform:
type: 3}
m_PrefabInstance: {fileID: 5703825663780783650}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &5958177192227291968
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 783599146639672379}
m_Modifications:
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_Pivot.x
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_Pivot.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_AnchorMax.x
value: 1
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_AnchorMax.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_AnchorMin.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_SizeDelta.y
value: -40
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_LocalRotation.x
value: -0
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_LocalRotation.y
value: -0
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_LocalRotation.z
value: -0
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_AnchoredPosition.y
value: -20
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4568439738335415760, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
propertyPath: m_Name
value: March Panel Narrow
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: dd998d23eb8ce477b9a60c6a6f216ede, type: 3}
--- !u!224 &5362531688911303417 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 1784613741407482297, guid: dd998d23eb8ce477b9a60c6a6f216ede,
type: 3}
m_PrefabInstance: {fileID: 5958177192227291968}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &6201982296183330662
PrefabInstance:
m_ObjectHideFlags: 0
@@ -21,6 +21,7 @@ namespace eagle {
for (int i = 0; i < contentsCount; i++) {
int buttonIndex = i;
var newButton = Instantiate(buttonPrefab, buttonsContainer.transform);
TouchTargetUtils.EnsureMinimumTouchTarget(newButton);
newButton.GetComponentInChildren<RawImage>().texture = contents[buttonIndex].image;
newButton.onClick.AddListener(() => gameController.SelectButtonIndex(buttonIndex));
@@ -51,6 +51,18 @@ namespace eagle {
public void UpdateSelectorLayoutMode(LayoutMode mode) {
if (Selector != null && Selector.CurrentLayoutMode != mode) {
if (!Selector.SupportsLayoutMode(mode) && AvailableCommand != null) {
var replacementSelector = commandSelectors.Find(
sel => sel.IsAppropriateFor(AvailableCommand) &&
sel.SupportsLayoutMode(mode));
if (replacementSelector != null) {
Selector = replacementSelector;
Selector.CurrentLayoutMode = mode;
Selector.UpdateAvailableCommand(Model, AvailableCommand);
headerLabel.text = Selector.HeaderString;
return;
}
}
Selector.CurrentLayoutMode = mode;
Selector.OnLayoutModeChanged();
}
@@ -77,7 +89,10 @@ namespace eagle {
// If help was showing for previous command, show help for new command
if (helpWasShowing) { ShowCurrentCommandHelp(); }
} catch (Exception e) { errorHandler.Add(e); }
} catch (Exception e) {
Debug.LogException(e);
throw;
}
}
/// <summary>
@@ -115,7 +130,9 @@ namespace eagle {
}
public List<CommandSelector> AppropriateSelectors(AvailableCommand cmd) =>
commandSelectors.FindAll(sel => sel.IsAppropriateFor(cmd));
commandSelectors.FindAll(
sel => sel.IsAppropriateFor(cmd) &&
sel.SupportsLayoutMode(mainController.CurrentLayoutMode));
public List<GUIContent> ButtonContentsForCommand(AvailableCommand cmd) {
return (from sel in AppropriateSelectors(cmd)
@@ -141,6 +158,8 @@ namespace eagle {
public void Start() {
foreach (var kvp in commandSelectors) { kvp.gameObject.SetActive(false); }
TouchTargetUtils.EnsureMinimumTouchTarget(commitCommandButton);
TouchTargetUtils.EnsureMinimumTouchTarget(infoButton);
}
public void Update() {

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