Spaces:
Running
Running
Download classroom_static/pygame-runner.js from Telearn/Project_Demo: direct link, hf CLI and curl.
- Browser
- Download file 4.41 kB
-
https://huggingface.co/spaces/Telearn/Project_Demo/resolve/main/classroom_static/pygame-runner.js
- Command line
-
hf download hf://spaces/Telearn/Project_Demo/classroom_static/pygame-runner.js
-
curl -L -o pygame-runner.js https://huggingface.co/spaces/Telearn/Project_Demo/resolve/main/classroom_static/pygame-runner.js
4.41 kB
| (() => { | |
| 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(); | |
| })(); | |