[리팩토링] 2주차 tick tack toe

Soozynn·2021년 8월 19일
0

[ 2 주차 핵심 ]

  • 내가 마킹한 값을 배열에 그대로 저장하는 법

  • 이걸 응용해서 위너를 어떻게 선발하는지 (제시되어있던 함수 이해)

  • 처음에 불리언 값을 변수에 담아서 X,O 값을 보드에 마킹하는방법



로직 순서 정리

피드백)

우선 수진님께서는 전체적으로 게임의 진행방식에 대한 로직에 대해서 다시 한번 정리를 해보시는 것이 필요해보입니다.

예를 간단하게 들자면,

X, O 중 누가 첫번째 턴이 될지 미리 결정한다.
(누구의 턴인지에 대한 정보는 자바스크립트 내에서 true/false 값을 이용하여 변수를 만든다)

  • 게임 시작을 할 수 있는 startGame 함수가 발동된다.

  • 게임 시작을 하면 startGame 함수로 인해 게임 시작 버튼은 사라지고, 각 박스에 박스를 마킹하는 이벤트 리스너를 붙인다 (이렇게 하면, 게임 시작 전에 박스에 마킹되는 것을 미리 방지할 수 있습니다)

  • 플레이어 클릭으로 인해 이벤트 리스너에 있는 checkBox 함수가 발동되면 콜백 함수의 event 인자의 className 프로퍼티의 번호를 확인하여 해당 번호와 누구인지 squares 배열에 기록한다. 그리고 브라우저에 각 플레이어 턴에 맞게 심볼을 마킹을 하고, 수정된 squares 배열을 calculateWinner 함수에 전달하여 승자가 나왔는지 체크를 한다.

  • 승자가 없으면 승자 메시지 출력은 패스하고 다음 플레이어 턴으로 넘어간다

  • 승자가 있다면 승자 메시지 출력을 하고 게임을 리셋하는 resetGame 함수를 발동한다 -> resetGame 함수가 발동되면, 모든 박스에 있던 체크 표시는 다 지우고, 등록되어있던 이벤트 리스너를 삭제하고, 플레이어 턴을 초기화하며, 게임 시작 버튼을 다시 보이게 한다.

  • 처음으로 복귀




⛔ 주어진 함수가 뜻하는 것 바로알기

for (let i = 0; i < boxes.length; i++) {
  squares[i] = boxes[i].textContent; // 내가 표기한 마크를 squeares 배열에 담는다
  calculateWinner(squares);
}
                                 
                                   
const squares = [];

function calculateWinner(squares) {
  const 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],
  ];

  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];

    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}  

진짜 이게 뜻하는게 무슨 소린지 몇 시간동안 모니터만 멍하게 쳐다봤다.. 뜻하는 바를 이해하긴했지만 후에 응용을 한다거나 다시 코드를 짜보라고 하면 1도 못적을거같다

처음에 내가 표기한 마크를 대체 배열에 어떻게 담으란거지?.. 이 생각만 오만번 했다 이해가 안가서 디버깅을 돌리면서 계속 배열이 어떻게 담기는지 확인해보았다

먼저, 내가 짠 html은 아래와 같다

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width">
    <title>Vanilla Coding | Bootcamp Prep</title>
    <link href="./styles/index.css" rel="stylesheet" />
    <script src="https://kit.fontawesome.com/4a25e9acfe.js" crossorigin="anonymous"></script>
    </head>
<body>
    <img class="main-logo" src="./images/vc.png" alt="Vanilla Coding Logo" />
    <h1>Tick Tack Toe</h1>
    <section class="board">
        <div class="start-page">
            <button class="start-button" type="button">Game Start</button>
        </div>
            <div class="choose-player">
                <button class="x-button" type="button"><i class="fas fa-times"></i></button>
                <button class="o-button" type="button"><i class="far fa-circle"></i></button>
            </div>
        <div class="player-turn"></div>
        <div class="game-board">
            <div class="row">
                <div class="box"></div>
                <div class="box"></div>
                <div class="box"></div>
            </div>
            <div class="row">
                <div class="box"></div>
                <div class="box"></div>
                <div class="box"></div>
            </div>
            <div class="row">
                <div class="box"></div>
                <div class="box"></div>
                <div class="box"></div>
            </div>
        </div>
    </section>
    <script src="./app/index.js"></script>
</body>
</html>

위에 코드와 같이 켄님이 오피스아워에서 보여주신 방법으로 동일하게 게임보드를 짰다. div tag를 9개 만들어주고 3개씩 다시 wrapper해서 CSS => 클래스 .row 에 대해서 diplay: flex 값을 주면 틱택토 보드 모양을 구현 할 수 있다.

따라서 보드의 순서는 아래 사진과 같아지게 된다.
사실 뭐 정답을 확인하는 함수의 짜임이 세로순으로 나열해도 정답이긴 하다

1) 내가 표기한 마크를 배열에 담기

const squares = []; 
  
for (let i = 0; i < boxes.length; i++) {
 ⭐ squares[i] = boxes[i].textContent; // 빈 배열에 내가 마킹한 값을 담기
    calculateWinner(squares); // 그 값을 인자로 넘겨주기
}

자세한 설명은 아래에 움짤과 같다
디버깅을 통해 squares 배열이 어떻게 담기는지 볼 수 있는데, html에 작성했던 박스 순서대로 순회를 하는 것이다.

⭐ 즉, i = 0; 일 때 => squares[0] = boxes[0].textContent; 이므로
squeares의 0번 째 index 값에 첫 번째 박스 안의 텍스트 값이 저장된다.

위에서는 내가 3번 째 칸에만 마킹을 했으므로 for문 을 다 순회하였을 때 나오는 값은 squares = (9) ["첫번 째 박스 텍스트 값", "두번 째 박스 텍스트 값", "X", "4번 째", ..."마지막 9번째 까지] 가 된다.

squares = (9) ["0", "1", "2", "3", ..."8]

박스 갯수가 9개 이므로 조건문에서 i < boxes.length; => i < 9;
따라서 index의 값이 8일 때까지의 값을 계속 순회하고 위에 식처럼 squares에는 총 0부터 8까지 총 9개의 값이 각각의 인덱스의 담긴다.

2) 마킹 값을 담은 squares를 함수로 넘겨주기

그 다음, 내가 마킹한 이 squares 값을 위너함수호출과 동시에 인자로 넘겨준다. => calculateWinner(squares);

이제 내가 마킹한 값을 가지고 이 함수가 승자가 있는지 판별해주는데,
이 함수를 뜯어보면, 아래와 같다.

function calculateWinner(squares) {
  const 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],
  ];

  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i]; ⭐

 ⭐ if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}  

위에 lines 변수는 정답 심볼을 맞추기 위한 배열이다 (빙고와 같다)
lines[0]~[2] 까지는 내가 작성한 html 순서도에서 가로 빙고를 뜻하는 것이고, 그 밑 세 줄은 세로 빙고, 마지막 밑에 두줄은 대각선 빙고를 가르킨다.


이 함수에서 제일 중요한 부분 (별표 표시)
위에 반복문에서 i = 0; 일 때, const [a, b, c] = lines[0] => const [a, b, c] = [0, 1, 2],...[2, 4, 6]

const [a, b, c] =
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]

👉 빙고 값이 배열 [a, b, c] 에 다 담기게 된다.

또, 연산자 우선순위로 인해 === 일치연산자가 우선 ↑

아래 조건에서 a === b, b===c 가 우선으로 비교 된 다음, 논리연산자 &&가 비교된다

if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
    return squares[a];
  }
}
return null;
}  

즉 아래와 같은 순서이다.
squares[a] && (squares[a] === squares[b]) && (squares[a] === squares[c])

사진과 같이 비교 순서도는, [a, b, c] 에서
1) [a] === [b]
2) [a] === [c]
3) [a]와 ([a] === [b]) 값 비교
4) (3)의 값) 와 ([a] === [c]) 값 비교


a와 b 같은지 / a와 c 같은지 비교 후 논리연산자

⛔ 앞에 squares[a] &&이 있는 이유는 null 값을 판별하기 위함이다
=> a 값이 없을 경우 바로 return null이 출력.
또, 값이 빈 객체 일 수도 있으므로.?



내가 제대로 구현하지 못했던 부분

피드백)

누구의 턴인지 브라우저에 표시된 텍스트 컬러를 의존해 턴을 결정하는 방식은 dom 요소를 잡고 style에 매번 접근하기 때문에 다소 비효율적인 것 같습니다.

브라우저와 관계 없이, 최초 게임 시작시 누가 먼저 턴인지 미리 설정하고, 자바스크립트 내에서 true/false 변수를 사용해서 턴을 간단하게 제어할 수 있을 것 같습니다.

또 여기서, 불리언 값을 주기 위해 변수를 선언했으므로
해당 변수 네이밍은 => is~로 시작한다

// 처음 구현했던 내용 ⛔
// 어떤 식으로 접근해야할지 몰라 그나마 떠오른게
바뀌는 색상에 의해서 안에 값도 바꿔주는 것이었다
// 지금 생각해보면 말도 안된다..

