이번 게임에는 이벤트 리스너가 각각 들어가서 순서도가 끊기는 부분이 많다.
<style>
.cell {
display: inline-block;
position: relative;
width: 200px;
height: 200px;
background: 'yellow';
overflow: hidden;
}
.gopher,
.bomb {
width: 200px;
height: 200px;
bottom: 0;
position: absolute;
transition: bottom 1s;
}
.gopher {
background: url('/images/gopher.png') center center no-repeat;
background-size: 200px 200px;
}
.dead {
background: url('/images/dead_gopher.png') center center no-repeat;
background-size: 200px 200px;
}
.bomb {
background: url('/images/bomb.png') center center no-repeat;
background-size: 200px 200px;
}
.boom {
background: url('/images/explode.png') center center no-repeat;
background-size: 200px 200px;
}
.hidden {
bottom: -200px;
}
.hole {
width: 200px;
height: 150px;
position: absolute;
bottom: 0;
background: url('/images/mole-hole.png') center center no-repeat;
background-size: 200px 150px;
}
.hole-front {
width: 200px;
height: 30px;
position: absolute;
bottom: 0;
background: url('/images/mole-hole-front.png') center center no-repeat;
background-size: 200px 30px;
}
</style>
</head>
<body>
<div>
<span id="timer">8</span>초
<span id="score">0</span>점
<span id="life">3</span>목숨
<button id="start">시작</button>
</div>
<div id="game">
<div class="row">
<div class="cell">
<div class="hole"></div>
<div class="gopher hidden"></div>
<div class="bomb hidden"></div>
<div class="hole-front"></div>
</div>
<div class="cell">
<div class="hole"></div>
<div class="gopher hidden"></div>
<div class="bomb hidden"></div>
<div class="hole-front"></div>
</div>
<div class="cell">
<div class="hole"></div>
<div class="gopher hidden"></div>
<div class="bomb hidden"></div>
<div class="hole-front"></div>
</div>
</div>
<div class="row">
<div class="cell">
<div class="hole"></div>
<div class="gopher hidden"></div>
<div class="bomb hidden"></div>
<div class="hole-front"></div>
</div>
<div class="cell">
<div class="hole"></div>
<div class="gopher hidden"></div>
<div class="bomb hidden"></div>
<div class="hole-front"></div>
</div>
<div class="cell">
<div class="hole"></div>
<div class="gopher hidden"></div>
<div class="bomb hidden"></div>
<div class="hole-front"></div>
</div>
</div>
<div class="row">
<div class="cell">
<div class="hole"></div>
<div class="gopher hidden"></div>
<div class="bomb hidden"></div>
<div class="hole-front"></div>
</div>
<div class="cell">
<div class="hole"></div>
<div class="gopher hidden"></div>
<div class="bomb hidden"></div>
<div class="hole-front"></div>
</div>
<div class="cell">
<div class="hole"></div>
<div class="gopher hidden"></div>
<div class="bomb hidden"></div>
<div class="hole-front"></div>
</div>
</div>
</div>
<script>
const $timer = document.querySelector('#timer');
const $score = document.querySelector('#score');
const $game = document.querySelector('#game');
const $start = document.querySelector('#start');
const $$cells = document.querySelectorAll('.cell');
const $life = document.querySelector('#life');
const holes = [0, 0, 0, 0, 0, 0, 0, 0, 0];
let started = false;
let score = 0;
let time = 60;
let life = 3;
let timerId;
let tickId;
$start.addEventListener('click', () => {
if (started) return; //이미 시작했으면 무시
started = true;
console.log('시작');
const timerId = setInterval(() => {
time = (time * 10 - 1) / 10; //소수점 계산시 문제 있음
$timer.textContent = time;
if (time === 0) {
setTimeout(() => {
clearInterval(timerId);
clearInterval(tickId);
alert(`게임오버! 점수는 ${score}점`);
}, 50);
}
}, 100);
const tickId = setInterval(tick, 1000);
tick();
});
//if의 특성을 생각해서 나오는 비율 누적확률로 해주면 편리하다.
let gopherPercent = 0.3;
let bombPercent = 0.5;
//비동기때문에 오류가 생기면 이벤트 루프로 해석
function tick() {
holes.forEach((hole, index) => {
if (hole) return; //무언가 일어나고 있으면 return / settimeout이 2초에 한번 실행되게 됨
const randomValue = Math.random();
if (randomValue < gopherPercent) {
const $gopher = $$cells[index].querySelector('.gopher');
holes[index] = setTimeout(() => { //hole이라고 넣어주면 원시값이기 때문에 원시값 재할당 불가 그래서 holes[index]에 넣어준다.
$gopher.classList.add('hidden');
holes[index] = 0;
}, 1000);
$gopher.classList.remove('hidden');
} else if (randomValue < bombPercent) {
const $bomb = $$cells[index].querySelector('.bomb');
holes[index] = setTimeout(() => { //hole이라고 넣어주면 원시값이기 때문에 원시값 재할당 불가 그래서 holes[index]에 넣어준다.
$bomb.classList.add('hidden');
holes[index] = 0;
}, 1000);
$bomb.classList.remove('hidden');
}
});
}
$$cells.forEach(($cell, index) => {
$cell.querySelector('.gopher').addEventListener('click', (event) => {
if (!event.target.classList.contains('dead')) {
score += 1;
$score.textContent = score;
}
event.target.classList.add('dead');
event.target.classList.add('hidden');
clearTimeout(holes[index]); //기존 내려가는 타이머 제거
setTimeout(() => {
holes[index] = 0;
event.target.classList.remove('dead'); //울고있는 두더지 제거
}, 1000);
});
$cell.querySelector('.bomb').addEventListener('click', (event) => {
if (!event.target.classList.contains('boom')) {
life--;
$life.textContent = life;
if (life === 0) {
clearInterval(timerId);
clearInterval(tickId);
setTimeout(() => {
alert(`게임오버! 점수는 ${score}점`);
}, 50);
}
}
event.target.classList.add('boom');
event.target.classList.add('hidden');
clearTimeout(holes[index]);
setTimeout(() => {
holes[index] = 0;
event.target.classList.remove('boom');
}, 1000);
});
});
</script>
</body>
컴퓨터의 소수점 계산 : 컴퓨터가 소수점 단위를 계산할 때 나머지 값 때문에 오류가 날 가능성이 있으므로 다음과 같이 계산해준다.
time = (time * 10 - 1) / 10;
이벤트 루프 분석으로 문제 해결하기
function tick() {
holes.forEach((hole, index) => {
if (hole) return; //무언가 일어나고 있으면 return / settimeout이 2초에 한번 실행되게 됨
const randomValue = Math.random();
if (randomValue < gopherPercent) {
const $gopher = $$cells[index].querySelector('.gopher');
holes[index] = setTimeout(() => {
$gopher.classList.add('hidden');
holes[index] = 0;
}, 1000);
$gopher.classList.remove('hidden');
} else if (randomValue < bombPercent) {
const $bomb = $$cells[index].querySelector('.bomb');
holes[index] = setTimeout(() => {
$bomb.classList.add('hidden');
holes[index] = 0;
}, 1000);
$bomb.classList.remove('hidden');
}
});
}
비동기 코드에서 오류가 나면 이벤트 루프로 문제를 해결한다.
위 코드에서 if (hole) return ;
부분이 빠지면 두더지가 올라오지 않는 오류가 발생한다.
두더지가 올라오자마자 지워지는 이벤트가 실행되기때문이다.
따라서 hole에 무언가 있으면 return해주는 코드를 추가해 오류를 막아준다.
비율 계산하기
30%, 20%, 50% 의 확률로 두더지, 폭탄, 아무것도 올라오지 않을 확률을 세팅한다고 가정했을때 0.3, 0.2, 0.5 로 비율을 세팅할 확률이 높지만
if문의 특성을 생각하여 누적 확률로 세팅해주면 편리하다.
let gopherPercent = 0.3;
let bombPercent = 0.5;
if (randomValue < gopherPercent) {
const $gopher = $$cells[index].querySelector('.gopher');
holes[index] = setTimeout(() => {
$gopher.classList.add('hidden');
holes[index] = 0;
}, 1000);
$gopher.classList.remove('hidden');
} else if (randomValue < bombPercent) {
const $bomb = $$cells[index].querySelector('.bomb');
holes[index] = setTimeout(() => {
$bomb.classList.add('hidden');
holes[index] = 0;
}, 1000);
$bomb.classList.remove('hidden');
}
});