kaggle-environments 0.2.1__py3-none-any.whl → 1.20.0__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.
- kaggle_environments/__init__.py +49 -13
- kaggle_environments/agent.py +177 -124
- kaggle_environments/api.py +31 -0
- kaggle_environments/core.py +295 -170
- kaggle_environments/envs/cabt/cabt.js +164 -0
- kaggle_environments/envs/cabt/cabt.json +28 -0
- kaggle_environments/envs/cabt/cabt.py +186 -0
- kaggle_environments/envs/cabt/cg/__init__.py +0 -0
- kaggle_environments/envs/cabt/cg/cg.dll +0 -0
- kaggle_environments/envs/cabt/cg/game.py +75 -0
- kaggle_environments/envs/cabt/cg/libcg.so +0 -0
- kaggle_environments/envs/cabt/cg/sim.py +48 -0
- kaggle_environments/envs/cabt/test_cabt.py +120 -0
- kaggle_environments/envs/chess/chess.js +4289 -0
- kaggle_environments/envs/chess/chess.json +60 -0
- kaggle_environments/envs/chess/chess.py +4241 -0
- kaggle_environments/envs/chess/test_chess.py +60 -0
- kaggle_environments/envs/connectx/connectx.ipynb +3186 -0
- kaggle_environments/envs/connectx/connectx.js +1 -1
- kaggle_environments/envs/connectx/connectx.json +15 -1
- kaggle_environments/envs/connectx/connectx.py +6 -23
- kaggle_environments/envs/connectx/test_connectx.py +70 -24
- kaggle_environments/envs/football/football.ipynb +75 -0
- kaggle_environments/envs/football/football.json +91 -0
- kaggle_environments/envs/football/football.py +277 -0
- kaggle_environments/envs/football/helpers.py +95 -0
- kaggle_environments/envs/football/test_football.py +360 -0
- kaggle_environments/envs/halite/__init__.py +0 -0
- kaggle_environments/envs/halite/halite.ipynb +44741 -0
- kaggle_environments/envs/halite/halite.js +199 -83
- kaggle_environments/envs/halite/halite.json +31 -18
- kaggle_environments/envs/halite/halite.py +164 -303
- kaggle_environments/envs/halite/helpers.py +720 -0
- kaggle_environments/envs/halite/test_halite.py +190 -0
- kaggle_environments/envs/hungry_geese/__init__.py +0 -0
- kaggle_environments/envs/{battlegeese/battlegeese.js → hungry_geese/hungry_geese.js} +38 -22
- kaggle_environments/envs/{battlegeese/battlegeese.json → hungry_geese/hungry_geese.json} +21 -14
- kaggle_environments/envs/hungry_geese/hungry_geese.py +316 -0
- kaggle_environments/envs/hungry_geese/test_hungry_geese.py +0 -0
- kaggle_environments/envs/identity/identity.json +6 -5
- kaggle_environments/envs/identity/identity.py +15 -2
- kaggle_environments/envs/kore_fleets/__init__.py +0 -0
- kaggle_environments/envs/kore_fleets/helpers.py +1005 -0
- kaggle_environments/envs/kore_fleets/kore_fleets.ipynb +114 -0
- kaggle_environments/envs/kore_fleets/kore_fleets.js +658 -0
- kaggle_environments/envs/kore_fleets/kore_fleets.json +164 -0
- kaggle_environments/envs/kore_fleets/kore_fleets.py +555 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/Bot.java +54 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/README.md +26 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/jars/hamcrest-core-1.3.jar +0 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/jars/junit-4.13.2.jar +0 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Board.java +518 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Cell.java +61 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Configuration.java +24 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Direction.java +166 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Fleet.java +72 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/KoreJson.java +97 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Observation.java +72 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Pair.java +13 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Player.java +68 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Point.java +65 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Shipyard.java +70 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/ShipyardAction.java +59 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/main.py +73 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/BoardTest.java +567 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/ConfigurationTest.java +25 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/KoreJsonTest.java +62 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/ObservationTest.java +46 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/PointTest.java +21 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/ShipyardTest.java +22 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/configuration.json +1 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/fullob.json +1 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/observation.json +1 -0
- kaggle_environments/envs/kore_fleets/starter_bots/python/__init__.py +0 -0
- kaggle_environments/envs/kore_fleets/starter_bots/python/main.py +27 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/Bot.ts +34 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/DoNothingBot.ts +12 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/MinerBot.ts +62 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/README.md +55 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/interpreter.ts +402 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Board.ts +514 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Cell.ts +63 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Configuration.ts +25 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Direction.ts +169 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Fleet.ts +76 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/KoreIO.ts +70 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Observation.ts +45 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Pair.ts +11 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Player.ts +68 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Point.ts +65 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Shipyard.ts +72 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/ShipyardAction.ts +58 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/main.py +73 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/miner.py +73 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/package.json +23 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/BoardTest.ts +551 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/ConfigurationTest.ts +16 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/ObservationTest.ts +33 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/PointTest.ts +17 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/ShipyardTest.ts +18 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/configuration.json +1 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/fullob.json +1 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/observation.json +1 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/tsconfig.json +22 -0
- kaggle_environments/envs/kore_fleets/test_kore_fleets.py +331 -0
- kaggle_environments/envs/lux_ai_2021/README.md +3 -0
- kaggle_environments/envs/lux_ai_2021/__init__.py +0 -0
- kaggle_environments/envs/lux_ai_2021/agents.py +11 -0
- kaggle_environments/envs/lux_ai_2021/dimensions/754.js +2 -0
- kaggle_environments/envs/lux_ai_2021/dimensions/754.js.LICENSE.txt +296 -0
- kaggle_environments/envs/lux_ai_2021/dimensions/main.js +1 -0
- kaggle_environments/envs/lux_ai_2021/index.html +43 -0
- kaggle_environments/envs/lux_ai_2021/lux_ai_2021.json +100 -0
- kaggle_environments/envs/lux_ai_2021/lux_ai_2021.py +231 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/__init__.py +0 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/game_constants.js +6 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/game_constants.json +59 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/game_objects.js +145 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/io.js +14 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/kit.js +209 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/map.js +107 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/parser.js +79 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/main.js +88 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/main.py +75 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/simple.tar.gz +0 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/__init__.py +0 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/annotate.py +20 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/constants.py +25 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game.py +86 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_constants.json +59 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_constants.py +7 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_map.py +106 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_objects.py +154 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/random_agent.py +38 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/simple_agent.py +82 -0
- kaggle_environments/envs/lux_ai_2021/test_lux.py +19 -0
- kaggle_environments/envs/lux_ai_2021/testing.md +23 -0
- kaggle_environments/envs/lux_ai_2021/todo.md.og +18 -0
- kaggle_environments/envs/lux_ai_s3/README.md +21 -0
- kaggle_environments/envs/lux_ai_s3/agents.py +5 -0
- kaggle_environments/envs/lux_ai_s3/index.html +42 -0
- kaggle_environments/envs/lux_ai_s3/lux_ai_s3.json +47 -0
- kaggle_environments/envs/lux_ai_s3/lux_ai_s3.py +178 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/__init__.py +1 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/env.py +819 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/globals.py +9 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/params.py +101 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/profiler.py +141 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/pygame_render.py +222 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/spaces.py +27 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/state.py +464 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/utils.py +12 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/wrappers.py +156 -0
- kaggle_environments/envs/lux_ai_s3/test_agents/python/agent.py +78 -0
- kaggle_environments/envs/lux_ai_s3/test_agents/python/lux/__init__.py +0 -0
- kaggle_environments/envs/lux_ai_s3/test_agents/python/lux/kit.py +31 -0
- kaggle_environments/envs/lux_ai_s3/test_agents/python/lux/utils.py +17 -0
- kaggle_environments/envs/lux_ai_s3/test_agents/python/main.py +66 -0
- kaggle_environments/envs/lux_ai_s3/test_lux.py +9 -0
- kaggle_environments/envs/mab/__init__.py +0 -0
- kaggle_environments/envs/mab/agents.py +12 -0
- kaggle_environments/envs/mab/mab.js +100 -0
- kaggle_environments/envs/mab/mab.json +74 -0
- kaggle_environments/envs/mab/mab.py +146 -0
- kaggle_environments/envs/open_spiel/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/chess/chess.js +441 -0
- kaggle_environments/envs/open_spiel/games/chess/image_config.jsonl +20 -0
- kaggle_environments/envs/open_spiel/games/chess/openings.jsonl +20 -0
- kaggle_environments/envs/open_spiel/games/connect_four/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/connect_four/connect_four.js +284 -0
- kaggle_environments/envs/open_spiel/games/connect_four/connect_four_proxy.py +86 -0
- kaggle_environments/envs/open_spiel/games/go/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/go/go.js +481 -0
- kaggle_environments/envs/open_spiel/games/go/go_proxy.py +99 -0
- kaggle_environments/envs/open_spiel/games/tic_tac_toe/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/tic_tac_toe/tic_tac_toe.js +345 -0
- kaggle_environments/envs/open_spiel/games/tic_tac_toe/tic_tac_toe_proxy.py +98 -0
- kaggle_environments/envs/open_spiel/games/universal_poker/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker.js +431 -0
- kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker_proxy.py +159 -0
- kaggle_environments/envs/open_spiel/html_playthrough_generator.py +31 -0
- kaggle_environments/envs/open_spiel/observation.py +128 -0
- kaggle_environments/envs/open_spiel/open_spiel.py +565 -0
- kaggle_environments/envs/open_spiel/proxy.py +138 -0
- kaggle_environments/envs/open_spiel/test_open_spiel.py +191 -0
- kaggle_environments/envs/rps/__init__.py +0 -0
- kaggle_environments/envs/rps/agents.py +84 -0
- kaggle_environments/envs/rps/helpers.py +25 -0
- kaggle_environments/envs/rps/rps.js +117 -0
- kaggle_environments/envs/rps/rps.json +63 -0
- kaggle_environments/envs/rps/rps.py +90 -0
- kaggle_environments/envs/rps/test_rps.py +110 -0
- kaggle_environments/envs/rps/utils.py +7 -0
- kaggle_environments/envs/tictactoe/test_tictactoe.py +43 -77
- kaggle_environments/envs/tictactoe/tictactoe.ipynb +1397 -0
- kaggle_environments/envs/tictactoe/tictactoe.json +10 -2
- kaggle_environments/envs/tictactoe/tictactoe.py +1 -1
- kaggle_environments/errors.py +2 -4
- kaggle_environments/helpers.py +377 -0
- kaggle_environments/main.py +340 -0
- kaggle_environments/schemas.json +23 -18
- kaggle_environments/static/player.html +206 -74
- kaggle_environments/utils.py +46 -73
- kaggle_environments-1.20.0.dist-info/METADATA +25 -0
- kaggle_environments-1.20.0.dist-info/RECORD +211 -0
- {kaggle_environments-0.2.1.dist-info → kaggle_environments-1.20.0.dist-info}/WHEEL +1 -2
- kaggle_environments-1.20.0.dist-info/entry_points.txt +3 -0
- kaggle_environments/envs/battlegeese/battlegeese.py +0 -223
- kaggle_environments/temp.py +0 -14
- kaggle_environments-0.2.1.dist-info/METADATA +0 -393
- kaggle_environments-0.2.1.dist-info/RECORD +0 -32
- kaggle_environments-0.2.1.dist-info/entry_points.txt +0 -3
- kaggle_environments-0.2.1.dist-info/top_level.txt +0 -1
- {kaggle_environments-0.2.1.dist-info → kaggle_environments-1.20.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,1005 @@
|
|
|
1
|
+
# Copyright 2021 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
|
+
import math
|
|
16
|
+
import sys
|
|
17
|
+
from copy import deepcopy
|
|
18
|
+
from enum import Enum, auto
|
|
19
|
+
from functools import wraps
|
|
20
|
+
from typing import *
|
|
21
|
+
|
|
22
|
+
import kaggle_environments.helpers
|
|
23
|
+
from kaggle_environments.helpers import Direction, Point, group_by
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# region Data Model Classes
|
|
27
|
+
class Observation(kaggle_environments.helpers.Observation):
|
|
28
|
+
"""
|
|
29
|
+
Observation primarily used as a helper to construct the Board from the raw observation.
|
|
30
|
+
This provides bindings for the observation type described at https://github.com/Kaggle/kaggle-environments/blob/master/kaggle_environments/envs/kore/kore.json
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def kore(self) -> List[float]:
|
|
35
|
+
"""Serialized list of available kore per cell on the board."""
|
|
36
|
+
return self["kore"]
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def players(self) -> List[List[int]]:
|
|
40
|
+
"""List of players and their assets."""
|
|
41
|
+
return self["players"]
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def player(self) -> int:
|
|
45
|
+
"""The current agent's player index."""
|
|
46
|
+
return self["player"]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Configuration(kaggle_environments.helpers.Configuration):
|
|
50
|
+
"""
|
|
51
|
+
Configuration provides access to tunable parameters in the environment.
|
|
52
|
+
This provides bindings for the configuration type described at https://github.com/Kaggle/kaggle-environments/blob/master/kaggle_environments/envs/kore/kore.json
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def agent_timeout(self) -> float:
|
|
57
|
+
"""Maximum runtime (seconds) to initialize an agent."""
|
|
58
|
+
return self["agentTimeout"]
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def starting_kore(self) -> int:
|
|
62
|
+
"""The starting amount of kore available on the board."""
|
|
63
|
+
return self["startingKore"]
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def size(self) -> int:
|
|
67
|
+
"""The number of cells vertically and horizontally on the board."""
|
|
68
|
+
return self["size"]
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def spawn_cost(self) -> int:
|
|
72
|
+
"""The amount of kore to spawn a new ship."""
|
|
73
|
+
return self["spawnCost"]
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def convert_cost(self) -> int:
|
|
77
|
+
"""The amount of ships needed from a fleet to create a shipyard."""
|
|
78
|
+
return self["convertCost"]
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def regen_rate(self) -> float:
|
|
82
|
+
"""The rate kore regenerates on the board."""
|
|
83
|
+
return self["regenRate"]
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def max_cell_kore(self) -> int:
|
|
87
|
+
"""The maximum kore that can be in any cell."""
|
|
88
|
+
return self["maxRegenCellKore"]
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def random_seed(self) -> int:
|
|
92
|
+
"""The seed to the random number generator (0 means no seed)."""
|
|
93
|
+
return self["randomSeed"]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ShipyardActionType(Enum):
|
|
97
|
+
SPAWN = auto()
|
|
98
|
+
LAUNCH = auto()
|
|
99
|
+
|
|
100
|
+
def __str__(self) -> str:
|
|
101
|
+
return self.name
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class ShipyardAction:
|
|
105
|
+
def __init__(self, type: ShipyardActionType, num_ships: Optional[int], flight_plan: Optional[str]) -> None:
|
|
106
|
+
self._type = type
|
|
107
|
+
assert num_ships >= 0, "must be a non-negative number"
|
|
108
|
+
assert num_ships == int(num_ships), "must be an integer"
|
|
109
|
+
self._num_ships = num_ships
|
|
110
|
+
self._flight_plan = flight_plan
|
|
111
|
+
|
|
112
|
+
def __str__(self) -> str:
|
|
113
|
+
if self._type == ShipyardActionType.SPAWN:
|
|
114
|
+
return f"{self._type.name}_{self._num_ships}"
|
|
115
|
+
if self._type == ShipyardActionType.LAUNCH:
|
|
116
|
+
return f"{self._type.name}_{self._num_ships}_{self._flight_plan}"
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def name(self):
|
|
120
|
+
return str(self)
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def from_str(raw: str):
|
|
124
|
+
if not raw:
|
|
125
|
+
return None
|
|
126
|
+
if raw.startswith(ShipyardActionType.SPAWN.name):
|
|
127
|
+
_, ship_str = raw.split("_")
|
|
128
|
+
try:
|
|
129
|
+
num_ships = int(ship_str.split(".")[0])
|
|
130
|
+
except _:
|
|
131
|
+
num_ships = 0
|
|
132
|
+
return ShipyardAction.spawn_ships(num_ships)
|
|
133
|
+
if raw.startswith(ShipyardActionType.LAUNCH.name):
|
|
134
|
+
_, ship_str, plan_str = raw.split("_")
|
|
135
|
+
try:
|
|
136
|
+
num_ships = int(ship_str.split(".")[0])
|
|
137
|
+
except _:
|
|
138
|
+
num_ships = 0
|
|
139
|
+
return ShipyardAction.launch_fleet_with_flight_plan(num_ships, plan_str)
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def launch_fleet_in_direction(number_ships: int, direction: Direction):
|
|
143
|
+
flight_plan = None
|
|
144
|
+
if isinstance(direction, Direction):
|
|
145
|
+
flight_plan = direction.to_char()
|
|
146
|
+
else:
|
|
147
|
+
flight_plan = flight_plan.upper()
|
|
148
|
+
return ShipyardAction.launch_fleet_with_flight_plan(number_ships, flight_plan)
|
|
149
|
+
|
|
150
|
+
@staticmethod
|
|
151
|
+
def launch_fleet_with_flight_plan(number_ships: int, flight_plan: str):
|
|
152
|
+
flight_plan = flight_plan.upper()
|
|
153
|
+
assert number_ships > 0, "must be a positive number_ships"
|
|
154
|
+
assert number_ships == int(number_ships), "must be an integer number_ships"
|
|
155
|
+
assert flight_plan is not None and len(flight_plan) > 0, "flight_plan must be a str of len > 0"
|
|
156
|
+
assert flight_plan[0].isalpha() and flight_plan[0] in "NESW", (
|
|
157
|
+
"flight_plan must start with a valid direciton NESW"
|
|
158
|
+
)
|
|
159
|
+
assert all([c in "NESWC0123456789" for c in flight_plan]), (
|
|
160
|
+
"flight_plan (" + flight_plan + ")can only contain NESWC0-9"
|
|
161
|
+
)
|
|
162
|
+
if len(flight_plan) > Fleet.max_flight_plan_len_for_ship_count(number_ships):
|
|
163
|
+
print(
|
|
164
|
+
"flight plan will be truncated: flight plan for "
|
|
165
|
+
+ str(number_ships)
|
|
166
|
+
+ " must be at most "
|
|
167
|
+
+ str(Fleet.max_flight_plan_len_for_ship_count(number_ships))
|
|
168
|
+
)
|
|
169
|
+
return ShipyardAction(ShipyardActionType.LAUNCH, number_ships, flight_plan)
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
def spawn_ships(number_ships: int):
|
|
173
|
+
assert number_ships == int(number_ships), "must be an integer number_ships"
|
|
174
|
+
return ShipyardAction(ShipyardActionType.SPAWN, number_ships, None)
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def action_type(self) -> ShipyardActionType:
|
|
178
|
+
return self._type
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def num_ships(self) -> Optional[int]:
|
|
182
|
+
return self._num_ships
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def flight_plan(self) -> Optional[str]:
|
|
186
|
+
return self._flight_plan
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
FleetId = NewType("FleetId", str)
|
|
190
|
+
ShipyardId = NewType("ShipyardId", str)
|
|
191
|
+
PlayerId = NewType("PlayerId", int)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class Cell:
|
|
195
|
+
def __init__(
|
|
196
|
+
self,
|
|
197
|
+
position: Point,
|
|
198
|
+
kore: float,
|
|
199
|
+
shipyard_id: Optional[ShipyardId],
|
|
200
|
+
fleet_id: Optional[FleetId],
|
|
201
|
+
board: "Board",
|
|
202
|
+
) -> None:
|
|
203
|
+
self._position = position
|
|
204
|
+
self._kore = kore
|
|
205
|
+
self._shipyard_id = shipyard_id
|
|
206
|
+
self._fleet_id = fleet_id
|
|
207
|
+
self._board = board
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def position(self) -> Point:
|
|
211
|
+
return self._position
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def kore(self) -> float:
|
|
215
|
+
return self._kore
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def shipyard_id(self) -> Optional[ShipyardId]:
|
|
219
|
+
return self._shipyard_id
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def fleet_id(self) -> Optional[FleetId]:
|
|
223
|
+
return self._fleet_id
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def fleet(self) -> Optional["Fleet"]:
|
|
227
|
+
"""Returns the fleet on this cell if it exists and None otherwise."""
|
|
228
|
+
return self._board.fleets.get(self.fleet_id)
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def shipyard(self) -> Optional["Shipyard"]:
|
|
232
|
+
"""Returns the shipyard on this cell if it exists and None otherwise."""
|
|
233
|
+
return self._board.shipyards.get(self.shipyard_id)
|
|
234
|
+
|
|
235
|
+
def neighbor(self, offset: Point) -> "Cell":
|
|
236
|
+
"""Returns the cell at self.position + offset."""
|
|
237
|
+
(x, y) = self.position + offset
|
|
238
|
+
return self._board[x, y]
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def north(self) -> "Cell":
|
|
242
|
+
"""Returns the cell north of this cell."""
|
|
243
|
+
return self.neighbor(Direction.NORTH.to_point())
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def south(self) -> "Cell":
|
|
247
|
+
"""Returns the cell south of this cell."""
|
|
248
|
+
return self.neighbor(Direction.SOUTH.to_point())
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def east(self) -> "Cell":
|
|
252
|
+
"""Returns the cell east of this cell."""
|
|
253
|
+
return self.neighbor(Direction.EAST.to_point())
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def west(self) -> "Cell":
|
|
257
|
+
"""Returns the cell west of this cell."""
|
|
258
|
+
return self.neighbor(Direction.WEST.to_point())
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class Fleet:
|
|
262
|
+
def __init__(
|
|
263
|
+
self,
|
|
264
|
+
fleet_id: FleetId,
|
|
265
|
+
ship_count: int,
|
|
266
|
+
direction: Direction,
|
|
267
|
+
position: Point,
|
|
268
|
+
kore: int,
|
|
269
|
+
flight_plan: str,
|
|
270
|
+
player_id: PlayerId,
|
|
271
|
+
board: "Board",
|
|
272
|
+
) -> None:
|
|
273
|
+
self._id = fleet_id
|
|
274
|
+
self._ship_count = ship_count
|
|
275
|
+
self._direction = direction
|
|
276
|
+
self._position = position
|
|
277
|
+
self._flight_plan = flight_plan
|
|
278
|
+
self._kore = kore
|
|
279
|
+
self._player_id = player_id
|
|
280
|
+
self._board = board
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def id(self) -> FleetId:
|
|
284
|
+
return self._id
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def ship_count(self) -> int:
|
|
288
|
+
return self._ship_count
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def direction(self) -> Direction:
|
|
292
|
+
return self._direction
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
def position(self) -> Point:
|
|
296
|
+
return self._position
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def kore(self) -> int:
|
|
300
|
+
return self._kore
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def player_id(self) -> PlayerId:
|
|
304
|
+
return self._player_id
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def cell(self) -> Cell:
|
|
308
|
+
"""Returns the cell this fleet is on."""
|
|
309
|
+
return self._board[self.position]
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def player(self) -> "Player":
|
|
313
|
+
"""Returns the player that owns this ship."""
|
|
314
|
+
return self._board.players[self.player_id]
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def flight_plan(self) -> str:
|
|
318
|
+
"""Returns the current flight plan of the fleet"""
|
|
319
|
+
return self._flight_plan
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def collection_rate(self) -> float:
|
|
323
|
+
"""ln(ship_count) / 10"""
|
|
324
|
+
return min(math.log(self.ship_count) / 20, 0.99)
|
|
325
|
+
|
|
326
|
+
@staticmethod
|
|
327
|
+
def max_flight_plan_len_for_ship_count(ship_count) -> int:
|
|
328
|
+
"""Returns the length of the longest possible flight plan this fleet can be assigned"""
|
|
329
|
+
return math.floor(2 * math.log(ship_count)) + 1
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def _observation(self) -> List[int]:
|
|
333
|
+
"""Converts a fleet back to the normalized observation subset that constructed it."""
|
|
334
|
+
return [
|
|
335
|
+
self.position.to_index(self._board.configuration.size),
|
|
336
|
+
self.kore,
|
|
337
|
+
self.ship_count,
|
|
338
|
+
self.direction.to_index(),
|
|
339
|
+
self.flight_plan,
|
|
340
|
+
]
|
|
341
|
+
|
|
342
|
+
def less_than_other_allied_fleet(self, other):
|
|
343
|
+
if not self.ship_count == other.ship_count:
|
|
344
|
+
return self.ship_count < other.ship_count
|
|
345
|
+
if not self.kore == other.kore:
|
|
346
|
+
return self.kore < other.kore
|
|
347
|
+
return self.direction.to_index() > other.direction.to_index()
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
upgrade_times = [pow(i, 2) + 1 for i in range(1, 10)]
|
|
351
|
+
SPAWN_VALUES = []
|
|
352
|
+
current = 0
|
|
353
|
+
for t in upgrade_times:
|
|
354
|
+
current += t
|
|
355
|
+
SPAWN_VALUES.append(current)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class Shipyard:
|
|
359
|
+
def __init__(
|
|
360
|
+
self,
|
|
361
|
+
shipyard_id: ShipyardId,
|
|
362
|
+
ship_count: int,
|
|
363
|
+
position: Point,
|
|
364
|
+
player_id: PlayerId,
|
|
365
|
+
turns_controlled: int,
|
|
366
|
+
board: "Board",
|
|
367
|
+
next_action: Optional[ShipyardAction] = None,
|
|
368
|
+
) -> None:
|
|
369
|
+
self._id = shipyard_id
|
|
370
|
+
self._ship_count = ship_count
|
|
371
|
+
self._position = position
|
|
372
|
+
self._player_id = player_id
|
|
373
|
+
self._turns_controlled = turns_controlled
|
|
374
|
+
self._board = board
|
|
375
|
+
self._next_action = next_action
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
def id(self) -> ShipyardId:
|
|
379
|
+
return self._id
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def ship_count(self):
|
|
383
|
+
return self._ship_count
|
|
384
|
+
|
|
385
|
+
@property
|
|
386
|
+
def position(self) -> Point:
|
|
387
|
+
return self._position
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def player_id(self) -> PlayerId:
|
|
391
|
+
return self._player_id
|
|
392
|
+
|
|
393
|
+
@property
|
|
394
|
+
def max_spawn(self) -> int:
|
|
395
|
+
for idx, target in enumerate(SPAWN_VALUES):
|
|
396
|
+
if self._turns_controlled < target:
|
|
397
|
+
return idx + 1
|
|
398
|
+
return len(SPAWN_VALUES) + 1
|
|
399
|
+
|
|
400
|
+
@property
|
|
401
|
+
def cell(self) -> Cell:
|
|
402
|
+
"""Returns the cell this shipyard is on."""
|
|
403
|
+
return self._board[self.position]
|
|
404
|
+
|
|
405
|
+
@property
|
|
406
|
+
def player(self) -> "Player":
|
|
407
|
+
return self._board.players[self.player_id]
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def next_action(self) -> ShipyardAction:
|
|
411
|
+
"""Returns the action that will be executed by this shipyard when Board.next() is called (when the current turn ends)."""
|
|
412
|
+
return self._next_action
|
|
413
|
+
|
|
414
|
+
@next_action.setter
|
|
415
|
+
def next_action(self, value: Optional[ShipyardAction]) -> None:
|
|
416
|
+
"""Sets the action that will be executed by this shipyard when Board.next() is called (when the current turn ends)."""
|
|
417
|
+
self._next_action = value
|
|
418
|
+
|
|
419
|
+
@property
|
|
420
|
+
def _observation(self) -> List[int]:
|
|
421
|
+
"""Converts a shipyard back to the normalized observation subset that constructed it."""
|
|
422
|
+
return [self.position.to_index(self._board.configuration.size), self.ship_count, self._turns_controlled]
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
class Player:
|
|
426
|
+
def __init__(
|
|
427
|
+
self, player_id: PlayerId, kore: int, shipyard_ids: List[ShipyardId], fleet_ids: List[FleetId], board: "Board"
|
|
428
|
+
) -> None:
|
|
429
|
+
self._id = player_id
|
|
430
|
+
self._kore = kore
|
|
431
|
+
self._shipyard_ids = shipyard_ids
|
|
432
|
+
self._fleet_ids = fleet_ids
|
|
433
|
+
self._board = board
|
|
434
|
+
|
|
435
|
+
@property
|
|
436
|
+
def id(self) -> PlayerId:
|
|
437
|
+
return self._id
|
|
438
|
+
|
|
439
|
+
@property
|
|
440
|
+
def kore(self) -> int:
|
|
441
|
+
return self._kore
|
|
442
|
+
|
|
443
|
+
@property
|
|
444
|
+
def shipyard_ids(self) -> List[ShipyardId]:
|
|
445
|
+
return self._shipyard_ids
|
|
446
|
+
|
|
447
|
+
@property
|
|
448
|
+
def fleet_ids(self) -> List[FleetId]:
|
|
449
|
+
return self._fleet_ids
|
|
450
|
+
|
|
451
|
+
@property
|
|
452
|
+
def shipyards(self) -> List[Shipyard]:
|
|
453
|
+
"""Returns all shipyards owned by this player."""
|
|
454
|
+
return [self._board.shipyards[shipyard_id] for shipyard_id in self.shipyard_ids]
|
|
455
|
+
|
|
456
|
+
@property
|
|
457
|
+
def fleets(self) -> List[Fleet]:
|
|
458
|
+
"""Returns all fleets owned by this player."""
|
|
459
|
+
return [self._board.fleets[fleet_id] for fleet_id in self.fleet_ids]
|
|
460
|
+
|
|
461
|
+
@property
|
|
462
|
+
def is_current_player(self) -> bool:
|
|
463
|
+
"""Returns whether this player is the current player (generally if this returns True, this player is you)."""
|
|
464
|
+
return self.id == self._board.current_player_id
|
|
465
|
+
|
|
466
|
+
@property
|
|
467
|
+
def next_actions(self) -> Dict[str, str]:
|
|
468
|
+
"""Returns all queued fleet and shipyard actions for this player formatted for the kore interpreter to receive as an agent response."""
|
|
469
|
+
shipyard_actions = {
|
|
470
|
+
shipyard.id: shipyard.next_action.name for shipyard in self.shipyards if shipyard.next_action is not None
|
|
471
|
+
}
|
|
472
|
+
return {**shipyard_actions}
|
|
473
|
+
|
|
474
|
+
@property
|
|
475
|
+
def _observation(self):
|
|
476
|
+
"""Converts a player back to the normalized observation subset that constructed it."""
|
|
477
|
+
shipyards = {shipyard.id: shipyard._observation for shipyard in self.shipyards}
|
|
478
|
+
fleets = {fleet.id: fleet._observation for fleet in self.fleets}
|
|
479
|
+
return [self.kore, shipyards, fleets]
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
# endregion
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class Board:
|
|
486
|
+
def __init__(
|
|
487
|
+
self,
|
|
488
|
+
raw_observation: Dict[str, Any],
|
|
489
|
+
raw_configuration: Union[Configuration, Dict[str, Any]],
|
|
490
|
+
next_actions: Optional[List[Dict[str, str]]] = None,
|
|
491
|
+
) -> None:
|
|
492
|
+
"""
|
|
493
|
+
Creates a board from the provided observation, configuration, and next_actions as specified by
|
|
494
|
+
https://github.com/Kaggle/kaggle-environments/blob/master/kaggle_environments/envs/kore/kore.json
|
|
495
|
+
Board tracks players (by id), fleets (by id), shipyards (by id), and cells (by position).
|
|
496
|
+
Each entity contains both key values (e.g. fleet.player_id) as well as entity references (e.g. fleet.player).
|
|
497
|
+
References are deep and chainable e.g.
|
|
498
|
+
[fleet.kore for player in board.players for fleet in player.fleets]
|
|
499
|
+
fleet.player.shipyards[0].cell.north.east.fleet
|
|
500
|
+
Consumers should not set or modify any attributes except and Shipyard.next_action
|
|
501
|
+
"""
|
|
502
|
+
observation = Observation(raw_observation)
|
|
503
|
+
# next_actions is effectively a Dict[Union[[FleetId, FleetAction], [ShipyardId, ShipyardAction]]]
|
|
504
|
+
# but that type's not very expressible so we simplify it to Dict[str, str]
|
|
505
|
+
# Later we'll iterate through it once for each fleet and shipyard to pull all the actions out
|
|
506
|
+
next_actions = next_actions or ([{}] * len(observation.players))
|
|
507
|
+
|
|
508
|
+
self._step = observation.step
|
|
509
|
+
self._remaining_overage_time = observation.remaining_overage_time
|
|
510
|
+
self._configuration = Configuration(raw_configuration)
|
|
511
|
+
self._current_player_id = observation.player
|
|
512
|
+
self._players: Dict[PlayerId, Player] = {}
|
|
513
|
+
self._fleets: Dict[FleetId, Fleet] = {}
|
|
514
|
+
self._shipyards: Dict[ShipyardId, Shipyard] = {}
|
|
515
|
+
self._cells: Dict[Point, Cell] = {}
|
|
516
|
+
|
|
517
|
+
size = self.configuration.size
|
|
518
|
+
# Create a cell for every point in a size x size grid
|
|
519
|
+
for x in range(size):
|
|
520
|
+
for y in range(size):
|
|
521
|
+
position = Point(x, y)
|
|
522
|
+
kore = observation.kore[position.to_index(size)]
|
|
523
|
+
# We'll populate the cell's fleets and shipyards in _add_fleet and _add_shipyard
|
|
524
|
+
self.cells[position] = Cell(position, kore, None, None, self)
|
|
525
|
+
|
|
526
|
+
for player_id, player_observation in enumerate(observation.players):
|
|
527
|
+
# We know the len(player_observation) == 3 based on the schema -- this is a hack to have a tuple in json
|
|
528
|
+
[player_kore, player_shipyards, player_fleets] = player_observation
|
|
529
|
+
# We'll populate the player's fleets and shipyards in _add_fleet and _add_shipyard
|
|
530
|
+
self.players[player_id] = Player(player_id, player_kore, [], [], self)
|
|
531
|
+
player_actions = next_actions[player_id] or {}
|
|
532
|
+
|
|
533
|
+
for fleet_id, [fleet_index, fleet_kore, ship_count, direction, flight_plan] in player_fleets.items():
|
|
534
|
+
fleet_position = Point.from_index(fleet_index, size)
|
|
535
|
+
fleet_direction = Direction.from_index(direction)
|
|
536
|
+
self._add_fleet(
|
|
537
|
+
Fleet(
|
|
538
|
+
fleet_id, ship_count, fleet_direction, fleet_position, fleet_kore, flight_plan, player_id, self
|
|
539
|
+
)
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
for shipyard_id, [shipyard_index, ship_count, turns_controlled] in player_shipyards.items():
|
|
543
|
+
shipyard_position = Point.from_index(shipyard_index, size)
|
|
544
|
+
raw_action = player_actions.get(shipyard_id)
|
|
545
|
+
action = ShipyardAction.from_str(raw_action)
|
|
546
|
+
self._add_shipyard(
|
|
547
|
+
Shipyard(shipyard_id, ship_count, shipyard_position, player_id, turns_controlled, self, action)
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
@property
|
|
551
|
+
def configuration(self) -> Configuration:
|
|
552
|
+
return self._configuration
|
|
553
|
+
|
|
554
|
+
@property
|
|
555
|
+
def players(self) -> Dict[PlayerId, Player]:
|
|
556
|
+
return self._players
|
|
557
|
+
|
|
558
|
+
@property
|
|
559
|
+
def fleets(self) -> Dict[FleetId, Fleet]:
|
|
560
|
+
"""Returns all fleets on the current board."""
|
|
561
|
+
return self._fleets
|
|
562
|
+
|
|
563
|
+
@property
|
|
564
|
+
def shipyards(self) -> Dict[ShipyardId, Shipyard]:
|
|
565
|
+
"""Returns all shipyards on the current board."""
|
|
566
|
+
return self._shipyards
|
|
567
|
+
|
|
568
|
+
@property
|
|
569
|
+
def cells(self) -> Dict[Point, Cell]:
|
|
570
|
+
"""Returns all cells on the current board."""
|
|
571
|
+
return self._cells
|
|
572
|
+
|
|
573
|
+
@property
|
|
574
|
+
def step(self) -> int:
|
|
575
|
+
return self._step
|
|
576
|
+
|
|
577
|
+
@property
|
|
578
|
+
def current_player_id(self) -> PlayerId:
|
|
579
|
+
return self._current_player_id
|
|
580
|
+
|
|
581
|
+
@property
|
|
582
|
+
def current_player(self) -> Player:
|
|
583
|
+
"""Returns the current player (generally this is you)."""
|
|
584
|
+
return self._players[self.current_player_id]
|
|
585
|
+
|
|
586
|
+
@property
|
|
587
|
+
def opponents(self) -> List[Player]:
|
|
588
|
+
"""
|
|
589
|
+
Returns all players that aren't the current player.
|
|
590
|
+
You can get all opponent fleets with [fleet for fleet in player.fleets for player in board.opponents]
|
|
591
|
+
"""
|
|
592
|
+
return [player for player in self.players.values() if not player.is_current_player]
|
|
593
|
+
|
|
594
|
+
@property
|
|
595
|
+
def observation(self) -> Dict[str, Any]:
|
|
596
|
+
"""Converts a Board back to the normalized observation that constructed it."""
|
|
597
|
+
size = self.configuration.size
|
|
598
|
+
kore = [self[Point.from_index(index, size)].kore for index in range(size * size)]
|
|
599
|
+
players = [player._observation for player in self.players.values()]
|
|
600
|
+
|
|
601
|
+
return {
|
|
602
|
+
"kore": kore,
|
|
603
|
+
"players": players,
|
|
604
|
+
"player": self.current_player_id,
|
|
605
|
+
"step": self.step,
|
|
606
|
+
"remainingOverageTime": self._remaining_overage_time,
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
def __deepcopy__(self, _) -> "Board":
|
|
610
|
+
actions = [player.next_actions for player in self.players.values()]
|
|
611
|
+
return Board(self.observation, self.configuration, actions)
|
|
612
|
+
|
|
613
|
+
def __getitem__(self, point: Union[Tuple[int, int], Point]) -> Cell:
|
|
614
|
+
"""
|
|
615
|
+
This method will wrap the supplied position to fit within the board size and return the cell at that location.
|
|
616
|
+
e.g. on a 3x3 board, board[2, 1] is the same as board[5, 1]
|
|
617
|
+
"""
|
|
618
|
+
if not isinstance(point, Point):
|
|
619
|
+
(x, y) = point
|
|
620
|
+
point = Point(x, y)
|
|
621
|
+
return self._cells[point % self.configuration.size]
|
|
622
|
+
|
|
623
|
+
def __str__(self) -> str:
|
|
624
|
+
"""
|
|
625
|
+
The board is printed in a grid with the following rules:
|
|
626
|
+
Capital letters are shipyards
|
|
627
|
+
Lower case letters are fleets
|
|
628
|
+
Digits are cell kore and scale from 0-9 directly proportional to a value between 0 and self.configuration.max_cell_kore
|
|
629
|
+
Player 1 is letter a/A
|
|
630
|
+
Player 2 is letter b/B
|
|
631
|
+
etc.
|
|
632
|
+
"""
|
|
633
|
+
size = self.configuration.size
|
|
634
|
+
result = ""
|
|
635
|
+
for y in range(size):
|
|
636
|
+
for x in range(size):
|
|
637
|
+
cell = self[(x, size - y - 1)]
|
|
638
|
+
result += "|"
|
|
639
|
+
result += chr(ord("a") + cell.ship.player_id) if cell.fleet is not None else " "
|
|
640
|
+
# This normalizes a value from 0 to max_cell kore to a value from 0 to 9
|
|
641
|
+
normalized_kore = int(9.0 * cell.kore / float(self.configuration.max_cell_kore))
|
|
642
|
+
result += str(normalized_kore)
|
|
643
|
+
result += chr(ord("A") + cell.shipyard.player_id) if cell.shipyard is not None else " "
|
|
644
|
+
result += "|\n"
|
|
645
|
+
return result
|
|
646
|
+
|
|
647
|
+
def _add_fleet(self: "Board", fleet: Fleet) -> None:
|
|
648
|
+
fleet.player.fleet_ids.append(fleet.id)
|
|
649
|
+
fleet.cell._fleet_id = fleet.id
|
|
650
|
+
self._fleets[fleet.id] = fleet
|
|
651
|
+
|
|
652
|
+
def _add_shipyard(self: "Board", shipyard: Shipyard) -> None:
|
|
653
|
+
shipyard.player.shipyard_ids.append(shipyard.id)
|
|
654
|
+
shipyard.cell._shipyard_id = shipyard.id
|
|
655
|
+
shipyard.cell._kore = 0
|
|
656
|
+
self._shipyards[shipyard.id] = shipyard
|
|
657
|
+
|
|
658
|
+
def _delete_fleet(self: "Board", fleet: Fleet) -> None:
|
|
659
|
+
fleet.player.fleet_ids.remove(fleet.id)
|
|
660
|
+
if fleet.cell.fleet_id == fleet.id:
|
|
661
|
+
fleet.cell._fleet_id = None
|
|
662
|
+
del self._fleets[fleet.id]
|
|
663
|
+
|
|
664
|
+
def _delete_shipyard(self: "Board", shipyard: Shipyard) -> None:
|
|
665
|
+
shipyard.player.shipyard_ids.remove(shipyard.id)
|
|
666
|
+
if shipyard.cell.shipyard_id == shipyard.id:
|
|
667
|
+
shipyard.cell._shipyard_id = None
|
|
668
|
+
del self._shipyards[shipyard.id]
|
|
669
|
+
|
|
670
|
+
def get_fleet_at_point(self: "Board", position: Point) -> Optional[Fleet]:
|
|
671
|
+
matches = [fleet for fleet in self.fleets.values() if fleet.position == position]
|
|
672
|
+
if matches:
|
|
673
|
+
assert len(matches) == 1
|
|
674
|
+
return matches[0]
|
|
675
|
+
return None
|
|
676
|
+
|
|
677
|
+
def get_shipyard_at_point(self: "Board", position: Point) -> Optional[Shipyard]:
|
|
678
|
+
matches = [shipyard for shipyard in self.shipyards.values() if shipyard.position == position]
|
|
679
|
+
if matches:
|
|
680
|
+
assert len(matches) == 1
|
|
681
|
+
return matches[0]
|
|
682
|
+
return None
|
|
683
|
+
|
|
684
|
+
def get_cell_at_point(self: "Board", position: Point):
|
|
685
|
+
return self.cells.get(position)
|
|
686
|
+
|
|
687
|
+
def print(self: "Board") -> None:
|
|
688
|
+
size = self.configuration.size
|
|
689
|
+
player_chars = {pid: alpha for pid, alpha in zip(self.players, "abcdef"[: len(self.players)])}
|
|
690
|
+
print(self.configuration.size * "=")
|
|
691
|
+
for i in range(size):
|
|
692
|
+
row = ""
|
|
693
|
+
for j in range(size):
|
|
694
|
+
pos = Point(j, size - 1 - i)
|
|
695
|
+
curr_cell = self.cells[pos]
|
|
696
|
+
if curr_cell.shipyard is not None:
|
|
697
|
+
row += player_chars[curr_cell.shipyard.player_id].upper()
|
|
698
|
+
elif curr_cell.fleet is not None:
|
|
699
|
+
row += player_chars[curr_cell.fleet.player_id]
|
|
700
|
+
elif curr_cell.kore <= 50:
|
|
701
|
+
row += " "
|
|
702
|
+
elif curr_cell.kore <= 250:
|
|
703
|
+
row += "."
|
|
704
|
+
elif curr_cell.kore <= 400:
|
|
705
|
+
row += "*"
|
|
706
|
+
elif curr_cell.kore > 400:
|
|
707
|
+
row += "o"
|
|
708
|
+
print(row)
|
|
709
|
+
print(self.configuration.size * "=")
|
|
710
|
+
|
|
711
|
+
def print_kore(self: "Board") -> None:
|
|
712
|
+
size = self.configuration.size
|
|
713
|
+
print(self.configuration.size * "=")
|
|
714
|
+
for i in range(size):
|
|
715
|
+
row = ""
|
|
716
|
+
for j in range(size):
|
|
717
|
+
pos = Point(j, size - 1 - i)
|
|
718
|
+
curr_cell = self.cells[pos]
|
|
719
|
+
row += str(int(curr_cell.kore)) + ","
|
|
720
|
+
print(row)
|
|
721
|
+
print(self.configuration.size * "=")
|
|
722
|
+
|
|
723
|
+
def next(self) -> "Board":
|
|
724
|
+
"""
|
|
725
|
+
Returns a new board with the current board's next actions applied.
|
|
726
|
+
The current board is unmodified.
|
|
727
|
+
This can form a kore interpreter, e.g.
|
|
728
|
+
next_observation = Board(current_observation, configuration, actions).next().observation
|
|
729
|
+
"""
|
|
730
|
+
# Create a copy of the board to modify so we don't affect the current board
|
|
731
|
+
board = deepcopy(self)
|
|
732
|
+
configuration = board.configuration
|
|
733
|
+
convert_cost = configuration.convert_cost
|
|
734
|
+
spawn_cost = configuration.spawn_cost
|
|
735
|
+
uid_counter = 0
|
|
736
|
+
|
|
737
|
+
# This is a consistent way to generate unique strings to form fleet and shipyard ids
|
|
738
|
+
def create_uid():
|
|
739
|
+
nonlocal uid_counter
|
|
740
|
+
uid_counter += 1
|
|
741
|
+
return f"{self.step + 1}-{uid_counter}"
|
|
742
|
+
|
|
743
|
+
# this checks the validity of a flight plan
|
|
744
|
+
def is_valid_flight_plan(flight_plan):
|
|
745
|
+
return len([c for c in flight_plan if c not in "NESWC0123456789"]) == 0
|
|
746
|
+
|
|
747
|
+
# Process actions and store the results in the fleets and shipyards lists for collision checking
|
|
748
|
+
for player in board.players.values():
|
|
749
|
+
for shipyard in player.shipyards:
|
|
750
|
+
if shipyard.next_action == None:
|
|
751
|
+
pass
|
|
752
|
+
elif shipyard.next_action.num_ships == 0:
|
|
753
|
+
pass
|
|
754
|
+
elif (
|
|
755
|
+
shipyard.next_action.action_type == ShipyardActionType.SPAWN
|
|
756
|
+
and player.kore >= spawn_cost * shipyard.next_action.num_ships
|
|
757
|
+
and shipyard.next_action.num_ships <= shipyard.max_spawn
|
|
758
|
+
):
|
|
759
|
+
# Handle SPAWN actions
|
|
760
|
+
player._kore -= spawn_cost * shipyard.next_action.num_ships
|
|
761
|
+
shipyard._ship_count += shipyard.next_action.num_ships
|
|
762
|
+
elif (
|
|
763
|
+
shipyard.next_action.action_type == ShipyardActionType.LAUNCH
|
|
764
|
+
and shipyard.ship_count >= shipyard.next_action.num_ships
|
|
765
|
+
):
|
|
766
|
+
flight_plan = shipyard.next_action.flight_plan
|
|
767
|
+
if not flight_plan or not is_valid_flight_plan(flight_plan):
|
|
768
|
+
continue
|
|
769
|
+
shipyard._ship_count -= shipyard.next_action.num_ships
|
|
770
|
+
direction = Direction.from_char(flight_plan[0])
|
|
771
|
+
max_flight_plan_len = Fleet.max_flight_plan_len_for_ship_count(shipyard.next_action.num_ships)
|
|
772
|
+
if len(flight_plan) > max_flight_plan_len:
|
|
773
|
+
flight_plan = flight_plan[:max_flight_plan_len]
|
|
774
|
+
board._add_fleet(
|
|
775
|
+
Fleet(
|
|
776
|
+
FleetId(create_uid()),
|
|
777
|
+
shipyard.next_action.num_ships,
|
|
778
|
+
direction,
|
|
779
|
+
shipyard.position,
|
|
780
|
+
0,
|
|
781
|
+
flight_plan,
|
|
782
|
+
player.id,
|
|
783
|
+
board,
|
|
784
|
+
)
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
# Clear the shipyard's action so it doesn't repeat the same action automatically
|
|
788
|
+
for shipyard in player.shipyards:
|
|
789
|
+
shipyard.next_action = None
|
|
790
|
+
shipyard._turns_controlled += 1
|
|
791
|
+
|
|
792
|
+
def find_first_non_digit(candidate_str):
|
|
793
|
+
for i in range(len(candidate_str)):
|
|
794
|
+
if not candidate_str[i].isdigit():
|
|
795
|
+
return i
|
|
796
|
+
else:
|
|
797
|
+
return len(candidate_str) + 1
|
|
798
|
+
return 0
|
|
799
|
+
|
|
800
|
+
for fleet in player.fleets:
|
|
801
|
+
# remove any errant 0s
|
|
802
|
+
while fleet.flight_plan and fleet.flight_plan.startswith("0"):
|
|
803
|
+
fleet._flight_plan = fleet.flight_plan[1:]
|
|
804
|
+
if (
|
|
805
|
+
fleet.flight_plan
|
|
806
|
+
and fleet.flight_plan[0] == "C"
|
|
807
|
+
and fleet.ship_count >= convert_cost
|
|
808
|
+
and fleet.cell.shipyard_id is None
|
|
809
|
+
):
|
|
810
|
+
player._kore += fleet.kore
|
|
811
|
+
fleet.cell._kore = 0
|
|
812
|
+
board._add_shipyard(
|
|
813
|
+
Shipyard(
|
|
814
|
+
ShipyardId(create_uid()),
|
|
815
|
+
fleet.ship_count - convert_cost,
|
|
816
|
+
fleet.position,
|
|
817
|
+
player.id,
|
|
818
|
+
0,
|
|
819
|
+
board,
|
|
820
|
+
)
|
|
821
|
+
)
|
|
822
|
+
board._delete_fleet(fleet)
|
|
823
|
+
continue
|
|
824
|
+
|
|
825
|
+
while fleet.flight_plan and fleet.flight_plan[0] == "C":
|
|
826
|
+
# couldn't build, remove the Convert and continue with flight plan
|
|
827
|
+
fleet._flight_plan = fleet.flight_plan[1:]
|
|
828
|
+
|
|
829
|
+
if fleet.flight_plan and fleet.flight_plan[0].isalpha():
|
|
830
|
+
fleet._direction = Direction.from_char(fleet.flight_plan[0])
|
|
831
|
+
fleet._flight_plan = fleet.flight_plan[1:]
|
|
832
|
+
elif fleet.flight_plan:
|
|
833
|
+
idx = find_first_non_digit(fleet.flight_plan)
|
|
834
|
+
digits = int(fleet.flight_plan[:idx])
|
|
835
|
+
rest = fleet.flight_plan[idx:]
|
|
836
|
+
digits -= 1
|
|
837
|
+
if digits > 0:
|
|
838
|
+
fleet._flight_plan = str(digits) + rest
|
|
839
|
+
else:
|
|
840
|
+
fleet._flight_plan = rest
|
|
841
|
+
|
|
842
|
+
# continue moving in the fleet's direction
|
|
843
|
+
fleet.cell._fleet_id = None
|
|
844
|
+
fleet._position = fleet.position.translate(fleet.direction.to_point(), configuration.size)
|
|
845
|
+
# We don't set the new cell's fleet_id here as it would be overwritten by another fleet in the case of collision.
|
|
846
|
+
|
|
847
|
+
def combine_fleets(fid1: FleetId, fid2: FleetId) -> FleetId:
|
|
848
|
+
f1 = board.fleets[fid1]
|
|
849
|
+
f2 = board.fleets[fid2]
|
|
850
|
+
if f1.less_than_other_allied_fleet(f2):
|
|
851
|
+
f1, f2 = f2, f1
|
|
852
|
+
fid1, fid2 = fid2, fid1
|
|
853
|
+
f1._kore += f2.kore
|
|
854
|
+
f1._ship_count += f2._ship_count
|
|
855
|
+
board._delete_fleet(f2)
|
|
856
|
+
return fid1
|
|
857
|
+
|
|
858
|
+
# resolve any allied fleets that ended up in the same square
|
|
859
|
+
fleets_by_loc = group_by(player.fleets, lambda fleet: fleet.position.to_index(configuration.size))
|
|
860
|
+
for value in fleets_by_loc.values():
|
|
861
|
+
value.sort(key=lambda fleet: (fleet.ship_count, fleet.kore, -fleet.direction.to_index()), reverse=True)
|
|
862
|
+
fid = value[0].id
|
|
863
|
+
for i in range(1, len(value)):
|
|
864
|
+
fid = combine_fleets(fid, value[i].id)
|
|
865
|
+
|
|
866
|
+
# Lets just check and make sure.
|
|
867
|
+
assert player.kore >= 0
|
|
868
|
+
|
|
869
|
+
def resolve_collision(fleets: List[Fleet]) -> Tuple[Optional[Fleet], List[Fleet]]:
|
|
870
|
+
"""
|
|
871
|
+
Accepts the list of fleets at a particular position (must not be empty).
|
|
872
|
+
Returns the fleet with the most ships or None in the case of a tie along with all other fleets.
|
|
873
|
+
"""
|
|
874
|
+
if len(fleets) == 1:
|
|
875
|
+
return fleets[0], []
|
|
876
|
+
fleets_by_ships = group_by(fleets, lambda fleet: fleet.ship_count)
|
|
877
|
+
most_ships = max(fleets_by_ships.keys())
|
|
878
|
+
largest_fleets = fleets_by_ships[most_ships]
|
|
879
|
+
if len(largest_fleets) == 1:
|
|
880
|
+
# There was a winner, return it
|
|
881
|
+
winner = largest_fleets[0]
|
|
882
|
+
return winner, [fleet for fleet in fleets if fleet != winner]
|
|
883
|
+
# There was a tie for most ships, all are deleted
|
|
884
|
+
return None, fleets
|
|
885
|
+
|
|
886
|
+
# Check for fleet to fleet collisions
|
|
887
|
+
fleet_collision_groups = group_by(board.fleets.values(), lambda fleet: fleet.position)
|
|
888
|
+
for position, collided_fleets in fleet_collision_groups.items():
|
|
889
|
+
winner, deleted = resolve_collision(collided_fleets)
|
|
890
|
+
shipyard = group_by(board.shipyards.values(), lambda shipyard: shipyard.position).get(position)
|
|
891
|
+
if winner is not None:
|
|
892
|
+
winner.cell._fleet_id = winner.id
|
|
893
|
+
max_enemy_size = max([fleet.ship_count for fleet in deleted]) if deleted else 0
|
|
894
|
+
winner._ship_count -= max_enemy_size
|
|
895
|
+
for fleet in deleted:
|
|
896
|
+
board._delete_fleet(fleet)
|
|
897
|
+
if winner is not None:
|
|
898
|
+
# Winner takes deleted fleets' kore
|
|
899
|
+
winner._kore += fleet.kore
|
|
900
|
+
elif winner is None and shipyard and shipyard[0].player:
|
|
901
|
+
# Desposit the kore into the shipyard
|
|
902
|
+
shipyard[0].player._kore += fleet.kore
|
|
903
|
+
elif winner is None:
|
|
904
|
+
# Desposit the kore on the square
|
|
905
|
+
board.cells[position]._kore += fleet.kore
|
|
906
|
+
|
|
907
|
+
# Check for fleet to shipyard collisions
|
|
908
|
+
for shipyard in list(board.shipyards.values()):
|
|
909
|
+
fleet = shipyard.cell.fleet
|
|
910
|
+
if fleet is not None and fleet.player_id != shipyard.player_id:
|
|
911
|
+
if fleet.ship_count > shipyard.ship_count:
|
|
912
|
+
count = fleet.ship_count - shipyard.ship_count
|
|
913
|
+
board._delete_shipyard(shipyard)
|
|
914
|
+
board._add_shipyard(
|
|
915
|
+
Shipyard(ShipyardId(create_uid()), count, shipyard.position, fleet.player.id, 1, board)
|
|
916
|
+
)
|
|
917
|
+
fleet.player._kore += fleet.kore
|
|
918
|
+
board._delete_fleet(fleet)
|
|
919
|
+
else:
|
|
920
|
+
shipyard._ship_count -= fleet.ship_count
|
|
921
|
+
shipyard.player._kore += fleet.kore
|
|
922
|
+
board._delete_fleet(fleet)
|
|
923
|
+
|
|
924
|
+
# Deposit kore from fleets into shipyards
|
|
925
|
+
for shipyard in list(board.shipyards.values()):
|
|
926
|
+
fleet = shipyard.cell.fleet
|
|
927
|
+
if fleet is not None and fleet.player_id == shipyard.player_id:
|
|
928
|
+
shipyard.player._kore += fleet.kore
|
|
929
|
+
shipyard._ship_count += fleet.ship_count
|
|
930
|
+
board._delete_fleet(fleet)
|
|
931
|
+
|
|
932
|
+
# apply fleet to fleet damage on all orthagonally adjacent cells
|
|
933
|
+
incoming_fleet_dmg = DefaultDict(lambda: DefaultDict(int))
|
|
934
|
+
for fleet in board.fleets.values():
|
|
935
|
+
for direction in Direction.list_directions():
|
|
936
|
+
curr_pos = fleet.position.translate(direction.to_point(), board.configuration.size)
|
|
937
|
+
fleet_at_pos = board.get_fleet_at_point(curr_pos)
|
|
938
|
+
if fleet_at_pos and not fleet_at_pos.player_id == fleet.player_id:
|
|
939
|
+
incoming_fleet_dmg[fleet_at_pos.id][fleet.id] = fleet.ship_count
|
|
940
|
+
|
|
941
|
+
# dump 1/2 kore to the cell of killed fleets
|
|
942
|
+
# mark the other 1/2 kore to go to surrounding fleets proportionally
|
|
943
|
+
to_distribute = DefaultDict(lambda: DefaultDict(int))
|
|
944
|
+
for fleet_id, fleet_dmg_dict in incoming_fleet_dmg.items():
|
|
945
|
+
fleet = board.fleets[fleet_id]
|
|
946
|
+
damage = sum(fleet_dmg_dict.values())
|
|
947
|
+
if damage >= fleet.ship_count:
|
|
948
|
+
fleet.cell._kore += fleet.kore / 2
|
|
949
|
+
to_split = fleet.kore / 2
|
|
950
|
+
for f_id, dmg in fleet_dmg_dict.items():
|
|
951
|
+
to_distribute[f_id][fleet.position.to_index(board.configuration.size)] = to_split * dmg / damage
|
|
952
|
+
board._delete_fleet(fleet)
|
|
953
|
+
else:
|
|
954
|
+
fleet._ship_count -= damage
|
|
955
|
+
|
|
956
|
+
# give kore claimed above to surviving fleets, otherwise add it to the kore of the tile where the fleet died
|
|
957
|
+
for fleet_id, loc_kore_dict in to_distribute.items():
|
|
958
|
+
fleet = board.fleets.get(fleet_id)
|
|
959
|
+
if fleet:
|
|
960
|
+
fleet._kore += sum(loc_kore_dict.values())
|
|
961
|
+
else:
|
|
962
|
+
for loc_idx, kore in loc_kore_dict.items():
|
|
963
|
+
board.cells.get(Point.from_index(loc_idx, board.configuration.size))._kore += kore
|
|
964
|
+
|
|
965
|
+
# Collect kore from cells into fleets
|
|
966
|
+
for fleet in board.fleets.values():
|
|
967
|
+
cell = fleet.cell
|
|
968
|
+
delta_kore = round(cell.kore * min(fleet.collection_rate, 0.99), 3)
|
|
969
|
+
if delta_kore > 0:
|
|
970
|
+
fleet._kore += delta_kore
|
|
971
|
+
cell._kore -= delta_kore
|
|
972
|
+
|
|
973
|
+
# Regenerate kore in cells
|
|
974
|
+
for cell in board.cells.values():
|
|
975
|
+
if cell.fleet_id is None and cell.shipyard_id is None:
|
|
976
|
+
if cell.kore < configuration.max_cell_kore:
|
|
977
|
+
next_kore = round(cell.kore * (1 + configuration.regen_rate), 3)
|
|
978
|
+
cell._kore = next_kore
|
|
979
|
+
|
|
980
|
+
board._step += 1
|
|
981
|
+
|
|
982
|
+
# self.print()
|
|
983
|
+
|
|
984
|
+
return board
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
def board_agent(agent: Callable[[Board], None]):
|
|
988
|
+
"""
|
|
989
|
+
Decorator used to create an agent that modifies a board rather than an observation and a configuration
|
|
990
|
+
Automatically returns the modified board's next actions
|
|
991
|
+
|
|
992
|
+
@board_agent
|
|
993
|
+
def my_agent(board: Board) -> None:
|
|
994
|
+
...
|
|
995
|
+
"""
|
|
996
|
+
|
|
997
|
+
@wraps(agent)
|
|
998
|
+
def agent_wrapper(obs, config) -> Dict[str, str]:
|
|
999
|
+
board = Board(obs, config)
|
|
1000
|
+
agent(board)
|
|
1001
|
+
return board.current_player.next_actions
|
|
1002
|
+
|
|
1003
|
+
if agent.__module__ is not None and agent.__module__ in sys.modules:
|
|
1004
|
+
setattr(sys.modules[agent.__module__], agent.__name__, agent_wrapper)
|
|
1005
|
+
return agent_wrapper
|