proper anchor invalidation

This commit is contained in:
2025-10-19 14:43:18 +01:00
parent aa42066f57
commit 47aea08d49
2 changed files with 125 additions and 85 deletions
+55 -47
View File
@@ -14,64 +14,72 @@ export function useMazeNavigation(maze: MazeCell[][]): MazeNavigator {
return true; return true;
} }
function findAnchorTarget(from: Coord, direction: 'left'|'right'|'top'|'bottom'): Coord | null { function findAnchorTarget(from: Coord, direction: 'left'|'right'|'top'|'bottom'): Coord | null {
if (!maze.length) return null; if (!maze.length) return null;
const rowsCount = maze.length; const rowsCount = maze.length;
const colsCount = maze[0]?.length ?? 0; const colsCount = maze[0]?.length ?? 0;
const { row: r, col: c } = from; const { row: r, col: c } = from;
if (!maze[r] || !maze[r][c] || maze[r][c].includes('wall')) return null; if (!maze[r] || !maze[r][c] || maze[r][c].includes('wall')) return null;
if (direction === 'left' || direction === 'right') { if (direction === 'left' || direction === 'right') {
const step = direction === 'left' ? -1 : 1; const step = direction === 'left' ? -1 : 1;
let cc = c; let cc = c;
let lastValidPos = { row: r, col: c };
while (true) { while (true) {
const next = cc + step; const next = cc + step;
if (next < 0 || next >= colsCount) break; if (next < 0 || next >= colsCount) return null;
if (maze[r][next].includes('wall')) break; if (maze[r][next].includes('wall')) {
if (!maze[r][next].includes('wall')) { const wallIdx = next;
lastValidPos = { row: r, col: next }; const expectedWallIdx = direction === 'left' ? 0 : colsCount - 1;
} const expectedCellIdx = direction === 'left' ? 1 : colsCount - 2;
cc = next;
}
return lastValidPos.col !== c ? lastValidPos : null; if (wallIdx !== expectedWallIdx) return null;
} else { if (cc !== expectedCellIdx) return null;
const step = direction === 'top' ? -1 : 1; if (cc === c) return null;
let rr = r;
let lastValidPos = { row: r, col: c };
while (true) { return { row: r, col: cc };
const next = rr + step; }
if (next < 0 || next >= rowsCount) break; cc = next;
if (maze[next][c].includes('wall')) break; }
if (!maze[next][c].includes('wall')) { } else {
lastValidPos = { row: next, col: c }; const step = direction === 'top' ? -1 : 1;
} let rr = r;
rr = next;
}
return lastValidPos.row !== r ? lastValidPos : null; while (true) {
} const next = rr + step;
} if (next < 0 || next >= rowsCount) return null;
if (maze[next][c].includes('wall')) {
const wallIdx = next;
const expectedWallIdx = direction === 'top' ? 0 : rowsCount - 1;
const expectedCellIdx = direction === 'top' ? 1 : rowsCount - 2;
function validatePath(from: Coord, direction: Coord, steps: number): boolean { if (wallIdx !== expectedWallIdx) return null;
if (!maze.length) return false; if (rr !== expectedCellIdx) return null;
if (rr === r) return null;
let r = from.row; return { row: rr, col: c };
let c = from.col; }
rr = next;
}
}
}
for (let i = 1; i <= steps; i++) { function validatePath(from: Coord, direction: Coord, steps: number): boolean {
r += direction.row; if (!maze.length) return false;
c += direction.col;
if (!isValidMove(from, { row: r, col: c })) return false;
}
return true; let r = from.row;
} let c = from.col;
for (let i = 1; i <= steps; i++) {
r += direction.row;
c += direction.col;
if (!isValidMove(from, { row: r, col: c })) return false;
}
return true;
}
return { return {
isValidMove, isValidMove,
+70 -38
View File
@@ -5,6 +5,25 @@ import { useGameKeys } from './useGameKeys';
import { useMazeNavigation } from '../useBoard'; import { useMazeNavigation } from '../useBoard';
import { useVimMotions } from './useVimMotions'; import { useVimMotions } from './useVimMotions';
function validateAnchorMove(direction: 'start' | 'end' | 'top' | 'bottom', currentPos: Coord, board: BoardManager): boolean {
const navDirection = direction === 'start' ? 'left' :
direction === 'end' ? 'right' :
direction === 'top' ? 'top' : 'bottom';
const navigator = useMazeNavigation(board.mazeRef.current);
const target = navigator.findAnchorTarget(currentPos, navDirection);
if (!target) return false;
const dr = target.row > currentPos.row ? 1 :
target.row < currentPos.row ? -1 : 0;
const dc = target.col > currentPos.col ? 1 :
target.col < currentPos.col ? -1 : 0;
const steps = Math.abs(target.row - currentPos.row) + Math.abs(target.col - currentPos.col);
return navigator.validatePath(currentPos, { row: dr, col: dc }, steps);
}
export function useCursor(board: BoardManager, gameStatusManager: GameStatusManager) { export function useCursor(board: BoardManager, gameStatusManager: GameStatusManager) {
const position = useRef<Coord>({ row: 0, col: 0 }) const position = useRef<Coord>({ row: 0, col: 0 })
const mode = useRef<CursorMode>('normal') const mode = useRef<CursorMode>('normal')
@@ -25,7 +44,7 @@ export function useCursor(board: BoardManager, gameStatusManager: GameStatusMana
const currentPos = position.current; const currentPos = position.current;
const steps = Math.max(1, count); const steps = Math.max(1, count);
const direction = { row: dRows, col: dCols }; const direction = { row: dRows, col: dCols };
const targetCoord = { const targetCoord = {
row: currentPos.row + direction.row * steps, row: currentPos.row + direction.row * steps,
col: currentPos.col + direction.col * steps col: currentPos.col + direction.col * steps
@@ -59,53 +78,66 @@ export function useCursor(board: BoardManager, gameStatusManager: GameStatusMana
move(0, 1, count); move(0, 1, count);
} }
function moveToAnchor(target: 'start'|'end'|'top'|'bottom'): void { function moveToAnchor(target: 'start' | 'end' | 'top' | 'bottom'): void {
const currentPos = position.current; const currentPos = position.current;
const direction = target === 'start' ? 'left' : const direction = target === 'start' ? 'left' :
target === 'end' ? 'right' : target === 'end' ? 'right' :
target === 'top' ? 'top' : 'bottom'; target === 'top' ? 'top' : 'bottom';
const anchorTarget = navigator.findAnchorTarget(currentPos, direction);
if (!anchorTarget) {
window.dispatchEvent(new Event("maze-invalid"));
return;
}
const dr = anchorTarget.row > currentPos.row ? 1 : const anchorTarget = navigator.findAnchorTarget(currentPos, direction);
anchorTarget.row < currentPos.row ? -1 : 0; if (!anchorTarget) return;
const dc = anchorTarget.col > currentPos.col ? 1 :
anchorTarget.col < currentPos.col ? -1 : 0;
const steps = Math.abs(anchorTarget.row - currentPos.row) + Math.abs(anchorTarget.col - currentPos.col);
position.current = anchorTarget; const dr = anchorTarget.row > currentPos.row ? 1 :
if (hero && hero.heroPos) { anchorTarget.row < currentPos.row ? -1 : 0;
hero.moveTo(anchorTarget); const dc = anchorTarget.col > currentPos.col ? 1 :
if (steps > 0) { anchorTarget.col < currentPos.col ? -1 : 0;
vimMotions.setLastMotion({ dr, dc, steps }); const steps = Math.abs(anchorTarget.row - currentPos.row) + Math.abs(anchorTarget.col - currentPos.col);
}
}
}
function moveToStart(): void { position.current = anchorTarget;
moveToAnchor('start'); if (hero && hero.heroPos) {
} hero.moveTo(anchorTarget);
if (steps > 0) {
vimMotions.setLastMotion({ dr, dc, steps });
}
}
}
function moveToEnd(): void { function moveToStart(): void {
moveToAnchor('end'); if (!validateAnchorMove('start', position.current, board)) {
} window.dispatchEvent(new Event("maze-invalid"));
return;
}
moveToAnchor('start');
}
function moveToTop(): void { function moveToEnd(): void {
moveToAnchor('top'); if (!validateAnchorMove('end', position.current, board)) {
} window.dispatchEvent(new Event("maze-invalid"));
return;
}
moveToAnchor('end');
}
function moveToBottom(): void { function moveToTop(): void {
moveToAnchor('bottom'); if (!validateAnchorMove('top', position.current, board)) {
} window.dispatchEvent(new Event("maze-invalid"));
return;
}
moveToAnchor('top');
}
function moveToBottom(): void {
if (!validateAnchorMove('bottom', position.current, board)) {
window.dispatchEvent(new Event("maze-invalid"));
return;
}
moveToAnchor('bottom');
}
function repeatLastMotion(): void { function repeatLastMotion(): void {
const motion = vimMotions.repeatLastMotion(); const motion = vimMotions.repeatLastMotion();
if (!motion || !hero || !hero.heroPos) return; if (!motion || !hero || !hero.heroPos) return;
const currentPos = position.current; const currentPos = position.current;
const targetCoord = { const targetCoord = {
row: currentPos.row + motion.dr * motion.steps, row: currentPos.row + motion.dr * motion.steps,