Mercurial
view react_games/src/Tictactoe/main.tsx @ 150:c37490913530
[Config] Updated .vimrc files and made command for ctags movements.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Sat, 10 Jan 2026 13:30:28 -0800 |
| parents | fb9bcd3145cb |
| children |
line wrap: on
line source
import { CSSProperties, memo, useCallback, useReducer } from 'react'; enum GameStatus { PLAYABLE, WON, TIE, } enum Player { O = "O", X = "X", } type CellValue = Player | ""; interface Cell { value: CellValue; isWonSquare: boolean; } type Board = Cell[][] interface GameState { board: Board; status: GameStatus; player: Player; winner?: Player | null; } const MAX_ROW = 3; const MAX_COL = 3; const getInitialGameState = (): GameState => { return { board: Array.from({ length: MAX_ROW }, () => Array(MAX_COL).fill( { value: "", isWonSquare: false, }) as Cell[], ), status: GameStatus.PLAYABLE, player: Player.X, } }; interface CellComponentProp { cell: Cell; row: number; col: number; moveDispatch: (r: number, c: number) => void; } const cellCssStyle = (isWinningSquare: boolean): CSSProperties => ({ display: "flex", justifyContent: "center", alignItems: "center", width: "100%", aspectRatio: "1 / 1", background: isWinningSquare ? "blue" : "lightblue", border: "1px solid black" }) const CellComponent = memo(({ cell, row, col, moveDispatch }: CellComponentProp) => { const handleOnClick = useCallback(() => { moveDispatch(row, col); }, [row,col]) return ( <div style={cellCssStyle(cell.isWonSquare)} onClick={handleOnClick}> {cell.value} </div> ) }) const boardCssStyle: CSSProperties = { display: "grid", gridTemplateColumns: `repeat(${MAX_COL}, 1fr)`, width: 500, } enum ActionType { PLACE, RESET, }; type Action = | { type: ActionType.PLACE, row: number, col: number } | { type: ActionType.RESET }; type GameStatusAndPosition = | { newStatus: GameStatus.WON, winningPositions: number[][]} | { newStatus: GameStatus.TIE } | { newStatus: GameStatus.PLAYABLE } const getGameStatus = (board: Board): GameStatusAndPosition => { const direction = [ [0, 1], [1, 0], [1, 1], [1, -1], ]; let numberOfFilledValue = 0; for (let row=0; row<MAX_ROW; row++) { for (let col=0; col<MAX_COL; col++) { if (board[row][col].value === "") continue; numberOfFilledValue++; for (const [dr, dc] of direction) { if ( board[row][col].value === board[row+dr]?.[col+dc]?.value && board[row][col].value === board[row+(dr*2)]?.[col+(dc*2)]?.value ) { return { newStatus: GameStatus.WON, winningPositions: [ [row, col], [row+dr, col+dc], [row+dr+dr, col+dc+dc], ] } } } } } return numberOfFilledValue === MAX_ROW * MAX_COL ? { newStatus: GameStatus.TIE } : { newStatus: GameStatus.PLAYABLE }; } const gameStateReducer = (state: GameState, action: Action): GameState => { if (state.status === GameStatus.WON) return state; switch(action.type) { case ActionType.PLACE: if (state.board[action.row][action.col].value) return state; let newBoard: Board = state.board.map( (row, rowIdx) => rowIdx === action.row ? row.map((col, c) => c === action.col ? {...col, value: state.player} : col ) : row ); const res = getGameStatus(newBoard); let winner: Player | null = null; if (res.newStatus === GameStatus.WON) { winner = state.player; newBoard = newBoard.map((row, rowIdx) => row.map((col, colIdx) => res.winningPositions.some(([wr, wc]) => wr === rowIdx && wc === colIdx) ? { ...col, isWonSquare: true } : col ) ) } return { ...state, board: newBoard, status: res.newStatus, player: Player.X === state.player ? Player.O : Player.X, winner, }; case ActionType.RESET: return getInitialGameState(); } } const TicTacToe = () => { const [state, dispatch] = useReducer( gameStateReducer, null, getInitialGameState); const moveDispatch = useCallback( (rowIdx: number, colIdx: number) => dispatch({type: ActionType.PLACE, row: rowIdx, col: colIdx}), []) return ( <> <h1> TicTacToe </h1> <h2> { state.status === GameStatus.PLAYABLE ? `Player ${state.player} Turn!` : state.status === GameStatus.TIE ? "Game is tied! Please reset" : `Player ${state.winner} has won!` } </h2> <div style={boardCssStyle}> { state.board.map((row, rowIdx) => { return row.map((cell, colIdx) => { const cellComponentProp: CellComponentProp = { cell, row: rowIdx, col: colIdx, moveDispatch, }; return ( <CellComponent key={`${rowIdx}-${colIdx}`} {...cellComponentProp} />) }) }) } </div> <button onClick={() => dispatch({ type: ActionType.RESET })}>Reset</button> </> ); } export { TicTacToe, }