Core: create the per world random object in the world constructor (#2083)

* Core: create the per world random object in the world constructor

* remove the check that multiworld exists

* add a deprecation warning to per_slot_randoms

* move random import and fix conflicts

* assert worlds don't exist before setting the multiworld seed

* fix the dlcq and sdv tests

* actually use the seed
This commit is contained in:
Aaron Wagener 2024-03-10 12:47:45 -05:00 committed by GitHub
parent b8c24def8d
commit 2e1a5b0e3b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 16 additions and 14 deletions

View File

@ -85,7 +85,7 @@ class MultiWorld():
game: Dict[int, str] game: Dict[int, str]
random: random.Random random: random.Random
per_slot_randoms: Dict[int, random.Random] per_slot_randoms: Utils.DeprecateDict[int, random.Random]
"""Deprecated. Please use `self.random` instead.""" """Deprecated. Please use `self.random` instead."""
class AttributeProxy(): class AttributeProxy():
@ -217,7 +217,8 @@ class MultiWorld():
set_player_attr('game', "A Link to the Past") set_player_attr('game', "A Link to the Past")
set_player_attr('completion_condition', lambda state: True) set_player_attr('completion_condition', lambda state: True)
self.worlds = {} self.worlds = {}
self.per_slot_randoms = {} self.per_slot_randoms = Utils.DeprecateDict("Using per_slot_randoms is now deprecated. Please use the "
"world's random object instead (usually self.random)")
self.plando_options = PlandoOptions.none self.plando_options = PlandoOptions.none
def get_all_ids(self) -> Tuple[int, ...]: def get_all_ids(self) -> Tuple[int, ...]:
@ -251,14 +252,13 @@ class MultiWorld():
return {group_id for group_id, group in self.groups.items() if player in group["players"]} return {group_id for group_id, group in self.groups.items() if player in group["players"]}
def set_seed(self, seed: Optional[int] = None, secure: bool = False, name: Optional[str] = None): def set_seed(self, seed: Optional[int] = None, secure: bool = False, name: Optional[str] = None):
assert not self.worlds, "seed needs to be initialized before Worlds"
self.seed = get_seed(seed) self.seed = get_seed(seed)
if secure: if secure:
self.secure() self.secure()
else: else:
self.random.seed(self.seed) self.random.seed(self.seed)
self.seed_name = name if name else str(self.seed) self.seed_name = name if name else str(self.seed)
self.per_slot_randoms = {player: random.Random(self.random.getrandbits(64)) for player in
range(1, self.players + 1)}
def set_options(self, args: Namespace) -> None: def set_options(self, args: Namespace) -> None:
# TODO - remove this section once all worlds use options dataclasses # TODO - remove this section once all worlds use options dataclasses
@ -275,7 +275,6 @@ class MultiWorld():
for player in self.player_ids: for player in self.player_ids:
world_type = AutoWorld.AutoWorldRegister.world_types[self.game[player]] world_type = AutoWorld.AutoWorldRegister.world_types[self.game[player]]
self.worlds[player] = world_type(self, player) self.worlds[player] = world_type(self, player)
self.worlds[player].random = self.per_slot_randoms[player]
options_dataclass: typing.Type[Options.PerGameCommonOptions] = world_type.options_dataclass options_dataclass: typing.Type[Options.PerGameCommonOptions] = world_type.options_dataclass
self.worlds[player].options = options_dataclass(**{option_key: getattr(args, option_key)[player] self.worlds[player].options = options_dataclass(**{option_key: getattr(args, option_key)[player]
for option_key in options_dataclass.type_hints}) for option_key in options_dataclass.type_hints})

View File

@ -1,5 +1,5 @@
from argparse import Namespace from argparse import Namespace
from typing import Type, Tuple from typing import Optional, Tuple, Type
from BaseClasses import MultiWorld, CollectionState from BaseClasses import MultiWorld, CollectionState
from worlds.AutoWorld import call_all, World from worlds.AutoWorld import call_all, World
@ -7,18 +7,21 @@ from worlds.AutoWorld import call_all, World
gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill") gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill")
def setup_solo_multiworld(world_type: Type[World], steps: Tuple[str, ...] = gen_steps) -> MultiWorld: def setup_solo_multiworld(
world_type: Type[World], steps: Tuple[str, ...] = gen_steps, seed: Optional[int] = None
) -> MultiWorld:
""" """
Creates a multiworld with a single player of `world_type`, sets default options, and calls provided gen steps. Creates a multiworld with a single player of `world_type`, sets default options, and calls provided gen steps.
:param world_type: Type of the world to generate a multiworld for :param world_type: Type of the world to generate a multiworld for
:param steps: The gen steps that should be called on the generated multiworld before returning. Default calls :param steps: The gen steps that should be called on the generated multiworld before returning. Default calls
steps through pre_fill steps through pre_fill
:param seed: The seed to be used when creating this multiworld
""" """
multiworld = MultiWorld(1) multiworld = MultiWorld(1)
multiworld.game[1] = world_type.game multiworld.game[1] = world_type.game
multiworld.player_name = {1: "Tester"} multiworld.player_name = {1: "Tester"}
multiworld.set_seed() multiworld.set_seed(seed)
multiworld.state = CollectionState(multiworld) multiworld.state = CollectionState(multiworld)
args = Namespace() args = Namespace()
for name, option in world_type.options_dataclass.type_hints.items(): for name, option in world_type.options_dataclass.type_hints.items():

