iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
JavaScript

重新拿回思考力!一起來用 JavaScript 打造 CLI 好玩遊戲區!系列 第 19 篇

Day19 爆爆王11: 先玩 10 遍,不行的話就 20 遍

  • 分享至 

  • xImage
  •  

今天的目標是要讓爆爆王有一個可以玩的版本出來。其實我前面在開發的時候就時不時看到一些需要修改的問題,但我刻意留到這個時間,主要是因為我不想要打亂前面的思考主軸。

現在其實我整理了一些需要修改的項目,不過我比較期待和大家一起親眼見證會發生什麼問題,所以今天的主題是:先玩 10 遍,不行的話就 20 遍。

意思是說我們就直接從玩遊戲開始,每次遊戲時發現問題,就馬上停下來修改,再玩下一次,如果玩 10 遍修不好,我們就玩 20 遍!這樣是不是比起單純照著已知的清單修改還要有趣一點呢,或許還可以順便修正一些之前沒注意到的錯誤。

那就馬上開始吧!

儀表板敵人數字沒有減少

敵人數量沒有減少

第一遍我們就發現儀表板沒有隨著敵人減少,但是在 Day15 的時候實測是有成功扣除的,所以問題是出在哪裡呢?一樣不知道的時候不卡住,馬上請 AI 來幫忙除錯。

然後發現原來是因為我們踩到了 JavaScript 傳值與傳址的大坑裡面去了,有興趣深入研究的人可以參考我找到的這篇參考資料,感謝鐵人前輩的貢獻。總之由於 enemies 是用下面這種寫法:

const enemies = [enemy1, enemy2, enemy3];

這裡面每個 enemy 都有自己原本的位址,但我們這幾天做了重新賦值:

function moveEnemy(map, boxes, enemy, player,bombs,explodeCells) {

    // 計算並重新賦予座標
    if (enemy === enemy1) {
        enemy1 = pickNextEnemy1(map, boxes, enemy1);
    }

    if (enemy === enemy2) {
        enemy2 = pickNextEnemy2(map, boxes, enemy2, player);
    }

    if (enemy === enemy3) {
        enemy3 = pickNextEnemy3(map, boxes, enemy3, player,bombs,explodeCells);
    }
}

在這裡每個 enemy 都新的位址給覆蓋掉了,但是 enemies 裡面保存的還是舊的位址,所以才會沒更新到。

那我們就馬上來修正吧!

// 把 explodeBomb 裡面的這一句
enemyNum = enemies.filter(enemy => enemy.live === true).length;

// 改成直接用新的 enemy 計算
enemyNum = [enemy1, enemy2, enemy3].filter(enemy => enemy.live === true).length;

玩家沒有扣血

這次我們發現了兩個問題:

  1. 就遊玩的體驗上來講,敵人不應該跨過炸彈,不然玩家就毫無策略可言了,這是設計上的問題
  2. 另一個屬於比較大的 bug ,敵人碰到玩家但沒有扣血,這是我們還沒實作的邏輯

碰到玩家要扣 HP

先從簡單的開始,扣個 HP 吧!不過該在哪裡寫這個邏輯比較好呢?

  1. 接在敵人移動的邏輯後面,每次敵人移動如果碰到玩家就扣 HP
  2. 接在玩家移動的邏輯後面,每次玩家移動如果碰到敵人就扣HP

好像都怪怪的,那萬一敵人移動、玩家也移動,不就扣兩次?

所以我們寫在 render 裡面好了!這樣每次 render 的時候拿當下的座標比較,如果重疊了就可以扣血。

試試看,大家先別跟我說這個可以怎麼精簡,我就說了要從最爛的方法寫起嘛! clean code 是後面的事!

if(
    (player.x=== enemy1.x && player.y === enemy1.y) || 
    (player.x=== enemy2.x && player.y === enemy2.y) || 
    (player.x=== enemy3.x && player.y === enemy3.y)
    ) {
        HP -= 1;
    }

扣血扣太多了

第三次發現 HP 都扣到 -5 了,結果遊戲還沒結束!原來是因為遊戲結束目前被寫在炸彈爆炸的邏輯裡面,那我們現在來看看哪些東西一起搬到 render 會更乾淨:

    // 勝負判定
    if (HP <= 0) {
        process.stdout.write("\n失去所有HP,遊戲結束\n");
        process.exit();
    }

    if (enemyNum <= 0) {
        process.stdout.write("\n所有敵人都被消滅,恭喜獲勝\n");
        process.exit();
    }

我把這兩個勝負判定的邏輯一起移動到 render 印出畫面的最下方,然後再來嘗試第四次:

還沒碰到敵人就扣HP

還沒碰到敵人就扣了 HP

第四次我們發現到敵人根本還沒碰到玩家,就扣了 HP,經過 AI 排查原因是因為這兩個計時器發生了競態:

  • 炸彈爆炸的 setTimeout
  • 敵人移動的 setInterval

我要來用上之前曾經用過的方法,先 commit 現在的修改,然後讓 AI 來修正這個錯誤

https://ithelp.ithome.com.tw/upload/images/20261002/20182439ZvbNDeB1nk.png

從這裡我們可以看到 AI 加了一個 explodeAt 的變數,目的在於計算炸彈什麼時候會爆炸

