steveyehtw commited on
Commit
05345c6
·
1 Parent(s): e2da85d

Add week03 branching mystery template

Browse files
classroom_data/course.json CHANGED
@@ -1,7 +1,7 @@
1
  {"course_id":"2026Fall","title":"2026 Fall 進階專題","demo_class":"2026Fall進階專題","weeks":[
2
  {"id":"week01","week":1,"title":"任務管理器開發挑戰","project":"能力診斷","output":"文字版任務管理器","template":true},
3
  {"id":"week02","week":2,"title":"消失的校徽","project":"校園謎案調查局","output":"探索地點與收集線索的文字案件","template":true},
4
- {"id":"week03","week":3,"title":"線索會改變世界","project":"校園謎案調查局","output":"條件解鎖與多分支案件","template":false},
5
  {"id":"week04","week":4,"title":"把文字案件變成可點的現場","project":"校園謎案調查局","output":"Pygame 互動現場","template":false},
6
  {"id":"week05","week":5,"title":"案件發布日","project":"校園謎案調查局","output":"原創短案件與線上展示","template":false},
7
  {"id":"week06","week":6,"title":"領養一顆神祕蛋","project":"電子寵物進化實驗室","output":"可餵食、玩耍、休息的寵物原型","template":false},
 
1
  {"course_id":"2026Fall","title":"2026 Fall 進階專題","demo_class":"2026Fall進階專題","weeks":[
2
  {"id":"week01","week":1,"title":"任務管理器開發挑戰","project":"能力診斷","output":"文字版任務管理器","template":true},
3
  {"id":"week02","week":2,"title":"消失的校徽","project":"校園謎案調查局","output":"探索地點與收集線索的文字案件","template":true},
4
+ {"id":"week03","week":3,"title":"線索會改變世界","project":"校園謎案調查局","output":"條件解鎖、變化證詞與多結局案件","template":true},
5
  {"id":"week04","week":4,"title":"把文字案件變成可點的現場","project":"校園謎案調查局","output":"Pygame 互動現場","template":false},
6
  {"id":"week05","week":5,"title":"案件發布日","project":"校園謎案調查局","output":"原創短案件與線上展示","template":false},
7
  {"id":"week06","week":6,"title":"領養一顆神祕蛋","project":"電子寵物進化實驗室","output":"可餵食、玩耍、休息的寵物原型","template":false},
classroom_data/templates/week03/case_data.json ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "title": "午夜廣播室",
3
+ "intro": "放學後,校內廣播突然播放一段求救訊息,卻沒有人承認進過廣播室。",
4
+ "actions": {
5
+ "1": {
6
+ "name": "搜索導師桌",
7
+ "before": "抽屜裡有一張被塗改的廣播值日表。",
8
+ "after": "抽屜已經檢查過了。",
9
+ "changes_after": "duty_list",
10
+ "gives": {"id": "duty_list", "name": "被塗改的廣播值日表"}
11
+ },
12
+ "2": {
13
+ "name": "詢問工友",
14
+ "before": "工友搖搖頭:我只看到有人往舊校舍走。",
15
+ "after": "看到值日表後,工友改口:我撿到一張紙條,上面寫著門禁碼 0426。",
16
+ "changes_after": "duty_list",
17
+ "gives_requires": ["duty_list"],
18
+ "gives": {"id": "door_code", "name": "廣播室門禁碼 0426"}
19
+ },
20
+ "3": {
21
+ "name": "進入廣播室",
22
+ "requires": ["door_code"],
23
+ "locked_text": "門鎖需要四位數密碼。也許有人知道密碼的下落。",
24
+ "before": "控制台旁有一台仍在播放的錄音機。",
25
+ "after": "錄音機已經檢查過了。",
26
+ "changes_after": "recording",
27
+ "gives": {"id": "recording", "name": "剪接過的求救錄音"}
28
+ }
29
+ },
30
+ "endings": {
31
+ "1": {
32
+ "label": "指控值日生惡作劇",
33
+ "requires": ["recording"],
34
+ "text": "【誤判結局】錄音雖然是剪接的,但值日表被塗改,不能證明值日生真的進過廣播室。"
35
+ },
36
+ "2": {
37
+ "label": "判斷這是預演錄音誤播",
38
+ "requires": ["duty_list", "door_code", "recording"],
39
+ "text": "【真相結局】求救聲是校慶戲劇預演錄音;工友誤觸了排程播放,怕被責怪才隱瞞紙條。案件破解!"
40
+ }
41
+ }
42
+ }
classroom_data/templates/week03/main.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Week 3:線索會改變世界 Starter。
2
+
3
+ 先完成 TODO 1~4。你會讓線索解鎖行動、改變證詞,並產生不同結局。
4
+ """
5
+
6
+ import json
7
+ from pathlib import Path
8
+
9
+ CASE_FILE = Path(__file__).with_name("case_data.json")
10
+
11
+
12
+ def load_case():
13
+ with CASE_FILE.open("r", encoding="utf-8") as file:
14
+ return json.load(file)
15
+
16
+
17
+ def requirements_met(required_ids, clues):
18
+ """所有 required_ids 都已取得時回傳 True。"""
19
+ # TODO 1:可使用 all(required_id in clues for required_id in required_ids)
20
+ return True
21
+
22
+
23
+ def choose_text(action, clues):
24
+ """依目前線索,選擇行動前或行動後的文字。"""
25
+ # TODO 2:若 changes_after 已在 clues,回傳 after;否則回傳 before。
26
+ return action["before"]
27
+
28
+
29
+ def perform_action(action, clues):
30
+ """檢查前置條件、顯示內容並加入新線索。"""
31
+ required_ids = action.get("requires", [])
32
+ # TODO 3:條件不足時顯示 locked_text 並直接 return。
33
+
34
+ print(f"\n{choose_text(action, clues)}")
35
+ clue = action.get("gives")
36
+ can_give = requirements_met(action.get("gives_requires", []), clues)
37
+ # TODO 4:clue 存在、can_give 且尚未取得時,加入 clue["id"] 並顯示名稱。
38
+
39
+
40
+ def show_clues(clues, case):
41
+ print("\n【調查狀態】")
42
+ if not clues:
43
+ print("尚未取得任何線索。")
44
+ return
45
+ names = {
46
+ action["gives"]["id"]: action["gives"]["name"]
47
+ for action in case["actions"].values()
48
+ if action.get("gives")
49
+ }
50
+ for number, clue_id in enumerate(clues, start=1):
51
+ print(f"{number}. {names.get(clue_id, clue_id)}")
52
+
53
+
54
+ def choose_ending(case, clues):
55
+ print("\n【結案判斷】")
56
+ for ending_id, ending in case["endings"].items():
57
+ print(f"{ending_id}. {ending['label']}")
58
+ choice = input("你的判斷:").strip()
59
+ ending = case["endings"].get(choice)
60
+ if ending is None:
61
+ print("沒有這個結局選項。")
62
+ return False
63
+ if not requirements_met(ending.get("requires", []), clues):
64
+ print("目前的線索還不足以支持這個判斷。")
65
+ return False
66
+ print(ending["text"])
67
+ return True
68
+
69
+
70
+ def main():
71
+ case = load_case()
72
+ clues = []
73
+ print(f"=== {case['title']} ===")
74
+ print(case["intro"])
75
+ while True:
76
+ print("\n你要做什麼?")
77
+ for action_id, action in case["actions"].items():
78
+ print(f"{action_id}. {action['name']}")
79
+ print("4. 查看調查狀態")
80
+ print("5. 提出結案判斷")
81
+ print("6. 暫停案件")
82
+ choice = input("請選擇:").strip()
83
+ if choice in case["actions"]:
84
+ perform_action(case["actions"][choice], clues)
85
+ elif choice == "4":
86
+ show_clues(clues, case)
87
+ elif choice == "5":
88
+ if choose_ending(case, clues):
89
+ break
90
+ elif choice == "6":
91
+ print("案件已暫停。")
92
+ break
93
+ else:
94
+ print("請輸入畫面上的選項。")
95
+
96
+
97
+ if __name__ == "__main__":
98
+ main()
tests/test_classroom.py CHANGED
@@ -64,6 +64,7 @@ class ClassroomServiceTests(unittest.TestCase):
64
  course = json.loads((classroom.DATA / "course.json").read_text(encoding="utf-8"))
65
  self.assertEqual(len(course["weeks"]), 18)
66
  self.assertEqual(set(classroom.template_files("week02")), {"main.py", "case_data.json"})
 
67
 
68
  def test_catalog_lists_courses_and_existing_students(self):
69
  state = classroom.empty("L6A", "student-1", "Alex", "week02")
 
64
  course = json.loads((classroom.DATA / "course.json").read_text(encoding="utf-8"))
65
  self.assertEqual(len(course["weeks"]), 18)
66
  self.assertEqual(set(classroom.template_files("week02")), {"main.py", "case_data.json"})
67
+ self.assertEqual(set(classroom.template_files("week03")), {"main.py", "case_data.json"})
68
 
69
  def test_catalog_lists_courses_and_existing_students(self):
70
  state = classroom.empty("L6A", "student-1", "Alex", "week02")