iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
JavaScript

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

Day27 RPG6:撿拾藥草到背包的狀態機練習

  • 分享至 

  • xImage
  •  

時間很快地來到了第 27 天,真是沒想到我也能走得這麼遠。

今天我們要先來完成撿拾藥草放到背包裡的這個功能,原因是接下來才可以在撿拾完藥草之後完成任務。

一樣我先來描述一下想做出怎樣的功能:

  1. 靠近在藥草的 H 四周按下 F 按鍵,藥草會消失,背包數量加一
  2. 在任意位置按下 G 按鍵,可以放下藥草到當下的位置,背包數量減一

其實感覺還蠻簡單的,不過因為前面都是做單一物件的狀態切換,現在我們就必須要來處理到多個藥草要如何去規劃狀態的問題,這個之後再來想。

狀態機設計

之前其實有畫過,不過針對後來的設計,我稍微調整一下:

https://ithelp.ithome.com.tw/upload/images/20261009/20182439qJkEb4qMkN.png

步驟拆解

  1. 將狀態加入到藥草的遊戲物件中
  2. 新增背包側邊欄位
  3. 設定監聽到 F 的時候,將藥草從畫面上移除、計入背包數量
  4. 設定監聽到 G 的時候,將藥草顯示於玩家位置、不計入背包數量

從觀察開始

一樣要來觀察從哪裡下手,回顧一下藥草的物件是從哪裡產生的:

// 森林物件
const forest = createForest();
const trees = forest.trees;
const herbs = forest.herbs;
const forestExit = forest.exit;
const forestWalls = forest.walls;

在這一段程式碼中,我們看到 herbs 是從 createForest 這裡出來的,所以理想狀態是可以從 createForest 出來的時候,就已經有帶狀態的欄位:

function createForest() {
    let walls = [];
    let trees = [];
    let herbs = [];
    let exit;

    // 之後換地圖只要改這裡
    const forest = [
        '##############################',
        '#           TTT  H           #',
        '#           TTTTT   H        #',
        '#              TTTT    TTT   #',
        '#     T     TTTTT      TTT   #',
        '#%    TT               TTT   #',
        '#     TTT H      TTTTTH      #',
        '#     TTTT       TTT         #',
        '##############################',
    ]

    for (let y = 0; y < forest.length; y++) {

        for (let x = 0; x < forest[y].length; x++) {

            if (forest[y][x] === '#') {
                walls.push({ x: x, y: y })

            }
            if (forest[y][x] === 'T') {
                trees.push({ x: x, y: y })
            }
            if (forest[y][x] === 'H') {
                herbs.push({ x: x, y: y , status: 'floor'}) // 修改這一行,floor / bag
            }
            if (forest[y][x] === '%') {
                exit = { x, y }
            }
        }

    }

    return { walls, trees, herbs, exit };
}

完成這個功能吧!

後續的實作也都蠻簡單的,就依序來完成

新增背包的側邊欄位

let bag = 0; // 制定變數
            // 擴充一下 render 裡面的這一段
            if (y === canvaMinY) {
                row += ` 任務:${task}`;
            }else if (y === canvaMinY + 1) { // 在第二行加上背包
                row += ` 背包:${bag}`;
            }

設定監聽到 F 的時候,將藥草從畫面上移除、計入背包數量

        // 新增監聽
        if ((key === 'f' || key === 'F')) {
            pickUpHerbs();
        }

在新增監聽這裡,我們不放 return 的原因是,這裡沒有要重新渲染畫面,這邊只是要修改 herb 的狀態,我們還是需要監聽區塊結尾的 render 來重新渲染地圖。

接著來處理 pickUpHerbs ,因為 herbs 是帶有多個藥草的陣列,因此我們需要加一層 for 迴圈來逐一判斷 player 是否位於他的四周,如果是的話就撿起那個 herb 。

function pickUpHerbs() {

    for (let i = 0; i < herbs.length; i++) {
        const herbX = herbs[i].x;
        const herbY = herbs[i].y;

        const surroundingCells = [
            { x: herbX, y: herbY },
            { x: herbX - 1, y: herbY },
            { x: herbX + 1, y: herbY },
            { x: herbX, y: herbY - 1 },
            { x: herbX, y: herbY + 1 },
        ]

        const isNext = surroundingCells.some(cell => (cell.x === player.x && cell.y === player.y))

        if (isNext && herbs[i].status === 'floor') {
            herbs[i].status = 'bag'
        }
    }

}

但是現在我們還需要依據狀態,讓地圖上只渲染 status 為 floor 的項目,所以在 render 內修改條件:

 else if (herbs.some(herb => herb.x === x && herb.y === y && herb.status === 'floor')) {
   row += `\x1b35mH\x1b[0m`
 }

試玩看看吧!

1

現在已經可以成功把藥草隱藏起來了!但是還需要計算背包數量,在剛剛寫好的修改狀態的函式中加上 bag 的數量增加一個。

       if (isNext && herbs[i].status === 'floor') {
            herbs[i].status = 'bag';
            bag += 1;
        }

玩玩看:

2

設定監聽到 G 的時候,將藥草顯示於玩家位置、不計入背包數量

一樣我們就新增監聽跟函式

        // 新增監聽
        if ((key === 'g' || key === 'G')) {
            putDownHerbs();
        }
function putDownHerbs () {
    // 找到第一個 status 為 bag 的項目

    // 修改狀態、背包
}

這裡第一題有點卡關,我先停一下來想一想,我腦海中第一個浮現的是 find 方法,不過有一點忘記是不是我想的那樣,我想確定一下:

https://ithelp.ithome.com.tw/upload/images/20261009/20182439rJXW6W41wQ.png

那就試著寫看看:

function putDownHerbs () {
    // 找到第一個 status 為 bag 的項目
    let hasHerb = herbs.find(herb => herb.status === 'bag');

    // 修改狀態、背包
    hasHerb.status === 'floor';
    bag -= 1;
}

我先試著這樣寫,不過我感覺還少了指定玩家位置給它,但我還是想先印出來看看是不是可以放回到地圖上,不過我預想可能會被放回原地:

3

結果我發現沒有放回原位,而是整個放不回來,原來是這邊出錯了

// 修改狀態、背包
hasHerb.status === 'floor';

我在這裡用成了 === 比較的用法,重新賦值應該要用 = ,唉,有時候還是會出這種錯呢。

修改之後就可以放回了,只不過是放回到原地:

4

跟我們剛剛預想的一樣了!來加上用 player 座標重新定位的邏輯吧

    // 修改狀態、背包
    hasHerb.x = player.x;
    hasHerb.y = player.y;
    hasHerb.status = 'floor';
    bag -= 1;

5

這樣就完成了!我們現在可以撿拾藥草到背包裡,並且將它放回到地圖上囉。明天我們要來完成整個遊戲的最後一步,就是回報任務啦,如果你願意的話,試著擬定看看流程吧,這個部分使用現有的架構就可以了嗎?會不會需要用到狀態機呢。

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


上一篇
Day26 RPG4:在完成 NPC 對話的過程中,看見經驗在發酵
系列文
重新拿回思考力!一起來用 JavaScript 打造 CLI 好玩遊戲區! 共 27 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言