// Pre-loaded SVG content cache let blackStoneSvg = null; let whiteStoneSvg = null; // 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); } catch (_) { return; } if (msg.type !== 'animation') { return; } // 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); } });