<commit_before>"""Contains the Playfield device class which represents the actual playfield in
a pinball machine."""
# playfield.py
# Mission Pinball Framework
# Written by <NAME> & <NAME>
# Released under the MIT License. (See license info at the end of this file.)

import logging
from collections import defaultdict

from mpf.devices.ball_device import BallDevice
from mpf.system.tasks import DelayManager


class Playfield(BallDevice):

    def __init__(self, machine, name, collection):
        self.log = logging.getLogger('Playfield')

        self.machine = machine
        self.name = name
        self.tags = list()
        self.config = defaultdict(lambda: None)
        self.config['eject_targets'] = list()

        self.ball_controller = self.machine.ball_controller

        self.delay = DelayManager()

        # Add the playfield ball device to the existing device collection
        collection_object = getattr(self.machine, collection)[name] = self

        # Attributes
        self._balls = 0
        self.num_balls_requested = 0
        self.player_controlled_eject_in_progress = None
        self.queued_balls = list()
        self._playfield = True

        # Set up event handlers

        # Watch for balls added to the playfield
        for device in self.machine.balldevices:
            for target in device.config['eject_targets']:
                if target == self.name:
                    self.machine.events.add_handler(
                        event='balldevice_' + device.name +
                        '_ball_eject_success',
                        handler=self._source_device_eject_success)
                    self.machine.events.add_handler(
                        event='balldevice_' + device.name +
                        '_ball_eject_attempt',
                        handler=self._source_device_eject_attempt)
                break

        # Watch for balls removed from the playfield
        self.machine.events.add_handler('balldevice_captured_from_' + self.name,
                                        self._ball_removed_handler)

        # Watch for any switch hit which indicates a ball on the playfield
        self.machine.events.add_handler('sw_' + self.name + '_active',
                                        self.playfield_switch_hit)

    @property
    def balls(self):
        return self._balls

    @balls.setter
    def balls(self, balls):

        prior_balls = self._balls
        ball_change = balls - prior_balls

        if ball_change:
            self.log.debug("Ball count change. Prior: %s, Current: %s, Change: "
                           "%s", prior_balls, balls, ball_change)

        if balls > 0:
            self._balls = balls
            #self.ball_search_schedule()
        elif balls == 0:
            self._balls = 0
            #self.ball_search_disable()
        else:
            self.log.warning("Playfield balls went to %s. Resetting to 0, but "
                             "FYI that something's weird", balls)
            self._balls = 0
            #self.ball_search_disable()

        self.log.debug("New Ball Count: %s. (Prior count: %s)",
                       self._balls, prior_balls)

        if ball_change > 0:
            self.machine.events.post_relay('balldevice_' + self.name +
                                           '_ball_enter', balls=ball_change)

        if ball_change:
            self.machine.events.post(self.name + '_ball_count_change',
                                     balls=balls, change=ball_change)

    def count_balls(self, **kwargs):
        """Used to count the number of balls that are contained in a ball
        device. Since this is the playfield device, this method always returns
        zero.

        Returns: 0
        """
        return 0

    def get_additional_ball_capacity(self):
        """Used to find out how many more balls this device can hold. Since this
        is the playfield device, this method always returns 999.

        Returns: 999
        """
        return 999

    def add_ball(self, balls=1, source_name=None, source_device=None,
                 trigger_event=None):

        """Adds live ball(s) to the playfield.

        Args:
            balls: Integer of the number of balls you'd like to add.
            source_name: Optional string name of the ball device you'd like to
                add the ball(s) from.
            source_device: Optional ball device object you'd like to add the
                ball(s) from.
            trigger_event: The optional name of an event that MPF will wait for
                before adding the ball into play. Typically used with player-
                controlled eject tag events. If None, the ball will be added
                immediately.

        Returns:
            True if it's able to process the add_ball() request, False if it
            cannot.

        Both source_name and source_device args are included to give you two
        options for specifying the source of the ball(s) to be added. You don't
        need to supply both. (it's an "either/or" thing.) Both of these args are
        optional, so if you don't supply them then MPF will look for a device
        tagged with 'ball_add_live'. If you don't provide a source and you don't
        have a device with the 'ball_add_live' tag, MPF will quit.

        This method does *not* increase the game controller's count of the
        number of balls in play. So if you want to add balls (like in a
        ball scenario(, you need to call this method along with
        ``self.machine.game.add_balls_in_play()``.)

        MPF tracks the number of balls in play separately from the actual balls
        on the playfield because there are numerous situations where the two
        counts are not the same. For example, if a ball is in a VUK while some
        animation is playing, there are no balls on the playfield but still one
        ball in play, or if the player has a two-ball multiball and they shoot
        them both into locks, there are still two balls in play even though
        there are no balls on the playfield, or if the player tilts then there
        are still balls on the playfield but no balls in play.

        """

        if balls < 1:
            self.log.error("Received request to add %s balls, which doesn't "
                           "make sense. Not adding any balls...")
            return False

        # Figure out which device we'll get a ball from

        if source_device:
            pass
        elif source_name and source_name in self.machine.balldevices:
            source_device = self.machine.balldevices[source_name]
        else:
            for device in self.machine.balldevices.items_tagged('ball_add_live'):
                source_device = device
                break

        if not source_device:
            self.log.critical("Received request to add a ball to the playfield, "
                              "but no source device was passed and no ball "
                              "devices are tagged with 'ball_add_live'. Cannot "
                              "add a ball.")
            raise Exception("Received request to add a ball to the playfield, "
                            "but no source device was passed and no ball "
                            "devices are tagged with 'ball_add_live'. Cannot "
                            "add a ball.")

        # If there's a player controlled eject in progress for this device, we
        # hold this request until it's over.
        if self.player_controlled_eject_in_progress == source_device:
            self.queued_balls.append((balls, source_name, source_device,
                                      trigger_event))
            self.log.debug("An add_ball() request came in while there was a "
                           "current player-controlled eject in progress for the"
                            "same device. Will queue the eject request")
            return True

        self.log.debug("Received request to add %s ball(s). Source device: %s. "
                       "Wait for event: %s", balls, source_device.name,
                       trigger_event)
