"use client" import React, { useEffect, useState } from "react"; import anime from 'animejs/lib/anime.es.js'; const cellSize = 32; // Size of each cell in the SVG const offset = 0.15 * cellSize; // Offset for the outline const findClusters = (shape: [number, number], walls: [number, number][]) => { const [width, height] = shape; const clusters: [number, number][][] = []; const visited = new Set(); const directions = [ [0, 1], [1, 0], [0, -1], [-1, 0] ]; const inBounds = (x: number, y: number) => x >= 0 && x < width && y >= 0 && y < height; const dfs = (x: number, y: number, cluster: [number, number][]) => { if (!inBounds(x, y) || visited.has(`${x},${y}`) || !walls.some(([bx, by]) => bx === x && by === y)) { return; } visited.add(`${x},${y}`); cluster.push([x, y]); for (const [dx, dy] of directions) { dfs(x + dx, y + dy, cluster); } }; for (const [x, y] of walls) { if (!visited.has(`${x},${y}`)) { const cluster: [number, number][] = []; dfs(x, y, cluster); clusters.push(cluster); } } return clusters; }; // Generate the path for a single cluster using a perimeter tracing algorithm with inner and outer boundaries const createPath = (cluster: [number, number][]) => { const pathCommands: string[] = []; const visitedCorners = new Set(); const startPoint = cluster[0]; let [x, y] = startPoint; let corner = 0; const move = () => { switch (corner) { case 0: // check if there is a wall to the left if (cluster.some(([bx, by]) => bx === x - 1 && by === y)) { corner = 1; x -= 1; } else { corner = 2; } break; case 1: // check if there is a wall to the top if (cluster.some(([bx, by]) => bx === x && by === y - 1)) { corner = 3; y -= 1; } else { corner = 0; } break; case 2: // check if there is a wall to the bottom if (cluster.some(([bx, by]) => bx === x && by === y + 1)) { corner = 0; y += 1; } else { corner = 3; } break; case 3: // check if there is a wall to the right if (cluster.some(([bx, by]) => bx === x + 1 && by === y)) { corner = 2; x += 1; } else { corner = 1; } break; default: break; } }; { let startCorner = 3; corner = startCorner; const px = x * cellSize + (corner % 2 === 0 ? offset : cellSize - offset); const py = y * cellSize + (corner < 2 ? offset : cellSize - offset); pathCommands.push(`M${px},${py}`); do { const node = `${x},${y},${corner}`; if (visitedCorners.has(node)) { break; } visitedCorners.add(node); const px = x * cellSize + (corner % 2 === 0 ? offset : cellSize - offset); const py = y * cellSize + (corner < 2 ? offset : cellSize - offset); pathCommands.push(`L${px} ${py}`); move() } while (!(x === startPoint[0] && y === startPoint[1] && corner === startCorner)); pathCommands.push('Z'); // Close the path }; const node = `${x},${y},${0}`; if (!visitedCorners.has(node)) { let startCorner = 0; corner = startCorner; const px = x * cellSize + (corner % 2 === 0 ? offset : cellSize - offset); const py = y * cellSize + (corner < 2 ? offset : cellSize - offset); pathCommands.push(`M${px},${py}`); do { const node = `${x},${y},${corner}`; if (visitedCorners.has(node)) { break; } visitedCorners.add(node); const px = x * cellSize + (corner % 2 === 0 ? offset : cellSize - offset); const py = y * cellSize + (corner < 2 ? offset : cellSize - offset); pathCommands.push(`L${px},${py}`); move() } while (!(x === startPoint[0] && y === startPoint[1] && corner === startCorner)); pathCommands.push('Z'); // Close the path }; //console.log(pathCommands, cluster); return pathCommands.join(' '); }; function Bot({ position, color, width }: { position: [number, number], color: string, width: number }) { const leftSide = position[0] < width / 2; const inHomezone = () => { switch (color) { case "blue": return leftSide; case "red": return !leftSide; } } useEffect(() => { anime.timeline() .add({ targets: '#mazebox .blue', opacity: [0, 1], easing: 'linear', duration: 2000, }, 3500) .add({ targets: '#mazebox .red', opacity: [0, 1], easing: 'linear', duration: 2000, }, 3500);; }, []); return ( ) } function Maze({ game_uuid, shape, walls, food, a, b, x, y, whowins, gameover }: { game_uuid: string, shape: [number, number], walls: [number, number][], food: [number, number][], a: [number, number], b: [number, number], x: [number, number], y: [number, number], whowins: number | null, gameover: boolean } ) { const [width, height] = shape; const clusters = findClusters(shape, walls); useEffect(() => { if (game_uuid) { let pathAnimation = anime.timeline() .add({ targets: '#mazebox #maze path', strokeDashoffset: [anime.setDashoffset, 0], easing: 'easeInCubic', duration: 2000, delay: function (el, i) { return i * 25 }, direction: 'alternate', loop: false }) .add({ targets: '#mazebox #maze path', fill: ['rgb(214, 219, 220)', "#faa"], // ffa easing: 'linear', duration: 2000 }, 2000) .add({ targets: '#mazebox .foodblue', opacity: [0, 1], easing: 'linear', duration: 2000, }, 3000) .add({ targets: '#mazebox .foodred', opacity: [0, 1], easing: 'linear', duration: 2000, }, 3000) .add({ targets: '#mazebox .blue', opacity: [0, 1], easing: 'linear', duration: 2000, }, 3500) .add({ targets: '#mazebox .red', opacity: [0, 1], easing: 'linear', duration: 2000, }, 3500); } }, [walls]); useEffect(() => { console.log(gameover, whowins); if (gameover && whowins === 0) { let pathAnimation = anime.timeline() .add({ targets: '#mazebox #maze path', fill: ["#faa", "rgb(94, 158, 217)"], easing: 'linear', duration: 200 }); }; if (gameover && whowins === 1) { let pathAnimation = anime.timeline() .add({ targets: '#mazebox #maze path', fill: ["#faa", "rgb(235, 90, 90)"], easing: 'linear', duration: 200 }); }; if (gameover && whowins === 2) { let pathAnimation = anime.timeline() .add({ targets: '#mazebox #maze path', fill: ["#faa", "#fff"], easing: 'linear', duration: 200 }); }; }, [gameover, whowins]); return (
{walls.map(([x, y], index) => ( ))} {clusters.map((cluster, index) => ( ))} {food.map(([x, y], index) => ( ))} { gameover ? ( GAME OVER ) : null }
); }; export default Maze;