Spaces:
Runtime error
Runtime error
| import { Container, Graphics, Ticker } from 'pixi.js' | |
| import { CELL_SIZE } from 'shared/board.ts' | |
| import { theme } from '../../theme.ts' | |
| import { ParticleSystem } from './ParticleSystem.ts' | |
| import type { CameraController } from '../CameraController.ts' | |
| const ticker = Ticker.shared | |
| export class WinEffect { | |
| private effectLayer: Container | |
| private particleSys: ParticleSystem | |
| private camera: CameraController | |
| private vw: number | |
| private vh: number | |
| private screenLayer: Container | |
| private flashGraphics: Graphics | |
| private highlightGraphics: Graphics | null = null | |
| constructor(effectLayer: Container, particleSys: ParticleSystem, camera: CameraController, vw: number, vh: number, screenLayer: Container) { | |
| this.effectLayer = effectLayer | |
| this.particleSys = particleSys | |
| this.camera = camera | |
| this.vw = vw | |
| this.vh = vh | |
| this.screenLayer = screenLayer | |
| this.flashGraphics = new Graphics() | |
| } | |
| clear(): void { | |
| if (this.highlightGraphics) { | |
| this.effectLayer.removeChild(this.highlightGraphics) | |
| this.highlightGraphics.destroy() | |
| this.highlightGraphics = null | |
| } | |
| } | |
| play(cells: { x: number; y: number }[]): void { | |
| if (cells.length === 0) return | |
| this.clear() | |
| this.cameraFocus(cells) | |
| this.highlightLine(cells) | |
| this.emitGold(cells) | |
| this.screenFlash() | |
| } | |
| private cameraFocus(cells: { x: number; y: number }[]): void { | |
| let sumX = 0, sumY = 0 | |
| for (const c of cells) { sumX += c.x; sumY += c.y } | |
| const targetX = (sumX / cells.length) * CELL_SIZE + CELL_SIZE / 2 | |
| const targetY = (sumY / cells.length) * CELL_SIZE + CELL_SIZE / 2 | |
| const startX = this.camera.state.x | |
| const startY = this.camera.state.y | |
| let elapsed = 0 | |
| const dur = 0.3 | |
| let cb: () => void | |
| const tick = (dt: number) => { | |
| elapsed += dt | |
| const t = Math.min(1, elapsed / dur) | |
| this.camera.state.x = startX + (targetX - startX) * t | |
| this.camera.state.y = startY + (targetY - startY) * t | |
| this.camera.apply() | |
| this.camera.triggerChange() | |
| if (t >= 1) ticker.remove(cb) | |
| } | |
| if (ticker) { | |
| cb = () => tick(ticker.deltaMS / 1000) | |
| ticker.add(cb) | |
| } | |
| } | |
| private highlightLine(cells: { x: number; y: number }[]): void { | |
| const g = new Graphics() | |
| this.highlightGraphics = g | |
| this.effectLayer.addChild(g) | |
| for (const c of cells) { | |
| const lx = c.x * CELL_SIZE + CELL_SIZE / 2 | |
| const ly = c.y * CELL_SIZE + CELL_SIZE / 2 | |
| g.circle(lx, ly, CELL_SIZE * 0.5) | |
| } | |
| g.stroke({ width: 3, color: theme.winLineColor }) | |
| g.fill({ color: theme.winLineColor, alpha: 0.15 }) | |
| let elapsed = 0 | |
| const dur = 0.8 | |
| let cb: () => void | |
| const tick = (dt: number) => { | |
| elapsed += dt | |
| const t = Math.min(1, elapsed / dur) | |
| const pulse = 0.5 + 0.5 * Math.sin(t * Math.PI * 4) | |
| g.alpha = 0.4 + 0.6 * pulse | |
| if (t >= 1) { | |
| g.alpha = 1 | |
| ticker.remove(cb) | |
| } | |
| } | |
| if (ticker) { | |
| cb = () => tick(ticker.deltaMS / 1000) | |
| ticker.add(cb) | |
| } | |
| } | |
| private emitGold(cells: { x: number; y: number }[]): void { | |
| for (const c of cells) { | |
| const lx = c.x * CELL_SIZE + CELL_SIZE / 2 | |
| const ly = c.y * CELL_SIZE + CELL_SIZE / 2 | |
| this.particleSys.emit({ | |
| count: 12, | |
| x: lx, | |
| y: ly, | |
| color: theme.particleGold, | |
| spread: 120, | |
| speed: 150, | |
| size: 3, | |
| life: 0.8, | |
| }) | |
| this.particleSys.emit({ | |
| count: 4, | |
| x: lx, | |
| y: ly, | |
| color: theme.particleSpark, | |
| spread: 80, | |
| speed: 200, | |
| size: 1.5, | |
| life: 0.5, | |
| }) | |
| } | |
| } | |
| private screenFlash(): void { | |
| this.flashGraphics.clear() | |
| this.flashGraphics.rect(0, 0, this.vw, this.vh) | |
| this.flashGraphics.fill({ color: theme.screenFlash, alpha: 0.4 }) | |
| this.screenLayer.addChild(this.flashGraphics) | |
| let elapsed = 0 | |
| const dur = 0.5 | |
| let cb: () => void | |
| const tick = (dt: number) => { | |
| elapsed += dt | |
| const t = Math.min(1, elapsed / dur) | |
| this.flashGraphics.alpha = 0.4 * (1 - t) | |
| if (t >= 1) { | |
| this.screenLayer.removeChild(this.flashGraphics) | |
| this.flashGraphics.clear() | |
| ticker.remove(cb) | |
| } | |
| } | |
| if (ticker) { | |
| cb = () => tick(ticker.deltaMS / 1000) | |
| ticker.add(cb) | |
| } | |
| } | |
| } | |