基于React+Redux+TypeScript的井字棋胜负判定实现求助
井字棋Redux状态管理下的胜负判定实现方案
问题背景
我是React和Redux的初学者,正在自学。我的井字棋项目使用Redux管理状态,希望在X或O连成一线时判定胜负。以下是我的项目代码:
store.ts
// store.ts import { configureStore } from '@reduxjs/toolkit'; import tactactoeReducer from '../components/ticTacToeSlice'; export const store = configureStore({ reducer: { tactactoeReducer }, }); export type AppDispatch = typeof store.dispatch; export type RootState = ReturnType<typeof store.getState>;
hooks.ts
// hooks.ts import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; import type { RootState, AppDispatch } from './store'; export const useAppDispatch = () => useDispatch<AppDispatch>(); export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
ticTacToeSlice.ts
// ticTacToeSlice.ts import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { RootState } from "../app/store"; type Cell = 'X' | 'O' | '-' export type Table = Array<Array<Cell>> export type Turn = Exclude<Cell, '-'> export interface State { table: Table turn: Turn } const initialState: State = { table: [ ['-', '-', '-'], ['-', '-', '-'], ['-', '-', '-'] ], turn: "X" } export interface TurnAction { i: number, j: number, } export const tactactoeSlice = createSlice({ name: "tactactoe", initialState, reducers: { turn: (state, action: PayloadAction<TurnAction>) => { state.table[action.payload.i][action.payload.j] = state.turn state.turn = state.turn == 'O' ? 'X' : 'O' } }, }); export const { turn } = tactactoeSlice.actions; export const turnSelector = (state: RootState) => state.tactactoeReducer; export default tactactoeSlice.reducer;
ticTacToe.tsx
// ticTacToe.tsx import { useEffect, useState } from "react"; import { useAppDispatch, useAppSelector } from "../app/hooks"; import { State, turn, turnSelector } from "./ticTacToeSlice"; function TicTacToe() { const [states, setStates] = useState<State>(); const selectedTurn = useAppSelector(turnSelector); const dispatch = useAppDispatch(); useEffect(() => { setStates(selectedTurn); }, [selectedTurn]); function handleTurn(i: number, j: number) { const constTurn = { i: i, j: j }; dispatch(turn(constTurn)); } return ( <div className="container"> <h3>current turn: {states?.turn}</h3> <table> { states?.table.map((row, index) => { return ( <tr> {row[0] === "-" ? ( <td className="paper-btn" onClick={() => { handleTurn(index, 0) }}>{row[0]}</td> ) : ( <td className="paper-btn">{row[0]}</td> )} {row[1] === "-" ? ( <td className="paper-btn" onClick={() => { handleTurn(index, 1) }}>{row[1]}</td> ) : ( <td className="paper-btn">{row[1]}</td> )} {row[2] === "-" ? ( <td className="paper-btn" onClick={() => { handleTurn(index, 2) }}>{row[2]}</td> ) : ( <td className="paper-btn">{row[2]}</td> )} </tr> ) }) } </table> </div> ); } export default TicTacToe;
我已理解胜负判定的索引逻辑:
[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]
但不知道如何结合Redux实现该判定功能。
实现方案
1. 更新Slice状态结构
首先在State接口中添加游戏状态字段,记录胜负状态:
export interface State { table: Table turn: Turn winner: Turn | null // 记录赢家,null表示未分出胜负 gameOver: boolean // 标记游戏是否结束 } // 更新初始状态 const initialState: State = { table: [ ['-', '-', '-'], ['-', '-', '-'], ['-', '-', '-'] ], turn: "X", winner: null, gameOver: false }
2. 编写胜负判定函数
在slice中添加工具函数,检查当前棋盘是否有玩家连成一线:
// 定义获胜组合(二维坐标形式) const winningCombos = [ // 横向 [[0,0], [0,1], [0,2]], [[1,0], [1,1], [1,2]], [[2,0], [2,1], [2,2]], // 纵向 [[0,0], [1,0], [2,0]], [[0,1], [1,1], [2,1]], [[0,2], [1,2], [2,2]], // 斜线 [[0,0], [1,1], [2,2]], [[0,2], [1,1], [2,0]] ]; const checkWinner = (table: Table): Turn | null => { for (const combo of winningCombos) { const [a, b, c] = combo; const cellA = table[a[0]][a[1]]; const cellB = table[b[0]][b[1]]; const cellC = table[c[0]][c[1]]; if (cellA !== '-' && cellA === cellB && cellB === cellC) { return cellA as Turn; } } return null; };
3. 修改turn reducer逻辑
落子后立即调用判定函数,更新游戏状态:
export const tactactoeSlice = createSlice({ name: "tactactoe", initialState, reducers: { turn: (state, action: PayloadAction<TurnAction>) => { // 游戏已结束则拦截落子操作 if (state.gameOver) return; const {i, j} = action.payload; // 确保点击空白格子 if (state.table[i][j] === '-') { state.table[i][j] = state.turn; // 检查是否有赢家 const winner = checkWinner(state.table); if (winner) { state.winner = winner; state.gameOver = true; } else { // 无赢家则切换回合 state.turn = state.turn === 'O' ? 'X' : 'O'; } } }, // 添加重置游戏的reducer resetGame: () => initialState }, }); export const { turn, resetGame } = tactactoeSlice.actions;
4. 更新组件逻辑
展示胜负结果,游戏结束后禁用格子点击:
import { useAppDispatch, useAppSelector } from "../app/hooks"; import { turn, turnSelector, resetGame } from "./ticTacToeSlice"; function TicTacToe() { // 直接从Redux获取状态,无需额外useState同步 const gameState = useAppSelector(turnSelector); const dispatch = useAppDispatch(); function handleTurn(i: number, j: number) { dispatch(turn({i, j})); } return ( <div className="container"> {gameState.gameOver ? ( <h3>赢家是: {gameState.winner}!</h3> ) : ( <h3>当前回合: {gameState.turn}</h3> )} {gameState.gameOver && ( <button onClick={() => dispatch(resetGame())}>重置游戏</button> )} <table> {gameState.table.map((row, rowIndex) => ( <tr key={rowIndex}> {row.map((cell, colIndex) => ( <td key={colIndex} className="paper-btn" onClick={() => { // 仅游戏未结束且格子为空时允许点击 if (!gameState.gameOver && cell === '-') { handleTurn(rowIndex, colIndex); } }} > {cell} </td> ))} </tr> ))} </table> </div> ); } export default TicTacToe;
关键说明
- 胜负判定逻辑放在Redux reducer中,确保状态更新的一致性
- 游戏结束后直接在reducer中拦截后续落子操作,避免无效状态变更
- 组件直接从Redux获取状态,简化状态同步逻辑
内容的提问来源于stack exchange,提问作者alex
相关产品推荐
相关产品推荐

