不用一個一個上傳、不用手動翻到眼花。用「先量化、再抽樣人工校正」的流程,把速度和公平性都顧到。

適用:國小五、六年級 Scratch 作業(劇情動畫、互動遊戲都可)|工具:Python 3(免套件)

1. 引起動機:為什麼不要一個一個傳 sb3

這篇文章你會學到什麼

批次評分的最快流程 先用程式把每個 sb3 的客觀指標整理成 CSV,再用少量抽樣作品做「人工校正」。
怎麼避免不公平 劇情動畫和互動遊戲用不同權重,不會拿同一把尺硬比。
缺點怎麼補救 CSV 看不到「好不好看」,所以用抽樣校正把誤差壓下來。

如果你手上有上百個 Scratch 作業(.sb3),最痛的通常不是「打分」本身,而是:

  1. 開檔、等載入、切角色、找積木、聽配音,時間像被吸走。
  2. 學生作品類型差很大:有人做劇情動畫、有人做互動遊戲,拿同一標準容易吵架。
  3. 只靠印象給分會不安心:你會希望有「客觀依據」撐住分數。

所以本文的核心做法是:

核心流程(老師實戰版)

  1. 本機批次掃描 sb3 → 產出 report.csv(每個作品一列)
  2. 用 CSV 先把全班排序、分類(劇情/互動/混合)
  3. 抽樣 10~20 個作品人工校正 → 把分數調到更接近你「真正想給的」

2. 現況說明:常見卡關與三種做法比較

先講現實:你不可能把每一份都「完整看完再評」。所以我們通常會在以下三種做法裡選一個平衡點:

做法 你要做的事 優點 缺點 適合誰
A. 批次掃描→CSV(最推薦) 在自己電腦跑腳本,產 report.csv,再抽樣少量 sb3 讓我/你人工校正 最快、最省上傳、最能做全班統計 CSV 只看得到客觀指標,看不到「好不好看」 上百份、想要又快又有依據
B. 分批壓縮上傳 把 sb3 拆成多個 zip 分次上傳 你不用寫程式 上傳仍然麻煩、檔案管理費工、我端仍要逐個讀 份數中等、或你沒辦法跑 Python
C. 抽樣 20 份先定尺 只傳高/中/低各幾份,先做評分規準 最省事、最快建立「班級尺度」 不能直接得到每一份的完整分數 你只需要評分標準,不一定要我逐份打

老師小提醒

如果你想「全班每份都有分數」又不想上傳到爆,通常就是選 A:用 CSV 先把全體拉起來,再針對代表作校正。

3. 功能介紹:批次掃描 sb3 其實在做什麼

Scratch 的 sb3 本質上是一個壓縮檔,裡面有 project.json。我們做的「批次掃描」,不是在幫你評美術,而是把可量化的結構拉出來,例如:

  1. 角色數、造型數、音效數(作品完成度的客觀線索)
  2. 積木總數、等待積木比例(劇情型常見 wait 很多)
  3. 是否使用:廣播訊息、變數、清單、迴圈、判斷、自訂積木、分身
  4. 互動線索:點擊角色、按鍵、碰撞偵測等

重點:這不是「最終分數」,而是「初評指標」

有用廣播不代表用得好;wait 多也不一定壞(劇情動畫本來就可能要配音對齊)。所以我們會用「抽樣校正」把誤差修回來。

4. 應用實例:用 CSV 先排出高/中/低,抽樣校正更準

你可以把 report.csv 想成「全班作品體檢表」。最常見的用法是:

  1. 先分類:劇情動畫、互動遊戲、混合型
  2. 先排序:找出可能最高分、最低分、以及中間段的代表作
  3. 抽樣校正:各類型抽 3~5 件,人工看一遍,把權重調到你認同的尺度
  4. 批次套用:剩下 80~90% 的作品,用校正後的規則快速落點

一個很穩的抽樣法(共 10 件)

  1. 自動分最高 3 件(看是不是「真的強」)
  2. 自動分最低 3 件(看是不是「真的需要加強」)
  3. 中位數附近 4 件(看「主流平均」落在哪)

抽樣看完,你就能把「技術分」和「表現分」調成你心中合理的比例。

圖 4-1:把 sb3 批次掃描後整理成 CSV,你會得到全班作品的結構指標,便於排序、分類與抽樣校正。

5. 操作教學:從資料夾到 report.csv 的完整步驟

你需要準備

  1. 一個資料夾:裡面放所有 .sb3
  2. Python 3(Mac 通常有;Windows 若沒有再裝)

步驟 1:把所有 sb3 放到同一個資料夾

例如:

/Users/你的名字/Desktop/scratch_hw/

步驟 2:建立批次掃描腳本(免套件)

