Skip to content

Commit 502c27f

Browse files
committed
WIP: Typing
1 parent e9dd3a9 commit 502c27f

4 files changed

Lines changed: 155 additions & 47 deletions

File tree

pelita/game.py

Lines changed: 48 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from .gamestate_filters import noiser, relocate_expired_food, update_food_age, in_homezone
1717
from .layout import get_legal_positions, initial_positions
1818
from .network import Controller, RemotePlayerFailure, RemotePlayerRecvTimeout, RemotePlayerSendError, ZMQPublisher
19+
from .spec import GameState, Layout, Pos
1920
from .team import RemoteTeam, make_team
2021
from .viewer import (AsciiViewer, ProgressViewer, ReplayWriter, ReplyToViewer,
2122
ResultPrinter)
@@ -280,11 +281,11 @@ def setup_viewers(viewers, print_result=True):
280281
return viewer_state
281282

282283

283-
def setup_game(team_specs, *, layout_dict, max_rounds=300, rng=None,
284+
def setup_game(team_specs, *, layout_dict: Layout, max_rounds=300, rng=None,
284285
allow_camping=False, error_limit=5, timeout_length=3,
285286
viewers=None, store_output=False,
286287
team_names=(None, None), team_infos=(None, None),
287-
raise_bot_exceptions=False, print_result=True):
288+
raise_bot_exceptions=False, print_result=True) -> GameState:
288289
""" Generates a game state for the given teams and layout with otherwise default values. """
289290
if viewers is None:
290291
viewers = []
@@ -328,124 +329,124 @@ def setup_game(team_specs, *, layout_dict, max_rounds=300, rng=None,
328329

329330
# Initialize the game state.
330331

331-
game_state = dict(
332+
game_state: GameState = {
332333
### The layout attributes
333334
#: Walls. Set of (int, int)
334-
walls=set(layout_dict['walls']),
335+
'walls': set(layout_dict['walls']),
335336

336337
#: Shape of the maze. (int, int)
337-
shape=layout_dict['shape'],
338+
'shape': layout_dict['shape'],
338339

339340
#: Food per team. List of sets of (int, int)
340-
food=food,
341+
'food': food,
341342

342343
#: Food ages per team. Dict of (int, int) to int
343-
food_age=[{}, {}],
344+
'food_age': ({}, {}),
344345

345346
### Round/turn information
346347
#: Phase
347-
game_phase='INIT',
348+
'game_phase': 'INIT',
348349

349350
#: Current bot, int, None
350-
turn=None,
351+
'turn': None,
351352

352353
#: Current round, int, None
353-
round=None,
354+
'round': None,
354355

355356
#: Is the game finished? bool
356-
gameover=False,
357+
'gameover': False,
357358

358359
#: Who won? int, None
359-
whowins=None,
360+
'whowins': None,
360361

361362
### Bot/team status
362363
#: Positions of all bots. List of (int, int)
363-
bots=layout_dict['bots'][:],
364+
'bots': layout_dict['bots'][:],
364365

365366
#: Score of the teams. List of int
366-
score=[0] * 2,
367+
'score': (0, 0),
367368

368369
#: Fatal errors
369-
fatal_errors=[[], []],
370+
'fatal_errors': ([], []),
370371

371372
#: Number of timeouts for a team
372-
timeouts=[{}, {}],
373+
'timeouts': ({}, {}),
373374

374375
### Configuration
375376
#: Maximum number of rounds, int
376-
max_rounds=max_rounds,
377+
'max_rounds': max_rounds,
377378

378379
#: Time till timeout, int
379-
timeout=3,
380+
'timeout': 3,
380381

381382
#: Initial timeout, int
382-
initial_timeout=6,
383+
'initial_timeout': 6,
383384

384385
#: Noise radius, int
385-
noise_radius=NOISE_RADIUS,
386+
'noise_radius': NOISE_RADIUS,
386387

387388
#: Sight distance, int
388-
sight_distance=SIGHT_DISTANCE,
389+
'sight_distance': SIGHT_DISTANCE,
389390

390391
#: Max food age
391-
max_food_age=max_food_age,
392+
'max_food_age': max_food_age,
392393

393394
#: Shadow distance, int
394-
shadow_distance=SHADOW_DISTANCE,
395+
'shadow_distance': SHADOW_DISTANCE,
395396

396397
### Informative
397398

398399
#: Name of the teams. Tuple of str
399-
team_names=team_names,
400+
'team_names': team_names,
400401

401402
#: Additional team info. Tuple of str|None
402-
team_infos=team_infos,
403+
'team_infos': team_infos,
403404

404405
#: Time each team needed, list of float
405-
team_time=[0, 0],
406+
'team_time': [0.0, 0.0],
406407

407408
# List of bot deaths, which counts the number of deaths per bot
408409
# In other words, deaths[bot_idx] is the number of times the bot
409410
# bot_idx has been killed until now.
410-
deaths = [0]*4,
411+
'deaths': [0] * 4,
411412

412413
# List of bot kills, which counts the number of kills per bot
413414
# In other words, kills[bot_idx] is the number of times the bot
414415
# bot_idx has killed another bot until now.
415-
kills = [0]*4,
416+
'kills': [0] * 4,
416417

417418
# List of boolean flags weather bot has been eaten since its last move
418-
bot_was_killed = [False]*4,
419+
'bot_was_killed': [False]*4,
419420

420421
# The noisy positions that the bot in `turn` has currently been shown.
421422
# None, if not noisy
422-
noisy_positions = [None] * 4,
423+
'noisy_positions': [None] * 4,
423424

424425
#: The moves that the bots returned. Keeps only the recent one at the respective bot’s index.
425-
requested_moves=[None] * 4,
426+
'requested_moves': [None] * 4,
426427

427428
#: Messages the bots say. Keeps only the recent one at the respective bot’s index.
428-
say=[""] * 4,
429+
'say': [""] * 4,
429430

430431
### Internal
431432
#: Internal team representation
432-
teams=[None] * 2,
433+
'teams': [None] * 2,
433434

434435
#: Random number generator
435-
rng=rng,
436+
'rng': rng,
436437

437438
#: Timeout length, int, None
438-
timeout_length=timeout_length,
439+
'timeout_length': timeout_length,
439440

440441
#: Error limit. A team loses when the limit is reached, int
441-
error_limit=error_limit,
442+
'error_limit': error_limit,
442443

443444
#: Viewers, list
444-
viewers=viewer_state['viewers'],
445+
'viewers': viewer_state['viewers'],
445446

446447
#: Controller
447-
controller=viewer_state['controller']
448-
)
448+
'controller': viewer_state['controller']
449+
}
449450

450451

451452
# Wait until the controller tells us that it is ready
@@ -711,20 +712,20 @@ def prepare_bot_state(game_state, team_idx=None):
711712
'error_count': [len(e) for e in game_state['timeouts'][:]],
712713
'food': [list(team_food) for team_food in game_state['food']],
713714
'shaded_food': shaded_food,
714-
'team_names': game_state['team_names'][:],
715715
'team_time': game_state['team_time'][:],
716716
'is_noisy': is_noisy,
717717
'round': game_state['round'],
718718
'turn': turn,
719719
'timeout_length': game_state['timeout_length'],
720-
'max_rounds': game_state['max_rounds'],
721720
}
722721

