틱택토(Tic Tac Toe) 게임 (3x3 표, 삼목게임)
이중 반복문을 이용한 이차원 배열 다루기
표와 같은 이차원 배열을 만들 때는 'for문 안에 for문(또는 forEach)' 형태로 작성한다.
const $table = document.createElement('table');
for (let i = 0; i < 3; i++) {
const $tr = document.createElement('tr');
for (let j = 0; j < 3; j++) {
const $td = document.createElement('td');
$tr.append($td);
}
$table.append($tr);
}
document.body.append($table);
🔽 예제) 4줄x3칸 이차원 배열 만들기
const array = [];
for (let i = 0; i > 4; i++) {
const cells = [];
for (let j = 0; j > 3; j++) {
cells.push('XOX');
}
array.push(cells);
}
😲 줄은 가로행(row)! 칸은 세로열(column)!로 채우는 것~ like excel.
addEventListener로 클릭 이벤트가 걸려있는 상태에서 만약 클릭을 하면 안되는 상황에서는 removeEventListener를 사용하지 말고, 조건을 두고 조건에 맞지 않으면 함수가 종료되도록 한다.
ex. if (event.target.textContent) return;
📕최신문법 알아두기!(ES6)
객체의 속성명과 속성을 변수에 할당하는 변수명이 같을 때 사용해 코드를 줄일 수 있다.
<구조분해 할당 - 객체>
const { body } = document;
const body = document.body; // 동일 코드
// 아래의 경우는 구조분해 할당하면 실행되지 않는다.
const createElement = document.createElement;
// document는 객체이므로 body라는 속성과 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;
반복문 안에는 반복문만 실행되도록 관련 없는 실행문은 별도로 함수를 빼서 작성하기!
짧은 조건문은 삼항 연산자를 이용해 코드를 줄일 수 있다.
삼항 연산자 ⇛ 조건문
? 참일 때 실행문
: 거짓일 때 실행문
let turn = 'O';
const callback = (event) => {
if (event.target.textContent) { // 빈칸이 아닌 경우
console.log('다른 칸을 클릭하세요');
return;
}
event.target.textContent = turn; // 빈칸인 경우 현재 순서 화면에 표시
turn = turn === 'O' ? 'X' : 'O'; // 차례 전환(삼항 연산자 이용)
};
for (let j = 0; j < 3; j++) {
// ...
$td.addEventListener('click', callback);
}
let turn = 'O';
if (turn === 'O') {
turn = 'X';
} else if (turn === 'X') {
turn = 'O';
}
turn = (turn === 'O') ? 'X' : 'O'; // 상동
// 우선순위는 === > ?, : > =(대입 연산자) 순으로
// 헷갈리는 경우 괄호를 사용해 작성 가능하다.
<target과 $td가 같은 값인 경우가 있는지 비교>
같은 값이 있으면 몇번째 줄과 몇번째 칸에 위치한 값인지 알 수 있도록 줄, 칸 인덱스를 저장한다.
이중 반복문을 사용하는데 (for 대신) forEach는 index를 사용할 수 있다.
매번 빈칸을 클릭할 때마다 checkWinner가 실행되면서 값을 비교하고, 몇번째 줄/칸인지 저장하여 hasWinner가 true/false인지 검사한다.
// target과 위의 배열에서 $td들 중 같은 값이 있는지 이중 반복문을 이용해 확인
// 같은 값이 있으면 해당 $td가 몇번째 줄, 몇번째 칸인지 인덱스를 저장
const checkWinner = (target) => { // event.target($td)이 target
let rowIndex;
let colIndex;
rows.forEach((row, ri) => {
row.forEach((col, ci) => {
if (col === target) {
rowIndex = ri;
colIndex = ci;
}
});
});
// 9칸이 모두 채워져있는가?
let hasWinner = false;
// ...(이기는 조건들 코드)
검사할 때는 처음에 변수 hasWinner를 false로 시작한다.
중간에 승자가 있는 경우가 발생하면 true로 바꿔준다.
승부 판단하기 코드 줄이기(diet!🏋️♂️)
<부모-자식 관계>
etc. Array.from
메서드를 사용해서 유사 배열 객체를 진짜 배열로 바꿀 수 있다. (배열 메서드 사용 가능)
const checkWinner = (target) => { // target은 td 태그
let rowIndex = target.parentNode.rowIndex; // rowIndex 속성은 $tr($td의 부모요소)이 제공
let colIndex = target.colIndex; // $td는 colIndex 속성을 제공
let hasWinner = false;
// ...(이기는 조건들 코드)
};
<무승부 검사하기>
9칸을 모두 클릭했는데 승부가 나지 않는 경우이다.
즉, 이중 for문을 돌려 빈칸이 있는지 확인하고 9칸 중 빈칸이 하나라도 있는 경우는 무승부가 아님을 의미한다.
(검사하는 상황이지만 이 경우는 false가 나오면 안되기 때문에 true로 시작)
let draw = true;
rows.forEach((row) => {
row.forEach((col) => {
if (!col.textContent) { // 한 칸이라도 비어있으면
draw = false; // 무승부가 아님
}
});
});
if (draw) {
$result.textContent = `무승부!`;
return;
}
➕ 알아두어야 할 개념!👩🏫 암기✨
자식 요소에게 addEventListener를 단 상태에서 게임이 종료되어 removeEventListener를 취하는데, 이 현상을 반복하면 버그가 발생할 수 있어 부모 요소에게 이벤트를 달아준다.
<Event Bubbling> (기본적으로 필요한 현상)
addEventListener를 달은 자식 요소에서 모든 부모 요소들에게 올라가는 형상으로 이벤트가 전파되어 해당 이벤트가 실행된다.
event.target : 실제로 이벤트가 발생한 요소 (자식 태그들 중 하나로 누가될지 모른다.) $tr? $td?
event.currentTarget : 이벤트를 붙인 (부모)요소 $table
이벤트 버블링 막는 메서드 : event.stopPropagation();
태그의 기본동작 막는 메서드 : event.preventDefault();
<Event Capturing>
부모 요소에서 자식 요소에게 내려가는 형상으로 event가 전파되어 해당 이벤트가 실행된다.
🙉유일한 예시❗ (그 외에 잘 사용X)
팝업창(select/option)을 닫기 위해 팝업창 밖에 빈 화면을 클릭할 때 사용한다.
팝업창의 부모 요소가 빈 화면으로 이벤트 캡처링을 통해 빈 화면을 클릭하면 팝업창으로 전달되어 닫히게 된다.
기본적으로 발생하는 현상은 이벤트 버블링이기 때문에 이벤트 캡처링을 달고 싶다면 세번째 인자(useCapture)를 true로 작성한다.
(이벤트 버블링인 경우, 기본값은 false로 생략 가능)
예시) $table.addEventListener('click', callback, true);
무승부 검사하기에서 한 칸이라도 빈칸이 있는 경우 무승부가 아니라고 했다.
forEach를 이용해 중간에 반복문을 종료하기 어렵다.
처음 검사한 칸부터 빈칸인 경우 끝까지 반복문을 돌지 않고 종료하기 위해 다음과 같은 배열 메서드를 사용한다.
이중 반복문 대신 효율적인 알고리즘 코드를 작성할 수 있다.
every, some은 반복문의 일종으로 요소를 순회하면서 조건 함수의 리턴값에 따라 메서드의 리턴값이 달라진다.
1차원 배열에 가능한 배열 메서드로 중간에 false이면 빠르게 함수를 종료한다.
rows // (3) [Array(3), Array(3), Array(3)] - 2차원 배열
rows.flat() // (9) [td, td, td, ..., td] - 1차원 배열
rows.flat().every((td) => td.textContent)
// 모두 칸이 차있으면 true
// 한 칸이라도 비어있으면 false
리턴값이 td.textContent로 하나라도 false가 되는 값이 들어 있으면 every는 false가 되어 종료된다.
(✅ false가 되는 값: '', false, 0, null, undefined, NaN)