island-battles-python 0.1.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.
- island_battles_client/__init__.py +89 -0
- island_battles_client/_entities.py +107 -0
- island_battles_client/_generated.py +870 -0
- island_battles_client/bot.py +189 -0
- island_battles_client/cli.py +539 -0
- island_battles_client/client.py +763 -0
- island_battles_client/errors.py +92 -0
- island_battles_client/fixture.py +88 -0
- island_battles_client/game_log.py +346 -0
- island_battles_client/models.py +33 -0
- island_battles_python-0.1.0.dist-info/METADATA +8 -0
- island_battles_python-0.1.0.dist-info/RECORD +14 -0
- island_battles_python-0.1.0.dist-info/WHEEL +4 -0
- island_battles_python-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
# Generated by scripts/generate.py from backend/api/openapi.json. Do not edit;
|
|
2
|
+
# change the Go API types, export the document, and regenerate instead.
|
|
3
|
+
|
|
4
|
+
from collections.abc import Mapping, Sequence
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from typing import Annotated, Any, Literal
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
|
|
9
|
+
from island_battles_client._entities import (
|
|
10
|
+
BoardEntity,
|
|
11
|
+
GameModel,
|
|
12
|
+
GenerationBase,
|
|
13
|
+
SoldierGroupBase,
|
|
14
|
+
)
|
|
15
|
+
from pydantic import (
|
|
16
|
+
AnyUrl,
|
|
17
|
+
AwareDatetime,
|
|
18
|
+
ConfigDict,
|
|
19
|
+
Field,
|
|
20
|
+
RootModel,
|
|
21
|
+
StrictBool,
|
|
22
|
+
StrictFloat,
|
|
23
|
+
StrictInt,
|
|
24
|
+
StrictStr,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BotChoice(GameModel):
|
|
29
|
+
model_config = ConfigDict(
|
|
30
|
+
extra="ignore",
|
|
31
|
+
)
|
|
32
|
+
kind: Literal["localhost", "default"]
|
|
33
|
+
"""
|
|
34
|
+
localhost: the player's own bot, which island-battles-bot serve runs on their machine; their game client relays each turn to it and submits its answer. default: a default bot of GET /bots/defaults, which the backend plays itself, so the player's client submits nothing.
|
|
35
|
+
"""
|
|
36
|
+
level: Annotated[StrictInt | None, Field(ge=1)] = None
|
|
37
|
+
"""
|
|
38
|
+
For kind default, the level of the default bot, one that GET /bots/defaults lists. Absent for kind localhost.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class BotTurn(GameModel):
|
|
43
|
+
model_config = ConfigDict(
|
|
44
|
+
extra="ignore",
|
|
45
|
+
)
|
|
46
|
+
game_id: Annotated[StrictInt, Field(ge=1)]
|
|
47
|
+
generation: Generation
|
|
48
|
+
memory: Mapping[str, Any] | None
|
|
49
|
+
"""
|
|
50
|
+
The memory the bot returned with its previous turn in this game, or null on its first turn and after a turn that returned null.
|
|
51
|
+
"""
|
|
52
|
+
team_color: Annotated[StrictInt, Field(ge=1)]
|
|
53
|
+
turn_deadline_at: AwareDatetime | None
|
|
54
|
+
"""
|
|
55
|
+
When the turn times out, GenerationState.turn_deadline_at, or null when turns have no time limit. A bot that hasn't answered by then makes its whole team lose.
|
|
56
|
+
"""
|
|
57
|
+
upgrades: Sequence[Upgrade]
|
|
58
|
+
"""
|
|
59
|
+
The upgrade cost table, the same list GET /game/upgrades returns as its upgrades. It is the same for the whole game, so a client reads it once and reuses it in every turn. Reaching a level costs its soldier_cost.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class BotTurnResult(GameModel):
|
|
64
|
+
model_config = ConfigDict(
|
|
65
|
+
extra="ignore",
|
|
66
|
+
)
|
|
67
|
+
commands: Annotated[Sequence[PublicGenerationCommand], Field(max_length=1000)]
|
|
68
|
+
logs: Annotated[Sequence[GenerationLogLine], Field(max_length=100)]
|
|
69
|
+
"""
|
|
70
|
+
Game log lines for this turn, of at most 500 characters each.
|
|
71
|
+
"""
|
|
72
|
+
memory: Mapping[str, Any] | None
|
|
73
|
+
"""
|
|
74
|
+
Any JSON object of at most 64 KiB as compact JSON, given back as BotTurn.memory next turn, or null for none.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Castle(BoardEntity):
|
|
79
|
+
model_config = ConfigDict(
|
|
80
|
+
extra="ignore",
|
|
81
|
+
)
|
|
82
|
+
id: Annotated[StrictInt, Field(ge=0)]
|
|
83
|
+
"""
|
|
84
|
+
Castle ID, stable for the whole game.
|
|
85
|
+
"""
|
|
86
|
+
soldiers: Annotated[StrictInt, Field(ge=0)]
|
|
87
|
+
team_color: Annotated[StrictInt, Field(ge=0)]
|
|
88
|
+
"""
|
|
89
|
+
Owning team; 0 is neutral.
|
|
90
|
+
"""
|
|
91
|
+
upgrades: Mapping[UpgradeType, StrictInt]
|
|
92
|
+
"""
|
|
93
|
+
Every upgrade type's level. GET /game/upgrades lists the levels and what the next one costs.
|
|
94
|
+
"""
|
|
95
|
+
x: StrictFloat
|
|
96
|
+
y: StrictFloat
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class CastleTemplate(GameModel):
|
|
100
|
+
model_config = ConfigDict(
|
|
101
|
+
extra="ignore",
|
|
102
|
+
)
|
|
103
|
+
starting_soldier_amount: Annotated[StrictInt | None, Field(ge=0)] = None
|
|
104
|
+
"""
|
|
105
|
+
Soldiers in the castle when a game starts. Defaults to neutral_starting_soldier_amount.
|
|
106
|
+
"""
|
|
107
|
+
team_color: Annotated[StrictInt, Field(ge=0)]
|
|
108
|
+
"""
|
|
109
|
+
Owning team colour, from 1 to player_amount; 0 is neutral.
|
|
110
|
+
"""
|
|
111
|
+
upgrades: Mapping[UpgradeType, StrictInt] | None = None
|
|
112
|
+
"""
|
|
113
|
+
Starting level of each upgrade type. Omitted types start at level 1.
|
|
114
|
+
"""
|
|
115
|
+
x: StrictFloat
|
|
116
|
+
"""
|
|
117
|
+
Distance from the centre of the plane, between -plane_size_x/2 and plane_size_x/2.
|
|
118
|
+
"""
|
|
119
|
+
y: StrictFloat
|
|
120
|
+
"""
|
|
121
|
+
Distance from the centre of the plane, between -plane_size_y/2 and plane_size_y/2.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Clash(GameModel):
|
|
126
|
+
model_config = ConfigDict(
|
|
127
|
+
extra="ignore",
|
|
128
|
+
)
|
|
129
|
+
id: Annotated[StrictInt, Field(ge=1)]
|
|
130
|
+
"""
|
|
131
|
+
Identifies the clash within its generation.
|
|
132
|
+
"""
|
|
133
|
+
participants: Sequence[ClashParticipant]
|
|
134
|
+
time: Annotated[StrictFloat, Field(ge=0.0, le=1.0)]
|
|
135
|
+
"""
|
|
136
|
+
Fraction of the turn at which contact happened.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class ClashParticipant(GameModel):
|
|
141
|
+
model_config = ConfigDict(
|
|
142
|
+
extra="ignore",
|
|
143
|
+
)
|
|
144
|
+
clash_radius: Annotated[StrictFloat, Field(gt=0.0)]
|
|
145
|
+
group_id: Annotated[StrictInt, Field(ge=0)]
|
|
146
|
+
"""
|
|
147
|
+
Soldier group ID; the group may no longer exist.
|
|
148
|
+
"""
|
|
149
|
+
soldiers_after: Annotated[StrictInt, Field(ge=0)]
|
|
150
|
+
"""
|
|
151
|
+
Soldiers immediately after this contact; 0 means the group was defeated.
|
|
152
|
+
"""
|
|
153
|
+
soldiers_before: Annotated[StrictInt, Field(ge=0)]
|
|
154
|
+
"""
|
|
155
|
+
Soldiers immediately before this contact.
|
|
156
|
+
"""
|
|
157
|
+
team_color: Annotated[StrictInt, Field(ge=0)]
|
|
158
|
+
x: StrictFloat
|
|
159
|
+
y: StrictFloat
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class ConnectionToken(GameModel):
|
|
163
|
+
model_config = ConfigDict(
|
|
164
|
+
extra="ignore",
|
|
165
|
+
)
|
|
166
|
+
field_schema: Annotated[
|
|
167
|
+
AnyUrl | None,
|
|
168
|
+
Field(alias="$schema", examples=["https://example.com/ConnectionToken.json"]),
|
|
169
|
+
] = None
|
|
170
|
+
"""
|
|
171
|
+
A URL to the JSON Schema for this object.
|
|
172
|
+
"""
|
|
173
|
+
expires_at: AwareDatetime
|
|
174
|
+
token: StrictStr
|
|
175
|
+
websocket_url: AnyUrl
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class ConnectionTokenInputBody(GameModel):
|
|
179
|
+
model_config = ConfigDict(
|
|
180
|
+
extra="ignore",
|
|
181
|
+
)
|
|
182
|
+
field_schema: Annotated[
|
|
183
|
+
AnyUrl | None,
|
|
184
|
+
Field(
|
|
185
|
+
alias="$schema",
|
|
186
|
+
examples=["https://example.com/ConnectionTokenInputBody.json"],
|
|
187
|
+
),
|
|
188
|
+
] = None
|
|
189
|
+
"""
|
|
190
|
+
A URL to the JSON Schema for this object.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class DefaultBot(GameModel):
|
|
195
|
+
model_config = ConfigDict(
|
|
196
|
+
extra="ignore",
|
|
197
|
+
)
|
|
198
|
+
description: StrictStr
|
|
199
|
+
"""
|
|
200
|
+
How the bot plays, in a sentence or two.
|
|
201
|
+
"""
|
|
202
|
+
level: Annotated[StrictInt, Field(ge=1)]
|
|
203
|
+
"""
|
|
204
|
+
The bot's level. Higher levels play better.
|
|
205
|
+
"""
|
|
206
|
+
name: StrictStr
|
|
207
|
+
"""
|
|
208
|
+
A short name to show in a bot picker.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class Demo(GameModel):
|
|
213
|
+
model_config = ConfigDict(
|
|
214
|
+
extra="ignore",
|
|
215
|
+
)
|
|
216
|
+
format: Literal["island-battles-demo"]
|
|
217
|
+
"""
|
|
218
|
+
Marks the file as an Island Battles demo.
|
|
219
|
+
"""
|
|
220
|
+
frames: Annotated[Sequence[Generation], Field(min_length=1)]
|
|
221
|
+
"""
|
|
222
|
+
Every generation played when the demo was saved, in order. Each is the board view that players received.
|
|
223
|
+
"""
|
|
224
|
+
game_id: Annotated[StrictInt, Field(ge=1)]
|
|
225
|
+
logs: Sequence[GenerationLogs]
|
|
226
|
+
"""
|
|
227
|
+
One entry per frame, in the same order, with the game log lines its saver could see when saving: while the game runs only the saver's team's, once it has ended every team's. hidden_team_colors names the teams left out.
|
|
228
|
+
"""
|
|
229
|
+
outcome: GameOutcome | None
|
|
230
|
+
"""
|
|
231
|
+
How the game ended, or null when the demo was saved while the game was still running.
|
|
232
|
+
"""
|
|
233
|
+
saved_at: AwareDatetime
|
|
234
|
+
"""
|
|
235
|
+
When the demo was saved.
|
|
236
|
+
"""
|
|
237
|
+
schema_version: Literal[1]
|
|
238
|
+
"""
|
|
239
|
+
The demo format's version. The demo viewer opens only the current version and rejects a demo saved by another version of the game.
|
|
240
|
+
"""
|
|
241
|
+
teams: Sequence[GameTeam]
|
|
242
|
+
"""
|
|
243
|
+
The teams that played, with their lobby names and the display names their players had at the start.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class ErrorDetail(GameModel):
|
|
248
|
+
model_config = ConfigDict(
|
|
249
|
+
extra="ignore",
|
|
250
|
+
)
|
|
251
|
+
location: StrictStr | None = None
|
|
252
|
+
"""
|
|
253
|
+
Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
|
|
254
|
+
"""
|
|
255
|
+
message: StrictStr | None = None
|
|
256
|
+
"""
|
|
257
|
+
Error message text
|
|
258
|
+
"""
|
|
259
|
+
value: Any | None = None
|
|
260
|
+
"""
|
|
261
|
+
The value at the given location
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class ErrorModel(GameModel):
|
|
266
|
+
model_config = ConfigDict(
|
|
267
|
+
extra="ignore",
|
|
268
|
+
)
|
|
269
|
+
field_schema: Annotated[
|
|
270
|
+
AnyUrl | None,
|
|
271
|
+
Field(alias="$schema", examples=["https://example.com/ErrorModel.json"]),
|
|
272
|
+
] = None
|
|
273
|
+
"""
|
|
274
|
+
A URL to the JSON Schema for this object.
|
|
275
|
+
"""
|
|
276
|
+
detail: Annotated[
|
|
277
|
+
StrictStr | None, Field(examples=["Property foo is required but is missing."])
|
|
278
|
+
] = None
|
|
279
|
+
"""
|
|
280
|
+
A human-readable explanation specific to this occurrence of the problem.
|
|
281
|
+
"""
|
|
282
|
+
errors: Sequence[ErrorDetail] | None = None
|
|
283
|
+
"""
|
|
284
|
+
Optional list of individual error details
|
|
285
|
+
"""
|
|
286
|
+
instance: Annotated[
|
|
287
|
+
AnyUrl | None, Field(examples=["https://example.com/error-log/abc123"])
|
|
288
|
+
] = None
|
|
289
|
+
"""
|
|
290
|
+
A URI reference that identifies the specific occurrence of the problem.
|
|
291
|
+
"""
|
|
292
|
+
status: Annotated[StrictInt | None, Field(examples=[400])] = None
|
|
293
|
+
"""
|
|
294
|
+
HTTP status code
|
|
295
|
+
"""
|
|
296
|
+
title: Annotated[StrictStr | None, Field(examples=["Bad Request"])] = None
|
|
297
|
+
"""
|
|
298
|
+
A short, human-readable summary of the problem type. This value should not change between occurrences of the error.
|
|
299
|
+
"""
|
|
300
|
+
type: Annotated[
|
|
301
|
+
AnyUrl | None, Field(examples=["https://example.com/errors/example"])
|
|
302
|
+
] = "about:blank"
|
|
303
|
+
"""
|
|
304
|
+
A URI reference to human-readable documentation for the error.
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class GameEndedEvent(GameModel):
|
|
309
|
+
model_config = ConfigDict(
|
|
310
|
+
extra="ignore",
|
|
311
|
+
)
|
|
312
|
+
schema_version: Literal[1]
|
|
313
|
+
state: GenerationState
|
|
314
|
+
"""
|
|
315
|
+
The final generation, with its outcome.
|
|
316
|
+
"""
|
|
317
|
+
type: Literal["game.ended"]
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class GameOutcome(GameModel):
|
|
321
|
+
model_config = ConfigDict(
|
|
322
|
+
extra="ignore",
|
|
323
|
+
)
|
|
324
|
+
reason: Literal[
|
|
325
|
+
"last_team_standing", "all_eliminated", "generation_cap", "turn_timeout"
|
|
326
|
+
]
|
|
327
|
+
"""
|
|
328
|
+
last_team_standing: one team was left after every other team lost all its castles and soldier groups. all_eliminated: the last teams were eliminated in the same turn, a draw. generation_cap: the game reached its last generation; the team with the most castles won, and equal counts are a draw. turn_timeout: a turn passed its deadline; every team with a player who hadn't completed it lost, and among the other teams still standing the team with the most castles won, while equal counts, or no team left, are a draw.
|
|
329
|
+
"""
|
|
330
|
+
summary: StrictStr
|
|
331
|
+
"""
|
|
332
|
+
The outcome in one sentence, naming the winning team, and for turn_timeout the teams that lost and their players who didn't answer.
|
|
333
|
+
"""
|
|
334
|
+
winner_team_color: Annotated[StrictInt | None, Field(ge=1)]
|
|
335
|
+
"""
|
|
336
|
+
The winning team, or null for a draw.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class GameTeam(GameModel):
|
|
341
|
+
model_config = ConfigDict(
|
|
342
|
+
extra="ignore",
|
|
343
|
+
)
|
|
344
|
+
color: Annotated[StrictInt, Field(ge=1)]
|
|
345
|
+
"""
|
|
346
|
+
The team colour that castles and soldier groups carry as team_color.
|
|
347
|
+
"""
|
|
348
|
+
name: StrictStr
|
|
349
|
+
players: Sequence[StrictStr]
|
|
350
|
+
"""
|
|
351
|
+
The display names the team's players had at the start.
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class Generation(GenerationBase):
|
|
356
|
+
model_config = ConfigDict(
|
|
357
|
+
extra="ignore",
|
|
358
|
+
)
|
|
359
|
+
castles: Sequence[Castle]
|
|
360
|
+
clashes: Sequence[Clash]
|
|
361
|
+
id: Annotated[StrictInt, Field(ge=1)]
|
|
362
|
+
"""
|
|
363
|
+
Generation ID; increases with every turn.
|
|
364
|
+
"""
|
|
365
|
+
soldier_groups: Sequence[SoldierGroup]
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class GenerationEvent(GameModel):
|
|
369
|
+
model_config = ConfigDict(
|
|
370
|
+
extra="ignore",
|
|
371
|
+
)
|
|
372
|
+
schema_version: Literal[1]
|
|
373
|
+
state: GenerationState
|
|
374
|
+
type: Literal["game.next_generation"]
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class GenerationLogLine(GameModel):
|
|
378
|
+
model_config = ConfigDict(
|
|
379
|
+
extra="ignore",
|
|
380
|
+
)
|
|
381
|
+
level: Literal["debug", "info", "warning", "error"]
|
|
382
|
+
logger: Annotated[StrictStr, Field(max_length=100)]
|
|
383
|
+
"""
|
|
384
|
+
The name of the logger that wrote the line, such as island_battles.player.
|
|
385
|
+
"""
|
|
386
|
+
message: Annotated[StrictStr, Field(max_length=500, min_length=1)]
|
|
387
|
+
"""
|
|
388
|
+
The line, of 1 to 500 Unicode code points. A client cuts a longer line to fit and says so at its end.
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class GenerationLogs(GameModel):
|
|
393
|
+
model_config = ConfigDict(
|
|
394
|
+
extra="ignore",
|
|
395
|
+
)
|
|
396
|
+
field_schema: Annotated[
|
|
397
|
+
AnyUrl | None,
|
|
398
|
+
Field(alias="$schema", examples=["https://example.com/GenerationLogs.json"]),
|
|
399
|
+
] = None
|
|
400
|
+
"""
|
|
401
|
+
A URL to the JSON Schema for this object.
|
|
402
|
+
"""
|
|
403
|
+
generation_id: Annotated[StrictInt, Field(ge=1)]
|
|
404
|
+
hidden_team_colors: Sequence[HiddenTeamColor]
|
|
405
|
+
"""
|
|
406
|
+
Teams whose logs you can't see yet. While the game runs you see only your own team's logs; once it has an outcome you see every team's.
|
|
407
|
+
"""
|
|
408
|
+
players: Sequence[PlayerGenerationLogs]
|
|
409
|
+
"""
|
|
410
|
+
Every player of the teams you can see, ordered by team colour and then display name, with their lines in the order they were written.
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class GenerationState(GameModel):
|
|
415
|
+
model_config = ConfigDict(
|
|
416
|
+
extra="ignore",
|
|
417
|
+
)
|
|
418
|
+
field_schema: Annotated[
|
|
419
|
+
AnyUrl | None,
|
|
420
|
+
Field(alias="$schema", examples=["https://example.com/GenerationState.json"]),
|
|
421
|
+
] = None
|
|
422
|
+
"""
|
|
423
|
+
A URL to the JSON Schema for this object.
|
|
424
|
+
"""
|
|
425
|
+
bot: BotChoice
|
|
426
|
+
"""
|
|
427
|
+
Your bot in this game, the one you chose in the lobby, copied when the game started. For kind localhost your client relays each turn to your bot and submits its answer. For kind default the backend plays your turns itself as soon as each generation is created, so your client submits nothing.
|
|
428
|
+
"""
|
|
429
|
+
channel: StrictStr
|
|
430
|
+
"""
|
|
431
|
+
Your personal Centrifugo channel, which receives GenerationEvent after each turn and GameEndedEvent after the last one.
|
|
432
|
+
"""
|
|
433
|
+
game_id: Annotated[StrictInt, Field(ge=1)]
|
|
434
|
+
generation: Generation
|
|
435
|
+
generation_id: Annotated[StrictInt, Field(ge=1)]
|
|
436
|
+
lobby_id: UUID
|
|
437
|
+
log_lines_stored: Annotated[StrictInt, Field(ge=0)]
|
|
438
|
+
"""
|
|
439
|
+
How many game log lines you have stored for this generation, out of the 100 a player may store per generation. 0 when there are none. A client that restarts during a turn continues counting from it.
|
|
440
|
+
"""
|
|
441
|
+
memory: Mapping[str, Any] | None
|
|
442
|
+
"""
|
|
443
|
+
The memory sent with your latest completed turn of this game, for your bot's next BotTurn.memory. Null before your first completed turn, and after a completed turn without memory.
|
|
444
|
+
"""
|
|
445
|
+
outcome: GameOutcome | None = None
|
|
446
|
+
"""
|
|
447
|
+
How the game ended. Absent while the game runs. A finished game keeps its final generation and rejects every command.
|
|
448
|
+
"""
|
|
449
|
+
player_id: UUID
|
|
450
|
+
submitted: StrictBool
|
|
451
|
+
"""
|
|
452
|
+
Whether you completed this generation's turn.
|
|
453
|
+
"""
|
|
454
|
+
team_color: Annotated[StrictInt, Field(ge=1)]
|
|
455
|
+
turn_deadline_at: AwareDatetime | None
|
|
456
|
+
"""
|
|
457
|
+
When this generation's turn times out: the generation's creation time plus the lobby's turn_timeout_seconds. Null when turns have no time limit, and once the game has ended. If a player of a team hasn't completed the turn by then, the team loses and the game ends. A client waiting for other players calls POST /game/from-lobby/{lobby_id}/deadline about a second after it, because the server runs no timers.
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class HiddenTeamColor(RootModel[StrictInt]):
|
|
462
|
+
root: Annotated[StrictInt, Field(ge=0)]
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
class ListDefaultBotsResponseBody(GameModel):
|
|
466
|
+
model_config = ConfigDict(
|
|
467
|
+
extra="ignore",
|
|
468
|
+
)
|
|
469
|
+
field_schema: Annotated[
|
|
470
|
+
AnyUrl | None,
|
|
471
|
+
Field(
|
|
472
|
+
alias="$schema",
|
|
473
|
+
examples=["https://example.com/ListDefaultBotsResponseBody.json"],
|
|
474
|
+
),
|
|
475
|
+
] = None
|
|
476
|
+
"""
|
|
477
|
+
A URL to the JSON Schema for this object.
|
|
478
|
+
"""
|
|
479
|
+
bots: Sequence[DefaultBot]
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
class ListMapTemplatesResponseBody(GameModel):
|
|
483
|
+
model_config = ConfigDict(
|
|
484
|
+
extra="ignore",
|
|
485
|
+
)
|
|
486
|
+
field_schema: Annotated[
|
|
487
|
+
AnyUrl | None,
|
|
488
|
+
Field(
|
|
489
|
+
alias="$schema",
|
|
490
|
+
examples=["https://example.com/ListMapTemplatesResponseBody.json"],
|
|
491
|
+
),
|
|
492
|
+
] = None
|
|
493
|
+
"""
|
|
494
|
+
A URL to the JSON Schema for this object.
|
|
495
|
+
"""
|
|
496
|
+
maps: Sequence[MapTemplateSummary]
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
class ListUpgradesResponseBody(GameModel):
|
|
500
|
+
model_config = ConfigDict(
|
|
501
|
+
extra="ignore",
|
|
502
|
+
)
|
|
503
|
+
field_schema: Annotated[
|
|
504
|
+
AnyUrl | None,
|
|
505
|
+
Field(
|
|
506
|
+
alias="$schema",
|
|
507
|
+
examples=["https://example.com/ListUpgradesResponseBody.json"],
|
|
508
|
+
),
|
|
509
|
+
] = None
|
|
510
|
+
"""
|
|
511
|
+
A URL to the JSON Schema for this object.
|
|
512
|
+
"""
|
|
513
|
+
upgrades: Sequence[Upgrade]
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
class LobbyBots(GameModel):
|
|
517
|
+
model_config = ConfigDict(
|
|
518
|
+
extra="ignore",
|
|
519
|
+
)
|
|
520
|
+
field_schema: Annotated[
|
|
521
|
+
AnyUrl | None,
|
|
522
|
+
Field(alias="$schema", examples=["https://example.com/LobbyBots.json"]),
|
|
523
|
+
] = None
|
|
524
|
+
"""
|
|
525
|
+
A URL to the JSON Schema for this object.
|
|
526
|
+
"""
|
|
527
|
+
lobby_id: UUID
|
|
528
|
+
members: Sequence[LobbyMemberBot]
|
|
529
|
+
"""
|
|
530
|
+
Every member of the lobby, in the lobby's member order, bot seats included.
|
|
531
|
+
"""
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
class LobbyMemberBot(GameModel):
|
|
535
|
+
model_config = ConfigDict(
|
|
536
|
+
extra="ignore",
|
|
537
|
+
)
|
|
538
|
+
bot: BotChoice | None = None
|
|
539
|
+
"""
|
|
540
|
+
The member's bot. Absent until they choose one, and a member can't get ready without one. A bot seat that the host added has the default bot of its level.
|
|
541
|
+
"""
|
|
542
|
+
display_name: StrictStr
|
|
543
|
+
player_id: UUID
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
class LobbySettings(GameModel):
|
|
547
|
+
model_config = ConfigDict(
|
|
548
|
+
extra="ignore",
|
|
549
|
+
)
|
|
550
|
+
map: MapTemplate | None = None
|
|
551
|
+
"""
|
|
552
|
+
Start on this map file. Cannot be combined with map_template_id.
|
|
553
|
+
"""
|
|
554
|
+
map_template_id: Annotated[StrictInt | None, Field(ge=1)] = None
|
|
555
|
+
"""
|
|
556
|
+
Start on this default map, a map_template_id from GET /map/templates. Cannot be combined with map.
|
|
557
|
+
"""
|
|
558
|
+
turn_timeout_seconds: Annotated[StrictInt | None, Field(ge=5, le=600)] = 30
|
|
559
|
+
"""
|
|
560
|
+
How long each turn lasts, in whole seconds from 5 to 600, counted from when its generation was created; 30 when left out. null means no limit, for example while debugging a bot. When a turn's deadline passes before every player has completed it, each team with a player who hadn't loses, and the game ends.
|
|
561
|
+
"""
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
class MapTemplate(GameModel):
|
|
565
|
+
model_config = ConfigDict(
|
|
566
|
+
extra="ignore",
|
|
567
|
+
)
|
|
568
|
+
castles: Annotated[Sequence[CastleTemplate], Field(min_length=1)]
|
|
569
|
+
"""
|
|
570
|
+
Each castle's position in the list is its castle ID in every game built from the map.
|
|
571
|
+
"""
|
|
572
|
+
map_name: Annotated[StrictStr, Field(min_length=1)]
|
|
573
|
+
"""
|
|
574
|
+
Name of the map, unique among default maps.
|
|
575
|
+
"""
|
|
576
|
+
neutral_starting_soldier_amount: Annotated[StrictInt, Field(ge=0)]
|
|
577
|
+
"""
|
|
578
|
+
Soldiers in every castle, owned or neutral, that sets no starting_soldier_amount.
|
|
579
|
+
"""
|
|
580
|
+
plane_size_x: Annotated[StrictInt, Field(ge=1)]
|
|
581
|
+
"""
|
|
582
|
+
Width of the plane, which is centred on the origin.
|
|
583
|
+
"""
|
|
584
|
+
plane_size_y: Annotated[StrictInt, Field(ge=1)]
|
|
585
|
+
"""
|
|
586
|
+
Height of the plane, which is centred on the origin.
|
|
587
|
+
"""
|
|
588
|
+
player_amount: Annotated[StrictInt, Field(ge=1)]
|
|
589
|
+
"""
|
|
590
|
+
Players the map is made for. Castle team colours run from 1 to player_amount.
|
|
591
|
+
"""
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
class MapTemplateSummary(GameModel):
|
|
595
|
+
model_config = ConfigDict(
|
|
596
|
+
extra="ignore",
|
|
597
|
+
)
|
|
598
|
+
castle_count: StrictInt
|
|
599
|
+
"""
|
|
600
|
+
Castles on the map, team and neutral.
|
|
601
|
+
"""
|
|
602
|
+
map_name: StrictStr
|
|
603
|
+
map_template_id: Annotated[StrictInt, Field(ge=0)]
|
|
604
|
+
"""
|
|
605
|
+
Choose the map by passing this ID as game_settings.map_template_id when creating a lobby.
|
|
606
|
+
"""
|
|
607
|
+
plane_size_x: Annotated[StrictInt, Field(ge=0)]
|
|
608
|
+
plane_size_y: Annotated[StrictInt, Field(ge=0)]
|
|
609
|
+
team_count: StrictInt
|
|
610
|
+
"""
|
|
611
|
+
Teams the map is made for, its player_amount. A lobby on this map has exactly this many teams, in team colour order.
|
|
612
|
+
"""
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
class PlayerGenerationLogs(GameModel):
|
|
616
|
+
model_config = ConfigDict(
|
|
617
|
+
extra="ignore",
|
|
618
|
+
)
|
|
619
|
+
display_name: StrictStr
|
|
620
|
+
lines: Sequence[GenerationLogLine]
|
|
621
|
+
team_color: Annotated[StrictInt, Field(ge=1)]
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
class Problem(GameModel):
|
|
625
|
+
model_config = ConfigDict(
|
|
626
|
+
extra="ignore",
|
|
627
|
+
)
|
|
628
|
+
code: Literal[
|
|
629
|
+
"unauthenticated",
|
|
630
|
+
"not_found",
|
|
631
|
+
"not_host",
|
|
632
|
+
"lobby_full",
|
|
633
|
+
"team_full",
|
|
634
|
+
"phase_conflict",
|
|
635
|
+
"not_ready",
|
|
636
|
+
"empty_teams",
|
|
637
|
+
"stale_membership",
|
|
638
|
+
"idempotency_conflict",
|
|
639
|
+
"invalid_input",
|
|
640
|
+
"invalid_game_settings",
|
|
641
|
+
"invalid_seat_settings",
|
|
642
|
+
"game_requirement",
|
|
643
|
+
"watcher_kicked",
|
|
644
|
+
"watchers_full",
|
|
645
|
+
"unavailable",
|
|
646
|
+
"internal_error",
|
|
647
|
+
]
|
|
648
|
+
detail: StrictStr
|
|
649
|
+
status: StrictInt
|
|
650
|
+
team_ids: Sequence[StrictStr] | None = None
|
|
651
|
+
"""
|
|
652
|
+
The teams the error is about. For empty_teams, the teams that still need a player, in team order.
|
|
653
|
+
"""
|
|
654
|
+
title: StrictStr
|
|
655
|
+
type: StrictStr | None = None
|
|
656
|
+
"""
|
|
657
|
+
For game_requirement, the game's own problem type, a URI that the game documents, such as one meaning a player has yet to choose something in the game's API. Absent for every other code.
|
|
658
|
+
"""
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
class RegisterGenerationCommands(GameModel):
|
|
662
|
+
model_config = ConfigDict(
|
|
663
|
+
extra="ignore",
|
|
664
|
+
)
|
|
665
|
+
field_schema: Annotated[
|
|
666
|
+
AnyUrl | None,
|
|
667
|
+
Field(
|
|
668
|
+
alias="$schema",
|
|
669
|
+
examples=["https://example.com/RegisterGenerationCommands.json"],
|
|
670
|
+
),
|
|
671
|
+
] = None
|
|
672
|
+
"""
|
|
673
|
+
A URL to the JSON Schema for this object.
|
|
674
|
+
"""
|
|
675
|
+
commands: Annotated[Sequence[PublicGenerationCommand], Field(max_length=1000)]
|
|
676
|
+
complete: StrictBool | None = None
|
|
677
|
+
"""
|
|
678
|
+
Seals your turn after this batch. Defaults to true.
|
|
679
|
+
"""
|
|
680
|
+
game_id: Annotated[StrictInt, Field(ge=1)]
|
|
681
|
+
generation_id: Annotated[StrictInt, Field(ge=1)]
|
|
682
|
+
logs: Annotated[Sequence[GenerationLogLine] | None, Field(max_length=100)] = None
|
|
683
|
+
"""
|
|
684
|
+
Game log lines written since your previous batch for this generation, appended in order after the lines already stored. At most 100 lines per player per generation, each of at most 500 characters; a batch that would break either limit is rejected whole with 422 and problem type urn:island-battles:problem:game-log-limit.
|
|
685
|
+
"""
|
|
686
|
+
memory: Mapping[str, Any] | None = None
|
|
687
|
+
"""
|
|
688
|
+
Your bot's memory, the BotTurnResult.memory it returned: any JSON object of at most 64 KiB as compact JSON. Only the batch with complete true saves it, replacing your previous memory, and an absent or null memory clears it; other batches ignore it. GenerationState.memory returns it. Larger memory is rejected whole with 422 and problem type urn:island-battles:problem:bot-memory-too-large.
|
|
689
|
+
"""
|
|
690
|
+
request_id: UUID
|
|
691
|
+
"""
|
|
692
|
+
Identifies this batch. Retry a lost request with the same UUID and body; use a new UUID for every new batch.
|
|
693
|
+
"""
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
class SendSoldierGroupCommand(GameModel):
|
|
697
|
+
model_config = ConfigDict(
|
|
698
|
+
extra="ignore",
|
|
699
|
+
)
|
|
700
|
+
command_type: Literal["send_soldier_group"]
|
|
701
|
+
parameters: SendSoldierGroupParameters
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
class SendSoldierGroupParameters(GameModel):
|
|
705
|
+
model_config = ConfigDict(
|
|
706
|
+
extra="ignore",
|
|
707
|
+
)
|
|
708
|
+
destination_castle_id: Annotated[StrictInt, Field(ge=0)]
|
|
709
|
+
"""
|
|
710
|
+
Any other castle.
|
|
711
|
+
"""
|
|
712
|
+
origin_castle_id: Annotated[StrictInt, Field(ge=0)]
|
|
713
|
+
"""
|
|
714
|
+
Castle your team owns.
|
|
715
|
+
"""
|
|
716
|
+
soldier_amount: Annotated[StrictInt, Field(ge=1)]
|
|
717
|
+
"""
|
|
718
|
+
Soldiers to send; counts against soldiers already reserved by your team this turn.
|
|
719
|
+
"""
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
class SoldierGroup(SoldierGroupBase):
|
|
723
|
+
model_config = ConfigDict(
|
|
724
|
+
extra="ignore",
|
|
725
|
+
)
|
|
726
|
+
clash_radius: Annotated[StrictFloat, Field(gt=0.0)]
|
|
727
|
+
"""
|
|
728
|
+
Enemies clash when their centers are within the larger of their radii.
|
|
729
|
+
"""
|
|
730
|
+
destination_castle_id: Annotated[StrictInt, Field(ge=0)]
|
|
731
|
+
id: Annotated[StrictInt, Field(ge=0)]
|
|
732
|
+
"""
|
|
733
|
+
Soldier group ID, never reassigned within a game.
|
|
734
|
+
"""
|
|
735
|
+
origin_castle_id: Annotated[StrictInt, Field(ge=0)]
|
|
736
|
+
soldiers: Annotated[StrictInt, Field(ge=0)]
|
|
737
|
+
team_color: Annotated[StrictInt, Field(ge=0)]
|
|
738
|
+
"""
|
|
739
|
+
Owning team; 0 is neutral.
|
|
740
|
+
"""
|
|
741
|
+
upgrades: Mapping[UpgradeType, StrictInt]
|
|
742
|
+
"""
|
|
743
|
+
Every upgrade type's level, copied from the origin castle when the group left.
|
|
744
|
+
"""
|
|
745
|
+
x: StrictFloat
|
|
746
|
+
y: StrictFloat
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
class Upgrade(GameModel):
|
|
750
|
+
model_config = ConfigDict(
|
|
751
|
+
extra="ignore",
|
|
752
|
+
)
|
|
753
|
+
description: StrictStr
|
|
754
|
+
"""
|
|
755
|
+
What the levels' values measure.
|
|
756
|
+
"""
|
|
757
|
+
levels: Sequence[UpgradeLevel]
|
|
758
|
+
"""
|
|
759
|
+
Every level from 1 to max_level, in order.
|
|
760
|
+
"""
|
|
761
|
+
max_level: Annotated[StrictInt, Field(ge=1)]
|
|
762
|
+
"""
|
|
763
|
+
The highest level. Every castle starts at level 1.
|
|
764
|
+
"""
|
|
765
|
+
type: UpgradeType
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
class UpgradeCastleCommand(GameModel):
|
|
769
|
+
model_config = ConfigDict(
|
|
770
|
+
extra="ignore",
|
|
771
|
+
)
|
|
772
|
+
command_type: Literal["upgrade_castle"]
|
|
773
|
+
parameters: UpgradeCastleParameters
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
class PublicGenerationCommand(
|
|
777
|
+
RootModel[SendSoldierGroupCommand | UpgradeCastleCommand]
|
|
778
|
+
):
|
|
779
|
+
root: Annotated[
|
|
780
|
+
SendSoldierGroupCommand | UpgradeCastleCommand,
|
|
781
|
+
Field(discriminator="command_type"),
|
|
782
|
+
]
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
class UpgradeCastleParameters(GameModel):
|
|
786
|
+
model_config = ConfigDict(
|
|
787
|
+
extra="ignore",
|
|
788
|
+
)
|
|
789
|
+
castle_id: Annotated[StrictInt, Field(ge=0)]
|
|
790
|
+
"""
|
|
791
|
+
Castle your team owns. It pays the level's soldier_cost from GET /game/upgrades.
|
|
792
|
+
"""
|
|
793
|
+
upgrade_type: UpgradeType
|
|
794
|
+
"""
|
|
795
|
+
The type to raise by one level. Several upgrades of a castle in one turn each buy the next level.
|
|
796
|
+
"""
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
class UpgradeLevel(GameModel):
|
|
800
|
+
model_config = ConfigDict(
|
|
801
|
+
extra="ignore",
|
|
802
|
+
)
|
|
803
|
+
level: Annotated[StrictInt, Field(ge=1)]
|
|
804
|
+
soldier_cost: Annotated[StrictInt, Field(ge=0)]
|
|
805
|
+
"""
|
|
806
|
+
Soldiers the castle pays to reach this level from the level below. Level 1 is free.
|
|
807
|
+
"""
|
|
808
|
+
value: StrictFloat
|
|
809
|
+
"""
|
|
810
|
+
The level's effect, in the unit its type's description names.
|
|
811
|
+
"""
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
class UpgradeType(StrEnum):
|
|
815
|
+
clash_stop_generations = "clash_stop_generations"
|
|
816
|
+
speed = "speed"
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
class WatchGameEndedEvent(GameModel):
|
|
820
|
+
model_config = ConfigDict(
|
|
821
|
+
extra="ignore",
|
|
822
|
+
)
|
|
823
|
+
schema_version: Literal[1]
|
|
824
|
+
state: WatchState
|
|
825
|
+
"""
|
|
826
|
+
The final generation, with its outcome.
|
|
827
|
+
"""
|
|
828
|
+
type: Literal["game.ended"]
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
class WatchGenerationEvent(GameModel):
|
|
832
|
+
model_config = ConfigDict(
|
|
833
|
+
extra="ignore",
|
|
834
|
+
)
|
|
835
|
+
schema_version: Literal[1]
|
|
836
|
+
state: WatchState
|
|
837
|
+
type: Literal["game.next_generation"]
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
class WatchState(GameModel):
|
|
841
|
+
model_config = ConfigDict(
|
|
842
|
+
extra="ignore",
|
|
843
|
+
)
|
|
844
|
+
field_schema: Annotated[
|
|
845
|
+
AnyUrl | None,
|
|
846
|
+
Field(alias="$schema", examples=["https://example.com/WatchState.json"]),
|
|
847
|
+
] = None
|
|
848
|
+
"""
|
|
849
|
+
A URL to the JSON Schema for this object.
|
|
850
|
+
"""
|
|
851
|
+
channel: StrictStr
|
|
852
|
+
"""
|
|
853
|
+
The lobby's watch channel, watch:island-battles:<lobby_id>, which receives WatchGenerationEvent after each turn and WatchGameEndedEvent after the last one.
|
|
854
|
+
"""
|
|
855
|
+
game_id: Annotated[StrictInt, Field(ge=1)]
|
|
856
|
+
generation: Generation
|
|
857
|
+
generation_id: Annotated[StrictInt, Field(ge=1)]
|
|
858
|
+
lobby_id: UUID
|
|
859
|
+
outcome: GameOutcome | None = None
|
|
860
|
+
"""
|
|
861
|
+
How the game ended. Absent while the game runs.
|
|
862
|
+
"""
|
|
863
|
+
teams: Sequence[GameTeam]
|
|
864
|
+
"""
|
|
865
|
+
The teams that play, with their lobby names and the display names their players had at the start.
|
|
866
|
+
"""
|
|
867
|
+
turn_deadline_at: AwareDatetime | None
|
|
868
|
+
"""
|
|
869
|
+
When this generation's turn times out, as in GenerationState. Null when turns have no time limit, and once the game has ended. Watchers never apply a deadline themselves.
|
|
870
|
+
"""
|