constants.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /*
  2. * constants.h, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #pragma once
  11. #include <array>
  12. #include <stdexcept>
  13. #include <string>
  14. #include <tuple>
  15. #include "schema/base.h"
  16. #include "schema/v13/types.h"
  17. #include "schema/v13/util.h"
  18. namespace MMAI::Schema::V13
  19. {
  20. constexpr int N_NONHEX_ACTIONS = 2;
  21. constexpr Action ACTION_RETREAT = 0;
  22. constexpr Action ACTION_WAIT = 1;
  23. constexpr int N_HEX_ACTIONS = EI(HexAction::_count);
  24. constexpr int N_ACTIONS = N_NONHEX_ACTIONS + (165 * N_HEX_ACTIONS);
  25. constexpr int STACK_ATTR_OFFSET = EI(HexAttribute::_count) - EI(StackAttribute::_count);
  26. // Control actions (not part of the regular action space)
  27. constexpr Action ACTION_UNSET = -666;
  28. constexpr Action ACTION_RESET = -1;
  29. constexpr Action ACTION_RENDER_ANSI = -2;
  30. // Value used when masking NULL values during encoding
  31. constexpr int NULL_VALUE_ENCODED = -1;
  32. constexpr int NULL_VALUE_UNENCODED = -1;
  33. // Convenience definitions which do not need to be exported
  34. namespace X
  35. {
  36. inline constexpr auto AE = Encoding::ACCUMULATING_EXPLICIT_NULL;
  37. inline constexpr auto AI = Encoding::ACCUMULATING_IMPLICIT_NULL;
  38. inline constexpr auto AM = Encoding::ACCUMULATING_MASKING_NULL;
  39. inline constexpr auto AS = Encoding::ACCUMULATING_STRICT_NULL;
  40. inline constexpr auto AZ = Encoding::ACCUMULATING_ZERO_NULL;
  41. inline constexpr auto BE = Encoding::BINARY_EXPLICIT_NULL;
  42. inline constexpr auto BM = Encoding::BINARY_MASKING_NULL;
  43. inline constexpr auto BS = Encoding::BINARY_STRICT_NULL;
  44. inline constexpr auto BZ = Encoding::BINARY_ZERO_NULL;
  45. inline constexpr auto CE = Encoding::CATEGORICAL_EXPLICIT_NULL;
  46. inline constexpr auto CI = Encoding::CATEGORICAL_IMPLICIT_NULL;
  47. inline constexpr auto CM = Encoding::CATEGORICAL_MASKING_NULL;
  48. inline constexpr auto CS = Encoding::CATEGORICAL_STRICT_NULL;
  49. inline constexpr auto CZ = Encoding::CATEGORICAL_ZERO_NULL;
  50. inline constexpr auto EE = Encoding::EXPNORM_EXPLICIT_NULL;
  51. inline constexpr auto EM = Encoding::EXPNORM_MASKING_NULL;
  52. inline constexpr auto ES = Encoding::EXPNORM_STRICT_NULL;
  53. inline constexpr auto EZ = Encoding::EXPNORM_ZERO_NULL;
  54. inline constexpr auto LE = Encoding::LINNORM_EXPLICIT_NULL;
  55. inline constexpr auto LM = Encoding::LINNORM_MASKING_NULL;
  56. inline constexpr auto LS = Encoding::LINNORM_STRICT_NULL;
  57. inline constexpr auto LZ = Encoding::LINNORM_ZERO_NULL;
  58. inline constexpr auto RAW = Encoding::RAW;
  59. using GA = GlobalAttribute;
  60. using PA = PlayerAttribute;
  61. using HA = HexAttribute;
  62. /*
  63. * The encoding schema `{a, e, n, vmax, p}`, where:
  64. * a=attribute
  65. * e=encoding
  66. * n=size
  67. * vmax=max_value
  68. * p=param (encoding-specific)
  69. */
  70. using E5G = std::tuple<GlobalAttribute, Encoding, int, int, double>;
  71. using E5P = std::tuple<PlayerAttribute, Encoding, int, int, double>;
  72. using E5H = std::tuple<HexAttribute, Encoding, int, int, double>;
  73. }
  74. using GlobalEncoding = std::array<X::E5G, EI(GlobalAttribute::_count)>;
  75. using PlayerEncoding = std::array<X::E5P, EI(PlayerAttribute::_count)>;
  76. using HexEncoding = std::array<X::E5H, EI(HexAttribute::_count)>;
  77. /*
  78. * Compile-time constructor for E5H and E5S tuples
  79. * https://stackoverflow.com/a/23784921
  80. */
  81. template<typename T>
  82. constexpr std::tuple<T, Encoding, int, int, double> E5(T a, Encoding e, int vmax, double slope = -1, int bins = -1)
  83. {
  84. switch(e)
  85. {
  86. // "0" is a value => vmax+1 values
  87. case X::AE:
  88. return {a, e, vmax + 2, vmax, -1};
  89. case X::AI:
  90. case X::AM:
  91. case X::AS:
  92. case X::AZ:
  93. return {a, e, vmax + 1, vmax, -1};
  94. // Log2(8)=3 (2^3), but if vmax=8 then 4 bits will be required
  95. // => Log2(9)=4
  96. case X::BE:
  97. return {a, e, static_cast<int>(Log2(vmax + 1)) + 1, vmax, -1};
  98. case X::BM:
  99. case X::BS:
  100. case X::BZ:
  101. return {a, e, static_cast<int>(Log2(vmax + 1)), vmax, -1};
  102. // "0" is a category => vmax+1 categories
  103. case X::CE:
  104. return {a, e, vmax + 2, vmax, -1};
  105. case X::CI:
  106. case X::CM:
  107. case X::CS:
  108. case X::CZ:
  109. return {a, e, vmax + 1, vmax, -1};
  110. case X::LE:
  111. return {a, e, 2, vmax, -1};
  112. case X::LM:
  113. case X::LS:
  114. case X::LZ:
  115. return {a, e, 1, vmax, -1};
  116. case X::EE:
  117. return {a, e, 2, vmax, slope};
  118. case X::EM:
  119. case X::ES:
  120. case X::EZ:
  121. return {a, e, 1, vmax, slope};
  122. case X::RAW:
  123. return {a, e, 1, vmax, -1};
  124. default:
  125. throw std::runtime_error("Unexpected encoding: " + std::to_string(EI(e)));
  126. }
  127. }
  128. // 0-6 regular; 7=war machines; 8=other (summoned, commander, etc.)
  129. constexpr int STACK_SLOT_WARMACHINES = 7;
  130. constexpr int STACK_SLOT_SPECIAL = 8;
  131. // Values above MAX are simply capped
  132. constexpr int STACK_QUEUE_SIZE = 30;
  133. constexpr int CREATURE_ID_MAX = 149; // H3 core has creature IDs 0..149
  134. constexpr int STACK_SLOT_MAX = 8;
  135. // NOTE: the generated maps use old AIValue() which is 4-6x LOWER
  136. // than the one calculated by MMAI (in Stack::CalcValue())
  137. // => a map with 100K pools corresponds to 400K..600K pools now
  138. // The biggest pools are:
  139. // (1) 4x1096 => 500K pools (old) => 3000K (new) => total = 6000K (new)
  140. // (2) 8x64 => 800K pools (old) => 4800K (new) => total = 9600K (new)
  141. // (3) 8x64 => 1600K pools (old) => 9600K (new) => total = 19200K (new)
  142. // Since (1) is used for training while (2) and (3) are for evaluation, we
  143. // set max=10000K=10M (new) in order to:
  144. // - test higher-than-trained values via (2), but within limits,
  145. // - test higher-than-trained values via (3), but outside limits
  146. //
  147. // XXX: THIS IS NOW LEFT UNUSED (switched to relative values instead)
  148. // constexpr int ARMY_VALUE_MAX = 10 * 1000 * 1000; // 10M
  149. constexpr auto BFIELD_VALUE_MAX = static_cast<int>(10e6); // 4.2M max for 4x1024.vmap
  150. constexpr auto BFIELD_VALUE_SLOPE = 5;
  151. constexpr auto BFIELD_HP_MAX = static_cast<int>(200e3); // 90k max for 4x1024.vmap
  152. constexpr auto BFIELD_HP_SLOPE = 7.5;
  153. constexpr GlobalEncoding GLOBAL_ENCODING{
  154. E5(X::GA::BATTLE_SIDE, X::CS, 1),
  155. E5(X::GA::BATTLE_SIDE_ACTIVE_PLAYER, X::CE, 1), // NULL means no battle
  156. E5(X::GA::BATTLE_WINNER, X::CE, 1), // NULL means ongoing battle
  157. E5(X::GA::BFIELD_VALUE_START_ABS, X::ES, BFIELD_VALUE_MAX, BFIELD_VALUE_SLOPE),
  158. E5(X::GA::BFIELD_VALUE_NOW_ABS, X::ES, BFIELD_VALUE_MAX, BFIELD_VALUE_SLOPE),
  159. E5(X::GA::BFIELD_VALUE_NOW_REL0, X::LS, 1000), // bfield_value_now / bfield_value_at_start
  160. E5(X::GA::BFIELD_HP_START_ABS, X::ES, BFIELD_HP_MAX, BFIELD_HP_SLOPE),
  161. E5(X::GA::BFIELD_HP_NOW_ABS, X::ES, BFIELD_HP_MAX, BFIELD_HP_SLOPE),
  162. E5(X::GA::BFIELD_HP_NOW_REL0, X::LS, 1000), // bfield_hp_now / bfield_hp_at_start
  163. E5(X::GA::ACTION_MASK, X::BS, (1 << EI(GlobalAction::_count)) - 1)
  164. };
  165. // 100 Ghost dragons => ~4K base dmg
  166. // vs. Grand Elf = 8K dmg (+22 attack advantage)
  167. // => 667 kills * 1.8k value = 1.2M value killed
  168. constexpr auto VALUE_KILLED_NOW_MAX = static_cast<int>(2e6);
  169. constexpr auto VALUE_KILLED_NOW_NBINS = 50;
  170. constexpr auto VALUE_KILLED_NOW_SLOPE = 7.5; // granularity at low values OK (1 imp = 213)
  171. constexpr auto DMG_DEALT_NOW_MAX = static_cast<int>(20e3);
  172. constexpr auto DMG_DEALT_NOW_NBINS = 50;
  173. constexpr auto DMG_DEALT_NOW_SLOPE = 6.5;
  174. constexpr PlayerEncoding PLAYER_ENCODING{
  175. E5(X::PA::BATTLE_SIDE, X::CS, 1),
  176. E5(X::PA::ARMY_VALUE_NOW_ABS, X::ES, BFIELD_VALUE_MAX, BFIELD_VALUE_SLOPE),
  177. E5(X::PA::ARMY_VALUE_NOW_REL, X::LS, 1000), // (army_value_now / global_value_now)
  178. E5(X::PA::ARMY_VALUE_NOW_REL0, X::LS, 1000), // (army_value_now / global_value_at_start)
  179. E5(X::PA::ARMY_HP_NOW_ABS, X::ES, BFIELD_HP_MAX, BFIELD_HP_SLOPE),
  180. E5(X::PA::ARMY_HP_NOW_REL, X::LS, 1000), // (army_hp_now / global_hp_now)
  181. E5(X::PA::ARMY_HP_NOW_REL0, X::LS, 1000), // (army_hp_now / global_hp_at_start)
  182. E5(X::PA::VALUE_KILLED_NOW_ABS, X::ES, VALUE_KILLED_NOW_MAX, VALUE_KILLED_NOW_SLOPE),
  183. E5(X::PA::VALUE_KILLED_NOW_REL, X::LS, 1000), // (value_killed_this_turn / global_value_last_turn)
  184. E5(X::PA::VALUE_KILLED_ACC_ABS, X::ES, BFIELD_VALUE_MAX, BFIELD_VALUE_SLOPE),
  185. E5(X::PA::VALUE_KILLED_ACC_REL0, X::LS, 1000), // (value_killed_lifetime / global_value_at_start)
  186. E5(X::PA::VALUE_LOST_NOW_ABS, X::ES, VALUE_KILLED_NOW_MAX, VALUE_KILLED_NOW_SLOPE),
  187. E5(X::PA::VALUE_LOST_NOW_REL, X::LS, 1000), // (value_lost_this_turn / global_value_last_turn)
  188. E5(X::PA::VALUE_LOST_ACC_ABS, X::ES, BFIELD_VALUE_MAX, BFIELD_VALUE_SLOPE),
  189. E5(X::PA::VALUE_LOST_ACC_REL0, X::LS, 1000), // (value_lost_lifetime / global_value_at_start)
  190. E5(X::PA::DMG_DEALT_NOW_ABS, X::ES, DMG_DEALT_NOW_MAX, DMG_DEALT_NOW_SLOPE),
  191. E5(X::PA::DMG_DEALT_NOW_REL, X::LS, 1000), // (dmg_dealt_this_turn / global_hp_last_turn)
  192. E5(X::PA::DMG_DEALT_ACC_ABS, X::ES, BFIELD_HP_MAX, BFIELD_HP_SLOPE),
  193. E5(X::PA::DMG_DEALT_ACC_REL0, X::LS, 1000), // (dmg_dealt_lifetime / global_hp_at_start)
  194. E5(X::PA::DMG_RECEIVED_NOW_ABS, X::ES, DMG_DEALT_NOW_MAX, DMG_DEALT_NOW_SLOPE),
  195. E5(X::PA::DMG_RECEIVED_NOW_REL, X::LS, 1000), // (dmg_received_this_turn / global_hp_last_turn)
  196. E5(X::PA::DMG_RECEIVED_ACC_ABS, X::ES, BFIELD_HP_MAX, BFIELD_HP_SLOPE),
  197. E5(X::PA::DMG_RECEIVED_ACC_REL0, X::LS, 1000), // (dmg_received_lifetime / global_hp_at_start)
  198. };
  199. // Visualise on https://www.desmos.com/calculator:
  200. // ln(1 + (x/M) * (exp(S)-1))/S
  201. // Add slider "S" (slope) and "M" (vmax).
  202. // Play with the sliders to see the nonlinearity (use M=1 for best view)
  203. // XXX: slope cannot be 0
  204. constexpr auto STACK_QTY_MAX = 1500;
  205. constexpr auto STACK_QTY_SLOPE = 5;
  206. constexpr auto STACK_HP_MAX = 1000;
  207. constexpr auto STACK_HP_SLOPE = 6;
  208. constexpr auto STACK_VALUE_MAX = 200e3; // titan 55k, crystal dr. 113k, azure 180k...
  209. constexpr auto STACK_VALUE_NBINS = 20;
  210. constexpr auto STACK_VALUE_SLOPE = 6.5;
  211. constexpr HexEncoding HEX_ENCODING{
  212. E5(X::HA::Y_COORD, X::CS, 10),
  213. E5(X::HA::X_COORD, X::CS, 14),
  214. E5(X::HA::STATE_MASK, X::BS, (1 << EI(HexState::_count)) - 1),
  215. E5(X::HA::ACTION_MASK, X::BZ, (1 << EI(HexAction::_count)) - 1),
  216. E5(X::HA::IS_REAR, X::CZ, 1), // 1=this is the rear hex of a stack
  217. E5(X::HA::STACK_SIDE, X::CE, 1), // 0=attacker, 1=defender
  218. E5(X::HA::STACK_SLOT, X::CE, STACK_SLOT_MAX),
  219. E5(X::HA::STACK_QUANTITY, X::EZ, STACK_QTY_MAX, STACK_QTY_SLOPE),
  220. E5(X::HA::STACK_ATTACK, X::LZ, 80),
  221. E5(X::HA::STACK_DEFENSE, X::LZ, 80), // azure dragon is 60 when defending
  222. E5(X::HA::STACK_SHOTS, X::LZ, 32), // sharpshooter is 32
  223. E5(X::HA::STACK_DMG_MIN, X::LZ, 100),
  224. E5(X::HA::STACK_DMG_MAX, X::LZ, 100),
  225. E5(X::HA::STACK_HP, X::EZ, STACK_HP_MAX, STACK_HP_SLOPE),
  226. E5(X::HA::STACK_HP_LEFT, X::EZ, STACK_HP_MAX, STACK_HP_SLOPE),
  227. E5(X::HA::STACK_SPEED, X::CE, 20),
  228. E5(X::HA::STACK_QUEUE, X::BZ, (1 << STACK_QUEUE_SIZE) - 1), // 0..14, 0=active stack
  229. E5(X::HA::STACK_VALUE_ONE, X::EZ, STACK_VALUE_MAX, STACK_VALUE_SLOPE),
  230. E5(X::HA::STACK_FLAGS1, X::BZ, (1 << EI(StackFlag1::_count)) - 1),
  231. E5(X::HA::STACK_FLAGS2, X::BZ, (1 << EI(StackFlag2::_count)) - 1),
  232. E5(X::HA::STACK_VALUE_REL, X::LZ, 1000),
  233. E5(X::HA::STACK_VALUE_REL0, X::LZ, 1000),
  234. E5(X::HA::STACK_VALUE_KILLED_REL, X::LZ, 1000),
  235. E5(X::HA::STACK_VALUE_KILLED_ACC_REL0, X::LZ, 1000),
  236. E5(X::HA::STACK_VALUE_LOST_REL, X::LZ, 1000),
  237. E5(X::HA::STACK_VALUE_LOST_ACC_REL0, X::LZ, 1000),
  238. E5(X::HA::STACK_DMG_DEALT_REL, X::LZ, 1000),
  239. E5(X::HA::STACK_DMG_DEALT_ACC_REL0, X::LZ, 1000),
  240. E5(X::HA::STACK_DMG_RECEIVED_REL, X::LZ, 1000),
  241. E5(X::HA::STACK_DMG_RECEIVED_ACC_REL0, X::LZ, 1000),
  242. };
  243. // Dedining encodings for each attribute by hand is error-prone
  244. // The below compile-time asserts are essential.
  245. static_assert(UninitializedEncodingAttributes(GLOBAL_ENCODING) == 0, "Found uninitialized elements");
  246. static_assert(UninitializedEncodingAttributes(PLAYER_ENCODING) == 0, "Found uninitialized elements");
  247. static_assert(UninitializedEncodingAttributes(HEX_ENCODING) == 0, "Found uninitialized elements");
  248. static_assert(DisarrayedEncodingAttributeIndex(GLOBAL_ENCODING) == -1, "Found wrong element at this index");
  249. static_assert(DisarrayedEncodingAttributeIndex(PLAYER_ENCODING) == -1, "Found wrong element at this index");
  250. static_assert(DisarrayedEncodingAttributeIndex(HEX_ENCODING) == -1, "Found wrong element at this index");
  251. static_assert(MisconfiguredExpnormSlopeIndex(GLOBAL_ENCODING) == -1, "Found miscalculated binary vmax element at this index");
  252. static_assert(MisconfiguredExpnormSlopeIndex(PLAYER_ENCODING) == -1, "Found miscalculated binary vmax element at this index");
  253. static_assert(MisconfiguredExpnormSlopeIndex(HEX_ENCODING) == -1, "Found miscalculated binary vmax element at this index");
  254. constexpr int BATTLEFIELD_STATE_SIZE_GLOBAL = EncodedSize(GLOBAL_ENCODING);
  255. constexpr int BATTLEFIELD_STATE_SIZE_ONE_PLAYER = EncodedSize(PLAYER_ENCODING);
  256. constexpr int BATTLEFIELD_STATE_SIZE_ONE_HEX = EncodedSize(HEX_ENCODING);
  257. constexpr int BATTLEFIELD_STATE_SIZE_ALL_HEXES = 165 * BATTLEFIELD_STATE_SIZE_ONE_HEX;
  258. constexpr int BATTLEFIELD_STATE_SIZE =
  259. BATTLEFIELD_STATE_SIZE_GLOBAL + BATTLEFIELD_STATE_SIZE_ONE_PLAYER + BATTLEFIELD_STATE_SIZE_ONE_PLAYER + BATTLEFIELD_STATE_SIZE_ALL_HEXES;
  260. }