돈이 만드는 세상

틱택토 게임 - zerocho 2021ES 강의 본문

프로그래밍/JavaScript

틱택토 게임 - zerocho 2021ES 강의

피델리오 2021. 11. 9. 22:52

createElement한 것이 실제 화면에 보이지 않더라도 미리 이벤트리스너를 달 수 있습니다

 

const arr = [1, 2, 3, 4, 5];
const one = arr[0];
const two = arr[1];
const three = arr[2];
const four = arr[3];
const five = arr[4];
const [one, two, three, four, five] = arr;
const [one,, three, five] = arr;
const { body } = document; // === const body = document.body;, 구조분해 할당이라고 불림.
const obj = {
a : 'hello',
b: {
	c: 'hi',
    d: { e: 'wow' },
   },
};

const {a, b: {c, d: {e}}} = opj;
const a = obj.a;
const c = obj.a.c;
const e = obj.b.d.e;

여기서 특징은 모든 친구들에게 구조분해 할당이 되는 것이 아니라 말단 친구들에게만 할당이 된다는 것이다. 여기서는 a, c, e만 할당되었다.

const {a, b} = obj;
const {d: {e}} = b;

이 문제를 해결하기 위해서는 구조분해를 여러 번해서 해결하는 방법이 있다. 구조분해 할당하면 안 돌아가는 친구들이 있으므로 주의하자.

 

이벤트 버블링

removeEventLisner는 프로그램이 종료될 때 붙혀주는 게 좋다. 끝나기 전까지는 return으로 막는게 좋다. 이벤트 버블링이란 td의 클릭이벤트가 발생시 tr로 올라가고 table로 body로 올라간다. 즉, 이벤트가 부모태그 따라서 올라가는 것이다.

document.querySelector('header').addEventLisner('click', () => {
	console.log('button 태그를 클릭해도 이벤트 버블링에 의해서 작동하게 됨.');
});

 

event.target은 이벤트를 발생시킨 태그에 가리킨다. 그렇다면 진짜 table을 호출하고 싶다면 어떡해야 하는가? -> event.currentTarget을 이용하면 된다. currentTarget은 이벤트리스너를 붙힌 친구를 가리킨다.

 

이벤트 버블링 현상을 막을라면 어떡해야 하는가? -> event.stopPropagation()을 사용하면 된다.

 

이벤트 캡쳐링

이벤트 버블링과 반대 현상이다. 부모 태그에서 자식 태그로 가는 현상이다.

 

 

자바스크립트 코드

rows.flat().every((td) => td.textContent) // 모든 td의 textContent가 존재하는가?
// false
rows.flat().some((td) => td.textContent)  // td 중 단 한개라도 textContent가 존재하는가?
// true

flat()은 2차원 배열을 1차원 배열로 만들어준다. 이 방식은 상위 배열에도 똑같이 적용된다. 3->2->1로 가기 위해 flat()을 두번 사용하면 된다.

 

자바스크립트 코드

const { body} = document; // === const body = document.body;, 구조분해 할당이라고 불림.
const $result = document.createElement('div');
const $table = document.createElement('table');
const rows = [];
let clickable = true;
let turn = 'O';

const checkWinner = (target) => {
    const rowIndex = target.parentNode.rowIndex;
    const cellIndex = target.cellIndex;
    // 세칸 다 채워졌나?
    let hasWinner = false;
    // 가로 줄 검사
    if(
        rows[rowIndex][0].textContent === turn &&
        rows[rowIndex][1].textContent === turn &&
        rows[rowIndex][2].textContent === turn
    ) {
        hasWinner = true;
    }
    // 세로줄 검사
    if(
        rows[0][cellIndex].textContent === turn &&
        rows[1][cellIndex].textContent === turn &&
        rows[2][cellIndex].textContent === turn
    ) {
        hasWinner = true;
    }
    // 대각선 검사
    if(
        rows[0][0].textContent === turn &&
        rows[1][1].textContent === turn &&
        rows[2][2].textContent === turn
    ) {
        hasWinner = true;
    }
    if(
        rows[0][2].textContent === turn &&
        rows[1][1].textContent === turn &&
        rows[2][0].textContent === turn
    ) {
        hasWinner = true;
    }
    // 무승부 검사
    return hasWinner;
};