<commit_msg>select a launcher which has the playfield as target when adding a ball<commit_after>"""Contains the Playfield device class which represents the actual playfield in
a pinball machine."""
# playfield.py
# Mission Pinball Framework
# Written by <NAME> & <NAME>
# Released under the MIT License. (See license info at the end of this file.)

import logging
from collections import defaultdict

from mpf.devices.ball_device import BallDevice
from mpf.system.tasks import DelayManager


class Playfield(BallDevice):

    def __init__(self, machine, name, collection):
        self.log = logging.getLogger('Playfield')

        self.machine = machine
        self.name = name
        self.tags = list()
        self.config = defaultdict(lambda: None)
        self.config['eject_targets'] = list()

        self.ball_controller = self.machine.ball_controller

        self.delay = DelayManager()

        # Add the playfield ball device to the existing device collection
        collection_object = getattr(self.machine, collection)[name] = self

        # Attributes
        self._balls = 0
        self.num_balls_requested = 0
        self.player_controlled_eject_in_progress = None
        self.queued_balls = list()
        self._playfield = True

        # Set up event handlers

        # Watch for balls added to the playfield
        for device in self.machine.balldevices:
            for target in device.config['eject_targets']:
                if target == self.name:
                    self.machine.events.add_handler(
                        event='balldevice_' + device.name +
                        '_ball_eject_success',
                        handler=self._source_device_eject_success)
                    self.machine.events.add_handler(
                        event='balldevice_' + device.name +
                        '_ball_eject_attempt',
                        handler=self._source_device_eject_attempt)
                break

        # Watch for balls removed from the playfield
        self.machine.events.add_handler('balldevice_captured_from_' + self.name,
                                        self._ball_removed_handler)

        # Watch for any switch hit which indicates a ball on the playfield
        self.machine.events.add_handler('sw_' + self.name + '_active',
                                        self.playfield_switch_hit)

    @property
    def balls(self):
        return self._balls

    @balls.setter
    def balls(self, balls):

        prior_balls = self._balls
        ball_change = balls - prior_balls

        if ball_change:
            self.log.debug("Ball count change. Prior: %s, Current: %s, Change: "
                           "%s", prior_balls, balls, ball_change)

        if balls > 0:
            self._balls = balls
            #self.ball_search_schedule()
        elif balls == 0:
            self._balls = 0
            #self.ball_search_disable()
        else:
            self.log.warning("Playfield balls went to %s. Resetting to 0, but "
                             "FYI that something's weird", balls)
            self._balls = 0
            #self.ball_search_disable()

        self.log.debug("New Ball Count: %s. (Prior count: %s)",
                       self._balls, prior_balls)

        if ball_change > 0:
            self.machine.events.post_relay('balldevice_' + self.name +
                                           '_ball_enter', balls=ball_change)

        if ball_change:
            self.machine.events.post(self.name + '_ball_count_change',
                                     balls=balls, change=ball_change)

    def count_balls(self, **kwargs):
        """Used to count the number of balls that are contained in a ball
        device. Since this is the playfield device, this method always returns
        zero.

        Returns: 0
        """
        return 0

    def get_additional_ball_capacity(self):
        """Used to find out how many more balls this device can hold. Since this
        is the playfield device, this method always returns 999.

        Returns: 999
        """
        return 999

    def add_ball(self, balls=1, source_name=None, source_device=None,
                 trigger_event=None):

        """Adds live ball(s) to the playfield.

        Args:
            balls: Integer of the number of balls you'd like to add.
            source_name: Optional string name of the ball device you'd like to
                add the ball(s) from.
            source_device: Optional ball device object you'd like to add the
                ball(s) from.
            trigger_event: The optional name of an event that MPF will wait for
                before adding the ball into play. Typically used with player-
                controlled eject tag events. If None, the ball will be added
                immediately.

        Returns:
            True if it's able to process the add_ball() request, False if it
            cannot.

        Both source_name and source_device args are included to give you two
        options for specifying the source of the ball(s) to be added. You don't
        need to supply both. (it's an "either/or" thing.) Both of these args are
        optional, so if you don't supply them then MPF will look for a device
        tagged with 'ball_add_live'. If you don't provide a source and you don't
        have a device with the 'ball_add_live' tag, MPF will quit.

        This method does *not* increase the game controller's count of the
        number of balls in play. So if you want to add balls (like in a
        ball scenario(, you need to call this method along with
        ``self.machine.game.add_balls_in_play()``.)

        MPF tracks the number of balls in play separately from the actual balls
        on the playfield because there are numerous situations where the two
        counts are not the same. For example, if a ball is in a VUK while some
        animation is playing, there are no balls on the playfield but still one
        ball in play, or if the player has a two-ball multiball and they shoot
        them both into locks, there are still two balls in play even though
        there are no balls on the playfield, or if the player tilts then there
        are still balls on the playfield but no balls in play.

        """

        if balls < 1:
            self.log.error("Received request to add %s balls, which doesn't "
                           "make sense. Not adding any balls...")
            return False

        # Figure out which device we'll get a ball from

        if source_device:
            pass
        elif source_name and source_name in self.machine.balldevices:
            source_device = self.machine.balldevices[source_name]
        else:
            for device in self.machine.balldevices.items_tagged('ball_add_live'):
                if self in device.config['eject_targets']:
                    source_device = device
                    break

        if not source_device:
            self.log.critical("Received request to add a ball to the playfield, "
                              "but no source device was passed and no ball "
                              "devices are tagged with 'ball_add_live'. Cannot "
                              "add a ball.")
            raise Exception("Received request to add a ball to the playfield, "
                            "but no source device was passed and no ball "
                            "devices are tagged with 'ball_add_live'. Cannot "
                            "add a ball.")

        # If there's a player controlled eject in progress for this device, we
        # hold this request until it's over.
        if self.player_controlled_eject_in_progress == source_device:
            self.queued_balls.append((balls, source_name, source_device,
                                      trigger_event))
            self.log.debug("An add_ball() request came in while there was a "
                           "current player-controlled eject in progress for the"
                            "same device. Will queue the eject request")
            return True

        self.log.debug("Received request to add %s ball(s). Source device: %s. "
                       "Wait for event: %s", balls, source_device.name,
                       trigger_event)

{'fraction_non_alphanumeric': 0.0654624803006425, 'fraction_numerical': 0.002545763122802764, 'mean_word_length': 1.809775204359673, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6925946', 'n_tokens_mistral': 4294, 'n_tokens_neox': 4013, 'n_words': 1590}