新手村30 - Whack A Mole

30 - Whack A Mole

俗話說的好,一天一蘋果,醫生遠離我

一天一 JS,What the f*ck JavaScript?

small steps every day - 記錄著新手村日記

完成目標

做一個打地鼠的遊戲

  • 功能
    • 一個關卡要幾分鐘要固定
    • 地鼠冒出來、自動縮下去的時間間隔要不一樣
  • 畫面
    • 畫面要顯示目前的關卡
    • 地鼠要自動冒出來、縮下去
    • 地鼠被點到要縮下去、數量要增加

index_START.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Whack A Mole!</title>
<link href='https://fonts.googleapis.com/css?family=Amatic+SC:400,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="style.css">
</head>
<body>

<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>

<script>
const holes = document.querySelectorAll('.hole');
const scoreBoard = document.querySelector('.score');
const moles = document.querySelectorAll('.mole');

</script>
</body>
</html>

style.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
html {
box-sizing: border-box;
font-size: 10px;
background: #ffc600;
}

*, *:before, *:after {
box-sizing: inherit;
}

body {
padding: 0;
margin: 0;
font-family: 'Amatic SC', cursive;
}

h1 {
text-align: center;
font-size: 10rem;
line-height: 1;
margin-bottom: 0;
}

.score {
background: rgba(255,255,255,0.2);
padding: 0 3rem;
line-height: 1;
border-radius: 1rem;
}

.game {
width: 600px;
height: 400px;
display: flex;
flex-wrap: wrap;
margin: 0 auto;
}

.hole {
flex: 1 0 33.33%;
overflow: hidden;
position: relative;
}

.hole:after {
display: block;
background: url(dirt.svg) bottom center no-repeat;
background-size: contain;
content: '';
width: 100%;
height:70px;
position: absolute;
z-index: 2;
bottom: -30px;
}

.mole {
background: url('mole.svg') bottom center no-repeat;
background-size: 60%;
position: absolute;
top: 100%;
width: 100%;
height: 100%;
transition:all 0.4s;
}

.hole.up .mole {
top: 0;
}

JS - step by step

首先,我們先將需要的元素選取起來,必須先找到地鼠洞的List、地鼠圖的List、 span 的數量

1
2
3
4
5
6
7
8
9
<script>
const holes = document.querySelectorAll('.hole');
const scoreBoard = document.querySelector('.score');
const moles = document.querySelectorAll('.mole');
console.log(holes)
console.log(scoreBoard)
console.log(moles)

</script>

先來製作一個函式,讓這個函式執行後有一個上下限的隨機時間

1
2
3
4
5
6
7
8
9
10
<script>
const holes = document.querySelectorAll('.hole');
const scoreBoard = document.querySelector('.score');
const moles = document.querySelectorAll('.mole');

function randomTime(min, max) {
let time = Math.floor(Math.random() * (max - min) + min);
return time
};
</script>

接下來,我們來取得隨機一個 hole 元素,如果與上一次取到的相同,我們就再次執行一次隨機選取的函式,不同的話就把現在的Hole指定給變數 lastHole

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script>
const holes = document.querySelectorAll('.hole');
const scoreBoard = document.querySelector('.score');
const moles = document.querySelectorAll('.mole');

function randomTime(min, max) {
let time = Math.floor(Math.random() * (max - min) + min);
return time
};

let lastHole;

function randomHole(holes) {
const idx = Math.floor(Math.random() * holes.length);
const hole = holes[idx];
if (hole === lastHole) {
console.log('Ah nah thats the same one bud');
return randomHole(holes);
}
lastHole = hole;
return hole;
}
</script>

時間都處理完畢後,我們要來來地鼠可以從地鼠洞跑出來,在前面的CSS當中可以知道,地鼠的一開始是被做了相對定位並 top:100%overflow:hidden 起來了!因此剛開始的時候地鼠其實是偷偷躲在底下的w

現在只要判斷什麼時候加上一個 Class 讓地鼠從地鼠洞出來以及多久會從地鼠洞出來一次,要新增&移除 Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>  
//上略
let timeUp = false;

function peep() {
const time = randomTime(200, 1000);
const hole = randomHole(holes);
hole.classList.add('up');
setTimeout(() => {
hole.classList.remove('up');
if (!timeUp) peep();
}, time);
}
</script>

最後,再加上遊戲開始及點到一個就增加分數吧!這部分比較單純只要將 score 宣告變數出來,以及點到分數++就可以,然後趕緊來打地鼠吧w

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>  
//上略
moles.forEach(mole => mole.addEventListener('click', bonk));

function bonk(e) {
if(!e.isTrusted) return; // cheater!
score++;
this.parentNode.classList.remove('up');
scoreBoard.textContent = score;
}

function startGame() {
scoreBoard.textContent = 0;
timeUp = false;
score = 0;
peep();
setTimeout(() => timeUp = true, 10000)
}
</script>

就大功告成啦!

JS - final

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<script>
const holes = document.querySelectorAll('.hole');
const scoreBoard = document.querySelector('.score');
const moles = document.querySelectorAll('.mole');

function randomTime(min, max) {
let time = Math.floor(Math.random() * (max - min) + min);
return time
};

let lastHole;

function randomHole(holes) {
const idx = Math.floor(Math.random() * holes.length);
const hole = holes[idx];
if (hole === lastHole) {
console.log('Ah nah thats the same one bud');
return randomHole(holes);
}
lastHole = hole;
return hole;
}

function peep() {
const time = randomTime(200, 1000);
const hole = randomHole(holes);
hole.classList.add('up');
setTimeout(() => {
hole.classList.remove('up');
if (!timeUp) peep();
}, time);
}

moles.forEach(mole => mole.addEventListener('click', bonk));

function bonk(e) {
if(!e.isTrusted) return; // cheater!
score++;
this.parentNode.classList.remove('up');
scoreBoard.textContent = score;
}

function startGame() {
scoreBoard.textContent = 0;
timeUp = false;
score = 0;
peep();
setTimeout(() => timeUp = true, 10000)
}

</script>

本刊同步於個人網站:http://chestertang.site/

本次範例程式碼原作者來源:https://tinyurl.com/yavm5f5n