シリーズ: Angelia
java
611 行
· 更新日 2026-08-13
Mountain.java
Angelia/Java/Lab_3/code/Mountain.java
import java.util.Arrays;
import java.util.HashSet;
/**
* Mountain.java —— Lab 3: Mountain Climbing(登山實驗)完整參考實作
* 課程:COMP1110/1140/6710 — Structured Programming
*
* ─────────────────────────────────────────────
* 情境說明
* ─────────────────────────────────────────────
* 你是一位在「數位山脈」中工作的測量員。
* 地形用一張高度圖(heightmap)表示:一個 2D 整數陣列,
* heightmap[y][x] 就是座標 (y, x) 的海拔高度。
* 地圖中存在唯一的「黃金山頂」:全地圖的最高點。
*
* 本檔案依照講義完成三個活動:
* Activity 1: lookAround —— 觀察四周,回傳最高鄰居的方向
* Activity 2: climb —— 從 [0][0] 一步一步爬到山頂
* Activity 3: fastTravel —— 用「矩形二分搜尋」快速找到山頂
*
* ─────────────────────────────────────────────
* 編譯與執行(-ea 會啟用 assert,測試才會真正生效!)
* ─────────────────────────────────────────────
* javac Direction.java Mountain.java
* java -ea Mountain
*/
public class Mountain {
// ================================================================
// 常數與共用輔助函式
// ================================================================
/**
* 「界外」的高度值。
*
* 講義說:「地圖邊界之外的高度,可以當成 0,
* 或某個非常低的數字。」
* 我們選擇 Integer.MIN_VALUE(int 的最小值,約 -21 億),
* 這樣即使地圖上真的出現 0 或負數高度,
* 界外也永遠不可能被誤認成「最高的鄰居」。
*/
static final int OUT_OF_BOUNDS = Integer.MIN_VALUE;
/**
* 安全地讀取座標 (y, x) 的高度;
* 若座標超出地圖範圍,回傳 OUT_OF_BOUNDS。
*
* 這個小函式是本作業「邊界處理」的核心技巧:
* 與其在 lookAround 裡寫四段重複的 if 邊界判斷,
* 不如把「安全讀取」包成一個函式,讓主要邏輯保持乾淨。
* 這種把麻煩細節包起來的做法,叫做「抽象化」(abstraction)。
*
* 前置條件:heightmap 是非空的矩形陣列(見 isRectangular)。
* 後置條件:
* - (y, x) 在地圖內 → 回傳 heightmap[y][x]
* - (y, x) 在地圖外 → 回傳 OUT_OF_BOUNDS
*/
static int heightAt(int y, int x, int[][] heightmap) {
// y 必須是合法的「列」索引:0 <= y < 列數
boolean insideRows = 0 <= y && y < heightmap.length;
// x 必須是合法的「行」索引:0 <= x < 行數
boolean insideCols = 0 <= x && x < heightmap[0].length;
if (!insideRows || !insideCols) {
return OUT_OF_BOUNDS; // 界外:回傳「超級低」的高度
}
return heightmap[y][x]; // 界內:正常讀取
}
/**
* 檢查 heightmap 是否為「非空的矩形」2D 陣列。
*
* Java 的 2D 陣列其實是「陣列的陣列」,
* 每一列的長度可以不同(稱為 ragged array,鋸齒狀陣列),
* 所以「是不是矩形」需要自己檢查。
* 這個函式只拿來寫前置條件的 assert 用。
*/
static boolean isRectangular(int[][] map) {
if (map == null || map.length == 0) return false; // 沒有任何列
if (map[0] == null || map[0].length == 0) return false; // 第一列是空的
for (int[] row : map) {
// 每一列都必須存在,而且長度要跟第一列一樣
if (row == null || row.length != map[0].length) return false;
}
return true;
}
// ================================================================
// Activity 1: lookAround —— 觀察四周
// ================================================================
/**
* 觀察座標 (y, x) 的四個鄰居,決定該往哪個方向走。
*
* 規則:
* - 比較北、東、南、西四個鄰居的高度,
* 回傳「最高鄰居」所在的方向。
* - 如果目前的格子比四個鄰居都「嚴格更高」,
* 回傳 Direction.PEAK(你就是山頂)。
* - 超出地圖邊界的鄰居視為高度 OUT_OF_BOUNDS(超級低),
* 所以永遠不會勝出。
*
* 打平規則(tie-breaking):
* 若有多個鄰居同為最高,依 北 → 東 → 南 → 西 的順序取第一個。
* (Mountain Lab 產生的有效地圖,沿途不會出現這種情形,
* 但寫程式時仍要讓行為「明確、可預測」。)
*
* 前置條件(precondition):
* 1. heightmap 是非空的矩形 2D 陣列。
* 2. 0 <= y < 列數,0 <= x < 行數(座標必須在地圖內)。
*
* 後置條件(postcondition):
* - 回傳 PEAK 時:heightmap[y][x] 嚴格大於四個鄰居的高度。
* - 回傳其他方向時:該方向鄰居的高度是四個鄰居中的最大值,
* 且目前格子的高度 <= 該鄰居的高度
* (也就是:目前格子並沒有嚴格高於所有鄰居)。
*/
static Direction lookAround(int y, int x, int[][] heightmap) {
// ---- 前置條件檢查(執行時要加 -ea 才會生效)----
assert isRectangular(heightmap)
: "前置條件違反:heightmap 必須是非空的矩形陣列";
assert 0 <= y && y < heightmap.length
: "前置條件違反:y=" + y + " 超出列範圍";
assert 0 <= x && x < heightmap[0].length
: "前置條件違反:x=" + x + " 超出行範圍";
// ---- 讀取自己與四個鄰居的高度 ----
int current = heightmap[y][x]; // 腳下的高度
int north = heightAt(y - 1, x, heightmap); // 北:上一列
int east = heightAt(y, x + 1, heightmap); // 東:右一行
int south = heightAt(y + 1, x, heightmap); // 南:下一列
int west = heightAt(y, x - 1, heightmap); // 西:左一行
// ---- 找出四個鄰居中的最大高度 ----
// Math.max 一次只能比兩個數,所以用巢狀呼叫比出四個的最大值。
int best = Math.max(Math.max(north, east), Math.max(south, west));
// ---- 判斷結果 ----
if (current > best) {
return Direction.PEAK; // 我比所有鄰居都高:山頂!
}
// 依「北 → 東 → 南 → 西」的優先順序,回傳第一個最高的鄰居。
if (north == best) return Direction.NORTH;
if (east == best) return Direction.EAST;
if (south == best) return Direction.SOUTH;
return Direction.WEST; // 剩下的只可能是西
}
// ================================================================
// Activity 2: climb —— 一步一步爬到山頂
// ================================================================
/** 僅供測試/示範用:記錄最近一次 climb 花了幾步。 */
static int lastClimbSteps = -1;
/**
* 從 [0][0] 出發,每一步都走向「最高的鄰居」,
* 直到 lookAround 回報 PEAK 為止。這種策略叫做
* 「貪婪爬坡法」(greedy hill climbing)。
*
* 為什麼這樣一定會到達山頂?
* 因為 Mountain Lab 產生的地圖保證:
* 「局部最佳就是全域最佳」——lookAround 給的方向
* 永遠讓你嚴格更接近山頂,絕不會把你帶遠。
* 每走一步,腳下的高度嚴格上升,所以不可能繞圈子,
* 也不可能走回頭路,最後一定停在山頂。
*
* 前置條件:
* heightmap 是非空的矩形 2D 陣列,
* 且為「有效地圖」:唯一山頂、沿途方向必定指向山頂。
* 後置條件:
* 回傳長度為 2 的新陣列 {y, x}:山頂的座標,
* 且 lookAround(y, x, heightmap) == PEAK。
*
* @return 山頂座標 {y, x}
*/
static int[] climb(int[][] heightmap) {
// ---- 前置條件 ----
assert isRectangular(heightmap)
: "前置條件違反:heightmap 必須是非空的矩形陣列";
int y = 0; // 目前位置:從左上角出發
int x = 0;
int steps = 0; // 已經走了幾步
// 先看看四周;只要還不是山頂,就繼續走
Direction dir = lookAround(y, x, heightmap);
while (dir != Direction.PEAK) {
// 依照方向更新座標。
// 這裡用 Java 14+ 的「箭頭 switch」:
// 每個分支互相獨立,不會像傳統 switch 忘了 break 而「掉落」。
switch (dir) {
case NORTH -> y--; // 北:列索引減 1(往上)
case EAST -> x++; // 東:行索引加 1(往右)
case SOUTH -> y++; // 南:列索引加 1(往下)
case WEST -> x--; // 西:行索引減 1(往左)
case PEAK -> { } // 不可能到這裡(while 已排除)
}
steps++;
// 防禦性檢查:有效地圖上,路徑高度嚴格上升,
// 走的步數絕不可能超過格子總數。
// 若超過,代表地圖無效(例如有高原),與其無窮迴圈,
// 不如讓 assert 立刻失敗、告訴我們哪裡出問題。
assert steps <= heightmap.length * heightmap[0].length
: "步數超過格子總數:這張地圖大概不是有效的單峰地圖";
dir = lookAround(y, x, heightmap); // 到新位置再看一次四周
}
// 依講義要求:印出總步數
System.out.println("'climb' found the peak in " + steps + " steps!");
lastClimbSteps = steps; // 記下來,讓 main 的測試可以驗證
// ---- 後置條件:回傳的座標必須真的是山頂 ----
assert lookAround(y, x, heightmap) == Direction.PEAK
: "後置條件違反:climb 停下來的位置不是山頂";
return new int[]{y, x}; // 打包成長度 2 的新陣列 {y, x}
}
// ================================================================
// Activity 3: fastTravel —— 矩形二分搜尋
// ================================================================
/** 僅供測試/示範用:記錄最近一次 fastTravel 探測了幾次。 */
static int lastFastTravelSteps = -1;
/**
* 用「2D 版的二分搜尋」快速找到山頂。
*
* 核心想法:
* 我們隨時維護一個「山頂必定在裡面」的矩形搜尋範圍
* [top..bottom] × [left..right](含邊界)。
* 每一輪:
* 1. 取矩形的中心點 (midY, midX),呼叫 lookAround。
* 2. 若回傳 PEAK:直接找到山頂,結束。
* 3. 否則,方向告訴我們山頂在中心點的哪一側:
* - NORTH → 山頂的列 < midY,砍掉下半部(bottom = midY - 1)
* - SOUTH → 山頂的列 > midY,砍掉上半部(top = midY + 1)
* - EAST → 山頂的行 > midX,砍掉左半部(left = midX + 1)
* - WEST → 山頂的行 < midX,砍掉右半部(right = midX - 1)
* 每一輪都把矩形的某一邊縮小約一半,
* 所以步數是 O(log m + log n),遠小於 climb 的 O(m + n)。
*
* 為什麼「方向」可以拿來砍範圍?
* 有效地圖保證 lookAround 的方向讓你「嚴格更接近山頂」。
* 如果往北走會更接近,山頂的列號必定小於現在的列號——
* 所以山頂絕不可能在中心點那一列或更南邊,可以安心砍掉。
*
* 前置條件:同 climb。
* 後置條件:同 climb —— 回傳山頂座標 {y, x}。
*
* @return 山頂座標 {y, x}
*/
static int[] fastTravel(int[][] heightmap) {
// ---- 前置條件 ----
assert isRectangular(heightmap)
: "前置條件違反:heightmap 必須是非空的矩形陣列";
// 搜尋範圍:一開始是整張地圖(注意都是「含」邊界的索引)
int top = 0; // 最上面可能的列
int bottom = heightmap.length - 1; // 最下面可能的列
int left = 0; // 最左邊可能的行
int right = heightmap[0].length - 1; // 最右邊可能的行
int steps = 0; // 探測(probe)了幾次
while (true) {
// 取搜尋矩形的中心點(整數除法自動向下取整)
int midY = (top + bottom) / 2;
int midX = (left + right) / 2;
steps++;
Direction dir = lookAround(midY, midX, heightmap);
if (dir == Direction.PEAK) {
// 依講義要求:印出總步數
System.out.println(
"'fastTravel' found the peak in " + steps + " steps!");
lastFastTravelSteps = steps;
return new int[]{midY, midX};
}
// 根據方向,砍掉「山頂不可能在」的那半邊
switch (dir) {
case NORTH -> bottom = midY - 1; // 山頂在更北:往上縮
case SOUTH -> top = midY + 1; // 山頂在更南:往下縮
case EAST -> left = midX + 1; // 山頂在更東:往右縮
case WEST -> right = midX - 1; // 山頂在更西:往左縮
case PEAK -> { } // 不可能(上面已 return)
}
// 迴圈不變量(loop invariant):
// 山頂永遠在 [top..bottom] × [left..right] 這個矩形裡。
// 若矩形變成空的,代表地圖違反了前置條件。
assert top <= bottom && left <= right
: "搜尋矩形變成空的:這張地圖大概不是有效的單峰地圖";
}
}
// ================================================================
// 測試輔助工具
// ================================================================
/**
* 產生一張「錐形」測試地圖:
* 以 (peakY, peakX) 為山頂,高度隨距離遞減——
* 高度 = base - 3 * |y - peakY| - 2 * |x - peakX|
*
* 兩個方向刻意用「不同」的權重(3 與 2),
* 這樣任何一格的四個鄰居永遠不會同高(不會打平),
* 每一格的正確答案就唯一,方便寫測試。
*
* 這種地圖是有效的單峰地圖:
* 任何位置往最高鄰居走,都嚴格更接近山頂。
*/
static int[][] coneMap(int rows, int cols, int peakY, int peakX) {
int[][] map = new int[rows][cols];
int base = 3 * rows + 2 * cols; // 讓所有高度都是正數
for (int y = 0; y < rows; y++) {
for (int x = 0; x < cols; x++) {
map[y][x] = base - 3 * Math.abs(y - peakY)
- 2 * Math.abs(x - peakX);
}
}
return map;
}
/**
* 錐形地圖上,座標 (y, x) 的「理論正確答案」。
*
* 因為列方向權重(3)大於行方向權重(2):
* - 只要列還沒對齊山頂(y != peakY),
* 南北方向的高度差 ±3 一定壓過東西的 ±2,
* 所以答案必是 NORTH 或 SOUTH。
* - 列對齊之後才輪到東西方向。
* - 兩者都對齊,就是 PEAK。
* 有了這個「封閉公式」,我們就能窮舉地圖上每一格,
* 把 lookAround 的輸出跟理論答案逐一比對。
*/
static Direction expectedOnCone(int y, int x, int peakY, int peakX) {
if (y == peakY && x == peakX) return Direction.PEAK;
if (y != peakY) {
return (peakY < y) ? Direction.NORTH : Direction.SOUTH;
}
return (peakX > x) ? Direction.EAST : Direction.WEST;
}
/** 計算 ceil(log2(n)),用來檢驗 fastTravel 的步數上限。 */
static int ceilLog2(int n) {
if (n <= 1) return 0;
// numberOfLeadingZeros:int 的 32 位元中,開頭有幾個 0。
// 32 - nlz(n - 1) 剛好就是 ceil(log2(n))。
return 32 - Integer.numberOfLeadingZeros(n - 1);
}
// ================================================================
// 測試(全部用 assert;請以 java -ea Mountain 執行)
// ================================================================
/** 測試 1:講義裡的 3×3 範例地圖。 */
static void testLookAroundOnLabExample() {
int[][] smallMap = {
{10, 20, 10}, // Row 0
{20, 50, 20}, // Row 1
{10, 20, 10} // Row 2
};
// 中央 50 比四周(20,20,20,20)都高 → PEAK
assert lookAround(1, 1, smallMap) == Direction.PEAK;
// 四個「邊」的中點:最高鄰居都是中央的 50
assert lookAround(0, 1, smallMap) == Direction.SOUTH; // 下方 50 最高
assert lookAround(1, 0, smallMap) == Direction.EAST; // 右方 50 最高
assert lookAround(2, 1, smallMap) == Direction.NORTH; // 上方 50 最高
assert lookAround(1, 2, smallMap) == Direction.WEST; // 左方 50 最高
// 角落 (0,0):東(20)與南(20)同高「打平」。
// 依我們文件化的打平規則(北→東→南→西),應回傳 EAST。
assert lookAround(0, 0, smallMap) == Direction.EAST;
System.out.println("[1] lookAround:3x3 講義範例地圖 ... 通過");
}
/** 測試 2:手工設計的 3×4 地圖,全部 12 格逐一驗證(完整分支覆蓋)。 */
static void testLookAroundFullBranchCoverage() {
// 山頂在 (1, 2) 的 90。所有相鄰高度都不同,答案唯一。
int[][] map = {
{ 5, 30, 40, 35}, // Row 0
{10, 50, 90, 60}, // Row 1
{ 8, 20, 45, 30} // Row 2
};
// ---- Row 0(上邊界:北邊是界外)----
assert lookAround(0, 0, map) == Direction.EAST; // 角落:東30 > 南10
assert lookAround(0, 1, map) == Direction.SOUTH; // 南50 最高
assert lookAround(0, 2, map) == Direction.SOUTH; // 南90 最高
assert lookAround(0, 3, map) == Direction.SOUTH; // 角落:南60 > 西40
// ---- Row 1 ----
assert lookAround(1, 0, map) == Direction.EAST; // 左邊界:東50 最高
assert lookAround(1, 1, map) == Direction.EAST; // 東90 最高
assert lookAround(1, 2, map) == Direction.PEAK; // 山頂本人!
assert lookAround(1, 3, map) == Direction.WEST; // 右邊界:西90 最高
// ---- Row 2(下邊界:南邊是界外)----
assert lookAround(2, 0, map) == Direction.EAST; // 角落:東20 > 北10
assert lookAround(2, 1, map) == Direction.NORTH; // 北50 最高
assert lookAround(2, 2, map) == Direction.NORTH; // 北90 最高
assert lookAround(2, 3, map) == Direction.NORTH; // 角落:北60 > 西45
// 五種 Direction 值(含 PEAK)都至少被回傳一次 → 完整分支覆蓋
System.out.println("[2] lookAround:3x4 地圖 12 格全驗證(分支覆蓋)... 通過");
}
/** 測試 3:退化地圖——1×1、單列、單行。邊界處理最容易出錯的地方。 */
static void testLookAroundDegenerateMaps() {
// 1×1 地圖:四個鄰居全是界外 → 自己就是山頂
int[][] tiny = {{42}};
assert lookAround(0, 0, tiny) == Direction.PEAK;
// 單列地圖(1×5):只能往東西走
int[][] oneRow = {{10, 20, 30, 40, 25}};
assert lookAround(0, 0, oneRow) == Direction.EAST; // 20 在右邊
assert lookAround(0, 3, oneRow) == Direction.PEAK; // 40 是山頂
assert lookAround(0, 4, oneRow) == Direction.WEST; // 40 在左邊
// 單行地圖(5×1):只能往南北走
int[][] oneCol = {{10}, {20}, {30}, {40}, {25}};
assert lookAround(0, 0, oneCol) == Direction.SOUTH; // 20 在下面
assert lookAround(3, 0, oneCol) == Direction.PEAK; // 40 是山頂
assert lookAround(4, 0, oneCol) == Direction.NORTH; // 40 在上面
System.out.println("[3] lookAround:1x1/單列/單行 退化地圖 ... 通過");
}
/** 測試 4:在多張錐形地圖上「窮舉」每一格,逐一比對理論答案。 */
static void testLookAroundExhaustivelyOnConeMaps() {
int[][] shapes = {{1, 1}, {1, 8}, {8, 1}, {5, 5}, {9, 7}, {12, 24}};
int checked = 0;
for (int[] shape : shapes) {
int rows = shape[0];
int cols = shape[1];
// 讓山頂輪流出現在地圖的「每一個」位置
for (int peakY = 0; peakY < rows; peakY++) {
for (int peakX = 0; peakX < cols; peakX++) {
int[][] map = coneMap(rows, cols, peakY, peakX);
// 再對地圖上的「每一格」比對答案
for (int y = 0; y < rows; y++) {
for (int x = 0; x < cols; x++) {
Direction got = lookAround(y, x, map);
Direction want = expectedOnCone(y, x, peakY, peakX);
assert got == want
: "在 " + rows + "x" + cols + " 地圖(山頂 ("
+ peakY + "," + peakX + "))的座標 ("
+ y + "," + x + "):期望 " + want
+ ",卻得到 " + got;
checked++;
}
}
}
}
}
System.out.println("[4] lookAround:錐形地圖窮舉測試,共比對 "
+ checked + " 個座標 ... 通過");
}
/** 測試 5:climb 的手工小地圖測試(山頂在不同角落)。 */
static void testClimbOnHandCraftedMaps() {
System.out.println("[5] climb:手工地圖測試");
// (a) 山頂就在出發點 [0][0]:應該 0 步完成
int[][] cornerPeak = {
{99, 80, 60},
{85, 70, 50},
{62, 55, 40}
};
int[] p1 = climb(cornerPeak);
assert Arrays.equals(p1, new int[]{0, 0})
: "山頂應在 (0,0),卻回傳 " + Arrays.toString(p1);
assert lastClimbSteps == 0 : "從山頂出發應該走 0 步";
// (b) 山頂在對角(右下角):距離出發點最遠
int[][] farPeak = {
{ 5, 12, 20},
{14, 30, 44},
{22, 48, 77}
};
int[] p2 = climb(farPeak);
assert Arrays.equals(p2, new int[]{2, 2})
: "山頂應在 (2,2),卻回傳 " + Arrays.toString(p2);
// 有效地圖上每一步都嚴格接近山頂,
// 所以步數 = 曼哈頓距離 |2-0| + |2-0| = 4
assert lastClimbSteps == 4 : "步數應為 4,卻是 " + lastClimbSteps;
// (c) 講義的 3×3 範例:山頂在正中央
int[][] smallMap = {
{10, 20, 10},
{20, 50, 20},
{10, 20, 10}
};
int[] p3 = climb(smallMap);
assert Arrays.equals(p3, new int[]{1, 1})
: "山頂應在 (1,1),卻回傳 " + Arrays.toString(p3);
assert lastClimbSteps == 2 : "步數應為 2,卻是 " + lastClimbSteps;
System.out.println(" 手工地圖全部通過");
}
/** 測試 6:climb 與 fastTravel 在各種尺寸、各種山頂位置的錐形地圖上。 */
static void testClimbAndFastTravelOnConeMaps() {
System.out.println("[6] climb + fastTravel:錐形地圖系統化測試");
int[][] shapes = {{1, 1}, {1, 9}, {9, 1}, {5, 8}, {24, 12}};
for (int[] shape : shapes) {
int rows = shape[0];
int cols = shape[1];
// 測試的山頂位置:四個角落 + 正中央(用 HashSet 去掉重複)
int[][] peaks = {
{0, 0}, {0, cols - 1},
{rows - 1, 0}, {rows - 1, cols - 1},
{rows / 2, cols / 2}
};
HashSet<String> seen = new HashSet<>();
for (int[] pk : peaks) {
int peakY = pk[0];
int peakX = pk[1];
if (!seen.add(peakY + "," + peakX)) continue; // 跳過重複位置
int[][] map = coneMap(rows, cols, peakY, peakX);
System.out.println(" 地圖 " + rows + "x" + cols
+ ",山頂在 (" + peakY + "," + peakX + "):");
// ---- climb ----
System.out.print(" ");
int[] got = climb(map);
assert Arrays.equals(got, new int[]{peakY, peakX})
: "climb 找錯山頂:" + Arrays.toString(got);
// 步數應等於從 (0,0) 到山頂的曼哈頓距離
assert lastClimbSteps == peakY + peakX
: "climb 步數應為 " + (peakY + peakX)
+ ",卻是 " + lastClimbSteps;
// ---- fastTravel ----
System.out.print(" ");
int[] got2 = fastTravel(map);
assert Arrays.equals(got2, new int[]{peakY, peakX})
: "fastTravel 找錯山頂:" + Arrays.toString(got2);
// 步數上限:每次探測至少砍掉某一邊的一半
int bound = ceilLog2(rows) + ceilLog2(cols) + 2;
assert lastFastTravelSteps <= bound
: "fastTravel 步數 " + lastFastTravelSteps
+ " 超過理論上限 " + bound;
// ---- 兩種方法必須找到同一個山頂 ----
assert Arrays.equals(got, got2)
: "climb 與 fastTravel 的答案不一致!";
}
}
System.out.println(" 系統化測試全部通過");
}
/** 示範:在大地圖上比較 climb 與 fastTravel 的步數差距。 */
static void demoSpeedComparison() {
System.out.println("[7] 步數比較:128x128 地圖,山頂在 (100, 111)");
int[][] big = coneMap(128, 128, 100, 111);
System.out.print(" ");
int[] a = climb(big); // 預期 100 + 111 = 211 步
System.out.print(" ");
int[] b = fastTravel(big); // 預期最多 7 + 7 + 2 = 16 次探測
assert Arrays.equals(a, b);
assert lastClimbSteps == 211;
assert lastFastTravelSteps <= 16;
System.out.println(" 同一座山,climb 走了 " + lastClimbSteps
+ " 步;fastTravel 只探測 " + lastFastTravelSteps + " 次!");
}
// ================================================================
// main:依序執行所有測試
// ================================================================
public static void main(String[] args) {
// 小技巧:故意利用 assert 的「副作用」偵測 -ea 是否開啟。
// 若 assert 沒啟用,這行不會執行,assertionsEnabled 維持 false。
boolean assertionsEnabled = false;
assert assertionsEnabled = true; // 啟用時才會把它設成 true
if (!assertionsEnabled) {
System.out.println("【警告】assert 未啟用!以下測試不會真正檢查。");
System.out.println(" 請改用:java -ea Mountain");
}
System.out.println("=== Lab 3: Mountain Climbing — 完整測試 ===");
testLookAroundOnLabExample(); // Activity 1
testLookAroundFullBranchCoverage(); // Activity 1
testLookAroundDegenerateMaps(); // Activity 1
testLookAroundExhaustivelyOnConeMaps();// Activity 1
testClimbOnHandCraftedMaps(); // Activity 2
testClimbAndFastTravelOnConeMaps(); // Activity 2 + 3
demoSpeedComparison(); // Activity 2 vs 3
System.out.println();
System.out.println("=== 全部測試通過! ===");
}
}
関連記事
Angelia
haskell
EN
更新日 2026-02-03
Adding.hs
Adding.hs — haskell source code from the Angelia learning materials (Angelia/Haskell/Adding.hs).
記事を読む →
Angelia
haskell
EN
更新日 2026-02-03
Area.hs
Area.hs — haskell source code from the Angelia learning materials (Angelia/Haskell/Area.hs).
記事を読む →
Angelia
haskell
EN
更新日 2026-02-03
Chess.hs
Chess.hs — haskell source code from the Angelia learning materials (Angelia/Haskell/Chess.hs).
記事を読む →
Angelia
haskell
EN
更新日 2026-02-03
ChessGraphics.hs
ChessGraphics.hs — haskell source code from the Angelia learning materials (Angelia/Haskell/ChessGraphics.hs).
記事を読む →
Angelia
haskell
EN
更新日 2026-02-03
Examples.hs
Examples.hs — haskell source code from the Angelia learning materials (Angelia/Haskell/Examples.hs).
記事を読む →
Angelia
haskell
EN
更新日 2026-02-03
Fibonacci.hs
Fibonacci.hs — haskell source code from the Angelia learning materials (Angelia/Haskell/Fibonacci.hs).
記事を読む →