diff --git a/public/scripts/make-animations.js b/public/scripts/make-animations.js index 9cfd96e..008ec97 100755 --- a/public/scripts/make-animations.js +++ b/public/scripts/make-animations.js @@ -1,9 +1,214 @@ -const animations = {}; -// animations['black-victory'] = TODO: implement black victory animation -// animations['white-victory'] = TODO: implement white victory animation -// animations['draw'] = TODO: implement draw animation +// Pre-loaded SVG content cache +let blackStoneSvg = null; +let whiteStoneSvg = null; -document.addEventListener('htmx:wsAfterMessage', function (e) { +// Pre-load SVG content when the script loads +async function preloadStoneSvgs() { + try { + // Fetch black stone SVG + const blackResponse = await fetch('/black-stone.svg'); + if (blackResponse.ok) { + blackStoneSvg = await blackResponse.text(); + } else { + console.error('Failed to load black stone SVG:', blackResponse.status); + } + + // Fetch white stone SVG + const whiteResponse = await fetch('/white-stone.svg'); + if (whiteResponse.ok) { + whiteStoneSvg = await whiteResponse.text(); + } else { + console.error('Failed to load white stone SVG:', whiteResponse.status); + } + + console.log('Stone SVGs pre-loaded successfully'); + } catch (error) { + console.error('Error pre-loading stone SVGs:', error); + } +} + +// Pre-load the SVGs immediately +preloadStoneSvgs(); + +// Create bouncing stone animation without anime.js +async function createBouncingStonesAnimation(color, count = 20) { + const gameBoard = document.getElementById('game-board'); + if (!gameBoard) return; + + // Clear any existing animation elements + const existingAnimations = gameBoard.querySelectorAll('.animation-stone'); + existingAnimations.forEach((stone) => stone.remove()); + + const boardRect = gameBoard.getBoundingClientRect(); + const stones = []; + + // Create stone elements with pre-loaded SVG content + function createStones() { + // Get the appropriate pre-loaded SVG content + const svgContent = color === 'black' ? blackStoneSvg : whiteStoneSvg; + + if (!svgContent) { + console.error(`No pre-loaded SVG content available for ${color} stone`); + return; + } + + for (let i = 0; i < count; i++) { + const stone = document.createElement('div'); + stone.className = 'animation-stone'; + stone.style.position = 'absolute'; + stone.style.width = '30px'; + stone.style.height = '30px'; + stone.style.pointerEvents = 'none'; + stone.style.zIndex = '1000'; + stone.style.opacity = '1'; + + // Set the pre-loaded SVG content as innerHTML + stone.innerHTML = svgContent; + + // Make sure the SVG inside fills the container + const svg = stone.querySelector('svg'); + if (svg) { + svg.style.width = '100%'; + svg.style.height = '100%'; + } + + // Random starting position within the board + const startX = Math.random() * (boardRect.width - 30); + const startY = Math.random() * (boardRect.height - 30); + + stone.style.left = startX + 'px'; + stone.style.top = startY + 'px'; + + gameBoard.appendChild(stone); + stones.push({ + element: stone, + x: startX, + y: startY, + vx: (Math.random() - 0.5) * 8, // Random velocity between -4 and 4 + vy: (Math.random() - 0.5) * 8, + }); + } + } + + // Create the stones (no need to wait since we're using pre-loaded content) + createStones(); + + // Animation state + let animationId; + let startTime; + let isPlaying = false; + + // Animation function + function animateStones() { + stones.forEach((stone) => { + // Update position + stone.x += stone.vx; + stone.y += stone.vy; + + // Bounce off edges + if (stone.x <= 0 || stone.x >= boardRect.width - 30) { + stone.vx = -stone.vx; + stone.x = Math.max(0, Math.min(boardRect.width - 30, stone.x)); + } + if (stone.y <= 0 || stone.y >= boardRect.height - 30) { + stone.vy = -stone.vy; + stone.y = Math.max(0, Math.min(boardRect.height - 30, stone.y)); + } + + // Apply position + stone.element.style.left = stone.x + 'px'; + stone.element.style.top = stone.y + 'px'; + }); + } + + // Fade out function + function fadeOut() { + const animationStones = document.querySelectorAll('.animation-stone'); + const fadeStartTime = performance.now(); + const fadeDuration = 15 * 100; // 100 = 1sec + + function fade(currentTime) { + const elapsed = currentTime - fadeStartTime; + const progress = Math.min(elapsed / fadeDuration, 1); + const opacity = 1 - progress; + + animationStones.forEach((stone) => { + stone.style.opacity = opacity; + }); + + if (progress < 1) { + requestAnimationFrame(fade); + } else { + // Remove stones after fade is complete + animationStones.forEach((stone) => stone.remove()); + } + } + + requestAnimationFrame(fade); + } + + // Main animation loop + function animate(currentTime) { + if (!startTime) startTime = currentTime; + const elapsed = currentTime - startTime; + + if (elapsed < 5000) { + animateStones(); + animationId = requestAnimationFrame(animate); + } else { + // Animation complete - start fade out + fadeOut(); + } + } + + // Animation control object + const animationControl = { + play: function () { + if (!isPlaying) { + isPlaying = true; + startTime = null; // Reset start time + animationId = requestAnimationFrame(animate); + } + }, + pause: function () { + if (animationId) { + cancelAnimationFrame(animationId); + isPlaying = false; + } + }, + stop: function () { + if (animationId) { + cancelAnimationFrame(animationId); + isPlaying = false; + } + // Remove all stones immediately + const animationStones = document.querySelectorAll('.animation-stone'); + animationStones.forEach((stone) => stone.remove()); + }, + }; + + return animationControl; +} + +const animations = {}; + +// Define the three animations +animations['black-victory'] = () => { + return createBouncingStonesAnimation('black', 40); +}; + +animations['white-victory'] = () => { + return createBouncingStonesAnimation('white', 40); +}; + +animations['draw'] = () => { + // For draw, create 10 black and 10 white stones + const blackAnimation = createBouncingStonesAnimation('black', 20); + const whiteAnimation = createBouncingStonesAnimation('white', 20); + return [blackAnimation, whiteAnimation]; +}; + +document.addEventListener('htmx:wsAfterMessage', async function (e) { let msg; try { msg = JSON.parse(e.detail.message); @@ -13,6 +218,20 @@ document.addEventListener('htmx:wsAfterMessage', function (e) { if (msg.type !== 'animation') { return; } - // TODO: implement animation playback - console.log('Animation requested:', msg.animation); + + // Play the requested animation + const animationFn = animations[msg.animation]; + if (animationFn) { + console.log('Playing animation:', msg.animation); + const result = await animationFn(); + + // Handle both single animations and arrays of animations (for draw) + if (Array.isArray(result)) { + result.forEach((animation) => animation.play()); + } else if (result) { + result.play(); + } + } else { + console.log('Unknown animation:', msg.animation); + } });