Skip to content

feat(examples): plinko betting — slot prediction with multiplier#1462

Merged
obiot merged 2 commits into
masterfrom
feat/plinko-betting
May 24, 2026
Merged

feat(examples): plinko betting — slot prediction with multiplier#1462
obiot merged 2 commits into
masterfrom
feat/plinko-betting

Conversation

@obiot
Copy link
Copy Markdown
Member

@obiot obiot commented May 24, 2026

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 × 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 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 separated from Text-mutation
  • WinFlash extracted to its own file
  • All "feel" timing constants co-located in constants.ts
  • placeBetClick returns number | null (the new wager); call site drops ?? 1 fallback and non-null assertion

Also: HUD subtitle → // www.melonjs.org.

Test plan

  • Open the plinko-planck example
  • Tap a slot — BET ×2 appears with breathing outline, credits decrement, click cue plays
  • Tap the same slot again — multiplier climbs (×3, ×4, ×5), capped at ×6 display
  • Tap a different slot — prior wager refunded, new bet opens at ×2
  • Drop a ball into the bet slot — fanfare + gold flash + score + credits both multiplied
  • Drop a ball into a non-bet slot — bust flash on the bet slot, wager is lost
  • While balls are falling — TAP hints are greyed, slot clicks are no-ops
  • Run out of credits — TAP hints fade away entirely

…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>
Copilot AI review requested due to automatic review settings May 24, 2026 00:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 shared findWorld utility.
  • 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.

Comment thread packages/examples/src/examples/plinko-planck/gameState.ts Outdated
Comment thread packages/examples/src/examples/plinko-planck/entities/slot.ts Outdated
Comment thread packages/examples/src/examples/plinko-planck/entities/slot.ts Outdated
Comment thread packages/examples/src/examples/plinko-planck/audio.ts Outdated
Comment thread packages/examples/src/examples/plinko-planck/audio.ts Outdated
Comment thread packages/examples/src/examples/plinko-planck/entities/winFlash.ts Outdated
… 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>
@obiot obiot merged commit 42185a8 into master May 24, 2026
6 checks passed
@obiot obiot deleted the feat/plinko-betting branch May 24, 2026 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants