TIL

2024.02.22 TIL #tic-tac-toe 게임

inz1234 2024. 2. 22. 23:25

오늘 틱택토 게임 만들기를 해봤다.

게임을 만드는 과정 중 어려웠던 부분을 기록하려한다.

 

내가 생각했던 로직

1.  9개의 숫자 중 3개로 빙고가 될 수 있을 만한 배열들을 추려놓는다.

2. 9개의 버튼 중에 철수(X)와 영희(O)가 차례대로 누른다.

3. 철수가 누르는 버튼의 숫자(X가 배정된 숫자 = xList)와 영희가 누른 버튼의 숫자(O가 배정된 숫자 = oList)를 각각 다른 배열에 따로 담아둔다.

4.  xList와 oList 중에서 3개를 골라서 나올 수 있는 모든 경우의 수를 구한다.

5. 만약 xList(또는 oList)에서 무작위로 3개를 고른 배열들 중 1번의 빙고가 될 수 있는 배열을 포함하면 X(철수) 또는 O(영희)가 이긴다.

 

나의 코드 

import { useState } from "react";

function App() {
  const [buttons, setButtons] = useState(["", "", "", "", "", "", "", "", ""]);

  const dapList = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [2, 4, 6],
    [0, 4, 8],
  ];

  const [whosTurn, setTurn] = useState("X");

  const [xList, setXList] = useState([]);
  const [oList, setOList] = useState([]);

  const randomSelect = (list) => {
    let arr = [];
    let n = list.length;
    for (let i = 0; i < n - 2; i++) {
      for (let j = i + 1; j < n - 1; j++) {
        for (let k = j + 1; k < n; k++) {
          arr.push([list[i], list[j], list[k]]);
        }
      }
    }
    return arr;
  };

  const whosTheWinner = (list, who) => {
    const combilist = randomSelect(list);
    if (list.length > 2) {
      for (let i = 0; i < dapList.length; i++) {
        if (
          combilist.some((arr) => arr.every((num) => dapList[i].includes(num)))
        ) {
          alert(`${who}가 승리하였습니다.`);
        }
      }
    }
  };

  const changeResult = (num) => {
    const newButtons = [...buttons];
    if (newButtons[num]) {
      alert("너님 이미 누름 ㅋ");
      return;
    }

    if (whosTurn === "X") {
      newButtons[num] = "X";
      setButtons(newButtons);

      const updatedXList = [...xList, num];
      setXList(updatedXList);
      setTurn("O");
      whosTheWinner(updatedXList, buttons[num]);
    } else {
      newButtons[num] = "O";
      setButtons(newButtons);
      const updatedOList = [...oList, num];
      setOList(updatedOList);
      setTurn("X");
      whosTheWinner(updatedOList, newButtons[num]);
    }
  };

  return (
    <>
      <div>
        <button onClick={() => changeResult(0)}>{buttons[0]}</button>
        <button onClick={() => changeResult(1)}>{buttons[1]}</button>
        <button onClick={() => changeResult(2)}>{buttons[2]}</button>
      </div>
      <div>
        <button onClick={() => changeResult(3)}>{buttons[3]}</button>
        <button onClick={() => changeResult(4)}>{buttons[4]}</button>
        <button onClick={() => changeResult(5)}>{buttons[5]}</button>
      </div>
      <div>
        <button onClick={() => changeResult(6)}>{buttons[6]}</button>
        <button onClick={() => changeResult(7)}>{buttons[7]}</button>
        <button onClick={() => changeResult(8)}>{buttons[8]}</button>
      </div>
    </>
  );
}

export default App;

 

특히 내 코드에서 이 부분을 쓸 때 매우 오래 걸렸다

combilist.some((arr) => arr.every((num) => dapList[i].includes(num)))

의도했던 바는 "xList나 oList 중에서 무작위로 3개를 뽑은 배열들 중 빙고가 될 수 있는 배열을 포함하면~" 이었고,

처음에는

combilist.includes(dapList[i])

이렇게 썼었다. 그랬더니 절대 false가 되는 것이다.

객체형 데이터는 각자 메모리의 주소값을 가지기 때문에 두 배열이 같아보여도 사실은 다른 주소값을 가진다.
 즉, 두 배열은 같다고 할 수 없다 = includes로 찾을 수 없다....

따라서 두 배열이 같은지를 따져보려면 객체형 데이터 안에 있는 단순 데이터들끼리를 비교해 봐야한다.

결국에는 xList에서 무작위로 세 개를 뽑은 배열들(= combilist) 중에서, 빙고가 될 수 있는 배열(= dapList[i])이 combilist의 배열 하나의(arr), 각 요소들을 모두(every)포함(그래야 똑같은거니까)하는 combi가 하나라도 있으면(some) 승자를 알리는 alert가 뜨는 것이다.

A배열(combilist) 안의 요소와 B배열(dapList) 안의 요소가 같은지를 비교할 때는 이중 반복문을 써야하는데 그 안의 요소들이 또 배열이라면, 배열과 배열의 직접 비교는 불가능
=> A배열 안의 각 요소들을 다시 돌리면서 다른 배열이 그 요소들을 다 포함하는지를 봐야한다. 


답 코드를 봤다.

const WINNING_LINES = [
  [0, 1, 2],
  [3, 4, 5],
  [6, 7, 8],
  [0, 3, 6],
  [1, 4, 7],
  [2, 5, 8],
  [0, 4, 8],
  [2, 4, 6],
];

initialState: {
    board: Array(9).fill(null),
    currentPlayer: "X",
    winner: null,
    histories: [],
  }



>> 

...

state.board[index] = state.currentPlayer;

state.currentPlayer = state.currentPlayer === "X" ? "O" : "X";

...

>>



const winningLine = WINNING_LINES.find((line) => {
    const [a, b, c] = line;
    if (board[a] && board[a] === board[b] && board[a] === board[c]) {
      return board[a];
    }

 

이건 마치 애기한테 빙고라는 게임을 알려주는 것과 같다고 생각했다.

 

1. 우리는 X랑 O중에 누가 이기는지를 알아볼거야
2. board(9개의 칸)에는 O 또는 X가 들어갈거야

state.board[index] = state.currentPlayer;

3. 그리고 이거, 이거, 이거 다 O이거나 X면 우린 그걸 "one 빙고"라고 할겨
4. 여기서 이렇게, 이렇게, 이렇게는 board의 순서고, 그 경우는 WINNING_LINES에 다 들어있어.
5. 근데 WINNING_LINES 을 보니 될 수 있는 경우들이 많지?
매번 board의 0번째, 3번째, 6번째 그러고 또 board의 2번째, 5번째, 8번째 이렇게 모든 경우를 비교하기엔 너무 많으니 이 경우들을 통틀어서 우리는 [a, b, c]라고 할거야.

const [a, b, c] = line;

6. 그럼 board의 a번째랑, b번째랑, c번째가 모두 같으면 "one 빙고!"라는 말이랑 같은 말이지?

if (board[a] && board[a] === board[b] && board[a] === board[c])

7. 그때 board의 a번째에 들어가 있는 값이 O면 O가 이기는 거고 X면 X가 이기는 거야

 return board[a];

 

이미 답이 될 수 있는 경우들은 정해져있고 결국 누가 이기는지가 중요한 것이고
철수와 영희가 매번 뭘 고르는지는 몰라도 되는 것이었다.

결국 난 눌린 버튼 숫자 간의 직접비교를 했던 것이고

답은 버튼의 숫자가 몇이든 어차피 그 숫자에는 O 또는 X가 들어갈 것이니,
세 개에 모두 같은 게 들어갈 경우 그게 O인지 X인지로 풀이를 했다.