-
Whack a Mole Game 🎮Web/JS30 2021. 1. 2. 19:52반응형
벌써 Javascript 30의 마지막 주제까지 왔다.
사실 2020년 안에 전부 끝내는 게 목표였는데, 아쉽게도 이틀정도 딜레이됐다.
다른 이유가 있는 건 아니고, 그냥 중간중간에 딴 것도 자꾸 하고싶고
게을러지기도 하고 그랬던 것 같다.
그래도 예제들 중에 실제로 써먹어본 것들도 많고,
전혀 몰랐던 브라우저의 기능을 새로 알게 된 경우도 많고 해서
끝까지 오는 데에 크게 지루한 적은 없었다.
나름 재미있었다.
생각보다 실제로 써먹을 일이 좀 있었다는 게 신기했지.
마지막 주제는 'Whack-A-Mole', 즉 두더지 잡기 게임을 만들어보는 예제이다.
우선 HTML 코드를 살펴보자.
<h1>Whack-a-mole! <span class="score">0</span></h1> <button onClick="startGame()">Start!</button> <div class="game"> <div class="hole hole1"> <div class="mole"></div> </div> <div class="hole hole2"> <div class="mole"></div> </div> <div class="hole hole3"> <div class="mole"></div> </div> <div class="hole hole4"> <div class="mole"></div> </div> <div class="hole hole5"> <div class="mole"></div> </div> <div class="hole hole6"> <div class="mole"></div> </div> </div>
HTML 코드를 보면 알 수 있듯이, 각각의 구멍들 안에는 이미 mole(두더지)들이 한 마리씩 숨어있다.
걔네들이 언제 구멍 밖으로 튀어나올지, 어떤 구멍에 있는 두더지가 튀어나올지,
몇 초 동안 튀어나와있을지 등은 완전히 랜덤으로 구현한다.
우선 스코어보드와 두더지가 튀어나오는 구멍들, 그리고 두더지들을 전부 변수에 담아준다.
const holes = document.querySelectorAll('.hole'); const scoreBoard = document.querySelector('.score'); const moles = document.querySelectorAll('.mole');
우선, 랜덤한 시간을 반환하는 함수를 만들어보자.
function randTime(min, max) { return Math.round(Math.random() * (max - min) + min); }
min ~ max 사이의 랜덤한 숫자를 구하는 공식은 이미 다른 예제에서도 많이 등장했기 때문에
더 이상 언급하지 않아도 될 정도로 익숙한 공식이다.
Math.round()를 해주는 이유는, 그냥 소수점 숫자를 없애주기 위함.
다음은 랜덤한 구멍을 선택하는 함수를 만들어 볼 차례.
function randomHole(holes) { const idx = Math.floor(Math.random() * holes.length); const hole = holes[idx]; console.log(hole); }
holes에는 구멍들, 즉 nodeList가 들어올 것이기 때문에
return하는 hole도 마찬가지로 node일 것이다.
이제 콘솔창에서 결과를 출력해보자.
위의 결과를 봤을 때, 우리는 한 가지 문제점을 예측할 수가 있다.
동그라미 친 부분을 보면, 똑같은 구멍이 연속적으로 선택되는 경우가 있다는 것을 알게 될텐데
똑같은 부분이 연속적으로 선택된다고 해서, 똑같은 구멍에서 두더지가 겹치게 튀어나오면 안된다는 것.
다음과 같은 해결책이 필요하다.
let lastHole; function randomHole(holes) { const idx = Math.floor(Math.random() * holes.length); const hole = holes[idx]; if(hole === lastHole) { return randomHole(holes); } lastHole = hole; return hole; }
약간의 재귀가 들어간다.
가장 최근에 return했던 hole을 저장하는 'lastHole'이라는 변수를 만들어놓고,
만약 이번에 랜덤하게 골라진 hole이 lastHole과 같을 경우 다시 randomHole을 호출한 후에 return하도록 하는 것.
'return randomHole(holes)' 같은 재귀 코드는,
그냥 randomHole을 호출하고 기존의 함수는 끝낼 필요가 있을 때 사용한다고 보면 될 듯.
그렇게 해서 새롭게 호출한 randomHole 함수에서는 랜덤하게 고른 hole과 lastHole이 같을 수도 있고 다를 수도 있다.
만약 다르다면 이번에 랜덤하게 골라진 hole을 새로운 lastHole로 지정하고 해당 hole을 return하면 된다.
그렇지 않다면 lastHole과 다른 hole이 골라질 때까지 계속해서 재귀적으로 호출을 하겠지.
이런 과정을 거쳐 선택된 hole은 이전에 선택되었던 hole과 절대 같을 수 없다.
이제 두더지가 튀어나오도록 하는 peep 함수를 작성할 차례이다.
지금까지 작성해뒀던 함수들을 사용하면 금방 만들 수 있다.
function peep() { const time = randTime(200, 1000); const hole = randomHole(holes); hole.classList.add('up'); }
time은 ms 단위로 200ms에서 1000ms사이의 랜덤한 시간동안 튀어나와있도록 randTime 함수를 이용해주고
hole은 그냥 랜덤한 구멍을 골라주기 위해서 구멍들의 nodeList인 holes를 집어넣어주면 된다.
참고로 'up'이라는 클래스는 안에 있는 mole의 top을 100% 에서 0% 로 변경해주는 클래스이다.
근데 두더지가 튀어나와서 들어가지 않는다는 것을 알 수 있다.
두더지가 튀어나왔다가, 우리가 지정한 시간인 'time'만큼 기다린 후에 들어갈 수 있도록
setTimeout을 달아주도록 하자.
그리고 당연히, 게임이 끝날 때까지는 두더지가 계속해서 튀어나와야 하니까
맨 처음에 한 번만 peep()을 호출해주면 두더지 하나가 들어가고나서
다른 구멍에서 두더지가 다시 튀어나올 수 있도록
setTimeout의 마지막에 peep()을 다시 호출해준다.
function peep() { const time = randTime(200, 1000); const hole = randomHole(holes); hole.classList.add('up'); setTimeout(() => { hole.classList.remove('up'); peep(); }, time); }
근데 이렇게만 해주면 또 게임이 끝나질 않는다.
그래서 timeUp이라는 boolean 변수를 만들어서, timeUp이 false인 동안만 peep()을 호출해주기로 하자.
function peep() { const time = randTime(200, 1000); const hole = randomHole(holes); hole.classList.add('up'); setTimeout(() => { hole.classList.remove('up'); if(!timeUp) peep(); }, time); }
이제 startGame이라는 함수를 만든다.
이 함수는 말 그대로 게임을 시작하는 함수로써, 스코어 보드를 0으로 초기화시켜주고
timeUp을 false로 만들고 peep()을 호출해준다.
그리고 10초동안만 게임을 진행하기 위해서,
역시나 setTimeout을 이용해서 10초가 지나면 timeUp을 true로 만들어주도록 설정한다.
function startGame() { scoreBoard.textContent = 0; timeUp = false; peep(); setTimeout(() => timeUp = true, 10000); }
마지막으로 작성해 줄 함수는, 두더지를 클릭했을 때 스코어가 증가하고
두더지가 쏙 들어가도록 만들어주는 함수이다.
이름은 'bonk'. 망치로 두더지 머리를 콩 하고 찍는 효과음을 생각하면 된다.
let score = 0; function startGame() { scoreBoard.textContent = 0; timeUp = false; score = 0; // 요기 peep(); setTimeout(() => timeUp = true, 10000); } function bonk(e) { if(!e.isTrusted) return; score++; this.classList.remove('up'); scoreBoard.textContent = score; } moles.forEach(mole => mole.addEventListener('click', bonk));
일단 score라는 변수를 추가하고, 게임을 시작할 때 score를 0으로 만들어준다.
bonk 함수의 맨 처음을 보면, e.isTrusted가 true인 경우를 걸러주는 것을 알 수 있다.
e.isTrusted는 자바스크립트로 가짜로 만들어 낸 클릭 이벤트가 아니라,
사용자가 실제로 마우스로 클릭을 해서 만들어 낸 클릭 이벤트일 경우에만 true가 된다.
그래서 치팅을 방지하는 느낌으로 간단한 if문 처리를 해주었다.
(e.isTrusted라는 속성을 알려주기 위해서 억지로 넣은 듯한 느낌이 솔솔 나긴 한다)
만약 실제로 발생한 클릭인 경우, score를 1만큼 증가시키고
해당 구멍에 붙은 'up' 클래스를 제거해준다. 그러면 두더지가 내려가기 때문.
그리고 스코어보드를 업데이트 해주면 된다.
전체 자바스크립트 코드
const holes = document.querySelectorAll('.hole'); const scoreBoard = document.querySelector('.score'); const moles = document.querySelectorAll('.mole'); let lastHole; let timeUp = false; let score = 0; function randTime(min, max) { return Math.round(Math.random() * (max - min) + min); } function randomHole(holes) { const idx = Math.floor(Math.random() * holes.length); const hole = holes[idx]; if(hole === lastHole) { return randomHole(holes); } lastHole = hole; return hole; } function peep() { const time = randTime(200, 1000); const hole = randomHole(holes); hole.classList.add('up'); setTimeout(() => { hole.classList.remove('up'); if(!timeUp) peep(); }, time); } function startGame() { scoreBoard.textContent = 0; timeUp = false; score = 0; peep(); setTimeout(() => timeUp = true, 10000); } function bonk(e) { if(!e.isTrusted) return; score++; this.classList.remove('up'); scoreBoard.textContent = score; } moles.forEach(mole => mole.addEventListener('click', bonk));
반응형'Web > JS30' 카테고리의 다른 글
Countdown Timer ⏱ (0) 2021.01.02 Video Speed Controller UI (0) 2020.12.27 Click and Drag to Scroll (0) 2020.12.26 Stripe Follow Along Nav (0) 2020.12.26 Event Capture, Propagation, Bubbling and Once (0) 2020.12.24