testsAndMisc-archive/bucket_catch/packages/frontend/src/components/PuzzleCanvas.tsx
Krzysztof kuhy Rudnicki ef9b250a1d Add bucket_catch: osu!catch browser game with 100% frontend test coverage
Frontend (React 19 + Vite 6 + TypeScript strict):
- DropZone, ModeSelect, GameCanvas, PuzzleCanvas, ScoreScreen, PuzzleResult
- File-drop game with AABB collision; download (JSZip) and upload (NestJS) modes
- Puzzle mode: NxN image slice via OffscreenCanvas; Union-Find spatial clustering
  guarantees 100% catch rate is always achievable regardless of piece speeds
- ESLint typescript-eslint strict-type-checked (zero errors)
- 145 Vitest tests; 100% coverage on statements/branches/functions/lines

Backend (NestJS 11):
- POST /files/upload (multer disk storage) and GET /health

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZ8QTmreFcaqrsvVb38Grd
2026-06-27 12:21:35 +02:00

47 lines
1.3 KiB
TypeScript

import React, { useEffect, useRef } from "react";
import type { PuzzleGameResult } from "../types";
import { useBasketControl } from "../hooks/useBasketControl";
import { usePuzzleGameLoop } from "../hooks/usePuzzleGameLoop";
import styles from "./PuzzleCanvas.module.css";
interface Props {
imageFile: File;
gridSize: number;
onDone: (result: PuzzleGameResult) => void;
}
export function PuzzleCanvas({
imageFile,
gridSize,
onDone,
}: Props): React.ReactElement {
const canvasRef = useRef<HTMLCanvasElement>(null);
const basketXRef = useBasketControl(canvasRef);
useEffect(() => {
const resize = (): void => {
const canvas = canvasRef.current;
/* istanbul ignore next */
if (!canvas) return;
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
};
resize();
window.addEventListener("resize", resize);
return () => { window.removeEventListener("resize", resize); };
}, []);
const result = usePuzzleGameLoop(canvasRef, basketXRef, imageFile, gridSize, true);
useEffect(() => {
if (result) onDone(result);
}, [result, onDone]);
return (
<div className={styles.wrapper}>
<canvas ref={canvasRef} className={styles.canvas} />
<div className={styles.hint}>Move mouse to catch the puzzle pieces!</div>
</div>
);
}