Skip to content

title: Recipe: GADGET Targets from Stern Batman '66

Recipe: GADGET Targets from Stern Batman '66

This guide shows you how to build an MPF config for Batman 66's GADGET targets. The idea is you can use this as a guide to implement a similar feature in your machine.

Note

This recipe requires MPF 0.53 or newer.

This guide uses the following concepts in MPF:

TODO You can find the complete runnable machine config for this recipe in the cookbook/B66_Gadget folder of the mpf-examples repository on GitHub.

What is GADGET mode?

In Bataman '66, a player may hit each of the 6 stand-up targets representing the letters of the word "GADGET". When all letters have been hit, the player is awarded a "Gadget" which gives the players special ability in the game.

Here are the specific rules we need to implement:

GADGET

  • Each letter begin unlit
  • Letters become lit when hit individually
  • When an already-lit letter is hit, award one adjacent unlit letter. (Friendly Neighbor)
  • After all letters are hit, award a gadget and reset the letters to the beginning.
  • Players may earn multiple gadgets
  • Light the lockdown bar to indicate to the player that they have earned a gadget.

Using an earned Gadget is outside the scope of this document. This cookbook only covers earning gadgets.

Step 1. The machine-wide prerequisites

Before we dig into how to handle the mode itself, we need to create a machine-wide config that has all the devices we'll need, including the switches for the targets.

Here's what our machine config looks like. (Note that this is complete in terms of what we need to make this recipe work, but if you have a real Batman '66 then you'll probably have a lot more than this in your machine config file. Also, the coil, switch, and light numbers are generic and need to be changes for a real machine.)

Notice the "player_vars" section. It has a two player variables named "gadgets_available" & "gadgets_earned". This exists outside of the mode to 'protect' earned, but unused gadgets from being reset in the rare cases when we may need to stop the mode that allows players to earn gadgets.

#config_version=5

modes:
  - gadget

player_vars:
  gadgets_available:
    initial_value: 0
  gadgets_earned:
    initial_value: 0

switches:
  s_left_flipper:
    number: 0
    tags: left_flipper, playfield_active
  s_right_flipper:
    number: 71
    tags: right_flipper
  s_credit:
    number: 6
    tags: start
  s_outhole:
    number: 8
    tags:
  s_gadget_g1:
    number: 17
    tags: gadget_targets
  s_gadget_a:
    number: 18
    tags: gadget_targets
  s_gadget_d:
    number: 19
    tags: gadget_targets
  s_gadget_g2:
    number: 22
    tags: gadget_targets
  s_gadget_e:
    number: 23
    tags: gadget_targets
  s_gadget_t:
    number: 24
    tags: gadget_targets
  s_trough_6:
    number: 33
    tags:
  s_trough_5:
    number: 36
    tags:
  s_trough_4:
    number: 37
    tags:
  s_trough_3:
    number: 38
    tags:
  s_trough_2:
    number: 39
    tags:
  s_trough_1:
    number: 40
    tags:
  s_start_button:
    number: 99
    tags: start, playfield_active

keyboard:
  s:
    switch: s_start_button

virtual_platform_start_active_switches: s_trough_1, s_trough_2, s_trough_3, s_trough_4, s_trough_5, s_trough_6

coils:
  c_flipper_left_main:
    number: 0
    default_pulse_ms: 20
  c_flipper_left_hold:
    number: 1
    allow_enable: true
  c_flipper_right_main:
    number: 2
    default_pulse_ms: 20
  c_flipper_right_hold:
    number: 3
    allow_enable: true
  c_trough_eject:
    number: 4
    allow_enable: true
  c_ball_eject:
    number: c12
    label:
    tags:
    default_pulse_ms: 20
  c_outhole:
    number: c14
    label:
    tags:
    default_pulse_ms: 20

