testsAndMisc/bucket_catch/packages/frontend/src/lib/sliceImage.ts
Krzysztof kuhy Rudnicki 5a9296d8aa 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

60 lines
1.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { PuzzlePiece } from "../types";
/** Slice an image file into a gridSize×gridSize grid of data-URL pieces. */
export function sliceImage(file: File, gridSize: number): Promise<PuzzlePiece[]> {
return new Promise<PuzzlePiece[]>((resolve, reject) => {
const img = new Image();
const url = URL.createObjectURL(file);
img.onload = () => {
const pieceWidth = Math.floor(img.width / gridSize);
const pieceHeight = Math.floor(img.height / gridSize);
const pieces: PuzzlePiece[] = [];
for (let row = 0; row < gridSize; row++) {
for (let col = 0; col < gridSize; col++) {
const offscreen = document.createElement("canvas");
offscreen.width = pieceWidth;
offscreen.height = pieceHeight;
const ctx = offscreen.getContext("2d");
/* istanbul ignore next */
if (!ctx) {
URL.revokeObjectURL(url);
reject(new Error("Canvas 2D context not available"));
return;
}
ctx.drawImage(
img,
col * pieceWidth,
row * pieceHeight,
pieceWidth,
pieceHeight,
0,
0,
pieceWidth,
pieceHeight,
);
pieces.push({
row,
col,
gridSize,
imageUrl: offscreen.toDataURL(),
pieceWidth,
pieceHeight,
});
}
}
URL.revokeObjectURL(url);
resolve(pieces);
};
img.onerror = () => {
URL.revokeObjectURL(url);
reject(new Error("Failed to load image for slicing"));
};
img.src = url;
});
}