Files
m3te/swipe.sx
swipelab f290e2614a P5.1: drag → adjacent-swap intent mapping (pure sx)
Add swipe.sx: a pure swipe_intent(layout, start, end) -> ?Swap that turns a
touch drag into an optional adjacent-swap intent (A, B). A is the cell under the
drag start (via BoardLayout.point_to_cell); B is its orthogonal neighbour along
the drag's dominant axis (larger of |dx|,|dy|, ties horizontal). Returns null for
sub-threshold drags (a tap, threshold = cell_size * 0.5), starts off the board,
or neighbours off the board. No rendering, no model mutation.

Lock the logic with tests/swipe_intent.sx (+ expected golden), feeding synthetic
down/up positions for right/left/up/down, sub-threshold tap, diagonal→dominant
(both axes), edge-outward off-board, and start-off-board. bash tools/run_tests.sh
passes (13/13); sx build --target ios-sim main.sx compiles.
2026-06-05 00:14:44 +03:00

53 lines
2.5 KiB
Plaintext

// Pure drag → adjacent-swap intent mapping (Phase 5 input). Turns a touch drag —
// a down position and an up/move position, both in the same view-local coordinate
// space BoardLayout uses — into an optional swap intent (A, B): A is the cell
// under the drag start, B its orthogonal neighbour along the drag's dominant axis.
// Owns no rendering and mutates no model, so it is unit-testable headless; P5.2
// feeds the resulting Swap straight into `commit_swap`.
#import "modules/std.sx";
#import "modules/math";
#import "modules/ui/types.sx";
#import "board.sx";
#import "board_layout.sx";
// A drag whose dominant-axis travel is below this fraction of a cell is treated
// as a tap, not a swipe, and yields no intent. Scaling to cell_size keeps the
// feel constant across screen sizes, since the layout sizes cells to the device.
SWIPE_THRESHOLD_FRACTION :f32: 0.5;
// Map a drag to the adjacent-swap intent it expresses, or null when the gesture
// is not a board swipe. Returns null when: the start point is off the board; the
// dominant-axis travel is below the swipe threshold (a tap, not a swipe); or the
// resolved neighbour B would fall off the board. A and B are always orthogonally
// adjacent — the intent never spans more than one cell. The dominant axis is the
// larger of |dx|, |dy| (an exact tie resolves horizontal), and its sign picks the
// direction: +x → right, -x → left, +y → down, -y → up (screen y grows downward,
// matching `cell_frame`). Reuses `point_to_cell` so the start resolves to exactly
// the cell drawn under the finger.
swipe_intent :: (layout: *BoardLayout, start: Point, end: Point) -> ?Swap {
if a := layout.point_to_cell(start) {
dx := end.x - start.x;
dy := end.y - start.y;
adx := abs(dx);
ady := abs(dy);
threshold := layout.cell_size * SWIPE_THRESHOLD_FRACTION;
if adx < threshold and ady < threshold { return null; } // a tap, not a swipe
bcol := a.col;
brow := a.row;
if adx >= ady {
if dx > 0.0 { bcol += 1; } else { bcol -= 1; }
} else {
if dy > 0.0 { brow += 1; } else { brow -= 1; }
}
if bcol < 0 or bcol >= BOARD_COLS or brow < 0 or brow >= BOARD_ROWS {
return null; // neighbour off the board
}
return Swap.{ a = a, b = Cell.{ col = bcol, row = brow } };
}
null // start off the board
}