[WIP] Enhance gameplay with bonuses and special effects#52
Conversation
✅ Deploy Preview for gulfnexus ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
…tore - Replace emoji gems with high-quality star graphics from IMG_* assets - Add combo multiplier system (up to 10x) that increases with chain reactions - Implement particle explosion effects for matched gems - Add animated combo banners (2x, 5x MEGA, 8x SUPER) - Create in-game store with power-up purchases (bomb, lightning, rainbow) - Implement coin currency system (earn coins from combos and level-ups) - Add visual feedback with pulsing animations and glow effects - Improve cell styling with gradients and hover effects - Add comprehensive CSS animations for explosions and particles Agent-Logs-Url: https://github.com/NicholaiMadias/gamifiedlearning.github.io/sessions/fe6719f2-2b16-430d-83cd-e116b603634a Co-authored-by: NicholaiMadias <73684379+NicholaiMadias@users.noreply.github.com>
- Implement bomb power-up (clears 3x3 area) - Implement lightning power-up (clears entire row and column) - Implement rainbow power-up (clears all gems of selected type) - Add power-up activation buttons with visual feedback - Add pulsing glow animation for active power-ups - Power-ups award bonus points (15 per gem) and coins - Add instructional banner when power-up is activated - Integrate special gem effects with chain reaction system Agent-Logs-Url: https://github.com/NicholaiMadias/gamifiedlearning.github.io/sessions/fe6719f2-2b16-430d-83cd-e116b603634a Co-authored-by: NicholaiMadias <73684379+NicholaiMadias@users.noreply.github.com>
- Add crosshair cursor when power-up is active for better UX - Store gameplay mechanics memory for future reference - Store graphics/assets system memory - Polish power-up activation visual feedback Agent-Logs-Url: https://github.com/NicholaiMadias/gamifiedlearning.github.io/sessions/fe6719f2-2b16-430d-83cd-e116b603634a Co-authored-by: NicholaiMadias <73684379+NicholaiMadias@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This WIP PR enhances the Match Maker minigame by adding combo-based scoring, purchasable/usable power-ups (bomb/lightning/rainbow), and updated visuals/animations using the image assets in the repo.
Changes:
- Add special-gem effect logic (bomb 3×3, lightning row+col, rainbow clear-by-type) to the game state module.
- Extend the UI with combo tracking, coin economy, a store panel, and power-up activation/consumption.
- Update rendering/CSS to use image-based gems plus explosion + particle effects and new HUD elements (combo/coins).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| matchMakerState.js | Introduces SPECIAL_GEM_TYPES and applySpecialGem() to clear cells according to power-up behavior. |
| match-maker-ui.js | Implements combo scoring, coins/store, power-up usage flow, and match animations/particles. |
| index.html | Adds combo/coins UI, power-up buttons, store markup, and styling/animations for the new effects. |
| // Animate matched cells | ||
| animateMatches(matches); | ||
|
|
||
| grid = clearMatches(grid, matches); | ||
| grid = applyGravity(grid); | ||
| renderGrid(); | ||
|
|
||
| // chain reactions | ||
| setTimeout(resolveMatches, CHAIN_REACTION_DELAY_MS); |
There was a problem hiding this comment.
animateMatches() adds explosion/particle effects, but resolveMatches() immediately calls renderGrid(), whose container.innerHTML = '' removes the animated nodes (and any particles) before the effects can be seen. To make the special effects visible, delay clearing/applying gravity + re-render until after the animation duration, or render into separate layers (cells vs. effects) so effects aren’t wiped by innerHTML.
| function createParticles(cell) { | ||
| const rect = cell.getBoundingClientRect(); | ||
| const container = document.getElementById('match-grid'); | ||
| const containerRect = container.getBoundingClientRect(); | ||
|
|
||
| for (let i = 0; i < 8; i++) { | ||
| const particle = document.createElement('div'); | ||
| particle.className = 'particle'; | ||
|
|
||
| const angle = (Math.PI * 2 * i) / 8; | ||
| const distance = 30 + Math.random() * 20; | ||
| const dx = Math.cos(angle) * distance; | ||
| const dy = Math.sin(angle) * distance; | ||
|
|
||
| particle.style.left = (rect.left - containerRect.left + rect.width / 2) + 'px'; | ||
| particle.style.top = (rect.top - containerRect.top + rect.height / 2) + 'px'; | ||
| particle.style.setProperty('--dx', dx + 'px'); | ||
| particle.style.setProperty('--dy', dy + 'px'); | ||
|
|
||
| container.appendChild(particle); | ||
|
|
||
| setTimeout(() => particle.remove(), 600); | ||
| } |
There was a problem hiding this comment.
createParticles creates 8 DOM nodes per cleared cell and runs every chain reaction step; a large match (or repeated cascades) can generate hundreds of elements and timeouts quickly, causing jank on low-end devices. Consider capping particles per resolution, basing count on match size, or pooling/reusing particle elements instead of creating/removing many nodes each time.
| <div class="store-item" id="store-bomb" onclick="purchasePowerUp('bomb', 30)"> | ||
| <div class="store-item-icon">💣</div> | ||
| <div class="store-item-name">Bomb</div> | ||
| <div class="store-item-cost">30 coins</div> | ||
| <div class="store-item-owned">Owned: <span id="store-bomb-count">0</span></div> | ||
| </div> | ||
| <div class="store-item" id="store-lightning" onclick="purchasePowerUp('lightning', 50)"> | ||
| <div class="store-item-icon">⚡</div> | ||
| <div class="store-item-name">Lightning</div> | ||
| <div class="store-item-cost">50 coins</div> | ||
| <div class="store-item-owned">Owned: <span id="store-lightning-count">0</span></div> | ||
| </div> | ||
| <div class="store-item" id="store-rainbow" onclick="purchasePowerUp('rainbow', 80)"> | ||
| <div class="store-item-icon">🌈</div> | ||
| <div class="store-item-name">Rainbow</div> | ||
| <div class="store-item-cost">80 coins</div> | ||
| <div class="store-item-owned">Owned: <span id="store-rainbow-count">0</span></div> | ||
| </div> |
There was a problem hiding this comment.
The store items are clickable <div>s with onclick, which are not keyboard-focusable and won’t be operable by keyboard/screen readers by default. Consider using <button> elements, or add role="button", tabindex="0", and key handlers (Enter/Space) so the store is accessible.
| <div class="store-item" id="store-bomb" onclick="purchasePowerUp('bomb', 30)"> | |
| <div class="store-item-icon">💣</div> | |
| <div class="store-item-name">Bomb</div> | |
| <div class="store-item-cost">30 coins</div> | |
| <div class="store-item-owned">Owned: <span id="store-bomb-count">0</span></div> | |
| </div> | |
| <div class="store-item" id="store-lightning" onclick="purchasePowerUp('lightning', 50)"> | |
| <div class="store-item-icon">⚡</div> | |
| <div class="store-item-name">Lightning</div> | |
| <div class="store-item-cost">50 coins</div> | |
| <div class="store-item-owned">Owned: <span id="store-lightning-count">0</span></div> | |
| </div> | |
| <div class="store-item" id="store-rainbow" onclick="purchasePowerUp('rainbow', 80)"> | |
| <div class="store-item-icon">🌈</div> | |
| <div class="store-item-name">Rainbow</div> | |
| <div class="store-item-cost">80 coins</div> | |
| <div class="store-item-owned">Owned: <span id="store-rainbow-count">0</span></div> | |
| </div> | |
| <button type="button" class="store-item" id="store-bomb" onclick="purchasePowerUp('bomb', 30)"> | |
| <div class="store-item-icon">💣</div> | |
| <div class="store-item-name">Bomb</div> | |
| <div class="store-item-cost">30 coins</div> | |
| <div class="store-item-owned">Owned: <span id="store-bomb-count">0</span></div> | |
| </button> | |
| <button type="button" class="store-item" id="store-lightning" onclick="purchasePowerUp('lightning', 50)"> | |
| <div class="store-item-icon">⚡</div> | |
| <div class="store-item-name">Lightning</div> | |
| <div class="store-item-cost">50 coins</div> | |
| <div class="store-item-owned">Owned: <span id="store-lightning-count">0</span></div> | |
| </button> | |
| <button type="button" class="store-item" id="store-rainbow" onclick="purchasePowerUp('rainbow', 80)"> | |
| <div class="store-item-icon">🌈</div> | |
| <div class="store-item-name">Rainbow</div> | |
| <div class="store-item-cost">80 coins</div> | |
| <div class="store-item-owned">Owned: <span id="store-rainbow-count">0</span></div> | |
| </button> |
| export function initMatchMaker(dbRef, userRef) { | ||
| db = dbRef; | ||
| user = userRef; | ||
| score = 0; | ||
| moves = 20; | ||
| level = 1; | ||
| combo = 0; | ||
| activePowerUp = null; | ||
| selected = null; | ||
| grid = createInitialGrid(); | ||
| renderGrid(); | ||
| updateStats(); | ||
| updateStoreDisplay(); | ||
| updatePowerUpButtons(); | ||
| } |
There was a problem hiding this comment.
initMatchMaker resets score/moves/level but does not reset coins or powerUps, even though coins is documented as “Starting coins”. Hitting Restart will carry over store state unexpectedly and can desync the initial UI values. Consider resetting coins/powerUps here (or update the comment/UI if persistence is intended).
| renderGrid(); | ||
|
|
||
| // chain reactions | ||
| setTimeout(resolveMatches, CHAIN_REACTION_DELAY_MS); | ||
| } |
There was a problem hiding this comment.
There’s no cancellation/guard for the recursive setTimeout(resolveMatches, ...) chain reaction. If the player hits Restart while a timeout is pending, the old callback can fire against the new game state and start multiple concurrent resolve loops. Track the timeout id (and/or a generation token) and clear/ignore stale callbacks in initMatchMaker/restart.
Agent-Logs-Url: https://github.com/NicholaiMadias/gamifiedlearning.github.io/sessions/2f862e85-45e6-4d6d-92c4-3d85e803f54a Co-authored-by: NicholaiMadias <73684379+NicholaiMadias@users.noreply.github.com>

Thanks for asking me to work on this. I will get started on it and keep this PR's description up to date as I form a plan and make progress.