Skip to content

Commit

Permalink
implement early ball save. fix #725
Browse files Browse the repository at this point in the history
  • Loading branch information
jabdoa2 committed Feb 12, 2017
1 parent 14627ca commit b4f9b59
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 20 deletions.
1 change: 1 addition & 0 deletions mpf/core/config_spec.py
Expand Up @@ -184,6 +184,7 @@
auto_launch: single|bool|True
balls_to_save: single|int|1
enable_events: dict|str:ms|None
early_ball_save_events: dict|str:ms|None
disable_events: dict|str:ms|ball_will_end, service_mode_entered
timer_start_events: dict|str:ms|None
bcp:
Expand Down
89 changes: 69 additions & 20 deletions mpf/devices/ball_save.py
Expand Up @@ -25,6 +25,7 @@ def __init__(self, machine, name):
self.enabled = False
self.timer_started = False
self.saves_remaining = 0
self.early_saved = 0

def _initialize(self):
self.unlimited_saves = self.config['balls_to_save'] == -1
Expand Down Expand Up @@ -56,6 +57,7 @@ def enable(self, **kwargs):
return

self.saves_remaining = self.config['balls_to_save']
self.early_saved = 0
self.enabled = True
self.log.debug("Enabling. Auto launch: %s, Balls to save: %s",
self.config['auto_launch'],
Expand Down Expand Up @@ -146,11 +148,7 @@ def _grace_period(self):
time.
'''

def _ball_drain_while_active(self, balls, **kwargs):
del kwargs
if balls <= 0:
return

def _get_number_of_balls_to_save(self, available_balls):
no_balls_in_play = False

try:
Expand All @@ -159,51 +157,102 @@ def _ball_drain_while_active(self, balls, **kwargs):

if self.config['only_last_ball'] and self.machine.game.balls_in_play > 1:
self.log.debug("Will only save last ball but %s are in play.", self.machine.game.balls_in_play)
return
return 0
except AttributeError:
no_balls_in_play = True

if no_balls_in_play:
self.log.debug("Received request to save ball, but no balls are in"
" play. Discarding request.")
return
return 0

balls_to_save = available_balls

balls_to_save = balls
if self.config['only_last_ball'] and balls_to_save > 1:
balls_to_save = 1

if balls_to_save > self.machine.game.balls_in_play:
balls_to_save = self.machine.game.balls_in_play

if balls_to_save > self.saves_remaining and not self.unlimited_saves:
balls_to_save = self.saves_remaining

return balls_to_save

def _reduce_remaining_saves_and_disable_if_zero(self, balls_to_save):
if not self.unlimited_saves:
self.saves_remaining -= balls_to_save
if self.debug:
self.log.debug("Saves remaining: %s", self.saves_remaining)
elif self.debug:
self.log.debug("Unlimited saves remaining")

if self.saves_remaining <= 0 and not self.unlimited_saves:
if self.debug:
self.log.debug("Disabling since there are no saves remaining")
self.disable()

def _ball_drain_while_active(self, balls, **kwargs):
del kwargs
if balls <= 0:
return

balls_to_save = self._get_number_of_balls_to_save(balls)

self.log.debug("Ball(s) drained while active. Requesting new one(s). "
"Autolaunch: %s", self.config['auto_launch'])

self.machine.events.post('ball_save_{}_saving_ball'.format(self.name),
balls=balls_to_save)
balls=balls_to_save, early_save=False)
'''event: ball_save_(name)_saving_ball
desc: The ball save called (name) has just saved one (or more) balls.
args:
balls: The number of balls this ball saver is saving.
early_save: True if this is an early ball save.
'''

self._schedule_balls(balls_to_save)

if not self.unlimited_saves:
self.saves_remaining -= balls_to_save
if self.debug:
self.log.debug("Saves remaining: %s", self.saves_remaining)
elif self.debug:
self.log.debug("Unlimited saves remaining")

if self.saves_remaining <= 0 and not self.unlimited_saves:
if self.debug:
self.log.debug("Disabling since there are no saves remaining")
self.disable()
self._reduce_remaining_saves_and_disable_if_zero(balls_to_save)

return {'balls': balls - balls_to_save}

def early_ball_save(self, **kwargs):
"""Perform early ball save if enabled."""
del kwargs
if not self.enabled:
return

if not self._get_number_of_balls_to_save(1):
return

if self.early_saved > 0:
self.log.debug("Already performed an early ball save. Ball needs to drain first.")
return

self.machine.events.post('ball_save_{}_saving_ball'.format(self.name),
balls=1, early_save=True)
# doc block above

self.log.debug("Performing early ball save.")
self.early_saved += 1
self._schedule_balls(1)
self.machine.events.add_handler('ball_drain',
self._early_ball_save_drain_handler,
priority=1001)

self._reduce_remaining_saves_and_disable_if_zero(1)

def _early_ball_save_drain_handler(self, balls, **kwargs):
del kwargs
if self.early_saved and balls > 0:
balls -= 1
self.early_saved -= 1
self.log.debug("Early saved ball drained.")
self.machine.events.remove_handler(self._early_ball_save_drain_handler)
return {'balls': balls}

def _schedule_balls(self, balls_to_save):
if self.config['eject_delay']:
self.delay.add(self.config['eject_delay'], self._add_balls, balls_to_save=balls_to_save)
Expand Down
4 changes: 4 additions & 0 deletions mpf/tests/machine_files/ball_save/config/config.yaml
Expand Up @@ -22,6 +22,8 @@ switches:
number:
s_ball_switch_launcher:
number:
s_left_outlane:
number:

ball_devices:
bd_trough:
Expand All @@ -46,6 +48,7 @@ ball_saves:
grace_period: 2s
enable_events: enable1
timer_start_events: balldevice_bd_launcher_ball_eject_success
early_ball_save_events: s_left_outlane_active
auto_launch: yes
balls_to_save: 1
debug: yes
Expand All @@ -54,6 +57,7 @@ ball_saves:
hurry_up_time: 2s
grace_period: 2s
enable_events: enable2
early_ball_save_events: s_left_outlane_active
auto_launch: yes
balls_to_save: -1
debug: yes
Expand Down
101 changes: 101 additions & 0 deletions mpf/tests/test_BallSave.py
Expand Up @@ -25,6 +25,107 @@ def test_ball_save_enable_in_mode(self):
# mode stopped. mode_ball_save should be disabled
self.assertFalse(self.machine.ball_saves.mode_ball_save.enabled)

def test_early_ball_save_once(self):
# prepare game
self.machine.switch_controller.process_switch('s_ball_switch1', 1)
self.machine.switch_controller.process_switch('s_ball_switch2', 1)
self.advance_time_and_run(10)
self.assertEqual(2, self.machine.ball_controller.num_balls_known)
self.assertEqual(2, self.machine.ball_devices.bd_trough.balls)

# start game
self.machine.switch_controller.process_switch('s_start', 1)
self.machine.switch_controller.process_switch('s_start', 0)
self.machine_run()
self.post_event("enable1")

# ball save should be enabled now
self.assertTrue(self.machine.ball_saves.default.enabled)

# takes roughly 4s to get ball confirmed
self.advance_time_and_run(4)
self.assertNotEqual(None, self.machine.game)
self.assertBallsOnPlayfield(1)
self.assertBallsInPlay(1)
self.assertTrue(self.machine.ball_saves.default.enabled)

# early ball save
self.hit_and_release_switch("s_left_outlane")

# should eject a second ball
self.advance_time_and_run(4)
self.assertNotEqual(None, self.machine.game)
self.assertBallsOnPlayfield(2)
self.assertFalse(self.machine.ball_saves.default.enabled)
self.assertBallsInPlay(1)

# second ball drains
self.hit_switch_and_run("s_ball_switch1", 1)
self.assertBallsOnPlayfield(1)
self.assertBallsInPlay(1)

# game should not end and ball should not come back
self.assertNotEqual(None, self.machine.game)

self.advance_time_and_run(10)
self.assertBallsOnPlayfield(1)

# second ball draining should end the game
self.hit_switch_and_run("s_ball_switch2", 1)
self.assertEqual(None, self.machine.game)

def test_early_ball_save_unlimited(self):
# prepare game
self.machine.switch_controller.process_switch('s_ball_switch1', 1)
self.machine.switch_controller.process_switch('s_ball_switch2', 1)
self.advance_time_and_run(10)
self.assertEqual(2, self.machine.ball_controller.num_balls_known)
self.assertEqual(2, self.machine.ball_devices.bd_trough.balls)

# start game
self.machine.switch_controller.process_switch('s_start', 1)
self.machine.switch_controller.process_switch('s_start', 0)
self.machine_run()
self.post_event("enable2")

# ball save should be enabled now
self.assertTrue(self.machine.ball_saves.unlimited.enabled)

# takes roughly 4s to get ball confirmed
self.advance_time_and_run(4)
self.assertNotEqual(None, self.machine.game)
self.assertBallsOnPlayfield(1)
self.assertBallsInPlay(1)
self.assertTrue(self.machine.ball_saves.unlimited.enabled)

# early ball save
self.hit_and_release_switch("s_left_outlane")

# should eject a second ball
self.advance_time_and_run(4)
self.assertNotEqual(None, self.machine.game)
self.assertBallsOnPlayfield(2)
self.assertTrue(self.machine.ball_saves.unlimited.enabled)
self.assertBallsInPlay(1)

# second ball drains
self.hit_switch_and_run("s_ball_switch1", 1)
self.assertBallsOnPlayfield(1)
self.assertBallsInPlay(1)

# game should not end and ball should not come back
self.assertNotEqual(None, self.machine.game)

self.advance_time_and_run(10)
self.assertBallsOnPlayfield(1)

# second ball draining should be saved
self.hit_switch_and_run("s_ball_switch2", 1)
self.assertNotEqual(None, self.machine.game)
self.assertBallsOnPlayfield(0)

self.advance_time_and_run(5)
self.assertBallsOnPlayfield(1)

def testBallSaveShootAgain(self):
# prepare game
Expand Down

0 comments on commit b4f9b59

Please sign in to comment.