Add win/loss/draw animations
This commit is contained in:
parent
b0a759dd26
commit
eceefcc19c
|
@ -1,9 +1,214 @@
|
||||||
const animations = {};
|
// Pre-loaded SVG content cache
|
||||||
// animations['black-victory'] = TODO: implement black victory animation
|
let blackStoneSvg = null;
|
||||||
// animations['white-victory'] = TODO: implement white victory animation
|
let whiteStoneSvg = null;
|
||||||
// animations['draw'] = TODO: implement draw animation
|
|
||||||
|
|
||||||
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;
|
let msg;
|
||||||
try {
|
try {
|
||||||
msg = JSON.parse(e.detail.message);
|
msg = JSON.parse(e.detail.message);
|
||||||
|
@ -13,6 +218,20 @@ document.addEventListener('htmx:wsAfterMessage', function (e) {
|
||||||
if (msg.type !== 'animation') {
|
if (msg.type !== 'animation') {
|
||||||
return;
|
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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue