kaggle-environments 0.2.0__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 +298 -173
- 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} +22 -15
- 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 +214 -50
- 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.0.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 -219
- kaggle_environments/temp.py +0 -14
- kaggle_environments-0.2.0.dist-info/METADATA +0 -393
- kaggle_environments-0.2.0.dist-info/RECORD +0 -33
- kaggle_environments-0.2.0.dist-info/entry_points.txt +0 -3
- kaggle_environments-0.2.0.dist-info/top_level.txt +0 -1
- {kaggle_environments-0.2.0.dist-info → kaggle_environments-1.20.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
# Copyright 2020 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 sys
|
|
16
|
+
from copy import deepcopy
|
|
17
|
+
from enum import Enum, auto
|
|
18
|
+
from functools import wraps
|
|
19
|
+
from typing import *
|
|
20
|
+
|
|
21
|
+
import kaggle_environments.helpers
|
|
22
|
+
from kaggle_environments.helpers import Point, group_by
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# region Data Model Classes
|
|
26
|
+
class Observation(kaggle_environments.helpers.Observation):
|
|
27
|
+
"""
|
|
28
|
+
Observation primarily used as a helper to construct the Board from the raw observation.
|
|
29
|
+
This provides bindings for the observation type described at https://github.com/Kaggle/kaggle-environments/blob/master/kaggle_environments/envs/halite/halite.json
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def halite(self) -> List[float]:
|
|
34
|
+
"""Serialized list of available halite per cell on the board."""
|
|
35
|
+
return self["halite"]
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def players(self) -> List[List[int]]:
|
|
39
|
+
"""List of players and their assets."""
|
|
40
|
+
return self["players"]
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def player(self) -> int:
|
|
44
|
+
"""The current agent's player index."""
|
|
45
|
+
return self["player"]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Configuration(kaggle_environments.helpers.Configuration):
|
|
49
|
+
"""
|
|
50
|
+
Configuration provides access to tunable parameters in the environment.
|
|
51
|
+
This provides bindings for the configuration type described at https://github.com/Kaggle/kaggle-environments/blob/master/kaggle_environments/envs/halite/halite.json
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def agent_timeout(self) -> float:
|
|
56
|
+
"""Maximum runtime (seconds) to initialize an agent."""
|
|
57
|
+
return self["agentTimeout"]
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def starting_halite(self) -> int:
|
|
61
|
+
"""The starting amount of halite available on the board."""
|
|
62
|
+
return self["startingHalite"]
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def size(self) -> int:
|
|
66
|
+
"""The number of cells vertically and horizontally on the board."""
|
|
67
|
+
return self["size"]
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def spawn_cost(self) -> int:
|
|
71
|
+
"""The amount of halite to spawn a new ship."""
|
|
72
|
+
return self["spawnCost"]
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def convert_cost(self) -> int:
|
|
76
|
+
"""The amount of halite to convert a ship into a shipyard."""
|
|
77
|
+
return self["convertCost"]
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def move_cost(self) -> float:
|
|
81
|
+
"""The percent deducted from ship's current halite per move."""
|
|
82
|
+
return self["moveCost"]
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def collect_rate(self) -> float:
|
|
86
|
+
"""The rate of halite collected by a ship from a cell by not moving."""
|
|
87
|
+
return self["collectRate"]
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def regen_rate(self) -> float:
|
|
91
|
+
"""The rate halite regenerates on the board."""
|
|
92
|
+
return self["regenRate"]
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def max_cell_halite(self) -> int:
|
|
96
|
+
"""The maximum halite that can be in any cell."""
|
|
97
|
+
return self["maxCellHalite"]
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def random_seed(self) -> int:
|
|
101
|
+
"""The seed to the random number generator (0 means no seed)."""
|
|
102
|
+
return self["randomSeed"]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ShipAction(Enum):
|
|
106
|
+
NORTH = auto()
|
|
107
|
+
EAST = auto()
|
|
108
|
+
SOUTH = auto()
|
|
109
|
+
WEST = auto()
|
|
110
|
+
CONVERT = auto()
|
|
111
|
+
# Use None to collect halite on a cell
|
|
112
|
+
|
|
113
|
+
def to_point(self) -> Optional[Point]:
|
|
114
|
+
"""
|
|
115
|
+
This returns the position offset associated with a particular action or None if the action does not change the ship's position.
|
|
116
|
+
NORTH -> (0, 1)
|
|
117
|
+
EAST -> (1, 0)
|
|
118
|
+
SOUTH -> (0, -1)
|
|
119
|
+
WEST -> (-1, 0)
|
|
120
|
+
"""
|
|
121
|
+
return (
|
|
122
|
+
Point(0, 1)
|
|
123
|
+
if self == ShipAction.NORTH
|
|
124
|
+
else Point(1, 0)
|
|
125
|
+
if self == ShipAction.EAST
|
|
126
|
+
else Point(0, -1)
|
|
127
|
+
if self == ShipAction.SOUTH
|
|
128
|
+
else Point(-1, 0)
|
|
129
|
+
if self == ShipAction.WEST
|
|
130
|
+
else None
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def __str__(self) -> str:
|
|
134
|
+
return self.name
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def moves() -> List["ShipAction"]:
|
|
138
|
+
return [
|
|
139
|
+
ShipAction.NORTH,
|
|
140
|
+
ShipAction.EAST,
|
|
141
|
+
ShipAction.SOUTH,
|
|
142
|
+
ShipAction.WEST,
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class ShipyardAction(Enum):
|
|
147
|
+
SPAWN = auto()
|
|
148
|
+
|
|
149
|
+
def __str__(self) -> str:
|
|
150
|
+
return self.name
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
ShipId = NewType("ShipId", str)
|
|
154
|
+
ShipyardId = NewType("ShipyardId", str)
|
|
155
|
+
PlayerId = NewType("PlayerId", int)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class Cell:
|
|
159
|
+
def __init__(
|
|
160
|
+
self,
|
|
161
|
+
position: Point,
|
|
162
|
+
halite: float,
|
|
163
|
+
shipyard_id: Optional[ShipyardId],
|
|
164
|
+
ship_id: Optional[ShipId],
|
|
165
|
+
board: "Board",
|
|
166
|
+
) -> None:
|
|
167
|
+
self._position = position
|
|
168
|
+
self._halite = halite
|
|
169
|
+
self._shipyard_id = shipyard_id
|
|
170
|
+
self._ship_id = ship_id
|
|
171
|
+
self._board = board
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def position(self) -> Point:
|
|
175
|
+
return self._position
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def halite(self) -> float:
|
|
179
|
+
return self._halite
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def shipyard_id(self) -> Optional[ShipyardId]:
|
|
183
|
+
return self._shipyard_id
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def ship_id(self) -> Optional[ShipId]:
|
|
187
|
+
return self._ship_id
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def ship(self) -> Optional["Ship"]:
|
|
191
|
+
"""Returns the ship on this cell if it exists and None otherwise."""
|
|
192
|
+
return self._board.ships.get(self.ship_id)
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def shipyard(self) -> Optional["Shipyard"]:
|
|
196
|
+
"""Returns the shipyard on this cell if it exists and None otherwise."""
|
|
197
|
+
return self._board.shipyards.get(self.shipyard_id)
|
|
198
|
+
|
|
199
|
+
def neighbor(self, offset: Point) -> "Cell":
|
|
200
|
+
"""Returns the cell at self.position + offset."""
|
|
201
|
+
(x, y) = self.position + offset
|
|
202
|
+
return self._board[x, y]
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def north(self) -> "Cell":
|
|
206
|
+
"""Returns the cell north of this cell."""
|
|
207
|
+
return self.neighbor(ShipAction.NORTH.to_point())
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def south(self) -> "Cell":
|
|
211
|
+
"""Returns the cell south of this cell."""
|
|
212
|
+
return self.neighbor(ShipAction.SOUTH.to_point())
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def east(self) -> "Cell":
|
|
216
|
+
"""Returns the cell east of this cell."""
|
|
217
|
+
return self.neighbor(ShipAction.EAST.to_point())
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def west(self) -> "Cell":
|
|
221
|
+
"""Returns the cell west of this cell."""
|
|
222
|
+
return self.neighbor(ShipAction.WEST.to_point())
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class Ship:
|
|
226
|
+
def __init__(
|
|
227
|
+
self,
|
|
228
|
+
ship_id: ShipId,
|
|
229
|
+
position: Point,
|
|
230
|
+
halite: int,
|
|
231
|
+
player_id: PlayerId,
|
|
232
|
+
board: "Board",
|
|
233
|
+
next_action: Optional[ShipAction] = None,
|
|
234
|
+
) -> None:
|
|
235
|
+
self._id = ship_id
|
|
236
|
+
self._position = position
|
|
237
|
+
self._halite = halite
|
|
238
|
+
self._player_id = player_id
|
|
239
|
+
self._board = board
|
|
240
|
+
self._next_action = next_action
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def id(self) -> ShipId:
|
|
244
|
+
return self._id
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def position(self) -> Point:
|
|
248
|
+
return self._position
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def halite(self) -> int:
|
|
252
|
+
return self._halite
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def player_id(self) -> PlayerId:
|
|
256
|
+
return self._player_id
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def cell(self) -> Cell:
|
|
260
|
+
"""Returns the cell this ship is on."""
|
|
261
|
+
return self._board[self.position]
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
def player(self) -> "Player":
|
|
265
|
+
"""Returns the player that owns this ship."""
|
|
266
|
+
return self._board.players[self.player_id]
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def next_action(self) -> Optional[ShipAction]:
|
|
270
|
+
"""Returns the action that will be executed by this ship when Board.next() is called (when the current turn ends)."""
|
|
271
|
+
return self._next_action
|
|
272
|
+
|
|
273
|
+
@next_action.setter
|
|
274
|
+
def next_action(self, value: Optional[ShipAction]) -> None:
|
|
275
|
+
"""Sets the action that will be executed by this ship when Board.next() is called (when the current turn ends)."""
|
|
276
|
+
self._next_action = value
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def _observation(self) -> List[int]:
|
|
280
|
+
"""Converts a ship back to the normalized observation subset that constructed it."""
|
|
281
|
+
return [self.position.to_index(self._board.configuration.size), self.halite]
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class Shipyard:
|
|
285
|
+
def __init__(
|
|
286
|
+
self,
|
|
287
|
+
shipyard_id: ShipyardId,
|
|
288
|
+
position: Point,
|
|
289
|
+
player_id: PlayerId,
|
|
290
|
+
board: "Board",
|
|
291
|
+
next_action: Optional[ShipyardAction] = None,
|
|
292
|
+
) -> None:
|
|
293
|
+
self._id = shipyard_id
|
|
294
|
+
self._position = position
|
|
295
|
+
self._player_id = player_id
|
|
296
|
+
self._board = board
|
|
297
|
+
self._next_action = next_action
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def id(self) -> ShipyardId:
|
|
301
|
+
return self._id
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def position(self) -> Point:
|
|
305
|
+
return self._position
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def player_id(self) -> PlayerId:
|
|
309
|
+
return self._player_id
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def cell(self) -> Cell:
|
|
313
|
+
"""Returns the cell this shipyard is on."""
|
|
314
|
+
return self._board[self.position]
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def player(self) -> "Player":
|
|
318
|
+
return self._board.players[self.player_id]
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def next_action(self) -> ShipyardAction:
|
|
322
|
+
"""Returns the action that will be executed by this shipyard when Board.next() is called (when the current turn ends)."""
|
|
323
|
+
return self._next_action
|
|
324
|
+
|
|
325
|
+
@next_action.setter
|
|
326
|
+
def next_action(self, value: Optional[ShipyardAction]) -> None:
|
|
327
|
+
"""Sets the action that will be executed by this shipyard when Board.next() is called (when the current turn ends)."""
|
|
328
|
+
self._next_action = value
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def _observation(self) -> int:
|
|
332
|
+
"""Converts a shipyard back to the normalized observation subset that constructed it."""
|
|
333
|
+
return self.position.to_index(self._board.configuration.size)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class Player:
|
|
337
|
+
def __init__(
|
|
338
|
+
self, player_id: PlayerId, halite: int, shipyard_ids: List[ShipyardId], ship_ids: List[ShipId], board: "Board"
|
|
339
|
+
) -> None:
|
|
340
|
+
self._id = player_id
|
|
341
|
+
self._halite = halite
|
|
342
|
+
self._shipyard_ids = shipyard_ids
|
|
343
|
+
self._ship_ids = ship_ids
|
|
344
|
+
self._board = board
|
|
345
|
+
|
|
346
|
+
@property
|
|
347
|
+
def id(self) -> PlayerId:
|
|
348
|
+
return self._id
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def halite(self) -> int:
|
|
352
|
+
return self._halite
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def shipyard_ids(self) -> List[ShipyardId]:
|
|
356
|
+
return self._shipyard_ids
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def ship_ids(self) -> List[ShipId]:
|
|
360
|
+
return self._ship_ids
|
|
361
|
+
|
|
362
|
+
@property
|
|
363
|
+
def shipyards(self) -> List[Shipyard]:
|
|
364
|
+
"""Returns all shipyards owned by this player."""
|
|
365
|
+
return [self._board.shipyards[shipyard_id] for shipyard_id in self.shipyard_ids]
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def ships(self) -> List[Ship]:
|
|
369
|
+
"""Returns all ships owned by this player."""
|
|
370
|
+
return [self._board.ships[ship_id] for ship_id in self.ship_ids]
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def is_current_player(self) -> bool:
|
|
374
|
+
"""Returns whether this player is the current player (generally if this returns True, this player is you)."""
|
|
375
|
+
return self.id == self._board.current_player_id
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
def next_actions(self) -> Dict[str, str]:
|
|
379
|
+
"""Returns all queued ship and shipyard actions for this player formatted for the halite interpreter to receive as an agent response."""
|
|
380
|
+
ship_actions = {ship.id: ship.next_action.name for ship in self.ships if ship.next_action is not None}
|
|
381
|
+
shipyard_actions = {
|
|
382
|
+
shipyard.id: shipyard.next_action.name for shipyard in self.shipyards if shipyard.next_action is not None
|
|
383
|
+
}
|
|
384
|
+
return {**ship_actions, **shipyard_actions}
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def _observation(self):
|
|
388
|
+
"""Converts a player back to the normalized observation subset that constructed it."""
|
|
389
|
+
shipyards = {shipyard.id: shipyard._observation for shipyard in self.shipyards}
|
|
390
|
+
ships = {ship.id: ship._observation for ship in self.ships}
|
|
391
|
+
return [self.halite, shipyards, ships]
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
# endregion
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class Board:
|
|
398
|
+
def __init__(
|
|
399
|
+
self,
|
|
400
|
+
raw_observation: Dict[str, Any],
|
|
401
|
+
raw_configuration: Union[Configuration, Dict[str, Any]],
|
|
402
|
+
next_actions: Optional[List[Dict[str, str]]] = None,
|
|
403
|
+
) -> None:
|
|
404
|
+
"""
|
|
405
|
+
Creates a board from the provided observation, configuration, and next_actions as specified by
|
|
406
|
+
https://github.com/Kaggle/kaggle-environments/blob/master/kaggle_environments/envs/halite/halite.json
|
|
407
|
+
Board tracks players (by id), ships (by id), shipyards (by id), and cells (by position).
|
|
408
|
+
Each entity contains both key values (e.g. ship.player_id) as well as entity references (e.g. ship.player).
|
|
409
|
+
References are deep and chainable e.g.
|
|
410
|
+
[ship.halite for player in board.players for ship in player.ships]
|
|
411
|
+
ship.player.shipyards[0].cell.north.east.ship
|
|
412
|
+
Consumers should not set or modify any attributes except Ship.next_action and Shipyard.next_action
|
|
413
|
+
"""
|
|
414
|
+
observation = Observation(raw_observation)
|
|
415
|
+
# next_actions is effectively a Dict[Union[[ShipId, ShipAction], [ShipyardId, ShipyardAction]]]
|
|
416
|
+
# but that type's not very expressible so we simplify it to Dict[str, str]
|
|
417
|
+
# Later we'll iterate through it once for each ship and shipyard to pull all the actions out
|
|
418
|
+
next_actions = next_actions or ([{}] * len(observation.players))
|
|
419
|
+
|
|
420
|
+
self._step = observation.step
|
|
421
|
+
self._remaining_overage_time = observation.remaining_overage_time
|
|
422
|
+
self._configuration = Configuration(raw_configuration)
|
|
423
|
+
self._current_player_id = observation.player
|
|
424
|
+
self._players: Dict[PlayerId, Player] = {}
|
|
425
|
+
self._ships: Dict[ShipId, Ship] = {}
|
|
426
|
+
self._shipyards: Dict[ShipyardId, Shipyard] = {}
|
|
427
|
+
self._cells: Dict[Point, Cell] = {}
|
|
428
|
+
|
|
429
|
+
size = self.configuration.size
|
|
430
|
+
# Create a cell for every point in a size x size grid
|
|
431
|
+
for x in range(size):
|
|
432
|
+
for y in range(size):
|
|
433
|
+
position = Point(x, y)
|
|
434
|
+
halite = observation.halite[position.to_index(size)]
|
|
435
|
+
# We'll populate the cell's ships and shipyards in _add_ship and _add_shipyard
|
|
436
|
+
self.cells[position] = Cell(position, halite, None, None, self)
|
|
437
|
+
|
|
438
|
+
for player_id, player_observation in enumerate(observation.players):
|
|
439
|
+
# We know the len(player_observation) == 3 based on the schema -- this is a hack to have a tuple in json
|
|
440
|
+
[player_halite, player_shipyards, player_ships] = player_observation
|
|
441
|
+
# We'll populate the player's ships and shipyards in _add_ship and _add_shipyard
|
|
442
|
+
self.players[player_id] = Player(player_id, player_halite, [], [], self)
|
|
443
|
+
player_actions = next_actions[player_id] or {}
|
|
444
|
+
|
|
445
|
+
for ship_id, [ship_index, ship_halite] in player_ships.items():
|
|
446
|
+
# In the raw observation, halite is stored as a 1d list but we convert it to a 2d dict for convenience
|
|
447
|
+
# Accordingly we also need to convert our list indices to dict keys / 2d positions
|
|
448
|
+
ship_position = Point.from_index(ship_index, size)
|
|
449
|
+
raw_action = player_actions.get(ship_id)
|
|
450
|
+
action = ShipAction[raw_action] if raw_action in ShipAction.__members__ else None
|
|
451
|
+
self._add_ship(Ship(ship_id, ship_position, ship_halite, player_id, self, action))
|
|
452
|
+
|
|
453
|
+
for shipyard_id, shipyard_index in player_shipyards.items():
|
|
454
|
+
shipyard_position = Point.from_index(shipyard_index, size)
|
|
455
|
+
raw_action = player_actions.get(shipyard_id)
|
|
456
|
+
action = ShipyardAction[raw_action] if raw_action in ShipyardAction.__members__ else None
|
|
457
|
+
self._add_shipyard(Shipyard(shipyard_id, shipyard_position, player_id, self, action))
|
|
458
|
+
|
|
459
|
+
@property
|
|
460
|
+
def configuration(self) -> Configuration:
|
|
461
|
+
return self._configuration
|
|
462
|
+
|
|
463
|
+
@property
|
|
464
|
+
def players(self) -> Dict[PlayerId, Player]:
|
|
465
|
+
return self._players
|
|
466
|
+
|
|
467
|
+
@property
|
|
468
|
+
def ships(self) -> Dict[ShipId, Ship]:
|
|
469
|
+
"""Returns all ships on the current board."""
|
|
470
|
+
return self._ships
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
def shipyards(self) -> Dict[ShipyardId, Shipyard]:
|
|
474
|
+
"""Returns all shipyards on the current board."""
|
|
475
|
+
return self._shipyards
|
|
476
|
+
|
|
477
|
+
@property
|
|
478
|
+
def cells(self) -> Dict[Point, Cell]:
|
|
479
|
+
"""Returns all cells on the current board."""
|
|
480
|
+
return self._cells
|
|
481
|
+
|
|
482
|
+
@property
|
|
483
|
+
def step(self) -> int:
|
|
484
|
+
return self._step
|
|
485
|
+
|
|
486
|
+
@property
|
|
487
|
+
def current_player_id(self) -> PlayerId:
|
|
488
|
+
return self._current_player_id
|
|
489
|
+
|
|
490
|
+
@property
|
|
491
|
+
def current_player(self) -> Player:
|
|
492
|
+
"""Returns the current player (generally this is you)."""
|
|
493
|
+
return self._players[self.current_player_id]
|
|
494
|
+
|
|
495
|
+
@property
|
|
496
|
+
def opponents(self) -> List[Player]:
|
|
497
|
+
"""
|
|
498
|
+
Returns all players that aren't the current player.
|
|
499
|
+
You can get all opponent ships with [ship for ship in player.ships for player in board.opponents]
|
|
500
|
+
"""
|
|
501
|
+
return [player for player in self.players.values() if not player.is_current_player]
|
|
502
|
+
|
|
503
|
+
@property
|
|
504
|
+
def observation(self) -> Dict[str, Any]:
|
|
505
|
+
"""Converts a Board back to the normalized observation that constructed it."""
|
|
506
|
+
size = self.configuration.size
|
|
507
|
+
halite = [self[Point.from_index(index, size)].halite for index in range(size * size)]
|
|
508
|
+
players = [player._observation for player in self.players.values()]
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
"halite": halite,
|
|
512
|
+
"players": players,
|
|
513
|
+
"player": self.current_player_id,
|
|
514
|
+
"step": self.step,
|
|
515
|
+
"remainingOverageTime": self._remaining_overage_time,
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
def __deepcopy__(self, _) -> "Board":
|
|
519
|
+
actions = [player.next_actions for player in self.players.values()]
|
|
520
|
+
return Board(self.observation, self.configuration, actions)
|
|
521
|
+
|
|
522
|
+
def __getitem__(self, point: Union[Tuple[int, int], Point]) -> Cell:
|
|
523
|
+
"""
|
|
524
|
+
This method will wrap the supplied position to fit within the board size and return the cell at that location.
|
|
525
|
+
e.g. on a 3x3 board, board[2, 1] is the same as board[5, 1]
|
|
526
|
+
"""
|
|
527
|
+
if not isinstance(point, Point):
|
|
528
|
+
(x, y) = point
|
|
529
|
+
point = Point(x, y)
|
|
530
|
+
return self._cells[point % self.configuration.size]
|
|
531
|
+
|
|
532
|
+
def __str__(self) -> str:
|
|
533
|
+
"""
|
|
534
|
+
The board is printed in a grid with the following rules:
|
|
535
|
+
Capital letters are shipyards
|
|
536
|
+
Lower case letters are ships
|
|
537
|
+
Digits are cell halite and scale from 0-9 directly proportional to a value between 0 and self.configuration.max_cell_halite
|
|
538
|
+
Player 1 is letter a/A
|
|
539
|
+
Player 2 is letter b/B
|
|
540
|
+
etc.
|
|
541
|
+
"""
|
|
542
|
+
size = self.configuration.size
|
|
543
|
+
result = ""
|
|
544
|
+
for y in range(size):
|
|
545
|
+
for x in range(size):
|
|
546
|
+
cell = self[(x, size - y - 1)]
|
|
547
|
+
result += "|"
|
|
548
|
+
result += chr(ord("a") + cell.ship.player_id) if cell.ship is not None else " "
|
|
549
|
+
# This normalizes a value from 0 to max_cell halite to a value from 0 to 9
|
|
550
|
+
normalized_halite = int(9.0 * cell.halite / float(self.configuration.max_cell_halite))
|
|
551
|
+
result += str(normalized_halite)
|
|
552
|
+
result += chr(ord("A") + cell.shipyard.player_id) if cell.shipyard is not None else " "
|
|
553
|
+
result += "|\n"
|
|
554
|
+
return result
|
|
555
|
+
|
|
556
|
+
def _add_ship(self: "Board", ship: Ship) -> None:
|
|
557
|
+
ship.player.ship_ids.append(ship.id)
|
|
558
|
+
ship.cell._ship_id = ship.id
|
|
559
|
+
self._ships[ship.id] = ship
|
|
560
|
+
|
|
561
|
+
def _add_shipyard(self: "Board", shipyard: Shipyard) -> None:
|
|
562
|
+
shipyard.player.shipyard_ids.append(shipyard.id)
|
|
563
|
+
shipyard.cell._shipyard_id = shipyard.id
|
|
564
|
+
shipyard.cell._halite = 0
|
|
565
|
+
self._shipyards[shipyard.id] = shipyard
|
|
566
|
+
|
|
567
|
+
def _delete_ship(self: "Board", ship: Ship) -> None:
|
|
568
|
+
ship.player.ship_ids.remove(ship.id)
|
|
569
|
+
if ship.cell.ship_id == ship.id:
|
|
570
|
+
ship.cell._ship_id = None
|
|
571
|
+
del self._ships[ship.id]
|
|
572
|
+
|
|
573
|
+
def _delete_shipyard(self: "Board", shipyard: Shipyard) -> None:
|
|
574
|
+
shipyard.player.shipyard_ids.remove(shipyard.id)
|
|
575
|
+
if shipyard.cell.shipyard_id == shipyard.id:
|
|
576
|
+
shipyard.cell._shipyard_id = None
|
|
577
|
+
del self._shipyards[shipyard.id]
|
|
578
|
+
|
|
579
|
+
def next(self) -> "Board":
|
|
580
|
+
"""
|
|
581
|
+
Returns a new board with the current board's next actions applied.
|
|
582
|
+
The current board is unmodified.
|
|
583
|
+
This can form a halite interpreter, e.g.
|
|
584
|
+
next_observation = Board(current_observation, configuration, actions).next().observation
|
|
585
|
+
"""
|
|
586
|
+
# Create a copy of the board to modify so we don't affect the current board
|
|
587
|
+
board = deepcopy(self)
|
|
588
|
+
configuration = board.configuration
|
|
589
|
+
convert_cost = configuration.convert_cost
|
|
590
|
+
spawn_cost = configuration.spawn_cost
|
|
591
|
+
uid_counter = 0
|
|
592
|
+
|
|
593
|
+
# This is a consistent way to generate unique strings to form ship and shipyard ids
|
|
594
|
+
def create_uid():
|
|
595
|
+
nonlocal uid_counter
|
|
596
|
+
uid_counter += 1
|
|
597
|
+
return f"{self.step + 1}-{uid_counter}"
|
|
598
|
+
|
|
599
|
+
# Process actions and store the results in the ships and shipyards lists for collision checking
|
|
600
|
+
for player in board.players.values():
|
|
601
|
+
leftover_convert_halite = 0
|
|
602
|
+
|
|
603
|
+
for shipyard in player.shipyards:
|
|
604
|
+
if shipyard.next_action == ShipyardAction.SPAWN and player.halite >= spawn_cost:
|
|
605
|
+
# Handle SPAWN actions
|
|
606
|
+
player._halite -= spawn_cost
|
|
607
|
+
board._add_ship(Ship(ShipId(create_uid()), shipyard.position, 0, player.id, board))
|
|
608
|
+
# Clear the shipyard's action so it doesn't repeat the same action automatically
|
|
609
|
+
shipyard.next_action = None
|
|
610
|
+
|
|
611
|
+
for ship in player.ships:
|
|
612
|
+
if ship.next_action == ShipAction.CONVERT:
|
|
613
|
+
# Can't convert on an existing shipyard but you can use halite in a ship to fund conversion
|
|
614
|
+
if ship.cell.shipyard_id is None and (ship.halite + player.halite) >= convert_cost:
|
|
615
|
+
# Handle CONVERT actions
|
|
616
|
+
delta_halite = ship.halite - convert_cost
|
|
617
|
+
# Excess halite leftover from conversion is added to the player's total only after all conversions have completed
|
|
618
|
+
# This is to prevent the edge case of chaining halite from one convert to fund other converts
|
|
619
|
+
leftover_convert_halite += max(delta_halite, 0)
|
|
620
|
+
player._halite += min(delta_halite, 0)
|
|
621
|
+
board._add_shipyard(Shipyard(ShipyardId(create_uid()), ship.position, player.id, board))
|
|
622
|
+
board._delete_ship(ship)
|
|
623
|
+
elif ship.next_action is not None:
|
|
624
|
+
# If the action is not None and is not CONVERT it must be NORTH, SOUTH, EAST, or WEST
|
|
625
|
+
ship.cell._ship_id = None
|
|
626
|
+
ship._position = ship.position.translate(ship.next_action.to_point(), configuration.size)
|
|
627
|
+
ship._halite *= 1 - board.configuration.move_cost
|
|
628
|
+
# We don't set the new cell's ship_id here as it would be overwritten by another ship in the case of collision.
|
|
629
|
+
# Later we'll iterate through all ships and re-set the cell._ship_id as appropriate.
|
|
630
|
+
|
|
631
|
+
player._halite += leftover_convert_halite
|
|
632
|
+
# Lets just check and make sure.
|
|
633
|
+
assert player.halite >= 0
|
|
634
|
+
|
|
635
|
+
def resolve_collision(ships: List[Ship]) -> Tuple[Optional[Ship], List[Ship]]:
|
|
636
|
+
"""
|
|
637
|
+
Accepts the list of ships at a particular position (must not be empty).
|
|
638
|
+
Returns the ship with the least halite or None in the case of a tie along with all other ships.
|
|
639
|
+
"""
|
|
640
|
+
if len(ships) == 1:
|
|
641
|
+
return ships[0], []
|
|
642
|
+
ships_by_halite = group_by(ships, lambda ship: ship.halite)
|
|
643
|
+
smallest_halite = min(ships_by_halite.keys())
|
|
644
|
+
smallest_ships = ships_by_halite[smallest_halite]
|
|
645
|
+
if len(smallest_ships) == 1:
|
|
646
|
+
# There was a winner, return it
|
|
647
|
+
winner = smallest_ships[0]
|
|
648
|
+
return winner, [ship for ship in ships if ship != winner]
|
|
649
|
+
# There was a tie for least halite, all are deleted
|
|
650
|
+
return None, ships
|
|
651
|
+
|
|
652
|
+
# Check for ship to ship collisions
|
|
653
|
+
ship_collision_groups = group_by(board.ships.values(), lambda ship: ship.position)
|
|
654
|
+
for position, collided_ships in ship_collision_groups.items():
|
|
655
|
+
winner, deleted = resolve_collision(collided_ships)
|
|
656
|
+
if winner is not None:
|
|
657
|
+
winner.cell._ship_id = winner.id
|
|
658
|
+
for ship in deleted:
|
|
659
|
+
board._delete_ship(ship)
|
|
660
|
+
if winner is not None:
|
|
661
|
+
# Winner takes deleted ships' halite
|
|
662
|
+
winner._halite += ship.halite
|
|
663
|
+
|
|
664
|
+
# Check for ship to shipyard collisions
|
|
665
|
+
for shipyard in list(board.shipyards.values()):
|
|
666
|
+
ship = shipyard.cell.ship
|
|
667
|
+
if ship is not None and ship.player_id != shipyard.player_id:
|
|
668
|
+
# Ship to shipyard collision
|
|
669
|
+
board._delete_shipyard(shipyard)
|
|
670
|
+
board._delete_ship(ship)
|
|
671
|
+
|
|
672
|
+
# Deposit halite from ships into shipyards
|
|
673
|
+
for shipyard in list(board.shipyards.values()):
|
|
674
|
+
ship = shipyard.cell.ship
|
|
675
|
+
if ship is not None and ship.player_id == shipyard.player_id:
|
|
676
|
+
shipyard.player._halite += ship.halite
|
|
677
|
+
ship._halite = 0
|
|
678
|
+
|
|
679
|
+
# Collect halite from cells into ships
|
|
680
|
+
for ship in board.ships.values():
|
|
681
|
+
cell = ship.cell
|
|
682
|
+
delta_halite = int(cell.halite * configuration.collect_rate)
|
|
683
|
+
if ship.next_action not in ShipAction.moves() and cell.shipyard_id is None and delta_halite > 0:
|
|
684
|
+
ship._halite += delta_halite
|
|
685
|
+
cell._halite -= delta_halite
|
|
686
|
+
# Clear the ship's action so it doesn't repeat the same action automatically
|
|
687
|
+
ship.next_action = None
|
|
688
|
+
|
|
689
|
+
# Regenerate halite in cells
|
|
690
|
+
for cell in board.cells.values():
|
|
691
|
+
if cell.ship_id is None:
|
|
692
|
+
next_halite = round(cell.halite * (1 + configuration.regen_rate), 3)
|
|
693
|
+
cell._halite = min(next_halite, configuration.max_cell_halite)
|
|
694
|
+
# Lets just check and make sure.
|
|
695
|
+
assert cell.halite >= 0
|
|
696
|
+
|
|
697
|
+
board._step += 1
|
|
698
|
+
|
|
699
|
+
return board
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
def board_agent(agent: Callable[[Board], None]):
|
|
703
|
+
"""
|
|
704
|
+
Decorator used to create an agent that modifies a board rather than an observation and a configuration
|
|
705
|
+
Automatically returns the modified board's next actions
|
|
706
|
+
|
|
707
|
+
@board_agent
|
|
708
|
+
def my_agent(board: Board) -> None:
|
|
709
|
+
...
|
|
710
|
+
"""
|
|
711
|
+
|
|
712
|
+
@wraps(agent)
|
|
713
|
+
def agent_wrapper(obs, config) -> Dict[str, str]:
|
|
714
|
+
board = Board(obs, config)
|
|
715
|
+
agent(board)
|
|
716
|
+
return board.current_player.next_actions
|
|
717
|
+
|
|
718
|
+
if agent.__module__ is not None and agent.__module__ in sys.modules:
|
|
719
|
+
setattr(sys.modules[agent.__module__], agent.__name__, agent_wrapper)
|
|
720
|
+
return agent_wrapper
|