A browser game that teaches Python from scratch. You write real Python code to move a robot through mazes, collect coins, and draw shapes on screen.
Double-click run.bat (Windows) or run ./run.sh (Mac/Linux). This opens the
game in your browser automatically.
Why can't I just open
index.htmldirectly? The game needs to load its Python engine from a local web address, and browsers won't allow that when a file is opened straight from disk.run.bat/run.shstart a small local server to get around this — nothing leaves your computer, it's all running locally.
To stop the server, close the "Python Quest Server" window, or press any key in the launcher window.
index.html App shell — loads everything else
css/styles.css All styling
js/main.js Boots the app, switches between screens
js/pyodide-worker.js Runs Python (in a Web Worker) — sandboxed, has a timeout
js/pyodide-bridge.js Main-thread <-> worker messaging, timeout/restart handling
js/editor.js CodeMirror 6 code editor setup
js/game-engine.js XP, streaks, badges, save/load (localStorage)
js/map-ui.js Level map screen
js/challenge-ui.js Challenge screen (editor + visual + instructions)
js/error-messages.js Turns Python exceptions into beginner-friendly text
js/levels-data.js All level content
js/visuals/grid-renderer.js Robot-on-a-grid animation
js/visuals/canvas-renderer.js Turtle-graphics drawing animation
js/visuals/console-renderer.js Plain output panel
Levels live in js/levels-data.js as plain objects in the LEVELS array. Shape:
{ id: 'lvl-06-loop-steps', // unique, stable id order: 6, // position in the path unit: 'Loops', // groups levels into "worlds" on the map title: 'Step Counter', concept: 'for loops / range()', visualType: 'grid', // 'grid' | 'canvas' | 'console' unlockRequires: ['lvl-05-comments'], // level id(s) that must be completed first teachingContent: { explanation: 'A for loop lets you repeat an action a set number of times.', example: 'for i in range(3):\n print(i)', }, storyText: 'Your robot is stuck 5 steps from the recharge station...', apiTypes: ['robot'], // which Python API objects to expose: 'robot', 'turtle', or [] commandsUsed: ['forward'], // which of that API's methods to list in the "Commands you can use" box starterCode: '# move the robot 5 steps forward\n', initialState: { gridSize: [6, 1], robot: { x: 0, y: 0, dir: 'E' }, walls: [], goal: { x: 5, y: 0 } }, captureVars: [], // variable names to read back after running (for console/value levels) hints: [ 'Try using range(5) inside a for loop.', 'Each loop iteration should call robot.forward() once.', ], checkSolution: (result) => { // result = { stdout, error, actions, state, vars, timedOut } const passed = !result.error && result.state.robot.x === 5; return { passed, message: passed ? 'You reached the station!' : 'Not quite — check your loop range.' }; }, xpReward: 50, badgeId: null, }
checkSolution is a real JS function, so it can check anything in the result payload:
final robot/turtle position, captured variable values, or the exact stdout text.
robot(grid levels):forward(),turn_left(),turn_right(),say(text),is_blocked_ahead(),collect()turtle(canvas levels):forward(distance),left(degrees),right(degrees),pen_up(),pen_down()- Plain
print()always works and is captured intoresult.stdout.
User code runs in a fresh, isolated Python namespace on every run (no leftover state between attempts), inside a Web Worker with a timeout — an infinite loop shows a friendly message instead of freezing the page.