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
+}