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.
53 lines
2.5 KiB
Plaintext
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
|
|
}
|