Drop Target Handler Library
Drop targets on pinball machines can be as simple as a switch for each target and a solenoid to reset those targets. Why would you need a library to handle those? In some machines (early Williams solid state machines), there is a switch that momentarily makes contact as each switch falls, and then another daisy-chained switch to indicate that all the targets are down. In early Bally machines, the switch is triggered when the target is all the way down and a poorly-gapped switch can give multiple readings when vibrations move through the playfield. This library handles those cases and only returns valid closures (one per target per reset). It also keeps track if the targets were dropped in order, and it knows to ignore closures that happen during the reset.
Constructor
DropTargetBank(byte s_numSwitches, byte s_numSolenoids, byte s_bankType, byte s_solenoidOnTime);
s_numSwitches - total number of drop targets in this bank
s_numSolenoids - total number of solenoids required to reset this bank
s_bankType
- DROP_TARGET_TYPE_BALLY_1 - normal Bally
- DROP_TARGET_TYPE_STERN_1 - normal Stern
- DROP_TARGET_TYPE_STERN_2 - Stern "memory" drops (each target can be retracted with it's own solenoid)
- DROP_TARGET_TYPE_WILLIAMS_1 - extra switch for all targets down
s_solenoidOnTime - number of cycles for solenoid reset (12 is common for Bally/Stern)
Functions
void DefineSwitch(byte switchOrder, byte switchNum); // this function is called for each target's switch
switchOrder - zero index (used for knowing if targets were dropped in order) switchNum - machine's switch matrix index
void DefineResetSolenoid(byte solIndex, byte solChannelNumber); // this function is called for each reset solenoid for this bank
solIndex - zero index solenoid number solChannelNumber - machine's solenoid index
void AddAllTargetsSwitch(byte s_allTargetsSwitch); // this function is only called if the bank has an "all down" switch (i.e. early Williams)
s_allTargetsSwitch - machine's switch matrix index
byte HandleDropTargetHit(byte switchNum); // this function is called whenever the game loop sees a drop target switch on the stack
switchNum - machine's switch matrix index
Return value - returns a bit mask of all targets down since last hit
- b0 - drop target index 0 is newly down (true/false)
- b1 - drop target index 1 is newly down (true/false)
- etc.
byte CheckIfBankCleared(); // this function is called after a target switch is seen to determine if bank has been cleared
Return values:
- 0 - bank not clear
- 1 - DROP_TARGET_BANK_CLEARED
- 2 - DROP_TARGET_BANK_CLEARED_IN_ORDER
void Update(unsigned long currentTime); // this function should be called in the program's main loop
currentTime - the current time in milliseconds
void ResetDropTargets(unsigned long timeToReset, boolean ignoreQuickDrops=false); // call this function to reset the bank
timeToReset - machine time in milliseconds to reset; typically CurrentTime + (any delay in ms) ignoreQuickDrops - set this parameter to ignore drops that come immediately after reset, in case of mechanical failure or Williams
byte GetStatus(); // call to get a bit mask of the current drops down
Return value:
- b0 - drop target index 0 is down (true/false)
- b1 - drop target index 1 is down (true/false)
- etc.
Common Usage
Global variable for each bank:
DropTargetBank DropTargets6(6, 2, DROP_TARGET_TYPE_WILLIAMS_1, 50);
Targets are configured in setup() function:
DropTargets6.DefineSwitch(0, SW_LEFT_3_DROP_1);
DropTargets6.DefineSwitch(1, SW_LEFT_3_DROP_2);
DropTargets6.DefineSwitch(2, SW_LEFT_3_DROP_3);
DropTargets6.DefineSwitch(3, SW_RIGHT_3_DROP_1);
DropTargets6.DefineSwitch(4, SW_RIGHT_3_DROP_2);
DropTargets6.DefineSwitch(5, SW_RIGHT_3_DROP_3);
DropTargets6.AddAllTargetsSwitch(SW_3_DROPS_COMPLETE);
DropTargets6.DefineResetSolenoid(0, SOL_3_DROP_LEFT_RESET);
DropTargets6.DefineResetSolenoid(1, SOL_3_DROP_RIGHT_RESET);
Targets are reset in InitNewBall
DropTargets6.ResetDropTargets(CurrentTime + 200, true);
Switch hits are sent to a handler function
case SW_LEFT_3_DROP_1:
case SW_LEFT_3_DROP_2:
case SW_LEFT_3_DROP_3:
case SW_RIGHT_3_DROP_1:
case SW_RIGHT_3_DROP_2:
case SW_RIGHT_3_DROP_3:
case SW_3_DROPS_COMPLETE:
if (Handle6Drops(switchHit)) {
LastSwitchHitTime = CurrentTime;
if (BallFirstSwitchHitTime == 0) BallFirstSwitchHitTime = CurrentTime;
}
break;
Score is added in switch handler, and targets are checked to see if they have been cleared
void Handle6Drops(byte switchHit) {
byte result = DropTargets6.HandleDropTargetHit(switchHit);
unsigned long numTargetsDown = (unsigned long)CountBits(result);
if (numTargetsDown) CurrentScores[CurrentPlayer] += PlayfieldMultiplier * numTargetsDown * 100;
byte cleared = DropTargets6.CheckIfBankCleared();
if (cleared) {
// currently no award for clearing in order
DropTargets6.ResetDropTargets(CurrentTime + 500, true);
if (Drop6Clears[CurrentPlayer]<4) {
CurrentScores[CurrentPlayer] += Drop6ClearRewards[Drop6Clears[CurrentPlayer]] * PlayfieldMultiplier;
Drop6Clears[CurrentPlayer] += 1;
} else {
AwardSpecial();
Drop6Clears[CurrentPlayer] = 0;
}
IncreasePlayfieldMultiplier(30000);
PlaySoundEffect(SOUND_EFFECT_DROP_TARGET_RESET);
} else if (numTargetsDown) {
PlaySoundEffect(SOUND_EFFECT_DROP_TARGET_HIT);
}
...
Targets are updated in loop() or ManageGameMode()
DropTargets6.Update(CurrentTime);