把下面這段存成 sb3_batch_report.py

# sb3_batch_report.py
# 目的:批次讀取 .sb3,抽出可量化指標,產出 report.csv 與 report.sample.txt
# 用法:
#   python3 sb3_batch_report.py "/path/to/sb3_folder" report.csv

import csv, json, zipfile, sys
from pathlib import Path
from collections import Counter

FEATURE_OPS = {
    "broadcast": {"event_broadcast", "event_broadcastandwait", "event_whenbroadcastreceived"},
    "vars": {"data_setvariableto", "data_changevariableby", "data_showvariable", "data_hidevariable"},
    "lists": {"data_addtolist", "data_deleteoflist", "data_deletealloflist", "data_insertatlist",
              "data_replaceitemoflist", "data_itemoflist", "data_lengthoflist", "data_listcontainsitem"},
    "loops": {"control_repeat", "control_forever", "control_repeat_until"},
    "ifs": {"control_if", "control_if_else", "control_wait_until"},
    "custom": {"procedures_definition", "procedures_call"},
    "clone": {"control_create_clone_of", "control_start_as_clone", "control_delete_this_clone"},
}

INTERACTIVE_HINTS = {
    "event_whenthisspriteclicked", "event_whenkeypressed",
    "sensing_mousedown", "sensing_keypressed", "sensing_touchingobject",
    "sensing_touchingcolor", "sensing_coloristouchingcolor"
}

def analyze_sb3(sb3_path: Path):
    with zipfile.ZipFile(sb3_path, "r") as z:
        try:
            project = json.load(z.open("project.json"))
        except KeyError:
            return None

    targets = project.get("targets", [])
    extensions = project.get("extensions", [])

    sprites = [t for t in targets if not t.get("isStage")]
    sprite_count = len(sprites)

    costumes = sum(len(t.get("costumes", [])) for t in targets)
    sounds = sum(len(t.get("sounds", [])) for t in targets)

    var_defs = sum(len(t.get("variables", {})) for t in targets)
    list_defs = sum(len(t.get("lists", {})) for t in targets)
    broadcast_defs = sum(len(t.get("broadcasts", {})) for t in targets)

    opcodes = Counter()
    for t in targets:
        blocks = t.get("blocks", {}) or {}
        for _, b in blocks.items():
            if isinstance(b, dict) and b.get("opcode"):
                opcodes[b["opcode"]] += 1

    total_blocks = sum(opcodes.values())
    wait_blocks = opcodes.get("control_wait", 0) + opcodes.get("control_wait_until", 0)
    wait_ratio = (wait_blocks / total_blocks) if total_blocks else 0.0

    def has_any(ops):
        return sum(opcodes.get(o, 0) for o in ops) > 0

    feats = {k: has_any(v) for k, v in FEATURE_OPS.items()}
    interactive = any(opcodes.get(o, 0) > 0 for o in INTERACTIVE_HINTS)

    # 用來排序用的初步分數(不是最終分數)
    tech = 0.0
    tech += min(10.0, (total_blocks / 200.0) * 10.0)
    tech += 8.0 if feats["broadcast"] else 0.0
    tech += 8.0 if feats["vars"] else 0.0
    tech += 6.0 if feats["lists"] else 0.0
    tech += 6.0 if feats["loops"] else 0.0
    tech += 6.0 if feats["ifs"] else 0.0
    tech += 6.0 if feats["custom"] else 0.0
    tech += 4.0 if feats["clone"] else 0.0
    tech += 2.0 if len(extensions) > 0 else 0.0
    tech -= min(10.0, wait_ratio * 20.0)  # wait 太多先扣一些,避免全靠 wait 撐分

    assets = 0.0
    assets += min(10.0, (costumes / 30.0) * 10.0)
    assets += min(6.0, (sounds / 20.0) * 6.0)
    assets += min(4.0, (sprite_count / 10.0) * 4.0)

    auto_score = max(0, min(100, round(tech * 0.65 + assets * 0.35)))

    return {
        "file": sb3_path.name,
        "size_mb": round(sb3_path.stat().st_size / (1024 * 1024), 2),
        "sprites": sprite_count,
        "costumes": costumes,
        "sounds": sounds,
        "blocks": total_blocks,
        "wait_ratio": round(wait_ratio, 3),
        "var_defs": var_defs,
        "list_defs": list_defs,
        "broadcast_defs": broadcast_defs,
        "extensions": ",".join(extensions) if extensions else "",
        "uses_broadcast": int(feats["broadcast"]),
        "uses_vars": int(feats["vars"]),
        "uses_lists": int(feats["lists"]),
        "uses_loops": int(feats["loops"]),
        "uses_if": int(feats["ifs"]),
        "uses_custom": int(feats["custom"]),
        "uses_clone": int(feats["clone"]),
        "interactive_hint": int(interactive),
        "auto_score_for_sorting": auto_score,
    }

