File size: 2,641 Bytes
7547388
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
(() => {
  let frame = null;
  let ready = false;
  let bootPromise = null;
  let resolveBoot = null;
  let rejectBoot = null;
  let timeout = null;
  let runId = 0;
  let activeRunId = 0;
  let onMessage = () => {};

  function receive(event) {
    if (!frame || event.source !== frame.contentWindow) return;
    const message = event.data || {};
    if (message.type === "ready") {
      ready = true;
      clearTimeout(timeout);
      timeout = null;
      resolveBoot?.();
      resolveBoot = null;
      rejectBoot = null;
      bootPromise = null;
      return;
    }
    if (message.type === "load_error") {
      const error = new Error(message.text || "無法載入瀏覽器版 Pygame");
      clearTimeout(timeout);
      timeout = null;
      rejectBoot?.(error);
      resolveBoot = null;
      rejectBoot = null;
      bootPromise = null;
      onMessage({ ...message, runId: activeRunId });
      return;
    }
    if (message.runId && message.runId !== activeRunId) return;
    onMessage(message);
  }

  window.addEventListener("message", receive);

  window.ClassroomPygame = {
    attach(target, handler) {
      frame = target;
      onMessage = handler;
    },

    isPygame(files) {
      return Object.entries(files || {}).some(([path, code]) =>
        path.toLowerCase().endsWith(".py") &&
        /^\s*(?:import\s+pygame(?:\s|,|$)|from\s+pygame(?:\.|\s+import\s))/m.test(code)
      );
    },

    async run(files) {
      const thisRun = ++runId;
      activeRunId = thisRun;
      frame.hidden = false;
      if (!ready) {
        if (!bootPromise) {
          bootPromise = new Promise((resolve, reject) => {
            resolveBoot = resolve;
            rejectBoot = reject;
            timeout = setTimeout(() => {
              const error = new Error("Pygame 載入逾時,請確認網路連線後重試。");
              bootPromise = null;
              resolveBoot = null;
              rejectBoot = null;
              reject(error);
            }, 60000);
            frame.src = "/classroom-assets/pygame-runner.html?v=2026fall-1";
          });
        }
        await bootPromise;
      }
      if (thisRun !== runId) return;
      frame.contentWindow.postMessage({ type: "run", runId: thisRun, files }, "*");
    },

    stop() {
      ++runId;
      activeRunId = runId;
      ready = false;
      clearTimeout(timeout);
      timeout = null;
      if (rejectBoot) rejectBoot(new Error("Pygame 已停止"));
      resolveBoot = null;
      rejectBoot = null;
      bootPromise = null;
      if (frame) {
        frame.hidden = true;
        frame.src = "about:blank";
      }
    },
  };
})();