diff --git a/pelita/game.py b/pelita/game.py index 2bd7c217b..cd777bc58 100644 --- a/pelita/game.py +++ b/pelita/game.py @@ -6,6 +6,7 @@ import subprocess import sys import time +import uuid from warnings import warn import zmq @@ -343,6 +344,9 @@ def setup_game(team_specs, *, layout_dict, max_rounds=300, rng=None, # Initialize the game state. game_state = dict( + #: UUID + game_uuid=str(uuid.uuid4()), + ### The layout attributes #: Walls. Set of (int, int) walls=set(layout_dict['walls']), @@ -458,6 +462,8 @@ def setup_game(team_specs, *, layout_dict, max_rounds=300, rng=None, controller=viewer_state['controller'] ) + _logger.info("Creating game %s", game_state['game_uuid']) + # Wait until the controller tells us that it is ready # We then can send the initial maze diff --git a/pelita/scripts/pelita_tournament.py b/pelita/scripts/pelita_tournament.py index 7515561c1..763a78d61 100755 --- a/pelita/scripts/pelita_tournament.py +++ b/pelita/scripts/pelita_tournament.py @@ -163,6 +163,12 @@ def setup(): del config["bonusmatch"] break + res = input_choice("Should the web-viewer be activated? (publishes to tcp://127.0.0.1:5559) (y/n)", [], "yn") + if res == "y": + config['publish'] = "tcp://127.0.0.1:5559" + elif res == "n": + config['publish'] = None + print("Specify the folder where we should look for teams (or none)") folder = input().strip() if folder: @@ -294,6 +300,7 @@ def escape(s): winner = tournament.play_round2(config, rr_ranking, state, rng) config.print('The winner of the %s Pelita tournament is...' % config.location, wait=2, end=" ") + config.print() config.print('{team_group}: {team_name}. Congratulations'.format( team_group=config.team_group(winner), team_name=config.team_name(winner)), wait=2) diff --git a/pelita/tournament/__init__.py b/pelita/tournament/__init__.py index a2193edac..a65eb28e2 100644 --- a/pelita/tournament/__init__.py +++ b/pelita/tournament/__init__.py @@ -98,7 +98,8 @@ def run_and_terminate_process(args, **kwargs): p.kill() -def call_pelita(team_specs, *, rounds, size, viewer, seed, team_infos=None, write_replay=False, store_output=False): +def call_pelita(team_specs, *, rounds, size, viewer, seed, publish=None, + team_infos=None, write_replay=False, store_output=False): """ Starts a new process with the given command line arguments and waits until finished. Returns @@ -134,6 +135,7 @@ def call_pelita(team_specs, *, rounds, size, viewer, seed, team_infos=None, writ size = ['--size', size] if size else [] viewer = ['--' + viewer] if viewer else [] seed = ['--seed', seed] if seed else [] + publish = ['--publish', publish] if publish else [] write_replay = ['--write-replay', write_replay] if write_replay else [] store_output = ['--store-output', store_output] if store_output else [] append_blue = ['--append-blue', team_infos[0]] if team_infos[0] else [] @@ -142,6 +144,8 @@ def call_pelita(team_specs, *, rounds, size, viewer, seed, team_infos=None, writ cmd = [sys.executable, '-m', 'pelita.scripts.pelita_main', team1, team2, '--reply-to', reply_addr, + '--stop-at', '0', + *publish, *append_blue, *append_red, *rounds, @@ -270,6 +274,7 @@ def __init__(self, config): self.size = config.get("size") self.viewer = config.get("viewer") + self.publish = config.get("publish") self.interactive = config.get("interactive") self.statefile = config.get("statefile") @@ -290,6 +295,13 @@ def __init__(self, config): self.tournament_log_folder = None self.tournament_log_file = None + if self.publish: + ctx = zmq.Context() + self.socket = ctx.socket(zmq.PUB) + self.socket.connect(self.publish) + else: + self.socket = None + @property def team_ids(self): return self.teams.keys() @@ -306,6 +318,15 @@ def team_name_group(self, team): def team_spec(self, team): return self.teams[team]["spec"] + def send_remote(self, action, data=None): + if not self.socket: + return + if data is None: + publish_string = {"__action__": action} + else: + publish_string = {"__action__": action, "__data__": data} + self.socket.send_json(publish_string) + def _print(self, *args, **kwargs): print(*args, **kwargs) if self.tournament_log_file: @@ -317,12 +338,15 @@ def print(self, *args, **kwargs): """Speak while you print. To disable set speak=False. You need the program %s to be able to speak. Set wait=X to wait X seconds after speaking.""" + if len(args) == 0: + self.send_remote("SPEAK", " ".join(args)) self._print() return stream = io.StringIO() wait = kwargs.pop('wait', 0.5) want_speak = kwargs.pop('speak', None) + self.send_remote("SPEAK", " ".join(args)) if (want_speak is False) or not self.speak: self._print(*args, **kwargs) else: @@ -382,6 +406,26 @@ def input(self, str, values=None): except IndexError: pass + def metadata(self): + return { + 'teams': self.teams, + 'location': self.location, + 'date': self.date, + 'rounds': self.rounds, + 'size': self.size, + 'greeting': self.greeting, + 'farewell': self.farewell, + 'host': self.host, + 'seed': self.seed, + 'bonusmatch': self.bonusmatch + } + + def init_tournament(self): + metadata = self.metadata() + self.send_remote("INIT", metadata) + + def clear_page(self): + self.send_remote("CLEAR") def wait_for_keypress(self): if self.interactive: @@ -424,7 +468,9 @@ def load(cls, config, filename): def present_teams(config): + config.init_tournament() config.wait_for_keypress() + config.clear_page() print("\33[H\33[2J") # clear the screen greeting = config.greeting @@ -453,8 +499,9 @@ def set_name(team): print(sys.stderr) raise - -def play_game_with_config(config, teams, rng, *, match_id=None): +# TODO: Log tournament match cmdline +def play_game_with_config(config: Config, teams, rng, *, match_id=None): + config.clear_page() team1, team2 = teams if config.tournament_log_folder: @@ -479,6 +526,7 @@ def play_game_with_config(config, teams, rng, *, match_id=None): rounds=config.rounds, size=config.size, viewer=config.viewer, + publish=config.publish, team_infos=team_infos, seed=seed, **log_kwargs) @@ -508,6 +556,7 @@ def start_match(config, teams, rng, *, shuffle=False, match_id=None): config.print('Starting match: '+ config.team_name_group(team1)+' vs ' + config.team_name_group(team2)) config.print() config.wait_for_keypress() + config.clear_page() (final_state, stdout, stderr) = play_game_with_config(config, teams, rng=rng, match_id=match_id) try: @@ -626,6 +675,7 @@ def play_round1(config, state, rng): rr_played = state.round1["played"] config.wait_for_keypress() + config.clear_page() config.print() config.print("ROUND 1 (Everybody vs Everybody)") config.print('================================', speak=False) @@ -655,6 +705,7 @@ def play_round1(config, state, rng): winner = start_match_with_replay(config, match, rng=rng, match_id=match_id) match_id.next_match() config.wait_for_keypress() + config.clear_page() if winner is False or winner is None: rr_played.append({ "match": match, "winner": False }) @@ -697,9 +748,11 @@ def recur_match_winner(match): def play_round2(config, teams, state, rng): """Run the second round and return the name of the winning team. - teams is the list [group0, group1, ...] not the names of the agens, sorted + teams is the list [group0, group1, ...] not the names of the agents, sorted by the result of the first round. """ + config.wait_for_keypress() + config.clear_page() config.print() config.print('ROUND 2 (K.O.)') config.print('==============', speak=False) @@ -729,6 +782,7 @@ def play_round2(config, teams, state, rng): winner = start_deathmatch(config, t1_id, t2_id, rng=rng, match_id=match_id) match.winner = winner + config.clear_page() config.print(knockout_mode.print_knockout(last_match, config.team_name, highlight=[match]), speak=False) state.round2["tournament"] = tournament @@ -741,5 +795,6 @@ def play_round2(config, teams, state, rng): match_id.next_match() config.wait_for_keypress() + config.clear_page() return last_match.winner diff --git a/test/test_game.py b/test/test_game.py index 24bf2121f..65760c036 100644 --- a/test/test_game.py +++ b/test/test_game.py @@ -1370,3 +1370,24 @@ def move(bot, state): state = play_turn(state) assert state['requested_moves'][1:] == [None, None, None] assert state['requested_moves'][0] == {'previous_position': (1, 1), 'requested_position': expected_req, 'success': expected_success} + +def test_games_have_different_uuid(): + state1 = setup_game([dummy_bot, dummy_bot], layout_dict=parse_layout(small_layout), max_rounds=2) + state2 = setup_game([dummy_bot, dummy_bot], layout_dict=parse_layout(small_layout), max_rounds=2) + + # removing internal objects + del state1['teams'] + del state2['teams'] + del state1['viewers'] + del state2['viewers'] + del state1['rng'] + del state2['rng'] + del state1['noisy_positions'] + del state2['noisy_positions'] + + assert state1['game_uuid'] != state2['game_uuid'] + del state1['game_uuid'] + del state2['game_uuid'] + + # remainder of the game state is equal + assert state1 == state2 diff --git a/test/test_tournament.py b/test/test_tournament.py index 27f21c60a..26e2f7b32 100644 --- a/test/test_tournament.py +++ b/test/test_tournament.py @@ -297,6 +297,7 @@ def test_play_game_with_config(self): config.viewer = 'ascii' config.size = 'small' config.tournament_log_folder = None + config.publish = None teams = ["pelita/player/StoppingPlayer", "pelita/player/StoppingPlayer"] (state, stdout, stderr) = tournament.play_game_with_config(config, teams, rng=RNG) @@ -338,6 +339,7 @@ def mock_print(str="", *args, **kwargs): config.size = 'small' config.print = mock_print config.tournament_log_folder = None + config.publish = None team_ids = ["first_id", "first_id"] result = tournament.start_match(config, team_ids, rng=RNG) @@ -376,6 +378,7 @@ def mock_print(str="", *args, **kwargs): config.size = 'small' config.print = mock_print config.tournament_log_folder = None + config.publish = None result = tournament.start_deathmatch(config, *teams.keys(), rng=RNG) assert result is not None