lights:
  l_gadget_g1:
    number: 5
    tags: gadget_letter
  l_gadget_a:
    number: 6
    tags: gadget_letter
  l_gadget_d:
    number: 7
    tags: gadget_letter
  l_gadget_g2:
    number: 8
    tags: gadget_letter
  l_gadget_e:
    number: 9
    tags: gadget_letter
  l_gadget_t:
    number: 10
    tags: gadget_letter
  l_lockdown_bar:
    number: 11

ball_devices:
  bd_drain:
    ball_switches: s_outhole
    eject_coil: c_outhole
    eject_targets: bd_trough
    tags: drain, outhole
  bd_trough:
    ball_switches: s_trough_1, s_trough_2, s_trough_3, s_trough_4, s_trough_5
    eject_coil: c_ball_eject
    tags: trough, home

playfields:
  playfield:
    default_source_device: bd_trough
    tags: default

##! mode: gadget
#! mode:
#!   #this mode starts when the ball starts
#!   start_events: ball_started
#!
#!   priority: 500

Step 2. Create the Gadget Mode Config File

Next, we can start setting up our gadget mode; below you see the contents of gadget.yaml

##! mode: gadget
config:
  - logic_blocks.yaml
  - event_player.yaml
  - show_player.yaml
  - variable_player.yaml

mode:
  #this mode starts when the ball starts
  start_events: ball_started

  priority: 500
##! config: modes/gadget/config/logic_blocks
#! # empty file
##! config: modes/gadget/config/event_player
#! # empty file
##! config: modes/gadget/config/show_player
#! # empty file
##! config: modes/gadget/config/variable_player
#! # empty file

Stepping through how we're using each setting:

##! mode: gadget
config:
  - logic_blocks.yaml

##! config: modes/gadget/config/logic_blocks
#! # empty file

The config section imports other config files; this is often easier to manage than on long config file.

##! mode: gadget
#! mode:
#!   start_events: ball_started
  priority: 500

The Gadget mode in Batman '66 is nearly always running and rarely blocked, so we have assigned it a very high priority, but one that can still be superceded if the need arises.

Step 3. Create the Accrual Logic Block

Also in our mode config folder, we will add logic_blocks.yaml to hold our mode-specific logic_blocks. In this case, we're using an Accrual Logic Blocks to track when all of the letters have been hit.

##! mode: gadget
accruals:
  gadget_accrual:
    events:
      - gadget_g1_complete # index [0]
      - gadget_a_complete # index [1]
      - gadget_d_complete # index [2]
      - gadget_g2_complete # index [3]
      - gadget_e_complete # index [4]
      - gadget_t_complete # index [5]
    reset_on_complete: true
    disable_on_complete: false
    reset_events: mode_gadget_started
    events_when_complete: award_gadget, reset_gadget_lights

Stepping through once again:

##! mode: gadget
accruals:
  gadget_accrual:
#!     events:
#!       - gadget_g1_complete # index [0]
#!       - gadget_a_complete # index [1]
#!       - gadget_d_complete # index [2]
#!       - gadget_g2_complete # index [3]
#!       - gadget_e_complete # index [4]
#!       - gadget_t_complete # index [5]
#!     reset_on_complete: true
#!     disable_on_complete: false
#!     reset_events: mode_gadget_started
#!     events_when_complete: award_gadget, reset_gadget_lights

These two lines simply tell MPF that we have an accrual and we've named it "gadget_accrual".

##! mode: gadget
#! accruals:
#!   gadget_accrual:
    events:
      - gadget_g1_complete # index [0]
      - gadget_a_complete # index [1]
      - gadget_d_complete # index [2]
      - gadget_g2_complete # index [3]
      - gadget_e_complete # index [4]
      - gadget_t_complete # index [5]
#!     reset_on_complete: true
#!     disable_on_complete: false
#!     reset_events: mode_gadget_started
#!     events_when_complete: award_gadget, reset_gadget_lights

Next, we have a list of events for the accrual to track. Accruals behave like arrays, so I added a comment after each event to help me remember the index of each event. We'll need to reference these events and their index later.

