diff --git a/pelita/game.py b/pelita/game.py index 643157a84..55da9c089 100644 --- a/pelita/game.py +++ b/pelita/game.py @@ -2,7 +2,7 @@ import logging import os -from random import Random +import random import subprocess import sys import time @@ -88,7 +88,7 @@ def controller_exit(state, await_action='play_step'): return False def run_game(team_specs, *, layout_dict, layout_name="", max_rounds=300, - seed=None, allow_camping=False, error_limit=5, timeout_length=3, + rng=None, allow_camping=False, error_limit=5, timeout_length=3, viewers=None, viewer_options=None, store_output=False, team_names=(None, None), team_infos=(None, None), allow_exceptions=False, print_result=True): @@ -119,8 +119,8 @@ def run_game(team_specs, *, layout_dict, layout_name="", max_rounds=300, max_rounds : int The maximum number of rounds to play before the game is over. Default: 300. - seed : int - seed used to initialize the random number generator. + rng : random.Random | int | None + random number generator or a seed used to initialize a new one. error_limit : int The limit of non fatal errors to reach for a team before the @@ -196,7 +196,7 @@ def run_game(team_specs, *, layout_dict, layout_name="", max_rounds=300, layout_name=layout_name, max_rounds=max_rounds, allow_camping=allow_camping, error_limit=error_limit, timeout_length=timeout_length, - seed=seed, viewers=viewers, + rng=rng, viewers=viewers, viewer_options=viewer_options, store_output=store_output, team_names=team_names, team_infos=team_infos, @@ -271,7 +271,7 @@ def setup_viewers(viewers=None, options=None, print_result=True): return viewer_state -def setup_game(team_specs, *, layout_dict, max_rounds=300, layout_name="", seed=None, +def setup_game(team_specs, *, layout_dict, max_rounds=300, layout_name="", rng=None, allow_camping=False, error_limit=5, timeout_length=3, viewers=None, viewer_options=None, store_output=False, team_names=(None, None), team_infos=(None, None), @@ -313,6 +313,9 @@ def setup_game(team_specs, *, layout_dict, max_rounds=300, layout_name="", seed= viewer_state = setup_viewers(viewers, options=viewer_options, print_result=print_result) + if not isinstance(rng, random.Random): + rng = random.Random(rng) + # Initialize the game state. game_state = dict( @@ -415,7 +418,7 @@ def setup_game(team_specs, *, layout_dict, max_rounds=300, layout_name="", seed= teams=[None] * 2, #: Random number generator - rng=Random(seed), + rng=rng, #: Timeout length, int, None timeout_length=timeout_length, diff --git a/pelita/gamestate_filters.py b/pelita/gamestate_filters.py index da7f938a0..5548284b9 100644 --- a/pelita/gamestate_filters.py +++ b/pelita/gamestate_filters.py @@ -48,8 +48,8 @@ def noiser(walls, shape, bot_position, enemy_positions, noise_radius=5, sight_di """ # set the random state - if rng is None: - rng = random.Random() + if not isinstance(rng, random.Random): + rng = random.Random(rng) # store the noised positions noised_positions = [None] * len(enemy_positions) diff --git a/pelita/layout.py b/pelita/layout.py index d30f06b3d..2a71b06ed 100644 --- a/pelita/layout.py +++ b/pelita/layout.py @@ -8,7 +8,7 @@ BOT_I2N = {0: 'a', 2: 'b', 1: 'x', 3: 'y'} -def get_random_layout(size='normal', rng=None, dead_ends=0): +def get_random_layout(size='normal', dead_ends=0, rng=None): """ Return a random layout string from the available ones. Parameters @@ -32,10 +32,10 @@ def get_random_layout(size='normal', rng=None, dead_ends=0): the name of the layout, a random layout string """ - + # set the random state - if rng is None: - rng = random.Random() + if not isinstance(rng, random.Random): + rng = random.Random(rng) if dead_ends and rng.random() < dead_ends: layouts_names = get_available_layouts(size=size, dead_ends=True) diff --git a/pelita/maze_generator.py b/pelita/maze_generator.py index 7e3d7c8ea..1fd926ff7 100644 --- a/pelita/maze_generator.py +++ b/pelita/maze_generator.py @@ -91,12 +91,14 @@ def str_to_maze(str_): bytes_maze = str_.encode('ascii') return bytes_to_maze(bytes_maze) -def create_half_maze(maze, ngaps_center): +def create_half_maze(maze, ngaps_center, rng=None): """Fill the left half of the maze with random walls. The second half can be created by mirroring the left part using the 'complete_maze' function. """ + if not isinstance(rng, random.Random): + rng = random.Random(rng) # first, we need a wall in the middle @@ -104,7 +106,7 @@ def create_half_maze(maze, ngaps_center): # be mirrored ch = maze.shape[0] - 2 candidates = list(range(ch//2)) - random.shuffle(candidates) + rng.shuffle(candidates) half_gaps_pos = candidates[:ngaps_center // 2] gaps_pos = [] for pos in half_gaps_pos: @@ -113,12 +115,12 @@ def create_half_maze(maze, ngaps_center): # make wall _add_wall_at(maze, (maze.shape[1] - 2) // 2 - 1, ngaps_center, - vertical=True, gaps_pos=gaps_pos) + vertical=True, rng=rng, gaps_pos=gaps_pos) # then, fill the left half with walls - _add_wall(maze[:, :maze.shape[1] // 2], ngaps_center // 2, vertical=False) + _add_wall(maze[:, :maze.shape[1] // 2], ngaps_center // 2, vertical=False, rng=rng) -def _add_wall_at(maze, pos, ngaps, vertical, gaps_pos=None): +def _add_wall_at(maze, pos, ngaps, vertical, rng, gaps_pos=None): """ add a wall with gaps @@ -142,7 +144,7 @@ def _add_wall_at(maze, pos, ngaps, vertical, gaps_pos=None): if gaps_pos is None: # choose aandom positions gaps_pos = list(range(ch)) - random.shuffle(gaps_pos) + rng.shuffle(gaps_pos) gaps_pos = gaps_pos[:ngaps] # do not block entrances if maze[0][pos + 1] == E: @@ -159,7 +161,7 @@ def _add_wall_at(maze, pos, ngaps, vertical, gaps_pos=None): return sub_mazes -def _add_wall(maze, ngaps, vertical): +def _add_wall(maze, ngaps, vertical, rng): """Recursively build the walls of the maze. grid -- 2D array of characters representing the maze @@ -177,15 +179,15 @@ def _add_wall(maze, ngaps, vertical): size = cw if vertical else ch # create a wall only if there is some space in this direction - min_size = random.randint(3, 5) + min_size = rng.randint(3, 5) if size >= min_size: # place the wall at random spot - pos = random.randint(1, size-2) - sub_mazes = _add_wall_at(maze, pos, ngaps, vertical) + pos = rng.randint(1, size-2) + sub_mazes = _add_wall_at(maze, pos, ngaps, vertical, rng=rng) # recursively add walls for sub_maze in sub_mazes: - _add_wall(sub_maze, max(1, ngaps // 2), not vertical) + _add_wall(sub_maze, max(1, ngaps // 2), not vertical, rng=rng) def walls_to_graph(maze): @@ -318,7 +320,10 @@ def get_neighboring_walls(maze, locs): walls.append((adjx, adjy)) return walls -def remove_all_chambers(maze): +def remove_all_chambers(maze, rng=None): + if not isinstance(rng, random.Random): + rng = random.Random(rng) + maze_graph = walls_to_graph(maze) # this will find one of the chambers, if there is any entrance, chamber = find_chamber(maze_graph) @@ -326,7 +331,7 @@ def remove_all_chambers(maze): # get all the walls around the chamber walls = get_neighboring_walls(maze, chamber) # choose a wall at random among the neighboring one and get rid of it - bad_wall = random.choice(walls) + bad_wall = rng.choice(walls) maze[bad_wall[1], bad_wall[0]] = E # we may have opened a door into this chamber, but there may be more # chambers to get rid of. Or, the wall we picked wasn't good enough and @@ -339,11 +344,14 @@ def remove_all_chambers(maze): entrance, chamber = find_chamber(maze_graph) -def add_food(maze, max_food): +def add_food(maze, max_food, rng=None): """Add max_food pellets on the left side of the maze. We exclude the pacmen's starting positions and the central dividing border """ + if not isinstance(rng, random.Random): + rng = random.Random(rng) + if max_food == 0: # no food needs to be added, return here return @@ -366,7 +374,7 @@ def add_food(maze, max_food): raise ValueError(f'Can not add negative number of food ({max_food} given)') # now take max_food random positions out of this list - food = random.sample(free, max_food) + food = rng.sample(free, max_food) # fit it in the maze for col, row in food: maze[row, col] = F @@ -378,7 +386,7 @@ def add_pacmen(maze): maze[1, -2] = b'y' maze[2, -2] = b'x' -def get_new_maze(height, width, nfood, seed=None, dead_ends=False): +def get_new_maze(height, width, nfood, dead_ends=False, rng=None): """Create a new maze in text format. The maze is created with a recursive creation algorithm. The maze part of @@ -401,12 +409,11 @@ def get_new_maze(height, width, nfood, seed=None, dead_ends=False): if width%2 != 0: raise ValueError(f'Width must be even ({width} given)') - if seed is None: - seed = random.randint(1, 2 ** 31 - 1) - random.seed(seed) + if not isinstance(rng, random.Random): + rng = random.Random(rng) maze = empty_maze(height, width) - create_half_maze(maze, height // 2) + create_half_maze(maze, height // 2, rng=rng) # make space for pacman (2 pacman each) maze[-2, 1] = E @@ -415,10 +422,10 @@ def get_new_maze(height, width, nfood, seed=None, dead_ends=False): # remove dead ends if not dead_ends: remove_all_dead_ends(maze) - remove_all_chambers(maze) + remove_all_chambers(maze, rng=rng) # add food - add_food(maze, nfood) + add_food(maze, nfood, rng=rng) # complete right part of maze with mirror copy maze[:, width // 2:] = np.flipud(np.fliplr(maze[:, :width // 2])) diff --git a/pelita/scripts/pelita_main.py b/pelita/scripts/pelita_main.py index 6bfb55868..aa87337de 100755 --- a/pelita/scripts/pelita_main.py +++ b/pelita/scripts/pelita_main.py @@ -454,7 +454,7 @@ def main(): print("Using layout '%s'" % layout_name) layout_dict = pelita.layout.parse_layout(layout_string) - pelita.game.run_game(team_specs=team_specs, max_rounds=args.rounds, layout_dict=layout_dict, layout_name=layout_name, seed=seed, + pelita.game.run_game(team_specs=team_specs, max_rounds=args.rounds, layout_dict=layout_dict, layout_name=layout_name, rng=rng, allow_camping=args.allow_camping, timeout_length=args.timeout_length, error_limit=args.error_limit, viewers=viewers, viewer_options=viewer_options, store_output=args.store_output, diff --git a/pelita/scripts/pelita_tournament.py b/pelita/scripts/pelita_tournament.py index ff17aaaa9..5af0199a0 100755 --- a/pelita/scripts/pelita_tournament.py +++ b/pelita/scripts/pelita_tournament.py @@ -275,7 +275,7 @@ def escape(s): else: state = tournament.State(config) - random.seed(config.seed) + rng = random.Random(config.seed) if state.round1['played']: # We have already played one match. Do not speak the introduction. @@ -286,11 +286,11 @@ def escape(s): else: tournament.present_teams(config) - rr_ranking = tournament.play_round1(config, state) + rr_ranking = tournament.play_round1(config, state, rng) state.round2["round_robin_ranking"] = rr_ranking state.save(args.state) - winner = tournament.play_round2(config, rr_ranking, state) + 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('{team_group}: {team_name}. Congratulations'.format( diff --git a/pelita/tournament/__init__.py b/pelita/tournament/__init__.py index abc279622..76844505d 100644 --- a/pelita/tournament/__init__.py +++ b/pelita/tournament/__init__.py @@ -4,7 +4,6 @@ import json import logging import os -import random import re import shlex import signal @@ -450,7 +449,7 @@ def set_name(team): raise -def play_game_with_config(config, teams, *, match_id=None): +def play_game_with_config(config, teams, rng, *, match_id=None): team1, team2 = teams if config.tournament_log_folder: @@ -468,7 +467,7 @@ def play_game_with_config(config, teams, *, match_id=None): log_folder = None log_kwargs = {} - seed = str(random.randint(0, sys.maxsize)) + seed = str(rng.randint(0, sys.maxsize)) team_infos = [config.team_group(team1), config.team_group(team2)] res = call_pelita([config.team_spec(team1), config.team_spec(team2)], @@ -487,7 +486,7 @@ def play_game_with_config(config, teams, *, match_id=None): return res -def start_match(config, teams, *, shuffle=False, match_id=None): +def start_match(config, teams, rng, *, shuffle=False, match_id=None): """Start a match between a list of teams. Return the index of the team that won False if there was a draw. """ @@ -496,7 +495,7 @@ def start_match(config, teams, *, shuffle=False, match_id=None): # assert len(teams) == len(set(teams)) if shuffle: - random.shuffle(teams) + rng.shuffle(teams) team1, team2 = teams @@ -505,7 +504,7 @@ def start_match(config, teams, *, shuffle=False, match_id=None): config.print() config.wait_for_keypress() - (final_state, stdout, stderr) = play_game_with_config(config, teams, match_id=match_id) + (final_state, stdout, stderr) = play_game_with_config(config, teams, rng=rng, match_id=match_id) try: whowins = final_state['whowins'] @@ -530,10 +529,10 @@ def start_match(config, teams, *, shuffle=False, match_id=None): return None -def start_match_with_replay(config, match, *, shuffle=False, match_id=None): +def start_match_with_replay(config, match, rng, *, shuffle=False, match_id=None): """ Runs start_match until it returns a proper output or manual intervention. """ - winner = start_match(config, match, shuffle=shuffle, match_id=match_id) + winner = start_match(config, match, rng=rng, shuffle=shuffle, match_id=match_id) while winner is None: config.print("Do you want to re-play the game or enter a winner manually?") res = config.input("(r)e-play/(0){}/(1){}/(d)raw > ".format(config.team_name(match[0]), @@ -542,7 +541,7 @@ def start_match_with_replay(config, match, *, shuffle=False, match_id=None): if match_id: # increase the repeat count match_id.next_repeat() - winner = start_match(config, match, shuffle=shuffle, match_id=match_id) + winner = start_match(config, match, rng=rng, shuffle=shuffle, match_id=match_id) elif res == '0': winner = match[0] elif res == '1': @@ -552,7 +551,7 @@ def start_match_with_replay(config, match, *, shuffle=False, match_id=None): return winner -def start_deathmatch(config, team1, team2, *, match_id=None): +def start_deathmatch(config, team1, team2, rng, *, match_id=None): """Start a match between team1 and team2 until one of them wins (ie no draw.) """ @@ -560,7 +559,7 @@ def start_deathmatch(config, team1, team2, *, match_id=None): config.print() config.print("{} v {}".format(config.team_name(team1), config.team_name(team2))) for i in range(3): - winner = start_match_with_replay(config, [team1, team2], shuffle=True, match_id=match_id) + winner = start_match_with_replay(config, [team1, team2], rng=rng, shuffle=True, match_id=match_id) config.wait_for_keypress() if winner is False or winner is None: @@ -572,7 +571,7 @@ def start_deathmatch(config, team1, team2, *, match_id=None): # if we are here, we have no winner after 3 death matches # just assign a random winner config.print('No winner after 3 Death Matches. Choose a winner at random:', wait=2) - winner = random.choice((team1, team2)) + winner = rng.choice((team1, team2)) config.print('And the winner is', config.team_name(winner)) return winner @@ -612,7 +611,7 @@ def pp_round1_results(config, rr_played, rr_unplayed, highlight=None): config.print() -def play_round1(config, state): +def play_round1(config, state, rng): """Run the first round and return a sorted list of team names. teams is the sorted list [group0, group1, ...] and not the actual names of @@ -648,7 +647,7 @@ def play_round1(config, state): while rr_unplayed: match = rr_unplayed.pop() - winner = start_match_with_replay(config, match, match_id=match_id) + winner = start_match_with_replay(config, match, rng=rng, match_id=match_id) match_id.next_match() config.wait_for_keypress() @@ -690,7 +689,7 @@ def recur_match_winner(match): return None -def play_round2(config, teams, state): +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 @@ -722,7 +721,7 @@ def play_round2(config, teams, state): t1_id = recur_match_winner(match.t1) t2_id = recur_match_winner(match.t2) if not match.winner: - winner = start_deathmatch(config, t1_id, t2_id, match_id=match_id) + winner = start_deathmatch(config, t1_id, t2_id, rng=rng, match_id=match_id) match.winner = winner config.print(knockout_mode.print_knockout(last_match, config.team_name, highlight=[match]), speak=False) diff --git a/pelita/tournament/roundrobin.py b/pelita/tournament/roundrobin.py index 4b07925d2..f287ee773 100644 --- a/pelita/tournament/roundrobin.py +++ b/pelita/tournament/roundrobin.py @@ -1,9 +1,8 @@ from collections import Counter import itertools -import random from typing import List, Tuple -def create_matchplan(teams: List[str]) -> List[Tuple[str, str]]: +def create_matchplan(teams: List[str], rng) -> List[Tuple[str, str]]: """ Takes a list of team ids and returns a list of tuples of team ids with the matchplan.""" @@ -30,14 +29,14 @@ def create_matchplan(teams: List[str]) -> List[Tuple[str, str]]: if len(teams) == 3: - matchplan_indexes = matchplan_three_teams() + matchplan_indexes = matchplan_three_teams(rng) elif len(teams) == 5: - matchplan_indexes = matchplan_five_teams() + matchplan_indexes = matchplan_five_teams(rng) else: - matchplan_indexes = circle_method(len(teams)) + matchplan_indexes = circle_method(len(teams), rng) shuffled_teams = list(teams) - random.shuffle(shuffled_teams) + rng.shuffle(shuffled_teams) matchplan = [] for match in matchplan_indexes: @@ -48,15 +47,15 @@ def create_matchplan(teams: List[str]) -> List[Tuple[str, str]]: return matchplan -def matchplan_three_teams(): +def matchplan_three_teams(rng): # Without loss of generality, there should be exactly two possible matchplans with evenly distributed blue/red matchplans = [ [(0, 1), (1, 2), (2, 0)], [(0, 1), (2, 0), (1, 2)] ] - return random.choice(matchplans) + return rng.choice(matchplans) -def matchplan_five_teams(): +def matchplan_five_teams(rng): # Without loss of generality, there are two possible matchplans for five teams (with the starting order undefined) # that don’t have the same team play two consecutive matches. (Assuming the teams are assigned randomly.) # (Source: pen&paper + brute force analysis) @@ -64,7 +63,7 @@ def matchplan_five_teams(): # [{0, 1}, {2, 3}, {0, 4}, {1, 2}, {3, 4}, {0, 2}, {1, 3}, {2, 4}, {0, 3}, {1, 4}] # [{0, 1}, {2, 3}, {0, 4}, {1, 2}, {3, 4}, {0, 2}, {1, 4}, {0, 3}, {2, 4}, {1, 3}] - matchplan = random.choice([ + matchplan = rng.choice([ [(0, 1), (2, 3), (0, 4), (1, 2), (3, 4), (0, 2), (1, 3), (2, 4), (0, 3), (1, 4)], [(0, 1), (2, 3), (0, 4), (1, 2), (3, 4), (0, 2), (1, 4), (0, 3), (2, 4), (1, 3)] ]) @@ -79,12 +78,12 @@ def matchplan_five_teams(): valid_mp = [(match[idx], match[1 - idx]) for match, idx in zip(matchplan, shuffle)] valid_matchplans.append(valid_mp) - return random.choice(valid_matchplans) + return rng.choice(valid_matchplans) -def circle_method(num_teams): - return list(circle_method_gen(num_teams)) +def circle_method(num_teams, rng): + return list(circle_method_gen(num_teams, rng)) -def circle_method_gen(num_teams): +def circle_method_gen(num_teams, rng): """ For the given number of teams, create a matchplan for a round-robin tournament using the circle method. """ FILLER_TEAM = object() @@ -119,10 +118,10 @@ def circle_method_gen(num_teams): # corner indexes if num_teams % 2 == 0: - fixed_index = random.choice([0, len(teams) // 2 - 1, len(teams) // 2, len(teams) - 1]) + fixed_index = rng.choice([0, len(teams) // 2 - 1, len(teams) // 2, len(teams) - 1]) else: # for an odd number of teams, only select the column with the filler team - fixed_index = random.choice([0, len(teams) - 1]) + fixed_index = rng.choice([0, len(teams) - 1]) flip_fixed = False for _iter in range(len(teams) - 1): diff --git a/pelita/utils.py b/pelita/utils.py index e51b430f3..0e6881367 100644 --- a/pelita/utils.py +++ b/pelita/utils.py @@ -124,7 +124,7 @@ def run_background_game(*, blue_move, red_move, layout=None, max_rounds=300, see layout_dict, layout_name = _parse_layout_arg(layout=layout, rng=rng) game_state = run_game((blue_move, red_move), layout_dict=layout_dict, - layout_name=layout_name, max_rounds=max_rounds, seed=seed, + layout_name=layout_name, max_rounds=max_rounds, rng=rng, team_names=('blue', 'red'), allow_exceptions=True, print_result=False) out = {} out['seed'] = seed diff --git a/test/test_maze_generation.py b/test/test_maze_generation.py index 2acab7c8d..835cca8f8 100644 --- a/test/test_maze_generation.py +++ b/test/test_maze_generation.py @@ -46,7 +46,7 @@ def test_maze_bytes_str_conversions(): assert mg.maze_to_str(maze_arr) == maze_str -def test_create_half_maze(set_seed): +def test_create_half_maze(): # this test is not really testing that create_half_maze does a good job # we only test that we keep in returning the same maze when the random # seed is fixed, in case something changes during future porting/refactoring @@ -68,8 +68,9 @@ def test_create_half_maze(set_seed): ################################""" maze = mg.empty_maze(16,32) - mg.create_half_maze(maze, 8) + mg.create_half_maze(maze, 8, rng=SEED) expected = mg.str_to_maze(maze_str) + print(mg.maze_to_str(maze)) assert np.all(maze == expected) def test_conversion_to_nx_graph(): @@ -296,10 +297,10 @@ def test_remove_all_chambers(set_seed, maze_chamber): # we are removing just a few walls? @pytest.mark.parametrize('iteration', range(1,11)) -def test_get_new_maze(set_seed, iteration): +def test_get_new_maze(iteration): # generate a few mazes and check them for consistency - local_seed = random.randint(1,2**31-1)*iteration - maze_str = mg.get_new_maze(8,16,nfood=15,seed=local_seed) + local_seed = 12345 * iteration + maze_str = mg.get_new_maze(8,16,nfood=15,rng=local_seed) maze = mg.str_to_maze(maze_str) height, width = maze.shape # check that the returned maze has all the pacmen diff --git a/test/test_player_base.py b/test/test_player_base.py index 7af9899ef..8e8584178 100644 --- a/test/test_player_base.py +++ b/test/test_player_base.py @@ -122,21 +122,21 @@ def test_demo_players(self): random_player, random_player ] - state = setup_game(teams, layout_dict=parse_layout(test_layout), max_rounds=20, seed=20) + state = setup_game(teams, layout_dict=parse_layout(test_layout), max_rounds=20, rng=20) assert state['bots'][0] == (4, 4) assert state['bots'][1] == (4 + 7, 4) - state = run_game(teams, layout_dict=parse_layout(test_layout), max_rounds=20, seed=20) + state = run_game(teams, layout_dict=parse_layout(test_layout), max_rounds=20, rng=20) pos_left_bot = state['bots'][0] pos_right_bot = state['bots'][1] # running again to test seed: - state = run_game(teams, layout_dict=parse_layout(test_layout), max_rounds=20, seed=20) + state = run_game(teams, layout_dict=parse_layout(test_layout), max_rounds=20, rng=20) assert state['bots'][0] == pos_left_bot assert state['bots'][1] == pos_right_bot # running again with other seed: - state = run_game(teams, layout_dict=parse_layout(test_layout), max_rounds=20, seed=200) + state = run_game(teams, layout_dict=parse_layout(test_layout), max_rounds=20, rng=200) # most probably, either the left bot or the right bot or both are at # a different position assert not (state['bots'][0] == pos_left_bot and state['bots'][1] == pos_right_bot) @@ -165,7 +165,7 @@ def rng_test(bot, state): return team, player_rngs team0, player_rngs0 = init_rng_players() - state = setup_game(team0, layout_dict=parse_layout(test_layout), max_rounds=5, seed=20) + state = setup_game(team0, layout_dict=parse_layout(test_layout), max_rounds=5, rng=20) # play two steps play_turn(play_turn(state)) assert len(player_rngs0) == 2 @@ -175,7 +175,7 @@ def rng_test(bot, state): assert random_numbers0[0] != random_numbers0[1] team1, player_rngs1 = init_rng_players() - state = setup_game(team1, layout_dict=parse_layout(test_layout), max_rounds=5, seed=20) + state = setup_game(team1, layout_dict=parse_layout(test_layout), max_rounds=5, rng=20) # play two steps play_turn(play_turn(state)) assert len(player_rngs1) == 2 @@ -186,7 +186,7 @@ def rng_test(bot, state): # now, use a different seed team2, player_rngs2 = init_rng_players() - state = setup_game(team2, layout_dict=parse_layout(test_layout), max_rounds=5, seed=200) + state = setup_game(team2, layout_dict=parse_layout(test_layout), max_rounds=5, rng=200) # play two steps play_turn(play_turn(state)) assert len(player_rngs2) == 2 diff --git a/test/test_tournament.py b/test/test_tournament.py index 9cbdbc11e..9cdcd14c0 100644 --- a/test/test_tournament.py +++ b/test/test_tournament.py @@ -1,6 +1,7 @@ import pytest from unittest.mock import MagicMock +import random import re from textwrap import dedent @@ -8,6 +9,7 @@ from pelita.tournament import knockout_mode, roundrobin from pelita.tournament.knockout_mode import Team, Match, Bye +RNG = random.Random() def test_match_id(): assert str(tournament.MatchID()) == 'round1-match01' @@ -158,7 +160,7 @@ def tuple_sort(t): return tuple(sorted(list(t))) for input, output in data: - matchplan = roundrobin.create_matchplan(input) + matchplan = roundrobin.create_matchplan(input, rng=RNG) matchplan_sorted = sorted([tuple_sort(x) for x in matchplan]) output = sorted([tuple_sort(x) for x in output]) assert matchplan_sorted == output @@ -170,7 +172,7 @@ def tuple_sort(t): [0, 1, 2, 3, 4, 5, 6, 7] ]) def test_no_team_plays_two_games_in_a_row(self, teams): - matches = roundrobin.create_matchplan(teams) + matches = roundrobin.create_matchplan(teams, rng=RNG) prev = {} for match in matches: current = set(match) @@ -188,7 +190,7 @@ def test_no_team_plays_two_games_in_a_row(self, teams): ]) def test_team_blue_red_are_similar(self, teams): # test that each team plays a similar amount as blue and red - matchplan = roundrobin.create_matchplan(teams) + matchplan = roundrobin.create_matchplan(teams, rng=RNG) from collections import Counter blue_count = Counter() red_count = Counter() @@ -227,14 +229,14 @@ def test_play_game_with_config(self): config.tournament_log_folder = None teams = ["pelita/player/StoppingPlayer", "pelita/player/StoppingPlayer"] - (state, stdout, stderr) = tournament.play_game_with_config(config, teams) + (state, stdout, stderr) = tournament.play_game_with_config(config, teams, rng=RNG) assert state['whowins'] == 2 config.rounds = 200 config.team_spec = lambda x: x config.viewer = 'ascii' teams = ["pelita/player/SmartEatingPlayer", "pelita/player/StoppingPlayer"] - (state, stdout, stderr) = tournament.play_game_with_config(config, teams) + (state, stdout, stderr) = tournament.play_game_with_config(config, teams, rng=RNG) print(state) assert state['whowins'] == 0 @@ -242,7 +244,7 @@ def test_play_game_with_config(self): config.team_spec = lambda x: x config.viewer = 'ascii' teams = ["pelita/player/StoppingPlayer", "pelita/player/SmartEatingPlayer"] - (state, stdout, stderr) = tournament.play_game_with_config(config, teams) + (state, stdout, stderr) = tournament.play_game_with_config(config, teams, rng=RNG) assert state['whowins'] == 1 def test_start_match(self): @@ -268,17 +270,17 @@ def mock_print(str="", *args, **kwargs): config.tournament_log_folder = None team_ids = ["first_id", "first_id"] - result = tournament.start_match(config, team_ids) + result = tournament.start_match(config, team_ids, rng=RNG) assert result == False assert stdout[-1] == '‘pelita/player/StoppingPlayer’ and ‘pelita/player/StoppingPlayer’ had a draw.' team_ids = ["second_id", "first_id"] - result = tournament.start_match(config, team_ids) + result = tournament.start_match(config, team_ids, rng=RNG) assert result == "second_id" assert stdout[-1] == '‘pelita/player/SmartEatingPlayer’ wins' team_ids = ["first_id", "second_id"] - result = tournament.start_match(config, team_ids) + result = tournament.start_match(config, team_ids, rng=RNG) assert result == "second_id" assert stdout[-1] == '‘pelita/player/SmartEatingPlayer’ wins' @@ -305,7 +307,7 @@ def mock_print(str="", *args, **kwargs): config.print = mock_print config.tournament_log_folder = None - result = tournament.start_deathmatch(config, *teams.keys()) + result = tournament.start_deathmatch(config, *teams.keys(), rng=RNG) assert result is not None assert result in ["first_id", "second_id"] @@ -347,20 +349,20 @@ def mock_print(str="", *args, **kwargs): config.tournament_log_folder = None # group1 should win - assert "group1" == tournament.start_match(config, ["group0", "group1"]) - assert "group1" == tournament.start_match(config, ["group1", "group0"]) - assert False == tournament.start_match(config, ["group0", "group0"]) + assert "group1" == tournament.start_match(config, ["group0", "group1"], rng=RNG) + assert "group1" == tournament.start_match(config, ["group1", "group0"], rng=RNG) + assert False == tournament.start_match(config, ["group0", "group0"], rng=RNG) tournament.present_teams(config) state = tournament.State(config) - rr_ranking = tournament.play_round1(config, state) + rr_ranking = tournament.play_round1(config, state, rng=RNG) if config.bonusmatch: sorted_ranking = knockout_mode.sort_ranks(rr_ranking[:-1]) + [rr_ranking[-1]] else: sorted_ranking = knockout_mode.sort_ranks(rr_ranking) - winner = tournament.play_round2(config, sorted_ranking, state) + winner = tournament.play_round2(config, sorted_ranking, state, rng=RNG) assert winner == 'group1'