AI HW2 一條龍教學:未知地圖探索 Agent

2026-10-04|人工智慧導論(成大朱威達)作業二,個人作業,10/15(四)23:59 截止

一句話結論
agent.py 已經寫好、測過,也經過獨立審查:老師的 3 張公開地圖全部成功、0 次撞牆,平均 94.48 分; 在 5,400 張模擬地圖上 0 失敗、0 撞牆。預估正式 9 張隱藏地圖的平均約 91 分(大概落在 88~94 之間:我模擬了兩萬次「抽 9 張考一次」,中間 90% 的結果落在 88.4~94.2;前提是隱藏地圖跟公開地圖一樣把終點放得遠。如果終點是隨便放的,預估約 88.8 分)。 你只剩「改 zip 檔名、上傳」兩個動作。
怎麼看這份
左邊點一節看一節。每節開頭都有一句「結論」,趕時間只讀那一句就好。
整份文件照「先懂問題 → 再懂背後的道理 → 再看怎麼做 → 最後看結果」的順序排,從頭讀到尾就是一條龍。

這份文件的五個部分

部分在講什麼讀完你會知道
開始這份文件怎麼看、我實際做了哪些事整份作業是怎麼一步一步做出來的
A 作業介紹老師要你做什麼、分數怎麼算題目、規則、拿高分的關鍵
B 邏輯框架課本裡跟這份作業有關的所有觀念,從最基本的講起為什麼這個作業要這樣想;考試考觀念時答得出來
C 實作從框架走到程式:工具、比較、最終做法、逐行讀程式、為什麼保證不出錯程式在做什麼、為什麼這樣寫
D 驗證與結論怎麼測的、獨立審查的結果、怎麼交件、一條龍總回顧這份作業有多可靠、還剩什麼風險
E 考試準備跟投影片的對照、可能的考題、名詞小字典考前複習用

建議讀法(每天 90 分鐘以內)

天讀哪幾節
Day 1A1、A2
Day 2B1~B6(邏輯框架)
Day 3C1、C2(含回放)、C3、C4(用真實數字算一遍),然後用自己的話講一遍 C3 的七個步驟,講完就交件(D3)
Day 4C5(逐段讀程式)、C6(為什麼不撞牆、不卡死),自己跑一次測試(A1.15),再讀「我做了什麼」、D1、D2、D4
Day 5E1~E3(考試準備)

這是估計的份量。Day 1 是測試日,做完看實際花多久再調整。B 部分是這次新加的,如果 Day 2 讀不完就拆成兩天。

檔案在哪裡

檔案是什麼要交嗎
C:\D槽\TAICA課程\人工智慧導論\HW2\HW2\agent.py寫好的 Agent(本作業唯一要改的檔案)要(壓成 zip)
C:\D槽\TAICA課程\人工智慧導論\HW2\學號_姓名.zip已經打包好的交件檔,裡面只有 agent.py。檔名要改成你的學號姓名要
C:\D槽\TAICA課程\人工智慧導論\HW2\HW2_COOL公告.mdNTU COOL 上的作業公告原文(你貼給我的)不要
C:\D槽\TAICA課程\人工智慧導論\HW2\agent_starter_original.py老師原本給的空白範本(備份)不要
C:\D槽\TAICA課程\人工智慧導論\HW2\lab\我做實驗用的「模擬考」工具不要
C:\D槽\TAICA課程\人工智慧導論\HW2\HW2\ 其他檔案老師給的環境、評分程式、公開地圖、README不要(也不能改)

我做了什麼(完整工作紀錄)

結論:從找題目到交件檔,一共 24 個步驟。核心是「先做一個模擬考,再用模擬考比較好幾種走法,挑分數最高、而且絕對不會失敗的那個」。所有實驗的程式和數字都留在電腦裡,可以重跑。

為什麼要寫這一節

這份作業的程式是我寫的。你要交出去、也要能在考試時講出來,所以你需要知道「它是怎麼被做出來的」,而不是只看到最後的成品。

每一步我都寫了:做了什麼、為什麼要做、結果是什麼。

階段一:搞清楚題目(步驟 1~4)

#做了什麼為什麼結果
1到處找作業二的題目:你的電腦、課程網站作業頁(連藏在 HTML 註解裡的都查了)、課程網站的作業檔案夾、W3 上課逐字稿你一開始只給我「作業二、10/15 交、跟寫程式有關」都沒有。只找到老師在 W3 說「下週會在線上公告」。推測在 NTU COOL(要登入)
2你貼了 COOL 公告、把 HW2-1.zip 放進資料夾。我解壓縮,讀完全部 5 個檔案:README、agent.py、environment.py、public_grader.py、3 張公開地圖老師說完整規格以 README 為準,所以每個字都要讀搞懂規則、介面、評分公式(見 A1、A2)
3確認你電腦上有 Python 3.11;用老師的空白範本跑一次評分程式正式評分是 Python 3.11,要用同版本測;先確認測試環境本身能動有 3.11。空白範本 0/3 失敗、每步都撞牆(正常,它只會往上走)
4分析 3 張公開地圖:用完整地圖算出「終點離起點多遠,比幾 % 的格子遠」想知道老師出題有沒有規律,可以拿來設計策略發現終點都放得很遠(84%~99%)。這變成後面策略的關鍵線索

階段二:做模擬考工具(步驟 5)

#做了什麼為什麼結果
5在 HW2\lab\ 寫了一套「模擬考」:
  • mapgen.py:地圖產生器,照公開地圖的三種風格(空曠、死路、迴圈)隨機產生地圖,終點可選「放遠處」或「隨便放」
  • sim.py:用老師的 environment.py 跑 Agent,照老師的公式算分
  • strategies.py:好幾種走法放在一起,方便切換比較
  • compare.py:同一批地圖,每種走法都考一次,印成績表
正式考是 9 張沒看過的地圖。只看 3 張公開地圖,運氣成分太大,分不出哪種走法真的比較好可以一次考幾百、幾千張地圖,幾秒鐘出結果

「終點隨便放」這個選項是保險:萬一老師的隱藏地圖不是把終點放遠,我們的策略也不能變差。

階段三:比較走法、調整參數(步驟 6~10)

#做了什麼結果決定
6第一輪:360 張地圖,比較 DFS、最近邊境、偏好遠處(4 種強度)偏好遠處約 90.4 分,最近邊境 89.1,DFS 87.5。全部 0 失敗、0 撞牆偏好遠處有效,繼續往這個方向改
7第二輪:900 張地圖,細調強度,再試「用迷宮實際路程算遠近」強度 0.05~1.0 都差不多(約 90.5);太強(3.0)反而變差。實際路程版沒有比較好,還比較慢淘汰實際路程版
8第三輪:試一個更聰明的做法——估計每個邊境「後面藏了多少可能是終點的格子」分數最高到 91.08,但有幾張地圖失敗(0 分)不能直接用。一張 0 分會拉低平均 10 分以上,遠大於多賺的 0.6 分
9找失敗原因:Agent 每一步都重選目標,在兩個目標之間來回踱步,把步數用光。加上「承諾」規則:選定目標後一直走向它,直到它不再是邊境才重選失敗全部歸零;平均分只差一點(次方 4 那一組:終點偏遠 91.08 → 91.01,終點隨機 88.76 → 88.80)保留承諾規則
10調兩個參數:次方(試 0、2、4、6、8、10)和分母加多少(試 1、2、3、6)次方 6 最好(終點偏遠:次方 2、4、6、8、10 分別是 90.52、91.01、91.31、91.21、91.15)。分母加 1 和加 2 幾乎一樣(91.33 對 91.31,差 0.02),選 2定案:次方 6、加 2

階段四:寫正式版、驗證(步驟 11~14)

#做了什麼為什麼結果
11把實驗版改寫成乾淨的正式版 agent.py:加註解、拆成小函式、外面包一層「出錯就走安全的一步」的保險。原本的空白範本先備份實驗版為了方便切換寫得很亂,不適合交,也不適合你讀216 行(加安全閥後是 230 行),只用 Python 內建的 deque
12三種驗證:
  • 老師的公開測試(用 Python 3.11)
  • 抄寫檢查:正式版和實驗版在同一批 1,800 張地圖上分數要一模一樣
  • 壓力測試:跟調參數時不同的一批 5,400 張地圖,而且把保險拿掉,有錯就會爆出來
確認改寫沒有抄錯,也確認沒有藏著的錯誤公開 3/3 成功;分數完全一樣;壓力測試 0 失敗、0 撞牆、0 出錯
13算分數分布,模擬兩萬次「抽 9 張考一次」想知道正式考大概會拿幾分,以及運氣好壞的範圍中間值約 91.1 分;運氣差 88.3、運氣好 94.0(加安全閥後重算:91.2/88.4/94.2)
14打包 學號_姓名.zip,確認裡面只有一個 agent.py老師規定 zip 最外層只能有一份 agent.py完成,檔名等你改

階段五:教學與審查(步驟 15~17)

#做了什麼為什麼結果
15讀課程投影片 Ch2、Ch3、Ch4 的文字,找出跟這份作業有關的頁數考試會考作業內容,要知道作業對應課本哪裡發現這份作業就是 Ch4 的「線上搜尋」,評分公式就是投影片的 competitive ratio
16寫這份教學文件;依你的要求把 A1 擴充成 15 小段,再加上 B 部分(邏輯框架)和這一節(工作紀錄)你要從頭到尾都懂就是你正在讀的這份
17派 3 個互相獨立的 AI 審查員,各自從零開始檢查:規章合規、程式抗壓、內容正確性自己檢查自己容易放水。換一個沒參與過的審查員,標準才不會降低見 D2「獨立審查結果」
18依審查結果修正:文件 7 個錯誤+14 個容易誤會的地方全部改掉;C6 證明重寫審查員 3 指出這些會影響考試答案見 D2.4、D2.5
19依審查員 2 的發現,在程式裡加「安全閥」:走超過步數上限的 5% 後,改去最近的邊境。先在實驗室比較 9 種版本、用三批不同的地圖確認,再改正式版,舊版備份審查員造出一張用掉 90% 步數的地圖,太接近 0 分最刁鑽地圖從 90.2% 降到 29.7%,平均分沒有變差
20改完程式後重跑全部驗證:公開測試、跟實驗版逐張比對、全新 5,400 張壓力測試、60 張刁鑽地圖、重新打包 zip程式改了,之前的驗證都要重做全部通過(見 D1)
21在 C2 加「四種走法回放」:讓四種走法在同一張地圖上真的跑一遍,記下每一步,做成可以播放的畫面和兩張變化圖你看不懂「找邊境」在說什麼,需要看它實際動起來C2 的回放;「邊境」改用「霧」的比喻
22新增 C4「用真實數字算一遍」:用程式把最終版在公開地圖 01 第 0 步、第 12 步的每個中間數字印出來(BFS 每一輪、分地盤、寶藏分逐組相加、划算度),再算出 A、B、C 在同一個狀況會選哪個你要知道寶藏分、四種走法、BFS 到底怎麼算C4(原本的 C4、C5 改成 C5、C6)
23再派一個獨立審查員,專門找「模糊、說不清楚」的句子。它找到 57 處,其中 11 處跟程式實際行為不一致(例如 DFS 其實有記憶、BFS 是從現在位置出發不是從起點)。我先驗證它說的數字,再逐條改你說很多敘述太模糊57 處全部改完
24回放加上「每一步的解說」:讓四種走法各自錄下每一步做決定時用的資訊和計算(記錄版跟原版逐步比對,走的路完全一樣),回放時每個機器人底下顯示「這一步為什麼這樣走」,候選格子在地圖上標 ①②③;另外加「只看一個走法」放大你說看數學公式對不到實際走的路,要一步一個原因C2 回放的解說框

哪些是我做的、哪些要你做

事情誰
寫程式、實驗、驗證、打包、寫文件、找審查員我(Claude)
把 zip 改成你的學號姓名、上傳到 NTU COOL你
讀懂這份文件,用自己的話講一遍 C3 的七個步驟你(你選的 B 方案)
要注意
課程沒有寫作業能不能用 AI 協助(只寫了期末考不能用 AI)。如果 NTU COOL 上有寫禁止,請告訴我。

A1 作業在問什麼

結論:寫一個「蒙著眼睛走迷宮」的小機器人程式。它每一步只能摸到上下左右四格,要靠自己的記憶找到終點,而且路走得越少,分數越高。你只要寫 agent.py 這一個檔案。

A1.1 用故事講一次

想像你被蒙住眼睛,丟進一個格子迷宮。

你只知道兩件事:迷宮有多大(例如 10 列 × 10 欄),還有你站在哪一格。

你不知道牆在哪,也不知道終點在哪。

每次要走之前,你可以伸手摸一摸上、下、左、右四格。每一格只會是三種東西之一:

摸到的意思能不能走進去
FREE一般的路可以
WALL牆壁,或是地圖邊界外面。老師不告訴你是哪一種,兩種都叫 WALL不行。硬走會「撞牆」:原地不動,但還是算用掉一步,而且每撞一次扣 1 分(每張地圖最多扣 10 分)
GOAL終點可以,走進去就過關

你決定往哪走、走一步。再摸一次、再決定、再走一步。一直重複,直到走進終點。

斜對角的格子摸不到,兩格以外的也摸不到。終點只有在你隔壁時,你才會知道它在那裡。

A1.2 地圖長什麼樣

這是老師給的第 1 張公開地圖(maps/public_map_01.json),10×10。起點標成 S、終點標成 G:

          col →  0 1 2 3 4 5 6 7 8 9
row ↓  0         . . . . . . . . . G
       1         . . # . # . . . . .
       2         . . . . . . . . . .
       3         . . . . # . . . # .
       4         # . . # . # . . . .
       5         . . . . . . . . # #
       6         # . . . . . . . . .
       7         S . . . . . . . . .
       8         . # . . . # . . # .
       9         . . . . . . # . . #

. 是路,# 是牆。機器人看不到這張圖。這張圖只有老師的評分程式看得到。

座標怎麼讀

每一格用 (row, col) 表示,也就是(第幾列, 第幾欄)。

方向座標怎麼變例子:從 (7, 3) 出發
UP 上row − 1到 (6, 3)
DOWN 下row + 1到 (8, 3)
LEFT 左col − 1到 (7, 2)
RIGHT 右col + 1到 (7, 4)
很多人這裡會搞反
往「上」走,row 是變小,不是變大。因為 row 是從上往下數的,越上面數字越小。這跟數學課的 y 軸方向剛好相反。

A1.3 機器人一開始知道什麼、不知道什麼

資訊知道嗎怎麼拿到的
地圖大小(幾列幾欄)知道新地圖開始時,reset() 會告訴你
起點位置知道同上
自己現在在哪知道每一步的感知裡都有 position
上下左右四格是什麼知道每一步的感知裡都有 neighbors
其他格子是路還是牆不知道要自己走過去摸,然後自己記住
終點在哪不知道只有走到終點隔壁時才會摸到 GOAL
最短路線要幾步不知道只有評分程式知道,拿來算你的分數

老師另外保證了幾件事,可以放心:

A1.4 一步裡面發生什麼事

每一步都是同樣的三拍,一直重複:

拍誰做做什麼
1. 感知評分程式告訴機器人:「你現在在 (7, 0),上面是牆、下面是路、左邊是牆、右邊是路。」
2. 決定你寫的程式想一想,回答一個方向,例如 "RIGHT"。
3. 執行評分程式真的往右移一格,步數 +1。如果那邊是牆,就原地不動、撞牆次數 +1。

第 1 拍收到的資料,在 Python 裡長這樣(這包資料叫 percept,感知):

{
    "position": (7, 0),          # 你現在站在第 7 列、第 0 欄
    "neighbors": {
        "UP": "WALL",
        "DOWN": "FREE",
        "LEFT": "WALL",
        "RIGHT": "FREE",
    },
}

四個方向每次都會給,值只會是 "FREE"、"WALL"、"GOAL" 三種之一。

A1.5 實際看一次:前三步

下面是我們的最終版機器人,在第 1 張公開地圖上真正走的前三步(從程式實際執行的紀錄抄下來):

第幾步站在摸到:上/下/左/右回答結果
1(7, 0)WALL / FREE / WALL / FREERIGHT移到 (7, 1)
2(7, 1)FREE / WALL / FREE / FREERIGHT移到 (7, 2)
3(7, 2)FREE / FREE / FREE / FREERIGHT移到 (7, 3)

對照上面的地圖看:

A1.6 整張地圖走完的樣子

同一張地圖,機器人走過的格子標成 *:

. . . . . . . * * G
. . # . # . . * . .
. . . . . . . * . .
. . . . # . . * # .
# . . # . # . * . .
. . . . . . . * # #
# . . . . . . * * *
S * * * * * * * * *
. # . . . # . . # .
. . . . . . # . . #

它沿第 7 列往右走到 (7, 9),往上一格到 (6, 9)。再上面 (5, 9) 是牆,所以往左兩格到 (6, 7),沿第 7 欄一路往上到 (0, 7),最後往右兩格走進 G (0, 9)。

項目數字
實際走了20 步
撞牆0 次
最短路線(老師用完整地圖算的)16 步
這張的分數80 + 20 × 16/20 − 0 = 96 分

多走的 4 步,就是「因為看不到地圖,必須付出的探索成本」。

A1.7 每一種動作的結果

你回答的那一格是會發生什麼
一個方向FREE移過去,步數 +1
一個方向FREE,但之前走過移過去,步數 +1。不扣分、不算撞牆,只是多用一步
一個方向WALL(牆或邊界)原地不動,步數 +1,撞牆 +1
一個方向GOAL移過去,過關,這張地圖結束
None、"up"(小寫)、"WAIT" 等等—這張地圖 0 分。只能是 "UP"、"DOWN"、"LEFT"、"RIGHT" 四個大寫字串

A1.8 步數上限

每張地圖最多可以用「4 × 列數 × 欄數」步。撞牆也算在裡面。

地圖大小步數上限
10×10400
15×15900
20×201,600

步數用完還沒到終點,那張就 0 分。上限其實很寬:等於每一格可以走 4 次。加了「安全閥」(C3)之後,所有測試裡最多只用掉上限的約 30%,包含審查員專門造來刁難程式的地圖。不過這是實測結果,理論上沒辦法保證一定不會超過(原因見 C6)。

A1.9 老師給的檔案,各自在做什麼

把整個作業想成一場考試:

檔案比喻實際在做什麼你要動它嗎
agent.py考生機器人的大腦。每一步決定往哪走要,唯一要寫、唯一要交的
environment.py迷宮本身+裁判讀地圖、告訴機器人四周是什麼、執行移動、數步數和撞牆、判斷有沒有到終點不能改
public_grader.py模擬考的考官把 3 張公開地圖一張一張拿給你的機器人走,最後印出成績表不能改,只用來跑
maps/ 資料夾模擬考考卷3 張公開地圖(10×10、15×15、20×20 各一張),JSON 格式不能改
README.md考試規則完整規格、評分公式、繳交格式讀就好
map_example.png規則的插圖README 裡的示意圖不用管

正式評分時,老師會換成 9 張你沒看過的隱藏地圖(README 只說三種尺寸各三張;「三種風格各一張」是我從公開地圖推測的)。公開的 3 張只是讓你練習,不算成績。

A1.10 評分程式怎麼使用你的程式

這是 public_grader.py 的核心流程,我改寫成白話:

agent = Agent()                          # 照你的設計圖,造出「一台」機器人

for 每一張地圖:
    env = GridEnvironment(這張地圖)       # 準備好迷宮和裁判
    agent.reset(列數, 欄數, 起點)          # 告訴機器人:新地圖開始了,忘掉上一張

    while 還沒走到終點 and 步數還沒用完:
        percept = env.observe()          # 第 1 拍:裁判告訴機器人四周是什麼
        action = agent.act(percept)      # 第 2 拍:機器人回答往哪走
        env.step(action)                 # 第 3 拍:裁判真的執行這一步

注意兩件事:

  1. 機器人只造一次,三張地圖都是同一台在走。所以每張新地圖開始時,reset() 一定要把舊的記憶清乾淨。不然它會拿第 1 張地圖的牆和路,來規劃第 2 張地圖的路線:可能以為某處是路結果撞牆,或以為某些地方已經探索過而跳過。
  2. 你的程式不用自己讀地圖、不用自己移動、不用自己算步數。那些都是裁判的事。你只負責「回答往哪走」。

A1.11 Python 小補充:為什麼記憶要放在 self 裡

先講它在做什麼:讓機器人能記住上一步發生過的事。

為什麼一定要放在 self 上?

因為 act() 每一步都會被重新呼叫一次。在 act() 裡面寫的普通變數(例如 known = {}),這一步結束就消失了,下一步什麼都不記得。

放在 self 上(self.known = {})就不一樣:它掛在機器人身上,會一直留到下一步、下下一步。

README 也特別提醒:「請使用 instance variables(例如 self.internal_map、self.visited、self.path)保存資訊」。instance variable 就是「掛在 self 上的變數」。

A1.12 你要寫的三個方法

名稱什麼時候被呼叫要做什麼回傳什麼
__init__()機器人剛造出來時,只有一次把要用的變數先建好。我們的程式在這裡直接呼叫一次 reset(1, 1, (0, 0))不用回傳
reset(rows, cols, start_position)每張新地圖開始前,一次記下新地圖的大小和起點;把上一張的記憶全部清掉不用回傳(回傳了也沒人看)
act(percept)每走一步前,一次看感知、更新記憶、決定方向一定要回傳四個方向字串之一

老師給的空白範本,act() 裡只寫了 return "UP",也就是永遠往上走。我實際跑過:3 張地圖全部失敗,每一步都撞牆(10×10 那張 400 步撞了 400 次)。因為起點上面剛好是牆,它就一直撞同一面牆。你的工作就是把這一行換成聰明的決策。

A1.13 三種地圖風格

公開地圖的 JSON 檔裡有一個 style 欄位,標明風格。隱藏地圖推測也是這三種(README 說三種尺寸各三張,但沒說風格;這是我從公開地圖推測的):

風格白話公開地圖難在哪
open 空曠型大片空地,零星散落幾面牆,沒有外框public_map_01(10×10)到處都能走,方向選錯就會繞很遠
dead_ends 死路型標準迷宮,走道寬一格,很多死路,只有一條正確路線public_map_02(15×15)走進死路要整段退回來
loops_dense 迴圈型迷宮,但很多牆被打通,路會繞成圈public_map_03(20×20)同一個地方有好幾條路可以到,容易繞圈子
看另外兩張公開地圖長什麼樣

public_map_02(死路型,15×15):起點 S 在 (3, 9),終點 G 在 (10, 1),最短 45 步

# # # # # # # # # # # # # # #
# . . . # . . . . . . . . . #
# . # . # # # . # # # . # . #
# . # . . . # . # S . . # . #
# . # # # # # . # . # # # # #
# . . . # . . . # . . . . . #
# # # . # # # # # # # # # . #
# . # . . . . . . . . . . . #
# . # # # # # # # # # # # . #
# . # . . . . . . . . . # . #
# G # . # # # # # # # . # . #
# . # . # . # . . . . . # . #
# . # . # . # . # # # # # . #
# . . . . . # . . . . . . . #
# # # # # # # # # # # # # # #

public_map_03(迴圈型,20×20):起點 S 在 (3, 18),終點 G 在 (11, 2),最短 26 步

# # # # # # # # # # # # # # # # # # # #
# . . . . . . . . . . . # . . . . . . .
# . # . # . # . # # # . # # . # # . # #
# . . . . . . . # . . . . . . . # . S .
# . # # # # # . # . # # # . # . # . . .
# . # . . . . . # . . . . . # . . . . .
# . # . # . # . # . # # # . # . # . # .
# . # . . . . . . . # . . . . . # . . .
# . # . . . # . # . . . # # # # # . # .
# . . . # . # . . . # . . . . . . . # .
# # # . # . # . . . # . # # . . # . # .
# . G . # . # . . . # . # . . . # . . .
# # # . # . # . # . # # # . # . # . . .
# . . . # . . . # . . . . . # . . . # .
# . # . # # # . # # # . # . # # . . # .
# . # . . . . . # . # . . . # . . . . .
# . # # . # # # # . # . # . # . # . . #
# . # . . . . . . . # . . . # . # . . .
# . # # # . . . . # # . # . . . # . # .
# . . . # . # . . . . . # . . . . . # .

A1.14 其他規定(有些違反會 0 分)

規定違反會怎樣(README 原文的根據)我們的程式
只能改、只能交 agent.py繳交格式錯誤、導致無法開始評分 → 整份 0 分(README 第 7 節)只改了 agent.py
只能用 Python 內建的標準函式庫用了未允許的套件、導致無法載入 → 整份 0 分(README 第 7 節)只用了 collections.deque(內建)
正式環境是 Linux+Python 3.11版本不相容、導致無法載入 → 整份 0 分(README 第 7 節)已用你電腦上的 Python 3.11 測過
act() 只能回傳四個大寫方向字串回傳其他值 → 那一張 0 分(README 第 5 節)連出錯時的備案也只回傳合法方向
不能偷看地圖檔、不能要求輸入README 明文禁止(README 第 7 節),但沒寫罰則;input() 會讓評分程式卡住等人打字沒有 open()、沒有 input()
不能跑太久README 只說「需在指定時間內完成」,沒寫幾秒、也沒寫罰則公開地圖每張約 0.05~0.1 秒;審查員刻意做的最難 20×20 地圖,最慢一張 3.3 秒

A1.15 自己跑跑看

想親眼看結果,照這樣做:

  1. 打開終端機(VS Code 下方的 Terminal 就可以)。
  2. 切到作業資料夾:
    cd "C:\D槽\TAICA課程\人工智慧導論\HW2\HW2"
  3. 跑模擬考:
    py -3.11 public_grader.py
  4. 想看每一步怎麼走:
    py -3.11 public_grader.py --verbose

README 寫的指令是 python3 public_grader.py,那是 Linux 的寫法。在你的 Windows 上用 py -3.11,意思是「指定用 Python 3.11 來跑」,跟正式評分環境同版本。

你應該會看到:

Map                    | Result     | Steps      | Collisions
-----------------------+------------+------------+-------------
public_map_01          | SUCCESS    | 20         | 0
public_map_02          | SUCCESS    | 45         | 0
public_map_03          | SUCCESS    | 70         | 0

Successful maps : 3/3
欄位意思
SUCCESS在步數上限內走到終點
FAILED步數用完還沒到
ERROR程式出錯了,下面會印出錯誤訊息
Steps總步數(包含撞牆)
Collisions撞牆次數

注意:這個成績表不會印分數。分數是我用老師的公式另外算的(C2 節)。

所以對你的影響是:這份作業的重點不是「寫很多程式」,而是「想出一個聰明的走法」。程式本身大約 230 行(含註解),規則都在這一節了。接下來 A2 節講分數怎麼算,然後 B 部分講背後的邏輯框架。

A2 分數怎麼算

結論:每張地圖= 80 分基本分 + 最多 20 分效率分 − 撞牆扣分。拿高分的三個關鍵:一定要走到、永遠不撞牆、少走冤枉路。

老師的公式

沒走到終點(步數用完):  這張 0 分

走到終點:
    實際移動步數 = 總步數 − 撞牆次數
    效率 = min(1, 最短路線步數 / 實際移動步數)
    撞牆扣分 = min(10, 撞牆次數)
    這張分數 = 80 + 20 × 效率 − 撞牆扣分

作業成績 = 9 張隱藏地圖的平均

「最短路線步數」是老師用完整地圖算出來的。你的 Agent 看不到這個數字。

舉例

假設某張地圖最短要走 20 步:

你的表現算式分數
走 20 步,沒撞牆80 + 20 × 20/20100
走 40 步,沒撞牆80 + 20 × 20/4090
總共 43 步,其中撞牆 3 次(實際移動 40 步)80 + 20 × 20/40 − 387
步數用光還沒到—0

三個關鍵,一個一個看

關鍵值多少分難不難我們怎麼做
1. 一定要走到80 分(沒走到就 0)不難步數上限很寬(每格 4 步:10×10 給 400 步)。只要不在原地打轉就有機會走到。我們的程式在 5,400 張壓力測試+60 張刁鑽地圖上 0 失敗。加了「安全閥」(C3)之後,所有測試裡最多只用掉上限的約 30%,包含審查員專門造來刁難程式的地圖。
2. 永遠不撞牆最多 10 分白送下一步一定是走到隔壁四格之一,而這四格每一步都剛摸過。程式只會走「已知是路」的格子,或直接走進終點,所以撞牆次數永遠是 0。
3. 少走冤枉路最多 20 分難因為一開始不知道終點在哪,一定會走一些冤枉路去「找」。這是唯一要動腦的地方,C2、C3 節在講這個。
重點
一張失敗(0 分)會讓 9 張的平均掉 10 分以上。而聰明的走法能多賺多少?在模擬考上,最終版比 DFS 平均多 3.56 分(終點偏遠時)或 0.64 分(終點隨機時)。 所以設計時的優先順序是:先保證絕對不會失敗,再去追求效率。這個原則在C3 節「承諾」那段會再出現一次。

所以對你的影響是:只要程式正確、不失敗,每張至少 80 分(模擬考裡最低是 80.15)。分數高低差在「探索策略」:在老師的 3 張公開地圖上,最終版平均比 DFS 多 5.47 分,單張最多多 11.15 分(公開地圖 01:96 對 84.85)。我們的最終版預估約 91 分。

B1 Agent 是什麼

結論:Agent 就是「會看、會想、會動」的東西。課本把任何 AI 都看成一個 Agent:從環境收到感知,決定一個動作,再把動作做回環境裡。這份作業的機器人就是一個標準的 Agent。

B1.1 一個 Agent 有三個部分

想像一台掃地機器人:

課本 Ch2 第 2 頁的定義就是這三件事:Agent 透過 sensors(感應器)感知環境,透過 actuators(執行器,例如輪子、手臂)對環境做動作。

部分課本的詞在這份作業裡
感應器sensors → 收到 percept(感知)摸上下左右四格,加上知道自己在哪
大腦agent program(Agent 程式)你的 agent.py
執行器actuators → 做出 action(動作)回傳 "UP"/"DOWN"/"LEFT"/"RIGHT",由評分程式真的去移動

B1.2 Agent function:從「看過的東西」對應到「動作」

課本 Ch2 第 3 頁說:Agent 的行為,可以用一個 agent function(Agent 函數)描述。它把「到目前為止收到的所有感知」(叫 percept sequence,感知序列)對應到一個動作。

重點在「到目前為止所有的」。好的 Agent 不是只看眼前這一刻,而是參考它看過的全部東西。

所以對你的作業的影響是:機器人應該把每一步摸到的東西都記下來(這就是 self.known 筆記本),用「全部記憶」來做決定,而不是只看眼前四格。

B1.3 理性的 Agent(rational agent)

課本 Ch2 第 5~7 頁:理性的 Agent,就是在它所知道的範圍內,選擇「預期會讓表現分數最高」的動作。

注意三件事:

  1. 表現分數(performance measure)是外面的人定的。這份作業裡,就是老師的評分公式(A2)。
  2. 理性不等於全知。機器人看不到整張地圖,所以它不可能每次都走最短路。理性的意思是:用手上有的資訊,做出最好的猜測。
  3. 所以「多走了一些冤枉路」不代表它不理性。只要它在當時的資訊下選了最划算的路,就是理性的。

B1.4 PEAS:描述一個任務的四個面向

課本 Ch2 第 9 頁:設計 Agent 之前,先用 PEAS 把任務講清楚。PEAS 是四個英文字的開頭:

字母英文白話這份作業
PPerformance measure怎麼打分數走到終點有 80 分;走越少步越高分(最多再加 20);撞牆一次扣 1 分(最多扣 10)
EEnvironment在什麼環境裡10×10、15×15 或 20×20 的格子迷宮,有牆、有一個終點
AActuators能做什麼動作往上、下、左、右移動一格
SSensors能感知什麼自己的座標;上下左右四格是 FREE、WALL 還是 GOAL
考試可以這樣用
如果考題問「請寫出 HW2 的 PEAS」,直接寫上面這張表就對了。這是課本最基本的題型。

B2 這是什麼樣的環境

結論:課本用六、七組「二選一」來分類環境。這份作業的環境是:部分可觀察、單一 Agent、確定性、序列式(sequential)、靜態、離散;known/unknown 那一項有爭議,考試建議答 unknown(見下面的提醒)。其中「部分可觀察」最重要,因為它決定了機器人一定要有記憶。

B2.1 為什麼要分類環境

不同的環境,要用不同的 Agent。就像下象棋和開車,需要的能力完全不同。

先搞清楚環境是什麼樣子,才知道 Agent 該具備哪些能力。課本 Ch2 第 11~14 頁列了這些分類。

B2.2 逐項看

分類白話這份作業是為什麼
Fully vs. partially observable
(完全/部分可觀察)
感應器能不能一次看到「做決定需要的全部資訊」Partially observable只摸得到四格,看不到整張地圖,也看不到終點
Single vs. multiagent
(單一/多個 Agent)
環境裡有沒有其他會影響你分數的 AgentSingle agent迷宮裡只有你一個
Deterministic vs. stochastic
(確定性/隨機性)
同一個狀態做同一個動作,結果是不是永遠一樣Deterministic往右就一定往右一格(或撞牆不動),沒有「有時候會滑到別格」
Episodic vs. sequential
(獨立回合/序列式)
現在的決定會不會影響之後Sequential這一步走哪,決定下一步站在哪、能看到什麼
Static vs. dynamic
(靜態/動態)
你在想的時候,環境會不會自己變Static牆和終點不會移動
Discrete vs. continuous
(離散/連續)
狀態和動作是不是一格一格、數得出來的Discrete格子座標、四個方向
Known vs. unknown
(規則已知/未知)
Agent(或設計者)知不知道這個世界的「物理規則」有爭議,考試建議答 UnknownCh4 把「迷宮地圖事先不知道」的情況叫 unknown environment。但移動規則本身是已知的,所以也有人會判 known。見下面的提醒
known/unknown 這一項:老師最可能要哪個答案
建議答:unknown,再補一句「移動規則本身已知,不知道的是地圖和每個動作的結果」。理由:
1. Ch4 第 29~35 頁的標題就叫 "Online Searching Agents with Unknown Environments",第 31 頁拿迷宮當例子:"the agent does not know that going Up from (1,1) leads to (1,2)"。這份作業就是這個情況。
2. Ch2 第 14 頁的定義是「對環境『物理規則』的了解程度」。照這個定義,也可以主張「規則已知、只是看不到地圖」=known+partially observable。所以這題有爭議。
3. 老師這門課的 Ch4 投影片明確把這種迷宮歸成 unknown,考試照老師投影片的用法答最安全。
這是我和審查員的判斷,老師沒有針對這份作業講過。

B2.3 「部分可觀察」帶來的後果

因為看不到全部,Agent 必須自己記住看過的東西,拼出一張「自己知道的地圖」。

這直接決定了下一節要講的:這份作業需要哪一種 Agent。

B3 四種 Agent,這份作業要哪一種

結論:課本由簡單到複雜介紹四種 Agent。只看眼前的「反射型」在迷宮裡會繞圈子;這份作業需要的是「有記憶(model-based)+有目標(goal-based)+會算划算度(utility-based)」的 Agent。我們的程式三樣都有。

B3.1 反射型 Agent(simple reflex agent):只看眼前

課本 Ch2 第 18~19 頁:只根據現在這一刻的感知做決定,完全不管以前看過什麼。規則像「如果怎樣,就做什麼」(condition-action rule)。

例子:「右邊是路就往右;不然下面是路就往下⋯⋯」。

課本說它只在環境「完全可觀察」時才行得通。這份作業是部分可觀察,所以反射型會出問題:

B3.2 有記憶的 Agent(model-based reflex agent)

課本 Ch2 第 20~21 頁:為了處理「看不到全部」,Agent 要維護一個內部狀態(internal state),記住現在看不到的那部分世界。這個內部狀態根據「過去所有的感知」來更新。

在我們的程式裡,內部狀態就是 self.known:每一格摸過的結果都寫在裡面。

B3.3 有目標的 Agent(goal-based agent)

課本 Ch2 第 22~23 頁:光知道世界長怎樣還不夠,還要知道想去哪裡。有目標的 Agent 會考慮「如果我這樣做,會發生什麼事?會不會更接近目標?」

我們的目標是「走進 GOAL」。程式會規劃路線(用 BFS),而不是只看下一步。注意:因為終點在哪裡不知道,BFS 規劃的是走到「選中的邊境」的路線,不是走到終點的路線。

B3.4 效用導向的 Agent(utility-based agent)

課本 Ch2 第 24~25 頁:當有好幾個可以追求的目標,或者結果不確定時,光有目標不夠。Agent 要對每個選擇打一個分數(叫 utility,效用),選預期效用最高的。課本特別說:效用導向的 Agent 能處理「部分可觀察」帶來的不確定性。

這正是我們的情況:

B3.5 整理

Agent 種類核心能力課本頁數我們的程式
Simple reflex只看當下Ch2 p18–19不是(在迷宮會繞圈)
Model-based有記憶(內部狀態)Ch2 p20–21有:self.known
Goal-based有目標、會規劃Ch2 p22–23有:目標是 GOAL,用 BFS 規劃路線
Utility-based會對選擇打分數Ch2 p24–25有:划算度

課本的四種 Agent 是一層一層往上加能力的,所以「是 utility-based」也同時代表它有記憶、有目標。

B4 把問題寫成「搜尋問題」

結論:課本 Ch3 說,任何「從起點走到目標」的問題,都可以用五個要素描述。把迷宮套進這五個要素,就可以用搜尋演算法(BFS、DFS、A*)來處理。

B4.1 五個要素

課本 Ch3 第 5 頁用「從 Arad 開車到 Bucharest」當例子。我們把迷宮套進去:

要素白話開車例子(課本)迷宮(這份作業)
Initial state
初始狀態
從哪裡開始在 Arad站在起點,例如 (7, 0)
Actions
可以做的動作
在某個狀態下能做什麼開往 Sibiu、Timisoara、Zerind往上、下、左、右
Transition model
轉移模型
做了某個動作會變成什麼狀態從 Arad 開往 Zerind → 到 Zerind從 (7, 0) 往右 → 到 (7, 1);如果是牆 → 留在原地
Goal test
目標測試
怎麼判斷到了是不是在 Bucharest是不是站在 GOAL 格
Path cost
路徑成本
一條路花多少代價開了幾公里走了幾步(每步成本 1)

課本還說:解(solution)就是一串從初始狀態走到目標的動作;解的好壞用路徑成本衡量。成本最低的解叫最佳解(optimal solution)。

這份作業的「最短路線步數」(評分公式裡的 optimal_steps)就是最佳解的成本。

B4.2 狀態空間:把迷宮看成一張「圖」

把每一個可以站的格子想成一個點,相鄰、可以走過去的兩格之間連一條線。這樣迷宮就變成一張由點和線組成的圖。所有狀態加起來叫狀態空間(state space)。

課本 Ch3 第 9 頁說:搜尋演算法從初始狀態出發,一步一步展開可能的動作,形成一棵搜尋樹(search tree)。樹根是初始狀態,樹枝是動作,每個節點是一個狀態。

B4.3 評估搜尋演算法的四個標準

課本 Ch3 第 11 頁:

標準問的問題
Completeness(完備性)只要有解,保證找得到嗎?
Optimality(最佳性)找到的是最好的解嗎?
Time complexity(時間複雜度)要花多久?
Space complexity(空間複雜度)要用多少記憶體?
這份作業跟 Ch3 最大的不同
Ch3 的搜尋假設你事先知道整張地圖(轉移模型是已知的),所以可以先在腦子裡算好整條路,再出發。
這份作業看不到地圖:你不實際走到那格旁邊,就不知道往那邊走會不會撞牆。所以不能直接套 Ch3 的做法。這就是 B6 要講的「線上搜尋」。

B5 三種基本搜尋:BFS、DFS、A*

結論:三種方法的差別只在「下一個先展開誰」。BFS 先展開近的,保證找到最短路;DFS 先往深處鑽,省記憶體但不保證最短;A* 用「估計離終點多遠」來挑,又快又能找到最短,但前提是知道終點在哪。

B5.1 共同的骨架:frontier(待展開清單)

三種方法都用同一個骨架:

  1. 手上有一份「等著被展開的節點」的清單。課本叫它 frontier。一開始只有起點。
  2. 從清單裡挑一個節點拿出來。
  3. 是目標就結束;不是的話,把它的鄰居(還沒看過的)加進清單。
  4. 重複。

課本 Ch3 第 12 頁說:所有搜尋策略的差別,就在於節點被展開的順序。

名字一樣、意思不同
Ch3 的 frontier=搜尋樹裡「等著被展開的節點清單」。
這份作業(和 C1 節)說的 frontier(邊境)=地圖上「已知是路、旁邊還有未知格」的格子。
兩個都是「下一個要去探索的地方」,概念相近,但不是同一個東西。考試要分清楚。

B5.2 BFS(廣度優先搜尋,Breadth-first search)

課本 Ch3 第 13~15 頁:先展開起點,再展開起點的所有鄰居,再展開鄰居的鄰居⋯⋯一層一層往外。

怎麼做到「一層一層」:清單用排隊的方式(先進先出,FIFO)。先加進去的先拿出來,所以近的一定先被處理。

小例子:3×3 的空地,從左上角 A 出發。

A B C
D E F
G H I
輪拿出來加進排隊排隊裡現在有
1A(距離 0)B、D(距離 1)B D
2BC、E(距離 2)D C E
3DG(距離 2;E 已經加過了)C E G
4CF(距離 3)E G F
⋯⋯⋯⋯

距離 1 的全部處理完,才輪到距離 2,再輪到距離 3。所以每一格第一次被加進排隊時的距離,就是最短距離。

性質BFS
保證找得到嗎(完備)是(分支數有限時)
保證最短嗎(最佳)是(每步成本一樣時)
缺點要記住很多節點,比較耗記憶體

所以對你的作業的影響是:我們的程式每一步都用 BFS,在「已經知道的地圖」上算出走到每一格的最短步數。

B5.3 DFS(深度優先搜尋,Depth-first search)

課本 Ch3 第 18~20 頁:永遠先展開最深的節點。一條路走到底,走不通才退回上一個岔路換一條。

怎麼做到「先往深處」:清單用疊盤子的方式(後進先出,LIFO)。最後加進去的先拿出來。

性質DFS
保證找得到嗎(完備)看情況:狀態有限、而且會記住走過哪裡(graph search)時是完備的;不記走過哪裡(tree search)可能在迴圈裡繞不出來;狀態無限時不完備
保證最短嗎不保證(可能繞一大圈才到)
優點記憶體用得少;而且除了退回的時候,下一個展開的都是現在這格的鄰居(這一點在 B6 很重要)

B5.4 A*(A-star):有方向感的搜尋

BFS 和 DFS 都是「盲目」的:它們不知道目標大概在哪個方向(課本叫 uninformed search,無資訊搜尋)。

如果有一點線索,例如「目標大概在東北邊」,就可以優先往那邊找。這種線索叫 heuristic(啟發式函數),寫成 h(n):估計從節點 n 到目標還要花多少(Ch3 第 27 頁)。

A* 每次挑這個值最小的節點展開(Ch3 第 31 頁):

f(n) = g(n) + h(n)
g(n) = 從起點走到 n 已經花了多少
h(n) = 估計從 n 到目標還要多少
f(n) = 估計「經過 n 的整條路」總共多少

在格子地圖上,最常用的 h(n) 是曼哈頓距離:上下差幾格+左右差幾格。

A* 什麼時候保證找到最短路(Ch3 第 32~33 頁):

曼哈頓距離在格子地圖上兩個條件都滿足。

為什麼這份作業不能直接用 A*
要算 h(n),必須知道目標在哪。這份作業的終點一開始未知,只有走到它旁邊才會發現。所以 A* 不能拿來「找終點」。
不過我們借用了 A* 的精神:用一個估計值來決定優先順序。只是我們估計的不是「離終點多遠」,而是「終點可能在哪裡」(C3)。

B5.5 三種方法比較

BFSDFSA*
下一個展開誰最淺的(最近的)最深的f(n) 最小的
清單的資料結構排隊(FIFO queue)疊盤子(LIFO stack)優先佇列(依 f 排序)
保證最短是否是(tree search 要 h 可容許;graph search 要 h 一致)
需要知道目標在哪否否是
在這份作業裡的角色在已知地圖上算最短路線對照組(C2 的走法 A)不直接用;借用它「用估計值排優先」的想法

B6 線上搜尋:看不到地圖時怎麼辦

結論:這份作業就是課本 Ch4 的「線上搜尋」(online search):走一步、看一下、想一下、再走一步。評分公式就是投影片講的 competitive ratio(競爭比)。我們的做法是「記住地圖+用 BFS 走到最值得探索的邊境」。

B6.1 離線 vs. 線上

課本 Ch4 第 29 頁(投影片角落編號 56):

離線搜尋(offline)線上搜尋(online)
做法出發前先在腦子裡把整條路算好,再照著走計算和行動交錯:先做一個動作,看看結果,再算下一步
需要事先知道整個環境不需要,邊走邊認識環境
例子Ch3 的開車找路(地圖都在手上)課本的例子:機器人被放進一棟陌生的建築,要邊探索邊畫地圖
這份作業做不到(看不到地圖)就是這個

課本 Ch4 第 30~31 頁還說:線上搜尋的 Agent,不實際走到某個狀態、做某個動作,就不知道結果。這份作業也一樣:不走到那一格旁邊,就不知道那格是路還是牆。

B6.2 競爭比(competitive ratio):怎麼評價線上 Agent

課本 Ch4 第 33 頁(角落編號 60):

競爭比 = Agent 實際走的路徑成本 ÷ 如果事先知道地圖時的最短路徑成本

越小越好,最好是 1(等於直接走最短路)。

回頭看這份作業的評分公式(A2):

效率 = 最短路線步數 ÷ 實際移動步數

在 0 次撞牆時,剛好是競爭比倒過來。競爭比 2(走了最短的兩倍)→ 效率 0.5 → 效率分 10 分。

兩個細節:

所以老師評分裡的效率分那一項,就是課本評價線上搜尋 Agent 的方式。80 分基本分、失敗 0 分、撞牆扣分,則是老師另外加的規則。

B6.3 課本的提示:線上搜尋要「就近展開」

課本 Ch4 第 34 頁(角落編號 61):

這就是為什麼課本的經典線上搜尋 Agent 是 DFS 型的。

B6.4 我們比課本的 DFS 更進一步

DFS 型的線上 Agent 有一個缺點:走到死路時,它要一格一格原路退回到上一個岔路。

我們的做法是:

  1. 記住整張已知地圖(不只記一條路)。
  2. 找出所有「站上去就能看到新東西」的格子,叫邊境(C1 會詳細講)。
  3. 用 BFS 在已知地圖上算最短路線,直接抄近路走到想去的邊境,不用原路退回。
  4. 有好幾個邊境時,用「划算度」挑最值得去的(C3)。

這種做法在機器人領域叫做以邊境為基礎的探索(frontier-based exploration)。課本投影片沒有用這個名字,是機器人探索的常見做法。

BFS 在這裡的角色變了:它不是用來「展開搜尋樹找終點」,而是用來「在已經知道的地圖上算走路路線」。這兩件事不一樣,考試時要講清楚。

B6.5 從框架到實作:剩下唯一要決定的事

到這裡,框架都有了:

需要的能力從哪裡來實作
記住看過的東西Model-based agent(B3)self.known
找出還能去探索的地方線上搜尋(B6)邊境
算出怎麼走過去最快BFS(B5)_bfs_from()
永遠不撞牆只走已知是路的格子BFS 只走 FREE

剩下唯一要動腦的問題:有好幾個邊境時,先去哪一個?

這個選擇決定了冤枉路的多寡,也就決定了效率分。C 部分就是在回答這個問題:先比較四種選法(C2),再講最終版怎麼選(C3)。

C1 三個基本工具

結論:所有走法都靠三個工具:記憶地圖(記住摸過什麼)、邊境(哪裡還有東西沒看過)、BFS(在已知的路上找最短路線)。

工具 1:記憶地圖

Agent 每摸一次,就把結果寫進一本筆記本。程式裡這本筆記本叫 self.known,是一個 Python 的 dict(字典:用「座標」查「狀態」)。

self.known[(4, 7)] = "FREE"   # 我站過的地方,一定是路
self.known[(5, 7)] = "WALL"   # 摸到下面是牆
self.known[(4, 6)] = "FREE"   # 摸到左邊是路

筆記本裡沒有寫到的格子 = 未知。未知的格子可能是路、牆、或終點。

README 特別提醒:「沒有看到牆壁,不代表已確認該格可通行」。所以我們只在已知是 FREE 的格子上規劃路線,不會賭未知的格子能走。

工具 2:邊境(frontier)

先講它在做什麼:找出「站上去才能看到新東西」的格子。

想一想:你要怎樣才能看到一個未知的格子?答案是:走到它旁邊,摸一下。

所以有用的目的地是:已知是路、而且旁邊還有未知格子的格子。這種格子就叫邊境(frontier),意思是「已知世界和未知世界的交界」。

精確地說,一格要同時符合 4 個條件才是邊境:

  1. 已經知道它是一般的路(不是牆、不是霧,也不是終點)。
  2. 不是機器人現在站的那一格。
  3. 從機器人現在的位置,只走「已知的路」走得到。
  4. 它的上下左右,至少有一格在地圖裡面、而且還是未知的。

下面是一個例子。@ 是你,. 是已知的路,# 是已知的牆,? 是未知,F 是邊境:

情境:你從左邊那格 . 出發,往右走到最右邊那格 .,再走回中間的 @。走過的三格,每一格都摸過它的上下左右。

? ? ? ? ?
? F F F ?
# . @ . #
? F # F ?
? ? ? ? ?

每個 F 都是「已知是路」,而且旁邊都還有 ?,站上去就能看到新格子。

走過的三格(兩個 . 和 @)四周都已經摸過了,旁邊沒有 ?,所以它們都不是邊境。

終點只有在「旁邊」時才摸得到,而且一摸到,程式就直接走進去(C3 的步驟 2)。所以還在找的時候,終點一定還沒被摸到過,一定藏在某個 ? 裡。所以探索=不斷走到邊境,把 ? 變成已知,直到摸到 GOAL。

工具 3:BFS(廣度優先搜尋)

先講它在做什麼:從你現在的位置出發,算出走到每一格「最少要幾步」,以及「第一步該往哪走」。

做法像丟一顆石頭到水裡,漣漪一圈一圈往外擴:

  1. 第 0 圈:你自己,距離 0。
  2. 第 1 圈:你旁邊「已知是路」的格子(不含未知的格子),距離 1。
  3. 第 2 圈:第 1 圈旁邊、還沒算過的格子,距離 2。
  4. 一直擴下去,直到沒有新格子。

因為是一圈一圈擴,第一次碰到某格時的圈數,一定就是最短距離。這是 BFS 最重要的性質。Ch3 第 13~15 頁講 BFS 怎麼一層一層展開;「保證最短」是從這個展開順序推出來的,投影片文字沒有直接寫這句。

我們的 BFS 只在「已知是 FREE」的格子上擴散。它從機器人現在站的格子出發(不是地圖一開始的起點),順便記下「走到每一格,第一步要往哪個方向」。這樣選好目的地之後,馬上知道這一步要回傳什麼。

BFS 跟老師說的 DFS、A* 差在哪(詳細比較在 B5)
BFS 一圈一圈擴,保證找到最短路。DFS(深度優先)是一條路走到底再回頭,不保證最短。A* 是用 f(n) = g(n) + h(n) 排優先順序的搜尋(best-first search),h(n) 就是「方向感」:估計離終點還有多遠。所以它需要知道終點在哪。這份作業終點未知,所以 A* 不能直接拿來找終點;我們是用 BFS 在已知地圖上找路。

所以對你的影響是:這三個工具是每一種走法共用的骨架。不同的走法只差在「有好幾個邊境時,先去哪一個」。

C2 四種走法比一比

結論:我做了模擬考,比較四種走法。在公開地圖、以及「終點放遠處」的模擬地圖上,最終版最好,比最基本的 DFS 多 3.56 分(終點偏遠時);如果終點是隨便放的,最終版跟走法 C 打平(88.78 對 88.79),只比 DFS 多 0.64 分。

四種走法

走法每一步怎麼選(精確規則)缺點
A. DFS(深度優先)記得兩樣東西:自己站過哪些格子、一路走來的路線。每一步照「上、下、左、右」的順序,走進第一個「是路、而且自己還沒站過」的格子;四格裡沒有這種格子(死路),就退回「來這格之前站的那一格」(一次退一格)。課本 Ch4 線上搜尋講的經典做法。退回時只能沿原路一格一格退,不會「抄近路」直接走到別的岔路。
B. 最近邊境每一步重新挑:用 BFS 算走到每個邊境要幾步(d),選 d 最小的。平手時先選上一步的舊目標,沒有的話選最先被 BFS 找到的。只看近不近,不管方向。終點在遠處時,常常先繞完起點附近。例如公開地圖 01:B 一開始往下(遠離終點),32 步才到;D 20 步。
C. 偏好遠處跟 B 一樣每一步重新挑,但分數改成「d − 這個邊境離起點幾格」,選最小的。也就是邊境每離起點遠 1 格,就當作少走 1 步。平手規則同 B。只看「邊境那一格」離起點多遠,看不到它後面還有多大一片霧。
D. 最終版:寶藏划算度把霧分給各邊境(分地盤),每格霧的分數=(離起點幾格 ÷(列數+欄數))⁶,加起來=寶藏分;選「寶藏分 ÷(d+2)」最大的,選定後走到它不再是邊境才重選;走超過步數上限的 5% 後改選 d 最小的(安全閥)。詳見 C3、C4。計算比較多;優勢依賴「終點通常離起點很遠」這個從公開地圖看到的規律。

實際走一遍:四種走法在同一張地圖上的回放

結論:同一張地圖、同一個起點,四個機器人同時出發。按「播放」就能看到它們每一步怎麼走:誰一開始就走錯方向、誰一直走回頭路、誰直直走到終點。

這不是動畫示意,是四種走法的程式真的跑一遍記下來的每一步。

先看懂「邊境」:用霧來想
想像你在玩一個地圖被霧蓋住的遊戲。一開始整張地圖都是灰色的霧,只有你腳邊看得到。
你每走到一格,那一格的上下左右四格的霧就會散開。散開後才知道那裡是路還是牆。

邊境(黃色格子)=「霧的邊緣上、你走得到的路」。精確地說,一格要同時符合 4 個條件才是黃色格子:
1. 已經知道它是一般的路(不是牆、不是霧,也不是終點)。
2. 不是機器人現在站的那一格。
3. 從機器人現在的位置,只走「已知的路」走得到。
4. 它的上下左右,至少有一格在地圖裡面、而且還是霧。
只有走到黃色格子上,才能把更多霧吹散。
終點一定還藏在霧裡面。為什麼?因為四種走法都有同一條優先規則:只要旁邊摸到終點,就直接走進去。所以還在找的時候,終點一定還沒被摸到過,也就是還在霧裡。
所以要一直走到黃色格子去吹散霧,直到看見終點。

「走過去要幾步」一律用 BFS 算:只能走已知的路、要繞過牆,不是直線距離(BFS 怎麼算見 C1、C4)。

四種走法的差別,只有一件事:下一個要去哪一個黃色格子。下面每一種都講清楚四件事:看什麼、選哪個、平手怎麼辦、會不會中途改主意。

走法每一步怎麼決定平手怎麼辦會不會中途改主意
A. DFS
(沒有虛線框)
DFS 記得兩樣東西:自己站過哪些格子,以及一路走來的路線(像一疊盤子,最新走到的格子放最上面)。它不記牆,也不找黃色格子。每一步:
① 旁邊有終點 → 走進去。
② 照「上、下、左、右」的順序,找第一個「是路、而且自己還沒站過」的格子,走過去。
③ 四格裡沒有任何一格「是路、而且還沒站過」,就叫死路。就算旁邊有路,只要都站過了,也算死路。這時退回「來這格之前站的那一格」(拿掉最上面的盤子),一次退一格,一直退到某一格還有沒站過的路。
不會平手:固定照上、下、左、右的順序不適用(它沒有目標)
B. 最近邊境① 用 BFS 算出走到每個黃色格子要幾步。
② 選步數最少的那個,往它走一步。
先看:上一步選的目標如果也在平手名單裡,繼續選它。
否則:選最先被 BFS 找到的(BFS 先看「上一步走的方向」,再照上、下、左、右)。
會。每一步都重新算、重新挑
C. 偏好遠處① 用 BFS 算步數。
② 每個黃色格子的分數=步數 − 它離起點幾格(離起點幾格=上下差幾格+左右差幾格)。
③ 選分數最小的。意思是:黃色格子每離起點遠 1 格,就當作少走 1 步。
跟 B 一樣會。每一步都重新挑
D. 最終版① 分地盤:每一格霧,歸「總距離最短」的那個黃色格子。總距離=機器人走到那個黃色格子的步數+從黃色格子穿過霧走到那格霧的格數(只能穿過霧)。一樣近時,歸 BFS 先找到的黃色格子。
② 每一格霧的分數=(它離起點幾格 ÷(列數+欄數))的 6 次方;一個黃色格子地盤裡的分數加起來,叫寶藏分。
③ 划算度=寶藏分 ÷(步數+2),選最大的。
④ 安全閥:這張地圖已經走超過步數上限的 5%(10×10 是 20 步、15×15 是 45 步、20×20 是 80 步),需要重選目標時就改成選步數最少的。
安全閥啟動前:選最先被 BFS 找到的。這時比的是寶藏分,幾乎不會剛好平手。
安全閥啟動後:只比步數,平手很常見,一樣選最先被 BFS 找到的。
不會。選定後一直走向它,直到它不再是黃色格子(走到了,或它旁邊的霧在路上已經被看到)才重選。
安全閥不會取消已經選好的目標,只在要重選的那一刻起作用。
例外(四種走法都一樣):旁邊一摸到終點,就直接走進去。
虛線框就是它「選中的那個黃色格子」。每一步的實際計算過程(真實數字),見 C4「用真實數字算一遍」。

建議看法:先選「公開地圖 01」,按「只看 D. 最終版」,然後一直按「下一步」。每按一次,地圖下面的解說框就會告訴你:這一步用了什麼資訊、怎麼算、算出來的數字、所以往哪邊走。解說裡的 ①②③ 對應地圖上標了 1、2、3 的黃色格子。看完 D,再換「只看 A」「只看 B」「只看 C」比較。

看哪幾個:
A. DFS
B. 最近邊境
C. 偏好遠處
D. 最終版
還沒看過(關掉上帝視角時) 還沒看過的路/牆(上帝視角) 看過的路 看過的牆 邊境(站上去能看到新東西) 它現在要去的目標 S 起點 G 終點(黃底=機器人已經發現)
離終點還有幾步(用完整地圖算的真實距離;機器人自己不知道)
已經看過幾格(含牆)

把滑鼠移到圖上,可以看到那一步四個機器人各自的數字;點一下就跳到那一步。圖上的虛線是現在播放到的步數。

公開地圖 01:空曠型 10×10:看哪裡

先停在第 0 步看。機器人站在起點 S,只看到身邊四格:上面是牆、左邊是地圖外面、下面和右邊是路。所以這時候黃色格子只有兩個:S 的下面一格、S 的右邊一格。

B、C 的虛線框在下面那格,D 的虛線框在右邊那格。這就是它們第一個不一樣的決定。

為什麼會這樣選:兩個黃色格子離機器人一樣近(都是 1 步)。C 的分數也一樣(兩格離起點都是 1 格,1 − 1 = 0)。

  • B、C 平手。第 0 步沒有上一步、也沒有舊目標,所以選最先被 BFS 找到的。BFS 照上、下、左、右看:上面是牆,所以先找到下面那格。
  • A(DFS)沒有平手的問題:它照上、下、左、右,走第一個「是路、而且沒站過」的格子。上面是牆,所以也往下。A 沒有虛線框,因為 DFS 不選目標。
  • 終點在右上角,所以往下這一步是在遠離終點。
  • D 算出來:右邊那格分到 77 格霧、寶藏分 1.9023、划算度 0.6341;下面那格分到 19 格霧、寶藏分 0.0895、划算度 0.0298。所以往右。(怎麼算的,見 C4。)

結果:D 一步回頭路都沒走,20 步到(最短 16 步)。B 32 步、C 24 步。A 走了 66 步,其中 12 步是回頭路。

