Commit ae9d5065 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(juice): chess capture explosions, ludo dice slam + star burst on 6

Chess juice:
- Capture: particle burst at capture square + board shake + haptic
- Check: board shake + haptic medium
- Checkmate: heavy shake + screen flash + haptic heavy

Ludo juice:
- Dice roll: slam-in with 1.6x overshoot + board shake + haptic
- Roll 6: golden star burst around dice
- Capture: confetti + heavy shake + haptic heavy
- Piece finish: star burst + haptic success
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent ee14c2cd
...@@ -7,6 +7,7 @@ import { t } from '../../../core/i18n.js'; ...@@ -7,6 +7,7 @@ import { t } from '../../../core/i18n.js';
import { ChessBoard } from '../canvas/board.js'; import { ChessBoard } from '../canvas/board.js';
import * as engine from '../logic/engine.js'; import * as engine from '../logic/engine.js';
import { ChessClock, parseTimeControl } from '../logic/clock.js'; import { ChessClock, parseTimeControl } from '../logic/clock.js';
import * as juice from '../../../core/juice.js';
let board, clock, gameState; let board, clock, gameState;
...@@ -193,17 +194,34 @@ function executeMove(el, from, to, promotion) { ...@@ -193,17 +194,34 @@ function executeMove(el, from, to, promotion) {
board.setPosition(engine.fen()); board.setPosition(engine.fen());
board.setLastMove(from, to); board.setLastMove(from, to);
// Sound // Sound + Juice
if (m.san.includes('#')) audio.play('gameOver', 'game'); if (m.san.includes('#')) {
else if (m.san.includes('+')) audio.play('check', 'game'); audio.play('gameOver', 'game');
else if (m.captured) audio.play('capture', 'game'); juice.shake(el, 6, 400);
else if (m.san === 'O-O' || m.san === 'O-O-O') audio.play('castle', 'game'); juice.flash('#fff', 200);
else audio.play('move', 'game'); juice.hapticHeavy();
} else if (m.san.includes('+')) {
audio.play('check', 'game');
juice.shake(el, 3, 200);
juice.hapticMedium();
} else if (m.captured) {
audio.play('capture', 'game');
juice.shake(el, 2, 150);
juice.hapticLight();
// Particle burst at capture square
const captureXY = board.squareToXY(to);
const boardRect = board.canvas.getBoundingClientRect();
juice.burst(boardRect.left + captureXY.x + board.squareSize/2, boardRect.top + captureXY.y + board.squareSize/2,
{ count: 8, colors: ['#EF4444', '#F97316', '#FBBF24'], size: 6, spread: 60, duration: 500 });
} else if (m.san === 'O-O' || m.san === 'O-O-O') {
audio.play('castle', 'game');
juice.hapticLight();
} else {
audio.play('move', 'game');
}
// Captured pieces // Captured pieces
if (m.captured) { if (m.captured) {
const capturedPiece = m.color === gameState.playerColor
? m.captured : m.captured.toUpperCase ? m.captured : m.captured;
if (m.color === gameState.playerColor) { if (m.color === gameState.playerColor) {
gameState.capturedByPlayer.push(m.captured); gameState.capturedByPlayer.push(m.captured);
} else { } else {
......
...@@ -5,6 +5,7 @@ import * as store from '../../../core/store.js'; ...@@ -5,6 +5,7 @@ import * as store from '../../../core/store.js';
import { t } from '../../../core/i18n.js'; import { t } from '../../../core/i18n.js';
import { createCanvas, clear } from '../../../core/canvas.js'; import { createCanvas, clear } from '../../../core/canvas.js';
import * as rules from '../logic/rules.js'; import * as rules from '../logic/rules.js';
import * as juice from '../../../core/juice.js';
let game, validMoves, canvasCtx, canvasEl, boardSize; let game, validMoves, canvasCtx, canvasEl, boardSize;
...@@ -57,10 +58,16 @@ function handleRoll(el) { ...@@ -57,10 +58,16 @@ function handleRoll(el) {
game.diceValue = dice; game.diceValue = dice;
game.rolled = true; game.rolled = true;
audio.play('dice', 'game'); audio.play('dice', 'game');
juice.hapticMedium();
const diceEl = el.querySelector('#dice-display'); const diceEl = el.querySelector('#dice-display');
diceEl.textContent = dice; diceEl.textContent = dice;
diceEl.style.color = '#E4AC38'; diceEl.style.color = '#E4AC38';
juice.slamIn(diceEl, { scale: 1.6, duration: 350 });
juice.shake(el.querySelector('#ludo-board'), 2, 150);
if (dice === 6) {
juice.starBurst(diceEl.getBoundingClientRect().left + 28, diceEl.getBoundingClientRect().top + 28, 5);
}
validMoves = rules.getValidMoves(game, 0, dice); validMoves = rules.getValidMoves(game, 0, dice);
...@@ -93,7 +100,18 @@ function handleRoll(el) { ...@@ -93,7 +100,18 @@ function handleRoll(el) {
function applyPlayerMove(el, move) { function applyPlayerMove(el, move) {
rules.applyMove(game, 0, move); rules.applyMove(game, 0, move);
game.rolled = false; game.rolled = false;
audio.play(move.type === 'capture' ? 'capture' : 'move', 'game'); if (move.type === 'capture') {
audio.play('capture', 'game');
juice.shake(el, 4, 250);
juice.hapticHeavy();
juice.confetti(window.innerWidth / 2, window.innerHeight / 2, 15);
} else if (move.type === 'finish') {
audio.play('win', 'reward');
juice.starBurst(window.innerWidth / 2, window.innerHeight / 2, 8);
juice.hapticSuccess();
} else {
audio.play('move', 'game');
}
if (game.gameOver || game.players[0].finished) { endGame(el); return; } if (game.gameOver || game.players[0].finished) { endGame(el); return; }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment