Search code examples
reactjstypescriptreduxreact-reduxfrontend

declaring a winner in tic tac toe using redux and react typescript


I am a very beginner in react and redux and I am self studying.
States in my tic tac toe project, are handled by redux.
I wanted to declare the winner when X or O are aligned.
this is my code:

// 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

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

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

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;

I checked react.dev and many similars documentations for a solution.
I get the part which the algorithm is comparing my values by index in horizontal, vertical and diagonal lines like blow:

[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]

but I don't know how to do that due to my code which contains redux as an state manager.


Solution

  • I checked react.dev and many similars documentations for a solution. I get the part which the algorithm is comparing my values by index in horizontal, vertical and diagonal lines like below:

    but I don't know how to do that due to my code which contains redux as an state manager.

    The React tutorial has already defined a function (function calculateWinner(squares)) that takes an array of squares and returns the winner, if the game has been won. We can use that same function in a React and Redux app. Your question is about where we can use it.

    I would put this is a selector function, since it involves deriving a value (the winner) from data which you selected from your Redux store (the current board).

    You are storing your game state in a 2-dimensional 3x3 array, while the tutorial uses a 1D array of all 9 cells. This is not a problem as you can use .flat() to convert your data from 2D to 1D, as required by the calculateWinner function.

    function calculateWinner(squares) {
      // copy and paste from tutorial
    }
    
    // This selects the 2D array that you have in your redux state.
    export const selectSquares = (state: RootState) => state.tactactoeReducer.table;
    
    // This selects the current winner.
    export const selectWinner = createSelector(
      // First we select the raw data that we need from Redux.
      selectSquares,
      // Then we convert it to the output that we want.
      (squares) => {
        const squaresArray = squares.flat();
        return calculateWinner(squaresArray);
      }
    );
    

    Then use this selector function in your TicTacToe component.

    const winner = useAppSelector(selectWinner);
    

    Note: you do not need the useState or useEffect in your TicTacToe component. You can use the data directly from the useAppSelector hook.