時間很快地來到了第 27 天,真是沒想到我也能走得這麼遠。
今天我們要先來完成撿拾藥草放到背包裡的這個功能,原因是接下來才可以在撿拾完藥草之後完成任務。
一樣我先來描述一下想做出怎樣的功能:
H 四周按下 F 按鍵,藥草會消失,背包數量加一其實感覺還蠻簡單的,不過因為前面都是做單一物件的狀態切換,現在我們就必須要來處理到多個藥草要如何去規劃狀態的問題,這個之後再來想。
之前其實有畫過,不過針對後來的設計,我稍微調整一下:

一樣要來觀察從哪裡下手,回顧一下藥草的物件是從哪裡產生的:
// 森林物件
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}`;
}
// 新增監聽
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`
}
試玩看看吧!
現在已經可以成功把藥草隱藏起來了!但是還需要計算背包數量,在剛剛寫好的修改狀態的函式中加上 bag 的數量增加一個。
if (isNext && herbs[i].status === 'floor') {
herbs[i].status = 'bag';
bag += 1;
}
玩玩看:
一樣我們就新增監聽跟函式
// 新增監聽
if ((key === 'g' || key === 'G')) {
putDownHerbs();
}
function putDownHerbs () {
// 找到第一個 status 為 bag 的項目
// 修改狀態、背包
}
這裡第一題有點卡關,我先停一下來想一想,我腦海中第一個浮現的是 find 方法,不過有一點忘記是不是我想的那樣,我想確定一下:

那就試著寫看看:
function putDownHerbs () {
// 找到第一個 status 為 bag 的項目
let hasHerb = herbs.find(herb => herb.status === 'bag');
// 修改狀態、背包
hasHerb.status === 'floor';
bag -= 1;
}
我先試著這樣寫,不過我感覺還少了指定玩家位置給它,但我還是想先印出來看看是不是可以放回到地圖上,不過我預想可能會被放回原地:
結果我發現沒有放回原位,而是整個放不回來,原來是這邊出錯了
// 修改狀態、背包
hasHerb.status === 'floor';
我在這裡用成了 === 比較的用法,重新賦值應該要用 = ,唉,有時候還是會出這種錯呢。
修改之後就可以放回了,只不過是放回到原地:
跟我們剛剛預想的一樣了!來加上用 player 座標重新定位的邏輯吧
// 修改狀態、背包
hasHerb.x = player.x;
hasHerb.y = player.y;
hasHerb.status = 'floor';
bag -= 1;
這樣就完成了!我們現在可以撿拾藥草到背包裡,並且將它放回到地圖上囉。明天我們要來完成整個遊戲的最後一步,就是回報任務啦,如果你願意的話,試著擬定看看流程吧,這個部分使用現有的架構就可以了嗎?會不會需要用到狀態機呢。
如果你想要瀏覽完整的程式碼,可以參考這裡:第 27 天程式碼