view react_games/src/Connectfour/main.tsx @ 103:f6d2f2eaaf84

Removed unneeded files.
author June Park <parkjune1995@gmail.com>
date Sat, 03 Jan 2026 10:10:40 -0800
parents fb9bcd3145cb
children
line wrap: on
line source

import {
  useReducer, createContext, useContext, memo, Dispatch
} from 'react'

enum P { R = '🔴', Y = '🟡', _ = '' }

type Board = P[][]            // 6 rows × 7 cols
type Status = 'PLAYING' | 'TIE' | 'R_WON' | 'Y_WON'

type Action =
  | { type: 'DROP'; col: number }
  | { type: 'RESET' }

interface State {
  board: Board
  turn: P.R | P.Y
  status: Status
}

const ROWS = 6, COLS = 7
const emptyBoard = (): Board =>
  Array.from({ length: ROWS }, () => Array(COLS).fill(P._))

const init: State = { board: emptyBoard(), turn: P.R, status: 'PLAYING' }

function firstEmptyRow(board: Board, col: number): number | null {
  for (let r = ROWS - 1; r >= 0; r--) if (board[r][col] === P._) return r
  return null
}

function scanWinner(b: Board): Status {
  const lines = [
    [ 1,  0], [0, 1],   // vertical, horizontal
    [ 1,  1], [1, -1],  // two diagonals
  ]
  for (let r = 0; r < ROWS; r++)
    for (let c = 0; c < COLS; c++)
      if (b[r][c] !== P._)
        for (const [dr, dc] of lines)
          if (
            b[r + dr]?.[c + dc] === b[r][c] &&
            b[r + 2*dr]?.[c + 2*dc] === b[r][c] &&
            b[r + 3*dr]?.[c + 3*dc] === b[r][c]
          )
            return b[r][c] === P.R ? 'R_WON' : 'Y_WON'

  return b.flat().every(p => p !== P._) ? 'TIE' : 'PLAYING'
}


function reducer(s: State, a: Action): State {
  if (a.type === 'RESET') return init
  if (s.status !== 'PLAYING') return s                     // game over

  const row = firstEmptyRow(s.board, a.col)
  if (row == null) return s                                // full column

  // clone touched row only
  const newRow = [...s.board[row]]
  newRow[a.col] = s.turn
  const newBoard = s.board.map((r, i) => (i === row ? newRow : r))

  const nextTurn = s.turn === P.R ? P.Y : P.R
  const status   = scanWinner(newBoard)

  return { board: newBoard, turn: nextTurn, status }
}

const DispatchCtx = createContext<Dispatch<Action> | null>(null)
const useGameDispatch = () => {
  const d = useContext(DispatchCtx)
  if (!d) throw new Error('outside provider')
  return d
}

/* ------- Leaf ------- */
interface CellProps { v: P }
const Cell = memo<CellProps>(({ v }) => (
  <div style={{
    width: 52, height: 52, margin: 2, borderRadius: '50%',
    background: '#0e2a5a', display: 'grid', placeItems: 'center',
    fontSize: 30
  }}>
    {v}
  </div>
))

/* ------- Column button ------- */
const ColBtn = ({ col }: { col: number }) => {
  const dispatch = useGameDispatch()
  return (
    <button
      style={{ flex: 1, height: 20, cursor: 'pointer' }}
      onClick={() => dispatch({ type: 'DROP', col })}
    />
  )
}

/* ------- Board ------- */
const BoardView = memo(({ board }: { board: Board }) => (
  <div>
    {/* clickable top buttons */}
    <div style={{ display: 'flex' }}>
      {Array.from({ length: COLS }, (_, c) => <ColBtn key={c} col={c} />)}
    </div>

    {/* grid */}
    {board.map((row, r) => (
      <div key={r} style={{ display: 'flex' }}>
        {row.map((v, c) => <Cell key={c} v={v} />)}
      </div>
    ))}
  </div>
))

function ConnectFour() {
  const [state, dispatch] = useReducer(reducer, init)

  return (
    <DispatchCtx.Provider value={dispatch}>
      <h2 style={{ textAlign: 'center' }}>
        {state.status === 'PLAYING' && `Turn: ${state.turn}`}
        {state.status === 'TIE' && 'Tie game'}
        {state.status === 'R_WON' && 'Red wins!'}
        {state.status === 'Y_WON' && 'Yellow wins!'}
      </h2>

      <BoardView board={state.board} />

      {state.status !== 'PLAYING' && (
        <button style={{ marginTop: 16 }} onClick={() => dispatch({ type: 'RESET' })}>
          Play again
        </button>
      )}
    </DispatchCtx.Provider>
  )
}

export {
  ConnectFour,
}