723722
if game_state['game_phase'] == 'INIT':
724723
bot_state.update({
725724
'walls': game_state['walls'], # only in initial round
726725
'shape': game_state['shape'], # only in initial round
727-
'seed': seed # only used in set_initial phase
726+
'seed': seed, # only used in set_initial phase
727+
'max_rounds': game_state['max_rounds'],
728+
'team_names': game_state['team_names'][:],
728729
})
729730

730731
return bot_state
@@ -787,7 +788,8 @@ def prepare_viewer_state(game_state):
787788

788789
return viewer_state
789790

790-
def play_turn(game_state, raise_bot_exceptions=False):
791+
792+
def play_turn(game_state: GameState, raise_bot_exceptions=False):
791793
""" Plays the next turn of the game.
792794
793795
This function increases the round and turn counters, requests a move
@@ -910,7 +912,7 @@ def apply_bot_kills(game_state):
910912

911913
return state
912914

913-
def apply_move(gamestate, bot_position):
915+
def apply_move(gamestate: GameState, bot_position):
914916
"""Plays a single step of a bot by applying the game rules to the game state. The rules are:
915917
- if the playing team has an error count of >4 or a fatal error they lose
916918
- a legal step must not be on a wall, else the error count is increased by 1 and a random move is chosen for the bot
@@ -1184,8 +1186,8 @@ def exit_remote_teams(game_state):
11841186

11851187

11861188

1187-
def split_food(width, food):
1188-
team_food = [set(), set()]
1189+
def split_food(width, food: list[Pos]):
1190+
team_food: tuple[set[Pos], set[Pos]] = (set(), set())
11891191
for pos in food:
11901192
idx = pos[0] // (width // 2)
11911193
team_food[idx].add(pos)

pelita/player/SmartEatingPlayer.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,55 @@
11

2+
from pelita.game import apply_move, next_round_turn
23
from pelita.player import food_eating_player
4+
from pelita.team import Bot, _ensure_list_tuples, make_bots
5+
6+
def simulate_move(bot: Bot, next_pos):
7+
game_state = bot._game_state
8+
game_state['bots'] = _ensure_list_tuples(game_state['bots'])
9+
game_state['error_limit'] = 0
10+
game_state['gameover'] = False
11+
game_state['walls'] = bot.walls
12+
game_state['shape'] = bot.shape
13+
game_state['fatal_errors'] = [[], []]
14+
game_state['errors'] = [[], []]
15+
game_state['game_phase'] = 'RUNNING'
16+
17+
game_state = apply_move(game_state, next_pos)
18+
game_state.update(next_round_turn(game_state))
19+
20+
21+
for tidx in range(2):
22+
game_state['food'][tidx] = _ensure_list_tuples(game_state['food'][tidx])
23+
game_state['shaded_food'][tidx] = _ensure_list_tuples(game_state['shaded_food'][tidx])
24+
25+
next_bot = make_bots(bot_positions=game_state['bots'],
26+
is_noisy=game_state['is_noisy'],
27+
walls=bot.walls,
28+
shape=bot.shape,
29+
food=game_state['food'],
30+
shaded_food=game_state['shaded_food'],
31+
round=game_state['round'],
32+
turn=game_state['turn'],
33+
score=game_state['score'],
34+
deaths=game_state['deaths'],
35+
kills=game_state['kills'],
36+
bot_was_killed=game_state['bot_was_killed'],
37+
error_count=game_state['error_count'],
38+
initial_positions=[bot._initial_position, bot.other._initial_position, bot._initial_position, bot.other._initial_position],
39+
homezone=[bot.other.homezone, bot.homezone],
40+
team_names=game_state['team_names'],
41+
team_time=game_state['team_time'],
42+
rng="bot._rng",
43+
graph=bot.graph)
44+
45+
return next_bot
46+
347

448

549
def smart_eating_player(bot, state):
50+
51+
print(simulate_move(bot, next_pos=bot.position))
52+
653
# food eating player but won’t do kamikaze (although a sufficiently smart
754
# enemy will be able to kill the bot in its next turn as it doesn’t flee)
855
next_pos = food_eating_player(bot, state)

pelita/spec.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from typing import Literal, TypeAlias, TypedDict, Any
2+
from random import Random
3+
4+
from .team import Team
5+
6+
Shape: TypeAlias = tuple[int, int]
7+
Pos: TypeAlias = tuple[int, int]
8+
FoodAges: TypeAlias = dict[Pos, int]
9+
10+
class Layout(TypedDict):
11+
bots: list[Pos]
12+
walls: set[Pos]
13+
shape: Shape
14+
food: tuple[set[Pos], set[Pos]]
15+
16+
class GameState(TypedDict):
17+
walls: set[Pos]
18+
shape: Shape
19+
food: tuple[set[Pos], set[Pos]]
20+
food_age: tuple[FoodAges, FoodAges]
21+
game_phase: Literal["INIT", "RUNNING", "FAILURE", "FINISHED"]
22+
turn: None|int
23+
round: None|int
24+
gameover: bool
25+
whowins: None|int
26+
bots: list[Pos]
27+
score: tuple[int, int]
28+
fatal_errors: tuple[list[Any], list[Any]]
29+
timeouts: tuple[Any, Any]
30+
max_rounds: int
31+
timeout: int
32+
initial_timeout: int
33+
noise_radius: int
34+
sight_distance: int
35+
max_food_age: float|int
36+
shadow_distance: int
37+
team_names: tuple[None|str, None|str]
38+
team_infos: tuple[None|str, None|str]
39+
team_time: list[float]
40+
deaths: list[int]
41+
kills: list[int]
42+
bot_was_killed: list[bool]
43+
noisy_positions: list[None|Pos]
44+
requested_moves: list[None|Pos]
45+
say: list[str]
46+
teams: list[None|Team]
47+
rng: Random
48+
timeout_length: int
49+
error_limit: int
50+
viewers: list[Any]
51+
controller: None|Any

pelita/team.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ def set_initial(self, team_id, game_state):
181181
# Reset the team state
182182
self._state.clear()
183183

184+
self._game_state = {}
185+
self._game_state.update(game_state)
186+
184187
# Initialize the random number generator
185188
# with the seed that we received from game
186189
self._rng = Random(game_state['seed'])
@@ -194,6 +197,9 @@ def set_initial(self, team_id, game_state):
194197
# Store the shape, which is only transmitted once
195198
self._shape = tuple(game_state['shape'])
196199

200+
self._team_names = tuple(game_state['team_names'])
201+
self._max_rounds = game_state['max_rounds']
202+
197203
# Cache the initial positions so that we don’t have to calculate them at each step
198204
self._initial_positions = layout.initial_positions(self._walls, self._shape)
199205

@@ -241,11 +247,13 @@ def get_move(self, game_state):
241247
error_count=game_state['error_count'],
242248
initial_positions=self._initial_positions,
243249
homezone=self._homezone,
244-
team_names=game_state['team_names'],
250+
team_names=self._team_names,
245251
team_time=game_state['team_time'],
246252
rng=self._rng,
247253
graph=self._graph)
248254

255+
self._game_state.update(game_state)
256+
me._game_state = self._game_state
249257
team = me._team
250258

251259
for idx, mybot in enumerate(team):

0 commit comments

Comments
 (0)