pelita-webtournament/app/maze.tsx

702 lines
21 KiB
TypeScript
Raw Normal View History

2024-07-19 18:00:43 +02:00
"use client"
import React, {
2024-08-31 11:56:27 +02:00
useEffect,
useId,
2024-08-31 11:56:27 +02:00
useMemo,
useRef,
useState
2024-07-19 18:00:43 +02:00
} from "react";
import anime from 'animejs/lib/anime.es.js';
2024-08-31 11:56:27 +02:00
import { AnimatePresence, motion } from "framer-motion"
2024-07-19 18:00:43 +02:00
2024-07-25 11:35:58 +02:00
type Pos = [number, number];
2024-08-31 11:56:27 +02:00
const cellSize = 26; // Size of each cell in the SVG
2024-07-25 11:35:58 +02:00
const offset = 0.2 * cellSize; // Offset for the outline
let radius = 0.5 * cellSize - offset;
let radius_inner = offset;
2024-07-19 18:00:43 +02:00
2024-07-25 11:35:58 +02:00
const findClusters = (shape: Pos, walls: Pos[]) => {
2024-07-19 18:00:43 +02:00
const [width, height] = shape;
2024-07-25 11:35:58 +02:00
const clusters: Pos[][] = [];
2024-07-19 18:00:43 +02:00
const visited = new Set<string>();
const directions = [
[0, 1], [1, 0], [0, -1], [-1, 0]
];
const inBounds = (x: number, y: number) => x >= 0 && x < width && y >= 0 && y < height;
2024-07-25 11:35:58 +02:00
const dfs = (x: number, y: number, cluster: Pos[]) => {
2024-07-19 18:00:43 +02:00
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}`)) {
2024-07-25 11:35:58 +02:00
const cluster: Pos[] = [];
2024-07-19 18:00:43 +02:00
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
2024-07-25 11:35:58 +02:00
const createPath = (cluster: Pos[]) => {
2024-07-19 18:00:43 +02:00
const visitedCorners = new Set<string>();
2024-07-25 11:35:58 +02:00
const visitedCells = new Set<string>();
/* Corners:
Reference points
+-4-----+-------+
| 0 7
| | |
| | |
2024-07-25 18:22:17 +02:00
+-1-----+-----3-+
2024-07-25 11:35:58 +02:00
| | |
| | |
2024-07-25 18:22:17 +02:00
5 2 |
+-------+-----6-+
2024-07-19 18:00:43 +02:00
2024-07-25 11:35:58 +02:00
*/
2024-07-25 18:22:17 +02:00
const refPoint0 = [0.5 * cellSize, offset];
const refPoint1 = [offset, 0.5 * cellSize];
const refPoint2 = [0.5 * cellSize, cellSize - offset];
const refPoint3 = [cellSize - offset, 0.5 * cellSize];
const refPoint4 = [offset, 0];
const refPoint5 = [0, cellSize - offset];
const refPoint6 = [cellSize - offset, cellSize];
const refPoint7 = [cellSize, offset];
const refPoints = [
refPoint0, refPoint1, refPoint2, refPoint3,
refPoint4, refPoint5, refPoint6, refPoint7
];
2024-07-25 11:35:58 +02:00
type PathMove = { next: [Pos, number], path: string };
2024-07-25 18:22:17 +02:00
const makeNextLine = (x: number, y: number, nextRefPoint: number): PathMove => {
const px = x * cellSize + refPoints[nextRefPoint][0];
const py = y * cellSize + refPoints[nextRefPoint][1];
return { next: [[x, y], nextRefPoint], path: `L ${px},${py}` };
}
2024-07-25 11:35:58 +02:00
2024-07-25 18:22:17 +02:00
const makeNextCurve = (x: number, y: number, nextRefPoint: number): PathMove => {
const px = x * cellSize + refPoints[nextRefPoint][0];
const py = y * cellSize + refPoints[nextRefPoint][1];
return { next: [[x, y], nextRefPoint], path: `A ${radius},${radius} 0 0 0 ${px},${py}` };
}
2024-07-19 18:00:43 +02:00
2024-07-25 18:22:17 +02:00
const makeNextCurveInner = (x: number, y: number, nextRefPoint: number): PathMove => {
const px = x * cellSize + refPoints[nextRefPoint][0];
const py = y * cellSize + refPoints[nextRefPoint][1];
return { next: [[x, y], nextRefPoint], path: `A ${radius_inner},${radius_inner} 0 0 1 ${px},${py}` };
}
const move = (x: number, y: number, corner: number): PathMove | undefined => {
2024-07-19 18:00:43 +02:00
switch (corner) {
case 0:
2024-07-25 11:35:58 +02:00
// exit if wall to the top
if (cluster.some(([bx, by]) => bx === x && by === y - 1)) return;
2024-07-19 18:00:43 +02:00
// check if there is a wall to the left
if (cluster.some(([bx, by]) => bx === x - 1 && by === y)) {
2024-07-25 18:22:17 +02:00
return makeNextLine(x - 1, y, 7);
2024-07-19 18:00:43 +02:00
} else {
2024-07-25 18:22:17 +02:00
return makeNextCurve(x, y, 1);
2024-07-19 18:00:43 +02:00
}
case 1:
2024-07-25 11:35:58 +02:00
// exit if wall to the left
if (cluster.some(([bx, by]) => bx === x - 1 && by === y)) return;
2024-07-19 18:00:43 +02:00
// check if there is a wall to the bottom
if (cluster.some(([bx, by]) => bx === x && by === y + 1)) {
2024-07-25 18:22:17 +02:00
return makeNextLine(x, y + 1, 4);
2024-07-19 18:00:43 +02:00
} else {
2024-07-25 18:22:17 +02:00
return makeNextCurve(x, y, 2);
2024-07-19 18:00:43 +02:00
}
2024-07-25 18:22:17 +02:00
case 2:
2024-07-25 11:35:58 +02:00
// exit if wall to the bottom
if (cluster.some(([bx, by]) => bx === x && by === y + 1)) return;
2024-07-19 18:00:43 +02:00
// check if there is a wall to the right
if (cluster.some(([bx, by]) => bx === x + 1 && by === y)) {
2024-07-25 18:22:17 +02:00
return makeNextLine(x + 1, y, 5);
2024-07-19 18:00:43 +02:00
} else {
2024-07-25 18:22:17 +02:00
return makeNextCurve(x, y, 3);
2024-07-19 18:00:43 +02:00
}
2024-07-25 18:22:17 +02:00
case 3:
// exit if wall to the right
if (cluster.some(([bx, by]) => bx === x + 1 && by === y)) return;
// check if there is a wall to the top
if (cluster.some(([bx, by]) => bx === x && by === y - 1)) {
return makeNextLine(x, y - 1, 6);
2024-07-25 11:35:58 +02:00
} else {
2024-07-25 18:22:17 +02:00
return makeNextCurve(x, y, 0);
2024-07-25 11:35:58 +02:00
}
2024-07-19 18:00:43 +02:00
2024-07-25 18:22:17 +02:00
case 4:
// check if there is a wall to the left
if (cluster.some(([bx, by]) => bx === x - 1 && by === y)) {
return makeNextCurveInner(x - 1, y, 7);
2024-07-25 11:35:58 +02:00
} else {
2024-07-25 18:22:17 +02:00
return makeNextLine(x, y, 1);
2024-07-25 11:35:58 +02:00
}
2024-07-19 18:00:43 +02:00
2024-07-25 18:22:17 +02:00
case 5:
2024-07-25 11:35:58 +02:00
// check if there is a wall to the bottom
if (cluster.some(([bx, by]) => bx === x && by === y + 1)) {
2024-07-25 18:22:17 +02:00
return makeNextCurveInner(x, y + 1, 4);
2024-07-25 11:35:58 +02:00
} else {
2024-07-25 18:22:17 +02:00
return makeNextLine(x, y, 2);
2024-07-25 11:35:58 +02:00
}
2024-07-19 18:00:43 +02:00
2024-07-25 18:22:17 +02:00
case 6:
2024-07-25 11:35:58 +02:00
// check if there is a wall to the right
if (cluster.some(([bx, by]) => bx === x + 1 && by === y)) {
2024-07-25 18:22:17 +02:00
return makeNextCurveInner(x + 1, y, 5);
} else {
return makeNextLine(x, y, 3);
}
case 7:
// check if there is a wall to the top
if (cluster.some(([bx, by]) => bx === x && by === y - 1)) {
return makeNextCurveInner(x, y - 1, 6);
2024-07-25 11:35:58 +02:00
} else {
2024-07-25 18:22:17 +02:00
return makeNextLine(x, y, 0);
2024-07-25 11:35:58 +02:00
}
}
};
2024-07-19 18:00:43 +02:00
2024-07-25 11:56:18 +02:00
if (cluster.length === 1) {
// Widen the dot so that it does not look like food
const [x, y] = cluster[0];
const px = x * cellSize + 0.5 * cellSize;
const py = y * cellSize + offset;
return [
`M ${px},${py}`,
`l ${-offset} 0`,
2024-07-25 18:22:17 +02:00
`a 1,1 0 0 0 0,${cellSize - 2 * offset}`,
2024-07-25 11:56:18 +02:00
`l ${2 * offset} 0`,
2024-07-25 18:22:17 +02:00
`a 1,1 0 0 0 0,${- cellSize + 2 * offset}`,
2024-07-25 11:56:18 +02:00
`Z`
].join(" ");
}
2024-07-25 11:35:58 +02:00
const pathCommands = cluster.flatMap((startCell) => {
// iterate through all possible starting points in all cells (unless already visited)
const paths = [0, 1, 2, 3].map(startRefPoint => {
let [x, y] = startCell;
if (visitedCorners.has(`${x},${y},${startRefPoint}`)) return "";
2024-07-19 18:00:43 +02:00
2024-07-25 11:35:58 +02:00
let refPoint = startRefPoint;
2024-07-19 18:00:43 +02:00
2024-07-25 18:22:17 +02:00
const px = x * cellSize + refPoints[refPoint][0];
const py = y * cellSize + refPoints[refPoint][1];
2024-07-19 18:00:43 +02:00
2024-07-25 11:35:58 +02:00
let pathCommands = [];
pathCommands.push([`M ${px},${py}`]);
2024-07-19 18:00:43 +02:00
2024-07-25 11:35:58 +02:00
do {
const node = `${x},${y},${refPoint}`;
if (visitedCorners.has(node)) {
break;
}
2024-07-19 18:00:43 +02:00
2024-07-25 11:35:58 +02:00
visitedCorners.add(node);
visitedCells.add(`${x},${y}`);
2024-07-19 18:00:43 +02:00
2024-07-25 11:35:58 +02:00
let pathMove = move(x, y, refPoint);
if (!pathMove) return "";
let path = "";
({ next: [[x, y], refPoint], path: path } = pathMove);
2024-07-19 18:00:43 +02:00
2024-07-25 11:35:58 +02:00
pathCommands.push([path]);
} while (!(x === startCell[0] && y === startCell[1] && refPoint === startRefPoint));
2024-07-19 18:00:43 +02:00
2024-07-25 11:35:58 +02:00
pathCommands.push(["Z"]); // Close the path
2024-07-19 18:00:43 +02:00
2024-07-25 11:35:58 +02:00
const joined = pathCommands.join(" ");
return joined;
});
return paths;
});
2024-07-19 18:00:43 +02:00
2024-07-25 11:35:58 +02:00
return pathCommands.join(" ");
2024-07-19 18:00:43 +02:00
};
2024-08-31 11:56:27 +02:00
function usePrevious(value: any) {
const ref = useRef();
useEffect(() => {
ref.current = value; //assign the value of ref to the argument
}, [value]); //this code will run when the value of 'value' changes
return ref.current; //in the end, return the current ref value.
}
function Food({ position, color }: { position: Pos, color: string }) {
const [x, y] = position;
return (
<motion.circle
cx={(0.5 + x) * cellSize}
cy={(0.5 + y) * cellSize}
r={cellSize / 5}
opacity={1}
className={color}
// transition={{ duration: 1 }}
initial={{ opacity: 1, r: cellSize / 5 }}
2024-08-31 11:56:27 +02:00
// animate={{ opacity: 1 }}
exit={{ opacity: 0, r: cellSize }}
/>
)
}
2024-07-19 18:00:43 +02:00
2024-08-31 11:56:27 +02:00
// // from https://www.30secondsofcode.org/react/s/use-interval-explained/
// const useInterval = (callback: any, delay: number) => {
// const savedCallback = React.useRef();
// React.useEffect(() => {
// savedCallback.current = callback;
// }, [callback]);
// React.useEffect(() => {
// function tick() {
// savedCallback?.current();
// }
// if (delay !== null) {
// let id = setInterval(tick, delay);
// return () => clearInterval(id);
// }
// }, [delay]);
// };
function Pacman({ direction, mouthAngle, color }: { direction: number, mouthAngle: number, color: string }) {
const pacmanPath = (angle: number) => {
const angle_rad = angle / 180 * Math.PI;
const radius = 8;
const x = radius * Math.cos(angle_rad / 2);
const y = radius * Math.sin(angle_rad / 2);
return `M 0,0 L ${x},${-y} A ${radius},${radius} 0 1 0 ${x},${y} Z`;
}
return (
<g transform={ `rotate(${direction})` }
className={color}>
<path
d={pacmanPath(mouthAngle)}
stroke="black"
strokeWidth={0.2}
/>
<motion.circle cx={2.7} cy={direction < 160 ? -4.5 : 4.5} r={1.5}
className={`eye`}
stroke="black"
fill="yellow"
strokeWidth={0.2}
/>
</g>)
}
function Ghost({ direction, color }: { direction: number, color: string }) {
return (<g
id="ghost"
className={`${color} ghost`}
>
{/* Round path: // M -8 0 C -8 -4.4 -4.4 -8 0 -8 C 4.4 -8 8 -4.4 8 0 L 8 8 C 8 9 6.6667 5.6 6 5.6 S 4.6667 8.19 4 8.19 S 2.6667 5.6 2 5.6 S 0.6667 8.19 0 8.19 S -1.3333 5.6 -2 5.6 S -3.3333 8.19 -4 8.19 S -5.3333 5.6 -6 5.6 S -8 9 -8 8 C -8 5.3333 -8 2.6667 -8 0 Z */}
{/* Straight path: // M -8 0 C -8 -4.4 -4.4 -8 0 -8 C 4.4 -8 8 -4.4 8 0 L 8 8 L 6 5.6 L 4 8 L 2 5.6 L 0 8 L -2 5.6 L -4 8 L -6 5.6 L -8 8 L -8 0 Z */}
<path d="M -8 0 C -8 -4.4 -4.4 -8 0 -8 C 4.4 -8 8 -4.4 8 0 L 8 8 C 8 9 6.6667 5.6 6 5.6 S 4.6667 8.19 4 8.19 S 2.6667 5.6 2 5.6 S 0.6667 8.19 0 8.19 S -1.3333 5.6 -2 5.6 S -3.3333 8.19 -4 8.19 S -5.3333 5.6 -6 5.6 S -8 9 -8 8 C -8 5.3333 -8 2.6667 -8 0 Z"
stroke="black"
strokeWidth={0.2}
opacity={0.9}
></path>
<path d="M -3.2 1.1 C -2.2 1.1 -1.4 0.1 -1.4 -1.2 C -1.4 -2.5 -2.2 -3.6 -3.2 -3.6 C -4.2 -3.6 -5.1 -2.5 -5.1 -1.2 C -5.1 0.1 -4.2 1.1 -3.2 1.1 Z
M 1.8 1.1 C 2.8 1.1 3.6 0.1 3.6 -1.2 C 3.6 -2.5 2.8 -3.6 1.8 -3.6 C 0.8 -3.6 -0 -2.5 -0 -1.2 C -0 0.1 0.8 1.1 1.8 1.1 Z"
stroke="black"
strokeWidth={0.2}
fill="white"
></path>
<path d="M -3.5 0 C -3.1 0 -2.8 -0.4 -2.8 -0.9 C -2.8 -1.5 -3.1 -1.9 -3.5 -1.9 C -3.9 -1.9 -4.2 -1.5 -4.2 -0.9 C -4.2 -0.4 -3.9 0 -3.5 0 Z
M 1.5 0 C 1.9 0 2.2 -0.4 2.2 -0.9 C 2.2 -1.5 1.9 -1.9 1.5 -1.9 C 1.1 -1.9 0.8 -1.5 0.8 -0.9 C 0.8 -0.4 1.1 0 1.5 0 Z"
stroke="black"
strokeWidth={0.2}
fill="black"
>
</path>
</g>);
}
function Bot({ position, color, say, width, turnsAgo, fadeIn }: { position: Pos, color: string, say: string, width: number, turnsAgo: number, fadeIn: boolean }) {
2024-07-19 18:00:43 +02:00
const leftSide = position[0] < width / 2;
const inHomezone = () => {
switch (color) {
case "blue":
return leftSide;
case "red":
return !leftSide;
}
}
2024-08-31 11:56:27 +02:00
const [direction, setDirection] = useState(leftSide ? 0 : 180);
const oldPosition = usePrevious(position);
// const [mouthAngleTL, setMouthAngleTL] = useState(0);
// useInterval(() => setMouthAngleTL(mouthAngleTL + 0.08), 10);
const mouthAngle = 50; // Math.abs(50 * Math.sin(mouthAngleTL));
useEffect(() => {
if (oldPosition) {
const dx = position[0] - oldPosition[0];
const dy = position[1] - oldPosition[1];
if (dx < 0) setDirection(180);
else if (dy < 0) setDirection(270);
else if (dx > 0) setDirection(0);
else if (dy > 0) setDirection(90);
}
}, [position, oldPosition]);
2024-07-19 18:00:43 +02:00
useEffect(() => {
if (fadeIn)
2024-07-19 18:00:43 +02:00
anime.timeline()
.add({
targets: '.bot .blue',
2024-07-19 18:00:43 +02:00
opacity: [0, 1],
easing: 'linear',
duration: 2000,
}, 3500)
.add({
targets: '.bot .red',
2024-07-19 18:00:43 +02:00
opacity: [0, 1],
easing: 'linear',
duration: 2000,
}, 3500);
}, [fadeIn]);
2024-07-19 18:00:43 +02:00
return (
2024-08-31 11:56:27 +02:00
<motion.g
transform={ `translate(${(position[0] + 0.5) * cellSize} ${(position[1] + 0.5) * cellSize}) scale(${cellSize / 16})` }
className="bot"
2024-08-31 11:56:27 +02:00
animate={{
x: (position[0] + 0.5) * cellSize,
y: (position[1] + 0.5) * cellSize,
scale: cellSize / 16
}}
initial={{
x: (position[0] + 0.5) * cellSize,
y: (position[1] + 0.5) * cellSize,
scale: cellSize / 16
}}
2024-08-31 11:56:27 +02:00
transition={{ duration: 0.1 }}
>
{
inHomezone()
? (<Ghost direction={direction} color={`${color} ghost`}></Ghost>)
: (<Pacman direction={direction} mouthAngle={mouthAngle} color={`${color} pacman`}></Pacman>)
2024-08-31 11:56:27 +02:00
}
<text y="-10" className="sayBg">{say}</text>
<text y="-10" className="say">{say}</text>
</motion.g>
2024-07-19 18:00:43 +02:00
)
}
2024-08-31 11:56:27 +02:00
function Walls({ shape, walls }: { shape: Pos, walls: Pos[] }) {
const clusters = useMemo(() => findClusters(shape, walls), [shape, walls]);
const [width, height] = shape;
return (
<g className="maze">
2024-08-31 11:56:27 +02:00
<line x1={(width) * cellSize / 2} y1={0.3 * cellSize}
x2={width * cellSize / 2} y2={(height - 0.3) * cellSize} className="middleLine blackLine" />
<line x1={(width - 0.1) * cellSize / 2} y1={0.3 * cellSize}
x2={(width - 0.1) * cellSize / 2} y2={(height - 0.3) * cellSize} className="middleLine blueLine" />
<line x1={(width + 0.1) * cellSize / 2} y1={0.3 * cellSize}
x2={(width + 0.1) * cellSize / 2} y2={(height - 0.3) * cellSize} className="middleLine redLine" />
{walls.map(([x, y], index) => (
<rect
key={`${x},${y}`}
x={x * cellSize}
y={y * cellSize}
width={cellSize}
height={cellSize}
opacity="0"
// fill="lightblue"
stroke="lightgrey"
/>
))}
{clusters.map((cluster, index) => (
<path
className="maze"
key={`${cluster[0]},${cluster[1]}-${cluster.length}`}
d={createPath(cluster)}
// stroke="lightblue"
// stroke={ dark_mode ? "url(#grad)" : "black" }
stroke="url(#grad)"
strokeWidth="2"
//fill={ dark_mode ? "#ffa" : "black" }
fill="black"
strokeLinecap="round"
strokeLinejoin="bevel"
/>
))}
</g>
);
}
2024-07-25 11:35:58 +02:00
function Maze({ game_uuid, shape, walls, food, bots, team_names, say, whowins, gameover, round, turn, animate }:
2024-07-19 18:00:43 +02:00
{
game_uuid: string,
2024-07-25 11:35:58 +02:00
shape: Pos,
walls: Pos[],
food: Pos[],
2024-08-31 11:56:27 +02:00
bots: [Pos, Pos, Pos, Pos],
team_names: [string, string],
say: [string, string, string, string],
2024-07-19 18:00:43 +02:00
whowins: number | null,
2024-08-31 11:56:27 +02:00
gameover: boolean,
round: number,
turn: number,
animate: boolean
2024-07-19 18:00:43 +02:00
}
) {
const [width, height] = shape;
2024-08-31 11:56:27 +02:00
const [a, x, b, y] = bots;
const [sayA, sayX, sayB, sayY] = say;
2024-07-19 18:00:43 +02:00
const mazeBoxRef = useRef<HTMLDivElement>(null);
// used so that we can revert the animation
const [hasWonScreen, setHasWonScreen] = useState(false);
2024-07-19 18:00:43 +02:00
useEffect(() => {
if (game_uuid && animate) {
2024-07-19 18:00:43 +02:00
let pathAnimation = anime.timeline()
.add({
targets: mazeBoxRef.current?.querySelectorAll('.maze path'),
2024-07-19 18:00:43 +02:00
strokeDashoffset: [anime.setDashoffset, 0],
easing: 'easeInCubic',
duration: 2000,
delay: function (el, i) { return i * 25 },
direction: 'alternate',
loop: false
})
.add({
targets: mazeBoxRef.current?.querySelectorAll('.maze path'),
2024-07-25 11:35:58 +02:00
//fill: ['rgb(214, 219, 220)', "#faa"], // ffa
fillOpacity: [0, 0.7], // ffa
2024-07-19 18:00:43 +02:00
easing: 'linear',
duration: 2000
}, 2000)
2024-08-31 11:56:27 +02:00
.add({
targets: mazeBoxRef.current?.querySelectorAll('.maze path'),
2024-08-31 11:56:27 +02:00
strokeWidth: 0,
easing: 'linear',
duration: 2000
}, 4000)
2024-07-19 18:00:43 +02:00
.add({
targets: mazeBoxRef.current?.querySelectorAll('.foodblue'),
2024-07-19 18:00:43 +02:00
opacity: [0, 1],
easing: 'linear',
duration: 2000,
}, 3000)
.add({
targets: mazeBoxRef.current?.querySelectorAll('.foodred'),
2024-07-19 18:00:43 +02:00
opacity: [0, 1],
easing: 'linear',
duration: 2000,
}, 3000)
.add({
targets: mazeBoxRef.current?.querySelectorAll('.blue'),
2024-07-19 18:00:43 +02:00
opacity: [0, 1],
easing: 'linear',
duration: 2000,
}, 3500)
.add({
targets: mazeBoxRef.current?.querySelectorAll('.red'),
2024-07-19 18:00:43 +02:00
opacity: [0, 1],
easing: 'linear',
duration: 2000,
2024-08-31 11:56:27 +02:00
}, 3500)
.add({
targets: mazeBoxRef.current?.querySelectorAll('.middleLine'),
2024-08-31 11:56:27 +02:00
opacity: [0, 1],
easing: 'linear',
duration: 2000,
2024-07-19 18:00:43 +02:00
}, 3500);
}
}, [walls, game_uuid, animate, mazeBoxRef]);
2024-07-19 18:00:43 +02:00
useEffect(() => {
if (!gameover && hasWonScreen) {
let pathAnimation = anime.timeline()
.add({
targets: mazeBoxRef.current?.querySelectorAll('.maze path'),
fill: ["#000"],
easing: 'linear',
duration: 200
});
};
if (gameover) {
setHasWonScreen(true);
}
2024-07-19 18:00:43 +02:00
if (gameover && whowins === 0) {
let pathAnimation = anime.timeline()
.add({
targets: mazeBoxRef.current?.querySelectorAll('.maze path'),
fill: ["#000", "rgb(94, 158, 217)"],
2024-07-19 18:00:43 +02:00
easing: 'linear',
duration: 200
});
};
if (gameover && whowins === 1) {
let pathAnimation = anime.timeline()
.add({
targets: mazeBoxRef.current?.querySelectorAll('.maze path'),
fill: ["#000", "rgb(235, 90, 90)"],
2024-07-19 18:00:43 +02:00
easing: 'linear',
duration: 200
});
};
if (gameover && whowins === 2) {
let pathAnimation = anime.timeline()
.add({
targets: mazeBoxRef.current?.querySelectorAll('.maze path'),
fill: ["#000", "#ffa"],
2024-07-19 18:00:43 +02:00
easing: 'linear',
duration: 200
});
};
}, [gameover, whowins, mazeBoxRef, hasWonScreen]);
2024-07-19 18:00:43 +02:00
return (
<div ref={mazeBoxRef} className="mazebox object-fill">
2024-07-19 18:00:43 +02:00
<svg
2024-08-31 11:56:27 +02:00
// width={width * cellSize}
// height={height * cellSize}
2024-07-19 18:00:43 +02:00
viewBox={`0 0 ${width * cellSize} ${height * cellSize}`}
xmlns="http://www.w3.org/2000/svg"
2024-08-31 11:56:27 +02:00
style={{ overflow: "visible" }}
2024-07-19 18:00:43 +02:00
>
<style type="text/css">{`
line {
stroke: #000000;
stroke-linecap: round;
stroke-width: 3;
}
.foodblue {
2024-08-31 11:56:27 +02:00
// stroke: black;
2024-07-19 18:00:43 +02:00
fill: rgb(94, 158, 217);
}
.foodred {
2024-08-31 11:56:27 +02:00
// stroke: black;
2024-07-19 18:00:43 +02:00
fill: rgb(235, 90, 90);
}
.blue {
fill: rgb(94, 158, 217);
}
.red {
fill: rgb(235, 90, 90);
}
2024-08-31 11:56:27 +02:00
.blueLine {
stroke: rgb(94, 158, 217);
}
.redLine {
stroke: rgb(235, 90, 90);
}
.sayBg {
stroke-width: 1.7px;
stroke: white;
font-size: 7px;
text-anchor: middle;
dominant-baseline: middle;
z-index: 90;
}
.say {
// stroke-width: 0.2px;
// stroke: white;
font-size: 7px;
text-anchor: middle;
dominant-baseline: middle;
z-index: 100;
}
2024-07-19 18:00:43 +02:00
.gameover {
fill: #FFC903;
stroke: #ED1B22;
stroke-width: 10px;
stroke-linejoin: round;
paint-order: stroke;
}
`}</style>
<defs>
2024-07-25 11:35:58 +02:00
<linearGradient id="grad" gradientUnits="userSpaceOnUse">
<stop stopColor="blue" offset="0" />
<stop stopColor="blue" offset="50%" />
<stop stopColor="red" offset="50%" />
<stop stopColor="red" offset="100%" />
</linearGradient>
2024-07-19 18:00:43 +02:00
</defs>
2024-08-31 11:56:27 +02:00
<Walls shape={shape} walls={walls}></Walls>
<AnimatePresence>
{food.map(([x, y], index) => (
<Food key={`${x},${y}`} position={[x, y]} color={x < width / 2 ? "foodblue" : "foodred"}></Food>
2024-07-19 18:00:43 +02:00
))}
2024-08-31 11:56:27 +02:00
</AnimatePresence>
2024-07-19 18:00:43 +02:00
<Bot position={a} key="botA" color="blue" say={sayA} width={width} fadeIn={animate} turnsAgo={turn}></Bot>
<Bot position={x} key="botX" color="red" say={sayX} width={width} fadeIn={animate} turnsAgo={(turn + 3) % 4}></Bot>
<Bot position={b} key="botB" color="blue" say={sayB} width={width} fadeIn={animate} turnsAgo={(turn + 2) % 4}></Bot>
<Bot position={y} key="botY" color="red" say={sayY} width={width} fadeIn={animate} turnsAgo={(turn + 1) % 4}></Bot>
2024-07-19 18:00:43 +02:00
{
2024-08-31 11:56:27 +02:00
gameover ? (<>
2024-08-31 13:26:11 +02:00
<text fontSize="60" className="gameover"
2024-08-31 11:56:27 +02:00
x="50%" y="25%"
2024-07-25 11:35:58 +02:00
dominantBaseline="middle"
textAnchor="middle"
2024-07-19 18:00:43 +02:00
>
2024-07-25 11:35:58 +02:00
GAME OVER
2024-08-31 11:56:27 +02:00
</text>
2024-08-31 13:26:11 +02:00
<text fontSize="60" className="gameover"
2024-08-31 11:56:27 +02:00
x="50%" y="75%"
dominantBaseline="middle"
textAnchor="middle"
>
2024-08-31 13:28:30 +02:00
{ whowins == null || whowins == 2 ? "DRAW" : `${team_names[whowins]} wins!` }
2024-08-31 11:56:27 +02:00
</text>
</>) : null
2024-07-25 11:35:58 +02:00
}
2024-07-19 18:00:43 +02:00
</svg>
</div>
);
};
export default Maze;