def main():
    if len(sys.argv) < 3:
        print("Usage: python3 sb3_batch_report.py /path/to/folder report.csv")
        sys.exit(1)

    folder = Path(sys.argv[1]).expanduser()
    out_csv = Path(sys.argv[2]).expanduser()

    sb3_files = sorted(folder.glob("*.sb3"))
    rows = []
    for f in sb3_files:
        r = analyze_sb3(f)
        if r:
            rows.append(r)

    if not rows:
        print("No valid .sb3 found.")
        sys.exit(1)

    fieldnames = list(rows[0].keys())
    with out_csv.open("w", newline="", encoding="utf-8") as fp:
        w = csv.DictWriter(fp, fieldnames=fieldnames)
        w.writeheader()
        w.writerows(rows)

    # 抽樣清單:最高3 + 最低3 + 中位附近4(共10個)
    rows_sorted = sorted(rows, key=lambda x: x["auto_score_for_sorting"])
    picks = []
    picks += rows_sorted[:3]
    mid = len(rows_sorted)//2
    picks += rows_sorted[max(0, mid-2):min(len(rows_sorted), mid+2)]
    picks += rows_sorted[-3:]
    pick_names = [p["file"] for p in picks]

    sample_txt = out_csv.with_suffix(".sample.txt")
    sample_txt.write_text("\n".join(pick_names), encoding="utf-8")

    print(f"Done. Wrote: {out_csv}")
    print(f"Sample list: {sample_txt}")

if __name__ == "__main__":
    main()

步驟 3:執行腳本,產出 report.csv

打開終端機(Mac)或 PowerShell(Windows),執行:

python3 sb3_batch_report.py "/你的/sb3資料夾" report.csv

成功後,你會得到兩個檔:

  1. report.csv:全班作品指標報表
  2. report.sample.txt:建議你抽樣檢查的 10 份作品檔名

步驟 4:把「CSV + 抽樣 sb3」交給我(或你自己)做校正評分

最省事交付法:
  1. 上傳 report.csv
  2. report.sample.txt 的清單,上傳那 10 個 sb3

我就能用同一套規則把全班都打出 100 分制,並附「每份作品最關鍵的 3 條改進建議」。

我想先看缺點與補救

隱私提醒

如果作品包含學生姓名、聲音或個資,建議先用檔名匿名(例如 501_01.sb3)再整理與分享,並遵守學校的資料規範。

6. Q&A:缺點、風險、以及怎麼補救

Q1:用 CSV 批次掃描的缺點是什麼?會不會不準?

會有誤差,因為 CSV 看的多是「客觀指標」,看不到劇情張力、節奏、配音自然度、美術美感等。但它很適合做初評與排序,再用 10~20 份抽樣人工校正,把分數拉回你真正認同的尺度。

Q2:學生如果「堆積木、堆素材」會不會刷分?

有可能,所以我建議一定要做抽樣校正:看「最高分的 3 件」是不是實至名歸。若出現刷分型作品,就把權重調整成更重視互動、結構或可讀性(例如廣播、變數、判斷),而不是純數量。

Q3:劇情動畫 wait 很多,會不會被扣太多分?

如果你班上多是劇情型作品,確實要調整規則。wait 多不一定壞,關鍵是「有沒有用廣播訊息讓流程更穩」。做法:把 wait 扣分調輕,並提高 broadcast 的加分,這樣劇情類會更公平。

Q4:我不能跑 Python,還有替代方案嗎?

可以。你可以用「分批 zip 上傳」或「抽樣 20 份先定評分規準」。如果只是要建立全班尺度,抽樣 20 份會最快;如果一定要全班逐份評,就用分批 zip(每包 20~30 件)比較好管。

Q5:分批 zip 會遇到檔案太大,怎麼分割?

建議用 7-Zip 分卷(例如每卷 90MB),或直接把 sb3 分批放入不同資料夾再各自壓縮。分卷檔案命名會像 batch.zip.001、batch.zip.002,依序上傳即可。

老師版結論

如果你要兼顧「快、穩、公平」:用 CSV 做全量初評 + 抽樣人工校正是最漂亮的解。

免責聲明
本文提供的流程與程式碼屬於教學用途,實際評分仍應以任課老師的評量規準、課堂教學目標與學校相關規定為準。若作品含個資(姓名、聲音、照片等),請先完成匿名化與必要的授權/告知後再分享或上傳。
文章標籤
全站熱搜
創作者介紹
創作者 小黃老師 的頭像
小黃老師

小黃老師嘿技術

小黃老師 發表在 痞客邦 留言(0) 人氣(5)