「回頭路」的定義:走進一個之前已經站過的格子,那一步就算一次回頭路。

公開地圖 02:死路型 15×15:看哪裡

四個機器人走的路一模一樣,都是 45 步,剛好等於最短路線。

原因:這是死路型迷宮,只有一條正確的路。這張地圖上,四種走法在每個岔路口都剛好先選到正確的方向,所以沒有走進任何死路。這是運氣,換一張死路型迷宮就不一定了(C2 的模擬考裡,死路型 DFS 和最終版平均都約 91.9 分)。

這張地圖告訴你:不是每張地圖都分得出高下。

公開地圖 03:迴圈型 20×20:看哪裡

看 A(綠色)的路線。它一條路走到底,走不通就原路退回,路線上有很多來回重疊的線。238 步裡有 78 步是走回已經走過的格子。

C 和 D 前 17 步完全一樣,第 18 步才分開:C 走到 (9, 12),D 走到 (8, 11)。差別在打分數的方式:D 算的是「整片霧」的寶藏分,C 只看「邊境那一格離起點多遠」。最後 D 70 步到,C 90 步。

注意右邊的「離終點還有幾步」圖:A 的線上上下下好幾次,代表它常常走離終點越來越遠。

模擬地圖:空曠型 20×20(我產生的,不是老師的):看哪裡

這張是我產生的模擬地圖,不是老師的。我特地挑了一張「四種走法的排名跟平均一樣、但差距比較大」的地圖,讓差異看得清楚。平均情況的差距沒有這麼大(見上面的模擬考成績表)。

  • 起點在最上面一排、偏左,終點在右下角。
  • A、B、C 第一步都往左。原因:起點上面是地圖外面、下面 (1, 3) 是牆,只剩左右兩格,而且一樣近。照上、下、左、右的順序,先看到的是左邊。
  • D 往右沿著最上面一排走,因為右邊和下面那一大片未知區域比較可能藏終點。

結果:D 37 步(最短 29 步),一步回頭路都沒走;A 353 步,其中 107 步是回頭路。

用表格看四張地圖的結果
地圖走法步數(最短)分數回頭路步數
公開地圖 01:空曠型 10×10A. DFS66(16)84.8512
B. 最近邊境32(16)90.03
C. 偏好遠處24(16)93.332
D. 最終版20(16)96.00
公開地圖 02:死路型 15×15A. DFS45(45)1000
B. 最近邊境45(45)1000
C. 偏好遠處45(45)1000
D. 最終版45(45)1000
公開地圖 03:迴圈型 20×20A. DFS238(26)82.1878
B. 最近邊境116(26)84.4811
C. 偏好遠處90(26)85.7815
D. 最終版70(26)87.4313
模擬地圖:空曠型 20×20(我產生的,不是老師的)A. DFS353(29)81.64107
B. 最近邊境183(29)83.1713
C. 偏好遠處69(29)88.415
D. 最終版37(29)95.680

所以對你的影響是:考試如果問「你的走法跟 DFS 差在哪」,可以用公開地圖 01 的第一步當例子:DFS 照固定順序往下走、離終點越來越遠;最終版會估計哪一邊的未知區域比較可能有終點,直接往那邊走。

為什麼要「偏好遠處」?——老師的地圖有規律

我分析了三張公開地圖:終點離起點的路程,比地圖上百分之幾的格子還遠?

公開地圖類型最短路線終點比幾 % 的格子遠
「終點比幾 % 的格子遠」怎麼算:把從起點走得到的每一格,都用完整地圖算出「從起點走過去最少要幾步」。再數有幾 % 的格子步數比終點少。例如 99% 代表幾乎所有格子都比終點近,終點差不多是最遠的那格。實際數字是 98.8%、95.9%、83.5%,表裡四捨五入。
public_map_01(10×10)空曠型 open16 步99%(幾乎是最遠那格)
public_map_02(15×15)死路型 dead_ends45 步96%
public_map_03(20×20)迴圈型 loops_dense26 步84%

三張都很遠。所以合理推測:老師出題時,故意把終點放在離起點遠的地方。這是推測,不是老師講的;所以我也測了「終點隨便放」的情況,確認最終版在那種情況下也不會變差。

公開地圖成績(老師給的 3 張)

走法01:步數/分數02:步數/分數03:步數/分數平均
A. DFS66 / 84.8545 / 100238 / 82.1889.01
B. 最近邊境32 / 90.0045 / 100116 / 84.4891.49
C. 偏好遠處24 / 93.3345 / 10090 / 85.7893.04
D. 最終版20 / 96.0045 / 10070 / 87.4394.48

四種走法撞牆次數都是 0。02 號地圖四種都剛好走最短路(45 步),因為那是一個死路型迷宮,運氣好第一次就選對岔路。

模擬考成績(每種情況 2,700 張隨機地圖)

這張表用的是同一批地圖(亂數種子 777,三種大小 × 三種風格,每組 300 張)。C3 安全閥那張表、工作紀錄裡的數字,用的是別批地圖(種子 2026),所以數字會差一點點,不是算錯。

3 張公開地圖太少,運氣成分很大。所以我寫了一個地圖產生器,模仿老師的三種地圖風格,每種尺寸、每種風格各產 300 張,用老師的公式算分。

走法終點偏遠(像老師的地圖)終點隨便放(保險測試)失敗張數
A. DFS87.6888.140
B. 最近邊境89.1388.550
C. 偏好遠處90.4588.790
D. 最終版91.2488.780

怎麼讀這張表:

誠實說明
在「死路型」迷宮上,最終版(91.95)和 DFS(91.93)打平,沒有明顯優勢。原因是死路型迷宮只有一條正確路線,DFS「一條路走到底」的習慣剛好適合。最終版的優勢主要來自空曠型和迴圈型。(加安全閥之前,最終版在死路型是 91.24,略輸 DFS。)

C3 最終版的想法

結論:把每個邊境想成一扇門,門後面有一片未知的「地盤」。地盤裡離起點越遠的格子越可能藏著終點。算出每扇門的「寶藏分 ÷ 走過去的步數」,挑最划算的門走過去,而且選定了就一直走向它,直到它不再是邊境才重新挑。

七個步驟

每一步都從步驟 1 開始。步驟 2 如果發現終點,就直接走進去,後面都不做。否則做 3、6、7;步驟 4、5 只在「需要重新挑目標」時才做。「需要重新挑」的意思是:還沒有目標(剛出發),或舊目標已經不是邊境了。程式實際執行的順序是 1 → 2 → 3 → 6 →(需要時才做 4 → 5)→ 7。

#步驟白話
1記憶把這次摸到的四格寫進筆記本。
2看終點旁邊有 GOAL?直接走進去,結束。
3找邊境用 BFS 從機器人現在的位置出發,算出走到每一格「已知的路」要幾步(只算走得到的),再從裡面挑出符合 4 個條件的邊境(見 C1)。
4分地盤把未知格分給各個邊境:誰最快到得了,就歸誰。(被已知的牆完全圍住、從任何邊境都碰不到的未知格,不會被分到。)
5算划算度每個邊境的「寶藏分」÷(步數 + 2),挑最高的。安全閥:如果已經走超過步數上限的 5%,就不算划算度,直接挑最近的邊境。
6承諾如果上一步選的目標還是邊境,就繼續走向它,不重新挑。目標不再是邊境(走到了,或者它旁邊的未知格在路上已經被摸到了),才回到步驟 4、5 重新挑。
7走一步沿著 BFS 的最短路線走第一步。最短路線不只一條時,BFS 先看「上一步走的方向」,所以會優先繼續直走。

步驟 4:分地盤

先講它在做什麼:決定每一個未知格子「屬於哪一扇門」。

想像每個邊境同時派出一隊人,往未知區域擴散。可是每隊的出發時間不一樣:機器人走到那個邊境要 d 步,那隊就晚 d 步出發。

每個未知格子,被哪一隊先碰到,就歸那一隊。兩隊在同一圈碰到同一格時,歸 BFS 順序在前的那個邊境。隊伍只能穿過未知格子,不能穿過已知的路或牆。

這樣一來,每扇門後面就有一塊自己的地盤。這種做法叫「多起點 BFS」(multi-source BFS),同時從好幾個起點一起擴散。

步驟 5:寶藏分怎麼算

每個未知格子都有一個「藏著終點的可能性分數」:

注意:這裡的「起點」是這張地圖一開始的出發格(reset() 給的 start_position),不是機器人現在站的位置。

遠近 = 這格離起點的曼哈頓距離 ÷ (地圖列數 + 地圖欄數)
分數 = 遠近 ** 6          (遠近的 6 次方)

曼哈頓距離(Manhattan distance)就是「只能橫著走、直著走」時的格數:上下差幾格+左右差幾格。像在棋盤街道的城市裡走路。

為什麼要 6 次方?因為次方會把「遠」和「近」的差距放大。在 10×10 地圖上:

離起點遠近6 次方(寶藏分)
18 格(很遠)0.90.53
14 格0.70.12
10 格(中間)0.50.016
4 格(很近)0.20.00006

所以一個很遠的格子,價值是中間格子的 30 倍以上。意思是:「近的地方幾乎不可能藏終點,遠的地方才值得去看」。

一扇門的寶藏分 = 它地盤裡所有未知格的分數加起來。

6 這個數字是我用模擬考試出來的。在 900 張「終點偏遠」的地圖上(有承諾、分母加 2),次方 2、4、6、8、10 的平均分分別是 90.52、91.01、91.31、91.21、91.15,6 最高。次方 0(等於不看遠近)只在還沒加承諾的版本試過,是 89.02。

步驟 5(續):划算度

划算度 = 寶藏分 ÷ (走過去的步數 + 2)

舉例(這兩行是假設的數字,用來說明公式;真實數字見 C4):

邊境寶藏分步數划算度
A(很近,但後面地盤小、又靠近起點)0.930.9 ÷ 5 = 0.18
B(比較遠,但後面一大片遠方未知)4.0104.0 ÷ 12 = 0.33 ← 選這個

為什麼要「+2」?它的作用是縮小「近處邊境」的優勢。不加的話,步數 1 和步數 2 的邊境,划算度差 2 倍,Agent 會很容易被旁邊的小角落吸走;加 2 之後變成「3 比 4」,只差約 1.33 倍,遠方的大片未知區比較有機會勝出。(步數最少是 1,不會除以 0。)2 這個數字是模擬考試出來的:加 1 和加 2 幾乎一樣(終點偏遠 91.33 對 91.31、終點隨機 88.74 對 88.76),加 3 是 91.16,所以選 2。(加 6 只在還沒加承諾的版本試過,比較差。)

步驟 5 的安全閥:走太久就改成「先去最近的」

先講它在做什麼:程式自己數已經走了幾步。超過步數上限的 5% 之後,需要重選目標時,就不再「偏好遠處」,改成「先去離自己最近的邊境」。

精確地說:程式數的是「這張地圖 act() 被呼叫了幾次(包含這一次)」。這個數字大於 0.05 × 4 × 列數 × 欄數,安全閥才啟動。所以 10×10 地圖是從第 21 次呼叫開始。它只在需要重選目標的那一刻起作用,不會取消已經選好的目標。

在 3 張公開地圖和回放的 4 張地圖上,最終版都在安全閥啟動前就走到終點了(公開地圖:20/45/70 步,門檻 20/45/80 步)。

地圖大小步數上限安全閥在第幾步後啟動
10×1040020 步
15×1590045 步
20×201,60080 步

為什麼要加:獨立審查員專門造了一張刁鑽地圖,讓舊版程式走了 812 步(上限 900),只差 88 步就 0 分。原因是探索快結束時,只剩一些零散的小角落沒看過,舊版照「離起點多遠」決定先去哪個,結果在地圖兩端來回跑。

為什麼不會少分:「偏好遠處」真正有用的時候,是一開始決定往哪個大方向走。走了一陣子之後:

實際數字:同一批地圖上,沒有安全閥 91.10/88.86,安全閥 5% 是 91.18/88.96,沒有變差。

實驗結果(試過 5%、7.5%、10%、15%、20%、25%、30%、40%,用三批不同的地圖確認):

版本最刁鑽地圖最多用掉步數模擬地圖平均(終點遠/隨機)
沒有安全閥(舊版)90.2%91.10/88.86
安全閥 25%50.3%91.08/88.99
安全閥 10%35.5%91.16/88.90
安全閥 5%(採用)29.7%91.18/88.96

平均分那一欄是同一批 2,700+2,700 張地圖(跟調參數用的同一個亂數種子 2026,但每組 300 張,比調參數時的 100 張多)。另外兩批地圖(種子 777、4242)的結論相同:安全閥越早啟動越安全,平均分不會變差。

這個做法完全合規:程式只用到 reset() 給的地圖大小,加上自己數的步數,沒有偷看任何地圖資訊。

步驟 6:承諾——這一步是「不會失敗」的關鍵

一開始我沒有這一步。結果在 900+900 張模擬地圖上,依參數不同,有 0~11 張失敗了(0 分)。

原因是「三心二意」:Agent 每走一步,地盤就重新分一次,划算度也跟著變。它可能這一步覺得 A 門好、往 A 走一步;下一步又覺得 B 門好、往回走一步⋯⋯就這樣在兩扇門之間來回踱步,把步數用光。

解法:一旦選定目標,就一直走向它,直到它不再是邊境(走到了,或它旁邊的未知格已經被摸到)才重新挑。加上之後,5,400 張壓力測試 0 失敗。平均分只差一點:以次方 4 那組為例,終點偏遠 91.08 → 91.01(少 0.07),終點隨機 88.76 → 88.80(多 0.04)。用一點點分數,換到「不會失敗」,很划算。

考試可以這樣講
這是 utility-based agent(效用導向的 Agent):它不只問「能不能到終點」,而是對每個選擇算一個分數(效用),挑最高的。效用=「估計的收穫(寶藏分:越遠的未知格分數越高,這是啟發式估計,不是真的機率)」÷「代價(步數 + 2)」。

C4 用真實數字算一遍:BFS、寶藏分、四種走法怎麼選

結論:四種走法每一步都在做同一件事:從黃色格子(邊境)裡挑一個去。差別只在「打分數的公式」。這一節拿公開地圖 01 的兩個真實時刻,把每一個數字算給你看。這些數字是程式實際算出來的,不是我編的。

先講好規則
下面的數字都是「最終版(D)真的走到那一步時」的狀態。為了比較,我問的是:如果 A、B、C 站在同一個位置、知道一樣多的東西,它們會選哪一個?
(在回放裡,A、B、C 自己走的路不一樣,所以實際上不會站在同一個位置。這裡是為了讓你在同一個畫面上比較四種公式。)

C4.1 第一個例子:第 0 步,剛出發

這就是回放裡「大家分道揚鑣」的那一刻。機器人站在起點(7, 0),只摸了一次四周:

0123456789
0
1
2
3
4
5
6
7@B
8A
9

@ 機器人 黑色=已知的牆 黃色字母=邊境 灰色=霧(還不知道)

步驟一:BFS 算「走過去要幾步」

這一步很簡單:A、B 都在隔壁,都是 1 步。BFS 詳細怎麼跑,第二個例子(C4.2)再講,因為那時候格子比較多。

步驟二:分地盤——「分到 77 格霧」是什麼意思

