Mercurial
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 36:84672efec192 | 37:fb9bcd3145cb |
|---|---|
| 1 /** | |
| 2 * Scenario | |
| 3 * Design a miniature browser game in which a single “robot” sprite navigates a | |
| 4 * rectangular grid to reach a goal while avoiding obstacles. Everything—from bootstrapping the project | |
| 5 * to shipping a playable, restartable game—must be accomplished within one hour. | |
| 6 * | |
| 7 * | |
| 8 * Grid 10 x 10 | |
| 9 * Robot top left corner | |
| 10 * Obstical is going to be 10 | |
| 11 * | |
| 12 * one of the grid is going to be the goal | |
| 13 * | |
| 14 * Movement logic keyboard or WASD okay | |
| 15 */ | |
| 16 | |
| 17 import React, { CSSProperties, memo, useEffect, useReducer } from "react"; | |
| 18 | |
| 19 enum CellValue { | |
| 20 PLAYER, | |
| 21 GOAL, | |
| 22 OBSTICAL, | |
| 23 EMPTY | |
| 24 } | |
| 25 | |
| 26 interface Cell { | |
| 27 value: CellValue; | |
| 28 } | |
| 29 | |
| 30 interface RowProp { | |
| 31 rowValue: Cell[]; | |
| 32 } | |
| 33 | |
| 34 interface CellComponentProp { | |
| 35 cell: Cell; | |
| 36 } | |
| 37 | |
| 38 enum GameStatus { | |
| 39 PLAYABLE, | |
| 40 WON, | |
| 41 } | |
| 42 | |
| 43 interface RobotState { | |
| 44 x: number; | |
| 45 y: number, | |
| 46 } | |
| 47 | |
| 48 interface GameState { | |
| 49 board: Cell[][]; | |
| 50 status: GameStatus; | |
| 51 robotState: RobotState; | |
| 52 } | |
| 53 | |
| 54 enum GameActionType { | |
| 55 MOVE, | |
| 56 RESET, | |
| 57 } | |
| 58 | |
| 59 type GameAction = {type: GameActionType.MOVE, command: string } | { type: GameActionType.RESET } | |
| 60 | |
| 61 const MAX_WIDTH: number = 10; | |
| 62 const MAX_COLUMN: number = 10; | |
| 63 const BOARD: Cell[][] = Array.from( | |
| 64 { length: MAX_WIDTH }, | |
| 65 () => Array(MAX_COLUMN).fill({value: CellValue.EMPTY}) | |
| 66 ); | |
| 67 | |
| 68 BOARD[0][0] = { | |
| 69 value: CellValue.PLAYER | |
| 70 } | |
| 71 BOARD[0][5] = { | |
| 72 value: CellValue.OBSTICAL | |
| 73 } | |
| 74 BOARD[6][5] = { | |
| 75 value: CellValue.GOAL | |
| 76 } | |
| 77 | |
| 78 | |
| 79 const getCellValue = (cell: Cell) => { | |
| 80 switch(cell.value) { | |
| 81 case CellValue.EMPTY: | |
| 82 return ( | |
| 83 <></> | |
| 84 ) | |
| 85 case CellValue.PLAYER: | |
| 86 return ( | |
| 87 <>🤖</> | |
| 88 ) | |
| 89 case CellValue.OBSTICAL: | |
| 90 return ( | |
| 91 <>🧱</> | |
| 92 ) | |
| 93 case CellValue.GOAL: | |
| 94 return ( | |
| 95 <>🪙</> | |
| 96 ) | |
| 97 } | |
| 98 } | |
| 99 | |
| 100 const CellComponent = memo(({cell}: CellComponentProp) => { | |
| 101 const cellStyle: CSSProperties = { | |
| 102 padding: "10px", | |
| 103 width: "50px", | |
| 104 height: "50px", | |
| 105 border: "1px solid #ccc", | |
| 106 } | |
| 107 return ( | |
| 108 <div style={cellStyle}> | |
| 109 {getCellValue(cell)} | |
| 110 </div> | |
| 111 ) | |
| 112 }) | |
| 113 | |
| 114 const Row = memo(({rowValue}: RowProp) => { | |
| 115 const rowStyle: CSSProperties = { | |
| 116 display: "flex", | |
| 117 } | |
| 118 return ( | |
| 119 <div style={rowStyle}> | |
| 120 {rowValue.map((cell, idx) => {return <CellComponent key={idx} cell={cell} />})} | |
| 121 </div> | |
| 122 ) | |
| 123 }) | |
| 124 | |
| 125 function boardReducer(state: GameState, action: GameAction): GameState { | |
| 126 if (action.type === GameActionType.RESET) return INITIAL_GAME_STATE; | |
| 127 | |
| 128 const { x, y } = state.robotState; | |
| 129 let newX = x; | |
| 130 let newY = y; | |
| 131 | |
| 132 switch (action.command) { | |
| 133 case "W": newX = Math.max(0, x - 1); break; | |
| 134 case "A": newY = Math.max(0, y - 1); break; | |
| 135 case "S": newX = Math.min(MAX_WIDTH - 1, x + 1); break; | |
| 136 case "D": newY = Math.min(MAX_COLUMN - 1, y + 1); break; | |
| 137 default: return state; | |
| 138 } | |
| 139 | |
| 140 const newBoard = state.board.map(row => row.slice()); | |
| 141 | |
| 142 newBoard[x][y] = { value: CellValue.EMPTY }; // old spot | |
| 143 newBoard[newX][newY] = { value: CellValue.PLAYER }; // new spot | |
| 144 | |
| 145 return { | |
| 146 board: newBoard, | |
| 147 status: GameStatus.PLAYABLE, | |
| 148 robotState: { x: newX, y: newY }, | |
| 149 }; | |
| 150 } | |
| 151 | |
| 152 const INITIAL_GAME_STATE: GameState = { | |
| 153 board: BOARD, | |
| 154 status: GameStatus.PLAYABLE, | |
| 155 robotState: { x: 0, y: 0 }, | |
| 156 } | |
| 157 | |
| 158 const Current = () => { | |
| 159 const [state, dispatch] = useReducer(boardReducer, INITIAL_GAME_STATE); | |
| 160 | |
| 161 useEffect(() => { | |
| 162 const handleKeyDown = (event: KeyboardEvent) => { | |
| 163 dispatch({type: GameActionType.MOVE, command: event.key.toUpperCase()}) | |
| 164 }; | |
| 165 | |
| 166 document.addEventListener("keydown", handleKeyDown); | |
| 167 }, []) | |
| 168 | |
| 169 | |
| 170 | |
| 171 return ( | |
| 172 <> | |
| 173 {state.board.map((row, idx) => { return <Row key={idx} rowValue={row} />})} | |
| 174 </> | |
| 175 ); | |
| 176 } | |
| 177 | |
| 178 export { | |
| 179 Current | |
| 180 } |