View File

@ -13,6 +13,7 @@ from worlds.generic.Rules import CollectionRule, add_item_rule, locality_rules,
def generate_multiworld(players: int = 1) -> MultiWorld: def generate_multiworld(players: int = 1) -> MultiWorld:
multiworld = MultiWorld(players) multiworld = MultiWorld(players)
multiworld.set_seed(0)
multiworld.player_name = {} multiworld.player_name = {}
multiworld.state = CollectionState(multiworld) multiworld.state = CollectionState(multiworld)
for i in range(players): for i in range(players):
@ -32,8 +33,6 @@ def generate_multiworld(players: int = 1) -> MultiWorld:
world.options = world.options_dataclass(**{option_key: getattr(multiworld, option_key)[player_id] world.options = world.options_dataclass(**{option_key: getattr(multiworld, option_key)[player_id]
for option_key in world.options_dataclass.type_hints}) for option_key in world.options_dataclass.type_hints})
multiworld.set_seed(0)
return multiworld return multiworld

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import hashlib import hashlib
import logging import logging
import pathlib import pathlib
import random
import re import re
import sys import sys
import time import time
@ -299,6 +300,8 @@ class World(metaclass=AutoWorldRegister):
assert multiworld is not None assert multiworld is not None
self.multiworld = multiworld self.multiworld = multiworld
self.player = player self.player = player
self.random = random.Random(multiworld.random.getrandbits(64))
multiworld.per_slot_randoms[player] = self.random
def __getattr__(self, item: str) -> Any: def __getattr__(self, item: str) -> Any:
if item == "settings": if item == "settings":

View File

@ -37,8 +37,7 @@ def setup_dlc_quest_solo_multiworld(test_options=None, seed=None, _cache: Dict[F
if frozen_options in _cache: if frozen_options in _cache:
return _cache[frozen_options] return _cache[frozen_options]
multiworld = setup_base_solo_multiworld(DLCqworld, ()) multiworld = setup_base_solo_multiworld(DLCqworld, (), seed=seed)
multiworld.set_seed(seed)
# print(f"Seed: {multiworld.seed}") # Uncomment to print the seed for every test # print(f"Seed: {multiworld.seed}") # Uncomment to print the seed for every test
args = Namespace() args = Namespace()
for name, option in DLCqworld.options_dataclass.type_hints.items(): for name, option in DLCqworld.options_dataclass.type_hints.items():

View File

@ -124,8 +124,7 @@ def setup_solo_multiworld(test_options=None, seed=None,
if frozen_options in _cache: if frozen_options in _cache:
return _cache[frozen_options] return _cache[frozen_options]
multiworld = setup_base_solo_multiworld(StardewValleyWorld, ()) multiworld = setup_base_solo_multiworld(StardewValleyWorld, (), seed=seed)
multiworld.set_seed(seed)
# print(f"Seed: {multiworld.seed}") # Uncomment to print the seed for every test # print(f"Seed: {multiworld.seed}") # Uncomment to print the seed for every test
args = Namespace() args = Namespace()
for name, option in StardewValleyWorld.options_dataclass.type_hints.items(): for name, option in StardewValleyWorld.options_dataclass.type_hints.items():