Mercurial
diff react_games/src/LightsOut/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/LightsOut/main.tsx Mon Dec 01 20:22:47 2025 -0800 @@ -0,0 +1,164 @@ +import { ActionDispatch, CSSProperties, memo, useCallback, useReducer } from 'react'; + +/** + * create lights out game + * + * - 5 x 5 grid + * - when click it will turn on lights on top left right bottom. + * - count number of moves + * - timers + * - when every thing is on then tthey win + */ + +interface Cell { + value: boolean; // true on and false off. +} + +type Board = Cell[][]; + +enum GameStatus { + PLAYABLE, + WON, +} + +interface GameState { + board: Board; + status: GameStatus; + moves: number; +} + +const MAX_COL = 5; +const MAX_ROW = 5; +const DIRECTION = [ + [0, 0], + [0, 1], + [1, 0], + [0, -1], + [-1, 0], +] +const constructGameState = (): GameState => { + const board = Array.from( + { length: MAX_ROW }, + () => Array.from({ length: MAX_COL}, + () => ({ value: false }) + ) + ); + + for (let i = 0; i < 30; i++) { + const r = Math.floor(Math.random() * MAX_ROW); + const c = Math.floor(Math.random() * MAX_COL); + for (const [dr, dc] of DIRECTION) { + if (board[r+dr]?.[c+dc]) { + board[r+dr][c+dc].value = !board[r+dr]?.[c+dc].value + } + } + } + return { + board, + status: GameStatus.PLAYABLE, + moves: 0, + } +} + + +interface Styles { + board: CSSProperties; + cell: (isLightOn: boolean) => CSSProperties; +} + +const styles: Styles = { + board: { + display: "grid", + gridTemplateColumns: `repeat(${MAX_COL}, 1fr)`, + width: MAX_COL * 30, + }, + cell: (isLightOn: boolean) => ({ + width: "100%", + aspectRatio: "1 / 1", + border: `1px solid #222`, + backgroundColor: isLightOn ? "white" : "black", + transition: "background-color 120ms ease", + cursor: "pointer", + }) +} + +interface CellComponentProp { + cellValue: Cell; + rowPos: number; + colPos: number; + dispatch: ActionDispatch<[action: Action]>; +} + +const CellComponent = memo(({ cellValue, rowPos, colPos, dispatch }: CellComponentProp) => { + const onClickHandler = useCallback(()=>{ + dispatch({type: "place", newRowPos: rowPos, newColPos: colPos}); + }, [rowPos, colPos, dispatch]); + + return ( + <div onClick={onClickHandler} style={styles.cell(cellValue.value)}></div> + ) +}) + +type Action = + | { type: "place", newRowPos: number, newColPos: number } + | { type: "reset" } + +const gameStateReducer = (state: GameState, action: Action): GameState => { + if (state.status === GameStatus.WON) return state; + switch(action.type) { + case "place": + const allDirection: number[][] = []; + for (const [dr, dc] of DIRECTION) { + allDirection.push([action.newRowPos + dr, action.newColPos + dc]); + } + const newBoard: Board = state.board.map( + (row, rowIdx) => + row.map((col, colIdx) => + (allDirection.some( + ([nr, nc]) => rowIdx === nr && colIdx === nc + )) ? { ...col, value: !col.value } : col) + ) + const newStatus = + (newBoard.flat().filter((cell) => cell.value).length === MAX_COL * MAX_ROW) ? + GameStatus.WON : GameStatus.PLAYABLE; + + return { + ...state, + status: newStatus, + board: newBoard, + moves: state.moves + 1, + }; + case "reset": + return constructGameState(); + } +} + +const LightsOut = () => { + const [gameState, dispatch] = useReducer(gameStateReducer, null, constructGameState); + return ( + <> + <h1> Game Status {gameState.status}</h1> + <h2> Number of moves so far {gameState.moves}</h2> + <div style={styles.board}> + { + gameState.board.map( + (row, rowIdx) => + row.map( + (cellValue, colIdx) => + (<CellComponent + key={`${rowIdx}-${colIdx}`} + cellValue={cellValue} + rowPos={rowIdx} + colPos={colIdx} + dispatch={dispatch}/>) + ) + ) + } + </div> + </> + ); +} + +export { + LightsOut +}