|
16 | 16 | from .gamestate_filters import noiser, relocate_expired_food, update_food_age, in_homezone |
17 | 17 | from .layout import get_legal_positions, initial_positions |
18 | 18 | from .network import Controller, RemotePlayerFailure, RemotePlayerRecvTimeout, RemotePlayerSendError, ZMQPublisher |
| 19 | +from .spec import GameState, Layout, Pos |
19 | 20 | from .team import RemoteTeam, make_team |
20 | 21 | from .viewer import (AsciiViewer, ProgressViewer, ReplayWriter, ReplyToViewer, |
21 | 22 | ResultPrinter) |
@@ -280,11 +281,11 @@ def setup_viewers(viewers, print_result=True): |
280 | 281 | return viewer_state |
281 | 282 |
|
282 | 283 |
|
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, |
284 | 285 | allow_camping=False, error_limit=5, timeout_length=3, |
285 | 286 | viewers=None, store_output=False, |
286 | 287 | 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: |
288 | 289 | """ Generates a game state for the given teams and layout with otherwise default values. """ |
289 | 290 | if viewers is None: |
290 | 291 | viewers = [] |
@@ -328,124 +329,124 @@ def setup_game(team_specs, *, layout_dict, max_rounds=300, rng=None, |
328 | 329 |
|
329 | 330 | # Initialize the game state. |
330 | 331 |
|
331 | | - game_state = dict( |
| 332 | + game_state: GameState = { |
332 | 333 | ### The layout attributes |
333 | 334 | #: Walls. Set of (int, int) |
334 | | - walls=set(layout_dict['walls']), |
| 335 | + 'walls': set(layout_dict['walls']), |
335 | 336 |
|
336 | 337 | #: Shape of the maze. (int, int) |
337 | | - shape=layout_dict['shape'], |
| 338 | + 'shape': layout_dict['shape'], |
338 | 339 |
|
339 | 340 | #: Food per team. List of sets of (int, int) |
340 | | - food=food, |
| 341 | + 'food': food, |
341 | 342 |
|
342 | 343 | #: Food ages per team. Dict of (int, int) to int |
343 | | - food_age=[{}, {}], |
| 344 | + 'food_age': ({}, {}), |
344 | 345 |
|
345 | 346 | ### Round/turn information |
346 | 347 | #: Phase |
347 | | - game_phase='INIT', |
| 348 | + 'game_phase': 'INIT', |
348 | 349 |
|
349 | 350 | #: Current bot, int, None |
350 | | - turn=None, |
| 351 | + 'turn': None, |
351 | 352 |
|
352 | 353 | #: Current round, int, None |
353 | | - round=None, |
| 354 | + 'round': None, |
354 | 355 |
|
355 | 356 | #: Is the game finished? bool |
356 | | - gameover=False, |
| 357 | + 'gameover': False, |
357 | 358 |
|
358 | 359 | #: Who won? int, None |
359 | | - whowins=None, |
| 360 | + 'whowins': None, |
360 | 361 |
|
361 | 362 | ### Bot/team status |
362 | 363 | #: Positions of all bots. List of (int, int) |
363 | | - bots=layout_dict['bots'][:], |
| 364 | + 'bots': layout_dict['bots'][:], |
364 | 365 |
|
365 | 366 | #: Score of the teams. List of int |
366 | | - score=[0] * 2, |
| 367 | + 'score': (0, 0), |
367 | 368 |
|
368 | 369 | #: Fatal errors |
369 | | - fatal_errors=[[], []], |
| 370 | + 'fatal_errors': ([], []), |
370 | 371 |
|
371 | 372 | #: Number of timeouts for a team |
372 | | - timeouts=[{}, {}], |
| 373 | + 'timeouts': ({}, {}), |
373 | 374 |
|
374 | 375 | ### Configuration |
375 | 376 | #: Maximum number of rounds, int |
376 | | - max_rounds=max_rounds, |
| 377 | + 'max_rounds': max_rounds, |
377 | 378 |
|
378 | 379 | #: Time till timeout, int |
379 | | - timeout=3, |
| 380 | + 'timeout': 3, |
380 | 381 |
|
381 | 382 | #: Initial timeout, int |
382 | | - initial_timeout=6, |
| 383 | + 'initial_timeout': 6, |
383 | 384 |
|
384 | 385 | #: Noise radius, int |
385 | | - noise_radius=NOISE_RADIUS, |
| 386 | + 'noise_radius': NOISE_RADIUS, |
386 | 387 |
|
387 | 388 | #: Sight distance, int |
388 | | - sight_distance=SIGHT_DISTANCE, |
| 389 | + 'sight_distance': SIGHT_DISTANCE, |
389 | 390 |
|
390 | 391 | #: Max food age |
391 | | - max_food_age=max_food_age, |
| 392 | + 'max_food_age': max_food_age, |
392 | 393 |
|
393 | 394 | #: Shadow distance, int |
394 | | - shadow_distance=SHADOW_DISTANCE, |
| 395 | + 'shadow_distance': SHADOW_DISTANCE, |
395 | 396 |
|
396 | 397 | ### Informative |
397 | 398 |
|
398 | 399 | #: Name of the teams. Tuple of str |
399 | | - team_names=team_names, |
| 400 | + 'team_names': team_names, |
400 | 401 |
|
401 | 402 | #: Additional team info. Tuple of str|None |
402 | | - team_infos=team_infos, |
| 403 | + 'team_infos': team_infos, |
403 | 404 |
|
404 | 405 | #: Time each team needed, list of float |
405 | | - team_time=[0, 0], |
| 406 | + 'team_time': [0.0, 0.0], |
406 | 407 |
|
407 | 408 | # List of bot deaths, which counts the number of deaths per bot |
408 | 409 | # In other words, deaths[bot_idx] is the number of times the bot |
409 | 410 | # bot_idx has been killed until now. |
410 | | - deaths = [0]*4, |
| 411 | + 'deaths': [0] * 4, |
411 | 412 |
|
412 | 413 | # List of bot kills, which counts the number of kills per bot |
413 | 414 | # In other words, kills[bot_idx] is the number of times the bot |
414 | 415 | # bot_idx has killed another bot until now. |
415 | | - kills = [0]*4, |
| 416 | + 'kills': [0] * 4, |
416 | 417 |
|
417 | 418 | # 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, |
419 | 420 |
|
420 | 421 | # The noisy positions that the bot in `turn` has currently been shown. |
421 | 422 | # None, if not noisy |
422 | | - noisy_positions = [None] * 4, |
| 423 | + 'noisy_positions': [None] * 4, |
423 | 424 |
|
424 | 425 | #: 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, |
426 | 427 |
|
427 | 428 | #: Messages the bots say. Keeps only the recent one at the respective bot’s index. |
428 | | - say=[""] * 4, |
| 429 | + 'say': [""] * 4, |
429 | 430 |
|
430 | 431 | ### Internal |
431 | 432 | #: Internal team representation |
432 | | - teams=[None] * 2, |
| 433 | + 'teams': [None] * 2, |
433 | 434 |
|
434 | 435 | #: Random number generator |
435 | | - rng=rng, |
| 436 | + 'rng': rng, |
436 | 437 |
|
437 | 438 | #: Timeout length, int, None |
438 | | - timeout_length=timeout_length, |
| 439 | + 'timeout_length': timeout_length, |
439 | 440 |
|
440 | 441 | #: Error limit. A team loses when the limit is reached, int |
441 | | - error_limit=error_limit, |
| 442 | + 'error_limit': error_limit, |
442 | 443 |
|
443 | 444 | #: Viewers, list |
444 | | - viewers=viewer_state['viewers'], |
| 445 | + 'viewers': viewer_state['viewers'], |
445 | 446 |
|
446 | 447 | #: Controller |
447 | | - controller=viewer_state['controller'] |
448 | | - ) |
| 448 | + 'controller': viewer_state['controller'] |
| 449 | + } |
449 | 450 |
|
450 | 451 |
|
451 | 452 | # Wait until the controller tells us that it is ready |
@@ -711,20 +712,20 @@ def prepare_bot_state(game_state, team_idx=None): |
711 | 712 | 'error_count': [len(e) for e in game_state['timeouts'][:]], |
712 | 713 | 'food': [list(team_food) for team_food in game_state['food']], |
713 | 714 | 'shaded_food': shaded_food, |
714 | | - 'team_names': game_state['team_names'][:], |
715 | 715 | 'team_time': game_state['team_time'][:], |
716 | 716 | 'is_noisy': is_noisy, |
717 | 717 | 'round': game_state['round'], |
718 | 718 | 'turn': turn, |
719 | 719 | 'timeout_length': game_state['timeout_length'], |
720 | | - 'max_rounds': game_state['max_rounds'], |
721 | 720 | } |
722 | 721 |
|
723 | 722 | if game_state['game_phase'] == 'INIT': |
724 | 723 | bot_state.update({ |
725 | 724 | 'walls': game_state['walls'], # only in initial round |
726 | 725 | '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'][:], |
728 | 729 | }) |
729 | 730 |
|
730 | 731 | return bot_state |
@@ -787,7 +788,8 @@ def prepare_viewer_state(game_state): |
787 | 788 |
|
788 | 789 | return viewer_state |
789 | 790 |
|
790 | | -def play_turn(game_state, raise_bot_exceptions=False): |
| 791 | + |
| 792 | +def play_turn(game_state: GameState, raise_bot_exceptions=False): |
791 | 793 | """ Plays the next turn of the game. |
792 | 794 |
|
793 | 795 | This function increases the round and turn counters, requests a move |
@@ -910,7 +912,7 @@ def apply_bot_kills(game_state): |
910 | 912 |
|
911 | 913 | return state |
912 | 914 |
|
913 | | -def apply_move(gamestate, bot_position): |
| 915 | +def apply_move(gamestate: GameState, bot_position): |
914 | 916 | """Plays a single step of a bot by applying the game rules to the game state. The rules are: |
915 | 917 | - if the playing team has an error count of >4 or a fatal error they lose |
916 | 918 | - 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): |
1184 | 1186 |
|
1185 | 1187 |
|
1186 | 1188 |
|
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()) |
1189 | 1191 | for pos in food: |
1190 | 1192 | idx = pos[0] // (width // 2) |
1191 | 1193 | team_food[idx].add(pos) |
|
0 commit comments