先對照名字
回放的解說框用 ① ② 表示(照划算度排名):① = 右邊那格 (7, 1),② = 下面那格 (8, 0)。
這一節的表格用字母表示(照 BFS 找到的順序):A = 下面那格 (8, 0),B = 右邊那格 (7, 1)。
所以:① = B,② = A。下面的分輪圖用 ① ② 標,跟回放一致。

定義:「① 分到 77 格霧」的意思是:現在地圖上的 96 格霧裡面,有 77 格是「從 ① 出發,比從 ② 出發更快摸得到」的。這 77 格就叫 ① 的「地盤」。

為什麼要這樣分?因為終點一定藏在某一格霧裡。如果終點在 ① 的地盤裡,從 ① 出發去找最快;在 ② 的地盤裡,從 ② 出發最快。所以「地盤有多大、地盤裡的霧有多遠」,就代表「走向這個黃色格子,有多大機會找到終點」。

怎麼分:兩隊人同時往霧裡擴散

規則(照程式 _share_unknown_cells 的做法):

  1. 每個黃色格子派一隊人。出發時間=機器人走到那個黃色格子要幾步。① 和 ② 都是 1 步,所以同時出發。
  2. 每一輪,每一隊從「自己已經佔領的格子」往上下左右擴一格,佔領還沒被佔、而且是霧的格子。
  3. 只能走進霧,不能走進已知的牆或已知的路。
  4. 同一輪兩隊搶同一格:先處理的那隊先搶到。先處理的是 BFS 先找到的黃色格子,也就是 ②(下面那格;因為 BFS 照上、下、左、右看,「下」比「右」先)。
  5. 一直擴散,直到所有霧都被佔領。

實際過程(紅底的 ①=被 ① 佔領的霧,藍底的 ②=被 ② 佔領的霧,灰色=還沒被佔的霧,黃底=黃色格子本身,紅色 @=機器人,黑色=已知的牆):

第 1 輪後:① 2 格、② 2 格
①
@①①
②②
②
第 2 輪後:① 5 格、② 4 格
①
①①
@①①①
②②②
②②
第 3 輪後:① 10 格、② 6 格
①
①①①
①①①
@①①①①
②②②②
②②②
全部分完:① 77 格、② 19 格
①①①①①①①①①①
①①①①①①①①①①
①①①①①①①①①①
①①①①①①①①①①
①①①①①①①①①①
①①①①①①①①①①
①①①①①①①①①
@①①①①①①①①①
②②②②②②②②②②
②②②②②②②②②②

看第 1 輪:① 在右邊那格,往上佔了 (6, 1)、往右佔了 (7, 2)。② 在下面那格,往下佔了 (9, 0)、往右佔了 (8, 1)。
(8, 1) 其實 ① 往下也碰得到,兩隊同一輪搶同一格。但 ② 先處理,所以 (8, 1) 歸 ②。

為什麼最後 ② 只拿到最下面兩列:

所以 ② 拿到第 8 列 9 格+第 9 列 10 格=19 格,① 拿到其餘 77 格。合計 96 格,就是全部的霧。

要注意:這是「估計」,不是真的距離
機器人不知道霧裡哪裡是牆,所以分地盤時把霧都當成走得過去。實際上,這 96 格霧裡有 14 格其實是牆(① 的地盤裡有 9 格、② 的有 5 格)。
所以「分到 77 格」不是說 77 格都能走,而是「以目前知道的資訊,這 77 格從 ① 過去比較快」。等機器人走過去、看到更多,下一次重選時就會用新的地圖重新分。

步驟三:算寶藏分——每一格霧值多少分

先講它在做什麼:猜「終點在這一格」的可能性有多高。我們觀察到老師的終點都放得離起點很遠,所以:離起點越遠的霧,分數越高。

每一格霧的分數這樣算:

遠近 = 這格離起點的格數(曼哈頓距離) ÷ (列數 + 欄數)
分數 = 遠近 ** 6           (遠近乘自己 6 次)

這張地圖 10 列 10 欄,所以除以 20。舉三格當例子:

格子離起點 (7, 0) 幾格遠近分數 = 遠近⁶
右上角 (0, 9)
剛好就是終點,但機器人還不知道
7 + 9 = 1616 ÷ 20 = 0.800.80 × 0.80 × 0.80 × 0.80 × 0.80 × 0.80 = 0.2621
中間 (3, 5)4 + 5 = 99 ÷ 20 = 0.450.0083
左下角 (9, 0)2 + 0 = 22 ÷ 20 = 0.100.000001(幾乎是 0)

右上角一格的分數,是中間那格的 30 倍以上。這就是「6 次方」的效果:把遠和近的差距放大。

一個邊境的寶藏分=它地盤裡所有霧的分數加起來。下面這張表把霧按「離起點幾格」分組,一組一組加:

離起點幾格遠近每格分數A 有幾格A 小計B 有幾格B 小計
22/20 = 0.100.000020.000030.0000
33/20 = 0.150.000020.000040.0000
44/20 = 0.200.000120.000150.0003
55/20 = 0.250.000220.000560.0015
66/20 = 0.300.000720.001570.0051
77/20 = 0.350.001820.003780.0147
88/20 = 0.400.004120.008280.0328
99/20 = 0.450.008320.016680.0664
1010/20 = 0.500.015620.031270.1094
1111/20 = 0.550.027710.027760.1661
1212/20 = 0.600.046700.000050.2333
1313/20 = 0.650.075400.000040.3017
1414/20 = 0.700.117600.000030.3529
1515/20 = 0.750.178000.000020.3560
1616/20 = 0.800.262100.000010.2621
加總(寶藏分)190.0895771.9023

A 的地盤都在起點附近(離起點最多 11 格),每格分數都很小,加起來只有 0.0895。B 的地盤包含很多遠方的霧,加起來 1.9023,是 A 的 21 倍。

步驟四:算划算度,挑最高的

划算度 = 寶藏分 ÷ (走過去幾步 + 2)

A(下面):0.0895 ÷ (1 + 2) = 0.0298
B(右邊):1.9023 ÷ (1 + 2) = 0.6341   ← 最高,D 選它

同一個時刻,四種走法各選哪個

邊境位置走過去幾步 d
(BFS 算的)
B 的分數
= d
越小越好
離起點幾格
(曼哈頓)
C 的分數
= d − 離起點格數
越小越好
D:分到幾格未知D:寶藏分D 的划算度
= 寶藏分 ÷ (d + 2)
越大越好
A B 選C 選(8, 0)1110190.08950.0298
B D 選(7, 1)1110771.90230.6341
走法怎麼選這一步的計算結果
A. DFS不看邊境。照「上、下、左、右」的順序,挑身邊第一個沒去過的路上是牆 → 下是路、沒去過 → 就是它往下
B. 最近邊境挑「走過去幾步 d」最小的邊境A、B 都是 1 步,平手 → 選先被 BFS 找到的 A往下
C. 偏好遠處挑「d − 離起點格數」最小的邊境(每離起點遠 1 格,就當作少走 1 步)A:1 − 1 = 0;B:1 − 1 = 0,平手 → 選 A往下
D. 最終版挑划算度最高的邊境A:0.0298;B:0.6341往右
好幾個邊境一樣近(平手)時,選哪一個?
規則有兩層:
① B、C:如果上一步選的目標也在平手名單裡,就繼續選它(避免左右搖擺)。
② 不然的話,誰先被 BFS 找到,就選誰。BFS 看四周有固定順序:先看「上一步走的方向」(等於繼續直走),再照「上、下、左、右」看其他方向。
第 0 步還沒走過,沒有「上一步」,也沒有舊目標,所以只用第 ② 層,順序就是上、下、左、右:上面是牆,接著看「下」,下面那格先被找到。所以 B、C 選下面。

補充三點:
・離機器人 1 步的邊境,最多 4 個(剛出發、四周都是路的時候)。走了之後一定最多 3 個:走來的那一格機器人站過,站過的格子四周都摸過了,不可能是邊境。
・「隔壁是路」不等於「隔壁是邊境」:那格旁邊還有霧,才算邊境。
・D 也可能平手,規則是「選最先被 BFS 找到的」。安全閥啟動前,它比的是「整片霧的分數」,幾乎不會剛好一樣;安全閥啟動後只比步數,就常常平手。

重點:C 也想「往遠處走」,但它只看邊境那一格本身離起點多遠。下面那格和右邊那格離起點都是 1 格,所以 C 分不出差別。D 看的是邊境後面整片霧,所以看得出右邊那一大片比較值得去。

C4.2 第二個例子:第 12 步,邊境變多了

最終版已經走了 12 步:沿著第 7 列往右走到底,再往上一格、往左兩格,現在站在 (6, 7)。地圖上有 14 個邊境。

0123456789
0
1
2
3
4
5B
6KIGECA@
7
8NMLJFDH
9

BFS 是怎麼跑的

先講它在做什麼:從機器人現在的位置出發,算出走到每一格「已知的路」最少要幾步,順便記下「第一步要往哪走」。它只走已知是路的格子,不走霧、不走牆。

做法像排隊領號碼:

  1. 機器人自己先排進隊伍,距離 0。
  2. 從隊伍最前面叫一個人出來,看他上下左右四格。
  3. 是已知的路、而且還沒算過的,就給它「出來的人的距離 + 1」,排到隊伍最後面。
  4. 重複,直到隊伍空了。

因為「先排的先處理」,距離 1 的全部處理完,才會輪到距離 2 的。所以每一格第一次拿到的號碼,就是最短距離。

看四周的順序:上一步是往左走的,所以先看「左」,再看上、下、右。這樣兩條路一樣短的時候,會優先繼續直走。

實際的前 8 輪:

輪叫出來的格子新加進隊伍的格子隊伍現在長這樣
1(6, 7)(0 步)(6, 6)(1 步,第一步往左)、(5, 7)(1 步,第一步往上)、(7, 7)(1 步,第一步往下)、(6, 8)(1 步,第一步往右)(6, 6)、(5, 7)、(7, 7)、(6, 8)
2(6, 6)(1 步)(6, 5)(2 步,第一步往左)、(7, 6)(2 步,第一步往左)(5, 7)、(7, 7)、(6, 8)、(6, 5)、(7, 6)
3(5, 7)(1 步)沒有新格子(旁邊都是牆、霧或算過的)(7, 7)、(6, 8)、(6, 5)、(7, 6)
4(7, 7)(1 步)(8, 7)(2 步,第一步往下)、(7, 8)(2 步,第一步往下)(6, 8)、(6, 5)、(7, 6)、(8, 7)、(7, 8)
5(6, 8)(1 步)(6, 9)(2 步,第一步往右)(6, 5)、(7, 6)、(8, 7)、(7, 8)、(6, 9)
6(6, 5)(2 步)(6, 4)(3 步,第一步往左)、(7, 5)(3 步,第一步往左)(7, 6)、(8, 7)、(7, 8)、(6, 9)、(6, 4)、(7, 5)
7(7, 6)(2 步)(8, 6)(3 步,第一步往左)(8, 7)、(7, 8)、(6, 9)、(6, 4)、(7, 5)、(8, 6)
8(8, 7)(2 步)沒有新格子(旁邊都是牆、霧或算過的)(7, 8)、(6, 9)、(6, 4)、(7, 5)、(8, 6)

一直做到隊伍空了,全部的結果(數字=走過去要幾步;字母旁的小數字=那個邊境要幾步):

0123456789
0
1
2
3
4
5B1
6K6I5G4E3C2A1@12
78765432123
8N9M7L6J5F3D2H4
9

四種走法各選哪個

邊境位置走過去幾步 d
(BFS 算的)
B 的分數
= d
越小越好
離起點幾格
(曼哈頓)
C 的分數
= d − 離起點格數
越小越好
D:分到幾格未知D:寶藏分D 的划算度
= 寶藏分 ÷ (d + 2)
越大越好
A B 選(6, 6)117-660.17780.0593
B C 選D 選(5, 7)119-8151.40830.4694
C (6, 5)226-460.10420.0260
D (8, 7)228-620.02390.0060
E (6, 4)335-260.05830.0117
F (8, 6)337-420.00590.0012
G (6, 3)444060.03080.0051
H (8, 9)4410-610.02770.0046
I (6, 2)553260.01530.0022
J (8, 4)555010.00070.0001
K (6, 1)6624120.00990.0012
L (8, 3)664210.00020.0000
M (8, 2)773420.00010.0000
N (8, 0)991810.00000.0000
走法這一步的計算結果
A. DFS上面 (5, 7) 是路、沒去過 → 就是它往上
B. 最近邊境最小的 d 是 1,有兩個:A、B。平手 → 這裡假設沒有舊目標,所以選先被 BFS 找到的 A (6, 6)(上一步往左,BFS 先看左邊)往左(往回走,朝起點的方向)
C. 偏好遠處「d − 離起點格數」最小的是 B:1 − 9 = -8往上
D. 最終版划算度最高的是 B:分到 15 格霧,寶藏分 1.4083,÷ (1 + 2) = 0.4694往上

重點:

C4.3 四個公式放在一起

走法看什麼公式選缺點
A. DFS身邊四格+自己站過哪些格子+一路走來的路線照上、下、左、右順序,第一個「是路、而且沒站過」的格子;沒有就退回上一格第一個不看大局;死路要原路一格一格退回
B. 最近邊境所有邊境的步數d最小不管方向,常常往回走
C. 偏好遠處邊境那一格離起點多遠d − 離起點格數最小只看一格,看不到後面有多大片霧
D. 最終版邊境後面整片霧Σ(遠近⁶) ÷ (d + 2)最大算比較多;靠「終點通常很遠」這個規律

所以對你的影響是:考試問「你的 Agent 怎麼決定下一步」,可以直接用第 0 步這個例子:兩個邊境一樣近,但右邊那個後面有 77 格霧、寶藏分 1.90,下面那個只有 19 格、0.09,所以往右。

C5 逐段讀程式

結論:agent.py 分成「老師規定的三個方法」和「八個小幫手」。每個小幫手只做一件事:其中五個對應 C3 節的步驟,另外三個(_step、_inside、_safe_move)是基本工具和備案。

整份程式的地圖

名稱對應步驟做什麼
POWER = 6.05寶藏分的次方
COST_OFFSET = 2.05划算度分母的「+2」
SAFE_FRACTION = 0.055安全閥:走超過步數上限的 5% 後,改去最近的邊境
reset()—新地圖:清空筆記本、目標、上一步方向
act()—呼叫 _decide();萬一出錯就用 _safe_move() 保命
_decide()1→7主流程,照七個步驟走
_step(cell, action)—算「從某格往某方向走一格」是哪一格
_inside(cell)—這格在地圖範圍內嗎
_unknown_count(cell)3這格旁邊有幾個未知格(大於 0 就是邊境)
_bfs_from(position)3、7BFS:走到每格的步數、第一步方向
_choose_target()5算每個邊境的划算度,挑最高的
_share_unknown_cells()4分地盤(多起點 BFS)
_safe_move()—備案:照上、下、左、右的順序,回傳第一個是路或終點的方向;四個都不是就回傳 UP。在「程式出錯」或「找不到邊境」時才用(後者實際上不會發生,見 C6)

reset():每張新地圖都從零開始

def reset(self, rows, cols, start_position):
    self.rows = rows
    self.cols = cols
    self.start_position = tuple(start_position)
    self.known = {}            # 筆記本:座標 → "FREE" / "WALL" / "GOAL"
    self.target = None         # 目前要去的邊境
    self.last_action = None    # 上一步的方向(平手時繼續直走)
    self.steps_taken = 0       # 這張地圖已經走了幾步(安全閥用)

