Skip to content

Commit

Permalink
RF: Further removal of global randomness
Browse files Browse the repository at this point in the history
  • Loading branch information
Debilski committed Oct 4, 2024
1 parent b261dbe commit 806bd4f
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 99 deletions.
17 changes: 10 additions & 7 deletions pelita/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import logging
import os
from random import Random
import random
import subprocess
import sys
import time
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions pelita/gamestate_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions pelita/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
51 changes: 29 additions & 22 deletions pelita/maze_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,22 @@ 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

# the gaps in the central wall have to be chosen such that they can
# 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:
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -318,15 +320,18 @@ 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)
while entrance is not None:
# 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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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]))
Expand Down
2 changes: 1 addition & 1 deletion pelita/scripts/pelita_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions pelita/scripts/pelita_tournament.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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(
Expand Down
Loading

0 comments on commit 806bd4f

Please sign in to comment.