Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ def populate_doc_sections(self):
RGBColor='mpf.core.rgb_color.RGBColor',
RGBAColor='mpf.core.rgba_color.RGBAColor',
Randomizer='mpf.core.randomizer.Randomizer',
ListRandomizer='mpf.core.randomizer.ListRandomizer',
Timers='mpf.core.timer.Timer',
)

Expand Down
43 changes: 24 additions & 19 deletions mpf/config_players/random_event_player.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Random event config player."""
from mpf.core.config_player import ConfigPlayer
from mpf.core.randomizer import Randomizer
from mpf.core.randomizer import ListRandomizer
from mpf.core.utility_functions import Util


Expand All @@ -23,8 +23,9 @@ def is_entry_valid_outside_mode(settings) -> bool:
"""Return true if scope is not player."""
return settings['scope'] != "player"

def _build_randomizer(self, settings):
randomizer = Randomizer(settings['events'], self.machine, template_type="event")
def _build_randomizer(self, settings, name):
self.info_log(f"Instantiating ListRandomizer {name}")
randomizer = ListRandomizer(settings['events'], name=name, machine=self.machine, template_type="event")

if settings['force_all']:
randomizer.force_all = True
Expand All @@ -38,35 +39,39 @@ def _build_randomizer(self, settings):
randomizer.fallback_value = settings.get('fallback_event')
return randomizer

def _get_randomizer(self, settings, context, calling_context):
def find_or_create_randomizer(self, settings, context, calling_context):
"""Uses context and calling context to find a randomizer instance or create and register a new one."""
'''player_var: random_(x).(y)

desc: Holds references to ListRandomizer settings that need to be
tracked on a player basis. There is nothing you need to know
or do with this, rather this is just FYI on what the player
variables that start with "random_" are.
'''

'''machine_var: random_(x).(y)

desc: Holds references to ListRandomizer settings that need to be
tracked on a machine basis.
'''

key = "random_{}.{}".format(context, calling_context)

if settings['scope'] == "player":
if not self.machine.game.player[key]:
self.machine.game.player[key] = self._build_randomizer(settings)
self.machine.game.player[key] = self._build_randomizer(settings, key)

'''player_var: random_(x).(y)

desc: Holds references to Randomizer settings that need to be
tracked on a player basis. There is nothing you need to know
or do with this, rather this is just FYI on what the player
variables that start with "random_" are.
'''
return self.machine.game.player[key]

if key not in self._machine_wide_dict:
self._machine_wide_dict[key] = self._build_randomizer(settings)
self._machine_wide_dict[key] = self._build_randomizer(settings, key)

'''machine_var: random_(x).(y)

desc: Holds references to Randomizer settings that need to be
tracked on a machine basis.
'''
return self._machine_wide_dict[key]

def play(self, settings, context, calling_context, priority=0, **kwargs):
"""Play a random event from list based on config."""
del priority
randomizer = self._get_randomizer(settings, context, calling_context)
randomizer = self.find_or_create_randomizer(settings, context, calling_context)
# With conditional events in randomizer, there may not be a next event
next_event = randomizer.get_next(kwargs)
if next_event:
Expand Down
11 changes: 10 additions & 1 deletion mpf/core/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"""Contains the MachineController base class."""
import asyncio
import logging
import random
import sys
import threading
from typing import Any, Callable, Dict, List, Set, Optional
Expand All @@ -21,6 +22,7 @@
from mpf.core.utility_functions import Util
from mpf.core.config_loader import MpfConfig
from mpf.core.plugin import MpfPlugin
from mpf.core.randomizer import Randomizer # pylint: disable-msg=cyclic-import,unused-import

MYPY = False
if MYPY: # pragma: no cover
Expand Down Expand Up @@ -106,7 +108,7 @@ class MachineController(LogMixin):
"stop_future", "events", "switch_controller", "mode_controller", "settings",
"bcp", "ball_controller", "show_controller", "placeholder_manager", "device_manager", "auditor",
"tui", "service", "switches", "shows", "coils", "ball_devices", "lights", "playfield", "playfields",
"autofire_coils", "_crash_handlers", "__dict__", "mpf_config", "is_shutting_down"]
"autofire_coils", "_crash_handlers", "__dict__", "mpf_config", "is_shutting_down", "randomizers"]

# pylint: disable-msg=too-many-statements
def __init__(self, options: dict, config: MpfConfig) -> None:
Expand Down Expand Up @@ -143,6 +145,7 @@ def __init__(self, options: dict, config: MpfConfig) -> None:
self.mpf_config = config # type: MpfConfig
self.config_validator = ConfigValidator(self, self.mpf_config.get_config_spec())

self.randomizers = dict() # type: Dict[str, Randomizer]
self.variables = MachineVariables(self) # type: MachineVariables

# add some type hints
Expand Down Expand Up @@ -219,6 +222,7 @@ def __init__(self, options: dict, config: MpfConfig) -> None:
self.default_platform = None # type: Optional[SmartVirtualHardwarePlatform]

self.clock = self._load_clock()
self._initialize_randomizers()
self.stop_future = asyncio.Future() # type: asyncio.Future

def add_crash_handler(self, handler: Callable):
Expand Down Expand Up @@ -438,6 +442,11 @@ def _set_machine_path(self) -> None:
"""Add the machine folder to sys.path so we can import modules from it."""
sys.path.insert(0, self.machine_path)

def _initialize_randomizers(self) -> None:
"""Register the root randomizer."""
machine_seed = random.random() # NOSONAR
self.randomizers['root'] = Randomizer(machine=self, name='root', seed=machine_seed)

def verify_system_info(self):
"""Dump information about the Python installation to the log.