const checkWinnerAndDraw = (target) => {

    // 승부 판단하기
    if(checkWinner(target)) {
        $result.textContent = `축하드립니다. ${turn}님이 승리하셨습니다`;
        $table.removeEventListener('click', callback);
        return;
    }
    // 무승부 검사
    const draw = rows.flat().every((cell) => cell.textContent);
    if(draw) {
        $result.textContent = `아쉽게도 무승부입니다.`;
        return;
    }
    turn = (turn === 'O' ? 'X' : 'O');
};

const callback = (event) => {
    // 칸에 글자가 있나?
    if(!clickable) return;
    if(event.target.textContent) return;
    event.target.textContent = turn;

    checkWinnerAndDraw(event.target);
    if(turn === 'X') {
        const emptyCells = rows.flat().filter((v) => !v.textContent);
        const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
        clickable = false;

        setTimeout(() => {
            randomCell.textContent = 'X';
            checkWinnerAndDraw(randomCell);
            clickable = true;
        }, 1000);
    }
};


for(let i = 0; i < 3; i++) {
    const $tr = document.createElement('tr');
    const cells = [];
    for(let j = 0; j < 3; j++) {
        const $td = document.createElement('td');
        cells.push($td);
        $tr.append($td);
    }
    $table.append($tr);
    rows.push(cells);
}
$table.addEventListener('click', callback);
body.append($table);
body.append($result);

전체 코드

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>틱택토</title>
<style>
  table {
    border-collapse: collapse;
  }

  td {
    border: 1px solid black;
    width: 40px;
    height: 40px;
    text-align: center;
  }
</style>
</head>

<body>
<!--    <table>
        <tr>
            <td></td>
            <td></td>
            <td></td>
        </tr>
        <tr>
            <td></td>
            <td></td>
            <td></td>
        </tr>
        <tr>
            <td></td>
            <td></td>
            <td></td>
        </tr>
    </table> -->
<script>
    const { body} = document; // === const body = document.body;, 구조분해 할당이라고 불림.
    const $result = document.createElement('div');
    const $table = document.createElement('table');
    const rows = [];
    let clickable = true;
    let turn = 'O';

    const checkWinner = (target) => {
        const rowIndex = target.parentNode.rowIndex;
        const cellIndex = target.cellIndex;
        // 세칸 다 채워졌나?
        let hasWinner = false;
        // 가로 줄 검사
        if(
            rows[rowIndex][0].textContent === turn &&
            rows[rowIndex][1].textContent === turn &&
            rows[rowIndex][2].textContent === turn
        ) {
            hasWinner = true;
        }
        // 세로줄 검사
        if(
            rows[0][cellIndex].textContent === turn &&
            rows[1][cellIndex].textContent === turn &&
            rows[2][cellIndex].textContent === turn
        ) {
            hasWinner = true;
        }
        // 대각선 검사
        if(
            rows[0][0].textContent === turn &&
            rows[1][1].textContent === turn &&
            rows[2][2].textContent === turn
        ) {
            hasWinner = true;
        }
        if(
            rows[0][2].textContent === turn &&
            rows[1][1].textContent === turn &&
            rows[2][0].textContent === turn
        ) {
            hasWinner = true;
        }
        // 무승부 검사
        return hasWinner;
    };

    const checkWinnerAndDraw = (target) => {

        // 승부 판단하기
        if(checkWinner(target)) {
            $result.textContent = `축하드립니다. ${turn}님이 승리하셨습니다`;
            $table.removeEventListener('click', callback);
            return;
        }
        // 무승부 검사
        const draw = rows.flat().every((cell) => cell.textContent);
        if(draw) {
            $result.textContent = `아쉽게도 무승부입니다.`;
            return;
        }
        turn = (turn === 'O' ? 'X' : 'O');
    };

    const callback = (event) => {
        // 칸에 글자가 있나?
        if(!clickable) return;
        if(event.target.textContent) return;
        event.target.textContent = turn;

        checkWinnerAndDraw(event.target);
        if(turn === 'X') {
            const emptyCells = rows.flat().filter((v) => !v.textContent);
            const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
            clickable = false;
            
            setTimeout(() => {
                randomCell.textContent = 'X';
                checkWinnerAndDraw(randomCell);
                clickable = true;
            }, 1000);
        }
    };

    
    for(let i = 0; i < 3; i++) {
        const $tr = document.createElement('tr');
        const cells = [];
        for(let j = 0; j < 3; j++) {
            const $td = document.createElement('td');
            cells.push($td);
            $tr.append($td);
        }
        $table.append($tr);
        rows.push(cells);
    }
    $table.addEventListener('click', callback);
    body.append($table);
    body.append($result);
</script>
</body>
</html>