這裡每一個記憶都重新建立,所以換地圖時不會帶著舊記憶。

act():外面包一層保險

def act(self, percept):
    self.steps_taken += 1      # 每被問一次,就是走一步
    try:
        return self._decide(percept)
    except Exception:
        return self._safe_move(percept)

try / except 的意思是:「試著做;萬一中途出錯,不要整個當掉,改做備案」。

為什麼要這樣?因為如果程式出錯,老師的評分程式會判那張地圖 0 分。有了備案,最慘也只是走一步不太聰明的路。正常情況下備案永遠用不到(壓力測試時我把保險拿掉跑,0 次出錯)。

_decide():主流程

position = tuple(percept["position"])
neighbors = percept["neighbors"]

# 1. 記憶:自己站的格子一定是路;四個鄰居照摸到的寫
self.known[position] = "FREE"
for action, status in neighbors.items():
    self.known[self._step(position, action)] = status

# 2. 旁邊有終點就走進去
for action in MOVES:
    if neighbors[action] == "GOAL":
        return action

# 3. BFS,挑出所有邊境(不包含自己站的這格)
distance, first_move = self._bfs_from(position)
frontiers = [cell for cell in distance
             if cell != position and self._unknown_count(cell) > 0]
if not frontiers:                       # 找不到邊境(實際上不會發生,見 C6)
    return self._safe_move(percept)

# 6. 承諾:舊目標還是邊境,就不換
if self.target not in frontiers:
    self.target = self._choose_target(frontiers, distance)   # 4 + 5

# 7. 走最短路線的第一步
self.last_action = first_move[self.target]
return self.last_action

注意第 3 步:distance 只包含「已知是 FREE、而且走得到」的格子。所以挑出來的邊境一定走得到,走過去的路上也一定沒有牆。

_bfs_from():一圈一圈擴散

distance = {position: 0}        # 走到每格要幾步
first_move = {position: None}   # 走到每格的第一步方向
queue = deque([position])       # 排隊等著往外擴的格子
while queue:
    cell = queue.popleft()      # 先進先出:先處理比較近的
    for action in order:
        n = self._step(cell, action)
        if n not in distance and self.known.get(n) == "FREE":
            distance[n] = distance[cell] + 1
            first_move[n] = first_move[cell] or action
            queue.append(n)

deque 是 Python 內建的「排隊隊伍」,從尾巴加入、從頭拿出(先進先出)。這就是 BFS 能「一圈一圈」的原因:近的先排、先處理。

first_move[cell] or action 的意思:cell 是現在正在往外擴的那一格。如果 cell 已經有「第一步」,新格子就沿用它;如果 cell 就是機器人自己(它的第一步是 None,還沒有),那這一步的方向 action 就是第一步。

order 會把「上一步的方向」排第一。這樣兩條路一樣短的時候,Agent 會繼續直走,不會左右扭來扭去。

_share_unknown_cells():分地盤

buckets = {}
for f in frontiers:                     # 每個邊境在「自己的距離」那一層出發
    buckets.setdefault(distance[f], []).append((f, f))

owner = {}
level = min(buckets)
while buckets:
    for cell, frontier in buckets.pop(level, []):
        for action in MOVES:
            n = self._step(cell, action)
            if self._inside(n) and n not in self.known and n not in owner:
                owner[n] = frontier     # 這個未知格歸這個邊境
                buckets.setdefault(level + 1, []).append((n, frontier))
    level += 1

buckets 是「一層一層的籃子」:第 L 層的籃子裝「總距離= L」的(格子, 歸屬的邊境)。總距離=機器人走到那個邊境的 d+從邊境穿過霧走到那格的格數。每個邊境一開始放在第 d 層的籃子,所以遠的邊境等於「晚出發」。

只擴散到未知格(n not in self.known),因為我們要分的是未知的地盤。

_choose_target():算划算度

if self.steps_taken > SAFE_FRACTION * 4 * self.rows * self.cols:
    return min(frontiers, key=lambda f: distance[f])        # 安全閥:最近的邊境

owner = self._share_unknown_cells(frontiers, distance)      # 分地盤

span = self.rows + self.cols                                # 列數 + 欄數
sr, sc = self.start_position                                # 地圖一開始的起點
value = {f: 0.0 for f in frontiers}                         # 每個邊境的寶藏分,從 0 開始
for cell, frontier in owner.items():
    far = (abs(cell[0] - sr) + abs(cell[1] - sc)) / span   # 曼哈頓距離 ÷ (列+欄)
    value[frontier] += far ** POWER                         # 加進那扇門的寶藏分

best, best_ratio = None, -1.0
for f in frontiers:                                         # 照 BFS 找到的順序
    ratio = value[f] / (distance[f] + COST_OFFSET)          # 划算度
    if ratio > best_ratio:                                  # 只有「嚴格比較大」才換人
        best, best_ratio = f, ratio
return best

因為只有「嚴格比較大」才換人,兩個邊境划算度一樣時,會留下比較早出現的那個,也就是 BFS 先找到的那個。

所以對你的影響是:考試如果問「你的 Agent 怎麼決定下一步」,你可以照 _decide() 的七個註解順序講一遍,每一步都對得上程式。

C6 為什麼不會撞牆、不會卡死

結論:「不撞牆」和「一定會結束、一定會找到終點」這兩件事,都有道理可以證明。但「一定在步數上限內找到」證明不了,那一點是靠大量實測支撐的。

C6.1 為什麼永遠不撞牆(可以證明)

  1. 旁邊有 GOAL 時,我們走進 GOAL,不是牆。
  2. 其他時候,我們走的是 BFS 路線的第一步。
  3. BFS 只會走「已知是 FREE」的格子(self.known.get(n) == "FREE")。
  4. 「已知是 FREE」一定是真的:感知不會出錯,地圖也不會改變。
  5. 所以第一步一定是走進一條真的路,不會是牆。

_safe_move()(備案)也只會選 FREE 或 GOAL 的方向。它會在兩種情況被叫到:

它只有在「四面都是牆」時才會回傳 "UP" 去撞牆。但這不會發生:起點一定有一個鄰居是路(因為起點到終點有路,而終點不在起點旁邊);之後走到的每一格,至少「走來的那一格」是路。

C6.2 為什麼一定會找到終點(可以證明,但沒有步數保證)

分三段想。

第一段:還沒找到終點時,一定還有邊境可以去

第二段:每換一次目標,至少有一個未知格變成已知

目標只會在兩種情況下被換掉:

  1. 走到目標了。站上去之後會摸到它的四個鄰居,它旁邊的未知格就變成已知。
  2. 還沒走到,目標就不再是邊境了。這代表它旁邊的未知格,在路上已經被我們從別的格子摸到了。

兩種情況都至少有一格從「未知」變成「已知」。

在換目標之前,我們一直沿著最短路線走向同一個目標。每走一步,離目標就近一步:新摸到的資訊只會「多發現路」,不會讓已知的路消失,所以距離只會變短、不會變長。因此每一段都在有限步內結束。

第三段:未知格是有限的

地圖最多 400 格,所以未知格最多 400 個。每換一次目標至少少一個,所以換目標的次數有限。

終點就藏在某個未知格裡。第一段說「還沒找到就一定有邊境可以去」,第二段說「每一趟都會減少未知格」,所以在未知格用完之前,一定會摸到終點。

證明不了的部分:步數上限
上面只證明了「有限步內一定會結束」,沒有證明「一定在 4 × 列數 × 欄數 步以內」。理論上最壞的情況,可能要換目標幾百次、每次走很遠,總步數可能超過上限。
所以「80 分基本分很穩」這句話,依據是實測,不是證明:加了「安全閥」(C3)之後,所有測試裡最多只用掉上限的約 30%,包含審查員專門造來刁難程式的地圖。
還有一個小縫:程式出錯的情況
如果 _decide() 一直出錯,每一步都會落到 _safe_move()。它每次都照上、下、左、右,選第一個是路或終點的方向,所以可能在兩格之間來回踱步,直到步數用完。上面的證明不涵蓋這種情況。這一點靠的也是實測:獨立審查員和我的壓力測試都把保險拿掉跑過,_decide() 一次都沒出錯。

所以對你的影響是:考試問「你的 Agent 為什麼不會撞牆」可以講證明;問「為什麼一定找得到終點」可以講三段論證;但要誠實補一句「步數上限內完成是實驗驗證的,不是證明的」。

D1 怎麼驗證的、還有什麼風險

結論:做了三種驗證,全部通過。最大的不確定是「隱藏地圖跟我模擬的不一樣」,所以 91 分是預估,不是保證。

三種驗證

驗證怎麼做結果
1. 老師的公開測試在 HW2\HW2\ 資料夾執行 py -3.11 public_grader.py3/3 成功,0 撞牆
2. 抄寫檢查正式版跟實驗版在同一批地圖上逐張比對步數(加安全閥前比 1,800 張、加安全閥後比 720 張)每一張都一模一樣
3. 壓力測試全新一批 5,400 張地圖(調參數、選安全閥時都沒用過),把 try/except 保險拿掉,有錯就會爆出來0 失敗、0 撞牆、0 出錯;最低分 80.15;最多用掉步數上限 27%
4. 刁鑽地圖審查員 2 專門造來刁難程式的 60 張地圖,每張試遍所有終點位置全部成功;最多用掉步數上限 29.7%(加安全閥前是 90.2%)
5. 獨立審查三個互相獨立的審查員見 D2

預估分數

正式考試是 9 張(README 說三種尺寸各三張;我假設三種風格各一張)。我用「終點放遠處」的模擬地圖,模擬了兩萬次「抽 9 張考一次」:

情況9 張平均
運氣差(最差的 5%)88.4
一般(中間值)91.2
運氣好(最好的 5%)94.2

單張地圖來看:一半的地圖在 90.9 分以上,有 11% 的地圖拿到滿分 100。

還有什麼風險

風險機率後果我們的處理
隱藏地圖的終點不是放在遠處不確定平均少約 2.5 分(模擬:91.24 → 88.78)已測「終點隨便放」:走法 C 最高(88.79),最終版 88.78,只低 0.01,等於打平
隱藏地圖的形狀跟我模擬的不一樣不確定分數會有出入我的產生器是照三張公開地圖的風格做的;走法本身不依賴特定形狀
Linux 和 Windows 差異很低—程式只用 collections.deque,沒有讀檔、沒有路徑、沒有作業系統相關的東西
Python 版本問題很低—已經用你電腦上的 Python 3.11 測過,跟正式環境同版本
跑太慢超時很低—公開地圖每張約 0.05~0.1 秒;最難的對抗地圖最慢 3.3 秒,9 張加起來估計不到 30 秒
不確定的部分
「老師故意把終點放遠」是我從三張公開地圖推測的,老師沒有明講。隱藏地圖的實際分數要等老師公布才知道。

D2 獨立審查結果

結論:三個互相獨立的審查員都沒有找到會讓作業被判 0 分的問題。但它們找到了一個程式的安全風險(最慘情況用掉 90% 步數)和文件裡 7 個會影響考試答案的錯誤,全部已經修好並重新驗證。

D2.1 為什麼要找「別人」來審查

自己寫的東西自己檢查,很容易「看到自己想看的」。所以我派了三個全新的 AI 審查員:

D2.2 三個審查員各做了什麼

審查員查什麼做了多少結論
1. 規章合規程式和 zip 有沒有違反 README 和 COOL 公告的任何一條逐條核對 README 第 6、7、10、11 節;同一台機器人連跑 300 張地圖檢查記憶有沒有清乾淨;餵 6 種壞掉的資料給 act();比對 zip 和原檔的指紋可以交,沒有會 0 分的問題。唯一要你做的:改 zip 檔名
2. 程式抗壓想盡辦法讓程式失敗、撞牆、出錯、跑太慢約 5.7 萬局真實測試;12 類手工刁鑽地圖;用「爬山法」專門造出 38 萬張讓程式最慘的地圖;7 種介面濫用0 失敗、0 撞牆、0 出錯。但找到一張地圖用掉 90% 步數(已修,見 D2.3)
3. 內容正確性文件的每一句話對不對:規則、程式說明、投影片頁數、觀念逐條核對投影片原文 28 處引用;重跑我的實驗確認數字;檢查證明有沒有漏洞數字和程式說明大致正確;7 個錯誤、14 個容易誤會的地方(已全部修正,見 D2.4)

D2.3 程式的修改:加了「安全閥」

審查員 2 找到的問題:它造出一張 15×15 地圖,讓程式走了 812 步,上限 900 步,只差 88 步就會 0 分。

原因:「偏好遠處」太強。探索快結束時,地圖上只剩零星的小死角,程式照「離起點多遠」決定先去哪個,而不是照「離自己多近」,結果在地圖兩端來回跑。那張地圖上它換了 118 次目標,其中 19 趟長途移動就花了 684 步。

修法:程式自己數已經走了幾步。超過步數上限的 5% 之後,就改成「先去離自己最近的邊境」。詳見 C3 的「安全閥」。

修完的結果:

項目修之前修之後
審查員的 60 張最刁鑽地圖,最多用掉步數上限90.2%29.7%
那張 812 步的地圖812/900242/900
終點離起點 2 步、卻先跑完整張圖的 20×20 地圖1,090/1,600444/1,600
模擬地圖平均分(終點遠/終點隨機)91.14/88.7291.24/88.78(沒有變差)
全新一批 5,400 張壓力測試—0 失敗、0 撞牆、0 出錯,最多用掉 27%

舊版程式備份在 C:\D槽\TAICA課程\人工智慧導論\HW2\agent_v1_before_safety.py,舊的 zip 在 zip_v1_before_safety.zip,想退回隨時可以。

同時也修了 agent.py 裡一行說明文字:原本說「+2 是為了避免除以很小的數」,審查員指出不對(步數最少是 1,不會除以 0)。改成正確的理由:縮小近處邊境的優勢。

D2.4 文件的修改:7 個會影響考試答案的錯誤

#原本寫錯的改成在哪
1邊境示意圖標錯兩格(有兩格明明是邊境卻沒標)用程式模擬出一個真的走得出來的情況,重畫C1
2「known/unknown 正確答案是 known」這題有爭議。老師的 Ch4 投影片把這種迷宮叫 unknown environment,考試建議答 unknown,再補一句「移動規則本身已知」B2、E1、E2 Q3
3「A* 只要 h 不高估就保證最短」補上:記住走過哪裡的版本(graph search)還需要 h「一致」(Ch3 第 33 頁)B5
4「知道終點、只是不知道牆,就能用 A*」錯。A* 要出發前先算好整條路,需要知道地圖。看不到地圖,就算知道終點也不能直接用E2 Q6
5「選定目標就走到底,中途不換」「七個步驟每步都做」程式實際上是「目標不再是邊境就換」;步驟 4、5 只在要重選時才做C3、C6、E2 Q7
6「備案只在程式出錯時用」,而且程式摘錄漏了兩行補上「找不到邊境時也會用」(並證明這不會發生),摘錄補回漏掉的兩行C5、C6
7把 sequential 翻成「連續性」「連續」是 continuous 的譯名,考試會被誤會。改成「序列式」B2、E1

D2.5 其他修正(容易誤會、小問題)

