iT邦幫忙

2022 iThome 鐵人賽

DAY 30
0
自我挑戰組

JavaScript 30天挑戰 自學筆記系列 第 30

JS30 自學筆記 Day30_Whack A Mole Game

  • 分享至 

  • xImage
  •  

今日任務: 打地鼠

CSS部分

將洞class加上'up'時,地鼠就會出現

.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部分

Math.random(): 會回傳一個偽隨機小數 (pseudo-random) 介於 0 到 1 之間(包含 0,不包含 1)。

獲取元素

const holes = document.querySelectorAll('.hole');
const moles = document.querySelectorAll('.mole');
const score = document.querySelector('.score');

地鼠出現多久時間

function randomTime(min, max) {
    return Math.round((max - min) * Math.random() + min);
}

地鼠出現的洞

設定lastIdx是因為不想要地鼠連續兩次都出現在同一個洞。

計算機率:

1.const index = Math.round(Math.random() * (holes.length - 1))
帶入6個洞的話=Math.round(0~4.999)
機率為:
0~0.49=>0號洞
0.5~1.4=>1號洞
...
3.5~4.4=>4號洞
4.5~4.9=>5號洞
=> 0號洞和5號洞機率會比較小
2.const index = Math.floor(Math.random() * (holes.length));
帶入6個洞的話=Math.floor(0~5.999)
機率為
0~0.9=>0號洞
1~1.9=>1號洞
...
4~4.9=>4號洞
5~5.9=>5號洞
=> 機率一樣

let lastIdx;

function randomHole(holes) {
    const index = Math.floor(Math.random() * (holes.length));
    if (lastIdx == index) {
        console.log('重複了');
        return randomHole(holes);
    }
    const hole = holes[index];
    lastIdx = index;
    console.log(index);
    return hole;
}

地鼠出現/消失

地鼠出現/消失

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

循環不斷出現/消失

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

時間結束

let timeUp = false;

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

遊戲開始

HTML按鈕已經綁定startGame(),遊戲時間限制10秒內。
HTML:

<button onClick="startGame()">Start!</button>

JS:

function startGame() {
    scoreBoard.textContent = 0; //將畫面分數歸零
    timeUp = false;
    peep();
    setTimeout(() => (timeUp = true), 10000);
}

打到地鼠計分

遊戲開始分數歸零

let score = 0; //設定分數

function startGame() {
    scoreBoard.textContent = 0;
    score = 0; //遊戲開始分數歸零
    timeUp = false;
    peep();
    setTimeout(() => (timeUp = true), 10000);
}

打到地鼠消失,分數加1,顯示在計分板上

Event.isTrusted: 若事件物件是由使用者操作而產生,則 isTrusted 值為 true。
parentNode: 返回元素或節點的父節點。
因為class='up' 是加在hole上面,所以使用parentNode來找到mole的父層(hole),並移除class='up'。

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

避免連點事件

測試時發現連點會連續加分,使用setTimeout來避免玩家快速連點

let canClick = true; //可以點擊加分

function hit(e) {
    if (!e.isTrusted) return;

    if (canClick) {
        canClick = false;
        score++;
    }
    setTimeout(() => {
        canClick = true;
    }, 100);

    this.parentNode.classList.remove('up');
    scoreBoard.textContent = score;
}

額外增加

另外再加上倒數計時功能和遊戲介面,讓遊戲比較完整一些

倒數計時功能

  • 倒數計時功能: 可參考Day29
  • 監聽transition結束後執行某函式: 可參考Day01
  • 遊戲介面:可以參考W3.CSS Modal
function startGame() {
    scoreBoard.textContent = 0;
    score = 0;
    timeUp = false;
    modal.style.display = 'none'; //關閉modal
    peep();
    timer(10);
}

function timer(seconds) {
    clearInterval(countDown);
    displayTime(seconds);

    const now = Date.now();
    const then = Date.now() + seconds * 1000;

    countDown = setInterval(() => {
        let secondsLeft = Math.round((then - Date.now()) / 1000);

        if (secondsLeft <= 0) {
            displayTime(secondsLeft);
            timeUp = true; //時間到
            modalText(); //遊戲結束介面文字
            modal.style.display = 'block'; //開啟modal
            clearInterval(countDown);
            return;
        }
        displayTime(secondsLeft);
    }, 1000);
}

timeBoard.addEventListener('transitionend', function () {
    timeBoard.classList.remove('warn'); //可以製造縮放效果
});

function displayTime(seconds) {
    timeBoard.style.color = 'black';
    if (seconds > 0 && seconds <= 3) {
        timeBoard.style.color = 'red'; //倒數三秒提醒玩家
        timeBoard.classList.add('warn');
    }
    return (timeBoard.textContent = seconds);
}

function modalText() {
    modal.innerHTML = `
   <div class="modal-content">
        <h1>遊戲結束!</h1>
        <h2>成績:${score}</h2>
        <button onClick="startGame()">再來一次</button>
   </div>`;
}

避免版面太長詳請可以看我的git,還有很多功能可以增加,例如利用Day15的LocalStorage來存最高分,按下開始後倒數3秒再開始,打到地鼠後的特效等等,就留給大家自由發揮吧。

今日學習到的:

  • Math.random(): 會回傳一個偽隨機小數 (pseudo-random) 介於 0 到 1 之間(包含 0,不包含 1)
  • Event.isTrusted: 若事件物件是由使用者操作而產生,則 isTrusted 值為 true。
  • parentNode: 返回元素或節點的父節點。

效果連結:連結

參考連結:
MDN: Math.random()
MDN: Event.isTrusted
JS30

完賽心得

這30天的作品和git可以從這裡看到:Kate's JS30 自學筆記

在開賽之前,覺得要寫技術文章很難,別人會不會覺得我怎麼寫這麼簡單的東西,所以遲遲不敢開始,在截止日前抱著忐忑的心情按下參加,完全不確定能不能成功寫完30天,甚至是抱著「阿不然看看我能堅持幾天好了?」的心情參加的XD。經過這次鐵人賽,發現按下"發表文章"的瞬間超開心的,再看到觀看次數慢慢的增加,瞬間成就感爆棚。

以前筆記都是寫在自己的雲端上面自己享受(?),不需要考慮別人看不看得懂,或是在查資料的過程中,有些點沒有很了解但只是貼個網址想著以後再研究,大概理解後就往下一個前進,但在鐵人賽過程中,會想辦法畫示意圖,或是試著用口語化的方式描述,希望看我文章的人可以快速理解,花的時間比實際寫程式的時間還多,寫完之後對這些技術又有更深一層的認識,突然可以理解為什麼愛因斯坦說:「If you can’t explain it simply, you don’t understand it well enough.」透過整理和寫作,把這些知識點在腦中重新排列整理了一遍,我想受益最多的是我自己。

看到第30天時影片作者影片說:「I won't see you tomorrow.」,竟然有一種捨不得的感覺,雖然很累,但是很滿足。

我想,可以的話,之後還想繼續發文,謝謝看到這邊的你!


上一篇
JS30 自學筆記 Day29_Countdown Clock
系列文
JavaScript 30天挑戰 自學筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言