완성본
Result 화면만을 띄워놓고 테스트를 해보실 수 있습니다.
코드 풀이
틱택토는 가로, 세로, 대각선 중 1 라인을 채우는 사람이 이기는 게임입니다.
즉 승리 조건은 가로, 세로, 대각선 중 1 라인을 다 채운 플레이어를 찾으면 됩니다.
이제 틱택토 게임을 구현하기 전 필요한 기능들을 생각해 보았습니다.
필요 기능
- 새 게임판 만들기
- 네모판을 클릭 시 현재 턴인 유저에 따른 표시
- 게임판에 가로, 세로, 대각선 중 1 라인을 채웠는지 확인
- 만약 승리 조건이 일치하지 않을 시 다음 턴 넘기기
위 기능 구현을 목표로 구현해 보았습니다.
1. 새 게임판 만들기
newMake() {
let html = '';
for (let i = 0; i < 9; i++) {
html += `<div class='panel_item' onclick="Board.itemClick(this)"></div>`;
}
document.querySelector('.panel_list').innerHTML = html;
},
틱택토 게임판은 총 9개의 네모판이 필요합니다.
저는 flex-wrap: wrap 속성을 사용하여 9개의 div를 새로 만드는 방식을 사용했습니다.
2. 네모판을 클릭 시 현재 턴인 유저에 따른 표시
itemClick($target) {
if ($target.className != "panel_item") {
return;
}
const shape = {player_1: 'O', player_2: 'X'};
$target.innerHTML = `<div>${shape[Board.nowTurn]}</div>`;
$target.className += ` ${Board.nowTurn}`;
// 승리 조건 확인
Board.clearChk();
},
저는 각 네모판에 onclick
이벤트를 걸어 주었습니다.
만약 해당 네모 판이 이미 선택된 판일 시 선택이 되지 않게 if 문으로 예외처리를 해주었습니다.
그리고 유저에 따른 표시는 현재 턴인 플레이어에 따른 모양을 지정해주고 클래스를 추가하여 구분을 해줍니다.
그 후 다음 기능인 승리 조건 함수로 넘어갑니다.
3. 게임판에 가로, 세로, 대각선 1 라인을 채웠는지 확인
사실상 틱택토의 모든 기능을 다한다 보면 됩니다.
const BoardItem = document.querySelectorAll(`.panel_item`);
먼저 저는 선택이 되었는지를 해당 네모판에 클래스를 넣어 구분 지어 주었기에 모든 네모판을 가져와 줍니다.
const bolArray = new Array(8).fill(true);
// 가로, 세로 체크
for (let i = 0; i < 3; i++) {
const keyArr = [
i, 3+i, 6+i, // 가로 Index
i*3, i*3+1, i*3+2 // 세로 Index
];
for (let j = 0; j < 6; j++) {
bolArray[j] = bolArray[j] && Board.itemChk(BoardItem[keyArr[j]]);
}
}
// 대각선
bolArray[6] = Board.itemChk(BoardItem[0]) && Board.itemChk(BoardItem[4]) && Board.itemChk(BoardItem[8]);
bolArray[7] = Board.itemChk(BoardItem[2]) && Board.itemChk(BoardItem[4]) && Board.itemChk(BoardItem[6]);
각 가로 3개, 세로 3개, 대각선 2개 총 8개의 결과를 담을 배열을 만들어 줍니다.
위 이미지로 볼 수 있듯이 각 네모칸의 Index를 알 수 있습니다.
가로 [0, 1, 2], [3, 4, 5], [6, 7, 8]
세로 [0, 3, 6], [1, 4, 7], [2, 5, 8]
대각선 [0, 4, 8], [2, 4, 6]으로
각 인덱스에 해당하는 네모칸에 해당 턴 유저가 선택한 표시가 있는 네모칸인지 체크해줍니다.
(결국 승리 전은 턴 유저가 마지막 클릭을 해야 끝나기 때문)
위 인덱스 배열(가로 3개, 세로 3개)을 총 6개의 인덱스를 가지고 for 문을 돌려줍니다.
각 케이스마다 그전 네모칸이 해당 턴 유저가 체크한 표시가 없는 경우 해당 라인은 승리 조건에 해당되지 않습니다.
그리고 2개의 경우를 가지고 있는 대각선도 결과를 체크해 줍니다.
const resultBol = bolArray.some(v => v);
// (결과 체크) 승부가 났던가 안났던가.
if (resultBol || [...BoardItem].every(v => Board.itemChk(v, 'player'))) {
const $modal = document.querySelector(`.modal`);
// 모달 형식으로 승리, 무승부를 알려보았습니다.
$modal.className += ' open';
$modal.innerHTML = `<h2>${resultBol ? `${Board.nowTurn}님이 이겼습니다.` : '무승부'}</h2>`;
setTimeout(() => {
$modal.className = 'modal';
Board.newMake(); // 게임 초기화
}, 2000);
return;
}
Board.turnChange();
이제 각 라인마다의 결과를 담은 배열의 값이 나왔으니 some 메서드를 돌려 만약 한 라인이라도 배열에 true값이 있을 시 현재 턴 유저가 승리합니다.
하지만 모든 네모칸을 체크했지만 승부가 안 났을 시 무승부를 띄워주기 위해 if문에 조건을 추가해줍니다.
저는 결과창을 간단한 모달로 띄워주었습니다. 모달이 뜬 후에는 다시 닫아주며 게임판을 초기화해주었습니다.
하지만 승부가 안 났고 아직 선택할 네모칸이 있을 시 턴을 바꿔줍니다.
4. 만약 승리 조건이 일치하지 않을 시 다음 턴 넘기기
// 턴 체인지
turnChange() {
Board.nowTurn = {player_1: 'player_2'}[Board.nowTurn] || 'player_1';
// Board.nowTurn = Board.nowTurn === "player_1" ? "player_2" : "player_1";
},
만약 1번 플레이어 이면 2번으로 바꿔주고 2번 플레이어 일 시 1번 플레이어로 바꿔주는 코드입니다.