view react_games/src/Connectfour/latest.tsx @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents 5e6a5d3c6868
children
line wrap: on
line source

import { CSSProperties, memo, useCallback, useReducer } from "react";

/**
 * 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
}