Expand Down
36 changes: 30 additions & 6 deletions mpf/core/randomizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,35 @@

class Randomizer:

"""Generic list randomizer."""
"""Generates randomness with a managed seed."""

def __init__(self, items, machine=None, template_type='event'):
def __init__(self, name=None, machine=None, seed=None):
"""Initialize Randomizer."""
self.name = name
self.machine = machine
self.set_seed(seed or machine and machine.randomizers['root'].random(10000))

def set_seed(self, seed):
"""Reset the random generator to the start of the specified seed."""
self.seed = seed
self._random = random.Random(seed)
if self.machine:
self.machine.log.info(f"Randomizer {self.name} seeded with seed: {seed}")

def random(self, range_value):
"""Returns number between 0 and Range-1."""
return self._random.randrange(range_value)


class ListRandomizer(Randomizer):

"""Generic list randomizer."""

# pylint: disable-msg=too-many-arguments
def __init__(self, items, name=None, machine=None, template_type='event', seed=None):
"""Initialize ListRandomizer."""
super().__init__(name=name, machine=machine, seed=seed)

self.fallback_value = None
self.force_different = True
self.force_all = False
Expand Down Expand Up @@ -41,7 +66,7 @@ def __init__(self, items, machine=None, template_type='event'):
self.items.append((this_item, int(this_weight)))
self.items.sort(key=lambda x: x[0].name or x[0])
else:
raise AssertionError("Invalid input for Randomizer")
raise AssertionError("Invalid input for ListRandomizer")

self.data = dict()
self._init_data(self.data)
Expand Down Expand Up @@ -177,16 +202,15 @@ def generate_template(machine, template_type, value):
# Add additional template_type support here, as needed
return value

@staticmethod
def pick_weighted_random(items):
def pick_weighted_random(self, items):
"""Pick a random item.

Args:
----
items: Items to select from
"""
total_weights = sum([x[1] for x in items])
value = random.randint(1, total_weights)
value = self._random.randint(1, total_weights)
index_value = 0

for item in items:
Expand Down
2 changes: 1 addition & 1 deletion mpf/devices/achievement_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ def select_random_achievement(self):
self._selected_member.unselect()

try:
# todo change this to use our Randomizer class
# todo change this to use our ListRandomizer class
if self.config['disable_random']:
ach = self._get_available_achievements_for_selection()[0]
self.debug_log("Picked new non-random achievement: %s", ach)
Expand Down
Loading
Loading