From dc8308777048dbbf28202eb99e964015182551a1 Mon Sep 17 00:00:00 2001 From: Tiziano Zito Date: Mon, 30 Sep 2024 11:20:34 +0200 Subject: [PATCH 1/6] return given proportion of layouts with dead ends when asking for a random layout --- pelita/layout.py | 13 ++++++++----- test/test_layout.py | 10 ++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/pelita/layout.py b/pelita/layout.py index 6a880baa9..04aedfbe2 100644 --- a/pelita/layout.py +++ b/pelita/layout.py @@ -9,7 +9,7 @@ RNG = random.Random() -def get_random_layout(size='normal', seed=None, dead_ends=False): +def get_random_layout(size='normal', seed=None, dead_ends=0): """ Return a random layout string from the available ones. Parameters @@ -23,9 +23,9 @@ def get_random_layout(size='normal', seed=None, dead_ends=False): 'big' -> width=64, height=32, food=60 'all' -> all of the above - dead_ends: bool - if set, return a layout from the collection with dead_ends, otherwise - return a layout without dead_ends + dead_ends: float + Return a layout from the collection with dead ends with probabilty dead_ends. + By default never return a layout with dead_ends. Returns ------- @@ -35,7 +35,10 @@ def get_random_layout(size='normal', seed=None, dead_ends=False): """ if seed is not None: RNG.seed(seed) - layouts_names = get_available_layouts(size=size, dead_ends=dead_ends) + if dead_ends and RNG.random() < dead_ends: + layouts_names = get_available_layouts(size=size, dead_ends=True) + else: + layouts_names = get_available_layouts(size=size, dead_ends=False) layout_choice = RNG.choice(layouts_names) return layout_choice, get_layout_by_name(layout_choice) diff --git a/test/test_layout.py b/test/test_layout.py index 7685d68d2..2678f64cb 100644 --- a/test/test_layout.py +++ b/test/test_layout.py @@ -1,6 +1,7 @@ import pytest import itertools +import math from pathlib import Path from textwrap import dedent @@ -69,6 +70,15 @@ def test_get_random_layout_random_seed(): name, layout = get_random_layout(size='small', seed=1) assert name == 'small_017' +def test_get_random_layout_proportion_dead_ends(): + N = 1000 + prop = 0.25 + expected = int(prop*N) + assert not any('dead_ends' in get_random_layout()[0] for i in range(N)) + dead_ends = sum('dead_ends' in get_random_layout(dead_ends=prop)[0] for i in range(N)) + assert math.isclose(dead_ends, expected, rel_tol=0.1) + + def test_legal_layout(): layout = """ ###### From 555e37361ba2fe495b3d7e22cd2e7619f6142c10 Mon Sep 17 00:00:00 2001 From: Tiziano Zito Date: Mon, 30 Sep 2024 11:22:46 +0200 Subject: [PATCH 2/6] make get_available_layouts 7x faster by not using pathlib to iterate over directory... ... this commit makes the newly introduced test for returning the correct proportion of layouts with dead ends 7x faster on my machine. --- pelita/layout.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pelita/layout.py b/pelita/layout.py index 04aedfbe2..6307806de 100644 --- a/pelita/layout.py +++ b/pelita/layout.py @@ -1,5 +1,6 @@ import importlib.resources as importlib_resources import io +import os import random # bot to index conversion @@ -74,13 +75,13 @@ def get_available_layouts(size='normal', dead_ends=False): size = '' av_layouts = [] - for resource in importlib_resources.files('pelita._layouts').iterdir(): - if resource.is_file() and resource.name.endswith('.layout') and size in resource.name: - layout_name = resource.name.removesuffix('.layout') - if dead_ends and 'dead_ends' in resource.name: - av_layouts.append(layout_name) - if not dead_ends and 'dead_ends' not in resource.name: - av_layouts.append(layout_name) + for file in os.listdir(importlib_resources.files('pelita._layouts')): + if dead_ends: + cond = file.endswith('.layout') and size in file and 'dead_ends' in file + else: + cond = file.endswith('.layout') and size in file and 'dead_ends' not in file + if cond: + av_layouts.append(file.removesuffix('.layout')) return sorted(av_layouts) From 9645450f695b49cd1e3c9f06b14b4f1bcba27690 Mon Sep 17 00:00:00 2001 From: Tiziano Zito Date: Mon, 30 Sep 2024 11:47:47 +0200 Subject: [PATCH 3/6] make test for correct proprotion of dead ends stable and reproducible --- test/test_layout.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/test_layout.py b/test/test_layout.py index 2678f64cb..d17cdd6aa 100644 --- a/test/test_layout.py +++ b/test/test_layout.py @@ -2,6 +2,7 @@ import itertools import math +import random from pathlib import Path from textwrap import dedent @@ -74,8 +75,14 @@ def test_get_random_layout_proportion_dead_ends(): N = 1000 prop = 0.25 expected = int(prop*N) - assert not any('dead_ends' in get_random_layout()[0] for i in range(N)) - dead_ends = sum('dead_ends' in get_random_layout(dead_ends=prop)[0] for i in range(N)) + # get a fix sequence of seeds, so that the test is reproducible + RNG = random.Random() + RNG.seed(176399) + seeds = [RNG.random() for i in range(N)] + # check that we don't get any layout with dead ends if we don't ask for it + assert not any('dead_ends' in get_random_layout(seed=s)[0] for s in seeds) + # check that we get more or less the right proportion of layouts with dead ends + dead_ends = sum('dead_ends' in get_random_layout(seed=s, dead_ends=prop)[0] for s in seeds) assert math.isclose(dead_ends, expected, rel_tol=0.1) From a74f47e0246d2c6b8226d084d9b216f1cca7baaf Mon Sep 17 00:00:00 2001 From: Tiziano Zito Date: Mon, 30 Sep 2024 14:46:51 +0200 Subject: [PATCH 4/6] deinfe default dead ends proportion in game.py --- pelita/game.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pelita/game.py b/pelita/game.py index 2bf21dcac..89d62adee 100644 --- a/pelita/game.py +++ b/pelita/game.py @@ -39,6 +39,9 @@ #: Food pellet shadow distance SHADOW_DISTANCE = 1 +#: Proportion of layouts with dead ends +DEAD_ENDS = 0.25 + class TkViewer: def __init__(self, *, address, controller, geometry=None, delay=None, stop_after=None, fullscreen=False): self.proc = self._run_external_viewer(address, controller, geometry=geometry, delay=delay, stop_after=stop_after, fullscreen=fullscreen) From 0cb1b1e098dffe09cca67f45076e435619a54689 Mon Sep 17 00:00:00 2001 From: Tiziano Zito Date: Mon, 30 Sep 2024 14:47:31 +0200 Subject: [PATCH 5/6] use the new default for layout with dead ends in pelita CLI --- pelita/scripts/pelita_main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pelita/scripts/pelita_main.py b/pelita/scripts/pelita_main.py index a9251f826..821f2f392 100755 --- a/pelita/scripts/pelita_main.py +++ b/pelita/scripts/pelita_main.py @@ -14,6 +14,7 @@ import zmq import pelita +from pelita.game import DEAD_ENDS from .script_utils import start_logging from pelita.network import PELITA_PORT @@ -324,7 +325,8 @@ def main(): sys.exit(0) if args.list_layouts: - layouts = pelita.layout.get_available_layouts(size='all') + layouts = pelita.layout.get_available_layouts(size='all', dead_ends=False) + layouts += pelita.layout.get_available_layouts(size='all', dead_ends=True) layouts.sort() print('\n'.join(layouts)) sys.exit(0) @@ -447,7 +449,7 @@ def main(): layout_name = args.layout layout_string = pelita.layout.get_layout_by_name(args.layout) else: - layout_name, layout_string = pelita.layout.get_random_layout(args.size, seed=seed) + layout_name, layout_string = pelita.layout.get_random_layout(args.size, seed=seed, dead_ends=DEAD_ENDS) print("Using layout '%s'" % layout_name) From 46efdd89c0b3f818ec6d0509b1140d99cf444507 Mon Sep 17 00:00:00 2001 From: Tiziano Zito Date: Mon, 30 Sep 2024 14:47:50 +0200 Subject: [PATCH 6/6] use the new default for layout with dead ends in run_background_game --- pelita/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pelita/utils.py b/pelita/utils.py index d005dab18..0167bf8ab 100644 --- a/pelita/utils.py +++ b/pelita/utils.py @@ -4,7 +4,7 @@ from .team import make_bots, create_homezones -from .game import split_food, SHADOW_DISTANCE +from .game import split_food, SHADOW_DISTANCE, DEAD_ENDS from .layout import (get_random_layout, get_layout_by_name, get_available_layouts, parse_layout, BOT_N2I, initial_positions) from .gamestate_filters import manhattan_dist @@ -20,7 +20,7 @@ def _parse_layout_arg(*, layout=None, food=None, bots=None, seed=None): # prepare layout argument to be passed to pelita.game.run_game if layout is None: - layout_name, layout_str = get_random_layout(size='normal', seed=seed) + layout_name, layout_str = get_random_layout(size='normal', seed=seed, dead_ends=DEAD_ENDS) layout_dict = parse_layout(layout_str) elif layout in get_available_layouts(size='all'): # check if this is a built-in layout