3138fc4b08
CustomPainter board with gems/ghost/clear-flash, finger-lifted drag with snap preview, combo text effect, HUD chips, phase overlays with rescue stubs, demo stage. E2E widget test drives a real drag gesture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
42 lines
1.3 KiB
Dart
42 lines
1.3 KiB
Dart
import 'dart:math' as math;
|
|
import 'dart:ui';
|
|
|
|
import '../../game/models/grid.dart';
|
|
import '../../game/models/piece.dart';
|
|
|
|
/// Pure mapping between board-local pixels and grid cells.
|
|
class BoardGeometry {
|
|
const BoardGeometry({required this.boardSize});
|
|
|
|
final double boardSize;
|
|
|
|
double get cellSize => boardSize / GridState.size;
|
|
|
|
Rect cellRect(int x, int y) =>
|
|
Rect.fromLTWH(x * cellSize, y * cellSize, cellSize, cellSize);
|
|
|
|
/// Cell under a local point, or null outside the board.
|
|
(int, int)? cellAt(Offset local) {
|
|
if (local.dx < 0 || local.dy < 0) return null;
|
|
if (local.dx >= boardSize || local.dy >= boardSize) return null;
|
|
return (local.dx ~/ cellSize, local.dy ~/ cellSize);
|
|
}
|
|
|
|
/// Nearest anchor for a piece whose visual top-left sits at [pieceTopLeft],
|
|
/// clamped so the piece's bounding box stays on the board.
|
|
(int, int) snapAnchor(Piece piece, Offset pieceTopLeft) {
|
|
var maxDx = 0;
|
|
var maxDy = 0;
|
|
for (final (dx, dy) in piece.offsets) {
|
|
maxDx = math.max(maxDx, dx);
|
|
maxDy = math.max(maxDy, dy);
|
|
}
|
|
final x = (pieceTopLeft.dx / cellSize).round();
|
|
final y = (pieceTopLeft.dy / cellSize).round();
|
|
return (
|
|
x.clamp(0, GridState.size - 1 - maxDx),
|
|
y.clamp(0, GridState.size - 1 - maxDy),
|
|
);
|
|
}
|
|
}
|