https://ithelp.ithome.com.tw/upload/images/20261002/20182439XpZRluOjTw.png

接著在敵人的 setInterval 裡面加上了判斷,如果有炸彈在這次移動中要爆炸,敵人會跳過這一次的移動,確保炸彈的傷害先判定完成。

來嘗試第五次

第五次

這次終於可以玩了,不過敵人可以穿過炸彈還是太可怕了一點,很容易被逼到死角,我們要趕快處理這個問題。

敵人不能穿過炸彈

這邊又想到一個有趣的設計問題:如果敵人不能穿越炸彈,那玩家可以嗎?玩家目前放置炸彈一定會跟炸彈重疊,但如果離開了這顆炸彈,就不能再穿越呢?來試試看這個做法。

一樣先 commit 然後讓 AI 來做:

https://ithelp.ithome.com.tw/upload/images/20261002/20182439vdGbkoVXHV.png

首先我們看到它加了判斷下一步是不是炸彈,如果是的話就直接終止移動。

https://ithelp.ithome.com.tw/upload/images/20261002/20182439oJOSOrOqkk.png

接著我們看到它在 enemy1 和 enemy2 多傳入了 bombs ,我猜目的是為了進到裡面去判斷前方是不是炸彈。

https://ithelp.ithome.com.tw/upload/images/20261002/20182439uy2WEudWYu.png

驗證了確實它加入了這個判斷的邏輯以避免 AI 走入炸彈, enemy2 和 enemy3 都有做類似的判斷,如同先前所說,我這邊先不考慮如何精簡,我只要確定可以動就好。

試玩第六次了!!

第六次

莫名其妙死了

唉,第六次確實敵人跟玩家都不能夠穿越炸彈,但是為什麼敵人在遠方、也沒有炸彈,卻莫名其妙死掉了,我們得排查這個問題,經過 AI 排查之後發現事情是這樣發生的:

  1. 建立三顆炸彈、三個 setTimeout。
  2. 第一顆爆炸時,程式把同座標的三筆炸彈全部從畫面資料刪除。
  3. 另外兩個 setTimeout 並沒有取消,之後仍會呼叫 explodeBomb()。
  4. 如果玩家當時位於該格或上下左右,就會再次扣 HP

也就是說玩家會被幽靈爆炸給炸傷啦!!!

那一樣先 commit 前面的修改之後交給 AI 實作:

https://ithelp.ithome.com.tw/upload/images/20261002/20182439pz5kyGGl5V.png

這邊可以看到它在放置炸彈的時候也去判斷,如果該格已經是炸彈就不能再放置。另外它制定了 bomb 傳入 explodeBomb ,目的是要在最後「只清除這顆炸彈」,原本的邏輯是會清除掉這個座標全部的炸彈。

那我們就再玩第七次:

7

什麼啊!!!! 結果還是莫名其妙死了,原來主要是因為死掉的敵人也還在移動,而且會造成傷害,以下是修正:

https://ithelp.ithome.com.tw/upload/images/20261002/201824394K1BfY89Km.png

這邊 AI 修改讓還活著的敵人才能造成傷害。

https://ithelp.ithome.com.tw/upload/images/20261002/20182439wMS5gwM5Bv.png

這一段也是它修改如果敵人已死亡就不進行移動。

真希望這是最後一次測試XD 第八次來囉!

8

炸彈爆炸瞬間就消失了

第八次的遊玩終於比較順利一點了,但是炸彈有時候爆炸會看不見就消失了,或者很快的閃過去還來不及反應,顯然是重繪的時間點不對,我們要修正一下這個問題:

https://ithelp.ithome.com.tw/upload/images/20261002/20182439FK57up0V7D.png

這邊把原本寫在 setTimeout 之前的清除爆炸邏輯,移動到 setTimeout 裡面,以保留爆炸至少顯示一秒。

第九次!!!快結束了!!!

9

歷經千辛萬苦,我們終於是可以正常遊玩了!!!可喜可賀。你是不是也鬆一口氣了呢XD

今天的內容稍微繁瑣了一些,記錄了我每次修正的過程,當中遇到的部分問題其實可以在開發時先預防,不過我想我不是那種天才型的開發者,也還沒有累積足夠多的經驗以去判斷哪些邏輯這樣寫會出錯、那樣寫才安全。

所以我想藉由今天一邊遊玩一邊更新來發現:原來我這樣寫會導致幽靈炸彈、幽靈敵人的產生,又或者注意到在有多個計時器的狀況下要去避免掉競態的問題。

所以我想我們可以很自豪的說今天又多累積了一些經驗了!

那麼到這邊爆爆王的遊戲就做完啦,不過我建議還是多玩個幾次,以免有些 bug 其實是在還沒遇到的情境下觸發的。

明天我們又要做些什麼呢,大家有想到哪些遊戲可以做嗎,或者是有想要在現有的架構上做那些調整呢?我們明天見!

如果你想要瀏覽完整的程式碼,可以參考這裡:第 19 天程式碼


上一篇
Day 18 爆爆王10:演算法練習2 兼論思考的順序與目的
系列文
重新拿回思考力!一起來用 JavaScript 打造 CLI 好玩遊戲區! 共 19 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言