diff react_games/src/Connectfour/answer.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/Connectfour/answer.tsx	Mon Dec 01 20:22:47 2025 -0800
@@ -0,0 +1,141 @@
+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,
+}