diff react_games/src/Connectfour/latest.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 5e6a5d3c6868
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/react_games/src/Connectfour/latest.tsx	Mon Dec 01 20:22:47 2025 -0800
@@ -0,0 +1,219 @@
+import { CSSProperties, memo, useCallback, useReducer } from "react";
+import ReactDOM from "react-dom/client";
+
+/**
+ * Connect4 game is also known as Four Up, Plot Four, Find Four, Captain’s Mistress, Four in a Row, Drop Four, and Gravitrips in the Soviet Union.
+ * It is a two-player connection board game, in which the players choose a color and then take turns dropping colored discs into a seven-column, six-row vertically suspended grid.
+ * There could be human and bot players.
+ * The pieces fall straight down, occupying the lowest available space within the column.
+ * The objective of the game is to be the first to form a horizontal, vertical, or diagonal line of four of one’s own discs. Connect Four is a solved game.
+ * The first player can always win by playing the right moves.
+ *
+ */
+
+interface Cell {
+  value: CellValue;
+  isHighlighted: boolean;
+}
+
+type CellValue = Player | "";
+
+type Board = Cell[][];
+
+enum Player {
+  RED="🔴",
+  BLUE="🔵",
+}
+
+enum GameStatus {
+  PLAYABLE = "Your Turn!",
+  RED_WON = "RED WON!",
+  BLUE_WON = "BLUE WON!",
+}
+
+interface GameState {
+  board: Board;
+  player: Player;
+  status: GameStatus;
+}
+
+const MAX_ROW = 6;
+const MAX_COL = 7;
+
+const constructGameState = (): GameState => {
+  return {
+    board: Array.from(
+      { length: MAX_ROW }, () =>
+        Array.from({ length: MAX_COL }, (): Cell => ({ value: "", isHighlighted: false }) )),
+    player: Player.RED,
+    status: GameStatus.PLAYABLE
+  }
+}
+
+interface CellComponentProp {
+  cell: Cell;
+  placeDispatch: (col: number) => void;
+  col: number;
+}
+
+interface StylesProp {
+  board: CSSProperties;
+  cell: (isHighlighted: boolean) => CSSProperties;
+  circle: CSSProperties;
+}
+
+const styles: StylesProp = {
+  board: {
+    display: "grid",
+    gridTemplateColumns: `repeat(${MAX_COL}, 1fr)`,
+    width: 600,
+  },
+  cell: (isHighlighted: boolean) => ({
+    width: "100%",
+    display: "flex",
+    justifyContent: "center",
+    alignItems: "center",
+    aspectRatio: "1 / 1",
+    border: "1px solid black",
+    backgroundColor: isHighlighted ? "blue": "lightblue",
+  }),
+  circle: {
+    width: "60%",
+    borderRadius: 50,
+    backgroundColor: "aqua",
+    aspectRatio: "1 / 1",
+    display: "flex",
+    justifyContent: "center",
+    alignItems: "center",
+  }
+}
+
+type Action =
+  | { type: "place", col: number }
+  | { type: "reset" };
+
+
+const DIRECTION = [
+  [0, 1],
+  [1, 0],
+  [1, 1],
+  [1, -1],
+]
+const calculateGameStatus = (board: Board): { status: GameStatus, winningIndex: number[][] } => {
+  for (let rowIdx = 0; rowIdx < MAX_ROW; rowIdx++) {
+    for (let colIdx = 0; colIdx < MAX_COL; colIdx++) {
+      if (board[rowIdx][colIdx].value === "") continue;
+
+      for (const [dr, dc] of DIRECTION) {
+        if (
+          board[rowIdx][colIdx].value === board[rowIdx+dr]?.[colIdx+dc]?.value &&
+          board[rowIdx][colIdx].value === board[rowIdx+(dr*2)]?.[colIdx+(dc*2)]?.value &&
+          board[rowIdx][colIdx].value === board[rowIdx+(dr*3)]?.[colIdx+(dc*3)]?.value
+        ) {
+          return {
+            status: board[rowIdx][colIdx].value === Player.RED ? GameStatus.RED_WON : GameStatus.BLUE_WON,
+            winningIndex: [
+              [rowIdx,colIdx],
+              [rowIdx+dr,colIdx+dc],
+              [rowIdx+(dr*2),colIdx+(dc*2)],
+              [rowIdx+(dr*3),colIdx+(dc*3)],
+            ]
+          }
+        }
+      }
+    }
+  }
+
+  return {
+    status: GameStatus.PLAYABLE,
+    winningIndex: []
+  }
+}
+
+const gameStateReducer = (state: GameState, action: Action): GameState => {
+  if (state.status !== GameStatus.PLAYABLE) return state;
+
+  switch(action.type) {
+    case "place": {
+      let selectedRow: number = -1
+      for (let rowIdx = MAX_ROW - 1; rowIdx >= 0; rowIdx--) {
+        if (state.board[rowIdx][action.col].value === "") {
+          selectedRow = rowIdx;
+          break;
+        }
+      }
+      if (selectedRow === -1) return state;
+      let board = state.board.map(
+        (row, rowIdx) => rowIdx === selectedRow ? 
+          row.map((col, colIdx) => 
+            colIdx === action.col ? { ...col, value: state.player } : col) : row
+      );
+      const player = Player.RED === state.player ? Player.BLUE : Player.RED;
+      const {status, winningIndex} = calculateGameStatus(board);
+      if (winningIndex.length > 0) {
+        board = board.map(
+          (row, rowIdx) => 
+            row.map((col, colIdx): Cell => 
+              winningIndex.some(
+                ([r, c]) => (rowIdx === r && colIdx === c)) ?
+                  { ...col, isHighlighted: true } : col
+            )
+        );
+      }
+      return {
+        board,
+        player,
+        status,
+      };
+    }
+    case "reset":
+      return constructGameState();
+  }
+}
+
+const CellComponent = memo(({cell, col, placeDispatch}: CellComponentProp) => {
+  const handleOnClick = useCallback(() => {
+    placeDispatch(col);
+  },[col, placeDispatch]);
+
+  return (
+    <div style={styles.cell(cell.isHighlighted)} onClick={handleOnClick}>
+      <div style={styles.circle}>
+        {cell.value} 
+      </div>
+    </div>
+  )
+})
+
+const ConnectFour = () => {
+  const [state, dispatch] = useReducer(gameStateReducer, null, constructGameState);
+
+  const placeDispatch = useCallback(
+    (col: number) => {
+      dispatch({type: "place", col})
+    }, [dispatch]);
+
+  return (
+    <>
+      <h1> {state.status} </h1>
+      {state.status === GameStatus.PLAYABLE &&(<h2> {state.player} </h2>)}
+      <div style={styles.board}> 
+        {state.board.map(
+          (row, rowIdx) => row.map(
+            (cell, colIdx) => (
+              <CellComponent 
+                key={`${rowIdx}-${colIdx}`}
+                cell={cell}
+                col={colIdx}
+                placeDispatch={placeDispatch}/>
+            )
+          )
+        )}
+      </div>
+    </>
+  )
+} 
+
+export {
+  ConnectFour
+}