diff --git a/src/useBoard/useMazeNavigation.ts b/src/useBoard/useMazeNavigation.ts index 816dd51..0019a8a 100644 --- a/src/useBoard/useMazeNavigation.ts +++ b/src/useBoard/useMazeNavigation.ts @@ -14,64 +14,72 @@ export function useMazeNavigation(maze: MazeCell[][]): MazeNavigator { return true; } - function findAnchorTarget(from: Coord, direction: 'left'|'right'|'top'|'bottom'): Coord | null { - if (!maze.length) return null; + function findAnchorTarget(from: Coord, direction: 'left'|'right'|'top'|'bottom'): Coord | null { + if (!maze.length) return null; - const rowsCount = maze.length; - const colsCount = maze[0]?.length ?? 0; - const { row: r, col: c } = from; + const rowsCount = maze.length; + const colsCount = maze[0]?.length ?? 0; + 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') { - const step = direction === 'left' ? -1 : 1; - let cc = c; - let lastValidPos = { row: r, col: c }; + if (direction === 'left' || direction === 'right') { + const step = direction === 'left' ? -1 : 1; + let cc = c; - while (true) { - const next = cc + step; - if (next < 0 || next >= colsCount) break; - if (maze[r][next].includes('wall')) break; - if (!maze[r][next].includes('wall')) { - lastValidPos = { row: r, col: next }; - } - cc = next; - } + while (true) { + const next = cc + step; + if (next < 0 || next >= colsCount) return null; + if (maze[r][next].includes('wall')) { + const wallIdx = next; + const expectedWallIdx = direction === 'left' ? 0 : colsCount - 1; + const expectedCellIdx = direction === 'left' ? 1 : colsCount - 2; - return lastValidPos.col !== c ? lastValidPos : null; - } else { - const step = direction === 'top' ? -1 : 1; - let rr = r; - let lastValidPos = { row: r, col: c }; + if (wallIdx !== expectedWallIdx) return null; + if (cc !== expectedCellIdx) return null; + if (cc === c) return null; - while (true) { - const next = rr + step; - if (next < 0 || next >= rowsCount) break; - if (maze[next][c].includes('wall')) break; - if (!maze[next][c].includes('wall')) { - lastValidPos = { row: next, col: c }; - } - rr = next; - } + return { row: r, col: cc }; + } + cc = next; + } + } else { + const step = direction === 'top' ? -1 : 1; + let rr = r; - 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 (!maze.length) return false; + if (wallIdx !== expectedWallIdx) return null; + if (rr !== expectedCellIdx) return null; + if (rr === r) return null; - let r = from.row; - let c = from.col; + return { row: rr, col: c }; + } + rr = next; + } + } + } - for (let i = 1; i <= steps; i++) { - r += direction.row; - c += direction.col; - if (!isValidMove(from, { row: r, col: c })) return false; - } + function validatePath(from: Coord, direction: Coord, steps: number): boolean { + if (!maze.length) 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 { isValidMove, diff --git a/src/useCursor/useCursor.ts b/src/useCursor/useCursor.ts index 751587d..192d6fd 100644 --- a/src/useCursor/useCursor.ts +++ b/src/useCursor/useCursor.ts @@ -5,6 +5,25 @@ import { useGameKeys } from './useGameKeys'; import { useMazeNavigation } from '../useBoard'; 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) { const position = useRef({ row: 0, col: 0 }) const mode = useRef('normal') @@ -25,7 +44,7 @@ export function useCursor(board: BoardManager, gameStatusManager: GameStatusMana const currentPos = position.current; const steps = Math.max(1, count); const direction = { row: dRows, col: dCols }; - + const targetCoord = { row: currentPos.row + direction.row * steps, col: currentPos.col + direction.col * steps @@ -59,53 +78,66 @@ export function useCursor(board: BoardManager, gameStatusManager: GameStatusMana move(0, 1, count); } - function moveToAnchor(target: 'start'|'end'|'top'|'bottom'): void { - const currentPos = position.current; - const direction = target === 'start' ? 'left' : - target === 'end' ? 'right' : - target === 'top' ? 'top' : 'bottom'; - - const anchorTarget = navigator.findAnchorTarget(currentPos, direction); - if (!anchorTarget) { - window.dispatchEvent(new Event("maze-invalid")); - return; - } + function moveToAnchor(target: 'start' | 'end' | 'top' | 'bottom'): void { + const currentPos = position.current; + const direction = target === 'start' ? 'left' : + target === 'end' ? 'right' : + target === 'top' ? 'top' : 'bottom'; - const dr = anchorTarget.row > currentPos.row ? 1 : - anchorTarget.row < currentPos.row ? -1 : 0; - 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); + const anchorTarget = navigator.findAnchorTarget(currentPos, direction); + if (!anchorTarget) return; - position.current = anchorTarget; - if (hero && hero.heroPos) { - hero.moveTo(anchorTarget); - if (steps > 0) { - vimMotions.setLastMotion({ dr, dc, steps }); - } - } - } + const dr = anchorTarget.row > currentPos.row ? 1 : + anchorTarget.row < currentPos.row ? -1 : 0; + 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); - function moveToStart(): void { - moveToAnchor('start'); - } + position.current = anchorTarget; + if (hero && hero.heroPos) { + hero.moveTo(anchorTarget); + if (steps > 0) { + vimMotions.setLastMotion({ dr, dc, steps }); + } + } + } - function moveToEnd(): void { - moveToAnchor('end'); - } + function moveToStart(): void { + if (!validateAnchorMove('start', position.current, board)) { + window.dispatchEvent(new Event("maze-invalid")); + return; + } + moveToAnchor('start'); + } - function moveToTop(): void { - moveToAnchor('top'); - } + function moveToEnd(): void { + if (!validateAnchorMove('end', position.current, board)) { + window.dispatchEvent(new Event("maze-invalid")); + return; + } + moveToAnchor('end'); + } - function moveToBottom(): void { - moveToAnchor('bottom'); - } + function moveToTop(): void { + 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 { const motion = vimMotions.repeatLastMotion(); if (!motion || !hero || !hero.heroPos) return; - + const currentPos = position.current; const targetCoord = { row: currentPos.row + motion.dr * motion.steps,