PJH's Game World - 틱텍토

박정호·2022년 12월 18일
0

Game Project

목록 보기
10/13
post-thumbnail

🚀 Start

먼저 1빙고하는 사람이 이기는 게임이다. 한줄 판별, 유저 변경, 모달창 구현을 하였다. 목업 툴을 통해 대략적으로 구상해본 화면이다.



✔️ Table 생성

initialState로 설정된 tableData를 테이블로 출력하기.

1️⃣ tableDatatableprops로 전달

//MineSearch.tsx

interface ReducerState {
  ...
  tableData: string[][];
  ...
}

const initialState: ReducerState = {
  ...
  tableData: [
    ['', '', ''],
    ['', '', ''],
    ['', '', ''],
  ],
  
};

<Table
    onClick={onClickTable}
    tableData={tableData}
    dispatch={dispatch}
          />

2️⃣ tableData 길이만큼 테이블로 생성.

// Table.tsx
const Table = ({ tableData, dispatch }: Props) => {
  return (
    <table>
      {Array(tableData.length)
        .fill(null)
        .map((tr, i) =>
          useMemo(
            () => (
              <Tr
                key={i}
                dispatch={dispatch}
                rowIndex={i}
                rowData={tableData[i]}
              />
            ),
            [tableData[i]]
          )
        )}
    </table>
  );
};

export default Table;

3️⃣ 테이블 가로길이 생성.

// Tr.tsx
const Tr = ({ rowData, rowIndex, dispatch }: Props) => {
  console.log('tr rendered');

  return (
    <tr>
      {Array(rowData.length)
        .fill(null)
        .map((td, i) =>
          useMemo(
            () => (
              <Td
                key={i}
                dispatch={dispatch}
                rowIndex={rowIndex}
                cellIndex={i}
                cellData={rowData[i]}
              >
                {''}
              </Td>
            ),
            [rowData[i]]
          )
        )}
    </tr>
  );
};

export default memo(Tr);

4️⃣ 테이블 세로길이 생성.

// Td.tsx
const Td = ({ rowIndex, cellIndex, dispatch, cellData }: Props) => {
  console.log('td rendered');

  const onClickTd = useCallback(() => {
    console.log(rowIndex, cellIndex);
    if (cellData) {
      return;
    }
    dispatch(clickCell(rowIndex, cellIndex));
  }, [cellData]);

  return (
    <td onClick={onClickTd}>
      {cellData === 'O' && (
        <div>{cellData}</div>
      )}
      {cellData === 'X' && (
        <div>{cellData}</div>
      )}
    </td>
  );
};

export default memo(Td);


✔️ 테이블 한칸 클릭하기

1️⃣ 만약 이미 칸에 값이 존재한다면 return. 즉, 이미 클릭한 칸이라면 return.

2️⃣ clickCell라는 actiondispatch

// Td.tsx

...
const onClickTd = useCallback(() => {
    if (cellData) return; // 1️⃣ 번
  
    dispatch(clickCell(rowIndex, cellIndex)); // 2️⃣ 번
  }, [cellData]);
...
<td onClick={onClickTd}>...</td>

3️⃣ 기존의 테이블의 row, cell 값은 클릭되어 변경된 row, cell값으로 변경

4️⃣ recentCell 값이 업데이트.

// TicTacToe.tsx

export const CLICK_CELL = 'CLICK_CELL' as const;
...

interface ClickCellAction {
  type: typeof CLICK_CELL;
  row: number;
  cell: number;
}

...

export const clickCell = (row: number, cell: number): ClickCellAction => {
  return { type: CLICK_CELL, row, cell };
};

...

const reducer = (state: ReducerState, action: ReducerActions): ReducerState => {

  case CLICK_CELL: {
      // 3️⃣ 번
      const tableData = [...state.tableData];
      tableData[action.row] = [...tableData[action.row]];
      tableData[action.row][action.cell] = state.turn;
      return {
        ...state,
        tableData,
        recentCell: [action.row, action.cell], // 4️⃣ 번
      };
    }


}