function checkBox (event) {
if (selectXButton.style.color !== "black") { 
  event.target.innerHTML = `<i class="fas fa-times"></i>`;
  setTimeout(function () {
  selectXButton.style.color = "black";
  selectOButton.style.color = "blue";
  text.style.display = ""; 
  }, 500);

  } else if (selectOButton.style.color !== "black") {
  event.target.innerHTML = `<i class="far fa-circle"></i>`;
  



// 수정한 코드 ✅
// 복붙하면서 인덴팅이 좀 꼬여서 출력됨 귀찮으니까 패스
let isXTurn = true;

function checkBox(event) {
if (event.target.textContent) {
  return;
}

if (boxes.textContent = " ") {
  buttonO.setAttribute("disabled", "disabled");
  buttonX.setAttribute("disabled", "disabled");
}

if (isXTurn) {
  event.target.textContent = MARK_X;
  isXTurn = false;

  setTimeout(function() {
    playerTurnText.textContent = `${MANK_O} Turn`;
    buttonO.classList.add("player-click");
    buttonX.classList.remove("player-click");
  }, 300);
} else {
  event.target.textContent = MANK_O;
  isXTurn = true;

  setTimeout(function() {
    playerTurnText.textContent = `${MARK_X} Turn`;
    buttonX.classList.add("player-click");
    buttonO.classList.remove("player-click");
  }, 300);
}
findWinner();
}

재시작버튼이 계속 생성되지 않아 아직 미완성이지만 전체 코드
const startButton = document.querySelector(".start-button");
const startPage = document.querySelector(".start-page");
const buttonX = document.querySelector(".x-button");
const buttonO = document.querySelector(".o-button");
const boxes = document.querySelectorAll(".box");
const playerTurnText = document.querySelector(".player-turn");

const MARK_X = "X";
const MANK_O = "O";
let isXTurn = true;

const squares = [];

startButton.addEventListener("click", handleStartButtonClick);
buttonX.addEventListener("click", handlePlayerButtonClick);
buttonO.addEventListener("click", handlePlayerButtonClick);

for (let i = 0; i < boxes.length; i++) {
boxes[i].addEventListener("click", checkBox);
}

function handleStartButtonClick() {
startPage.classList.add("hide");
startButton.classList.add("hide");
buttonX.classList.add("player-click");
buttonO.classList.remove("player-click");
}

function handlePlayerButtonClick(event) {
if (event.target === buttonO) {
  playerTurnText.textContent = `${MARK_X} Turn`;
  buttonO.setAttribute("disabled", "disabled");
  isXTurn = true;
  return;
} 
playerTurnText.textContent = `${MARK_X} Turn`;
checkBox();
}

function checkBox(event) {
if (event.target.textContent) {
  return;
}

if (boxes.textContent = " ") {
  buttonO.setAttribute("disabled", "disabled");
  buttonX.setAttribute("disabled", "disabled");
}

if (isXTurn) {
  event.target.textContent = MARK_X;
  isXTurn = false;

  setTimeout(function() {
    playerTurnText.textContent = `${MANK_O} Turn`;
    buttonO.classList.add("player-click");
    buttonX.classList.remove("player-click");
  }, 300);
} else {
  event.target.textContent = MANK_O;
  isXTurn = true;

  setTimeout(function() {
    playerTurnText.textContent = `${MARK_X} Turn`;
    buttonX.classList.add("player-click");
    buttonO.classList.remove("player-click");
  }, 300);
}
findWinner();
}

function findWinner() {
for (let i = 0; i < boxes.length; i++) {
  squares[i] = boxes[i].textContent;
  calculateWinner(squares);
  
}

const winner = calculateWinner(squares);

if (winner) {
  startPage.innerHTML = `🎉 Congratulations~!! 🎉 <br/> <br/>  Winner is <br/> [ Player ${winner} ]`;
  restartGame();
  return;
} 

if (!winner && !squares.includes("")) {
  startPage.textContent = "Draw!! 🤜🤛";
  restartGame();
  return;
}
}

function restartGame() {
setTimeout(function() {
  startPage.classList.remove("hide");
  startButton.classList.remove("hide");
  startButton.textContent = "Restart Game";
  playerTurnText.textContent = "";

  for (let i = 0; i < boxes.length; i++) {
    boxes[i].textContent = "";
  }
}, 300);
}

function calculateWinner(squares) {
const 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],
];

for (let i = 0; i < lines.length; i++) {
  const [a, b, c] = lines[i]; 

  if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
    return squares[a];
  }
}
return null;
}

0개의 댓글