{"id":51124775,"url":"https://github.com/ktaletsk/wanderland","last_synced_at":"2026-06-25T06:30:31.420Z","repository":{"id":366259058,"uuid":"1273696240","full_name":"ktaletsk/wanderland","owner":"ktaletsk","description":"Interactive 3D learn-to-code playground for Python notebooks","archived":false,"fork":false,"pushed_at":"2026-06-21T01:06:39.000Z","size":453,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-21T03:07:28.810Z","etag":null,"topics":["3d","anywidget","education","jupyter","marimo","python","threejs"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ktaletsk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-18T19:28:25.000Z","updated_at":"2026-06-21T01:06:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ktaletsk/wanderland","commit_stats":null,"previous_names":["ktaletsk/wanderland"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ktaletsk/wanderland","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktaletsk%2Fwanderland","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktaletsk%2Fwanderland/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktaletsk%2Fwanderland/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktaletsk%2Fwanderland/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ktaletsk","download_url":"https://codeload.github.com/ktaletsk/wanderland/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktaletsk%2Fwanderland/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34763481,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["3d","anywidget","education","jupyter","marimo","python","threejs"],"created_at":"2026-06-25T06:30:29.989Z","updated_at":"2026-06-25T06:30:31.413Z","avatar_url":"https://github.com/ktaletsk.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1\u003e\n\u003cp align=\"center\"\u003e\n  🌱\n  \u003cbr\u003eWanderland\n\u003c/p\u003e\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/wanderland.svg\" width=\"180\" alt=\"A top-down Wanderland grid: a red character facing a locked yellow door, a key, and a goal ring\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://marimo.app/?src=https%3A%2F%2Fraw.githubusercontent.com%2Fktaletsk%2Fwanderland%2Frefs%2Fheads%2Fmain%2Fexample.py\u0026mode=read\"\u003e\u003cimg src=\"https://marimo.io/shield.svg\" alt=\"Open in marimo\" height=\"20\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://notebook.link/github.com/ktaletsk/wanderland/?path=example.ipynb\"\u003e\u003cimg src=\"https://img.shields.io/badge/notebook-link-e2d610?logo=jupyter\u0026logoColor=white\" alt=\"Open on notebook.link\" height=\"20\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nAn interactive low-poly **3D coding playground** as an [anywidget](https://anywidget.dev),\nbuilt for Python notebooks. Write simple Python commands and watch a charming\nlittle character — **Mo the Mossball** — animate through a stylized world, collecting\ngems and reaching goals.\n\nIt captures the joy of a learn-to-code playground — *write code, watch the character act\nit out* — with an original character, original art, and a small Python command API,\nrunning entirely inside an interactive notebook.\n\n```python\n# Create and show the world (it has its own ▶ Run My Code button)\n# Any anywidget-compatible notebook works: Jupyter, marimo, VS Code...\nimport wanderland as mp\nfrom wanderland import move_forward, turn_right, collect_gem\n\n# For marimo, you might wrap this in mo.ui.anywidget()\nworld = mp.World(mp.puzzles.gem_path())\nworld  # renders the 3D scene + the in-scene Run button\n\n# Your program (editing this just *loads* it; the character doesn't move yet)\n# gem_path's gems are non-blocking: walk the character onto each one, then collect_gem().\ndef solution():\n    move_forward()\n    move_forward()\n    move_forward()\n    collect_gem()   # first gem\n\n    turn_right()\n    move_forward()\n    move_forward()\n\n    turn_right()\n    move_forward()\n    move_forward()\n    move_forward()\n    collect_gem()   # second gem\n\n    turn_right()\n    turn_right()\n\n    move_forward()\n    move_forward()\n    move_forward()\n\n# Hand the program to the widget\nworld.load(solution)\n# ...now press ▶ Run My Code in the scene to animate it once.\n\n# Read the outcome back in Python (synchronous, after load)\nworld.success            # True\nworld.gems_collected     # 2\nworld.reached_goal       # True\n```\n\nThe **▶ Run My Code** button lives inside the widget. Editing the program reloads its\ntimeline silently; the character only moves when you press Run, and then stays at their final pose.\n\n\u003cp align=\"center\"\u003e\n  \u003cvideo src=\"https://github.com/user-attachments/assets/586d888f-7a24-4376-88db-f335070b64ed\" width=\"100%\" controls=\"controls\" muted=\"muted\"\u003e\u003c/video\u003e\n\u003c/p\u003e\n\n---\n\n## Install \u0026 run\n\n```bash\npip install wanderland          # or: uv add wanderland\n```\n\nThe published package ships the prebuilt 3D frontend — **no Node required**. It works in\nany notebook that supports [anywidget](https://anywidget.dev) (marimo, Jupyter). Open the\nexample notebook to play:\n\n```bash\nuv run marimo edit example.py   # the teaching playground\n```\n\nYou'll see your character (like Mo the Mossball) standing in a warm low-poly world; running a program animates them through\nyour commands step by step. Drag to orbit the camera.\n\n\u003cdetails\u003e\n\u003csummary\u003eDevelop from source (rebuild the frontend)\u003c/summary\u003e\n\nRequires Python ≥ 3.10 and Node ≥ 18.\n\n```bash\nnpm install \u0026\u0026 npm run build     # build the 3D bundle -\u003e src/wanderland/static/index.js\nuv venv \u0026\u0026 uv pip install -e \".[dev]\"\nuv run marimo edit example.py\n```\n\u003c/details\u003e\n\n---\n\n## Solve puzzles with code\n\nA **program** is an ordinary Python function. Inside it you call commands in the order\nyou want them to happen:\n\n| command | what it does |\n|---|---|\n| `move_forward()` | step one tile in the direction faced |\n| `turn_left()` / `turn_right()` | rotate 90° — turning is **egocentric** (relative to the current heading) |\n| `pickup()` | take the object in the cell **faced** (a key/ball/box, or a blocking gem) into your hand — carry limit one. You don't move. |\n| `drop()` | drop the carried object onto the empty floor cell faced |\n| `toggle()` | open/close the door faced (a locked door opens with a matching-color key, which you keep); open a box to reveal its contents |\n| `collect_gem()` | collect the **non-blocking** gem on the tile you're standing on (walk on, then collect; scores, not carried) |\n| `move_backward()` | step back without turning — **free-play only**, off the canonical action set |\n\nInteraction is always on the cell you **face**, standing adjacent — you never walk onto\na blocking object. A blocked move is animated by *why* it failed: the character teeters at the brink\nof the world's edge (a near-fall), and bonks off a wall, door, or object.\n\n### Action space\n\nEvery world declares the **exact set of verbs it permits** — explicitly, with no default\nand no canonical bundle. That declared set *is* the action space you're allowed to use, and\nit's enforced: calling a verb the world didn't list raises.\n\n```python\nworld.action_space     # ('move_forward', 'turn_left', 'turn_right', 'pickup', 'drop', 'toggle')\nworld.actions_doc      # [{'name': 'pickup', 'doc': '...'}, ...]  — documentation for the actions\n```\n\n### Running a program\n\nThree ways to drive the character, depending on who pulls the trigger:\n\n- **`world.load(solution)`** — the recommended notebook flow. Captures the commands,\n  simulates them, and hands the timeline to the widget **without playing**. The user\n  presses the widget's own **▶ Run My Code** button to animate it once; the character stays at their\n  final pose. Editing the program reloads silently.\n- **`world.run(solution)`** — captures *and plays immediately* (no button). Handy for\n  programmatic or headless use; returns the result dict.\n- **`@world.program`** — decorator form of `run()`; plays whenever the defining cell\n  re-executes.\n\nAll three capture the command sequence and simulate it in Python (the source of truth);\n`world.success` and friends are available synchronously regardless of which you use. The\nexample notebook uses `load()` + the in-scene button.\n\n### Reading the outcome\n\nBecause the simulation runs in Python, results are available **synchronously** right\nafter the program runs (and work even without a browser):\n\n```python\nworld.success          # all (non-blocking) gems collected AND goal reached\nworld.gems_collected   # int\nworld.total_gems       # int\nworld.reached_goal     # bool\nworld.result           # the full dict: final pose, what's carried, ...\n```\n\nFor reactive readback (like in marimo), read `world.value` (or `world.state`) in another cell — the\nfrontend writes a playback report there when the animation finishes.\n\n## For Educators: Creating Custom Worlds\n\nWanderland makes it easy to design your own levels and assignments for students. You can sketch out puzzles visually using simple ASCII text strings.\n\n```python\nfrom wanderland import from_ascii, World\n\nlevel_design = \"\"\"\n\u003e . # .\n. . Ly .\nKy . # O\n\"\"\"\n\nallowed_actions = (\n    \"move_forward\",\n    \"turn_left\",\n    \"turn_right\",\n    \"pickup\",\n    \"toggle\",\n)\n\npuzzle = from_ascii(\n    \"Locked Room\",\n    level_design,\n    actions=allowed_actions\n)\n\n# You can also pick a different character!\nworld = World(puzzle, character=\"rover\")  # A hovering drone-bot instead of Mo\n```\n\nEach cell is one whitespace-separated token; the top row is north, columns go east:\n\n| token | meaning |\n|---|---|\n| `^ \u003e v \u003c` | start tile **and** the character's facing (N/E/S/W) — `S` also works with `heading=` |\n| `.` `#` `~` `!` `O` | floor · **wall** · water (impassable) · **lava** (walkable but deadly) · goal |\n| `g` / `G` | non-blocking gem (walk on, then `collect_gem()`) / blocking gem (`pickup()` from the front) |\n| `Kc` `Bc` `Xc` | key / ball / box of color `c` (`r g b p y e`) — `Xc:obj` gives a box hidden contents |\n| `Dc` `Lc` | closed / locked door of color `c` |\n\n`actions=` is **required**. Built-in worlds live in `mp.puzzles` (`first_steps`,\n`gem_path`, `spiral`, `locked_room`).\n\n\u003e **Rendering:** floor, water, walls, gems, and the colored objects (keys, balls,\n\u003e boxes, doors) all render in 3D, and `pickup`/`drop`/`toggle` animate — the carried\n\u003e item floats above the character, doors unlock and swing open, boxes open to their\n\u003e contents. (Box contents stay hidden when printing the world state.)\n\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fktaletsk%2Fwanderland","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fktaletsk%2Fwanderland","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fktaletsk%2Fwanderland/lists"}