feat(examples): plinko betting — slot prediction with multiplier#1462
Merged
Conversation
…edit multiplier
Adds a per-drop betting layer to the plinko-planck example. Tap a
scoring slot to stake credits on the prediction that the next ball
will land there; wager stacks up to ×5; switching slots before
settlement refunds in full (only ball-lands-elsewhere loses the
wager). Win pays (slot_score × N) to score AND (base_refund + wager)
× N to credits, so winning a low-tier bet still recovers more than
it cost.
UI: each slot signals clickability with a breathing "TAP" hint over
a dark legibility backdrop; active bet shows "BET ×N" + "+M" payout
preview; bust paints the bet slot red; idle-locked while balls fall
greys the hint out. HUD hint extends with the live score + credit
payout. Win plays a layered fanfare (white-noise swoosh + triangle
brass arpeggio + bell sparkle) and spawns a full-viewport gold
flash with expanding shockwave rings.
Code shape (senior-review pass after the feature landed):
- `tierForScore` centralized in constants.ts (was duplicated between
slot.ts and bakedStatics.ts — divergence risk on rebalance)
- `gameState.lastBust` / `lastWin` typed as `BetEvent | null`
(`{ at, slotIndex }`); impossible to half-reset vs prior parallel
`lastFooAt` / `lastFooSlot` fields
- `gameState.activeBalls` integer counter ticked in
Ball.onActivateEvent / onDeactivateEvent; `hasActiveBalls()` is
now O(1) instead of O(N×slots) per frame
- `findWorld(node)` helper extracted to entities/util.ts; replaces
three identical ancestor-walks
- `Slot.collect()` decomposed into `settleBet` /
`playLandingAudio` / `spawnLandingEffects` / `spawnScoreFly` /
`spawnCreditFly`, each taking a frozen `BetOutcome`
- `Slot.update()` reduced to a `computeLabelState` projection +
`applyLabelState` applier; state-machine logic separated from
Text-mutation
- `WinFlash` extracted to its own file (entities/winFlash.ts)
- All "feel" timing constants (SLOT_PULSE_MS, BET_RESULT_PULSE_MS,
IDLE_BREATHE_MS, WIN_FLASH_MS) co-located in constants.ts
- `placeBetClick` returns `number | null` (the new wager) instead of
`boolean`; call site drops `?? 1` fallback and non-null assertion
Also: HUD subtitle → // www.melonjs.org.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a per-drop betting mechanic to the plinko-planck example, extending the existing score/credit loop with slot prediction wagers, win/bust feedback, and supporting refactors to keep the example maintainable and performant.
Changes:
- Introduces bet state + wager click handling in shared
gameState, plus HUD and slot UI/FX to reflect active bets and outcomes. - Refactors slot landing handling into smaller helpers, centralizes
tierForScore, and extracts sharedfindWorldutility. - Improves runtime checks by switching “active balls” detection to an O(1) counter and adds new win/bust audio + a full-viewport win flash effect.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/examples/src/examples/plinko-planck/gameState.ts | Adds betting state/events, wager constants, active-ball counter, and placeBetClick helper. |
| packages/examples/src/examples/plinko-planck/entities/winFlash.ts | New full-viewport win celebration renderable. |
| packages/examples/src/examples/plinko-planck/entities/util.ts | New findWorld helper to replace repeated ancestor-walks. |
| packages/examples/src/examples/plinko-planck/entities/slot.ts | Implements bet interactions/labels and refactors landing settlement into smaller helpers with win/bust visuals. |
| packages/examples/src/examples/plinko-planck/entities/hud.ts | Updates HUD hinting to include bet status and payout previews; uses new active-ball logic. |
| packages/examples/src/examples/plinko-planck/entities/dropZone.ts | Uses findWorld and new hasActiveBalls() signature for restart gating. |
| packages/examples/src/examples/plinko-planck/entities/ball.ts | Maintains gameState.activeBalls and simplifies hasActiveBalls() to O(1). |
| packages/examples/src/examples/plinko-planck/entities/bakedStatics.ts | Uses centralized tierForScore for baked slot rendering. |
| packages/examples/src/examples/plinko-planck/constants.ts | Centralizes tierForScore and co-locates new timing “feel” constants. |
| packages/examples/src/examples/plinko-planck/audio.ts | Adds bet click, bust, and win fanfare cues. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… depth Addresses Copilot review feedback on #1462: - placeBetClick: reserve at least 1 credit AFTER the click so a drop is always affordable to settle the wager. Switching slots accounts for the refund before checking the floor, so a wager-rich switch is still viable when a fresh bet wouldn't be. Fixes soft-lock when the last credit was spent on a bet that could never be settled. - audio.ts: pitchSlide is a frequency MULTIPLIER, not a delta — negative values clamp the end frequency to ~0.01 Hz. playBetClick was `-0.3` → `0.7` (30 % downward slide), playBust was `-0.8` → `0.25` (quarter-freq end for the dramatic thud). - WinFlash depth: was 150 (above HUD at 100), now 90 (below HUD) matching the original intent in the comment. Fixed both the constructor `this.depth` and the `world.addChild(..., 150)` z override at the call site. ScoreFly (200) still draws on top. - slot.ts: removed stale `hasActiveBalls(world)` / `(worldRef)` doc comments left over from the O(1) refactor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a per-drop betting layer to the plinko-planck example. Tap a scoring slot to stake credits on the prediction that the next ball will land there; wager stacks up to ×5; switching slots before settlement refunds in full (only ball-lands-elsewhere loses the wager). Win pays
slot_score × Nto score AND(base_refund + wager) × Nto credits, so winning a low-tier bet still recovers more than it cost.UI: each slot signals clickability with a breathing "TAP" hint over a dark legibility backdrop; active bet shows
BET ×N++Mpayout preview; bust paints the bet slot red; idle-locked while balls fall greys the hint out. HUD hint extends with the live score + credit payout. Win plays a layered fanfare (white-noise swoosh + triangle brass arpeggio + bell sparkle) and spawns a full-viewport gold flash with expanding shockwave rings.Code shape (senior-review pass after the feature landed)
tierForScorecentralized inconstants.ts(was duplicated betweenslot.tsandbakedStatics.ts— divergence risk on rebalance)gameState.lastBust/lastWintyped asBetEvent | null({ at, slotIndex }); impossible to half-reset vs prior parallel fieldsgameState.activeBallsinteger counter ticked inBall.onActivateEvent/onDeactivateEvent;hasActiveBalls()is now O(1) instead of O(N×slots) per framefindWorld(node)helper extracted toentities/util.ts; replaces three identical ancestor-walksSlot.collect()decomposed intosettleBet/playLandingAudio/spawnLandingEffects/spawnScoreFly/spawnCreditFly, each taking a frozenBetOutcomeSlot.update()reduced to acomputeLabelStateprojection +applyLabelStateapplier; state-machine separated from Text-mutationWinFlashextracted to its own fileconstants.tsplaceBetClickreturnsnumber | null(the new wager); call site drops?? 1fallback and non-null assertionAlso: HUD subtitle →
// www.melonjs.org.Test plan
BET ×2appears with breathing outline, credits decrement, click cue plays