kaggle-environments 0.2.1__py3-none-any.whl → 1.20.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kaggle-environments might be problematic. Click here for more details.

Files changed (214) hide show
  1. kaggle_environments/__init__.py +49 -13
  2. kaggle_environments/agent.py +177 -124
  3. kaggle_environments/api.py +31 -0
  4. kaggle_environments/core.py +295 -170
  5. kaggle_environments/envs/cabt/cabt.js +164 -0
  6. kaggle_environments/envs/cabt/cabt.json +28 -0
  7. kaggle_environments/envs/cabt/cabt.py +186 -0
  8. kaggle_environments/envs/cabt/cg/__init__.py +0 -0
  9. kaggle_environments/envs/cabt/cg/cg.dll +0 -0
  10. kaggle_environments/envs/cabt/cg/game.py +75 -0
  11. kaggle_environments/envs/cabt/cg/libcg.so +0 -0
  12. kaggle_environments/envs/cabt/cg/sim.py +48 -0
  13. kaggle_environments/envs/cabt/test_cabt.py +120 -0
  14. kaggle_environments/envs/chess/chess.js +4289 -0
  15. kaggle_environments/envs/chess/chess.json +60 -0
  16. kaggle_environments/envs/chess/chess.py +4241 -0
  17. kaggle_environments/envs/chess/test_chess.py +60 -0
  18. kaggle_environments/envs/connectx/connectx.ipynb +3186 -0
  19. kaggle_environments/envs/connectx/connectx.js +1 -1
  20. kaggle_environments/envs/connectx/connectx.json +15 -1
  21. kaggle_environments/envs/connectx/connectx.py +6 -23
  22. kaggle_environments/envs/connectx/test_connectx.py +70 -24
  23. kaggle_environments/envs/football/football.ipynb +75 -0
  24. kaggle_environments/envs/football/football.json +91 -0
  25. kaggle_environments/envs/football/football.py +277 -0
  26. kaggle_environments/envs/football/helpers.py +95 -0
  27. kaggle_environments/envs/football/test_football.py +360 -0
  28. kaggle_environments/envs/halite/__init__.py +0 -0
  29. kaggle_environments/envs/halite/halite.ipynb +44741 -0
  30. kaggle_environments/envs/halite/halite.js +199 -83
  31. kaggle_environments/envs/halite/halite.json +31 -18
  32. kaggle_environments/envs/halite/halite.py +164 -303
  33. kaggle_environments/envs/halite/helpers.py +720 -0
  34. kaggle_environments/envs/halite/test_halite.py +190 -0
  35. kaggle_environments/envs/hungry_geese/__init__.py +0 -0
  36. kaggle_environments/envs/{battlegeese/battlegeese.js → hungry_geese/hungry_geese.js} +38 -22
  37. kaggle_environments/envs/{battlegeese/battlegeese.json → hungry_geese/hungry_geese.json} +21 -14
  38. kaggle_environments/envs/hungry_geese/hungry_geese.py +316 -0
  39. kaggle_environments/envs/hungry_geese/test_hungry_geese.py +0 -0
  40. kaggle_environments/envs/identity/identity.json +6 -5
  41. kaggle_environments/envs/identity/identity.py +15 -2
  42. kaggle_environments/envs/kore_fleets/__init__.py +0 -0
  43. kaggle_environments/envs/kore_fleets/helpers.py +1005 -0
  44. kaggle_environments/envs/kore_fleets/kore_fleets.ipynb +114 -0
  45. kaggle_environments/envs/kore_fleets/kore_fleets.js +658 -0
  46. kaggle_environments/envs/kore_fleets/kore_fleets.json +164 -0
  47. kaggle_environments/envs/kore_fleets/kore_fleets.py +555 -0
  48. kaggle_environments/envs/kore_fleets/starter_bots/java/Bot.java +54 -0
  49. kaggle_environments/envs/kore_fleets/starter_bots/java/README.md +26 -0
  50. kaggle_environments/envs/kore_fleets/starter_bots/java/jars/hamcrest-core-1.3.jar +0 -0
  51. kaggle_environments/envs/kore_fleets/starter_bots/java/jars/junit-4.13.2.jar +0 -0
  52. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Board.java +518 -0
  53. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Cell.java +61 -0
  54. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Configuration.java +24 -0
  55. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Direction.java +166 -0
  56. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Fleet.java +72 -0
  57. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/KoreJson.java +97 -0
  58. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Observation.java +72 -0
  59. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Pair.java +13 -0
  60. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Player.java +68 -0
  61. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Point.java +65 -0
  62. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Shipyard.java +70 -0
  63. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/ShipyardAction.java +59 -0
  64. kaggle_environments/envs/kore_fleets/starter_bots/java/main.py +73 -0
  65. kaggle_environments/envs/kore_fleets/starter_bots/java/test/BoardTest.java +567 -0
  66. kaggle_environments/envs/kore_fleets/starter_bots/java/test/ConfigurationTest.java +25 -0
  67. kaggle_environments/envs/kore_fleets/starter_bots/java/test/KoreJsonTest.java +62 -0
  68. kaggle_environments/envs/kore_fleets/starter_bots/java/test/ObservationTest.java +46 -0
  69. kaggle_environments/envs/kore_fleets/starter_bots/java/test/PointTest.java +21 -0
  70. kaggle_environments/envs/kore_fleets/starter_bots/java/test/ShipyardTest.java +22 -0
  71. kaggle_environments/envs/kore_fleets/starter_bots/java/test/configuration.json +1 -0
  72. kaggle_environments/envs/kore_fleets/starter_bots/java/test/fullob.json +1 -0
  73. kaggle_environments/envs/kore_fleets/starter_bots/java/test/observation.json +1 -0
  74. kaggle_environments/envs/kore_fleets/starter_bots/python/__init__.py +0 -0
  75. kaggle_environments/envs/kore_fleets/starter_bots/python/main.py +27 -0
  76. kaggle_environments/envs/kore_fleets/starter_bots/ts/Bot.ts +34 -0
  77. kaggle_environments/envs/kore_fleets/starter_bots/ts/DoNothingBot.ts +12 -0
  78. kaggle_environments/envs/kore_fleets/starter_bots/ts/MinerBot.ts +62 -0
  79. kaggle_environments/envs/kore_fleets/starter_bots/ts/README.md +55 -0
  80. kaggle_environments/envs/kore_fleets/starter_bots/ts/interpreter.ts +402 -0
  81. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Board.ts +514 -0
  82. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Cell.ts +63 -0
  83. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Configuration.ts +25 -0
  84. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Direction.ts +169 -0
  85. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Fleet.ts +76 -0
  86. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/KoreIO.ts +70 -0
  87. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Observation.ts +45 -0
  88. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Pair.ts +11 -0
  89. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Player.ts +68 -0
  90. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Point.ts +65 -0
  91. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Shipyard.ts +72 -0
  92. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/ShipyardAction.ts +58 -0
  93. kaggle_environments/envs/kore_fleets/starter_bots/ts/main.py +73 -0
  94. kaggle_environments/envs/kore_fleets/starter_bots/ts/miner.py +73 -0
  95. kaggle_environments/envs/kore_fleets/starter_bots/ts/package.json +23 -0
  96. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/BoardTest.ts +551 -0
  97. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/ConfigurationTest.ts +16 -0
  98. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/ObservationTest.ts +33 -0
  99. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/PointTest.ts +17 -0
  100. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/ShipyardTest.ts +18 -0
  101. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/configuration.json +1 -0
  102. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/fullob.json +1 -0
  103. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/observation.json +1 -0
  104. kaggle_environments/envs/kore_fleets/starter_bots/ts/tsconfig.json +22 -0
  105. kaggle_environments/envs/kore_fleets/test_kore_fleets.py +331 -0
  106. kaggle_environments/envs/lux_ai_2021/README.md +3 -0
  107. kaggle_environments/envs/lux_ai_2021/__init__.py +0 -0
  108. kaggle_environments/envs/lux_ai_2021/agents.py +11 -0
  109. kaggle_environments/envs/lux_ai_2021/dimensions/754.js +2 -0
  110. kaggle_environments/envs/lux_ai_2021/dimensions/754.js.LICENSE.txt +296 -0
  111. kaggle_environments/envs/lux_ai_2021/dimensions/main.js +1 -0
  112. kaggle_environments/envs/lux_ai_2021/index.html +43 -0
  113. kaggle_environments/envs/lux_ai_2021/lux_ai_2021.json +100 -0
  114. kaggle_environments/envs/lux_ai_2021/lux_ai_2021.py +231 -0
  115. kaggle_environments/envs/lux_ai_2021/test_agents/__init__.py +0 -0
  116. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/game_constants.js +6 -0
  117. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/game_constants.json +59 -0
  118. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/game_objects.js +145 -0
  119. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/io.js +14 -0
  120. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/kit.js +209 -0
  121. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/map.js +107 -0
  122. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/parser.js +79 -0
  123. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/main.js +88 -0
  124. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/main.py +75 -0
  125. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/simple.tar.gz +0 -0
  126. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/__init__.py +0 -0
  127. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/annotate.py +20 -0
  128. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/constants.py +25 -0
  129. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game.py +86 -0
  130. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_constants.json +59 -0
  131. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_constants.py +7 -0
  132. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_map.py +106 -0
  133. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_objects.py +154 -0
  134. kaggle_environments/envs/lux_ai_2021/test_agents/python/random_agent.py +38 -0
  135. kaggle_environments/envs/lux_ai_2021/test_agents/python/simple_agent.py +82 -0
  136. kaggle_environments/envs/lux_ai_2021/test_lux.py +19 -0
  137. kaggle_environments/envs/lux_ai_2021/testing.md +23 -0
  138. kaggle_environments/envs/lux_ai_2021/todo.md.og +18 -0
  139. kaggle_environments/envs/lux_ai_s3/README.md +21 -0
  140. kaggle_environments/envs/lux_ai_s3/agents.py +5 -0
  141. kaggle_environments/envs/lux_ai_s3/index.html +42 -0
  142. kaggle_environments/envs/lux_ai_s3/lux_ai_s3.json +47 -0
  143. kaggle_environments/envs/lux_ai_s3/lux_ai_s3.py +178 -0
  144. kaggle_environments/envs/lux_ai_s3/luxai_s3/__init__.py +1 -0
  145. kaggle_environments/envs/lux_ai_s3/luxai_s3/env.py +819 -0
  146. kaggle_environments/envs/lux_ai_s3/luxai_s3/globals.py +9 -0
  147. kaggle_environments/envs/lux_ai_s3/luxai_s3/params.py +101 -0
  148. kaggle_environments/envs/lux_ai_s3/luxai_s3/profiler.py +141 -0
  149. kaggle_environments/envs/lux_ai_s3/luxai_s3/pygame_render.py +222 -0
  150. kaggle_environments/envs/lux_ai_s3/luxai_s3/spaces.py +27 -0
  151. kaggle_environments/envs/lux_ai_s3/luxai_s3/state.py +464 -0
  152. kaggle_environments/envs/lux_ai_s3/luxai_s3/utils.py +12 -0
  153. kaggle_environments/envs/lux_ai_s3/luxai_s3/wrappers.py +156 -0
  154. kaggle_environments/envs/lux_ai_s3/test_agents/python/agent.py +78 -0
  155. kaggle_environments/envs/lux_ai_s3/test_agents/python/lux/__init__.py +0 -0
  156. kaggle_environments/envs/lux_ai_s3/test_agents/python/lux/kit.py +31 -0
  157. kaggle_environments/envs/lux_ai_s3/test_agents/python/lux/utils.py +17 -0
  158. kaggle_environments/envs/lux_ai_s3/test_agents/python/main.py +66 -0
  159. kaggle_environments/envs/lux_ai_s3/test_lux.py +9 -0
  160. kaggle_environments/envs/mab/__init__.py +0 -0
  161. kaggle_environments/envs/mab/agents.py +12 -0
  162. kaggle_environments/envs/mab/mab.js +100 -0
  163. kaggle_environments/envs/mab/mab.json +74 -0
  164. kaggle_environments/envs/mab/mab.py +146 -0
  165. kaggle_environments/envs/open_spiel/__init__.py +0 -0
  166. kaggle_environments/envs/open_spiel/games/__init__.py +0 -0
  167. kaggle_environments/envs/open_spiel/games/chess/chess.js +441 -0
  168. kaggle_environments/envs/open_spiel/games/chess/image_config.jsonl +20 -0
  169. kaggle_environments/envs/open_spiel/games/chess/openings.jsonl +20 -0
  170. kaggle_environments/envs/open_spiel/games/connect_four/__init__.py +0 -0
  171. kaggle_environments/envs/open_spiel/games/connect_four/connect_four.js +284 -0
  172. kaggle_environments/envs/open_spiel/games/connect_four/connect_four_proxy.py +86 -0
  173. kaggle_environments/envs/open_spiel/games/go/__init__.py +0 -0
  174. kaggle_environments/envs/open_spiel/games/go/go.js +481 -0
  175. kaggle_environments/envs/open_spiel/games/go/go_proxy.py +99 -0
  176. kaggle_environments/envs/open_spiel/games/tic_tac_toe/__init__.py +0 -0
  177. kaggle_environments/envs/open_spiel/games/tic_tac_toe/tic_tac_toe.js +345 -0
  178. kaggle_environments/envs/open_spiel/games/tic_tac_toe/tic_tac_toe_proxy.py +98 -0
  179. kaggle_environments/envs/open_spiel/games/universal_poker/__init__.py +0 -0
  180. kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker.js +431 -0
  181. kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker_proxy.py +159 -0
  182. kaggle_environments/envs/open_spiel/html_playthrough_generator.py +31 -0
  183. kaggle_environments/envs/open_spiel/observation.py +128 -0
  184. kaggle_environments/envs/open_spiel/open_spiel.py +565 -0
  185. kaggle_environments/envs/open_spiel/proxy.py +138 -0
  186. kaggle_environments/envs/open_spiel/test_open_spiel.py +191 -0
  187. kaggle_environments/envs/rps/__init__.py +0 -0
  188. kaggle_environments/envs/rps/agents.py +84 -0
  189. kaggle_environments/envs/rps/helpers.py +25 -0
  190. kaggle_environments/envs/rps/rps.js +117 -0
  191. kaggle_environments/envs/rps/rps.json +63 -0
  192. kaggle_environments/envs/rps/rps.py +90 -0
  193. kaggle_environments/envs/rps/test_rps.py +110 -0
  194. kaggle_environments/envs/rps/utils.py +7 -0
  195. kaggle_environments/envs/tictactoe/test_tictactoe.py +43 -77
  196. kaggle_environments/envs/tictactoe/tictactoe.ipynb +1397 -0
  197. kaggle_environments/envs/tictactoe/tictactoe.json +10 -2
  198. kaggle_environments/envs/tictactoe/tictactoe.py +1 -1
  199. kaggle_environments/errors.py +2 -4
  200. kaggle_environments/helpers.py +377 -0
  201. kaggle_environments/main.py +340 -0
  202. kaggle_environments/schemas.json +23 -18
  203. kaggle_environments/static/player.html +206 -74
  204. kaggle_environments/utils.py +46 -73
  205. {kaggle_environments-0.2.1.dist-info → kaggle_environments-1.20.1.dist-info}/METADATA +36 -114
  206. kaggle_environments-1.20.1.dist-info/RECORD +211 -0
  207. {kaggle_environments-0.2.1.dist-info → kaggle_environments-1.20.1.dist-info}/WHEEL +1 -2
  208. kaggle_environments-1.20.1.dist-info/entry_points.txt +3 -0
  209. kaggle_environments/envs/battlegeese/battlegeese.py +0 -223
  210. kaggle_environments/temp.py +0 -14
  211. kaggle_environments-0.2.1.dist-info/RECORD +0 -32
  212. kaggle_environments-0.2.1.dist-info/entry_points.txt +0 -3
  213. kaggle_environments-0.2.1.dist-info/top_level.txt +0 -1
  214. {kaggle_environments-0.2.1.dist-info → kaggle_environments-1.20.1.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,658 @@
1
+ // Copyright 2022 Kaggle Inc
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ async function renderer({
16
+ parent,
17
+ // The gamestep we're rendering, starting at 0 and going by default up to 399.
18
+ step,
19
+ // We render several frames within a step for animation, and use float values in [0, 1] range.
20
+ // Rendering while the game is paused gives frame == 1.0.
21
+ frame,
22
+ // Optional list of agents which will render a legend with player names.
23
+ agents,
24
+ // update fn which lets us pass rendering info for `agents` for the legend.
25
+ update,
26
+ environment,
27
+ width = 800,
28
+ height = 600,
29
+ }) {
30
+ // Configuration.
31
+ const { size } = environment.configuration;
32
+ const directions = ["NORTH", "EAST", "SOUTH", "WEST"];
33
+ const state = environment.steps[step];
34
+ const { kore, players } = state[0].observation;
35
+
36
+ const colors = {
37
+ bg: "#000B49",
38
+ bgGradient: "#000B2A",
39
+ players: ["#1A85FF", "#A50D45", "#FEFE60", "#E36FC7"],
40
+ ships: [
41
+ [
42
+ "#35A0FF",
43
+ "#1A85FF",
44
+ "#026FF2",
45
+ "#0064EA",
46
+ ],
47
+ [
48
+ "#C02858",
49
+ "#A50D45",
50
+ "#8D0038",
51
+ "#7D0030",
52
+ ],
53
+ [
54
+ "#FFFF73",
55
+ "#FEFE60",
56
+ "#E6E853",
57
+ "#D6DD4B",
58
+ ],
59
+ [
60
+ "#FE8ADA",
61
+ "#E36FC7",
62
+ "#CB59BA",
63
+ "#BB4EB2",
64
+ ],
65
+ ],
66
+ };
67
+
68
+ // Rectangle coordinates on a 20x20 grid, with ';' as separator.
69
+ // Each entry is either a color or a list of [x, y, w, h, special, minFrame, maxFrame]
70
+ // with default values of [0, 0, 1, 1, 0, 0, 1] if missing. "special" is a bitmask
71
+ // which indicates to swap across axes to help with mirroring common subimages.
72
+ const rects = {
73
+ largeFleet: [
74
+ "1,1,2,4,4;6,0,2,4,4;9,6,1,4,4;4,7,2,4,4;1,14,2,4,4;6,13,2,4,4;",
75
+ "0,4,1,2,4;3,4,1,2,4;5,3,1,2,4;8,3,1,2,4;8,9,1,2,4;3,10,1,2,4;6,10,1,2,4;0,17,1,2,4;3,17,1,2,4;5,16,1,2,4;8,16,1,2,4;",
76
+ "1,2,2,1,4;6,1,2,1,4;9,7,1,1,4;4,8,2,1,4;1,15,2,1,4;6,14,2,1,4;"
77
+ ],
78
+ ship: [
79
+ "6,0,2,4,4;9,6,1,4,4;4,7,2,4,4;1,14,2,4,4;6,13,2,4,4;",
80
+ "5,3,1,2,4;8,3,1,2,4;8,9,1,2,4;3,10,1,2,4;6,10,1,2,4;0,17,1,2,4;3,17,1,2,4;5,16,1,2,4;8,16,1,2,4;",
81
+ "6,1,2,1,4;9,7,1,1,4;4,8,2,1,4;1,15,2,1,4;6,14,2,1,4;"
82
+ ],
83
+ largeFlame: [
84
+ "#FF972E;1,5,2,1,4;6,4,2,1,4;4,11,2,1,4;9,10,2,1,4;1,18,2,1,4;6,17,2,1,4;",
85
+ "#FEF545;1,6,2,1,4,.5;6,5,2,1,4,.5;4,12,2,1,4,.5;9,11,2,1,4,.5;1,19,2,1,4,.5;6,18,2,1,4,.5;",
86
+ ],
87
+ flame: [
88
+ "#FF972E;6,4,2,1,4,.33;4,11,2,1,4,.33;9,10,2,1,4,.33;1,18,2,1,4,.33;6,17,2,1,4,.33;",
89
+ "#FEF545;6,5,2,1,4,.67;4,12,2,1,4,.67;9,11,2,1,4,.67;1,19,2,1,4,.67;6,18,2,1,4,.67;",
90
+ ],
91
+ shipyard: [
92
+ "",
93
+ "7,0,3,1,14;5,1,5,1,14;3,2,7,1,14;2,3,3,2,14;1,5,3,2,14;0,7,3,3,14;",
94
+ "1,7,1,1,15;2,5,1,1,15;3,3,1,1,15;5,2,1,1,15;7,1,1,1,15;",
95
+ "2,9,8,1,14;9,2,1,8,14;",
96
+ //"0,8,1,1,15;1,6,1,1,15;2,4,1,1,15;4,3,1,1,15;6,2,1,1,15;",
97
+ ],
98
+ explosion: [
99
+ "#C84302BB;7,7,1,1,14;6,9,1,2,4;5,5,1,1,14,0.25;9,5,2,10,0,0.25;6,9,8,2,0,0.25;3,3,1,1,14,0.5;7,6,1,1,15,0.5;5,4,1,1,14,0.75;4,5,1,1,14,0.75;8,5,1,1,14,0.75;9,4,2,1,2,0.75;4,8,1,1,14,0.75;7,2,1,1,14,0.75;",
100
+ "#FF972EBB;9,6,2,1,2;8,9,4,2;4,9,1,2,4,0.25,0.74;9,6,2,8,0,0.25;7,7,6,2,2,0.25;2,9,1,2,4,0.5;6,7,8,2,2,0.75;9,5,2,10,0,0.75;8,6,4,8,0,0.5;6,6,1,1,14,0.75;5,5,1,1,14,0.75;5,7,1,1,14,0.75;7,4,1,1,14,0.75;",
101
+ "#FEF545BB;9,8,2,4;9,7,2,6,0,0.25;8,8,4,4,0,0.25;8,7,1,1,14,0.5;8,7,4,6,1,0.75;9,6,2,1,2,0.75;",
102
+ "#FFF5FFBB;9,9,2,2;8,9,4,2,1,0.25;7,9,6,2,0,0.5;9,7,2,6,0,0.75",
103
+ ],
104
+ largeKore: [
105
+ "#008DFF;17,6;2,13;9,1,2,18,1;5,7,10,6,1",
106
+ "#00C9FF;9,3,2,14;3,9,14,2",
107
+ "#00FFFF;6,2;13,17;4,9,12,2,1;7,7,6,6",
108
+ "#FFFFFF;13,2;17,13;2,6;6,17;6,9,8,2,1",
109
+ ],
110
+ mediumKore: [
111
+ "#008DFF;6,4;16,7;16,12;6,15;4,9,12,2,1;6,8,8,4,1",
112
+ "#00C9FF;9,5,2,10,1;",
113
+ "#00FFFF;13,4;3,7;3,12;13,15;9,6,2,8,1;8,8,4,4",
114
+ "#FFFFFF;9,7,2,6,1",
115
+ ],
116
+ smallKore: [
117
+ "#008DFF;13.5,6.5;13.5,12.5;9.5,5.5,1,9,1;8.5,6.5,3,7,1",
118
+ "#00C9FF;9.5,6.5,1,7,1",
119
+ "#00FFFF;5.5,6.5;5.5,12.5;9.5,7.5,1,5,1;8.5,8.5,3,3",
120
+ "#FFFFFF;9.5,8.5,1,3,1",
121
+ ],
122
+ };
123
+
124
+ const upgrade_times = [...Array(10).keys()].map(num => num * num + 1).slice(1);
125
+ const SPAWN_VALUES = upgrade_times.slice(1).reduce((arr, num) => arr.concat(arr.at(-1) + num), [upgrade_times[0]])
126
+ const getSpawnValue = (turnsControlled) => {
127
+ for (let i = 0; i < SPAWN_VALUES.length; i++) {
128
+ if (turnsControlled < SPAWN_VALUES[i]) {
129
+ return i + 1;
130
+ }
131
+ }
132
+ return SPAWN_VALUES.length + 1;
133
+ }
134
+
135
+
136
+ // Helper Functions.
137
+ const createElement = (type, id) => {
138
+ const el = document.createElement(type);
139
+ el.id = id;
140
+ parent.appendChild(el);
141
+ return el;
142
+ };
143
+
144
+ const getCanvas = (id, options = { clear: false, alpha: false }) => {
145
+ let canvas = document.querySelector(`#${id}`);
146
+ if (!canvas) {
147
+ canvas = createElement("canvas", id);
148
+ canvas.width = options.width || width;
149
+ canvas.height = options.height || height;
150
+ canvas.style.cssText = `
151
+ position: absolute;
152
+ top: 0;
153
+ left: 0;
154
+ width: 100%;
155
+ height: 100%;
156
+ `;
157
+ }
158
+ const ctx = canvas.getContext("2d", { alpha: options.alpha });
159
+ if (options.clear) ctx.clearRect(0, 0, canvas.width, canvas.height);
160
+ return [canvas, ctx];
161
+ };
162
+
163
+ const data = function(selector, key, value) {
164
+ const el =
165
+ typeof selector === "string"
166
+ ? document.querySelector(selector)
167
+ : selector;
168
+ if (arguments.length === 3) {
169
+ el.setAttribute(`data-${key}`, JSON.stringify(value));
170
+ return value;
171
+ }
172
+ if (el.hasAttribute(`data-${key}`)) {
173
+ return JSON.parse(el.getAttribute(`data-${key}`));
174
+ }
175
+ return null;
176
+ };
177
+
178
+ const move = (ctx, options = {}, fn) => {
179
+ const { x, y, width, height, angle, scale } = {
180
+ x: 0,
181
+ y: 0,
182
+ width: 100,
183
+ height: 100,
184
+ angle: 0,
185
+ ...options,
186
+ };
187
+ ctx.save();
188
+ ctx.translate(x, y);
189
+ if (scale) ctx.scale(scale, scale);
190
+ if (angle) {
191
+ ctx.translate(width / 2, height / 2);
192
+ ctx.rotate((Math.PI * angle) / 180);
193
+ ctx.translate(-width / 2, -height / 2);
194
+ }
195
+ fn();
196
+ ctx.restore();
197
+ };
198
+
199
+ const drawRects = (
200
+ ctx,
201
+ rects,
202
+ color,
203
+ scale = 1,
204
+ gridSize = 20,
205
+ drawFrame = -1
206
+ ) => {
207
+ if (drawFrame == -1) drawFrame = frame;
208
+ // rects="x,y,w,h,specials,minFrame,maxFrame;..."
209
+ ctx.save();
210
+ if (color) ctx.fillStyle = color;
211
+ ctx.beginPath();
212
+ const drawSpecials = (x, y, w, h, special) => {
213
+ const size = gridSize * scale;
214
+ if ((special & 1) === 1) ctx.rect(y, x, h, w); // swap x/y and w/h
215
+ if ((special & 2) === 2) ctx.rect(x, size - y - h, w, h); // Mirror over X Axis
216
+ if ((special & 4) === 4) ctx.rect(size - x - w, y, w, h); // Mirror over Y Axis
217
+ if ((special & 8) === 8) ctx.rect(size - x - w, size - y - h, w, h); // Mirror over X & Y Axis
218
+ // Repeat mirroring if a swap occurred.
219
+ if ((special & 1) === 1) drawSpecials(y, x, h, w, special - 1);
220
+ };
221
+ rects
222
+ .replace(/\s/g, "")
223
+ .split(";")
224
+ .filter(r => !!r)
225
+ .forEach(coords => {
226
+ // Apply a fill style.
227
+ if (coords[0] == "#" || coords[0] == "r") {
228
+ ctx.fillStyle = coords;
229
+ return;
230
+ }
231
+ const defaultCoords = ["0", "0", "1", "1", "0", "0", "1"];
232
+ coords = coords.split(",");
233
+ let [x, y, w, h, special, minFrame, maxFrame] = defaultCoords.map(
234
+ (v, i) =>
235
+ parseFloat(coords.length > i ? coords[i] : v) * (i < 4 ? scale : 1)
236
+ );
237
+ if (minFrame > drawFrame || maxFrame < drawFrame) return;
238
+ ctx.rect(x, y, w, h);
239
+ drawSpecials(x, y, w, h, special);
240
+ });
241
+ ctx.fill();
242
+ ctx.closePath();
243
+ ctx.restore();
244
+ };
245
+
246
+ const getColRow = pos => [pos % size, Math.floor(pos / size)];
247
+
248
+ const getDirStrFromIdx = (dirIdx) => {
249
+ switch(dirIdx) {
250
+ case 0:
251
+ return "NORTH"
252
+ case 1:
253
+ return "EAST"
254
+ case 2:
255
+ return "SOUTH"
256
+ case 3:
257
+ return "WEST"
258
+ default:
259
+ throw new Error(`"${dirIdx}" is not a valid direction idx.`);
260
+ }
261
+ }
262
+
263
+ const getDirStrFromChar = (dirChar) => {
264
+ switch(dirChar) {
265
+ case "N":
266
+ return "NORTH"
267
+ case "E":
268
+ return "EAST"
269
+ case "S":
270
+ return "SOUTH"
271
+ case "W":
272
+ return "WEST"
273
+ default:
274
+ throw new Error(`"${dirChar}" is not a valid direction char.`);
275
+ }
276
+ }
277
+
278
+ const getMovePos = (pos, direction) => {
279
+ const [col, row] = getColRow(pos);
280
+ switch (direction) {
281
+ case "NORTH":
282
+ return pos >= size ? pos - size : Math.pow(size, 2) - size + col;
283
+ case "SOUTH":
284
+ return pos + size >= Math.pow(size, 2) ? col : pos + size;
285
+ case "EAST":
286
+ return col < size - 1 ? pos + 1 : row * size;
287
+ case "WEST":
288
+ return col > 0 ? pos - 1 : (row + 1) * size - 1;
289
+ default:
290
+ throw new Error(`"${direction}" is not a valid move action.`);
291
+ }
292
+ };
293
+
294
+ const getCoords = pos => {
295
+ const [col, row] = getColRow(pos);
296
+ return {
297
+ col,
298
+ row,
299
+ scale: cellScale * cellInset,
300
+ dx: xOffset + cellSize * col + (cellSize - cellSize * cellInset) / 2,
301
+ dy: yOffset + cellSize * row + (cellSize - cellSize * cellInset) / 2,
302
+ ds: cellScale * cellInset * fixedCellSize,
303
+ ss: fixedCellSize,
304
+ };
305
+ };
306
+
307
+ // First time setup.
308
+ if (!parent.querySelector("#buffer")) {
309
+ const [bufferCanvas, ctx] = getCanvas("buffer", {
310
+ alpha: true,
311
+ clear: false,
312
+ width: 900,
313
+ height: 700,
314
+ });
315
+
316
+ // Setup common fields.
317
+ const cellInset = 0.8;
318
+ const fixedCellSize = 100;
319
+ const minOffset = Math.min(height, width) > 400 ? 30 : 4;
320
+ const cellSize = Math.min(
321
+ (width - minOffset * 2) / size,
322
+ (height - minOffset * 2) / size
323
+ );
324
+ const koreRotations = Array(size * size)
325
+ .fill(0)
326
+ .map(_ => Math.random() * 360);
327
+
328
+ data(bufferCanvas, "storage", {
329
+ cellInset,
330
+ cellScale: cellSize / fixedCellSize,
331
+ cellSize,
332
+ fixedCellSize,
333
+ koreRotations,
334
+ maxCellKore: Math.max(...kore),
335
+ xOffset: Math.max(0, (width - cellSize * size) / 2),
336
+ yOffset: Math.max(0, (height - cellSize * size) / 2),
337
+ });
338
+
339
+ // Pre-render visualizations (100x100 cells).
340
+ // Kore
341
+ ["largeKore", "mediumKore", "smallKore"].forEach((rectsName, i) => {
342
+ move(ctx, { x: 0, y: 100 * i }, () => {
343
+ rects[rectsName].forEach(v => drawRects(ctx, v, null, 5));
344
+ });
345
+ });
346
+ // Explosions.
347
+ for (let s = 0; s < 4; s++) {
348
+ move(ctx, { x: 100, y: 100 * s }, () => {
349
+ rects.explosion.forEach(v => drawRects(ctx, v, null, 5, 20, 1 - s / 3));
350
+ });
351
+ }
352
+ // Flames.
353
+ for (let s = 0; s < 3; s++) {
354
+ for (let d in directions) {
355
+ move(ctx, { x: 200 + s * 100, y: 100 * d, angle: d * 90 }, () => {
356
+ rects.flame.forEach(v => drawRects(ctx, v, null, 5, 20, s / 3));
357
+ });
358
+ }
359
+ }
360
+ // Ships.
361
+ colors.ships.forEach((color, n) => {
362
+ for (let d in directions) {
363
+ move(ctx, { x: 500 + 100 * n, y: d * 100, angle: d * 90 }, () => {
364
+ rects.ship.forEach((v, i) => drawRects(ctx, v, color[i], 5));
365
+ });
366
+ }
367
+ });
368
+ // Shipyards.
369
+ colors.ships.forEach((color, n) => {
370
+ move(ctx, { x: 500 + 100 * n, y: 400 }, () => {
371
+ rects.shipyard.forEach((v, i) => drawRects(ctx, v, color[i], 5));
372
+ });
373
+ })
374
+ }
375
+
376
+ // Restore Canvases.
377
+ const [bufferCanvas] = getCanvas("buffer", {
378
+ alpha: true,
379
+ clear: false,
380
+ });
381
+ const [bgCanvas, bgCtx] = getCanvas("background", {
382
+ alpha: true,
383
+ clear: false,
384
+ });
385
+ const [, fgCtx] = getCanvas("foreground", {
386
+ alpha: true,
387
+ clear: true,
388
+ });
389
+
390
+
391
+ // Expand storage.
392
+ const {
393
+ cellInset,
394
+ cellScale,
395
+ cellSize,
396
+ fixedCellSize,
397
+ koreRotations,
398
+ maxCellKore,
399
+ xOffset,
400
+ yOffset,
401
+ } = data(bufferCanvas, "storage");
402
+
403
+ const topLeftCell = getCoords(0);
404
+ const botRightCell = getCoords(size * size - 1);
405
+
406
+ const renderKore = (ctx, pos, kore, maxKore, scaleFactor, rotate) => {
407
+ if (kore <= 0) return;
408
+
409
+ let { dx, dy, ds, ss } = getCoords(pos);
410
+ let sx = 0;
411
+ let sy = 0;
412
+
413
+ const pct = Math.min(1, kore / maxKore);
414
+ let scale = 1;
415
+
416
+ // Scale by the kore size.
417
+ if (pct > 0.7) {
418
+ scale = pct;
419
+ } else if (pct > 0.3) {
420
+ sy = 100;
421
+ scale = pct + 0.3;
422
+ } else {
423
+ sy = 200;
424
+ scale = pct * 3;
425
+ }
426
+
427
+ // Apply the scale.
428
+ scale = Math.max(0.3, scaleFactor * scale);
429
+ dx += (ds - ds * scale) / 2;
430
+ dy += (ds - ds * scale) / 2;
431
+ ds *= scale;
432
+
433
+ // Rotate the kore to get a bit of randomness, if desired.
434
+ move(
435
+ ctx,
436
+ { x: dx, y: dy, width: ds, height: ds, angle: rotate ? koreRotations[pos] : 0 },
437
+ () => ctx.drawImage(bufferCanvas, sx, sy, ss, ss, 0, 0, ds, ds)
438
+ );
439
+ }
440
+
441
+ // Render Background once per step (Gradient + Kore)
442
+ const boxPadding = height * 0.007;
443
+ if (data(bgCanvas, "step") !== step) {
444
+ data(bgCanvas, "step", step);
445
+ bgCtx.fillStyle = colors.bg;
446
+ bgCtx.fillRect(0, 0, bgCanvas.width, bgCanvas.height);
447
+
448
+ const r = Math.min(height, width) / 2;
449
+ const bgStyle = bgCtx.createRadialGradient(r, r, 0, r, r, r);
450
+ bgStyle.addColorStop(0, colors.bg);
451
+ bgStyle.addColorStop(1, colors.bgGradient);
452
+ bgCtx.fillStyle = bgStyle;
453
+ bgCtx.fillRect(0, 0, bgCanvas.width, bgCanvas.height);
454
+
455
+ // Render bounding box.
456
+ bgCtx.strokeStyle = "white";
457
+ bgCtx.lineWidth = 0.5;
458
+ bgCtx.strokeRect(
459
+ topLeftCell.dx - boxPadding,
460
+ topLeftCell.dy - boxPadding,
461
+ botRightCell.dx + botRightCell.ds - topLeftCell.dx + 2 * boxPadding,
462
+ botRightCell.dy + botRightCell.ds - topLeftCell.dy + 2 * boxPadding);
463
+
464
+ // Render the kore.
465
+ kore.forEach((cellKore, pos) => renderKore(bgCtx, pos, cellKore, 500, 1, true));
466
+ }
467
+
468
+ // Render Foreground (every frame).
469
+
470
+ // Draw Shipyards.
471
+ const shipFontSizePx = Math.round(height / 64);
472
+ fgCtx.fillStyle = "#FFFFFF";
473
+ fgCtx.textBaseline = "top";
474
+ fgCtx.textAlign = "left";
475
+ players.forEach((player, playerIndex) => {
476
+ Object.entries(player[1]).forEach(([uid, [pos, shipCount, turnsControlled]]) => {
477
+ const shipx = 500 + 100 * playerIndex;
478
+ const ss = fixedCellSize;
479
+ const { dx, dy, ds } = getCoords(pos);
480
+ fgCtx.drawImage(bufferCanvas, shipx, 400, ss, ss, dx, dy, ds, ds);
481
+ fgCtx.font = `bold ${shipFontSizePx}px sans-serif`;
482
+ fgCtx.fillText(shipCount, dx, dy)
483
+ const spawnValue = getSpawnValue(turnsControlled);
484
+ fgCtx.font = `bold ${Math.round(.7 * shipFontSizePx)}px sans-serif`;
485
+ fgCtx.fillText(spawnValue, dx + Math.round(.85 * ds), dy + Math.round(.85 * ds));
486
+ });
487
+ });
488
+
489
+ // Draw Ships and a smaller Kore icon according to their current cargo.
490
+ players.forEach((player, playerIndex) => {
491
+ Object.entries(player[2]).forEach(([uid, [pos, cargo, shipCount, directionIdx, flightPath]]) => {
492
+ const shipx = 500 + 100 * playerIndex;
493
+ const flamex = 200 + 100 * Math.min(2, Math.floor(3 * frame));
494
+ const { dx, dy, ds } = getCoords(pos);
495
+ const sy = directionIdx * 100;
496
+ const ss = fixedCellSize;
497
+ fgCtx.drawImage(bufferCanvas, shipx, sy, ss, ss, dx, dy, ds, ds);
498
+ fgCtx.drawImage(bufferCanvas, flamex, sy, ss, ss, dx, dy, ds, ds);
499
+ fgCtx.fillText(shipCount, dx, dy)
500
+ renderKore(fgCtx, pos, cargo, 1500, 0.6, false);
501
+ });
502
+ });
503
+
504
+ // Draw collisions.
505
+ if (step > 1) {
506
+ const board = Array(size * size)
507
+ .fill(0)
508
+ .map(() => ({ shipyard: -1, ship: null, collision: false, shipPlayer: null }));
509
+ players.forEach((player, playerIndex) => {
510
+ const [playerInfo, shipyards, ships] = player;
511
+ Object.entries(shipyards).forEach(([uid, [pos, shipCount, turnsControlled]]) => (board[pos].shipyard = playerIndex));
512
+ Object.entries(ships).forEach(([uid, [pos, cargo, shipCount, directionIdx, flightPath]]) => {
513
+ board[pos].ship = uid;
514
+ board[pos].shipPlayer = playerIndex
515
+ });
516
+ });
517
+ environment.steps[step - 1][0].observation.players.forEach(
518
+ (player, playerIndex) => {
519
+ const status = state[playerIndex].status;
520
+ const [, shipyards, fleets] = player;
521
+ const action = environment.steps[step][playerIndex].action || {};
522
+
523
+ Object.entries(fleets).forEach(([uid, [pos, cargo, shipCount, directionIdx, flightPath]]) => {
524
+ var dir = getDirStrFromIdx(directionIdx)
525
+ while (flightPath.length > 0 && flightPath[0] == "0") {
526
+ flightPath = flightPath.substring(1);
527
+ }
528
+ if (flightPath.length > 0 && "NESW".includes(flightPath[0])) {
529
+ dir = getDirStrFromChar(flightPath[0])
530
+ }
531
+ const toPos = getMovePos(pos, dir);
532
+ // if there is an enemy shipyard in the next square,
533
+ const shipyardNextSquare = board[toPos].shipyard !== -1
534
+ const shipyardPreviousSquare = board[pos].shipyard !== -1
535
+ const enemyOrNoShipNextSquare = board[toPos].shipPlayer !== playerIndex
536
+ const enemyShipyardNextSquare = shipyardNextSquare && board[toPos].shipyard !== playerIndex
537
+ const alliedShipyardNextSquare = shipyardNextSquare && board[toPos].shipyard === playerIndex
538
+ const alliedShipyardPreviousSquare = shipyardPreviousSquare && board[pos].shipyard === playerIndex
539
+ if (alliedShipyardPreviousSquare || alliedShipyardNextSquare) {
540
+ // don't explode
541
+ } else if (enemyShipyardNextSquare) {
542
+ board[toPos].collision = true
543
+ } else if (enemyOrNoShipNextSquare) {
544
+ board[toPos].collision = true;
545
+ }
546
+ });
547
+ }
548
+ );
549
+
550
+ board.forEach(({ collision }, pos) => {
551
+ if (!collision) return;
552
+ const { dx, dy, ds, ss } = getCoords(pos);
553
+ const sx = 100;
554
+ const sy = 100 * Math.round(4 * (1 - frame));
555
+ fgCtx.drawImage(bufferCanvas, sx, sy, ss, ss, dx, dy, ds, ds);
556
+ });
557
+ }
558
+
559
+ const scoreboardFontSizePx = Math.round(height / 36);
560
+ const scoreboardPaddingPx = Math.max(1, scoreboardFontSizePx / 4);
561
+ const scoreboardLineYDiffPx = scoreboardFontSizePx + scoreboardPaddingPx;
562
+
563
+ const getKore = player => player[0];
564
+ const getCargo = player => Object.entries(player[2]).map(([, v]) => v[1]).reduce((a, b) => a + b, 0);
565
+ const getShipCount = player => Object.entries(player[2]).map(([, v]) => v[2]).reduce((a, b) => a + b, 0) + Object.entries(player[1]).map(([, v]) => v[1]).reduce((a, b) => a + b, 0);
566
+ const getNumFleets = player => Object.entries(player[2]).length;
567
+ const getNumShipyards = player => Object.entries(player[1]).length;
568
+
569
+ // Writes two lines, "Kore" and "Cargo", and returns y value for what would be the third line.
570
+ const writeScoreboardText = (ctx, player, x, y) => {
571
+ ctx.fillText(`Kore: ${Math.floor(getKore(player))}`, x, y);
572
+ ctx.fillText(`Cargo: ${Math.floor(getCargo(player))}`, x, y + scoreboardLineYDiffPx);
573
+ ctx.fillText(`Ships: ${getShipCount(player)}`, x, y + 2 * scoreboardLineYDiffPx);
574
+ return y + 3 * scoreboardLineYDiffPx;
575
+ }
576
+
577
+ const scoreboardShipSizePx = scoreboardFontSizePx * 1.7;
578
+ const drawShip = (ctx, playerIndex, x, y, iconSize = scoreboardShipSizePx) => ctx.drawImage(
579
+ bufferCanvas, 500 + 100 * playerIndex, 0, fixedCellSize, fixedCellSize,
580
+ x, y, iconSize, iconSize);
581
+ const drawShipYard = (ctx, playerIndex, x, y, iconSize = scoreboardShipSizePx) => ctx.drawImage(
582
+ bufferCanvas, 500 + 100 * playerIndex, 400, fixedCellSize, fixedCellSize,
583
+ x, y, iconSize, iconSize);
584
+
585
+ const scoreboardShipXPaddingPx = scoreboardShipSizePx + scoreboardPaddingPx;
586
+ const drawShipAndYardCounts = (ctx, player, playerIndex, x, y, iconSize = scoreboardShipSizePx) => {
587
+ drawShip(ctx, playerIndex, x, y);
588
+ ctx.fillText(`x ${getNumFleets(player)}`, x + scoreboardShipXPaddingPx, y + 0.28 * iconSize);
589
+ drawShipYard(ctx, playerIndex, x, y + iconSize);
590
+ ctx.fillText(`x ${getNumShipyards(player)}`, x + scoreboardShipXPaddingPx, y + 1.38 * iconSize);
591
+ return y + 2.38 * iconSize;
592
+ }
593
+
594
+ const drawFleetLaunches = (ctx, player, playerIndex, x, y, reverse) => {
595
+ const actions = environment.steps[step][playerIndex].action || {};
596
+ const launches = Object.values(actions).filter(a => a.includes("LAUNCH")).map(a => a.substring(7).replace(/_/, " ")).sort((a, b) => parseInt(a.split(" ")[0]) < parseInt(b.split(" ")[0]) ? 1: -1);
597
+ if (launches.length > 0) {
598
+ ctx.fillText("Launches:", x, y);
599
+ }
600
+ const dir = reverse ? -1 : 1;
601
+ for (let i = 0; i < Math.min(launches.length, 5); i++) {
602
+ ctx.fillText(launches[i], x, y + dir * (i + 1) * scoreboardLineYDiffPx);
603
+ }
604
+ if (launches.length > 5) {
605
+ ctx.fillText(`+ ${launches.length - 5} more...`, x, y + dir * 6 * scoreboardLineYDiffPx);
606
+
607
+ }
608
+
609
+ }
610
+
611
+ // Render Scoreboard for each player, if we have enough room on the sides of the window.
612
+ if (width / height >= 1.3) {
613
+ fgCtx.fillStyle = "#FFFFFF";
614
+ fgCtx.font = `normal ${scoreboardFontSizePx}px sans-serif`;
615
+ fgCtx.textBaseline = "top";
616
+ fgCtx.textAlign = "left";
617
+ const topStartY = topLeftCell.dy;
618
+ const bottomStartY = botRightCell.dy + botRightCell.ds - 2 * scoreboardShipSizePx - 2 * scoreboardLineYDiffPx;
619
+ players.forEach((player, playerIndex) => {
620
+ const x = playerIndex % 2 === 1
621
+ ? Math.max(
622
+ // Make sure we don't start within the game area on the right side.
623
+ botRightCell.dx + botRightCell.ds + 2 * boxPadding,
624
+ width - topLeftCell.dy - 5.5 * scoreboardFontSizePx)
625
+ : topLeftCell.dy;
626
+ const startY = playerIndex < 2 ? topStartY : bottomStartY;
627
+ const nextY = writeScoreboardText(fgCtx, player, x, startY);
628
+ const actionY = drawShipAndYardCounts(fgCtx, player, playerIndex, x, nextY);
629
+ if (playerIndex > 1) {
630
+ drawFleetLaunches(fgCtx, player, playerIndex, x, startY - scoreboardLineYDiffPx, true)
631
+ } else {
632
+ drawFleetLaunches(fgCtx, player, playerIndex, x, actionY)
633
+ }
634
+ });
635
+ }
636
+
637
+ // Populate the legend which renders agent icons and names (see player.html).
638
+ if (agents && agents.length && (!agents[0].color || !agents[0].image)) {
639
+ const getPieceImage = playerIndex => {
640
+ const pieceCanvas = document.createElement("canvas");
641
+ parent.appendChild(pieceCanvas);
642
+ pieceCanvas.style.marginLeft = "10000px";
643
+ pieceCanvas.width = 100;
644
+ pieceCanvas.height = 100;
645
+ ctx = pieceCanvas.getContext("2d");
646
+ drawShip(ctx, playerIndex, 0, 0, 100);
647
+ const dataUrl = pieceCanvas.toDataURL();
648
+ parent.removeChild(pieceCanvas);
649
+ return dataUrl;
650
+ };
651
+
652
+ agents.forEach(agent => {
653
+ agent.color = "#FFFFFF";
654
+ agent.image = getPieceImage(agent.index);
655
+ });
656
+ update({ agents });
657
+ }
658
+ }