import * as React from 'react'; import SolutionRunner from './SolutionRunner'; import { TypeCell, TypeHistory, TypePoint, TypeSnake, TypeValues } from './Types'; import { createBlock, moveForwards, moveBackwards } from './utils/history'; import getSurroundingCells from './utils/getSurroundingCells'; import isCellIncluded from './utils/isCellIncluded'; interface Props { solution: string; timeout?: number; worker: Worker; xLength?: number; yLength?: number; } export const SnakeContext = React.createContext<{ history: TypeHistory; isStarted: boolean; isRunning: boolean; logs: string[]; onClearLog: () => void; onPause: () => void; onPlay: () => void; onRefresh: () => void; onReset: () => void; onStart: () => void; onStepBackwards: () => void; onStepForwards: () => void; point: undefined | TypePoint; snake: undefined | TypeSnake; values: undefined | TypeValues; xLength: number; yLength: number; }>({ history: [], isStarted: false, isRunning: false, logs: [], onClearLog: () => {}, onPause: () => {}, onPlay: () => {}, onRefresh: () => {}, onReset: () => {}, onStart: () => {}, onStepBackwards: () => {}, onStepForwards: () => {}, point: undefined, snake: undefined, values: undefined, xLength: 15, yLength: 15, }); const Snake: React.FC = (props) => { const { solution, timeout = 1000, worker, xLength = 15, yLength = 15, ...rest } = props; const refAnimationFrame = React.useRef(); const refSolutionRunner = React.useRef(); const [isStarted, setIsStarted] = React.useState(false); const [isRunning, setIsRunning] = React.useState(false); const [history, setHistory] = React.useState([]); const [logs, setLogs] = React.useState([]); const [values, setValues] = React.useState(); const level = history[history.length - 1]; const snake = level && level.snake; const point = level && level.point; const handleReset = () => { if (refAnimationFrame.current) { cancelAnimationFrame(refAnimationFrame.current); } setIsStarted(false); setIsRunning(false); setValues(undefined); setHistory([]); }; const handleLog = (log: string) => { setLogs([log, ...logs]); }; const runSolution = React.useCallback(() => { if (point && snake) { refSolutionRunner.current?.run({ fn: solution, env: { xMax: xLength, yMax: yLength, snake: snake, point: point, }, }); } else if (snake.length === (xLength * yLength)) { setIsRunning(false); handleLog('🎉 You have conquered Snake! 🎉'); } }, [solution, point, snake]); const moveSnake = React.useCallback(() => { if (!Array.isArray(values)) { setIsRunning(false); return handleLog('🔥 There were no heuristic values to calculate a move 🔥'); } if (!snake || !point) { setIsRunning(false); return handleLog('Press start!'); } const cells = getSurroundingCells(xLength, yLength, snake); let nextValue; let nextCell: TypeCell | undefined; for (let i = 0; i < cells.length; i++) { if (snake.length === (xLength * yLength) - 1 && cells.length === 2) { if (isCellIncluded([cells[i]], point)) { nextValue = values[cells[i][1]][cells[i][0]]; nextCell = cells[i]; } } else { if (nextValue === undefined || values[cells[i][1]][cells[i][0]] < nextValue) { nextValue = values[cells[i][1]][cells[i][0]]; nextCell = cells[i]; } } } if (!nextCell) { setIsRunning(false); return handleLog('The 🐍 did not reach the point. There were no valid cells to move to.'); } if (isCellIncluded([nextCell], point) && snake) { setHistory(createBlock(xLength, yLength, moveForwards(history, nextCell, true))); } else { setHistory(moveForwards(history, nextCell)); } }, [values, snake, point]); React.useEffect(() => { refSolutionRunner.current = new SolutionRunner({ timeout, worker }); return () => { refSolutionRunner.current?.destroy(); }; }, [timeout, worker]); React.useEffect(() => { if (refSolutionRunner.current) { refSolutionRunner.current.onMessage = ({ values }) => { if (isStarted) { setValues(values); } }; refSolutionRunner.current.onError = ({ message }) => { setIsRunning(false); handleLog(message); }; if (!isStarted) { refSolutionRunner.current.reset(); } } }, [isStarted, isRunning]); React.useEffect(() => { if (!point || !snake) { setValues(undefined); setHistory(createBlock(xLength, yLength, history)); } }, [point, snake]); React.useEffect(() => { if (isStarted) { runSolution(); } }, [isStarted, point, snake]); React.useEffect(() => { if (isRunning && values) { refAnimationFrame.current = requestAnimationFrame(() => { moveSnake(); }); } }, [isRunning, values]); return ( setLogs([]), onPause: () => setIsRunning(false), onPlay: () => setIsRunning(true), onRefresh: () => runSolution(), onReset: () => handleReset(), onStart: () => setIsStarted(true), onStepBackwards: () => setHistory(moveBackwards(history)), onStepForwards: () => moveSnake(), point: point, snake: snake, values: values, xLength: xLength, yLength: yLength, } } /> ); }; export default Snake;