Convert a MakeCode Arcade game into a picogame game that runs on a PicoPad, a Fruit Jam, or any other CircuitPython board picogame supports.
It reads the block tree a MakeCode project is really made of and emits readable picogame Python plus a generated art module. No shim, no interpreter, no runtime dependency on anything MakeCode: what comes out is an ordinary picogame game you can open and edit.
python3 mkcd/mkcd_import.py https://arcade.makecode.com/S08257-52975-47680-29942 -o mygame
python3 ../picogame/sim/run.py mygame/code.py --backend pygame # play it on the desktop
The converter itself needs nothing but Python 3.8+. The second line does, because the simulator lives in the picogame repository rather than here:
git clone https://github.com/MakerClassCZ/picogame ../picogame
pip install pygame # only for the windowed simulator
pip install pillow # only to run the gate (stage 4 reads screenshots)
Converting from a share link needs the network; point it at a directory to work offline.
The input can be a share link, a share id, or a directory holding main.blocks and the project's
.jres assets. Output is code.py, <name>_art.py and IMPORT.md - read that last one, it
says what did not come across.
Measured on a random sample of 80 real MakeCode block games harvested from GitHub (projects that ship their assets, so the picture on screen means something):
| converts and reaches its frame loop | 100 % |
| survives a scripted play session with no exception | 89 % (±7) |
| converts with no reported gap at all | 32 % |
On real hardware (PicoPad, RP2040, CircuitPython 10.3.0) the reference games run at 27-31 fps with a flat heap - no fragmentation over a 1560-frame soak on the tightest of them. The same converted code runs unchanged on an RP2350 Fruit Jam.
"Runs" is not "plays the same." The numbers above say a game does not crash and is visibly alive; they do not say it feels like the original. Play a port before you trust it.
IMPORT.md lists every gap by name, and flags the ones that are fatal rather than leaving you
to weigh a count - measured on the corpus, a project with zero gaps is playable 72 % of the time
while ten unconverted blocks can be the entire game.
Known limits, by class:
- Raw TypeScript blocks (
typescript_statement,typescript_expression) are not the block language and cannot be translated. A project where they carry the data model will not run; the report says so explicitly above 6 % of blocks. - Per-pixel image drawing (
Image.getPixel/setPixel/drawLine) - picogame keeps art in flash as read-onlybytes, and copying every touched image onto the heap is not something an RP2040 has room for. - Browser-only APIs (
browserEvents, mouse input). - Particle spawn PATTERNS - each named effect's rate, lifespan, speed, acceleration and
palette come from
particleeffects.ts, and the four shaped effects (hearts, smiles, rings, clouds) draw pxt's own bitmaps; what is approximated is where each particle starts and how the factory spreads them. - Sound expressions keep the wave shape, the frequency sweep and the volume contour; the interpolation curve and the tremolo/warble effects have no synthio equivalent.
- Text sprite decoration (icons, outlines, borders) from
arcade-text. - Extensions outside the supported set are reported by name rather than guessed at.
mkcd/mkcd_import.py entry point: fetch, assets, the IMPORT.md report
mkcd/mkcd_blocks.py Blockly XML -> Arcade Python
mkcd/mkcd_ts.py TypeScript -> Arcade Python (the blocks subset only)
mkcd/mkcd_picogame.py Arcade Python -> picogame Python
mkcd/helpers/*.py the runtime the converter writes INTO the game, one file per helper
mkcd/mkcd_blockmap.json blockId -> API, scraped from MakeCode's own //% blockId= annotations
tests/run_gate.py the regression gate - run it before every commit
tests/fixtures/ the projects the gate converts (see fixtures/PROVENANCE.md)
tests/anim_cases/ generated animation vectors, the decoder's test data
tests/anim_codec.py encodes mkcd-animation the way MakeCode does, derived from pxt sources
tests/device_api.json what the DEVICE's builtins accept, read out of the firmware sources
tools/extract_device_api.py regenerates that table from a CircuitPython checkout
Arcade Python is the shared middle layer: both front ends emit it, and one back end lowers it.
Run the gate. Converter changes break other games routinely - that is the normal case, not an unlucky one - and the gate is what tells you which game and how.
python3 tests/run_gate.py --sim ../picogame
See docs/GATE.md for what each stage checks and why, and docs/HOW-IT-WORKS.md for the pipeline.
MIT licensed. MakeCode Arcade is a Microsoft project; this tool reads its file formats and is not affiliated with it.
The test fixtures are not our work: all eleven are the MakeCode Arcade Block Games gallery, MIT, Copyright (c) Microsoft Corporation, redistributed under that licence. tests/fixtures/PROVENANCE.md tabulates every one with its share link, so any of them can be re-fetched and diffed against what ships here.