✔️ O,X 값 전환

1️⃣ recentCell의 값 변경에 따라 CHANGE_TURN dispatch.

// TicTacToe.tsx
 
useEffect(() => {
    const [row, cell] = recentCell;
     
  	...
    else {
        dispatch({ type: CHANGE_TURN });
      }
    }
  }, [recentCell]);

2️⃣ 값이 O 였다면 X로, 그렇지 않고 X였다면 O로 변경.

// TicTacToe.tsx

export const CHANGE_TURN = 'CHANGE_TURN' as const;

interface ChangeTurnAction {
  type: typeof CHANGE_TURN;
}


const reducer = (state: ReducerState, action: ReducerActions): ReducerState => {

 case CHANGE_TURN: {
      return {
        ...state,
        turn: state.turn === 'O' ? 'X' : 'O', // 2️⃣ 번
      };
    }


}


✔️ 빙고 한줄 판별

1️⃣ 한줄이 모두 같은 값으로 일치한다면 win은 true. 즉, 승자가 나온 것.

2️⃣ 승리시 승자를 판별하는 SET_WINNER dispatch. 게임을 다시 리셋하는 RESET_GAME dispatch.

3️⃣ 만약 한줄을 만족하는 값이 존재하지 않을 경우 무승부로 판별.


useEffect(() => {
   		...
    let win = false;
    if ( // 1️⃣ 번
      tableData[row][0] === turn &&
      tableData[row][1] === turn &&
      tableData[row][2] === turn
    ) {
      win = true;
    }
    if ( // 1️⃣ 번
      tableData[0][cell] === turn &&
      tableData[1][cell] === turn &&
      tableData[2][cell] === turn
    ) {
      win = true;
    }
    if ( // 1️⃣ 번
      tableData[0][0] === turn &&
      tableData[1][1] === turn &&
      tableData[2][2] === turn
    ) {
      win = true;
    }
    if (
      tableData[0][2] === turn &&
      tableData[1][1] === turn &&
      tableData[2][0] === turn
    ) {
      win = true;
    }
    
    if (win) { // 2️⃣ 번
      // 승리시
      dispatch({ type: SET_WINNER, winner: turn });
      dispatch({ type: RESET_GAME });
    } else {
      let all = true; // all이 true면 무승부
      tableData.forEach(row => {
        // 무승부 검사
        row.forEach(cell => {
          if (!cell) {
            all = false;
          }
        });
      });
      if (all) { // 3️⃣ 번
        dispatch({ type: RESET_GAME });
        dispatch(setWinner('무승부'));
      } else {
        dispatch({ type: CHANGE_TURN });
      }
    }
  }, [recentCell]);


✔️ 승자 판별

위에서 dispatch되며 winner에 전달받은 turn값O 또는 X가 저장.

export const SET_WINNER = 'SET_WINNER';

interface SetWinnerAction {
  type: typeof SET_WINNER;
  winner: 'O' | 'X' | '무승부';
}

const setWinner = (winner: 'O' | 'X' | '무승부'): SetWinnerAction => {
  return { type: SET_WINNER, winner };
};


const reducer = (state: ReducerState, action: ReducerActions): ReducerState => {
	...
    case SET_WINNER:
      return {
        ...state,
        winner: action.winner,
      };

}

const TicTacToe = () => {
    const [state, dispatch] = useReducer<Reducer<ReducerState, ReducerActions>>(
    reducer,
    initialState
  );
  const { tableData, turn, winner, recentCell } = state;
  
  return ( // reducer에 저장된 winner값을 모달창에 전달하여 winner 출력.
   {winner && <Modal tictactoe={'틱텍토'} tictactoeWinner={winner} />}
  )

}

🖥 실제 구현 화면

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글