Mercurial
view react_games/src/Connectfour/latest.tsx @ 71:75de5903355c
Giagantic changes that update Dowa library to be more align with stb style array and hashmap. Updated Seobeo to be caching on server side instead of file level caching. Deleted bunch of things I don't really use.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Sun, 28 Dec 2025 20:34:22 -0800 |
| 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 }