D2.6 審查後還剩下的風險

風險會怎樣說明
隱藏地圖的終點不是放在遠處平均少 2~3 分,不會 0 分審查員 2 實測:終點全在近處時,平均約 86~87.5 分
終點就在起點附近那一張約 80 分「偏好遠處」的代價:近處的角落會晚一點才去看。因為不撞牆,最多少 20 分,不會 0 分
步數上限理論上無法證明不超過但目前所有測試(包含專門造的刁鑽地圖)最多只用到約 30%
老師對 AI 協助的規定不確定課程沒寫作業能不能用 AI,只寫了期末考不能用

D3 怎麼交件

結論:zip 已經打包好了,你只要把檔名改成自己的學號姓名,上傳到 NTU COOL。

步驟

  1. 打開 C:\D槽\TAICA課程\人工智慧導論\HW2\。
  2. 找到 學號_姓名.zip。
  3. 改名成你的學號和姓名,例如 P76123456_王小明.zip。底線是半形的 _,不要留大括號。
  4. 到 NTU COOL 的 Homework 2 上傳這個 zip。截止:10/15(四)23:59。
如果你之後改了 agent.py
zip 裡面是「打包當下」的 agent.py,不會自動更新。改完要重新打包:zip 打開後最外層只能有一個 agent.py,不能有資料夾。

老師 README 的繳交前檢查(逐條對過)

檢查項狀態
已在 Python 3.11 測試程式已做(py -3.11)
public_grader.py 正常執行,三張公開地圖均在步數限制內完成3/3
reset() 能處理不同尺寸與起點,並清除上一張地圖的狀態已做(壓力測試用同一個 Agent 連續跑上百張不同尺寸地圖)
act() 每次只回傳一個合法方向字串已做(連出錯時的備案也只回傳合法方向)
不需要使用者輸入、不讀檔程式裡沒有 input()、沒有 open()
只使用標準函式庫只匯入 collections.deque
ZIP 命名正確,內部只有一份 agent.py內容已確認只有 agent.py;檔名要你改

D4 結論:一條龍總回顧

結論:這份作業從頭到尾是一條因果鏈:看不到地圖 → 要有記憶 → 要找邊境 → 要用 BFS 走過去 → 要決定先去哪個邊境 → 用划算度+承諾來決定 → 驗證不會失敗、不會撞牆 → 預估約 91 分。

D4.1 一條龍:每一環為什麼接到下一環

#這一環為什麼會走到下一環在哪一節
1題目:蒙眼走迷宮,只摸得到四格,走越少分數越高只摸得到四格=看不到全部A1
2分數:80 基本分+最多 20 效率分-撞牆扣分要拿高分:不能失敗、不能撞牆、少走冤枉路A2
3這是一個 Agent:感知 → 決定 → 行動;用 PEAS 描述任務先搞清楚環境是什麼樣子,才知道 Agent 需要什麼能力B1
4環境是部分可觀察的看不到全部,就必須自己記住看過的東西B2
5需要有記憶、有目標、會打分數的 Agent有了記憶的地圖,就能把問題寫成搜尋問題B3
6搜尋問題:起點、動作、轉移、目標、成本搜尋問題可以用 BFS、DFS、A* 解,但它們假設地圖已知B4、B5
7線上搜尋:邊走邊看;評分=競爭比要決定「下一個去探索哪裡」B6
8三個工具:記憶地圖、邊境、BFS有好幾個邊境時,要選一個C1
9比較四種選法(DFS、最近、偏好遠處、划算度)「終點通常很遠」這個線索讓划算度勝出C2
10最終版:分地盤 → 算划算度 → 選最高 → 承諾走向它;走太久就改去最近的(安全閥)要確認它真的不會出錯C3、C5
11保證:只走已知的路所以不撞牆;承諾+格子有限所以一定走到理論說得通,還要實際測C6
12驗證:公開 3/3、5,400 張模擬 0 失敗 0 撞牆、獨立審查可以交了D1、D2
13交件:改 zip 檔名、上傳 COOL—D3

D4.2 數字總表

項目數字
公開地圖3/3 成功、0 撞牆;步數 20/45/70(最短 16/45/26);平均 94.48 分
模擬地圖(終點偏遠,2,700 張)平均 91.24 分,0 失敗
模擬地圖(終點隨便放,2,700 張)平均 88.78 分,0 失敗
預估正式 9 張平均約 91.2(運氣差 88.4、運氣好 94.2;前提是終點放得遠)
比最基本的 DFS 多約 3.5 分(終點偏遠時)
步數上限最多用掉約 30%(審查員專門造的最刁鑽地圖是 29.7%;一般模擬地圖最多 27%)
程式長度230 行,只用 Python 內建的 deque

D4.3 如果只能記五句話

  1. 這份作業是 線上搜尋(online search):看不到地圖,只能邊走邊看。評分裡的效率分,就是 competitive ratio 的倒數(0 撞牆時剛好相等)。
  2. 環境是 部分可觀察的,所以 Agent 必須有 記憶(model-based),只看眼前的反射型會繞圈。
  3. 每一步找出所有 邊境(已知是路、旁邊還有未知格),用 BFS 在已知地圖上走最短路線過去;只走已知的路,所以 永遠不撞牆。
  4. 有好幾個邊境時,用 划算度=估計的寶藏分 ÷(步數 + 2) 挑一個(utility-based);因為終點通常離起點遠,遠處的未知格權重比較高。
  5. 選定之後 承諾走向它(它不再是邊境才換),避免來回踱步;再加上格子數有限,所以 一定找得到終點。走超過步數上限的 5% 就改去 最近的邊境(安全閥),讓最慘情況也離步數上限很遠。

E1 跟課本的對照表

結論:這份作業就是 Ch4 投影片「Online Searching Agents with Unknown Environments」(線上搜尋)的實作,評分裡的效率分就是投影片講的 competitive ratio(競爭比)倒過來。另外也用到 Ch2 的環境分類、Agent 種類,和 Ch3 的 BFS。

這一節是 B 部分的濃縮對照表,考前快速複習用。每個觀念的詳細說明在 B1~B6。

Ch4:線上搜尋(最直接相關)

出處:Chapter 4 Search in Complex Environments.pdf PDF 第 29~35 頁(投影片角落編號 56~63)

投影片說的白話在作業裡是什麼
offline search: compute a complete solution before setting foot in the real world離線搜尋:先把整條路想好,再出發不可能,因為看不到地圖
an online search agent interleaves computation and action線上搜尋:走一步、看一下、想一下、再走一步就是 act() 被一直呼叫的流程
a robot that is placed in a new building and must explore it機器人被放進一棟陌生建築,要邊走邊探索就是這份作業
The agent cannot determine RESULT(s,a) except by actually being in s and doing a不實際去做,就不知道結果不走到那一格旁邊,就不知道那格是什麼
compare this cost with the path cost ... if it knew the search space in advance. This is called the competitive ratio競爭比=「實際走的路」÷「如果事先知道地圖,最短要走的路」,越小越好作業的「效率」=最短 ÷ 實際移動步數,在 0 撞牆時剛好是競爭比倒過來
an online agent receives a percept ... it can augment its map每次收到感知,就補充自己的地圖就是 self.known 筆記本
an online algorithm better expands nodes in a local order. DFS has exactly this property線上搜尋要「就近」展開,DFS 剛好有這個性質C2 節的走法 A(DFS)。我們的最終版用 BFS 找路,可以直接「抄近路」走到遠處的邊境,所以比 DFS 好

Ch2:這是什麼樣的環境?

出處:Chapter 2 Intelligent Agents.pdf 第 11~14 頁

環境性質這份作業是為什麼
Fully vs. partially observablePartially observable(部分可觀察)只看得到上下左右四格
Single vs. multiagentSingle agent只有你一個在走
Deterministic vs. stochasticDeterministic(確定性)往右走就一定往右一格(或撞牆不動),沒有隨機
Episodic vs. sequentialSequential(序列式)現在走哪,會影響之後的位置和選擇
Static vs. dynamicStatic(靜態)你在想的時候,地圖不會變
Discrete vs. continuousDiscrete(離散)一格一格、四個方向
Known vs. unknown建議答 Unknown(有爭議)Ch4 把地圖事先未知的迷宮叫 unknown environment;但移動規則已知,所以也可以主張 known。詳見 B2 的提醒

Known vs. unknown 那一列是我依投影片定義("state of knowledge about the laws of physics of the environment")判斷的,老師沒有針對這份作業講過。

Ch2:這是哪一種 Agent?

出處:Chapter 2 Intelligent Agents.pdf 第 20~25 頁

Agent 種類投影片的定義我們的 Agent 有沒有
Model-basedkeeps track of the part of the world it can't see now; maintain some sort of internal state有:self.known 就是 internal state(內部狀態)
Goal-basedneeds some sort of goal information有:目標是走進 GOAL
Utility-basedchooses the action that maximizes the expected utility; handle the uncertainty inherent in stochastic or partially observable environments有:划算度就是效用,挑最高的

Ch3:搜尋演算法

出處:Chapter 3 Solving Problems by Searching.pdf BFS 第 13~15 頁、DFS 第 18~20 頁、A* 第 31~35 頁

E2 考試可能怎麼問

結論:下面是我根據投影片內容推測的題目(老師沒說會考這些)。題目用英文出(期末考是英文出題),可以用中文作答。答案收在摺疊裡,先自己想再打開。

Q1. What is the difference between offline search and online search?

看答案

離線搜尋:出發前先算出完整的解(整條路),再照著執行。需要事先知道整個環境。

線上搜尋:計算和行動交錯進行。先做一個動作、觀察結果、再算下一步。用在環境未知、必須邊走邊探索的情況(例如機器人在陌生建築裡)。

Q2. Define the competitive ratio.

看答案

Agent 實際走的路徑成本 ÷ 如果事先知道整個環境時的最短路徑成本。越小越好,最好是 1。

(作業的 efficiency = optimal / movement_steps。在 0 次撞牆時,它剛好是競爭比的倒數;老師的 movement_steps 不算撞牆,課本的路徑成本則每個動作都算。)

Q3. Describe the task environment of an agent exploring an unknown grid maze where it can only sense its four neighbors.

看答案

Partially observable(只看到四鄰)、single agent、deterministic、sequential、static、discrete;known/unknown 建議答 unknown(Ch4 把地圖未知的迷宮叫 unknown environment),並補一句「移動規則本身是已知的」。

Q4. Why does an online search agent prefer DFS-like local expansion over BFS-like expansion?

看答案

因為線上 Agent 要「實際走過去」才能展開一個節點。BFS 會在不同分支之間跳來跳去,每次跳都要實際走很遠的路。DFS 是就近展開,下一個節點通常就在旁邊。

補充(作業的心得):我們的 Agent 先記住地圖,再用 BFS 在已知地圖上找最短路線走到邊境。這不是「用 BFS 展開搜尋樹」,而是「用 BFS 算走路路線」,兩件事不一樣。

Q5. Why is the agent in HW2 a model-based agent?

看答案

因為環境是部分可觀察的,Agent 每次只看到四格。它必須維護一個內部狀態(記憶地圖 self.known),記住過去看過的東西,才能做出好的決定。只看當下感知的 simple reflex agent 會在迷宮裡打轉。

Q6. Can A* be used directly to find the goal in HW2? Why or why not?

看答案

不能直接用,有兩個原因:

1. A* 是離線演算法:它要在出發前,先在腦子裡展開節點、算出整條路。展開節點需要知道「做某個動作會到哪個狀態」(轉移模型),也就是要知道地圖。HW2 看不到地圖,Ch4 第 30 頁說線上 Agent "cannot determine RESULT(s,a) except by actually being in s and doing a"。

2. A* 的 h(n) 需要知道終點在哪。HW2 的終點一開始未知,只有走到它旁邊才會發現。

補充:Ch4 第 32 頁說,線上 Agent 如果知道終點位置,可以用曼哈頓距離當 heuristic 來引導探索。但這仍然是線上搜尋,不是直接跑 A*。

Q7. 你的 Agent 怎麼保證不會撞牆?怎麼保證不會無限繞圈?

看答案

不撞牆:只沿著「已知是 FREE」的格子規劃路線,走之前已經知道下一格不是牆。

一定找得到終點:(1) 還沒找到終點時,一定還有邊境可以去;(2) 選定目標後一直走向它,直到它不再是邊境才換,每走一步離目標近一步;(3) 每換一次目標,至少有一個未知格變成已知,格子數有限,所以終點一定會被發現。

要誠實補充:這證明了「有限步內會找到」,但沒有證明「一定在步數上限內」,那一點是靠大量實測驗證的。

Q8. 你的探索策略是什麼?為什麼比 DFS 好?

看答案

邊境探索+效用選擇:每一步找出所有「已知可走、旁邊還有未知」的格子(邊境),估計每個邊境後面未知區域藏有終點的可能性(離起點越遠越可能),除以走過去的步數,選最高的。

比 DFS 好的原因:DFS 走到死路要一格一格原路退回;我們可以用 BFS 直接走最短路線到任何一個邊境,而且優先去比較可能有終點的方向。模擬考中,終點偏遠時多 3.56 分;終點隨機時只多 0.64 分。

E3 名詞小字典

結論:這份作業會遇到的詞,一行一個白話。

名詞白話
Agent任何「透過感應器感知環境、透過執行器做出動作」的東西(Ch2 第 2 頁),不一定是程式。這份作業裡,它是一個程式,就是那個蒙眼走迷宮的人。
Percept(感知)Agent 每一步收到的資訊:我在哪、四個鄰居是什麼。
Grid map(格子地圖)像圍棋盤一樣,由一格一格組成的地圖。
Frontier(邊境)已知是路、旁邊還有未知格的格子。站上去才看得到新東西。
BFS(廣度優先搜尋)像漣漪一圈一圈往外擴,能找到最短路線。
DFS(深度優先搜尋)一條路走到底,走不通再退回來換一條。
A*用 f(n) = g(n) + h(n)(已走的成本+估計還要走的成本)排優先順序的搜尋,需要知道終點在哪。
Heuristic(啟發式)一個「大概估計」,例如「離終點大約還有幾步」。
Manhattan distance(曼哈頓距離)只能橫著、直著走時的格數:上下差幾格+左右差幾格。
Multi-source BFS(多起點 BFS)同時從好幾個起點一起擴散的 BFS。我們用它來分地盤。
Utility(效用)對一個選擇打的分數,越高越想選。這裡是划算度。
Competitive ratio(競爭比)實際走的路 ÷ 事先知道地圖時的最短路。越接近 1 越好。
Online search(線上搜尋)邊走邊想,走一步看一步。
Partially observable(部分可觀察)Agent 一次只看得到環境的一部分。
Internal state(內部狀態)Agent 自己的記憶,例如記憶地圖。
dequePython 內建的排隊隊伍,頭尾都能快速放進拿出。BFS 用它來排隊。
try / exceptPython 的「試著做,出錯就改做備案」。
安全閥我們程式裡的保險:走超過步數上限的 5% 後,不再偏好遠處,改去最近的邊境,避免步數用完。

本報告的每一項判斷都來自實際查證(讀老師的 README 與程式、實際執行公開測試與模擬考、對照課程投影片原文)。標明「推測」的部分(終點放遠是老師的出題習慣、考試可能怎麼問、Known vs. unknown 的歸類)不是老師講的。