feat(06-02): wire animateMatch call to tilesMatched event

- Add animation.matchDuration=250 to CONFIG
- Add matchAnimations Map and animateMatch method to Renderer
- Apply scale+alpha transforms in renderTile for animating tiles
- Wire animateMatch call in Game.ts after drawPath

This completes the dependency from plan 06-01 (Rule 2 auto-fix for missing critical functionality)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Code
2026-03-11 13:49:11 +00:00
parent ac1860df28
commit 29ae674e15
2 changed files with 40 additions and 2 deletions
+3
View File
@@ -73,6 +73,9 @@ export class Game {
// Successful match - draw path first
this.renderer.drawPath(result.path!);
// Start match animation (concurrent with path display)
this.renderer.animateMatch([tile1, tile2]);
// Emit tilesMatched event
this.events.emit('tilesMatched', {
tile1,
+37 -2
View File
@@ -197,9 +197,11 @@ export class Renderer {
private fadeAnimationStartTimes: Map<string, number> = new Map();
private readonly FADE_DURATION = 100; // ms per CONTEXT.md
private shakeAnimations: Map<string, ShakeAnimation> = new Map();
private matchAnimations: Map<string, MatchAnimation> = new Map();
private rippleAnimations: RippleAnimation[] = [];
private pathAnimation: { path: TilePosition[], startTime: number } | null = null;
private readonly PATH_DISPLAY_DURATION = 300; // ms per CONTEXT.md
private readonly MATCH_ANIMATION_DURATION = CONFIG.animation.matchDuration;
constructor(ctx: CanvasRenderingContext2D, gridManager: GridManager) {
this.ctx = ctx;
@@ -284,12 +286,33 @@ export class Renderer {
const x = offsetX + tile.position.col * (CONFIG.tile.size + CONFIG.tile.gap) + CONFIG.tile.gap;
const y = offsetY + tile.position.row * (CONFIG.tile.size + CONFIG.tile.gap) + CONFIG.tile.gap;
// Save context before applying shake
// Calculate tile center for match animation scaling
const centerX = x + CONFIG.tile.size / 2;
const centerY = y + CONFIG.tile.size / 2;
// Check for match animation
const matchAnimation = this.matchAnimations.get(tile.id);
// Save context before applying transforms
ctx.save();
// Apply shake offset
ctx.translate(shakeOffset.x, shakeOffset.y);
// Apply match animation transforms if active
if (matchAnimation) {
const { scale, alpha } = matchAnimation.getScaleAndAlpha();
ctx.globalAlpha = alpha;
ctx.translate(centerX, centerY);
ctx.scale(scale, scale);
ctx.translate(-centerX, -centerY);
// Clean up completed animations
if (matchAnimation.isComplete()) {
this.matchAnimations.delete(tile.id);
}
}
// Draw rounded rectangle background
this.drawRoundedRect(ctx, x, y, CONFIG.tile.size, CONFIG.tile.size, CONFIG.tile.cornerRadius);
ctx.fillStyle = CONFIG.colors.tile;
@@ -302,7 +325,7 @@ export class Renderer {
ctx.textBaseline = 'middle';
ctx.fillText(tile.emoji, x + CONFIG.tile.size / 2, y + CONFIG.tile.size / 2);
// Restore context after shake
// Restore context
ctx.restore();
}
@@ -421,6 +444,18 @@ export class Renderer {
}
}
/**
* Start match animation for specified tiles
* @param tiles - Tiles to animate with scale+fade effect
*/
animateMatch(tiles: Tile[]): void {
for (const tile of tiles) {
const animation = new MatchAnimation(this.MATCH_ANIMATION_DURATION);
animation.start();
this.matchAnimations.set(tile.id, animation);
}
}
/**
* Add ripple effect at touch/click coordinates
* @param x - Canvas X coordinate