##! mode: gadget
#! accruals:
#!   gadget_accrual:
#!     events:
#!       - gadget_g1_complete # index [0]
#!       - gadget_a_complete # index [1]
#!       - gadget_d_complete # index [2]
#!       - gadget_g2_complete # index [3]
#!       - gadget_e_complete # index [4]
#!       - gadget_t_complete # index [5]
    reset_on_complete: true
#!     disable_on_complete: false
#!     reset_events: mode_gadget_started
#!     events_when_complete: award_gadget, reset_gadget_lights

Once the player has hit all of the letters, we want the accrual to reset so that they can earn more Gadgets.

##! mode: gadget
#! accruals:
#!   gadget_accrual:
#!     events:
#!       - gadget_g1_complete # index [0]
#!       - gadget_a_complete # index [1]
#!       - gadget_d_complete # index [2]
#!       - gadget_g2_complete # index [3]
#!       - gadget_e_complete # index [4]
#!       - gadget_t_complete # index [5]
#!     reset_on_complete: true
    disable_on_complete: false
#!     reset_events: mode_gadget_started
#!     events_when_complete: award_gadget, reset_gadget_lights

We also have to tell MPF to leave our accrual enabled, even after it's completed.

##! mode: gadget
#! accruals:
#!   gadget_accrual:
#!     events:
#!       - gadget_g1_complete # index [0]
#!       - gadget_a_complete # index [1]
#!       - gadget_d_complete # index [2]
#!       - gadget_g2_complete # index [3]
#!       - gadget_e_complete # index [4]
#!       - gadget_t_complete # index [5]
#!     reset_on_complete: true
#!     disable_on_complete: false
#!     reset_events: mode_gadget_started
    events_when_complete: award_gadget, reset_gadget_lights

When the accrual is complete, we want it to fire the two events in the list. We'll see what these events actually do a bit later.

Step 4. Create the 'Friendly Neighbor' Behavior

The Gadget targets exhibit a player-friendly behavior that makes them easier to complete. If the player hits a letter that is already complete, the game will award one of the neigbhoring targets if they are incomplete. To accomplish this, we'll use conditional events in our event player.

##! mode: gadget
event_player:
  #plus one gadget when accrual is complete
  award_gadget:
    - gadgets_earned
    - gadgets_available

  s_gadget_g1_active:
    #if the g is hit, and unlit
    - gadget_g1_complete{device.accruals.gadget_accrual.value[0]==False}
    #award a if we already have g1
    - gadget_a_complete{device.accruals.gadget_accrual.value[0]==True}
  s_gadget_a_active:
    #if a is hit and unlit
    - gadget_a_complete{device.accruals.gadget_accrual.value[1]==False}
    #award g1 if we already have a
    - gadget_g1_complete{device.accruals.gadget_accrual.value[0]==False and device.accruals.gadget_accrual.value[1]==True}
    #award d if we already have a and g1
    - gadget_d_complete{device.accruals.gadget_accrual.value[0]==True and device.accruals.gadget_accrual.value[1]==True and device.accruals.gadget_accrual.value[2]==False}
  s_gadget_d_active:
    - gadget_d_complete{device.accruals.gadget_accrual.value[2]==False}
    - gadget_a_complete{device.accruals.gadget_accrual.value[1]==False and device.accruals.gadget_accrual.value[2]==True}
    - gadget_g2_complete{device.accruals.gadget_accrual.value[1]==True and device.accruals.gadget_accrual.value[2] and device.accruals.gadget_accrual.value[3]==False}
  s_gadget_g2_active:
    - gadget_g2_complete{device.accruals.gadget_accrual.value[3]==False}
    - gadget_d_complete{device.accruals.gadget_accrual.value[2]==False and device.accruals.gadget_accrual.value[3]==True}
    - gadget_e_complete{device.accruals.gadget_accrual.value[2]==True and device.accruals.gadget_accrual.value[3]==True and device.accruals.gadget_accrual.value[4]==False}
  s_gadget_e_active:
    - gadget_e_complete{device.accruals.gadget_accrual.value[4]==False}
    - gadget_g2_complete{device.accruals.gadget_accrual.value[3]==False and device.accruals.gadget_accrual.value[4]==True}
    - gadget_t_complete{device.accruals.gadget_accrual.value[3]==True and device.accruals.gadget_accrual.value[4]==True and device.accruals.gadget_accrual.value[5]==False}
  s_gadget_t_active:
    - gadget_t_complete{device.accruals.gadget_accrual.value[5]==False}
    - gadget_e_complete{device.accruals.gadget_accrual.value[4]==False and device.accruals.gadget_accrual.value[5]==True}

There's a lot happening here, so let's get the easy stuff out of the way first:

##! mode: gadget
#! event_player:
  award_gadget:
    - gadgets_earned
    - gadgets_available

The "award_gadget" event - triggered by the accrual completion, simply adds one to both player_vars we configured in step one.

##! mode: gadget
#! event_player:
  s_gadget_a_active:
    #if a is hit and unlit
    - gadget_a_complete{device.accruals.gadget_accrual.value[1]==False}

This is our first conditional event, which covers the case of "a" having not yet been hit. When the "a" switch is active, trigger the event "gadget_a_complete" if it hasn't been seen by the accrual. Note the value[1] which refers to the 2nd index of our accrual.

##! mode: gadget
#! event_player:
#!   s_gadget_a_active:
    - gadget_g1_complete{device.accruals.gadget_accrual.value[0]==False and device.accruals.gadget_accrual.value[1]==True}

Now, we trigger gadget_g1_complete if it hasn't been seen by the accrual AND "a" is already complete.

##! mode: gadget
#! event_player:
#!   s_gadget_a_active:
    - gadget_d_complete{device.accruals.gadget_accrual.value[0]==True and device.accruals.gadget_accrual.value[1]==True and device.accruals.gadget_accrual.value[2]==False}

The final case for "a" is if "g1" and "a" are complete, then trigger the event for "d" if it hasn't been triggered yet.

If all three cases "g1", "a" and "d" have all been captured by the accrual, then nothing happens.

We repeat this series of conditional events for all letters. "g1" and "t" have fewer events because they each only have one neighboring target.

Step 5. Add Your Light Shows

Now, we'll add some visual feedback for the player to know when they've been awarded a letter, or completed the "gadget_accrual". This show is "light_gadget_letter.yaml" and it's in the "shows" folder for the mode. It's pretty straightforward, but uses tokens and tags to be efficient.

##! show: light_gadget_letter
- time: 0
  lights:
    (gadget_letter_made_led): (gadget_letter_made_color)

- time: +.05
  lights:
    (gadget_letter_made_led): off

- time: +.05
  lights:
    (gadget_letter_made_led): (gadget_letter_made_color)

- time: +.05
  lights:
    (gadget_letter_made_led): off

- time: +.05
  lights:
    (gadget_letter_made_led): (gadget_letter_made_color)

- time: +.05
  lights:
    (gadget_letter_made_led): off

- time: +.05
  lights:
    (gadget_letter_made_led): (gadget_letter_made_color)

- time: +.05
  lights:
    (gadget_letter_made_led): off

- time: +.05
  lights:
    (gadget_letter_made_led): (gadget_letter_made_color)

- time: +.05
  lights:
    (gadget_letter_made_led): off

- time: +.05
  lights:
    (gadget_letter_made_led): (gadget_letter_made_color)

- time: +.05
  lights:
    (gadget_letter_made_led): off

- time: +.05
  lights:
    (gadget_letter_made_led): (gadget_letter_made_color)

- time: +.05
  lights:
    (gadget_letter_made_led): off

- time: +.05
  lights:
    (gadget_letter_made_led): (gadget_letter_made_color)

- time: +.05
  lights:
    (gadget_letter_made_led): off

- time: +.05
  lights:
    (gadget_letter_made_led): (gadget_letter_final_color)

  duration: -1

This show isn't terribly complicated, but let's look at some of the features.

##! show: light_gadget_letter
- time: 0
  lights:
    (gadget_letter_made_led): (gadget_letter_made_color)

- time: +.05
  lights:
    (gadget_letter_made_led): off

When the show starts, it accepts a token from the show_player (we'll configure that next), that tells MPF what corresponding light(s) we're going to flash, and what color to flash them.

In a real Batman '66, we would simply flash the light because the inserts are yellow. However, since many custom games are using RGB LED, we'll allow for any color the builder prefers.

##! show: light_gadget_letter
- time: +.05
  lights:
    (gadget_letter_made_led): (gadget_letter_final_color)

  duration: -1

The last step is special for two reasons. We're passing in a second color that will be 'held' at the end of the show indefinitely as indicated by duration -1. We've done this in order to allow for the same show to end in a 'lit' or 'unlit' state, depending on our need in a situation.

In the code you can download from the link at the beginning of this cookbook, there is another show that lights the LED on the lockdown bar, but it's not worth explaining here.

Step 6. Configure the Show Player

Our show player is watching for events and triggering the appropriate shows.

show_player:
  gadget_g1_complete:
    light_gadget_letter:
      priority: 10
      key: gadget_g1_hit_show
      show_tokens:
        gadget_letter_made_led: l_gadget_g1
        gadget_letter_made_color: yellow
        gadget_letter_final_color: yellow
#! show_player:
  gadget_g1_complete:
    light_gadget_letter:
#!       priority: 10

When the "gadget_g1_complete" event is triggered, start the "light_gadget_letter" show starts.

#! show_player:
#!   gadget_g1_complete:
#!     light_gadget_letter:
#!       priority: 10
      key: gadget_g1_hit_show

We'll add a key to the show so that we can keep re-using the same show for all the letters.

#! show_player:
#!   gadget_g1_complete:
#!     light_gadget_letter:
#!       priority: 10
#!       key: gadget_g1_hit_show
#!       show_tokens:
        gadget_letter_made_led: l_gadget_g1
        gadget_letter_made_color: yellow
        gadget_letter_final_color: yellow

Finally, we pass show tokens to the show to tell it what light and what color we want for the on steps and the final step. This repeats for all of the individual letters.

show_player:
  reset_gadget_lights:
    light_gadget_letter:
      priority: 10
      show_tokens:
        gadget_letter_made_led: gadget_letter
        gadget_letter_made_color: yellow
        gadget_letter_final_color: 000000

"reset_gadget_lights" is fired by the accrual when it's complete. We make two small, but important changes. First "gadget_letter" is a tag from the machine config assigned to all the letters in GADGET. This will cause all of the letters to play the show simultaneously. Second, "gadget_letter_final_color" is now black/off. This effectively resets the lights and prepares the inserts for a new accrual to begin.

At this point, your Gadget mode is ready to go. You can add scoring in a variable_player and extend this by writing ways to use gadgets and reduce the "gadgets_available" player_vars. If any of this feels unclear or I've muddied up the explanation, feel free to join the discussion in the forum.


Something missing or wrong? You can fix it!

This website is edited by people like you! Is something wrong or missing? Is something out of date, or can you explain it better?

Please help us! You can fix it yourself and be an official "open source" contributor!

It's easy! See our Beginner's guide to editing the docs.

Page navigation via the keyboard: < >

You can navigate this site via the keyboard. There are two modes:

General navigation, when search is not focused:

  • F , S , / : open search dialog
  • P , , : go to previous page
  • N , . : go to next page

While using the search function:

  • Down , Up : select next / previous result
  • Esc , Tab : close search
  • Enter : go to highlighted page in the results