Mercurial
diff react_games/src/Robot/main.tsx @ 37:fb9bcd3145cb
[ReactGames] Few games I made using react just to practice few things.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 01 Dec 2025 20:22:47 -0800 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/react_games/src/Robot/main.tsx Mon Dec 01 20:22:47 2025 -0800 @@ -0,0 +1,180 @@ +/** + * Scenario + * Design a miniature browser game in which a single “robot” sprite navigates a + * rectangular grid to reach a goal while avoiding obstacles. Everything—from bootstrapping the project + * to shipping a playable, restartable game—must be accomplished within one hour. + * + * + * Grid 10 x 10 + * Robot top left corner + * Obstical is going to be 10 + * + * one of the grid is going to be the goal + * + * Movement logic keyboard or WASD okay + */ + +import React, { CSSProperties, memo, useEffect, useReducer } from "react"; + +enum CellValue { + PLAYER, + GOAL, + OBSTICAL, + EMPTY +} + +interface Cell { + value: CellValue; +} + +interface RowProp { + rowValue: Cell[]; +} + +interface CellComponentProp { + cell: Cell; +} + +enum GameStatus { + PLAYABLE, + WON, +} + +interface RobotState { + x: number; + y: number, +} + +interface GameState { + board: Cell[][]; + status: GameStatus; + robotState: RobotState; +} + +enum GameActionType { + MOVE, + RESET, +} + +type GameAction = {type: GameActionType.MOVE, command: string } | { type: GameActionType.RESET } + +const MAX_WIDTH: number = 10; +const MAX_COLUMN: number = 10; +const BOARD: Cell[][] = Array.from( + { length: MAX_WIDTH }, + () => Array(MAX_COLUMN).fill({value: CellValue.EMPTY}) +); + +BOARD[0][0] = { + value: CellValue.PLAYER +} +BOARD[0][5] = { + value: CellValue.OBSTICAL +} +BOARD[6][5] = { + value: CellValue.GOAL +} + + +const getCellValue = (cell: Cell) => { + switch(cell.value) { + case CellValue.EMPTY: + return ( + <></> + ) + case CellValue.PLAYER: + return ( + <>🤖</> + ) + case CellValue.OBSTICAL: + return ( + <>🧱</> + ) + case CellValue.GOAL: + return ( + <>🪙</> + ) + } +} + +const CellComponent = memo(({cell}: CellComponentProp) => { + const cellStyle: CSSProperties = { + padding: "10px", + width: "50px", + height: "50px", + border: "1px solid #ccc", + } + return ( + <div style={cellStyle}> + {getCellValue(cell)} + </div> + ) +}) + +const Row = memo(({rowValue}: RowProp) => { + const rowStyle: CSSProperties = { + display: "flex", + } + return ( + <div style={rowStyle}> + {rowValue.map((cell, idx) => {return <CellComponent key={idx} cell={cell} />})} + </div> + ) +}) + +function boardReducer(state: GameState, action: GameAction): GameState { + if (action.type === GameActionType.RESET) return INITIAL_GAME_STATE; + + const { x, y } = state.robotState; + let newX = x; + let newY = y; + + switch (action.command) { + case "W": newX = Math.max(0, x - 1); break; + case "A": newY = Math.max(0, y - 1); break; + case "S": newX = Math.min(MAX_WIDTH - 1, x + 1); break; + case "D": newY = Math.min(MAX_COLUMN - 1, y + 1); break; + default: return state; + } + + const newBoard = state.board.map(row => row.slice()); + + newBoard[x][y] = { value: CellValue.EMPTY }; // old spot + newBoard[newX][newY] = { value: CellValue.PLAYER }; // new spot + + return { + board: newBoard, + status: GameStatus.PLAYABLE, + robotState: { x: newX, y: newY }, + }; +} + +const INITIAL_GAME_STATE: GameState = { + board: BOARD, + status: GameStatus.PLAYABLE, + robotState: { x: 0, y: 0 }, +} + +const Current = () => { + const [state, dispatch] = useReducer(boardReducer, INITIAL_GAME_STATE); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + dispatch({type: GameActionType.MOVE, command: event.key.toUpperCase()}) + }; + + document.addEventListener("keydown", handleKeyDown); + }, []) + + + + return ( + <> + {state.board.map((row, idx) => { return <Row key={idx} rowValue={row} />})} + </> + ); +} + +export { + Current +}