Spaces:
Running
Running
File size: 4,406 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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | (() => {
const canvas = document.getElementById("canvas");
const message = document.getElementById("runnerMessage");
const PYODIDE_URL = "/classroom-assets/vendor/pyodide/";
let pyodide = null;
let busy = false;
let outputDecoders = {};
function tell(type, details = {}) {
window.parent.postMessage({ type, ...details }, "*");
}
function showMessage(text) {
message.textContent = text;
message.hidden = false;
}
function hideMessage() {
message.hidden = true;
}
function setStream(channel) {
return {
isatty: false,
write(bytes) {
const decoder = outputDecoders[channel];
const text = decoder.decode(bytes, { stream: true });
if (text) tell("output", { runId: window.currentRunId, text });
return bytes.length;
},
};
}
async function initialize() {
try {
if (typeof loadPyodide !== "function") throw new Error("Pyodide 主程式沒有載入");
pyodide = await loadPyodide({ indexURL: PYODIDE_URL });
pyodide.canvas.setCanvas2D(canvas);
pyodide._api._skip_unwind_fatal_error = true;
tell("ready");
showMessage("Pygame 已就緒,按下「執行」開始遊戲。");
} catch (error) {
const text = `瀏覽器版 Pygame 載入失敗:${String(error)}`;
showMessage(text);
tell("load_error", { text });
}
}
async function run(files, runId) {
if (!pyodide || busy) return;
busy = true;
window.currentRunId = runId;
outputDecoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
pyodide.setStdout(setStream("stdout"));
pyodide.setStderr(setStream("stderr"));
pyodide.setStdin({ stdin: () => { throw new Error("瀏覽器版 Pygame 不支援 input();請改用 pygame 的鍵盤或滑鼠事件。"); } });
showMessage("正在載入 Pygame…");
tell("status", { runId, label: "載入 Pygame 套件…" });
try {
await pyodide.loadPackage("pygame-ce");
pyodide.runPython(`
import os, shutil, sys
root = "/home/pyodide/classroom_pygame"
shutil.rmtree(root, ignore_errors=True)
os.makedirs(root, exist_ok=True)
for name, module in list(sys.modules.items()):
if str(getattr(module, "__file__", "")).startswith(root):
del sys.modules[name]
`);
for (const [path, content] of Object.entries(files || {})) {
if (path.startsWith("/") || path.split(/[\\/]/).includes("..")) continue;
const fullPath = `/home/pyodide/classroom_pygame/${path}`;
pyodide.FS.mkdirTree(fullPath.slice(0, fullPath.lastIndexOf("/")));
pyodide.FS.writeFile(fullPath, content, { encoding: "utf8" });
}
pyodide.globals.set("classroom_entry", "main.py");
canvas.focus();
hideMessage();
tell("status", { runId, label: "遊戲執行中" });
const succeeded = await pyodide.runPythonAsync(`
import asyncio, inspect, os, sys, traceback
root = "/home/pyodide/classroom_pygame"
os.chdir(root)
if root not in sys.path:
sys.path.insert(0, root)
entry = os.path.join(root, classroom_entry)
namespace = {"__name__": "__main__", "__file__": entry}
try:
with open(entry, "r", encoding="utf-8") as source:
exec(compile(source.read(), entry, "exec"), namespace)
main = namespace.get("main")
if inspect.iscoroutinefunction(main):
await main()
True
except BaseException:
traceback.print_exc()
raise
finally:
import pygame
pygame.quit()
`);
for (const channel of ["stdout", "stderr"]) {
const rest = outputDecoders[channel].decode();
if (rest) tell("output", { runId, text: rest });
}
tell(succeeded ? "done" : "error", { runId });
showMessage(succeeded ? "遊戲已結束。按「執行」可重新開始。" : "遊戲發生錯誤,請查看文字輸出。" );
} catch (error) {
for (const channel of ["stdout", "stderr"]) {
const rest = outputDecoders[channel].decode();
if (rest) tell("output", { runId, text: rest });
}
const text = String(error);
tell("error", { runId, text });
showMessage("遊戲發生錯誤,請查看文字輸出。");
} finally {
busy = false;
}
}
window.addEventListener("message", event => {
if (event.source !== window.parent || event.data?.type !== "run") return;
run(event.data.files, event.data.runId);
});
initialize();
})();
|