comparison 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
comparison
equal deleted inserted replaced
36:84672efec192 37:fb9bcd3145cb
1 import {
2 useReducer, createContext, useContext, memo, Dispatch
3 } from 'react'
4
5 enum P { R = '🔴', Y = '🟡', _ = '' }
6
7 type Board = P[][] // 6 rows × 7 cols
8 type Status = 'PLAYING' | 'TIE' | 'R_WON' | 'Y_WON'
9
10 type Action =
11 | { type: 'DROP'; col: number }
12 | { type: 'RESET' }
13
14 interface State {
15 board: Board
16 turn: P.R | P.Y
17 status: Status
18 }
19
20 const ROWS = 6, COLS = 7
21 const emptyBoard = (): Board =>
22 Array.from({ length: ROWS }, () => Array(COLS).fill(P._))
23
24 const init: State = { board: emptyBoard(), turn: P.R, status: 'PLAYING' }
25
26 function firstEmptyRow(board: Board, col: number): number | null {
27 for (let r = ROWS - 1; r >= 0; r--) if (board[r][col] === P._) return r
28 return null
29 }
30
31 function scanWinner(b: Board): Status {
32 const lines = [
33 [ 1, 0], [0, 1], // vertical, horizontal
34 [ 1, 1], [1, -1], // two diagonals
35 ]
36 for (let r = 0; r < ROWS; r++)
37 for (let c = 0; c < COLS; c++)
38 if (b[r][c] !== P._)
39 for (const [dr, dc] of lines)
40 if (
41 b[r + dr]?.[c + dc] === b[r][c] &&
42 b[r + 2*dr]?.[c + 2*dc] === b[r][c] &&
43 b[r + 3*dr]?.[c + 3*dc] === b[r][c]
44 )
45 return b[r][c] === P.R ? 'R_WON' : 'Y_WON'
46
47 return b.flat().every(p => p !== P._) ? 'TIE' : 'PLAYING'
48 }
49
50
51 function reducer(s: State, a: Action): State {
52 if (a.type === 'RESET') return init
53 if (s.status !== 'PLAYING') return s // game over
54
55 const row = firstEmptyRow(s.board, a.col)
56 if (row == null) return s // full column
57
58 // clone touched row only
59 const newRow = [...s.board[row]]
60 newRow[a.col] = s.turn
61 const newBoard = s.board.map((r, i) => (i === row ? newRow : r))
62
63 const nextTurn = s.turn === P.R ? P.Y : P.R
64 const status = scanWinner(newBoard)
65
66 return { board: newBoard, turn: nextTurn, status }
67 }
68
69 const DispatchCtx = createContext<Dispatch<Action> | null>(null)
70 const useGameDispatch = () => {
71 const d = useContext(DispatchCtx)
72 if (!d) throw new Error('outside provider')
73 return d
74 }
75
76 /* ------- Leaf ------- */
77 interface CellProps { v: P }
78 const Cell = memo<CellProps>(({ v }) => (
79 <div style={{
80 width: 52, height: 52, margin: 2, borderRadius: '50%',
81 background: '#0e2a5a', display: 'grid', placeItems: 'center',
82 fontSize: 30
83 }}>
84 {v}
85 </div>
86 ))
87
88 /* ------- Column button ------- */
89 const ColBtn = ({ col }: { col: number }) => {
90 const dispatch = useGameDispatch()
91 return (
92 <button
93 style={{ flex: 1, height: 20, cursor: 'pointer' }}
94 onClick={() => dispatch({ type: 'DROP', col })}
95 />
96 )
97 }
98
99 /* ------- Board ------- */
100 const BoardView = memo(({ board }: { board: Board }) => (
101 <div>
102 {/* clickable top buttons */}
103 <div style={{ display: 'flex' }}>
104 {Array.from({ length: COLS }, (_, c) => <ColBtn key={c} col={c} />)}
105 </div>
106
107 {/* grid */}
108 {board.map((row, r) => (
109 <div key={r} style={{ display: 'flex' }}>
110 {row.map((v, c) => <Cell key={c} v={v} />)}
111 </div>
112 ))}
113 </div>
114 ))
115
116 function ConnectFour() {
117 const [state, dispatch] = useReducer(reducer, init)
118
119 return (
120 <DispatchCtx.Provider value={dispatch}>
121 <h2 style={{ textAlign: 'center' }}>
122 {state.status === 'PLAYING' && `Turn: ${state.turn}`}
123 {state.status === 'TIE' && 'Tie game'}
124 {state.status === 'R_WON' && 'Red wins!'}
125 {state.status === 'Y_WON' && 'Yellow wins!'}
126 </h2>
127
128 <BoardView board={state.board} />
129
130 {state.status !== 'PLAYING' && (
131 <button style={{ marginTop: 16 }} onClick={() => dispatch({ type: 'RESET' })}>
132 Play again
133 </button>
134 )}
135 </DispatchCtx.Provider>
136 )
137 }
138
139 export {
140 ConnectFour,
141 }