기록
틱택토 본문
순서도부터!
그 다음, html의 table 태그로 3x3 칸을 준비한다.
자바스크립트로 만들어볼 것.. 이차원배열을 이용해서!
<style>
td {
border: 1px solid black;
width: 50px;
height: 50px;
}
</style>
var BODY = document.body;
var table = document.createElement("table");
var cells = [];
var rows = [];
for (var i = 0; i < 3; i++) {
var row = document.createElement("tr");
rows.push(row);
cells.push([]);
for (var j = 0; j < 3; j++) {
var cell = document.createElement("td");
cells[i].push(cell);
row.appendChild(cell);
}
table.appendChild(row);
}
BODY.appendChild(table);
이차원배열을 이용한 방법.
단순히 if문을 중첩하면 된다.
첫째줄에 세칸 넣고, 둘째줄에 세칸 넣고, 셋째줄에 세칸 넣는 방식.
줄들, 칸들 배열을 만들고
줄들 배열에 빈아이템을 하나씩 담고, 해당 아이템에 아이템 3개가 담긴 배열을 담으면
줄들 배열은
[a,
b,
c]
칸들 배열은 a 안에 1,2,3을 넣는
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
parentNode, children
e.target.parentNode: 클릭된 애의 부모태그
e.target.children: 클릭된 애의 자식태그
이차원배열
cells[0][0]
이차원배열이기때문에 칸들 배열에 위와 같이 인덱스를 두번 연달아 입력하여 접근할 수 있다.
이걸 이용해서 칸이 채워져있는지 체크하여 칸을 채우고, 턴을 넘기는 조건문을 완성하자.(clickHandler 안에)
var clickHandler = function (e) {
var rowIndex = rows.indexOf(e.target.parentNode);
var cellIndex = cells[rowIndex].indexOf(e.target);
if (cells[rowIndex][cellIndex].textContent !== "") {
console.log("칸이 채워져있음");
} else {
cells[rowIndex][cellIndex].textContent = turn;
if (turn === "X") {
turn = "O";
} else {
turn = "X";
}
}
};
순서도의 "칸이 채워져있는가?" 부분을 완성됐음
세 줄이 되었는가?
여기가 좀 복잡허다
var clickHandler = function (e) {
//클릭한 칸의 위치 확인
var rowIndex = rows.indexOf(e.target.parentNode);
var cellIndex = cells[rowIndex].indexOf(e.target);
//칸이 비었는지 확인
if (cells[rowIndex][cellIndex].textContent !== "") {
alert("칸이 이미 채워져있습니다.");
} else {
// 빈 칸이니까 채워주고,
cells[rowIndex][cellIndex].textContent = turn;
//3줄이 채워졌는가?
var filled = false;
// 가로줄 체크
if (
cells[rowIndex][0].textContent === turn &&
cells[rowIndex][1].textContent === turn &&
cells[rowIndex][2].textContent === turn
) {
filled = true;
}
// 세로줄 체크
if (
cells[0][cellIndex].textContent === turn &&
cells[1][cellndex].textContent === turn &&
cells[2][cellIndex].textContent === turn
) {
filled = true;
}
// 대각선 체크1
if (
cells[0][0].textContent === turn &&
cells[1][1].textContent === turn &&
cells[2][2].textContent === turn
) {
filled = true;
}
// 대각선 체크2
if (
cells[0][2].textContent === turn &&
cells[1][1].textContent === turn &&
cells[2][0].textContent === turn
) {
filled = true;
}
}
};
마찬가지로 클릭핸들러 안에서
칸이 채워졌는지 확인하고
빈칸이면 채워준 다음,
가로,세로,대각선을 각각 체크한다.
3칸이 채워졌는지 확인하는 변수 filled로 체크한다.
forEach를 이용하여 게임 초기화
2차원 배열이니까 위에서 반복문을 중첩해서 2번 쓴 것처럼 forEach도 중첩해서 2번 사용해야 한다.
var clickHandler = function (e) {
//클릭한 칸의 위치 확인
var rowIndex = rows.indexOf(e.target.parentNode);
var cellIndex = cells[rowIndex].indexOf(e.target);
//칸이 비었는지 확인
if (cells[rowIndex][cellIndex].textContent !== "") {
alert("칸이 이미 채워져있습니다.");
} else {
// 빈 칸이니까 채워주고,
cells[rowIndex][cellIndex].textContent = turn;
// 생략
if (filled) {
message.textContent = turn + "님이 승리!";
//게임이 끝났으니까 초기화
turn = "X";
cells.forEach(function (row) {
row.forEach(function (cell) {
cell.textContent = "";
});
});
} else {
//칸이 안찼으니까 다음 턴 넘기기
if (turn === "X") {
turn = "O";
} else {
turn = "X";
}
}
}
};
여기서 이제 업그레이드 버전: 컴퓨터의 턴 추가하기
위 코드의 다음 턴 넘기기 부분에 컴퓨터의 턴을 추가할거임
else {
//칸이 안찼으니까 다음 턴 넘기기
if (turn === "X") {
turn = "O";
}
// 컴퓨터의 턴
setTimeout(function () {
console.log("컴퓨터의 턴");
// 후보칸
var candidate = [];
// 우선 모든 후보를 넣고
cells.forEach(function (row) {
row.forEach(function (cell) {
candidate.push(cell);
});
});
// 클릭할 수 있는 칸만 필터링
candidate = candidate.filter(function (item) {
return !item.textContent;
// 디폴트가 cell.textContent = ""; 즉 빈값 (= false) 이니까
// 그 빈값들이 클릭할 수 있는 값
});
// 클릭 후보칸 중에 랜덤 선택
var selected = candidate[Math.floor(Math.random() * candidate.length)];
selected.textContent = turn;
// 컴퓨터가 3칸을 채웠는가?
// 턴을 다시 나한테 넘긴다.
turn = "X";
}, 1000);
}
기본적으로 setTimeout 으로, 내 턴이 끝난 후 1초 뒤에 컴퓨터가 선택을 함
컴퓨터가 클릭할 수 있는 칸 후보 배열을 만들어야 함
1)전체 칸을 후보로 넣은 후, 2)아직 채워지지 않은 칸만 필터링
filter 메서드는 true 값만 배열로 반환하는 녀석이고
기본적으로 cell.textContent = "" 빈값 즉 false 상태이다.
이때, 따라서 !cell.textContent 으로 입력하여 그 빈값들만 후보로 필터링
(만일 그냥 item.textContent 로 하면, 현재 선택된 칸이 후보로 들어감.. 이 부분이 좀 헷갈림
아 이해됐음
현재 textContent = "" 빈값임. 얘네들만 남겨야 함.
그냥 item.textContent 로 필터링하면 빈값인 애들이 제외되니까.
전체 칸 중에서 현재 선택된 칸만 남을 수밖에!!
그러니까 !item.textContent 를 해주면 빈값인 애들을 선택하는데, 얘네의 불리언을 f -> t로 바꾸는 거임
! not연산자를 까먹었네.. )
이제 컴퓨터의 칸 상태도 체크를 해야 함.
위에서 사용한 코드를 가져와야하는데, 중복이 되므로 리팩토링을 할 차례
결과체크하는 함수로 빼기.
function check(rowIndex, cellIndex){
var filled = false;
// 가로줄 체크
if (
cells[rowIndex][0].textContent === turn &&
cells[rowIndex][1].textContent === turn &&
cells[rowIndex][2].textContent === turn
) {
filled = true;
}
...
return filled
}
//결과 체크
var filled = check(rowIndex, cellIndex);
if (filled) {
message.textContent = turn + "님이 승리!";
//게임이 끝났으니까 초기화
turn = "X";
cells.forEach(function (row) {
row.forEac ...
3칸 다 찼는지 상태를 알리는 filled 변수가 결과체크 함수 안에 있음.
이 문제를 해결하려면,
1) 전역변수로 미리 var filled; 선언해놓고 시작하기
2) 결과체크 함수 마지막에 filled를 리턴하고, 그 값을 받아서 var filled = check(); 가져오기
또한, 매개변수 rowIndex, cellIndex 둘을 받아오는 함수로 만들어서,
이벤트리스터 안에 있는 몇줄, 몇칸을 나타내는 index를 할당해줄 수 있다!
(이 부분은 정말 생각 못했다.. 그래서 항상 이벤트리스너에 모든 걸 넣었는데,
함수의 매개변수와 인자를 활용하면 되는 것....)
단, 컴퓨터의 결과체크부분은 따로 만들어줘야 함
// 컴퓨터의 결과체크
// var rowIndex = table.indexOf(selected.parentNode);
var rowIndex = rows.indexOf(selected.parentNode);
// var cellIndex = selected.parentNode.indexOf(selected);
var cellIndex = cells[rowIndex].indexOf(selected);
var filled = check(rowIndex, cellIndex);
indexOf는 배열 메서드라서, 유사배열객체로 불러오면 에러가 발생함
또한, 초기화하는 부분도 겹치니까 마찬가지로 함수로 따로 빼주기.
//초기화
function init() {
message.textContent = turn + "님이 승리!";
//게임이 끝났으니까 초기화
turn = "X";
cells.forEach(function (row) {
row.forEach(function (cell) {
cell.textContent = "";
});
});
}
...
if (filled) {
init();
} else {
//칸이 안찼으니까 다음 턴 넘기기
if (turn === "X") {
turn = "O";
}
추가할 기능: 컴퓨터의 턴일 때는 선택할 수 없게 하기
//클릭이벤트함수
var clickHandler = function (e) {
// 컴퓨터의 턴이면 실행되지 않도록
if (turn === "O") {
return;
}
내 턴을 X로 정해놓고 하고 있어서!
추가: 승리메세지가 너무 빨리 뜨는 부분 개선하기
//초기화
function init() {
message.textContent = turn + "님이 승리!";
// 1초 뒤에 게임 초기화
setTimeout(function () {
message.textContent = "";
cells.forEach(function (row) {
row.forEach(function (cell) {
cell.textContent = "";
});
});
turn = "X";
}, 1000);
}
setTimeout 함수 안에 메세지랑, 턴, 게임판 모두 초기화
근데 컴퓨터가 랜덤으로 선택하니까, 내가 2개 연속해서 칸 먹고 있을 때, 나머지 칸을 막지를 않음.
연속한 두 부분이 있는지를 판단해서 컴퓨터가 선택하도록 하는 것은 숙제.(겁나복잡할듯)
추가: 무승부
헉.. 대박 간단함.. 후보칸이 없으면 무승부...
기본 변수를 스코프체인 바깥으로 꺼내는 것은 문제가 없음. 간단함.
//칸이 비었는지 확인
if (cells[rowIndex][cellIndex].textContent !== "") {
alert("칸이 이미 채워져있습니다.");
} else {
// 빈 칸이니까 채워주고,
cells[rowIndex][cellIndex].textContent = turn;
// 다 찼는지 결과체크
var filled = check(rowIndex, cellIndex);
// 후보칸
var candidate = [];
// 우선 모든 후보를 넣고
cells.forEach(function (row) {
row.forEach(function (cell) {
candidate.push(cell);
});
});
// 현재 클릭할 수 있는 칸만 필터링
candidate = candidate.filter(function (item) {
return !item.textContent;
// 디폴트가 cell.textContent = ""; 즉 빈값 (= false) 이니까
// 그 빈값들만 후보군에 넣어야 함
});
if (filled) {
init();
} else if (candidate.length === 0) {
init(true);
} else {
//칸이 안찼으니까 다음 턴 넘기기
if (turn === "X") {
turn = "O";
}
// 컴퓨터의 턴
후보 관련 변수들을 컴퓨터 턴 바깥이자 칸 비었는지 확인하고 선택하는 부분으로 옮김
function init(draw) {
if (draw) {
message.textContent = "무승부";
} else {
message.textContent = turn + "님이 승리!";
}
// 1초 뒤에 게임 초기화
setTimeout(function () {
초기화함수를 수정하고, 인자를 받아온다.
그 인자가 true면 무승부
아무것도 없거나 false면 승부(아무것도 없는게 false)
if (filled) {
init();
} else if (candidate.length === 0) {
init(true);
} else {
//칸이 안찼으니까 다음 턴 넘기기
if (turn === "X") {
turn = "O";
}
'JS > zerocho - 웹게임강좌' 카테고리의 다른 글
zerocho js 6 - 로또추첨기 (0) | 2020.11.04 |
---|---|
숫자야구 연습 (0) | 2020.08.12 |
zerocho js 4 (0) | 2020.08.09 |
zerocho js 3 (1) | 2020.08.08 |
zerocho js 1,2강 (9) | 2020.08.02 |