Compare commits

...
Author SHA1 Message Date
admin 3301817214 Use direct vector declarations in command factory tests 2026-06-23 13:06:21 -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
adminandGitHub 72ea2de25c Version Addressables by Unity editor (#7731) 2026-06-22 08:33:15 -07:00
adminandGitHub 4b559c2278 Report validated command lookup failures explicitly (#7720) 2026-06-22 07:38:14 -07:00
adminandGitHub 327ecf1aed Qualify std algorithms in hex map utils (#7721) 2026-06-22 07:37:26 -07:00
adminandGitHub 8fc893ce08 Qualify iterators in game state guesser (#7719) 2026-06-22 07:36:38 -07:00
adminandGitHub 0ec0e10c0f Remove stale iterator aliases from APD (#7718) 2026-06-22 07:34:02 -07:00
adminandGitHub 38c7a01743 Qualify std types in fixed APD test (#7717) 2026-06-22 07:33:50 -07:00
adminandGitHub a481b5ce60 Report recruit heroes ruling hero explicitly (#7716) 2026-06-22 07:32:35 -07:00
adminandGitHub 7786db1040 Qualify std types in Shardok games manager (#7715) 2026-06-22 07:30:48 -07:00
adminandGitHub 200aa0d319 Report exile vassal lookup failures explicitly (#7714) 2026-06-22 07:30:27 -07:00
adminandGitHub c8bb543907 Remove stale std aliases from AI headers (#7713) 2026-06-22 07:30:08 -07:00
adminandGitHub de7ecf45d1 Avoid repeated eligible gift get (#7712) 2026-06-22 07:27:18 -07:00
adminandGitHub f25298ed9e Report command factory lookup failures explicitly (#7711) 2026-06-22 07:27:01 -07:00
adminandGitHub d4ea2fbd93 Report arm troops lookup failures explicitly (#7710) 2026-06-22 07:24:24 -07:00
adminandGitHub 79c51fb21a Qualify STL containers in game state helpers (#7709) 2026-06-22 07:23:48 -07:00
adminandGitHub c57c9b5f18 Qualify std helpers in Shardok production code (#7708) 2026-06-22 07:20:09 -07:00
adminandGitHub 823cd73d33 Report missing hero descriptions explicitly (#7707) 2026-06-22 07:19:50 -07:00
adminandGitHub 54db4b6e34 Report missing suppress beasts effect text (#7706) 2026-06-22 07:18:56 -07:00
adminandGitHub 112399d67c Qualify smart pointer helpers in Shardok tests (#7705) 2026-06-22 07:18:42 -07:00
adminandGitHub 01b80d8798 Report missing province ruling faction explicitly (#7704) 2026-06-22 07:18:14 -07:00
adminandGitHub f6a3f36a13 Remove unused vector aliases in Shardok tests (#7703) 2026-06-22 07:18:01 -07:00
adminandGitHub d674e23987 Report missing common unit faction explicitly (#7702) 2026-06-22 07:17:46 -07:00
adminandGitHub 51aebf5e40 Qualify std vector in Shardok tests (#7701) 2026-06-22 07:15:59 -07:00
adminandGitHub 2ec6d2f1d7 Report missing seek-more-leaders faction explicitly (#7700) 2026-06-22 07:15:23 -07:00
adminandGitHub 9f94917a2d Report required lookup failures explicitly (#7687) 2026-06-22 07:08:10 -07:00
adminandGitHub 06f2104750 Report new game lookup failures explicitly (#7688) 2026-06-22 07:07:53 -07:00
adminandGitHub b1e1e36236 Report empty in-memory history explicitly (#7690) 2026-06-22 07:06:02 -07:00
adminandGitHub 9eb2056ac6 Report map load failures with context (#7691) 2026-06-22 07:05:40 -07:00
adminandGitHub 0c3cb22e83 Report unknown hex terrain types explicitly (#7692) 2026-06-22 07:05:24 -07:00
adminandGitHub 92f27ba273 Report missing faction utility lookups explicitly (#7693) 2026-06-22 07:05:04 -07:00
adminandGitHub 47d682e7c1 Report unaffiliated phase lookup failures explicitly (#7694) 2026-06-22 07:04:23 -07:00
adminandGitHub 75d84b67ff Report missing action ruling factions explicitly (#7695) 2026-06-22 07:03:52 -07:00
adminandGitHub be8f5ca5da Report missing beast fallback explicitly (#7696) 2026-06-22 07:03:24 -07:00
adminandGitHub c7910a6183 Report missing withdrawing flee province explicitly (#7697) 2026-06-22 07:03:08 -07:00
renovate[bot]andGitHub 98afc28762 Update alpine_linux:3.24 Docker digest to 28bd5fe (#7698) 2026-06-22 06:17:57 -07:00
adminandGitHub 4cd1349a5a Fix AI victory score test std types (#7699) 2026-06-22 05:44:05 -07:00
adminandGitHub cebac64264 Avoid unsafe diplomacy hero fallback head (#7668) 2026-06-21 21:48:23 -07:00
adminandGitHub 172125e03b Avoid unsafe AI game arg head (#7667) 2026-06-21 21:48:09 -07:00
adminandGitHub 15006091b4 Report missing AI commands explicitly (#7670) 2026-06-21 21:46:19 -07:00
adminandGitHub 2175f1d957 Report missing diplomacy offers explicitly (#7671) 2026-06-21 21:45:56 -07:00
adminandGitHub d445cbfe25 Avoid unsafe ruling hero head (#7673) 2026-06-21 21:44:07 -07:00
adminandGitHub 5fc56c342d Avoid unsafe shuffle tail heads (#7674) 2026-06-21 21:43:53 -07:00
adminandGitHub e5350d03a8 Report missing recon targets explicitly (#7676) 2026-06-21 21:43:40 -07:00
adminandGitHub 097daf3dc1 Report missing province ruling heroes explicitly (#7678) 2026-06-21 21:43:25 -07:00
adminandGitHub 3c3dc85187 Avoid standard aliases in command factory headers (#7675)
* Avoid standard aliases in command factory headers

* Fix available commands std type qualification
2026-06-21 21:42:47 -07:00
adminandGitHub a775a667f6 Remove unused vector alias from unit filter (#7677) 2026-06-21 21:41:27 -07:00
adminandGitHub a1a7a69aeb Remove unused vector alias from game settings (#7679) 2026-06-21 21:41:08 -07:00
adminandGitHub ff549ab337 Qualify shared pointers in cache and settings headers (#7686)
* Qualify shared pointers in cache and settings headers

* Fix map info test shared pointer spelling
2026-06-21 21:40:51 -07:00
adminandGitHub 464651503e Avoid vector aliases in utility headers (#7672) 2026-06-21 21:36:38 -07:00
adminandGitHub 2f93250c8e Avoid vector aliases in AI headers (#7669) 2026-06-21 21:36:14 -07:00
adminandGitHub 22ce7ba750 Report missing tutorial ruling heroes explicitly (#7680) 2026-06-21 21:35:54 -07:00
adminandGitHub acbb09f6a1 Qualify vector usage in action point distances (#7681) 2026-06-21 21:35:40 -07:00
adminandGitHub 900dd23ae7 Use explicit backstory text id accessors (#7683) 2026-06-21 21:35:14 -07:00
adminandGitHub 0f391776aa Qualify vector usage in duel utilities (#7682) 2026-06-21 21:34:42 -07:00
adminandGitHub 817fb613ab Clean up library implementation std aliases (#7684) 2026-06-21 21:34:16 -07:00
adminandGitHub 2f57ea0160 Qualify std types in available commands header (#7685) 2026-06-21 21:33:59 -07:00
adminandGitHub f5d5aedf9d Avoid std aliases in map loader header (#7647) 2026-06-21 19:56:47 -07:00
adminandGitHub c5114e9bf1 Avoid unsafe OpenAI choice head (#7648) 2026-06-21 19:56:35 -07:00
adminandGitHub 7514aed437 Remove unused vector alias from unit filter header (#7649) 2026-06-21 19:56:19 -07:00
adminandGitHub f4d381c9d4 Report missing headshot output path (#7650) 2026-06-21 19:56:09 -07:00
adminandGitHub 8698036f7c Avoid unsafe migration version lookup (#7651) 2026-06-21 19:55:51 -07:00
adminandGitHub 21f04cfd46 Avoid string alias in shardok exception header (#7652) 2026-06-21 19:55:30 -07:00
adminandGitHub 074cf62cc9 Avoid unsafe recruit province head (#7653) 2026-06-21 19:52:34 -07:00
adminandGitHub a66f833c90 Avoid unsafe aftermath auto-resolve head (#7654) 2026-06-21 19:51:53 -07:00
adminandGitHub d704d23d39 Avoid unsafe aftermath hostile error head (#7655) 2026-06-21 19:48:34 -07:00
adminandGitHub e2570aec4a Use explicit vectors in implementation files (#7656) 2026-06-21 19:48:06 -07:00
adminandGitHub 6b97fde9ab Report missing tutorial battle explicitly (#7657) 2026-06-21 19:47:50 -07:00
adminandGitHub 30adf1bcbf Avoid vector aliases in APD headers (#7658) 2026-06-21 19:46:25 -07:00
adminandGitHub f205b49a54 Avoid string alias in action result helper header (#7659) 2026-06-21 19:46:06 -07:00
adminandGitHub 5c7e18696e Avoid unsafe quest chooser hero heads (#7660) 2026-06-21 19:45:50 -07:00
adminandGitHub c78b00b0fc Avoid vector alias in coords set header (#7661) 2026-06-21 19:45:19 -07:00
adminandGitHub b05d6e1e1b Avoid unsafe recon candidate head (#7662) 2026-06-21 19:45:05 -07:00
adminandGitHub da0bce655c Avoid standard aliases in shardok action headers (#7663) 2026-06-21 19:44:41 -07:00
adminandGitHub d4dcbcc9d5 Report missing uncontested conqueror explicitly (#7664) 2026-06-21 19:44:26 -07:00
adminandGitHub f132e5b87b Remove unused string alias from hero header (#7665) 2026-06-21 19:42:05 -07:00
adminandGitHub 872475e82b Avoid map alias in unit header (#7666) 2026-06-21 19:41:48 -07:00
adminandGitHub 66d062bfda Avoid unsafe pregenerated hero head (#7631) 2026-06-21 15:55:51 -07:00
adminandGitHub a514ceb62f Cache repeated loop bounds (#7632) 2026-06-21 15:55:38 -07:00
adminandGitHub b0d8a5bc41 Avoid unsafe province distance frontier lookup (#7633) 2026-06-21 15:55:19 -07:00
adminandGitHub 8f4bb5130e Avoid namespace import in map info implementation (#7634) 2026-06-21 15:55:03 -07:00
adminandGitHub 9386589558 Avoid unsafe attack decision unit head (#7638) 2026-06-21 15:54:47 -07:00
adminandGitHub 5945289a81 Use explicit std types in map loader (#7639) 2026-06-21 15:54:31 -07:00
adminandGitHub 53346cc85b Use explicit std types in game over response populator (#7640) 2026-06-21 15:26:45 -07:00
adminandGitHub a6fad3ac4e Use explicit string type in shardok controller (#7641) 2026-06-21 15:26:32 -07:00
adminandGitHub cb7e2b5d5c Use explicit std types in shardok server main (#7642) 2026-06-21 15:26:15 -07:00
adminandGitHub 0f764416a3 Avoid namespace import in AI performance runner (#7643) 2026-06-21 15:25:56 -07:00
adminandGitHub c9e0012bf7 Avoid namespace import in random battle runner (#7644) 2026-06-21 15:25:37 -07:00
adminandGitHub 457f2a677a Avoid vector alias in game update receiver header (#7645) 2026-06-21 15:25:10 -07:00
adminandGitHub ced56ee41f Avoid string alias in server configuration header (#7646) 2026-06-21 15:24:56 -07:00
adminandGitHub 4e910f1f22 Report missing migration save files explicitly (#7637) 2026-06-21 15:16:53 -07:00
adminandGitHub 34bceebace Remove unused registrar header string import (#7636) 2026-06-21 15:16:34 -07:00
adminandGitHub b4f802ba77 Avoid unsafe LLM update group bounds (#7635) 2026-06-21 15:16:15 -07:00
adminandGitHub 3d9d464870 Avoid unsafe battle aftermath claimant head (#7630) 2026-06-21 15:04:00 -07:00
adminandGitHub a7e21427f9 Avoid namespace imports in map info header (#7627) 2026-06-21 15:03:43 -07:00
adminandGitHub bdbf10294b Avoid namespace import in map info runner (#7629) 2026-06-21 15:02:57 -07:00
adminandGitHub 8588430cf0 Avoid unsafe TSV header lookup (#7628) 2026-06-21 15:01:15 -07:00
adminandGitHub d920bb5c17 Report utility TSV load failures explicitly (#7626) 2026-06-21 14:59:01 -07:00
adminandGitHub 64fe9bf59d Cache map starting position count in tests (#7625) 2026-06-21 14:58:23 -07:00
adminandGitHub 101b0bf41f Cache hex map terrain count (#7624) 2026-06-21 14:54:31 -07:00
adminandGitHub 019865b5ee Reserve converted victory conditions (#7623) 2026-06-21 14:54:16 -07:00
adminandGitHub 799d34a1f1 Avoid unsafe Gemini response heads (#7622) 2026-06-21 14:54:03 -07:00
adminandGitHub 951d533ed3 Avoid unsafe rate limit header heads (#7619) 2026-06-21 14:53:49 -07:00
adminandGitHub 459eb0ef7b Cache Eagle interface player counts (#7621) 2026-06-21 14:53:15 -07:00
adminandGitHub f710105bd9 Cache action result repeated field counts (#7620) 2026-06-21 14:53:01 -07:00
adminandGitHub 4998e3ce40 Cache changed unit counts in gtest helpers (#7618) 2026-06-21 14:48:32 -07:00
adminandGitHub d739b374a4 Cache AI battle protobuf unit counts (#7617) 2026-06-21 14:48:15 -07:00
adminandGitHub bd61ebcbb6 Use flatbuffer-sized production knowledge indices (#7616) 2026-06-21 14:47:53 -07:00
adminandGitHub 3963275092 Report hero backstory load failures explicitly (#7569) 2026-06-21 14:40:52 -07:00
adminandGitHub a796e0fe67 Avoid unsafe battle request error message lookup (#7583) 2026-06-21 14:40:35 -07:00
adminandGitHub 52ac61fa1d Qualify flatbuffer wrapper abort calls (#7606) 2026-06-21 14:40:13 -07:00
adminandGitHub 29ca7665b1 Use size_t for AI integration unit loops (#7607) 2026-06-21 14:39:25 -07:00
adminandGitHub d9bdb814f4 Use size_t for MCTS reserve unit loops (#7608) 2026-06-21 14:39:09 -07:00
adminandGitHub 3dfb094a08 Use size_t for optimized score test unit loops (#7609) 2026-06-21 14:38:51 -07:00
adminandGitHub 7ce1818a7f Use flatbuffer-sized unit knowledge indices (#7610) 2026-06-21 14:37:45 -07:00
adminandGitHub 6e6eff7991 Use flatbuffer-sized retreat factory knowledge index (#7615) 2026-06-21 14:37:04 -07:00
adminandGitHub aa9c98f081 Use flatbuffer-sized game start knowledge indices (#7611) 2026-06-21 14:36:00 -07:00
adminandGitHub cdf0ad45f0 Use flatbuffer-sized move test knowledge index (#7612) 2026-06-21 14:35:14 -07:00
adminandGitHub 1b97b6673f Use flatbuffer-sized melee test knowledge index (#7613) 2026-06-21 14:34:10 -07:00
adminandGitHub 721d432bc6 Use flatbuffer-sized flee factory knowledge index (#7614) 2026-06-21 14:33:01 -07:00
adminandGitHub e513a784eb Report missing start game heroes explicitly (#7571) 2026-06-21 14:30:42 -07:00
adminandGitHub ff8da78992 Report missing tutorial battle players explicitly (#7577) 2026-06-21 14:30:11 -07:00
adminandGitHub 84a69ce982 Match prisoner leader faction options (#7572) 2026-06-21 14:28:27 -07:00
adminandGitHub 7d743f8678 Report missing proto converter fields explicitly (#7575) 2026-06-21 14:28:13 -07:00
adminandGitHub 3f24e15392 Bind custom battle human updates explicitly (#7578) 2026-06-21 14:27:49 -07:00
adminandGitHub 0f86bb6874 Match engine command lookup explicitly (#7580) 2026-06-21 14:27:29 -07:00
adminandGitHub 05ee7a2ab2 Report unruled defend flee provinces explicitly (#7582) 2026-06-21 14:25:46 -07:00
adminandGitHub b9321433d0 Report fixed hero load failures explicitly (#7567) 2026-06-21 14:24:29 -07:00
adminandGitHub 62791401e9 Use streams for raise dead setup output (#7581) 2026-06-21 14:18:59 -07:00
adminandGitHub 4d28bba9cc Use streams for MCTS decision investigation output (#7579) 2026-06-21 14:18:42 -07:00
adminandGitHub 1f3660e1f3 Use streams for reserve combat phase output (#7576) 2026-06-21 14:18:24 -07:00
adminandGitHub 95ff44e205 Use streams for reserve setup diagnostics (#7574) 2026-06-21 14:18:08 -07:00
adminandGitHub 987a164092 Report missing hostile army neighbor explicitly (#7584) 2026-06-21 14:17:51 -07:00
adminandGitHub 1b5b11fd5b Match pending aftermath info explicitly (#7586) 2026-06-21 14:17:35 -07:00
adminandGitHub ddcde52e27 Bind validated command options explicitly (#7587) 2026-06-21 14:12:54 -07:00
adminandGitHub ea2da84535 Report missing declined quest hero explicitly (#7589) 2026-06-21 14:12:04 -07:00
adminandGitHub abed8ee574 Bind control weather option explicitly (#7590) 2026-06-21 14:09:50 -07:00
adminandGitHub 556c59a9c7 Report S3 credential load failures explicitly (#7566) 2026-06-21 14:09:39 -07:00
adminandGitHub ad2d085b24 Stabilize sqlite history upload snapshot test (#7605) 2026-06-21 14:08:50 -07:00
adminandGitHub d7209102b8 Report missing battalion types explicitly (#7592) 2026-06-21 13:25:39 -07:00
adminandGitHub 14e1ba6795 Avoid unwrapping province distances (#7594) 2026-06-21 13:25:25 -07:00
adminandGitHub 65b56081ce Report beast info load failures explicitly (#7595) 2026-06-21 13:25:00 -07:00
adminandGitHub eafb51cbfa Report name generator load failures explicitly (#7564) 2026-06-21 13:24:37 -07:00
adminandGitHub 3444ae296e Report missing ransom offer prisoner explicitly (#7597) 2026-06-21 13:24:23 -07:00
adminandGitHub 4f2fff5da6 Use streams for MCTS start fire test output (#7565) 2026-06-21 13:23:50 -07:00
adminandGitHub 144483a45d Use streams for start fire cost explanation (#7573) 2026-06-21 13:21:53 -07:00
adminandGitHub 53345b2bb0 Use streams for MCTS rain fire output (#7568) 2026-06-21 13:17:20 -07:00
adminandGitHub 5d6bc429b0 Use streams for start fire cost summary (#7570) 2026-06-21 13:17:00 -07:00
adminandGitHub a4faf61613 Report missing divine acting province explicitly (#7599) 2026-06-21 13:16:41 -07:00
adminandGitHub fdd516598d Report missing apprehended outlaw explicitly (#7601) 2026-06-21 13:16:29 -07:00
adminandGitHub 70da6990c4 Match optional date protos explicitly (#7602) 2026-06-21 13:13:12 -07:00
adminandGitHub e8b18d0805 Use streams for raise dead filter output (#7585) 2026-06-21 13:12:03 -07:00
adminandGitHub 1ca9e8678c Use streams for MCTS investigation intro output (#7588) 2026-06-21 13:11:29 -07:00
adminandGitHub 94670149fe Use streams for MCTS end-turn score output (#7591) 2026-06-21 13:11:14 -07:00
adminandGitHub 8e253c2972 Use streams for MCTS start-fire failure output (#7596) 2026-06-21 13:11:00 -07:00
adminandGitHub 6e15c6d9cb Use streams for MCTS start-fire success output (#7593) 2026-06-21 13:10:21 -07:00
adminandGitHub ca00e90ad6 Use streams for AI command mismatch diagnostics (#7600) 2026-06-21 13:10:07 -07:00
adminandGitHub b1d76ea661 Use streams for MCTS path comparison output (#7598) 2026-06-21 13:09:52 -07:00
adminandGitHub 059d428cfa Make Shardok player id conversions explicit (#7603) 2026-06-21 13:08:19 -07:00
adminandGitHub a4b50eb357 Tighten map info index handling (#7604) 2026-06-21 13:07:43 -07:00
adminandGitHub 0dfd5aa11d Use streams for critical fire score output (#7563) 2026-06-21 10:32:50 -07:00
adminandGitHub 2b63ccd145 Report name format parse failures explicitly (#7562) 2026-06-21 10:32:38 -07:00
adminandGitHub 97cec5ec38 Add Shardok Linux ARM64 PR build (#7561) 2026-06-21 10:29:17 -07:00
adminandGitHub 31fb5a3616 Avoid partial battalion access in riot command errors (#7560) 2026-06-21 10:27:28 -07:00
adminandGitHub 7b7130d67d Use resource handling for Shardok map info (#7552) 2026-06-21 10:25:49 -07:00
adminandGitHub 6707ad55ff Report missing beasts event explicitly (#7559) 2026-06-21 10:25:37 -07:00
adminandGitHub 89aa73b63a Use resource handling for image paths (#7554) 2026-06-21 10:25:21 -07:00
adminandGitHub bb7453a002 Avoid partial Option access in HeroNameCache (#7555) 2026-06-21 10:25:09 -07:00
adminandGitHub 8cea29dcb0 Avoid partial command access in early AI (#7556) 2026-06-21 10:24:55 -07:00
adminandGitHub d5d74ec8d6 Avoid partial command access in mid-game AI (#7557) 2026-06-21 10:24:32 -07:00
adminandGitHub 9b000b94ac Report missing Shardok map info explicitly (#7558) 2026-06-21 10:24:13 -07:00
adminandGitHub 82ed9fcc83 Include array in CombatDamage (#7553) 2026-06-21 10:23:51 -07:00
adminandGitHub cd53283935 Use resource handling for LLM usage loads (#7551) 2026-06-21 10:17:08 -07:00
adminandGitHub 3faddf76f8 Use resource handling in JsonUtils (#7550) 2026-06-21 10:15:43 -07:00
adminandGitHub 6e24ae2f4f Use streams for MCTS archery debug output (#7538) 2026-06-21 10:10:27 -07:00
adminandGitHub 66912580c8 Use streams in C++ debug headers (#7540) 2026-06-21 10:10:13 -07:00
adminandGitHub 219dd830e1 Use streams for MCTS score sequence output (#7541) 2026-06-21 10:09:51 -07:00
adminandGitHub 3d5cdcc78e Use streams for combat damage strings (#7542) 2026-06-21 10:09:32 -07:00
adminandGitHub ab1d43fae3 Use streams for MCTS minimax debug output (#7543) 2026-06-21 10:09:19 -07:00
adminandGitHub 2d51f3c23d Use resource handling for name format reads (#7548) 2026-06-21 10:09:03 -07:00
adminandGitHub f9447cf7aa Use resource handling for map info migration (#7549) 2026-06-21 10:08:42 -07:00
adminandGitHub c6ed0bfc7d Use streams for AI choice diagnostics (#7534) 2026-06-21 10:06:57 -07:00
adminandGitHub 2d4621b1a6 Use pattern matching for parser literals (#7547) 2026-06-21 10:05:48 -07:00
adminandGitHub 3bf929b4a5 Use streams for MCTS fire score analysis (#7544) 2026-06-21 10:02:45 -07:00
adminandGitHub ebed01ba86 Use streams for abstract MCTS diagnostics (#7545) 2026-06-21 10:02:29 -07:00
adminandGitHub 880d005347 Use streams for Alah fire score analysis (#7546) 2026-06-21 10:02:12 -07:00
adminandGitHub e8ef216de8 Use streams for common C++ diagnostics (#7539) 2026-06-21 10:01:47 -07:00
adminandGitHub baa453594a Delete old dropped games automatically (#7535) 2026-06-21 09:46:01 -07:00
adminandGitHub 9c767d5c49 Use streams for MCTS score placement diagnostics (#7537) 2026-06-21 09:45:15 -07:00
adminandGitHub e0892bd2fa Use streams for MCTS setup diagnostics (#7536) 2026-06-21 09:44:55 -07:00
adminandGitHub 315be7c465 Use streams for ID AI performance logs (#7533) 2026-06-21 09:42:33 -07:00
adminandGitHub 9d34dc0954 Use streams for AI debug dump messages (#7532) 2026-06-21 09:42:14 -07:00
adminandGitHub bdedd43e3c Use streams for score timing diagnostic (#7531) 2026-06-21 09:42:02 -07:00
adminandGitHub 97e779675e Use streams in gtest contains diagnostics (#7528) 2026-06-21 09:41:43 -07:00
adminandGitHub 2ef4216886 Use cerr for flatbuffer allocator failures (#7530) 2026-06-21 09:41:28 -07:00
adminandGitHub 8c9764a57e Use cerr for APD thread diagnostic (#7529) 2026-06-21 09:41:09 -07:00
adminandGitHub eb94eb1a6d Use streams in available commands test diagnostics (#7527) 2026-06-21 09:40:53 -07:00
adminandGitHub 12e3fb99eb Keep dropped games in existing storage (#7525) 2026-06-21 09:32:34 -07:00
adminandGitHub 2bd90a26a8 Remove client text getTexts returns (#7526) 2026-06-21 09:32:04 -07:00
adminandGitHub 24db3a4cb1 Use streams for AI command count logging (#7524) 2026-06-21 09:31:02 -07:00
adminandGitHub 0d05ea1730 Use streams for iterative deepening diagnostics (#7523) 2026-06-21 09:30:45 -07:00
adminandGitHub 9c13b3c1bf Use streams for AI flee debug output (#7522) 2026-06-21 09:27:34 -07:00
adminandGitHub 492d9d16eb Remove StringConstructionParser returns (#7507) 2026-06-21 09:20:30 -07:00
adminandGitHub c4cd0eb683 Remove broken action point distance timer (#7509) 2026-06-21 09:15:53 -07:00
adminandGitHub 287f7b9818 Fix random battle runner build (#7521) 2026-06-21 09:15:35 -07:00
adminandGitHub 15b5a3e0c1 Remove SQLite client text guard returns (#7505) 2026-06-21 09:15:15 -07:00
adminandGitHub cf93c43ef2 Remove AI time budget debug print (#7506) 2026-06-21 09:14:55 -07:00
adminandGitHub 380c24537b Remove stale undead morale diagnostic (#7508) 2026-06-21 09:12:31 -07:00
adminandGitHub 6200bc7e02 Remove SqliteHistory flush return (#7510) 2026-06-21 09:12:05 -07:00
adminandGitHub e92465c894 Use explicit chrono imports in distance test (#7511) 2026-06-21 09:11:52 -07:00
adminandGitHub 6f432d221a Remove GameController debug return (#7512) 2026-06-21 09:11:28 -07:00
adminandGitHub b8ddca4e76 Route action point cache stats through streams (#7513) 2026-06-21 09:11:12 -07:00
adminandGitHub f343ee9f7f Send battalion type parse errors to stderr (#7514) 2026-06-21 09:10:53 -07:00
adminandGitHub 9a8c4c6fdd Remove GameController post battle return (#7515) 2026-06-21 09:10:38 -07:00
adminandGitHub 96f2dc0454 Send hex map migrator errors to stderr (#7516) 2026-06-21 09:10:21 -07:00
adminandGitHub e0988ef186 Remove GamesManager cache invalidation returns (#7517) 2026-06-21 09:07:20 -07:00
adminandGitHub a154174fa1 Remove broken Shardok inspector target (#7496) 2026-06-21 09:05:50 -07:00
adminandGitHub 042861f065 Remove GamesManager S3 archive return (#7520) 2026-06-21 09:04:54 -07:00
adminandGitHub 0f776af050 Remove GamesManager flush marker returns (#7518) 2026-06-21 09:04:18 -07:00
adminandGitHub 166e50924f Remove GamesManager LLM response return (#7519) 2026-06-21 09:04:00 -07:00
adminandGitHub efb34ede56 Remove Postgres client text guard returns (#7504) 2026-06-21 09:02:56 -07:00
551 changed files with 20321 additions and 4122 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
@@ -177,10 +177,12 @@ jobs:
env:
DO_CDN_PAT: ${{ secrets.DO_CDN_PAT }}
run: |
UNITY_VERSION=$(grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //')
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
curl -s -X DELETE "https://api.digitalocean.com/v2/cdn/endpoints/8c98df29-6f0c-4704-8e82-ca40a2b81aa8/cache" \
-H "Authorization: Bearer $DO_CDN_PAT" \
-H "Content-Type: application/json" \
-d '{"files": ["addressables/iOS/*"]}' \
-d "{\"files\": [\"addressables/iOS/$UNITY_MAJOR_MINOR/*\"]}" \
--fail || echo "Warning: CDN purge failed (non-fatal)"
- name: Archive Build Log
+5 -1
View File
@@ -267,6 +267,7 @@ jobs:
runs-on: [self-hosted, macOS, notarize]
outputs:
deployed_version: ${{ steps.deploy-mac.outputs.deployed_version }}
deployed_unity_major_minor: ${{ steps.deploy-mac.outputs.deployed_unity_major_minor }}
concurrency:
group: mac-deploy-${{ github.ref }}
cancel-in-progress: false
@@ -495,6 +496,9 @@ jobs:
rm "$SPARKLE_PRIVATE_KEY_PATH"
echo "deployed_version=$VERSION" >> $GITHUB_OUTPUT
UNITY_VERSION=$(grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //')
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
echo "deployed_unity_major_minor=$UNITY_MAJOR_MINOR" >> $GITHUB_OUTPUT
# Artifact cleanup is handled by the cleanup job on ubuntu-latest
- name: Cleanup build directory
@@ -515,7 +519,7 @@ jobs:
curl -s -X DELETE "https://api.digitalocean.com/v2/cdn/endpoints/8c98df29-6f0c-4704-8e82-ca40a2b81aa8/cache" \
-H "Authorization: Bearer $DO_CDN_PAT" \
-H "Content-Type: application/json" \
-d '{"files": ["mac/*", "addressables/StandaloneOSX/*"]}' \
-d '{"files": ["mac/*", "addressables/StandaloneOSX/${{ needs.deploy-mac.outputs.deployed_unity_major_minor }}/*"]}' \
--fail || echo "Warning: CDN purge failed (non-fatal)"
curl -X POST "https://admin.eagle0.net/notify-update?platform=mac&version=${{ needs.deploy-mac.outputs.deployed_version }}&required=false" \
+6
View File
@@ -20,6 +20,7 @@ on:
- 'ci/github_actions/ensure_bazel_installed.sh'
- 'scripts/build_eagle_ci.sh'
- 'scripts/build_shardok_ci.sh'
- 'scripts/build_shardok_linux_arm64_ci.sh'
- '.github/actions/setup-bazel/**'
- '.github/workflows/server_build.yml'
@@ -83,6 +84,7 @@ jobs:
'src/main/cpp/',
'src/main/protobuf/net/eagle0/shardok/',
'scripts/build_shardok_ci.sh',
'scripts/build_shardok_linux_arm64_ci.sh',
];
const shared = matches(sharedPatterns);
@@ -106,6 +108,10 @@ jobs:
name: 'Shardok server',
command: './scripts/build_shardok_ci.sh',
});
include.push({
name: 'Shardok Linux ARM64 server',
command: './scripts/build_shardok_linux_arm64_ci.sh',
});
}
core.setOutput('build_matrix', JSON.stringify({include}));
+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
+1 -1
View File
@@ -282,7 +282,7 @@ oci.pull(
# Base image for Admin Server (Alpine for lightweight Go binary)
oci.pull(
name = "alpine_linux",
digest = "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
digest = "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
image = "docker.io/library/alpine",
platforms = ["linux/amd64"],
tag = "3.24",
+8 -8
View File
@@ -904,7 +904,7 @@
"@@rules_oci+//oci:extensions.bzl%oci": {
"general": {
"bzlTransitiveDigest": "H23W9dvwCQM6h+Wt8PjIahx08EkAU5eSbUxAyx9Mopc=",
"usagesDigest": "tbQ7wHHObzzrBzSRv652104GY3xxiaQDK5OUp2YeEbg=",
"usagesDigest": "k6DBHAkb5br/tpwCPUMJgqwb+7U4DXyCmBZQBgLnHJ8=",
"recordedInputs": [
"REPO_MAPPING:aspect_bazel_lib+,bazel_tools bazel_tools",
"REPO_MAPPING:bazel_features+,bazel_tools bazel_tools",
@@ -922,7 +922,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/eclipse-temurin",
"identifier": "sha256:db3011b348ad87ced055afe5145238d87a3d8f0e8f23eb4d0cd8cecb4659b085",
"identifier": "sha256:0beb258e10ad28d2bae0f74349a170bdfa67da0ef87c028cbf3d95c3e1170120",
"platform": "linux/amd64",
"target_name": "eclipse_temurin_25_jre_linux_amd64",
"bazel_tags": []
@@ -936,7 +936,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/eclipse-temurin",
"identifier": "sha256:db3011b348ad87ced055afe5145238d87a3d8f0e8f23eb4d0cd8cecb4659b085",
"identifier": "sha256:0beb258e10ad28d2bae0f74349a170bdfa67da0ef87c028cbf3d95c3e1170120",
"platforms": {
"@@platforms//cpu:x86_64": "@eclipse_temurin_25_jre_linux_amd64"
},
@@ -980,7 +980,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/ubuntu",
"identifier": "24.04",
"identifier": "26.04",
"platform": "linux/amd64",
"target_name": "ubuntu_24_04_linux_amd64",
"bazel_tags": []
@@ -993,7 +993,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/ubuntu",
"identifier": "24.04",
"identifier": "26.04",
"platform": "linux/arm64/v8",
"target_name": "ubuntu_24_04_linux_arm64_v8",
"bazel_tags": []
@@ -1007,7 +1007,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/ubuntu",
"identifier": "24.04",
"identifier": "26.04",
"platforms": {
"@@platforms//cpu:x86_64": "@ubuntu_24_04_linux_amd64",
"@@platforms//cpu:arm64": "@ubuntu_24_04_linux_arm64_v8"
@@ -1023,7 +1023,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/alpine",
"identifier": "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
"identifier": "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
"platform": "linux/amd64",
"target_name": "alpine_linux_linux_amd64",
"bazel_tags": []
@@ -1037,7 +1037,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/alpine",
"identifier": "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
"identifier": "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
"platforms": {
"@@platforms//cpu:x86_64": "@alpine_linux_linux_amd64"
},
+1 -3
View File
@@ -5,8 +5,7 @@ set -x
COMMAND_LOG="$(bazel info command_log)"
bazel build \
//src/main/cpp/net/eagle0/shardok:shardok-server \
//src/main/cpp/net/eagle0/shardok:shardok-inspector
//src/main/cpp/net/eagle0/shardok:shardok-server
STATUS="$?"
if [ $STATUS -eq 0 ]
@@ -26,7 +25,6 @@ echo "Setting up Shardok files..."
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server ./shardok/
/bin/mkdir -p ./shardok/shardok-server.runfiles/net_eagle0/src/main/
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server.runfiles/net_eagle0/src/main/resources ./shardok/shardok-server.runfiles/net_eagle0/src/main/
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-inspector ./shardok/
echo "tar & zip shardok files..."
/bin/tar cfh - shardok | /usr/bin/pigz > ./deploy/shardok.tar.gz
-13
View File
@@ -40,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
+9 -6
View File
@@ -13,7 +13,10 @@ set -euxo pipefail
BUILD_TARGET=$1
WORKSPACE=$(pwd)
UNITY_PROJECT="$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
SERVER_DATA="$UNITY_PROJECT/ServerData/$BUILD_TARGET"
UNITY_VERSION=$(grep "m_EditorVersion:" "$UNITY_PROJECT/ProjectSettings/ProjectVersion.txt" | head -1 | sed 's/m_EditorVersion: //')
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
ADDRESSABLES_PREFIX="addressables/$BUILD_TARGET/$UNITY_MAJOR_MINOR"
SERVER_DATA="$UNITY_PROJECT/ServerData/$BUILD_TARGET/$UNITY_MAJOR_MINOR"
# DigitalOcean Spaces configuration (same region as other eagle0 buckets)
DO_ENDPOINT="https://sfo3.digitaloceanspaces.com"
@@ -75,7 +78,7 @@ summarize_pending_uploads() {
sorted_uploads_file=$(mktemp)
trap 'rm -f "$sync_args_file" "$uploads_file" "$sorted_uploads_file"' RETURN
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
--endpoint-url "$DO_ENDPOINT" \
--acl public-read \
--size-only \
@@ -178,7 +181,7 @@ if [ ! -d "$SERVER_DATA" ]; then
fi
echo "Uploading Addressables bundles from $SERVER_DATA"
echo "Target: s3://$DO_BUCKET/addressables/$BUILD_TARGET/"
echo "Target: s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/"
# Configure AWS CLI for DigitalOcean Spaces
export AWS_ACCESS_KEY_ID="$ACCESS_KEY_ID"
@@ -193,13 +196,13 @@ summarize_pending_uploads
#
# --delete removes files in destination that don't exist in source.
# --acl public-read makes files publicly accessible.
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
--endpoint-url "$DO_ENDPOINT" \
--acl public-read \
--size-only \
--delete
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
--endpoint-url "$DO_ENDPOINT" \
--acl public-read \
--exclude "*" \
@@ -208,4 +211,4 @@ aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
--include "*.json"
echo "Addressables upload complete"
echo "Files available at: https://assets.eagle0.net/addressables/$BUILD_TARGET/"
echo "Files available at: https://assets.eagle0.net/$ADDRESSABLES_PREFIX/"
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
bazel_cmd=(bazel)
if [ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]; then
bazel_cmd+=("--output_user_root=${BAZEL_OUTPUT_USER_ROOT}")
fi
if [ -n "${BAZEL_OUTPUT_BASE:-}" ]; then
bazel_cmd+=("--output_base=${BAZEL_OUTPUT_BASE}")
fi
"${bazel_cmd[@]}" build \
--platforms=//:linux_arm64 \
--extra_toolchains=@llvm_toolchain_linux_arm64//:cc-toolchain-aarch64-linux \
"$@" \
//src/main/cpp/net/eagle0/shardok:shardok-server
@@ -14,6 +14,7 @@
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <mutex>
#include "tools/cpp/runfiles/runfiles.h"
@@ -37,7 +38,7 @@ auto rloc(const string& execPath) -> string {
const std::unique_ptr<Runfiles> runfiles(Runfiles::Create(execPath, &error));
if (runfiles == nullptr) {
fprintf(stderr, "Error! %s\n", error.c_str());
std::cerr << "Error! " << error << '\n';
abort();
// error handling
}
@@ -78,9 +79,9 @@ auto FilesystemUtils::MapFilesDirectory() -> string {
void FilesystemUtils::MakeDirectoryIfNecessary(const string& directoryPath) {
if (fs::create_directories(directoryPath))
fprintf(stderr, "Directory %s created\n", directoryPath.c_str());
std::cerr << "Directory " << directoryPath << " created\n";
else
fprintf(stderr, "No new directory created for %s\n", directoryPath.c_str());
std::cerr << "No new directory created for " << directoryPath << '\n';
}
auto FilesystemUtils::SaveFilesDirectory() -> string {
@@ -140,11 +141,11 @@ auto FilesystemUtils::AtomicallySaveToPath(const string& path, const byte_vector
if (ostr.good()) {
const int err = rename(tempPath.c_str(), path.c_str());
if (err == -1) {
fprintf(stderr, "Failed to move file to %s! Errno %d\n", path.c_str(), errno);
std::cerr << "Failed to move file to " << path << "! Errno " << errno << '\n';
return false;
}
} else {
fprintf(stderr, "Failed writing to %s!\n", tempPath.c_str());
std::cerr << "Failed writing to " << tempPath << "!\n";
return false;
}
@@ -80,7 +80,7 @@ void EmitShardokLatencyTrace(
out << "}";
std::scoped_lock lock(LogMutex());
std::cerr << out.str() << std::endl;
std::cerr << out.str() << '\n';
}
ScopedShardokLatencyTrace::ScopedShardokLatencyTrace(
@@ -10,6 +10,7 @@
#include <condition_variable>
#include <functional>
#include <future>
#include <iostream>
#include <memory>
#include <mutex>
#include <queue>
@@ -171,7 +172,7 @@ public:
// Get detailed queue information for debugging
void debug_queue_state() const {
std::unique_lock<std::mutex> lock(const_cast<std::mutex&>(queue_mutex));
printf("ThreadPool: Queue size: %zu\n", tasks.size());
std::cout << "ThreadPool: Queue size: " << tasks.size() << '\n';
if (!tasks.empty()) {
// Create a copy to inspect priorities without modifying queue
auto queue_copy = tasks;
@@ -180,9 +181,9 @@ public:
priorities.push_back(queue_copy.top().priority);
queue_copy.pop();
}
printf("ThreadPool: Priorities in queue: ");
for (int p : priorities) { printf("%d ", p); }
printf("\n");
std::cout << "ThreadPool: Priorities in queue: ";
for (int p : priorities) { std::cout << p << ' '; }
std::cout << '\n';
}
}
@@ -197,4 +198,4 @@ public:
} // namespace eagle0::common
#endif // EAGLE0_THREADPOOL_HPP
#endif // EAGLE0_THREADPOOL_HPP
@@ -9,9 +9,11 @@
#include <fstream>
#include <future>
#include <iomanip>
#include <iostream>
#include <limits>
#include <mutex>
#include <random>
#include <sstream>
#include <stdexcept>
#include <thread>
@@ -862,12 +864,12 @@ auto AbstractMCTSAI::LogSearchResults(
// Log selected command
const std::string selectedDesc =
bestChild->action ? bestChild->action->getDescription() : "Unknown";
printf("MCTS: Selected %s (visit:%d, reward:%.2f, lookahead:%.2f) from %zu options\n",
selectedDesc.c_str(),
bestChild->visitCount,
bestChild->averageReward,
bestChild->lookaheadScore,
rootNode->children.size());
std::ostringstream selectedLine;
selectedLine << std::fixed << std::setprecision(2) << "MCTS: Selected " << selectedDesc
<< " (visit:" << bestChild->visitCount << ", reward:" << bestChild->averageReward
<< ", lookahead:" << bestChild->lookaheadScore << ") from "
<< rootNode->children.size() << " options";
std::cout << selectedLine.str() << '\n';
// Log top 3 actions by visits
std::vector<const MCTSNode*> sortedChildren;
@@ -879,20 +881,17 @@ auto AbstractMCTSAI::LogSearchResults(
// Show all actions if there are <= 10, otherwise top 5
const size_t numToShow = sortedChildren.size() <= 10 ? sortedChildren.size() : 5;
printf("MCTS: Top %zu actions by visits (out of %zu total):\n",
numToShow,
sortedChildren.size());
std::cout << "MCTS: Top " << numToShow << " actions by visits (out of " << sortedChildren.size()
<< " total):\n";
for (size_t i = 0; i < numToShow; ++i) {
const auto* child = sortedChildren[i];
printf(" [%zu] visits:%d avgReward:%.2f immediate:%.2f lookahead:%.2f",
i,
child->visitCount,
child->averageReward,
child->immediateScore,
child->lookaheadScore);
std::ostringstream line;
line << std::fixed << std::setprecision(2) << " [" << i << "] visits:" << child->visitCount
<< " avgReward:" << child->averageReward << " immediate:" << child->immediateScore
<< " lookahead:" << child->lookaheadScore;
// Show the action's own description
if (child->action) { printf(" %s", child->action->getDescription().c_str()); }
if (child->action) { line << ' ' << child->action->getDescription(); }
// Show sequence preview
if (!child->children.empty()) {
@@ -905,16 +904,15 @@ auto AbstractMCTSAI::LogSearchResults(
}
}
if (bestNext && bestNext->action) {
printf(" -> %s", bestNext->action->getDescription().c_str());
line << " -> " << bestNext->action->getDescription();
}
}
printf("\n");
std::cout << line.str() << '\n';
}
printf("MCTS: Tree stats - max depth:%d, total nodes:%d, root visits:%d\n",
result.searchDepth,
result.nodesEvaluated,
rootNode->visitCount);
std::cout << "MCTS: Tree stats - max depth:" << result.searchDepth
<< ", total nodes:" << result.nodesEvaluated
<< ", root visits:" << rootNode->visitCount << '\n';
// Log best sequence from chosen action (following most-visited path)
std::vector<const MCTSNode*> bestSequence;
@@ -940,7 +938,10 @@ auto AbstractMCTSAI::LogSearchResults(
}
if (!bestSequence.empty()) {
printf("MCTS: Best sequence from chosen action (final: %.2f):\n", sequenceScore);
std::ostringstream sequenceLine;
sequenceLine << std::fixed << std::setprecision(2)
<< "MCTS: Best sequence from chosen action (final: " << sequenceScore << "):";
std::cout << sequenceLine.str() << '\n';
int displayedStep = 0;
for (size_t i = 0; i < bestSequence.size(); ++i) {
const auto* node = bestSequence[i];
@@ -949,29 +950,31 @@ auto AbstractMCTSAI::LogSearchResults(
if (i > 0 && node->parent && node->parent->IsChanceNode()) { continue; }
displayedStep++;
printf(" %d.", displayedStep);
if (node->action) { printf(" %s", node->action->getDescription().c_str()); }
std::ostringstream line;
line << " " << displayedStep << ".";
if (node->action) { line << ' ' << node->action->getDescription(); }
// If this is a chance node, display outcome probabilities and scores
if (node->IsChanceNode() && !node->outcomeProbabilities.empty()) {
printf(" [");
line << " [";
for (size_t j = 0; j < node->outcomeProbabilities.size(); ++j) {
if (j > 0) printf(", ");
if (j > 0) { line << ", "; }
const double prob = node->outcomeProbabilities[j] * 100;
// Show lookahead score for each outcome if child exists
if (j < node->children.size() && node->children[j]->visitCount > 0) {
printf("%.0f%%->%.1f", prob, node->children[j]->lookaheadScore);
line << std::fixed << std::setprecision(0) << prob << "%->"
<< std::setprecision(1) << node->children[j]->lookaheadScore;
} else {
printf("%.0f%%->?", prob);
line << std::fixed << std::setprecision(0) << prob << "%->?";
}
}
printf("]");
line << "]";
}
printf(" (visits:%d, immediate:%.2f, lookahead:%.2f)\n",
node->visitCount,
node->immediateScore,
node->lookaheadScore);
line << std::fixed << std::setprecision(2) << " (visits:" << node->visitCount
<< ", immediate:" << node->immediateScore << ", lookahead:" << node->lookaheadScore
<< ")";
std::cout << line.str() << '\n';
// For non-root nodes in the sequence, show what the top alternatives were
// Skip showing alternatives for chance nodes (they have outcome children, not action
@@ -991,17 +994,17 @@ auto AbstractMCTSAI::LogSearchResults(
});
// Show top 3 alternatives at this decision point
printf(" Alternatives at this node (%zu total):\n", siblings.size());
std::cout << " Alternatives at this node (" << siblings.size() << " total):\n";
const size_t topN = std::min(siblings.size(), size_t(3));
for (size_t j = 0; j < topN; ++j) {
const auto* alt = siblings[j];
printf(" [%zu] visits:%d immediate:%.2f lookahead:%.2f",
j,
alt->visitCount,
alt->immediateScore,
alt->lookaheadScore);
if (alt->action) { printf(" %s", alt->action->getDescription().c_str()); }
printf("\n");
std::ostringstream altLine;
altLine << std::fixed << std::setprecision(2) << " [" << j
<< "] visits:" << alt->visitCount
<< " immediate:" << alt->immediateScore
<< " lookahead:" << alt->lookaheadScore;
if (alt->action) { altLine << ' ' << alt->action->getDescription(); }
std::cout << altLine.str() << '\n';
}
}
}
@@ -1013,7 +1016,7 @@ auto AbstractMCTSAI::DumpTreeToFile(const MCTSNode* root, const std::string& fil
std::ofstream out(filepath);
if (!out) {
fprintf(stderr, "Failed to open dump file: %s\n", filepath.c_str());
std::cerr << "Failed to open dump file: " << filepath << '\n';
return;
}
@@ -1042,7 +1045,7 @@ auto AbstractMCTSAI::DumpTreeToFile(const MCTSNode* root, const std::string& fil
out << "\n=== End of Tree Dump ===\n";
out.close();
printf("MCTS: Tree dumped to %s\n", filepath.c_str());
std::cout << "MCTS: Tree dumped to " << filepath << '\n';
}
auto AbstractMCTSAI::DumpNodeRecursive(
@@ -6,8 +6,11 @@
#define EAGLE0_ABSTRACT_MCTSNODE_HPP
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <memory>
#include <sstream>
#include <vector>
#include "MCTSAction.hpp"
@@ -205,13 +208,13 @@ struct MCTSNode {
isMaximizingPlayer ? child->lookaheadScore : -child->lookaheadScore;
const double explorationValue =
explorationConstant * std::sqrt(std::log(visitCount) / child->visitCount);
printf(" UCB: %s lookahead=%.2f expl=%.2f (+%.2f) = %.2f [%s]\n",
isMaximizingPlayer ? "MAX" : "MIN",
child->lookaheadScore,
exploitationValue,
explorationValue,
value,
child->action ? child->action->getDescription().c_str() : "root");
std::ostringstream line;
line << std::fixed << std::setprecision(2)
<< " UCB: " << (isMaximizingPlayer ? "MAX" : "MIN")
<< " lookahead=" << child->lookaheadScore << " expl=" << exploitationValue
<< " (+" << explorationValue << ") = " << value << " ["
<< (child->action ? child->action->getDescription() : "root") << "]";
std::cout << line.str() << '\n';
}
if (value > bestValue) {
@@ -28,24 +28,6 @@ cc_binary(
deps = [":shardok-server-lib"],
)
cc_binary(
name = "shardok-inspector",
srcs = ["shardok_inspector.cpp"],
copts = COPTS,
data = [
"//src/main/resources/net/eagle0/shardok:battalion_types",
"//src/main/resources/net/eagle0/shardok:settings",
"//src/main/resources/net/eagle0/shardok/maps",
],
visibility = ["//visibility:public"],
deps = [
"//src/main/cpp/net/eagle0/common:byte_vector",
"//src/main/cpp/net/eagle0/common:filesystem_utils",
"//src/main/cpp/net/eagle0/common:tsv_parser",
"//src/main/cpp/net/eagle0/shardok/library:engine",
],
)
cc_binary(
name = "hex-map-migrator",
srcs = ["HexMapMigrator.cpp"],
@@ -2,7 +2,6 @@
// Created by Dan Crosby on 3/19/20.
//
#include <cstdio>
#include <iostream>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
@@ -15,7 +14,7 @@ void ModifyMap(HexMapProto& map);
int main(const int argc, char** argv) {
if (argc < 3) {
printf("Usage: %s input_path output_path\n", argv[0]);
std::cerr << "Usage: " << argv[0] << " input_path output_path\n";
return -1;
}
@@ -32,12 +31,13 @@ int main(const int argc, char** argv) {
const std::string outputString = mapProto.SerializeAsString();
if (FilesystemUtils::AtomicallySaveToPath(outputPath, byte_vector(outputString))) return 0;
printf("Failed to save.\n");
std::cerr << "Failed to save.\n";
return -1;
}
void ModifyMap(HexMapProto& map) {
for (int i = 0; i < map.terrain_size(); i++) {
const int terrainCount = map.terrain_size();
for (int i = 0; i < terrainCount; i++) {
const auto existingTerrainType = map.terrain(i).type();
const auto newTerrain =
static_cast<net::eagle0::shardok::common::Terrain_Type>(existingTerrainType + 2);
@@ -4,6 +4,7 @@
#include "MapInfoCalculator.hpp"
#include <cstddef>
#include <cstdlib>
#include <iostream>
@@ -12,44 +13,43 @@
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/MapLoader.hpp"
using namespace shardok;
auto LoadMapFb(const string& mapName) -> Wrapper<HexMap> {
auto LoadMapFb(const std::string& mapName) -> shardok::Wrapper<shardok::HexMap> {
const HexMapProto mapProto = LoadMap(mapName);
flatbuffers::FlatBufferBuilder fbb{};
const auto hexMapOffset = fb::ConvertHexMapProto(fbb, mapProto);
const auto hexMapOffset = shardok::fb::ConvertHexMapProto(fbb, mapProto);
fbb.Finish(hexMapOffset);
return Wrapper<HexMap>(fbb);
return shardok::Wrapper<shardok::HexMap>(fbb);
}
auto CalculateMap(
const string& mapName,
const std::shared_ptr<ActionPointDistancesCache>& apdCache,
const BattalionTypeSPtr& battalionType) -> OneMapInfo {
const std::string& mapName,
const std::shared_ptr<shardok::ActionPointDistancesCache>& apdCache,
const shardok::BattalionTypeSPtr& battalionType) -> OneMapInfo {
auto hexMap = LoadMapFb(mapName);
const auto mapId = ActionPointDistancesCache::GetMapId(hexMap);
const auto mapId = shardok::ActionPointDistancesCache::GetMapId(hexMap);
const CoordsSet criticalTileLocations = GetCriticalTileLocations(hexMap);
const shardok::CoordsSet criticalTileLocations = shardok::GetCriticalTileLocations(hexMap);
OneMapInfo info{
.castleCount = criticalTileLocations.size(),
.name = mapName,
.positionsRequiringCrossing = {}};
for (unsigned int i = 0; i < hexMap->attacker_starting_positions()->size(); i++) {
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()->size() < 1) 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';
abort();
std::abort();
}
int positionsRequiringCrossing = 0;
for (const auto* startingPosition : *positionList->positions()) {
for (const Coords& castlePosition : criticalTileLocations) {
if (!CanReach(
for (const auto* startingPosition : *positions) {
for (const shardok::Coords& castlePosition : criticalTileLocations) {
if (!shardok::CanReach(
*startingPosition,
castlePosition,
hexMap,
@@ -58,10 +58,10 @@ auto CalculateMap(
battalionType)) {
++positionsRequiringCrossing;
break;
};
}
}
}
info.positionsRequiringCrossing[i] = positionsRequiringCrossing;
info.positionsRequiringCrossing[static_cast<int>(i)] = positionsRequiringCrossing;
}
return info;
@@ -12,19 +12,15 @@
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistancesCache.hpp"
using namespace shardok;
using std::map;
using std::string;
struct alignas(64) OneMapInfo {
uint32_t castleCount;
string name;
map<int, int> positionsRequiringCrossing;
std::string name;
std::map<int, int> positionsRequiringCrossing;
};
auto CalculateMap(
const string& mapName,
const std::shared_ptr<ActionPointDistancesCache>& apdCache,
const BattalionTypeSPtr& battalionType) -> OneMapInfo;
const std::string& mapName,
const std::shared_ptr<shardok::ActionPointDistancesCache>& apdCache,
const shardok::BattalionTypeSPtr& battalionType) -> OneMapInfo;
#endif // EAGLE0_MAPINFOCALCULATOR_HPP
@@ -5,36 +5,39 @@
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "MapInfoCalculator.hpp"
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/BattalionTypeRegistrar.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/MapLoader.hpp"
using namespace shardok;
auto main(const int argc, char** argv) -> int {
FilesystemUtils::SetExecPath(argv[0]);
std::ostream* outputStreamPtr = &std::cout;
std::ofstream outputFile{};
if (argc > 1) {
const string outputPath = argv[1];
const std::string outputPath = argv[1];
outputFile.open(outputPath);
outputStreamPtr = &outputFile;
}
std::ostream& outputStream = *outputStreamPtr;
const auto gameSettings = std::make_shared<GameSettings>();
const auto gameSettings = std::make_shared<shardok::GameSettings>();
auto setter = gameSettings->GetSetter();
BattalionTypeRegistrar::RegisterBattalionTypes(setter);
const BattalionTypeSPtr& battalionType = gameSettings->GetGetter().GetBattalionType(
shardok::BattalionTypeRegistrar::RegisterBattalionTypes(setter);
const shardok::BattalionTypeSPtr& battalionType = gameSettings->GetGetter().GetBattalionType(
net::eagle0::shardok::storage::fb::BattalionTypeId_LIGHT_INFANTRY);
vector<OneMapInfo> mapInfos{};
for (const string& mapName : GetMapNames()) {
std::shared_ptr<ActionPointDistancesCache> apdCache =
std::make_shared<ActionPointDistancesCache>();
const std::vector<std::string> mapNames = GetMapNames();
std::vector<OneMapInfo> mapInfos{};
mapInfos.reserve(mapNames.size());
for (const std::string& mapName : mapNames) {
std::shared_ptr<shardok::ActionPointDistancesCache> apdCache =
std::make_shared<shardok::ActionPointDistancesCache>();
mapInfos.push_back(CalculateMap(mapName, apdCache, battalionType));
}
@@ -2,10 +2,17 @@
// Created by Dan Crosby on 3/29/21.
//
#include <algorithm>
#include <array>
#include <climits>
#include <iostream>
#include <memory>
#include <numeric>
#include <mutex>
#include <random>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/common/UnitConversions.hpp"
@@ -14,8 +21,6 @@
#include "src/main/protobuf/net/eagle0/common/random_units.pb.h"
using net::eagle0::common::RandomUnits;
using std::shared_ptr;
using std::vector;
constexpr int THREAD_COUNT = 16;
@@ -28,96 +33,130 @@ std::random_device rd;
std::mt19937 g(rd());
#endif
vector<string> fileNames{};
std::mutex randomMutex;
std::vector<std::string> fileNames{};
using namespace shardok;
ShardokGamesManager* manager;
std::unique_ptr<shardok::ShardokGamesManager> manager;
auto randInt(const int lowInc, const int hiInc) {
std::lock_guard<std::mutex> lock(randomMutex);
return std::uniform_int_distribution(lowInc, hiInc)(g);
}
void runBattle(const vector<shared_ptr<const UnitProto>>& units) {
auto mapName = fileNames[randInt(0, (int)fileNames.size() - 1)];
auto month = randInt(1, 12);
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 p1UnitCount = randInt(1, 20);
vector<UnitProto> p1Units{};
const auto mapName = fileNames[randInt(0, static_cast<int>(fileNames.size()) - 1)];
const auto month = randInt(1, 12);
const auto maxP1UnitCount = std::min<size_t>(20, units.size() - 1);
const auto p1UnitCount = randInt(1, static_cast<int>(maxP1UnitCount));
std::vector<shardok::Unit> p1Units{};
p1Units.reserve(p1UnitCount);
for (int i = 0; i < p1UnitCount; i++) {
auto unit = *units[i];
unit.set_player_id(0);
p1Units.push_back(unit);
auto unit = units[i];
unit.mutate_player_id(0);
p1Units.push_back(std::move(unit));
}
auto p2UnitCount = randInt(1, 20);
vector<UnitProto> p2Units{};
const auto maxP2UnitCount = std::min<size_t>(20, units.size() - p1UnitCount);
const auto p2UnitCount = randInt(1, static_cast<int>(maxP2UnitCount));
std::vector<shardok::Unit> p2Units{};
p2Units.reserve(p2UnitCount);
for (int i = p1UnitCount; i < p1UnitCount + p2UnitCount; i++) {
auto unit = *units[i];
unit.set_player_id(1);
p2Units.push_back(unit);
auto unit = units[i];
unit.mutate_player_id(1);
p2Units.push_back(std::move(unit));
}
printf("map name is %s, %d attacking %d\n", mapName.c_str(), p1UnitCount, p2UnitCount);
std::cout << "map name is " << mapName << ", " << p1UnitCount << " attacking " << p2UnitCount
<< '\n';
vector<VictoryConditionProto> attVictoryConditions{
std::vector<VictoryConditionProto> attVictoryConditions{
VictoryConditionProto::VICTORY_CONDITION_HOLDS_CRITICAL_TILES,
VictoryConditionProto::VICTORY_CONDITION_LAST_PLAYER_STANDING};
vector<VictoryConditionProto> defVictoryConditions{
std::vector<VictoryConditionProto> defVictoryConditions{
VictoryConditionProto::VICTORY_CONDITION_LAST_PLAYER_STANDING,
VictoryConditionProto::VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS};
auto p1 = PlayerInfoWithUnits(0, true, 5, false, 10000, attVictoryConditions, {}, p1Units);
auto p2 = PlayerInfoWithUnits(1, false, 7, false, 10000, defVictoryConditions, {}, p2Units);
std::vector<shardok::PlayerInfoWithUnits> playerInfos;
playerInfos.reserve(2);
playerInfos.emplace_back(
0,
true,
5,
false,
10000,
attVictoryConditions,
std::vector<shardok::PlayerId>{},
p1Units,
false);
playerInfos.emplace_back(
1,
true,
7,
true,
10000,
defVictoryConditions,
std::vector<shardok::PlayerId>{},
p2Units,
false);
string gameId = std::to_string(randInt(INT_MIN, INT_MAX));
const std::string gameId = std::to_string(randInt(INT_MIN, INT_MAX));
manager->LockedCreateSpecifiedGame(gameId, {p1, p2}, mapName, "", month, "");
manager->UnlockedCreateSpecifiedGame(
gameId,
playerInfos,
mapName,
"",
month,
TutorialBattleConfigProto{},
"");
}
auto LoadUnits() -> vector<shared_ptr<const UnitProto>> {
auto LoadUnits() -> std::vector<shardok::Unit> {
const auto bytes = byte_vector::FromPath("/tmp/random_heroes.bin");
RandomUnits commonUnits;
commonUnits.ParseFromArray(bytes.data(), (int)bytes.size());
printf("Loaded %d units\n", commonUnits.units_size());
if (!commonUnits.ParseFromArray(bytes.data(), static_cast<int>(bytes.size()))) {
throw std::runtime_error("Failed to parse /tmp/random_heroes.bin");
}
std::cout << "Loaded " << commonUnits.units_size() << " units\n";
vector<shared_ptr<const UnitProto>> units{};
std::vector<shardok::Unit> units{};
units.reserve(commonUnits.units_size());
for (const auto& commonUnit : commonUnits.units()) {
// FIXME: assumes 2 players
units.push_back(std::make_shared<const UnitProto>(ConvertUnit(commonUnit, 0, {0, 1})));
units.push_back(shardok::ConvertUnit(commonUnit, 0, {0, 1}));
}
std::shuffle(units.begin(), units.end(), g);
return units;
}
[[noreturn]] void runBattlesThread(const vector<shared_ptr<const UnitProto>>& units) {
[[noreturn]] void runBattlesThread(const std::vector<shardok::Unit>& units) {
while (true) { runBattle(units); }
}
auto main(int argc, char** argv) -> int {
auto main(int /*argc*/, char** argv) -> int {
#if FIXED_SEED
std::srand(4564564);
g.seed(378473);
#else
std::srand(std::time(nullptr));
#endif
FilesystemUtils::SetExecPath(argv[0]);
auto randomUnits = LoadUnits();
printf("Map file directory is %s\n", FilesystemUtils::MapFilesDirectory().c_str());
std::cout << "Map file directory is " << FilesystemUtils::MapFilesDirectory() << '\n';
fileNames = FilesystemUtils::FilesInDirectory(FilesystemUtils::MapFilesDirectory(), ".e0m");
for (auto& fileName : fileNames) { fileName.resize(fileName.size() - 4); }
manager = new ShardokGamesManager(nullptr, std::vector<std::string>());
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);
@@ -16,9 +16,6 @@ namespace shardok {
constexpr double kOverpowerRatio = 2.0;
constexpr double kBraveWaterCostMultiplier = 1.2;
using std::pair;
using std::shared_ptr;
DIST_T NormalizedCostWhenBraving(const DIST_T cost) {
if (cost >= static_cast<double>(ActionPointDistances::IMPOSSIBLE) / kBraveWaterCostMultiplier)
return ActionPointDistances::IMPOSSIBLE;
@@ -156,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());
@@ -235,4 +233,4 @@ auto GenerateTargetPriorities(
return allTargetsUnitsAndDistances;
}
} // namespace shardok
} // namespace shardok
@@ -20,7 +20,6 @@ namespace shardok {
using GameState = net::eagle0::shardok::storage::fb::GameState;
using Unit = net::eagle0::shardok::storage::fb::Unit;
using net::eagle0::shardok::storage::fb::PlayerInfo;
using std::vector;
using BattalionTypeGetter = std::function<BattalionTypeSPtr(BattalionTypeId)>;
@@ -33,9 +32,9 @@ struct TargetAndAttackLocations {
struct TargetPriorityList {
UnitId attackingUnitId;
vector<TargetAndAttackLocations> priorityOrder;
std::vector<TargetAndAttackLocations> priorityOrder;
TargetPriorityList(const UnitId uid, const vector<TargetAndAttackLocations>& po)
TargetPriorityList(const UnitId uid, const std::vector<TargetAndAttackLocations>& po)
: attackingUnitId(uid),
priorityOrder(po) {}
};
@@ -64,16 +63,16 @@ auto EffectiveDistance(
// Chooses a list of targets in priority order for each unit.
auto GenerateTargetPriorities(
const vector<const Unit*>& occupants,
const std::vector<const Unit*>& occupants,
const HexMap* map,
const CoordsSet& targets,
PlayerId attackerId,
const vector<const Unit*>& remainingUnits,
const std::vector<const Unit*>& remainingUnits,
const APDCache& apdCache,
const ALCache& alCache,
const BattalionTypeGetter& battalionTypeGetter,
ActionPoints braveWaterCost,
bool isLateGame = false) -> vector<TargetPriorityList>;
bool isLateGame = false) -> std::vector<TargetPriorityList>;
} // namespace shardok
@@ -17,8 +17,6 @@
namespace shardok {
using Unit = net::eagle0::shardok::storage::fb::Unit;
using std::shared_ptr;
using std::vector;
class alignas(128) AttackLocations {
private:
@@ -165,7 +163,7 @@ public:
auto CachedLocations(const Coords& singleTarget, bool lateGame) -> const AttackLocations&;
auto CachedLocations(const CoordsSet& targets, bool lateGame) -> AttackLocations;
auto CachedLocations(const vector<const Unit*>& enemies, bool lateGame) -> AttackLocations;
auto CachedLocations(const std::vector<const Unit*>& enemies, bool lateGame) -> AttackLocations;
};
using ALCache = std::unique_ptr<AttackLocationsCache>;
@@ -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
@@ -8,6 +8,8 @@
#include <chrono>
#include <cmath>
#include <future>
#include <iomanip>
#include <iostream>
#include <limits>
#include "AICommandFilter.hpp"
@@ -309,25 +311,25 @@ auto AICommandEvaluator::FindBestCommand(
const auto filteredCommandCount = filteredIndices.size();
const int currentRound = gameState->current_round();
printf("AI_COMMAND_COUNT: Round %d, Player %d, Defender %d, MinDist %.1f, Commands %zu -> "
"%zu "
"(%.1f%% filtered)\n",
currentRound,
static_cast<int>(pid),
isDefender ? 1 : 0,
minDistToEnemies,
allCommandCount,
filteredCommandCount,
100.0 * (allCommandCount - filteredCommandCount) / allCommandCount);
const auto flags = std::cout.flags();
const auto precision = std::cout.precision();
std::cout << "AI_COMMAND_COUNT: Round " << currentRound << ", Player "
<< static_cast<int>(pid) << ", Defender " << (isDefender ? 1 : 0) << ", MinDist "
<< std::fixed << std::setprecision(1) << minDistToEnemies << ", Commands "
<< allCommandCount << " -> " << filteredCommandCount << " ("
<< 100.0 * (allCommandCount - filteredCommandCount) / allCommandCount
<< "% filtered)\n";
std::cout.flags(flags);
std::cout.precision(precision);
}
const auto commandCount = filteredIndices.size();
// Structure to hold all command evaluation data
struct CommandEvaluation {
size_t index;
CommandType type;
ScoreValue immediateScore;
size_t index{};
CommandType type{};
ScoreValue immediateScore{};
bool immediateCompleted = true;
std::vector<std::future<EvaluationResult>> lookaheadFutures;
};
@@ -420,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 =
@@ -430,7 +433,7 @@ auto AICommandEvaluator::FindBestCommand(
originalIndex,
remainingLookahead,
maxRepeatCount,
std::make_shared<SequenceRandomGenerator>(sequence),
std::make_shared<SequenceRandomGenerator>(std::move(sequence)),
guessedEngine,
attackerStrategy,
allCastleCoords,
@@ -509,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,
@@ -528,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;
@@ -573,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(
@@ -13,14 +13,11 @@
namespace shardok {
using std::shared_ptr;
using std::vector;
auto DefenderDistanceBuf(
const Coords &defenderLocation,
const HexMap *hexMap,
const MapId &mapId,
const vector<const Unit *> &attackerUnits,
const std::vector<const Unit *> &attackerUnits,
const APDCache &apdCache,
const ALCache &alCache,
const BattalionTypeGetter &battalionTypeGetter,
@@ -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; }
@@ -8,12 +8,29 @@
#include "AIFleeDecisionCalculator.hpp"
#include <iomanip>
#include <iostream>
#include "src/main/cpp/net/eagle0/shardok/ai/AIScoreUtilities.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIUnitScoreCalculator.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
namespace shardok {
namespace {
void WritePercent(std::ostream& output, const double value) {
const auto flags = output.flags();
const auto precision = output.precision();
output << std::fixed << std::setprecision(1) << value;
output.flags(flags);
output.precision(precision);
}
} // namespace
auto AIFleeDecisionCalculator::GetFleeCommandIndex(
const CommandList::const_iterator& fleeCommand,
const CommandListSPtr& availableCommands) -> size_t {
@@ -144,15 +161,14 @@ auto AIFleeDecisionCalculator::EvaluateFleeVsFight(
const int fleeSuccessChance = (*fleeCommand)->GetOddsPercentile();
if (enableDebugLogging) {
printf("AI FinalRound: Evaluating flee (odds=%d%%)...\n", fleeSuccessChance);
std::cout << "AI FinalRound: Evaluating flee (odds=" << fleeSuccessChance << "%)...\n";
}
// Check if flee odds are good enough to attempt
if (fleeSuccessChance >= minimumFleeOddsThreshold) {
if (enableDebugLogging) {
printf("AI FinalRound: Good flee odds (%d%% >= %d%%), choosing flee\n",
fleeSuccessChance,
minimumFleeOddsThreshold);
std::cout << "AI FinalRound: Good flee odds (" << fleeSuccessChance
<< "% >= " << minimumFleeOddsThreshold << "%), choosing flee\n";
}
return FleeDecision{
true,
@@ -166,9 +182,9 @@ auto AIFleeDecisionCalculator::EvaluateFleeVsFight(
// If combat situation is hopeless, even bad flee odds are better than certain death
if (combatWinChance <= 0.05 && fleeSuccessChance >= desperateFleeThreshold) {
if (enableDebugLogging) {
printf("AI FinalRound: Combat hopeless (%.1f%%), desperate flee attempt (%d%%)\n",
combatWinChance * 100,
fleeSuccessChance);
std::cout << "AI FinalRound: Combat hopeless (";
WritePercent(std::cout, combatWinChance * 100);
std::cout << "%), desperate flee attempt (" << fleeSuccessChance << "%)\n";
}
return FleeDecision{
true,
@@ -188,20 +204,21 @@ auto AIFleeDecisionCalculator::EvaluateFleeVsFight(
const double adjustedCombatThreshold = combatWinChance * FLEE_VS_COMBAT_MARGIN;
if (enableDebugLogging) {
printf("AI FinalRound: Flee=%d%%, Combat=%.1f%%, Threshold=%.1f%% -> ",
fleeSuccessChance,
combatWinChance * 100,
adjustedCombatThreshold * 100);
std::cout << "AI FinalRound: Flee=" << fleeSuccessChance << "%, Combat=";
WritePercent(std::cout, combatWinChance * 100);
std::cout << "%, Threshold=";
WritePercent(std::cout, adjustedCombatThreshold * 100);
std::cout << "% -> ";
}
if (fleeChance > adjustedCombatThreshold) {
if (enableDebugLogging) { printf("FLEE (better odds)\n"); }
if (enableDebugLogging) { std::cout << "FLEE (better odds)\n"; }
return FleeDecision{
true,
GetFleeCommandIndex(fleeCommand, availableCommands),
"Flee has better expected outcome"};
} else {
if (enableDebugLogging) { printf("FIGHT (better expected outcome)\n"); }
if (enableDebugLogging) { std::cout << "FIGHT (better expected outcome)\n"; }
// Return 0 to indicate we should use standard command selection
return FleeDecision{
false,
@@ -5,9 +5,6 @@
#ifndef EAGLE0_SHARDOK_AI_AI_MINIMUM_DISTANCE_AND_TARGET_HPP
#define EAGLE0_SHARDOK_AI_AI_MINIMUM_DISTANCE_AND_TARGET_HPP
#include <utility>
#include <vector>
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistances.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/coords.pb.h"
@@ -15,7 +12,6 @@
namespace shardok {
using net::eagle0::shardok::storage::fb::Coords;
using std::shared_ptr;
struct CoordsAndDistance {
Coords coords;
@@ -16,8 +16,6 @@ namespace shardok {
using ScoreValue = double;
using std::vector;
using GameState = net::eagle0::shardok::storage::fb::GameState;
using PlayerInfo = net::eagle0::shardok::storage::fb::PlayerInfo;
using Unit = net::eagle0::shardok::storage::fb::Unit;
@@ -116,15 +116,6 @@ auto CalculateTimeBudget(
const auto clampedBudgetMs = std::clamp(budgetMs, 200.0, maxBudgetMs);
const auto remainingBudget = std::chrono::milliseconds(static_cast<int64_t>(clampedBudgetMs));
// TEMPORARY DEBUG OUTPUT
printf("[DEBUG CalculateTimeBudget] numCommands=%zu, msPerCommand=%.2f, budgetMs=%.2f, "
"clampedBudgetMs=%.2f, isClose=%d\n",
numCommands,
msPerCommand,
budgetMs,
clampedBudgetMs,
isClose);
// Get minimum depth requirement
const size_t minDepth = settingsGetter.Backing().min_lookahead_turns();
@@ -134,4 +125,4 @@ auto CalculateTimeBudget(
.isCloseToEnemy = isClose};
}
} // namespace shardok
} // namespace shardok
@@ -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(
@@ -14,9 +14,6 @@
namespace shardok {
using net::eagle0::shardok::storage::fb::Unit;
using std::shared_ptr;
using std::vector;
using ScoreValue = double;
class AttackLocations;
@@ -29,8 +26,8 @@ auto GetRangedAttackBonus(
const Terrain *terrain,
const AttackLocations &attackLocations,
int roundsRemaining,
const vector<const Unit *> &attackerUnits,
const vector<const Unit *> &defenderUnits,
const std::vector<const Unit *> &attackerUnits,
const std::vector<const Unit *> &defenderUnits,
int meteorRange,
double minVigorToCast,
ActionPoints archeryActionPointCost = 0) -> double;
@@ -38,10 +35,10 @@ auto GetRangedAttackBonus(
auto UnitValue(
const Unit *unit,
bool isAttacker,
const vector<const Unit *> &attackerUnits,
const std::vector<const Unit *> &attackerUnits,
bool attackerWantsCastles,
bool includeCastleBonus,
const vector<const Unit *> &defenderUnits,
const std::vector<const Unit *> &defenderUnits,
const HexMap *map,
int roundsRemaining,
const AttackLocations &locationsThisSideCanAttackFrom,
@@ -5,6 +5,8 @@
#include "IterativeDeepeningAI.hpp"
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <limits>
#include <numeric>
#include <utility>
@@ -21,6 +23,20 @@ namespace shardok {
constexpr bool kDebugIterativeDeepeningTimings = true;
namespace {
void WriteFixed(std::ostream& output, const double value, const int precision) {
const auto flags = output.flags();
const auto originalPrecision = output.precision();
output << std::fixed << std::setprecision(precision) << value;
output.flags(flags);
output.precision(originalPrecision);
}
} // namespace
IterativeDeepeningAI::IterativeDeepeningAI(
const PlayerId playerId,
const bool isDefender,
@@ -55,7 +71,7 @@ auto IterativeDeepeningAI::IterativeSearch(
// g_transpositionTable.clear(); // Uncomment to test without cross-search caching
if (commands->empty()) {
if (kDebugIterativeDeepeningTimings) {
printf("ID AI: Commands are empty, returning early\n");
std::cout << "ID AI: Commands are empty, returning early\n";
}
result.searchCompleted = true;
return result;
@@ -169,21 +185,21 @@ auto IterativeDeepeningAI::IterativeSearch(
// Log if best command changed from previous depth
if (currentDepth > 1 && currentBestCommand != previousBestCommand) {
if (kDebugIterativeDeepeningTimings) {
printf("ID AI: Best command changed at depth %lu:\n", currentDepth);
printf(" Depth %lu best: command %zu (score %.2f) - type: %s\n",
currentDepth - 1,
previousBestCommand,
scoresByDepth[previousBestCommand][currentDepth - 1],
net::eagle0::shardok::common::CommandType_Name(
(*commands)[previousBestCommand]->GetCommandType())
.c_str());
printf(" Depth %lu best: command %zu (score %.2f) - type: %s\n",
currentDepth,
currentBestCommand,
currentBestScore,
net::eagle0::shardok::common::CommandType_Name(
(*commands)[currentBestCommand]->GetCommandType())
.c_str());
std::cout << "ID AI: Best command changed at depth " << currentDepth << ":\n"
<< " Depth " << currentDepth - 1 << " best: command "
<< previousBestCommand << " (score ";
WriteFixed(std::cout, scoresByDepth[previousBestCommand][currentDepth - 1], 2);
std::cout << ") - type: "
<< net::eagle0::shardok::common::CommandType_Name(
(*commands)[previousBestCommand]->GetCommandType())
<< '\n'
<< " Depth " << currentDepth << " best: command "
<< currentBestCommand << " (score ";
WriteFixed(std::cout, currentBestScore, 2);
std::cout << ") - type: "
<< net::eagle0::shardok::common::CommandType_Name(
(*commands)[currentBestCommand]->GetCommandType())
<< '\n';
}
}
@@ -239,9 +255,9 @@ auto IterativeDeepeningAI::IterativeSearch(
static_cast<double>(initialBudgetMs.count());
if (budgetUsedPercent > 0.5) {
printf("ID AI: Stopping after depth %lu - used %.1f%% of time budget\n",
currentDepth,
budgetUsedPercent * 100);
std::cout << "ID AI: Stopping after depth " << currentDepth << " - used ";
WriteFixed(std::cout, budgetUsedPercent * 100, 1);
std::cout << "% of time budget\n";
completionReason = EvaluationCompletionReason::NOT_ENOUGH_TIME_TO_CONTINUE;
break;
}
@@ -271,9 +287,9 @@ auto IterativeDeepeningAI::IterativeSearch(
// Validation: if completion reason is RAN_OUT_OF_COMMANDS, evaluation should be 100%
if (completionReason == EvaluationCompletionReason::RAN_OUT_OF_COMMANDS &&
result.commandCountEvaluated < result.availableCommandCount) {
printf("ERROR: Completion reason RAN_OUT_OF_COMMANDS but evaluation %lu/%zu < 100%%\n",
result.commandCountEvaluated,
result.availableCommandCount);
std::cerr << "ERROR: Completion reason RAN_OUT_OF_COMMANDS but evaluation "
<< result.commandCountEvaluated << '/' << result.availableCommandCount
<< " < 100%\n";
}
// Print TranspositionTable statistics
@@ -15,9 +15,12 @@
#include <chrono>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#endif
#include <iomanip>
#include <iostream>
#include <set>
#include <unordered_map>
@@ -174,25 +177,20 @@ auto ShardokAIClient::StandardChooseCommandIndex(
// Verify that the AI's guessed state produces the same available commands as reality
if (commandCount != realAvailableCommands->size()) {
fprintf(stderr,
"AI command count mismatch: guessed=%zu real=%zu player=%d round=%d\n",
commandCount,
realAvailableCommands->size(),
playerId,
guessedState->current_round());
std::cerr << "AI command count mismatch: guessed=" << commandCount
<< " real=" << realAvailableCommands->size() << " player=" << playerId
<< " round=" << guessedState->current_round() << "\n";
const auto maxDump = std::max(commandCount, realAvailableCommands->size());
for (size_t i = 0; i < maxDump; i++) {
const auto *realName = i < realAvailableCommands->size()
? net::eagle0::shardok::common::CommandType_Name(
(*realAvailableCommands)[i]->GetCommandType())
.c_str()
: "(none)";
const auto *guessedName = i < commandCount
? net::eagle0::shardok::common::CommandType_Name(
(*guessedCommands)[i]->GetCommandType())
.c_str()
: "(none)";
const auto realName = i < realAvailableCommands->size()
? net::eagle0::shardok::common::CommandType_Name(
(*realAvailableCommands)[i]->GetCommandType())
: "(none)";
const auto guessedName = i < commandCount
? net::eagle0::shardok::common::CommandType_Name(
(*guessedCommands)[i]->GetCommandType())
: "(none)";
const int realActor = i < realAvailableCommands->size()
? (*realAvailableCommands)[i]->GetActorUnitId()
: -1;
@@ -206,17 +204,10 @@ auto ShardokAIClient::StandardChooseCommandIndex(
: -1;
const int guessedRow = i < commandCount ? (*guessedCommands)[i]->GetTargetRow() : -1;
const int guessedCol = i < commandCount ? (*guessedCommands)[i]->GetTargetColumn() : -1;
fprintf(stderr,
" [%zu] real: %s unit=%d (%d,%d) guessed: %s unit=%d (%d,%d)\n",
i,
realName,
realActor,
realRow,
realCol,
guessedName,
guessedActor,
guessedRow,
guessedCol);
std::cerr << " [" << i << "] real: " << realName << " unit=" << realActor << " ("
<< realRow << "," << realCol << ") guessed: " << guessedName
<< " unit=" << guessedActor << " (" << guessedRow << "," << guessedCol
<< ")\n";
}
// Find positions in real commands that are missing from guessed commands
@@ -238,45 +229,34 @@ auto ShardokAIClient::StandardChooseCommandIndex(
for (const auto &[unitId, positions] : realPositions) {
for (const auto &[row, col] : positions) {
if (!guessedPositions[unitId].contains({row, col})) {
fprintf(stderr,
" Missing from guessed: unit=%d pos=(%d,%d)",
unitId,
row,
col);
std::cerr << " Missing from guessed: unit=" << unitId << " pos=(" << row << ","
<< col << ")";
const Coords missingCoords(row, col);
const auto *occupant = guessedState.GetOccupant(missingCoords);
if (occupant) {
fprintf(stderr,
" -> guessed occupant: unit_id=%d player=%d status=%d "
"loc=(%d,%d) hidden=%d battalion_size=%d\n",
occupant->unit_id(),
occupant->player_id(),
static_cast<int>(occupant->status()),
occupant->location().row(),
occupant->location().column(),
occupant->hidden(),
occupant->battalion().size());
std::cerr << " -> guessed occupant: unit_id=" << occupant->unit_id()
<< " player=" << occupant->player_id()
<< " status=" << static_cast<int>(occupant->status()) << " loc=("
<< occupant->location().row() << ","
<< occupant->location().column()
<< ") hidden=" << occupant->hidden()
<< " battalion_size=" << occupant->battalion().size() << "\n";
} else {
fprintf(stderr, " -> no occupant in guessed state\n");
std::cerr << " -> no occupant in guessed state\n";
}
}
}
}
// Dump all guessed state units for full picture
fprintf(stderr, " Guessed state units (%u total):\n", guessedState->units()->size());
std::cerr << " Guessed state units (" << guessedState->units()->size() << " total):\n";
for (size_t i = 0; i < guessedState->units()->size(); i++) {
const auto *u = guessedState->units()->Get(static_cast<unsigned int>(i));
fprintf(stderr,
" unit_id=%d player=%d status=%d loc=(%d,%d) hidden=%d "
"starting_pos_idx=%d\n",
u->unit_id(),
u->player_id(),
static_cast<int>(u->status()),
u->location().row(),
u->location().column(),
u->hidden(),
u->starting_position_index());
std::cerr << " unit_id=" << u->unit_id() << " player=" << u->player_id()
<< " status=" << static_cast<int>(u->status()) << " loc=("
<< u->location().row() << "," << u->location().column()
<< ") hidden=" << u->hidden()
<< " starting_pos_idx=" << u->starting_position_index() << "\n";
}
throw ShardokInternalErrorException(
@@ -356,9 +336,9 @@ auto ShardokAIClient::StandardChooseCommandIndex(
// Write the flatbuffer game state to file using SaveTo method
if (guessedState.SaveTo(statePath)) {
printf("Game state dumped to: %s\n", statePath.c_str());
std::cout << "Game state dumped to: " << statePath << '\n';
} else {
printf("Failed to dump game state to: %s\n", statePath.c_str());
std::cout << "Failed to dump game state to: " << statePath << '\n';
}
#endif // ENABLE_MCTS_DEBUG_DUMP
@@ -396,19 +376,17 @@ auto ShardokAIClient::StandardChooseCommandIndex(
if constexpr (kPerformanceLogging) {
if (result.commandCountEvaluated < result.availableCommandCount) {
printf("ID AI: Depth %d - evaluated %lu/%zu commands\n",
result.depthAchieved,
result.commandCountEvaluated,
result.availableCommandCount);
std::cout << "ID AI: Depth " << result.depthAchieved << " - evaluated "
<< result.commandCountEvaluated << '/' << result.availableCommandCount
<< " commands\n";
}
const auto chosenCommandType =
(*realAvailableCommands)[result.chosenIndex]->GetCommandType();
printf("ID AI: Search complete - achieved depth %d for best command %zu (%s)\n",
result.depthAchieved,
result.chosenIndex,
net::eagle0::shardok::common::CommandType_Name(chosenCommandType).c_str());
std::cout << "ID AI: Search complete - achieved depth " << result.depthAchieved
<< " for best command " << result.chosenIndex << " ("
<< net::eagle0::shardok::common::CommandType_Name(chosenCommandType) << ")\n";
fflush(stdout);
std::cout << std::flush;
}
return result;
@@ -510,7 +488,7 @@ auto ShardokAIClient::ChooseCommandIndex(
totalChoices++;
if (totalChoices % 1000 == 0) {
printf("TYPES CHOSEN:\n");
std::cout << "TYPES CHOSEN:\n";
vector<std::pair<int, CommandType>> choices{};
for (int i = 0; i <= net::eagle0::shardok::common::CommandType_MAX; i++) {
if (typeChosenCount[i] != 0) {
@@ -521,9 +499,9 @@ auto ShardokAIClient::ChooseCommandIndex(
std::ranges::sort(choices);
std::ranges::reverse(choices);
for (const auto &[index, choice] : choices) {
printf("%5d %s\n", index, CommandType_Name(choice).c_str());
std::cout << std::setw(5) << index << ' ' << CommandType_Name(choice) << '\n';
}
printf("\n\n");
std::cout << "\n\n";
}
return results;
@@ -533,7 +511,7 @@ auto ShardokAIClient::ChooseCommandIndex(const ShardokEngine &engine) const
-> CommandChoiceResults {
if (const auto &availableCommands = engine.GetAvailableCommandsForAIPlayer(playerId);
availableCommands->empty()) {
printf("no commands for player %d\n", playerId);
std::cout << "no commands for player " << playerId << '\n';
throw ShardokInternalErrorException(
"Asked to choose a command, but there are none available");
} else {
@@ -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(
@@ -5,8 +5,6 @@
#ifndef EAGLE0_SHARDOK_AI_SCORE_AI_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_SCORE_AI_SCORE_CALCULATOR_HPP
#include <future>
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCTypes.h"
@@ -16,8 +14,6 @@
namespace shardok {
using shardok::PlayerId;
using std::future;
using std::vector;
using ScoreValue = double;
@@ -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;
@@ -20,7 +20,6 @@ namespace shardok {
using GameState = net::eagle0::shardok::storage::fb::GameState;
using net::eagle0::shardok::storage::fb::PlayerInfo;
using std::vector;
using ScoreValue = double;
@@ -8,6 +8,9 @@
#include <atomic>
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <unordered_map>
#include "private/AIScoreCalculatorSharedUtilities.hpp"
@@ -57,11 +60,12 @@ struct AttackerScorePerformanceLogger {
if (callCount.load() % LOG_INTERVAL == 0) {
double intervalAvg = intervalTime.load() / LOG_INTERVAL;
double overallAvg = totalTime.load() / callCount.load();
printf("AttackerScoreForState: %d calls, last %d avg: %.1f µs, overall avg: %.1f µs\n",
callCount.load(),
LOG_INTERVAL,
intervalAvg * 1000000.0,
overallAvg * 1000000.0);
std::ostringstream message;
message << std::fixed << std::setprecision(1)
<< "AttackerScoreForState: " << callCount.load() << " calls, last "
<< LOG_INTERVAL << " avg: " << intervalAvg * 1000000.0
<< " µs, overall avg: " << overallAvg * 1000000.0 << " µs\n";
std::cout << message.str();
intervalTime.store(0.0); // Reset for next interval
}
}
@@ -140,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,13 +355,15 @@ 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
for (int i = 0; i < config_.attacker().units_size(); ++i) {
const int attackerUnitCount = config_.attacker().units_size();
for (int i = 0; i < attackerUnitCount; ++i) {
const auto& unitConfig = config_.attacker().units(i);
net::eagle0::shardok::storage::fb::Unit unit{};
@@ -449,12 +453,13 @@ 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
int defenderUnitIdStart = config_.attacker().units_size();
for (int i = 0; i < config_.defender().units_size(); ++i) {
const int defenderUnitIdStart = attackerUnitCount;
const int defenderUnitCount = config_.defender().units_size();
for (int i = 0; i < defenderUnitCount; ++i) {
const auto& unitConfig = config_.defender().units(i);
net::eagle0::shardok::storage::fb::Unit unit{};
@@ -544,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
@@ -678,8 +683,9 @@ BattleResult AiBattleSimulator::RunConfiguredSetupPhase(ShardokEngine& engine) {
playerConfig,
int unitIdOffset) {
std::vector<UnitPlacementInfo> placements;
placements.reserve(playerConfig.units_size());
for (int i = 0; i < playerConfig.units_size(); ++i) {
const int unitCount = playerConfig.units_size();
placements.reserve(unitCount);
for (int i = 0; i < unitCount; ++i) {
const auto& unitConfig = playerConfig.units(i);
if (!unitConfig.has_fixed_position()) {
throw std::invalid_argument(
@@ -911,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"
@@ -18,7 +19,15 @@
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/command_type.pb.h"
using namespace shardok;
using shardok::AIAlgorithmType;
using shardok::AIPerformanceMetrics;
using shardok::EvaluationCompletionReason;
using shardok::PerformanceTestConfig;
using shardok::PerformanceTestGameStateBuilder;
using shardok::PlayerId;
using shardok::ShardokAIClient;
using shardok::ShardokEngine;
namespace ai_testing_common = shardok::ai_testing_common;
namespace {
@@ -39,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) {
@@ -55,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=")) {
@@ -66,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.");
}
}
@@ -88,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";
@@ -155,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
@@ -300,4 +311,4 @@ int main(int argc, char* argv[]) {
}
return 0;
}
}
@@ -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
@@ -5,13 +5,15 @@
#ifndef EAGLE0_SHARDOK_CONTROLLER_GAME_UPDATE_RECEIVER_HPP
#define EAGLE0_SHARDOK_CONTROLLER_GAME_UPDATE_RECEIVER_HPP
#include <atomic>
#include <vector>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-redundant-constexpr-static-def"
#include "src/main/protobuf/net/eagle0/shardok/api/action_result_view.pb.h"
#pragma GCC diagnostic pop
namespace shardok {
using std::vector;
class GameUpdateReceiver {
private:
@@ -25,7 +27,7 @@ public:
virtual ~GameUpdateReceiver() = default;
virtual void ReceiveActionResult(const net::eagle0::shardok::api::ActionResultView& result) = 0;
virtual void ReceiveActionResults(
const vector<net::eagle0::shardok::api::ActionResultView>& results) = 0;
const std::vector<net::eagle0::shardok::api::ActionResultView>& results) = 0;
[[nodiscard]] auto GetIdentifier() const -> long { return identifier; }
};
@@ -24,12 +24,6 @@
namespace shardok {
using net::eagle0::common::ScopedShardokLatencyTrace;
using std::condition_variable;
using std::mutex;
using std::scoped_lock;
using std::string;
using std::unique_lock;
using std::weak_ptr;
using net::eagle0::shardok::common::GameStatus;
@@ -40,7 +34,7 @@ private:
PlayerId shardokPlayerId;
int eagleFactionId;
string message;
std::string message;
public:
MismatchedPlayerIdException(const PlayerId shardokPid, const int eagleFid)
@@ -57,7 +51,7 @@ class MissingPlayerIdException : public std::exception {
private:
PlayerId shardokPlayerId;
string message;
std::string message;
public:
MissingPlayerIdException(const PlayerId shardokPid) : shardokPlayerId(shardokPid) {
@@ -67,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;
@@ -79,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(); });
@@ -108,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;
});
@@ -121,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;
@@ -129,7 +124,7 @@ void ShardokGameController::DoAIThread() {
size_t expectedHistoryCount;
{
unique_lock lk(masterLock);
std::unique_lock lk(masterLock);
if (engine->GameIsOver()) {
aiThreadKeepGoing = false;
@@ -173,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) {
@@ -229,7 +224,7 @@ void ShardokGameController::DoAIThread() {
ShardokGameController::~ShardokGameController() {
{
unique_lock lk(masterLock);
std::unique_lock lk(masterLock);
aiThreadKeepGoing = false;
aiCondition.notify_one();
}
@@ -237,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;
@@ -257,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,
@@ -265,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);
@@ -304,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,
@@ -313,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);
@@ -357,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();
}
@@ -369,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();
@@ -401,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",
@@ -412,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(
@@ -431,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)};
@@ -484,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()) {
@@ -505,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(),
@@ -557,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,8 @@
#include "AvailableCommandsFactory.hpp"
#include <algorithm>
#include <iterator>
#include <memory>
#include <ranges>
#include "src/main/cpp/net/eagle0/shardok/library/FireUtils.hpp"
@@ -21,8 +23,6 @@
#include "src/main/cpp/net/eagle0/shardok/library/view_filters/GameStateFilter.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/view_filters/GameStateGuesser.hpp"
using std::make_shared;
namespace shardok {
class AvailableCommandsFactoryImpl : public AvailableCommandsFactory {
@@ -33,25 +33,25 @@ private:
if (pid == UNCONTROLLED_PLAYER_ID) return false;
const auto &player = std::find_if(
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 vector<shared_ptr<const CommandFactory>> commandFactories;
const std::vector<std::shared_ptr<const CommandFactory>> commandFactories;
const PlayerSetupCommandFactory playerSetupCommandFactory;
// AvailableCommands helpers for a single unit
@@ -97,7 +97,7 @@ auto AvailableCommandsFactoryImpl::GetPlayerSetupCommands(
gameState,
reinforcementPositions);
return make_shared<CommandList>(previewCommands);
return std::make_shared<CommandList>(previewCommands);
}
void AvailableCommandsFactoryImpl::AddAvailableCommandsForOneUnit(
@@ -116,7 +116,8 @@ void AvailableCommandsFactoryImpl::AddAvailableCommandsForOneUnit(
const Coords &currentCoords = unit->location();
const bool isEligibleCharger = unit->unit_id() == gameState->eligible_charger_id();
vector<UnitId> possibleChargees{};
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); }
}
@@ -195,11 +196,9 @@ auto AvailableCommandsFactoryImpl::GetAvailableCommands(
const std::optional<std::vector<Coords>> &reinforcementPositions) const -> CommandListSPtr {
CommandList commands{};
vector<AdjacentTile> adjacentTiles;
playerSetupCommandFactory
.AddAvailablePlayerSetupCommands(commands, playerId, gameState, reinforcementPositions);
if (!commands.empty()) return make_shared<CommandList>(commands);
if (!commands.empty()) return std::make_shared<CommandList>(commands);
for (const Unit *unit : *gameState->units()) {
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
@@ -239,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());
@@ -251,7 +251,7 @@ auto AvailableCommandsFactoryImpl::GetAvailableCommands(
}
if (commands.empty()) return nullptr;
return make_shared<CommandList>(commands);
return std::make_shared<CommandList>(commands);
}
} // namespace shardok
@@ -9,6 +9,7 @@
#ifndef EAGLE0_SHARDOK_LIBRARY_AVAILABLE_COMMANDS_FACTORY_HPP
#define EAGLE0_SHARDOK_LIBRARY_AVAILABLE_COMMANDS_FACTORY_HPP
#include <memory>
#include <optional>
#include <vector>
@@ -20,14 +21,12 @@
namespace shardok {
using std::optional;
using std::unique_ptr;
using UnitIdOptional = optional<UnitId>;
using UnitIdOptional = std::optional<UnitId>;
class AvailableCommandsFactory {
public:
static auto MakeAvailableCommandsFactory(const SettingsGetter &settingsGetter)
-> unique_ptr<AvailableCommandsFactory>;
-> std::unique_ptr<AvailableCommandsFactory>;
virtual ~AvailableCommandsFactory() = default;
@@ -8,17 +8,18 @@
#include "BattalionType.hpp"
#include <iostream>
#include <mutex>
#include <unordered_map>
#include "src/main/cpp/net/eagle0/common/MapUtils.hpp"
namespace shardok {
using std::unordered_map;
using BattalionTypeId = net::eagle0::shardok::storage::fb::BattalionTypeId;
ActionCost actionCostForKey(const unordered_map<string, string> &map, const string &key) {
ActionCost actionCostForKey(
const std::unordered_map<std::string, std::string> &map,
const std::string &key) {
const int pointCost = IntForKey(map, key);
return pointCost == -1 ? IMPOSSIBLE_ACTION_COST : ActionCost::StandardActionCost(pointCost);
@@ -173,7 +174,7 @@ std::shared_ptr<const BattalionType> BattalionType::NewBattalionTypeFromMap(
DoubleForKey(map, "TrainingDifferenceMultiplierForStunChanceOnCharge"),
DoubleForKey(map, "MonthlyFoodCost")});
} catch (const std::out_of_range &e) {
printf("Failed to parse file map; caught exception %s\n", e.what());
std::cerr << "Failed to parse file map; caught exception " << e.what() << '\n';
return nullptr;
}
}
@@ -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 {
@@ -13,7 +13,8 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>
@@ -244,33 +245,27 @@ public:
}
[[nodiscard]] auto ToString() const -> std::string {
std::array<char, 1024> buffer{};
snprintf(
buffer.data(),
buffer.size(),
"slashing=%f / %f, puncturing=%f / %f, stabbing=%f / %f, crushing=%f / %f, "
"fire=%f / "
"%f, cold=%f / %f, lightning=%f / %f, aessence=%f / %f, impact=%f / %f",
GetNormalDamageOfType(DamageType_slashing),
GetPenetratingDamageOfType(DamageType_slashing),
GetNormalDamageOfType(DamageType_puncturing),
GetPenetratingDamageOfType(DamageType_puncturing),
GetNormalDamageOfType(DamageType_stabbing),
GetPenetratingDamageOfType(DamageType_stabbing),
GetNormalDamageOfType(DamageType_crushing),
GetPenetratingDamageOfType(DamageType_crushing),
GetNormalDamageOfType(DamageType_fire),
GetPenetratingDamageOfType(DamageType_fire),
GetNormalDamageOfType(DamageType_cold),
GetPenetratingDamageOfType(DamageType_cold),
GetNormalDamageOfType(DamageType_lightning),
GetPenetratingDamageOfType(DamageType_lightning),
GetNormalDamageOfType(DamageType_aessence),
GetPenetratingDamageOfType(DamageType_aessence),
GetNormalDamageOfType(DamageType_impact),
GetPenetratingDamageOfType(DamageType_impact));
return {buffer.data()};
std::ostringstream out;
out << std::fixed << std::setprecision(6)
<< "slashing=" << GetNormalDamageOfType(DamageType_slashing) << " / "
<< GetPenetratingDamageOfType(DamageType_slashing)
<< ", puncturing=" << GetNormalDamageOfType(DamageType_puncturing) << " / "
<< GetPenetratingDamageOfType(DamageType_puncturing)
<< ", stabbing=" << GetNormalDamageOfType(DamageType_stabbing) << " / "
<< GetPenetratingDamageOfType(DamageType_stabbing)
<< ", crushing=" << GetNormalDamageOfType(DamageType_crushing) << " / "
<< GetPenetratingDamageOfType(DamageType_crushing)
<< ", fire=" << GetNormalDamageOfType(DamageType_fire) << " / "
<< GetPenetratingDamageOfType(DamageType_fire)
<< ", cold=" << GetNormalDamageOfType(DamageType_cold) << " / "
<< GetPenetratingDamageOfType(DamageType_cold)
<< ", lightning=" << GetNormalDamageOfType(DamageType_lightning) << " / "
<< GetPenetratingDamageOfType(DamageType_lightning)
<< ", aessence=" << GetNormalDamageOfType(DamageType_aessence) << " / "
<< GetPenetratingDamageOfType(DamageType_aessence)
<< ", impact=" << GetNormalDamageOfType(DamageType_impact) << " / "
<< GetPenetratingDamageOfType(DamageType_impact);
return out.str();
}
class Builder {
@@ -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);
@@ -10,12 +10,10 @@
namespace shardok {
using std::vector;
auto ShardokAction::Execute(
const GameStateW& currentState,
const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
vector<ActionResult> results = InternalExecute(currentState, generator);
const std::shared_ptr<RandomGenerator>& generator) const -> std::vector<ActionResult> {
std::vector<ActionResult> results = InternalExecute(currentState, generator);
return results;
}
@@ -23,10 +21,10 @@ auto ShardokAction::Execute(
auto ShardokAction::ExecuteWithRoll(
const GameStateW& currentState,
const std::shared_ptr<RandomGenerator>& generator,
const std::optional<int32_t> roll) const -> vector<ActionResult> {
vector<ActionResult> results = InternalExecuteWithRoll(currentState, generator, roll);
const std::optional<int32_t> roll) const -> std::vector<ActionResult> {
std::vector<ActionResult> results = InternalExecuteWithRoll(currentState, generator, roll);
return results;
}
} // namespace shardok
} // namespace shardok
@@ -9,6 +9,9 @@
#ifndef EAGLE0_SHARDOK_LIBRARY_SHARDOK_ACTION_HPP
#define EAGLE0_SHARDOK_LIBRARY_SHARDOK_ACTION_HPP
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "ShardokException.hpp"
@@ -22,8 +25,6 @@
namespace shardok {
using net::eagle0::shardok::storage::ActionResult;
using std::shared_ptr;
using std::vector;
using PercentileRollOdds = net::eagle0::shardok::storage::Odds;
class IllegalActionException : public ShardokException {
@@ -57,9 +57,9 @@ public:
}
};
using CommandSPtr = shared_ptr<ShardokCommand>;
using CommandList = vector<CommandSPtr>;
using CommandListSPtr = shared_ptr<const CommandList>;
using CommandSPtr = std::shared_ptr<ShardokCommand>;
using CommandList = std::vector<CommandSPtr>;
using CommandListSPtr = std::shared_ptr<const CommandList>;
} // namespace shardok
@@ -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));
}
@@ -14,12 +14,11 @@
#include <utility>
namespace shardok {
using std::string;
class ShardokException : public std::exception {
protected:
string description;
explicit ShardokException(string desc) : description(std::move(desc)) {}
std::string description;
explicit ShardokException(std::string desc) : description(std::move(desc)) {}
public:
[[nodiscard]] auto what() const noexcept -> const char* override {
@@ -30,13 +29,13 @@ public:
// Thrown when the client has provided us with bad info. The game state should still be OK.
class ShardokClientErrorException : public ShardokException {
public:
explicit ShardokClientErrorException(const string& desc) : ShardokException(desc) {}
explicit ShardokClientErrorException(const std::string& desc) : ShardokException(desc) {}
};
// Thrown when we have some bad internal state. Probably not recoverable.
class ShardokInternalErrorException : public ShardokException {
public:
explicit ShardokInternalErrorException(const string& desc) : ShardokException(desc) {}
explicit ShardokInternalErrorException(const std::string& desc) : ShardokException(desc) {}
};
static inline void internalAssert(const bool condition) {
@@ -49,13 +48,13 @@ static inline void internalAssert(const bool condition, const std::string& messa
class ShardokNotImplementedException : public ShardokException {
public:
explicit ShardokNotImplementedException(const string& operation)
explicit ShardokNotImplementedException(const std::string& operation)
: ShardokException("Operation " + operation + " is not yet implemented.") {}
};
class ShardokEagleInterfaceException : public ShardokException {
public:
explicit ShardokEagleInterfaceException(const string& desc) : ShardokException(desc) {}
explicit ShardokEagleInterfaceException(const std::string& desc) : ShardokException(desc) {}
};
} // namespace shardok
@@ -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)) {
@@ -11,9 +11,6 @@
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
namespace shardok {
using std::begin;
using std::end;
using std::vector;
struct ActionPointDistances::BraveableTileInfo {
struct BraveableFromInfo {
@@ -22,7 +19,7 @@ struct ActionPointDistances::BraveableTileInfo {
};
CoordsSet cs;
vector<BraveableFromInfo> details;
std::vector<BraveableFromInfo> details;
explicit BraveableTileInfo(const HexMap* hexMap) : cs(hexMap) {}
};
@@ -32,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);
@@ -68,10 +66,10 @@ auto ActionPointDistances::BraveWaterPossibleCoords(const HexMap* hexMap) const
return info;
}
auto ActionPointDistances::CreateIndexToCoords(const HexMap* hexMap) -> vector<Coords> {
auto ActionPointDistances::CreateIndexToCoords(const HexMap* hexMap) -> std::vector<Coords> {
const int8_t columnCount = hexMap->column_count();
const int indexCount = hexMap->row_count() * columnCount;
vector<Coords> indexToCoords;
std::vector<Coords> indexToCoords;
indexToCoords.reserve(indexCount);
for (int i = 0; i < indexCount; i++) {
indexToCoords.emplace_back(
@@ -82,10 +80,10 @@ auto ActionPointDistances::CreateIndexToCoords(const HexMap* hexMap) -> vector<C
}
auto ActionPointDistances::CreateAdjacencyTable(const HexMap* hexMap)
-> vector<std::array<int, 6>> {
-> std::vector<std::array<int, 6>> {
const int8_t columnCount = hexMap->column_count();
const int indexCount = hexMap->row_count() * columnCount;
vector<std::array<int, 6>> adjacencyTable;
std::vector<std::array<int, 6>> adjacencyTable;
adjacencyTable.reserve(indexCount);
for (int i = 0; i < indexCount; i++) {
const Coords coords(
@@ -105,22 +103,23 @@ auto ActionPointDistances::CreateAdjacencyTable(const HexMap* hexMap)
}
void ActionPointDistances::PopulateOne(
vector<DIST_T>& ds,
std::vector<DIST_T>& ds,
const HexMap* hexMap,
const bool includeBravingWater,
const int braveWaterCost,
const BattalionTypeSPtr& battalionType,
const std::shared_ptr<BraveableTileInfo>& braveWaterPossibleCoords,
const vector<Coords>& indexToCoords,
const vector<std::array<int, 6>>& adjacencyTable) {
vector<uint8_t> visited(ds.size());
const std::vector<Coords>& indexToCoords,
const std::vector<std::array<int, 6>>& adjacencyTable) {
std::vector<uint8_t> visited(ds.size());
// Priority queue for efficient minimum selection: {distance, index}
std::priority_queue<std::pair<DIST_T, int>, std::vector<std::pair<DIST_T, int>>, std::greater<>>
pq;
// Find starting index (the one with distance 0)
for (int i = 0; i < static_cast<int>(ds.size()); i++) {
const int distanceCount = static_cast<int>(ds.size());
for (int i = 0; i < distanceCount; i++) {
if (ds[i] == 0) {
pq.emplace(0, i);
break;
@@ -206,9 +205,9 @@ auto ActionPointDistances::GenerateDistances(
const bool includeBravingWater,
const int braveWaterCost,
const BattalionTypeSPtr& battalionType,
const std::shared_ptr<BraveableTileInfo>& braveWaterPossibleCoords) -> vector<DIST_T> {
const std::shared_ptr<BraveableTileInfo>& braveWaterPossibleCoords) -> std::vector<DIST_T> {
const int indexCount = hexMap->row_count() * hexMap->column_count();
auto ds = vector(indexCount, IMPOSSIBLE);
auto ds = std::vector(indexCount, IMPOSSIBLE);
ds[fromIndex] = 0;
@@ -241,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;
@@ -259,4 +257,4 @@ OnDemandActionPointDistances::OnDemandActionPointDistances(
}
}
} // namespace shardok
} // namespace shardok
@@ -5,8 +5,11 @@
#ifndef EAGLE0_SHARDOK_LIBRARY_ACTION_POINT_DISTANCES_ACTION_POINT_DISTANCES_HPP
#define EAGLE0_SHARDOK_LIBRARY_ACTION_POINT_DISTANCES_ACTION_POINT_DISTANCES_HPP
#include <array>
#include <future>
#include <optional>
#include <limits>
#include <memory>
#include <vector>
#include "src/main/cpp/net/eagle0/shardok/library/BattalionType.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/FlatbufferWrapper.hpp"
@@ -21,9 +24,6 @@ namespace shardok {
using HexMap = net::eagle0::shardok::storage::fb::HexMap;
using HexMapW = Wrapper<HexMap>;
using Coords = net::eagle0::shardok::storage::fb::Coords;
using std::optional;
using std::shared_future;
using std::vector;
using DIST_T = int16_t;
@@ -35,28 +35,29 @@ protected:
struct BraveableTileInfo;
static void PopulateOne(
vector<DIST_T> &ds,
std::vector<DIST_T> &ds,
const net::eagle0::shardok::storage::fb::HexMap *hexMap,
bool includeBravingWater,
int braveWaterCost,
const BattalionTypeSPtr &battalionType,
const std::shared_ptr<BraveableTileInfo> &braveWaterPossibleCoords,
const vector<Coords> &indexToCoords,
const vector<std::array<int, 6>> &adjacencyTable);
const std::vector<Coords> &indexToCoords,
const std::vector<std::array<int, 6>> &adjacencyTable);
static auto GenerateDistances(
int fromIndex,
const HexMap *hexMap,
bool includeBravingWater,
int braveWaterCost,
const BattalionTypeSPtr &battalionType,
const std::shared_ptr<BraveableTileInfo> &braveWaterPossibleCoords) -> vector<DIST_T>;
const std::shared_ptr<BraveableTileInfo> &braveWaterPossibleCoords)
-> std::vector<DIST_T>;
auto BraveWaterPossibleCoords(const HexMap *hexMap) const -> std::shared_ptr<BraveableTileInfo>;
// Create coordinate lookup table for efficient index->coords conversion
static auto CreateIndexToCoords(const HexMap *hexMap) -> vector<Coords>;
static auto CreateIndexToCoords(const HexMap *hexMap) -> std::vector<Coords>;
// Create adjacency lookup table for efficient neighbor access
static auto CreateAdjacencyTable(const HexMap *hexMap) -> vector<std::array<int, 6>>;
static auto CreateAdjacencyTable(const HexMap *hexMap) -> std::vector<std::array<int, 6>>;
[[nodiscard]] auto ToIndex(const Coords &coords) const -> int {
return coords.row() * column_count + coords.column();
@@ -79,7 +80,7 @@ private:
const HexMapW hexMap;
const BattalionTypeSPtr battalionType;
vector<shared_future<vector<int16_t>>> distances;
std::vector<std::shared_future<std::vector<int16_t>>> distances;
public:
explicit OnDemandActionPointDistances(
@@ -6,6 +6,8 @@
#include <algorithm>
#include <chrono>
#include <iostream>
#include <ostream>
#include <unordered_map>
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/FixedActionPointDistances.hpp"
@@ -32,22 +34,21 @@ thread_local struct {
} cacheStats;
// Helper function to print stats periodically
static void MaybePrintCacheStats() {
static void MaybePrintCacheStats(std::ostream& output) {
auto now = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::seconds>(now - cacheStats.lastReportTime).count() >=
kCacheStatsFrequencySeconds) {
printf("Thread cache stats: %d persistent hits, %d persistent misses, %d local hits, "
"%d local misses, %d shared accesses, %d eviction events\n",
cacheStats.persistentHits,
cacheStats.persistentMisses,
cacheStats.localHits,
cacheStats.localMisses,
cacheStats.sharedAccesses,
cacheStats.evictionEvents);
output << "Thread cache stats: " << cacheStats.persistentHits << " persistent hits, "
<< cacheStats.persistentMisses << " persistent misses, " << cacheStats.localHits
<< " local hits, " << cacheStats.localMisses << " local misses, "
<< cacheStats.sharedAccesses << " shared accesses, " << cacheStats.evictionEvents
<< " eviction events\n";
cacheStats.lastReportTime = now;
}
}
static void MaybePrintCacheStats() { MaybePrintCacheStats(std::cout); }
class BadHashException final : public std::exception {
public:
BadHashException() = default;
@@ -154,7 +155,7 @@ auto ActionPointDistancesCache::GetRaw(
if (kCacheStatsLogging) { cacheStats.localMisses++; }
// Check shared cache before expensive ice-clearing operation
shared_ptr<ActionPointDistances> sharedResult;
std::shared_ptr<ActionPointDistances> sharedResult;
if (sharedDistances.if_contains(cacheKey, [&sharedResult](const auto& kv) {
sharedResult = kv.second;
})) {
@@ -5,6 +5,7 @@
#ifndef EAGLE0_SHARDOK_LIBRARY_ACTION_POINT_DISTANCES_ACTION_POINT_DISTANCES_CACHE_HPP
#define EAGLE0_SHARDOK_LIBRARY_ACTION_POINT_DISTANCES_ACTION_POINT_DISTANCES_CACHE_HPP
#include <memory>
#include <shared_mutex>
#include <unordered_map>
@@ -17,8 +18,6 @@
namespace shardok {
using std::shared_ptr;
struct MapId {
uint64_t terrainTypesId;
uint64_t modifierId;
@@ -58,9 +57,10 @@ struct FullCacheKeyHash {
class ActionPointDistancesCache {
private:
struct CacheEntry {
shared_ptr<ActionPointDistances> sharedPtr;
std::shared_ptr<ActionPointDistances> sharedPtr;
explicit CacheEntry(shared_ptr<ActionPointDistances> ptr) : sharedPtr(std::move(ptr)) {}
explicit CacheEntry(std::shared_ptr<ActionPointDistances> ptr)
: sharedPtr(std::move(ptr)) {}
};
// Tier 1: persistent map. This is NOT safe to write to while reads may be happening.
@@ -70,10 +70,10 @@ private:
using APDMap = gtl::parallel_flat_hash_map<
FullCacheKey,
shared_ptr<ActionPointDistances>,
std::shared_ptr<ActionPointDistances>,
FullCacheKeyHash,
std::equal_to<FullCacheKey>,
std::allocator<std::pair<const FullCacheKey, shared_ptr<ActionPointDistances>>>,
std::allocator<std::pair<const FullCacheKey, std::shared_ptr<ActionPointDistances>>>,
6,
std::mutex>;
@@ -119,7 +119,7 @@ public:
static size_t GetThreadLocalCacheSize();
};
using APDCache = shared_ptr<ActionPointDistancesCache>;
using APDCache = std::shared_ptr<ActionPointDistancesCache>;
} // namespace shardok
@@ -54,31 +54,3 @@ cc_library(
"//src/main/cpp/net/eagle0/shardok/library/fb_helpers:hex_map_helpers",
],
)
cc_library(
name = "fixed_action_point_distances_timer",
srcs = ["FixedActionPointDistancesTimer.cpp"],
copts = COPTS,
data = [
"//src/main/resources/net/eagle0/shardok/maps",
],
deps = [
"//src/main/cpp/net/eagle0/common:sequence_random_generator",
"//src/main/cpp/net/eagle0/shardok/library/action_point_distances:fixed_action_point_distances",
"//src/main/cpp/net/eagle0/shardok/library/settings_loader",
"//src/main/cpp/net/eagle0/shardok/util:map_loader",
"@googletest//:gtest",
"@googletest//:gtest_main",
],
)
cc_binary(
name = "fixed_action_point_distances_timer_app",
copts = COPTS,
data = [
"//src/main/resources/net/eagle0/shardok/maps",
],
deps = [
":fixed_action_point_distances_timer",
],
)
@@ -4,6 +4,7 @@
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/FixedActionPointDistances.hpp"
#include <iostream>
#include <thread>
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
@@ -11,12 +12,10 @@
// 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);
fprintf(stderr,
"ActionPointDistances using %d threads (detected %d cores)\n",
threadCount,
cores);
std::cerr << "ActionPointDistances using " << threadCount << " threads (detected " << cores
<< " cores)\n";
return threadCount;
}();
@@ -36,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;
@@ -13,12 +13,11 @@
namespace shardok {
using std::vector;
using BattalionTypeSPtr = std::shared_ptr<const BattalionType>;
class FixedActionPointDistances final : public ActionPointDistances {
private:
vector<vector<DIST_T>> distances;
std::vector<std::vector<DIST_T>> distances;
// Private constructor - use Create factory method instead
explicit FixedActionPointDistances(const HexMap *map, int columnCount);
@@ -1,48 +0,0 @@
//
// Created by Dan Crosby on 11/26/22.
//
#include <include/gtest/gtest.h>
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistances.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/FixedActionPointDistances.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/HexMapHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/settings_loader/SettingsLoader.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/MapLoader.hpp"
using namespace std::chrono;
using namespace shardok;
auto main(int argc, char** argv) -> int {
FilesystemUtils::SetExecPath(
"/Users/dancrosby/CodingProjects/github/eagle0/bazel-bin/src/main/cpp/net/eagle0/"
"shardok/library/action_point_distances/fixed_action_point_distances_timer_app");
const auto gameSettings = SettingsLoader::LoadSettings();
const vector<string> mapNames = GetMapNames();
vector<HexMapW> hexMaps;
for (const string& name : mapNames) {
auto hexMapProto = LoadMap(name);
flatbuffers::FlatBufferBuilder fbb;
const auto offset = shardok::fb::ConvertHexMapProto(fbb, hexMapProto);
fbb.Finish(offset);
hexMaps.emplace_back(fbb);
}
const auto& battalionType = gameSettings->GetGetter().GetBattalionType(
net::eagle0::shardok::storage::fb::BattalionTypeId_LIGHT_INFANTRY);
const auto start = system_clock::now();
for (int i = 0; i < 100; i++) {
for (const HexMapW& hexMap : hexMaps) {
shardok::FixedActionPointDistances
distances(hexMap, 0x1234, 0xABCD, battalionType, true, 5);
}
}
const auto end = system_clock::now();
std::cerr << duration_cast<microseconds>(end - start).count() << " µs" << '\n';
}
@@ -19,8 +19,6 @@
namespace shardok {
using std::unique_ptr;
using ResolvedUnitProto = net::eagle0::shardok::storage::ResolvedUnit;
using GameState = net::eagle0::shardok::storage::fb::GameState;
using GameStateT = net::eagle0::shardok::storage::fb::GameStateT;
@@ -249,12 +247,13 @@ void MutatingApplyResult(
internalAssert(mutatingGameState->mutable_status()->mutate_state(
static_cast<net::eagle0::shardok::storage::fb::GameStatus_::State>(
result.game_status().state())));
for (int i = 0; i < result.game_status().winning_shardok_ids_size(); i++) {
const int winningShardokIdCount = result.game_status().winning_shardok_ids_size();
for (int i = 0; i < winningShardokIdCount; i++) {
mutatingGameState->mutable_status()->mutable_winning_shardok_ids()->Mutate(
i,
result.game_status().winning_shardok_ids(i));
}
for (int i = result.game_status().winning_shardok_ids_size(); i < 10; i++) {
for (int i = winningShardokIdCount; i < 10; i++) {
mutatingGameState->mutable_status()->mutable_winning_shardok_ids()->Mutate(i, -1);
}
@@ -302,23 +301,18 @@ void MutatingApplyResult(
for (const auto &changedUnitBytes : result.changed_units_fb()) {
const Unit *changedUnit = &UnitFromBytes(changedUnitBytes);
if (changedUnit->battalion().type() ==
net::eagle0::shardok::storage::fb::BattalionTypeId_UNDEAD &&
changedUnit->battalion().morale() != 50.0) {
printf("bad morale\n");
}
internalAssert(
changedUnit->unit_id() < (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)) {
@@ -390,10 +384,11 @@ void MutatingApplyResult(
} else {
internalAssert(mutatingGameState->mutate_eligible_charger_id(-1));
}
for (int i = 0; i < result.possible_chargees_size(); i++) {
const int possibleChargeeCount = result.possible_chargees_size();
for (int i = 0; i < possibleChargeeCount; i++) {
mutatingGameState->mutable_possible_chargee_ids()->Mutate(i, result.possible_chargees(i));
}
for (int i = result.possible_chargees_size(); i < 6; i++) {
for (int i = possibleChargeeCount; i < 6; i++) {
mutatingGameState->mutable_possible_chargee_ids()->Mutate(i, -1);
}
@@ -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
@@ -19,8 +19,6 @@ using net::eagle0::shardok::storage::fb::VictoryCondition;
using PlayerInfoFb = net::eagle0::shardok::storage::fb::PlayerInfo;
using GameStatusFb = net::eagle0::shardok::storage::fb::GameStatus;
using GameStatusProto = net::eagle0::shardok::common::GameStatus;
using std::optional;
using std::unordered_set;
static inline auto GameIsOver(const GameStatusFb* status) -> bool {
return (status->state() == net::eagle0::shardok::storage::fb::GameStatus_::State_VICTORY);
@@ -41,17 +39,19 @@ static inline auto HasVictoryCondition(const PlayerInfoFb* pi, const VictoryCond
return false;
}
auto UpdateGameStatusAction::SurvivingPlayers() const -> unordered_set<PlayerId> {
auto UpdateGameStatusAction::SurvivingPlayers() const -> std::unordered_set<PlayerId> {
if (gameState->status()->state() ==
net::eagle0::shardok::storage::fb::GameStatus_::State_SET_UP)
return unordered_set<PlayerId>{};
return std::unordered_set<PlayerId>{};
unordered_set<PlayerId> survivingPids{};
std::unordered_set<PlayerId> survivingPids{};
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))) {
@@ -288,7 +288,7 @@ auto UpdateGameStatusAction::InternalExecute(
}
// Second check: allied players collectively holding all critical tiles
unordered_set<PlayerId> occupantPlayerIds{};
std::unordered_set<PlayerId> occupantPlayerIds{};
bool allTilesOccupiedWithHeroes = true;
for (const auto& criticalTile : criticalTileLocations) {
const auto* possibleOccupant = currentState.GetOccupant(criticalTile);
@@ -356,4 +356,4 @@ auto UpdateGameStatusAction::InternalExecute(
// If we got all the way through, keep the status as it is
return results;
}
} // namespace shardok
} // namespace shardok
@@ -4,6 +4,8 @@
#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
@@ -16,7 +16,6 @@
#include "src/main/protobuf/net/eagle0/shardok/api/unit_view.pb.h"
namespace shardok {
using std::vector;
class ChallengeDuelCommandFactory : public CommandFactory {
private:
@@ -32,7 +31,7 @@ public:
ActionPoints remainingActionPoints,
const HexMap *map,
const Units *units,
const vector<PlayerId> &allyPids) const;
const std::vector<PlayerId> &allyPids) const;
void AddAvailableCommands(CommandList &commands, const CommandParams &params) const override;
};
@@ -4,6 +4,8 @@
#include "CommandFactoriesList.hpp"
#include <memory>
#include "src/main/cpp/net/eagle0/shardok/library/command_factories/BlowBridgeCommandFactory.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/command_factories/BraveWaterCommandFactory.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/command_factories/BuildBridgeCommandFactory.hpp"
@@ -34,42 +36,42 @@
namespace shardok {
using std::make_shared;
auto MakeFactories(const SettingsGetter& settings) -> vector<shared_ptr<const CommandFactory>> {
auto MakeFactories(const SettingsGetter& settings)
-> std::vector<std::shared_ptr<const CommandFactory>> {
// Command factories that produce "must-do" commands like Meteor Target should be first.
// Ultimately I should probably move that metadata into the CommandFactory and sort it here.
return {make_shared<MeteorTargetCommandFactory>(settings),
make_shared<ControlCommandFactory>(settings),
make_shared<ArcheryCommandFactory>(settings),
make_shared<BlowBridgeCommandFactory>(settings),
make_shared<BraveWaterCommandFactory>(settings),
make_shared<BuildBridgeCommandFactory>(settings),
make_shared<ChallengeDuelCommandFactory>(settings),
make_shared<ChargeCommandFactory>(settings),
make_shared<EvacuatePrisonersCommandFactory>(settings),
make_shared<ExtinguishFireCommandFactory>(settings),
make_shared<FearCommandFactory>(settings),
make_shared<FleeCommandFactory>(settings),
make_shared<FortifyCommandFactory>(settings),
make_shared<FreezeWaterCommandFactory>(settings),
make_shared<HideCommandFactory>(settings),
make_shared<HolyWaveCommandFactory>(settings),
make_shared<LightningBoltCommandFactory>(settings),
make_shared<MeleeCommandFactory>(settings),
make_shared<MeteorStartCommandFactory>(settings),
make_shared<RaiseDeadCommandFactory>(settings),
make_shared<ReduceCommandFactory>(settings),
make_shared<ReinforceCommandFactory>(settings),
make_shared<RepairCommandFactory>(settings),
make_shared<RetreatCommandFactory>(settings),
make_shared<ScoutCommandFactory>(settings),
make_shared<StartFireCommandFactory>(settings),
make_shared<MoveCommandFactory>(settings)};
return {std::make_shared<MeteorTargetCommandFactory>(settings),
std::make_shared<ControlCommandFactory>(settings),
std::make_shared<ArcheryCommandFactory>(settings),
std::make_shared<BlowBridgeCommandFactory>(settings),
std::make_shared<BraveWaterCommandFactory>(settings),
std::make_shared<BuildBridgeCommandFactory>(settings),
std::make_shared<ChallengeDuelCommandFactory>(settings),
std::make_shared<ChargeCommandFactory>(settings),
std::make_shared<EvacuatePrisonersCommandFactory>(settings),
std::make_shared<ExtinguishFireCommandFactory>(settings),
std::make_shared<FearCommandFactory>(settings),
std::make_shared<FleeCommandFactory>(settings),
std::make_shared<FortifyCommandFactory>(settings),
std::make_shared<FreezeWaterCommandFactory>(settings),
std::make_shared<HideCommandFactory>(settings),
std::make_shared<HolyWaveCommandFactory>(settings),
std::make_shared<LightningBoltCommandFactory>(settings),
std::make_shared<MeleeCommandFactory>(settings),
std::make_shared<MeteorStartCommandFactory>(settings),
std::make_shared<RaiseDeadCommandFactory>(settings),
std::make_shared<ReduceCommandFactory>(settings),
std::make_shared<ReinforceCommandFactory>(settings),
std::make_shared<RepairCommandFactory>(settings),
std::make_shared<RetreatCommandFactory>(settings),
std::make_shared<ScoutCommandFactory>(settings),
std::make_shared<StartFireCommandFactory>(settings),
std::make_shared<MoveCommandFactory>(settings)};
}
auto CommandFactoriesList::GetFactories() const -> vector<shared_ptr<const CommandFactory>> {
auto CommandFactoriesList::GetFactories() const
-> std::vector<std::shared_ptr<const CommandFactory>> {
return factories;
}
@@ -12,17 +12,14 @@
namespace shardok {
using std::shared_ptr;
using std::vector;
class CommandFactoriesList {
private:
vector<shared_ptr<const CommandFactory>> factories;
std::vector<std::shared_ptr<const CommandFactory>> factories;
public:
explicit CommandFactoriesList(const SettingsGetter& settings);
auto GetFactories() const -> vector<shared_ptr<const CommandFactory>>;
auto GetFactories() const -> std::vector<std::shared_ptr<const CommandFactory>>;
};
} // 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)) {

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