**quadplay✜** Fantasy Console by [Casual Effects](https://casual-effects.com)
[*Quick Start*](#quickstart) ∙ [API](#standardlibrary) ([*Input*](#standardlibrary/input) ∙ [*Physics*](#standardlibrary/physics) ∙ [*Graphics*](#standardlibrary/graphics) ∙ [*Sound*](#standardlibrary/sound) ∙ [*AI*](#standardlibrary/ai) ∙ [*Network*](#network))
[Assets](assets.md.html target=_blank) ([*Sprites*](assets.md.html#sprites target=_blank) ∙ [*Fonts*](assets.md.html#fonts target=_blank) ∙ [*Sounds*](assets.md.html#sounds target=_blank))
[Controls](#controls) ∙ [Symbols](#symbols) ∙ [JSON Formats](#projectfiles) ∙ [Language Spec](#pyxlscriptlanguage) ∙ [Change Log](changelog.md.html)
> "Imagine the year 1989 in a fantasy timeline. Instead of chasing > realistic 3D, game consoles get modern tools and mechanics for > 2D. is the fantasy sibling of the SNES, Neo Geo, and > PlayStation that puts fun first in game jams and education. Its > retro constraints scope art and code for success. 's > modern scripting, physics, local and online multiplayer, and AI > empower easy implementation and great gameplay." [**quadplay✜**](../console/quadplay.html?IDE=1) is a web-based fantasy game console for sprite-based 2.5D games. It lets you create games with the power and consistency of a game console and the flexibility of modern tools and web deployment. Because it is free, is easy to use, and runs everywhere, is perfect for hobby coding, a game programming course, teaching yourself programming and game development, and game jams. The integrated development environment (IDE) is packaged as an emulator with the tools that you need to make games: an editor, debugger, and profiler. **Virtual Hardware Features** - Support for 1-4 players, including automatic remote play - Standardized controller with D-pad + 8-button gamepads (SNES/PS1/Saturn-style) and automatic device-specific button prompts on all platforms - Touch screen/mouse input - 60 fps, with autoscaling on low-end machines - 384 x 224 pixel screen = 12:7 aspect ≈ 16:9.3 - 4096 sRGB (4:4:4) colors - Native 2.5D graphics with order-independent 16-level transparency - Bloom, motion blur, and phosphor burn-in post-processing - 2D rigid body, joint, and motor physics - 2D positional audio with pitch, volume, and rate - 10.5 MB of total sprite memory - Max 128 sprite and font sheets, max size 1024x1024 - 262 kB offline save data per game - Pathfinding and board game AI - Local and online multiplayer - Optional I/O extension modes for 640 x 360, 320 x 180, 192 x 112, 128 x 128, and 64 x 64 screen modes; analog sticks and triggers; dual D-pads; multitouch **Software Features** - [Export](#deployinggames) to your own website, itch.io, or github pages - Games run on web, mobile, desktop, tablet, Jetson Nano, Raspberry Pi 4/400 - Free for commercial, personal, and educational use - Friendly and powerful PyxlScript Python-like scripting language - Run locally and offline, using a full development environment that requires only Python + web browser, no binary installation - Hundreds of built-in Creative Commons assets - Full command line, external editor, and external art tool support for power users with automatic file sync - Open source: use online, fork, or run locally - Runtime PNG, MP3, TMX, JSON, YAML, TXT, and CSV data formats - Built-in support for collaboration via git version control from the IDE **API** - Sprite, pixel, circle, line, triangle, rectangle, and polygon drawing with rotation and scaling - Font, tilemap, and UI window drawing - Optional entity parenting hierarchy - Ray casting and collision detection - Postprocessing effects - Persistence (saving and loading) - Nestable state-machine "modes" - Per-mode callback "hooks" is ideal for game jams, and it has been used for many including [Ludum Dare](https://ludumdare.com/ target="_blank") and the [Global Game Jam](https://globalgamejam.org/ target="_blank"). It has special support of MIDI I/O for [Alt.Ctrl.GDC](https://gdconf.com/alt-ctrl-gdc target="_blank") and a 64x64 screen mode for [Lowrezjam](https://itch.io/jam/lowrezjam-2024 target="_blank"). Quick Start ==================================================================================== Welcome to !
Download Windows Installer *macOS and Linux:* 1. Install [Python 3.7](https://www.python.org/ target="_blank") or newer 2. Unzip [](https://github.com/morgan3d/quadplay/archive/main.zip) to wherever you want 3. Run `quadplay` from the terminal
To make a game, select the "Tools" menu from inside the quadplay development environment and then the "New Game" option. Look at the Built-In Games section for more examples. If you've worked with other scripting languages, then you may want to read the PICO-8 section, Python section, or JavaScript section of this document for some starting tips. If you're new to programming, just look at the many sample games and examples that are included and try modifying your own copies of them. !!! Info Pro Developers Experienced programmers can use external editors with for both programming and directly modifying game and asset `.json` files by:
  • Optionally [clone the SDK with git](https://github.com/morgan3d/quadplay) to manage SDK upgrades
  • Install a code editor (_[Visual Studio Code](https://code.visualstudio.com/), [Atom](https://atom.io/), [Sublime Text](https://www.sublimetext.com), Vim, Emacs, etc._)
  • Install the PyxlScript extension for your editor: _[Visual Studio Code](#advancedtools/visualstudiocode)_, _[Vim](#advancedTools/vim)_, _[Emacs](#advancedtools/emacs)_
  • Put your games under `~/my_quadplay/`. The IDE always creates new games in the root of that path, but you can reorganize it to form a hierarchy to group games or manage different games using different version control repositories.
On Windows, click on the shortcut on your desktop to run . Or launch from the command line on Windows CMD, macOS Terminal, or a Linux terminal with the `quadplay` command in the root of the SDK. Here are some examples: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bash # Run the emulator and IDE. You can use Open Game from the Tools # menu to load games quadplay # Load the games/quadpaddle/quadpaddle.game.json sample program quadplay quad://games/quadpaddle # Load the space.game.json game that you've made (on Mac and Linux) quadplay ~/my_quadplay/space # Load the space.game.json game that you've made (on Windows) quadplay C:\Users\MYNAME\my_quadplay\space # Load your game and allow mobile clients to connect by scanning # the QR code displayed in the IDE (Mac and Linux) quadplay --serve ~/my_quadplay/space # ... Windows quadplay --serve C:\Users\MYNAME\my_quadplay\space # Launch quadplay as a standalone game kiosk quadplay --kiosk # Load a game from the web (their server must support CORS) quadplay https://morgan3d.github.io/somegame/foo.game.json ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For more advanced controls and to make the quadplay local server visible, you can run `tools/quadplay-server` instead. These examples assume that your directory structure is: ***************************************************************************** * 📂 HOME (~/ on Mac and Linux, %HOMEDRIVE%%HOMEPATH% on Windows) * | * +-- 📂 my_quadplay * | | * | '-- 📂 space * | | * | +-- 📄 space.game.json * | | * ⋮ ⋮ * * 📂 quadplay * | * +-- 📄 quadplay * +-- 📄 quadplay.vbs * | * +-- 📂 fonts * | | * | ⋮ * | * +-- 📂 games * | | * | +-- 📂 quadpaddle * | | | * | | +-- 📄 quadpaddle.game.json * | | | * | ⋮ ⋮ * | * +-- 📂 sprites * | | * ⋮ ⋮ ***************************************************************************** [Directory structure with the `quadplay` script in the root.] The directory and `.game.json` file do not have to have the same name. You can list the full path to the actual game file instead. Your games do not have to be stored in a subdirectory of the SDK. They can be anywhere on your system. Every time you push the Run button (F5) or Reload button (Ctrl+R) in the emulator within the web browser, it will reload your code and assets from disk. So, you can mix an external editor with the built-in IDE without worrying about synchronization. You can use the built-in assets from the developer kit, existing ones from sites like [OpenGameArt.org](http://opengameart.org) and [FreeSound](http://freesound.org), or create your own using: - PNG pixel art editor for sprites and fonts (_[Gimp](https://www.gimp.org/), [AseSprite](https://www.aseprite.org/), [Krita](https://krita.org/en/), Photoshop, GraphicsGale, etc._) - MP3 sound tools (_[BFXR](https://www.bfxr.net/), [Audacity](https://www.audacityteam.org/), etc._) - TMX map tool ([Tiled](https://www.mapeditor.org/), [TileKit](https://rxi.itch.io/tilekit)) _People playing your game don't need Python, just a web browser. See the Deploying Games section for more information._ Workflow ------------------------------------------------------------------------------------ You should create your programs however works best for you. Here's how I like to work in : 1. I start with a project file to load standard assets from `quad://` and a single *mode* called `Play.pyxl` that has no sections. I write a little code to draw the game. 2. Pretty soon, I need some state that persists between frames, so I separate `Play.pyxl` into top level initialization code and a `frame` *section*. I then declare variables in in the top level of the mode, and as my main loop grows too long, I start to define helper functions there as well. 3. At some point, I'm ready for *another mode*. This is usually for gameplay reasons, and not polish, because it is still early in the project. This might be a map or inventory screen, for example. As soon as there is a new mode, I need to have variables that can be seen by both modes. So, I also create a `common.pyxl` file or other *script* to store the global variables. I continue this process as more modes are required, periodically checking the mode system diagram in the IDE to ensure that the game flow is what I expected. 4. As code starts to become duplicated in the modes, I move generally useful helper functions out to scripts. When the scripts themselves get long, I start grouping them by functionality. For example, `particle.pyxl`, `animation.pyxl`, and `hud.pyxl`. This is a workflow that is good for prototyping, especially in game jam and hobby projects. I feel like keeping everything as simple as possible and then refactoring at growth points is best for productivity. Otherwise, it is hard to iterate in the beginning because you're stuck with big program abstractions in what is still a small program. Many prototypes don't go the distance to become big programs, anyway. There's no reason to overdesign early on. Built-In Games ------------------------------------------------------------------------------------ Five complete games and many examples are included to show how to use the API. If you are accessing this manual from a web site or your local machine running the emulator, use the links here to launch the game in your browser, press the Run button (F5) to start the game. ### Playable
[Quadpaddle](../console/quadplay.html?game=quad://games/quadpaddle&IDE=1) is a four-player cooperative game inspired by [_Breakout_]("https://en.wikipedia.org/wiki/Breakout_(video_game)"): - Powerups - Level-end animations - Particle system - Screen shake - Frame hooks - Coordinates with +Y pointing down ----------------------------
[Doubles de Pon](../console/quadplay.html?game=quad://games/doublesdepon&IDE=1) is 1-4 player action puzzle matching game: - Custom music - High scores - Menus and character selection - Multiple play modes - Complex animation "juice" ----------------------------
[across the lake](../console/quadplay.html?game=quad://games/across_the_lake&IDE=1) is an endless runner created for Ludum Dare 46 based on skipping stones over a lake in the evening. - Crossfading - Simple custom physics - Custom camera - Coordinates with +Y pointing up - Procedural content - Fake reflections - Parallax and pseudo 3D ----------------------------
[Morgan's Mini Mecha](../console/quadplay.html?game=quad://games/minimecha&IDE=1) is turn-based strategy game created in a one week game jam. It is inspired by _Into The Breach_, _Advance Wars_, and Shogi. - Turn based UI - Board game artificial intelligence player - Complex scripted transitions and animations - High scores - Procedural map terrain ----------------------------
Friendly Fishing
[Friendly Fishing](../console/quadplay.html?game=quad://games/friendly_fishing&IDE=1) Join your friends for a relaxing afternoon of fishing and you just might top the lake records. - Animation using `sequence()` and `delay()` - The built-in shapes spritesheet - Complex high-score system - Use of `z` as depth - Color `make_spline()` - Tap, hold, and press button processing ----------------------------
[Serpitron](../console/quadplay.html?game=quad://games/serpitron&IDE=1) is a four-player competive game derived from Snake-like games, including [TRON]("https://en.wikipedia.org/wiki/Tron_(video_game)"): - Level transitions using post effects - Drop shadows - Player selection - Sorting - Collisions ----------------------------
[HexFlipper](../console/quadplay.html?game=quad://games/hexflipper&IDE=1) is a one to four-player competive game of area control: - Computer player AI - Hex grids - Boardgame style logic - Music ----------------------------
[Ice Time](../console/quadplay.html?game=quad://games/icetime&IDE=1) is a four-player competitive or cooperative 2 vs. 2 ice hockey game: - 192 x 112 graphics - AI players - Cut scenes - Persistent data - Reflection effects ----------------------------
[Duality](../console/quadplay.html?game=quad://games/duality&IDE=1) is a one- or two-player game inspired by Pong and Breakout: - Animations - Simulation - AI player - Post Effects - Slow motion ----------------------------
[Gravity](../console/quadplay.html?game=quad://games/gravity&IDE=1) is a 2-4 player game inspired by SPACEWAR! - Uses only built-in assets - Charging and cooldowns - Post-Effects afterglow - Simulation - Emergent behavior ----------------------------
[Black Firmament](../console/quadplay.html?game=quad://games/firmament&IDE=1) is a 1-2 player game extending Gravity with full fleets. - AI players - Multiple modes - Title menu - Complex control schemes - Unit selection - Post-Effects afterglow - Significant physics simulation - Emergent behavior ----------------------------
[Beat the Gobblins](../console/quadplay.html?game=quad://games/beat_the_gobblins&IDE=1) is a single-player arcade battle arena: - High scores - Custom music - Animated title - Animated explosions - Character spawning ----------------------------
[Beyond Control](../console/quadplay.html?game=quad://games/beyond_control&IDE=1) is a game for 1-4 players controlling broken robots: - Hexgrid - Asynchronous animations - Turn based system - In-game user interface - Dynamic layout - Dynamic map - Animated map ----------------------------
[R. P. S.](../console/quadplay.html?game=quad://games/rps&IDE=1) is a rock-paper-scissors battle royale for up to four players: - Extensive animation - Use of modes for simplicity - Timed sound effects - Private choices in multiplayer - Combinatorial logic ----------------------------
[Rescue Roguelike](../console/quadplay.html?game=quad://games/rescue_roguelike&IDE=1) is a tactical turn-based adventure game inspired by Into the Breach and Rogue: - Constants - Touch and mouse interaction - Aseprite sprites - Extensive data-driven design with external YAML files ----------------------------
[But...Skunks?!](../console/quadplay.html?game=quad://games/but_skunks&IDE=1) is a one or two player rhythm game. ----------------------------
[Drop Bloq](../console/quadplay.html?game=quad://games/drop_bloq&IDE=1) 5-in-a-row game at 8x8 resolution (!) that plays on any device, including an Ableton MIDI controller (Push, Launchpad, APC, etc.). Also demonstrates built-in AI routines. ### Examples
![](../examples/starter/label128.png) [Starter](../console/quadplay.html?game=quad://examples/starter&IDE=1) is a project with a basic setup from which you can build your own.
![](../examples/helloworld/label128.png) [Hello, World](../console/quadplay.html?game=quad://examples/helloworld&IDE=1) is a minimal example of a program with one mode, one asset, and no sections or optional elements, and that simply puts text on the screen.
![](../examples/rpg/label128.png) [RPG Demo](../console/quadplay.html?game=quad://examples/rpg&IDE=1) is a simple example of a single-player RPG game design: - Multiple [modes](#modes): Play, Inventory, and Shop - Multi-layer map - Player movement with obstructions - NPC interaction
![](../examples/dual-stick/label128.png) [Dual-Stick Example](../console/quadplay.html?game=quad://examples/dual-stick&IDE=1) shows how to use dual-stick controls. The body of the tank is controlled by player 1 and the turret is controlled by player 2. When using a dual-stick game controller or keyboard, a single player can also use the right stick or right side of the keyboard to control the turret. - Dual-stick controls - Working with angles - Entity parenting - 320x180 resolution
![](../examples/maze/label128.png) [Maze](../console/quadplay.html?game=quad://examples/maze&IDE=1) is a demonstration of `map_generate_maze()`.
![](../examples/robot/label128.png) [Robot Example](../console/quadplay.html?game=quad://examples/robot&IDE=1) shows how to create and animate a deep entity hierarchy. - Entity parenting - Pivots that are not at the center of sprites - Using scale and rotation together - Graphics tricks for reflections
![](../examples/animation/label128.png) [Animation Example](../console/quadplay.html?game=quad://examples/animation&IDE=1) shows how to handle complicated sprite animations. - Sprite animations - Basic jumping physics - Changing direction - Modular items
![](../examples/coordinate_system/label128.png) [Animation Example](../console/quadplay.html?game=quad://examples/coordinate_system&IDE=1) shows the coordinate systems produced by changing the default axes.
![](../examples/vehicles/label128.png) [Vehicles Example](../console/quadplay.html?game=quad://examples/vehicles&IDE=1) contains examples of several vehicle controls from a free-direction top-down perspective. - 2.5D camera perspective - Using the ⓔ and ⓕ buttons - Simple vehicle simulation for control feel - Entities with moving child parts - 2.5D sprite stacking - Dynamic shadows - Minimap
![](../examples/fluid/label128.png) [Fluid Example](../console/quadplay.html?game=quad://examples/fluid&IDE=1) is a cellular automata fluid flow simulation with gravity and pressure.
![](../examples/roguelike/label128.png) [Roguelike Example](../console/quadplay.html?game=quad://examples/roguelike&IDE=1) uses the roguelike sprite set for tilemap rendering with a simple text-heavy UI section.
![](../examples/dynamic_accel/label128.png) [Dynamic Acceleration Example](../console/quadplay.html?game=quad://examples/dynamic_accel&IDE=1) is a simple demonstration of how various acceleration curves feel, changing only three constants: top speed, acceleration time and deceleration time. Up and down change the constants to fit different games.
![](../examples/boids/label128.png) [Boids Example](../console/quadplay.html?game=quad://examples/boids&IDE=1) implements the famous ["boids" flocking algorithm](https://en.wikipedia.org/wiki/Boids).
![](../examples/clouds/label128.png) [Clouds Example](../console/quadplay.html?game=quad://examples/clouds&IDE=1) demonstrates per-pixel graphics and multi-octave `noise()` functions. The virtual GPU is fast enough to process one point per pixel, but this technique is not recommended in general because the computations to produce the points tend to be too slow, even if this simple.
![](../examples/dark_drive/label128.png) [A Dark Drive](../console/quadplay.html?game=quad://examples/dark_drive&IDE=1) A simple horror game jam project using polygon drawing to create darkness around car headlights.
![](../examples/entity/label128.png) [Entity Example](../console/quadplay.html?game=quad://examples/entity&IDE=1) Shows different ways of constructing entities with text and sprites.
![](../examples/physics/label128.png) [Physics Example](../console/quadplay.html?game=quad://examples/physics&IDE=1) shows all of the features of the physics engine, including the debugging visualization.
![](../examples/physics_arrow/label128.png) [Physics Example](../console/quadplay.html?game=quad://examples/physics_arrow&IDE=1) shows how to use dynamic attachments in the physics engine to make objects connect at runtime, and how to simulate aerodynamics so that arrows will fly straight.
![](../examples/text/label128.png) [Text Example](../console/quadplay.html?game=quad://examples/text&IDE=1) shows how to use `replace()`, `draw_text()`, `join()`, `format_number()`, and `draw_sprite_corner_rect()` to build text-heavy interfaces.
![](../examples/fontpreview/label128.png) [Font Preview](../console/quadplay.html?game=quad://examples/fontpreview&IDE=1) shows how to render text in various styles and acts as a tool for previewing fonts when creating new ones. Use the arrow keys to scroll down and see more examples in the program, and edit the `game.json` file to see a different font.
![](../examples/highscore/label128.png) [High Score Example](../console/quadplay.html?game=quad://examples/highscore&IDE=1) demonstrates `load_local()` and `save_local()` to maintain a high score list, with `push_mode()` for creating a popup dialog.
![](../examples/vaporwave/label128.png) [Vaporwave Example](../console/quadplay.html?game=quad://examples/vaporwave&IDE=1) uses pseudo-3D techniques of pre-rendered sprites and polygon meshes.
![](../examples/sproing/label128.png) [Sproing Example](../console/quadplay.html?game=quad://examples/sproing&IDE=1) shows a squash and stretch effect for transforming sprites.
![](../examples/perceptual_color/label128.png) [Perceptual Color](../console/quadplay.html?game=quad://examples/perceptual_color&IDE=1) shows the difference between `perceptual_lerp_color()` and `lerp()`.
![](../examples/zcar/label128.png) [Z-Car Example](../console/quadplay.html?game=quad://examples/zcar&IDE=1) draws a 3D wireframe car using 2.5D graphics and CRT orange-screen retro phosophor effects.
![](../examples/lift_team/label128.png) [Lift Team](../console/quadplay.html?game=quad://examples/lift_team&IDE=1) combines physics and dynamic constraints for an asymmetric platformer setup, where one player flies a helicopter that can lift the other player's mech.
![](../examples/anim_entity_example/label128.png) [Animated Entity Example](../console/quadplay.html?game=quad://examples/anim_entity_example&IDE=1) Entity animation example simplified from _Beat The Gobblins_.
![](../examples/bezier_eye_creature/label128.png) [Bezier Eye Creature](../console/quadplay.html?game=quad://examples/bezier_eye_creature&IDE=1) uses splines to create natural curves for a soft bodied creature.
![](../examples/sequence_demo/label128.png) [Sequence Demo (transition effect)](../console/quadplay.html?game=quad://examples/sequence_demo&IDE=1) Shows how to use the sequence function to create a transition effect, which is factored out into an easy to drop in library. The sequence function lets choreograph series of frame hooks one after the other.
![](../examples/gridmove/label128.png) [Grid Movement](../console/quadplay.html?game=quad://examples/gridmove&IDE=1) Atari style grid movement with smooth interpolation and wrapping, similar to PAC-MAN, Centipede, etc.
![](../examples/islands/label128.png) [Islands](../console/quadplay.html?game=quad://examples/islands&IDE=1) contains optimized implementations of per-pixel rendering and plausible water and sailboat simulation.
![](../examples/cards/label128.png) [Cards](../console/quadplay.html?game=quad://examples/cards&IDE=1) is an example of creating and manipulating a deck of cards, including `add_frame_hook()` for flipping animations.
![](../examples/dice/label128.png) [Dice](../console/quadplay.html?game=quad://examples/dice&IDE=1) uses `scripts/dice.pyxl` to show how to make and roll customizable 3D dice for virtual board games.
![](../examples/piano/label128.png) [Piano](../console/quadplay.html?game=quad://examples/piano&IDE=1) Using `pitch` with `play_sound()` to adjust frequency, and data-driven input testing.
![](../examples/spritestack/label128.png) [Spritestack](../console/quadplay.html?game=quad://examples/spritestack&IDE=1) is a little engine for Grand Theft Auto 2.5D rendering and physics of a 2D game using sprite stacking and camera perspective.
![](../examples/speedstreet/label128.png) [Speed Street](../console/quadplay.html?game=quad://examples/speedstreet&IDE=1) is the setup for a four-player competitive racing game inspired by [_Excitebike_](https://en.wikipedia.org/wiki/Excitebike) and Tony Hawk games, using: - Entity hierarchy - Simple custom physics - Orthographic 2.5D graphics - Coordinates with +Y pointing up - `override_color`
![](../examples/planetgen/label128.png) [Planet Generator](../console/quadplay.html?game=quad://examples/planetgen&IDE=1) uses built-in assets to create random 3D planets with moons, stars, atmosphere, and rings.
![](../examples/change_res/label128.png) [Change Resolution](../console/quadplay.html?game=quad://examples/change_res&IDE=1) is an example of using `set_screen_size()` to change resolution at runtime.
![](../examples/input/label128.png) [Input Example](../console/quadplay.html?game=quad://examples/input&IDE=1) is a demonstration and test of the full input API. It uses the cross-platform controllers and touch as well as the extended analog stick and mouse APIs. Shows how to completely hide the OS mouse cursor with `device_control("set_mouse_cursor", "none")`.
![](../examples/touch/label128.png) [Touch Example](../console/quadplay.html?game=quad://examples/touch&IDE=1) uses the mouse/touch API.
![](../examples/countdown/label128.png) [Countdown Example](../console/quadplay.html?game=quad://examples/countdown&IDE=1) shows how to used `local_time()` and perform time zone math.
![](../examples/hex/label128.png) [RPG Demo](../console/quadplay.html?game=quad://examples/hex&IDE=1) is an example of hex grids with coordinate system conversion, rendering, and click support.
![](../examples/tic_tac_toe/label128.png) [Tic Tac Toe](../console/quadplay.html?game=quad://examples/tic_tac_toe&IDE=1) shows how to take turns, and mix `gamepad_array`, `touch`, and `device_control("get_mouse_state")` to support mouseover/hover, touch screen, and gamepads in a single game user interface.
![](../examples/platformer/label128.png) [Platformer](../console/quadplay.html?game=quad://examples/platformer&IDE=1) is an example of classic 8-bit platforming physics, with jump, wall jump, wall slide, short jump, ledge forgiveness, hazards, and variable friction.
![](../examples/twin_analog/label128.png) [Twin Analog Example](../console/quadplay.html?game=quad://examples/twin_analog&IDE=1) shows how to use `device_control()` to access twin analog sticks and game controller triggers that are not standard on quadplay consoles.
![](../examples/kart/label128.png) [Kart Example](../console/quadplay.html?game=quad://examples/kart&IDE=1) is a perspective camera view with a textured ground plane similar to Space Harrier, Mario Kart, and Pilotwings.
![](../examples/warlock3D/label128.png) [Warlock Example](../console/quadplay.html?game=quad://examples/warlock3D&IDE=1) is 3D first person rendering similar to DOOM and Heretic, with shading, textured floors and walls, billboarded characters, pitch and yaw, and jumping. It demonstrates retro D-pad + shoulder button strafe, quadplay dual D-pad, conventional dual analog, and mouselook controls.
![](../examples/word_game/label128.png) [Word Game](../console/quadplay.html?game=quad://examples/word_game&IDE=1) Playful use of text and animated level transitions.
![](../examples/zoom2D/label128.png) [Zoom 2D](../console/quadplay.html?game=quad://examples/zoom2D&IDE=1) Simplified example of 2D zoom using `set_camera()`.
![](../examples/zoom/label128.png) [Zoom 3D](../console/quadplay.html?game=quad://examples/zoom&IDE=1) Simplified example of 3D perspective zoom using `set_camera()`. See also the Vehicle example.
![](../examples/private_view/label128.png) [Private Views](../console/quadplay.html?game=quad://examples/private_view&IDE=1) Private view example for online multiplayer using `set_screen_size()` and `VIEW_ARRAY`. Also shows how to streamline online game configuration using `start_guesting()`, `start_hosting()`, and `HOST_CODE`.
![](../examples/textspheres/label128.png) [Text Spheres](../console/quadplay.html?game=quad://examples/textspheres&IDE=1) Sample title screen animation converting text (from a pre-drawn PNG) into 3D shapes for animation.
![](../examples/midi_starrypad/label128.png) [MIDI Starrypad](../console/quadplay.html?game=quad://examples/midi_starrypad&IDE=1) Example of using the Donner Starrypad physical MIDI controller with , with all buttons, knobs, pads, and faders mapped.
![](../examples/midi_launchpad/label128.png) [MIDI Launchpad](../console/quadplay.html?game=quad://examples/midi_launchpad&IDE=1) Example of using the Novation Launchpad physical MIDI controller with and Sysex messages so that it acts as both an input and output display device.
![](../examples/midi_fcb1010/label128.png) [MIDI FCB1010](../console/quadplay.html?game=quad://examples/midi_fcb1010&IDE=1) Example of using the Behringer FCB1010 physical MIDI controller with with all default controls mapped.
![](../examples/midi_8x8/label128.png) [MIDI 8x8 Touch Jam](../console/quadplay.html?game=quad://examples/midi_8x8&IDE=1) Sample program for a MIDI controller jam, demonstrating the `midi_8x8.pyxl` helper script.
![](../examples/multitouch/label128.png) [Multitouch](../console/quadplay.html?game=quad://examples/multitouch&IDE=1) Example of `device_control()` for reading multitouch input on touch screens.
![](../examples/bezier_eye_creature/label128.png) [Bezier Eye Creature](../console/quadplay.html?game=quad://examples/bezier_eye_creature&IDE=1) Example of a library that uses a simple quadratic bezier + spring system to simulate bouncy cable/rope/arms.
![](../examples/anim_entity_example/label128.png) [Animation Entity Example](../console/quadplay.html?game=quad://examples/anim_entity_example&IDE=1) By way of an anim entity library, demonstrates how to read sprite frame timing from a sprite sheet authored in aseprite.
![](../examples/color_wheel/label128.png) [Color Wheel](../console/quadplay.html?game=quad://examples/color_wheel&IDE=1) shows the difference between `hsv()` and `artist_hsv_to_rgb()` hues and brightnesses.
### Inspiration Some games that were not made in , but which fit within the restrictions of quadplay and are sources of inspiration for their arcade graphics and gameplay follow. We've specifically determined that the main technical challenges of each of these can be met on .
- [TowerFall Ascension](http://www.towerfall-game.com/) - [Crawl](https://www.powerhoof.com/crawl/) - [Nuclear Throne](http://nuclearthrone.com/) - [Celeste](http://www.celestegame.com/) - [Undertale](https://undertale.com/) - [Monolith](https://store.steampowered.com/app/603960/Monolith/) - [Crypt of the Necrodancer](https://store.steampowered.com/app/247080/Crypt_of_the_NecroDancer/) - [Shovel Knight](https://yachtclubgames.com/shovel-knight/) - [Sonic Mania](https://www.sega.com/games/sonicmania) - [Spelunky Classic](https://spelunkyworld.com/original.html) - [Dwarf Fortress](http://www.bay12games.com/dwarves/) - [Contra]("https://en.wikipedia.org/wiki/Contra_(video_game)") - [Super Contra](https://en.wikipedia.org/wiki/Super_Contra) - [Kirby's Adventure](https://en.wikipedia.org/wiki/Kirby%27s_Adventure) - [Gradius](https://en.wikipedia.org/wiki/Gradius) - [Life Force](https://youtu.be/lgfTC3UVCVs) - [Gradius II](https://en.wikipedia.org/wiki/Gradius_II) - [Gradius III](https://en.wikipedia.org/wiki/Gradius_III) - [River City Ransom](https://en.wikipedia.org/wiki/River_City_Ransom) - [Double Dragon]("https://en.wikipedia.org/wiki/Double_Dragon_(video_game)") - [Super Double Dragon](https://en.wikipedia.org/wiki/Super_Double_Dragon) - [Castlevania II](https://en.wikipedia.org/wiki/Castlevania_II:_Simon%27s_Quest) - [Castlevania III](https://en.wikipedia.org/wiki/Castlevania_III:_Dracula%27s_Curse) - [Castlevania: Symphony of the Night](https://en.wikipedia.org/wiki/Castlevania:_Symphony_of_the_Night) - [Castlevania: Aria of Sorrow](https://en.wikipedia.org/wiki/Castlevania:_Aria_of_Sorrow) - [Castlevania: Dawn of Sorrow](https://en.wikipedia.org/wiki/Castlevania:_Dawn_of_Sorrow) - [Advance Wars](https://en.wikipedia.org/wiki/Advance_Wars) - [Advance Wars Dual Strike](https://en.wikipedia.org/wiki/Advance_Wars:_Dual_Strike) - [Out Run](https://en.wikipedia.org/wiki/Out_Run) - [Dig Dug](https://en.wikipedia.org/wiki/Dig_Dug) - [Batman](https://en.wikipedia.org/wiki/Batman:_The_Video_Game) - [Tetris](https://en.wikipedia.org/wiki/Tetris) - [Blades of Steel](https://en.wikipedia.org/wiki/Blades_of_Steel) - [Pac-Man](https://en.wikipedia.org/wiki/Pac-Man) - [PGA Tour Golf](https://en.wikipedia.org/wiki/PGA_Tour_Golf) - [Lode Runner](https://en.wikipedia.org/wiki/Lode_Runner) - [Star Fox](https://en.wikipedia.org/wiki/Star_Fox) - [Metroid](https://en.wikipedia.org/wiki/Metroid) - [The Legend of Zelda]("https://en.wikipedia.org/wiki/The_Legend_of_Zelda_(video_game)") - [Zelda II: The Adventure of Link](https://en.wikipedia.org/wiki/Zelda_II:_The_Adventure_of_Link) - [The Legend of Zelda: Link's Awakening](https://en.wikipedia.org/wiki/The_Legend_of_Zelda:_Link%27s_Awakening) - [The Legend of Zelda: A Link to the Past](https://en.wikipedia.org/wiki/The_Legend_of_Zelda:_A_Link_to_the_Past) - [Mega Man 2](https://en.wikipedia.org/wiki/Mega_Man_2) - [Super Mario Bros.](https://en.wikipedia.org/wiki/Super_Mario_Bros.) - [Super Mario Bros. 2](https://en.wikipedia.org/wiki/Super_Mario_Bros._2) - [Super Mario Bros. 3](https://en.wikipedia.org/wiki/Super_Mario_Bros._3) - [Rogue]("https://en.wikipedia.org/wiki/Rogue_(video_game)") - [NetHack](https://en.wikipedia.org/wiki/NetHack) - [Snake]("https://en.wikipedia.org/wiki/Snake_(video_game_genre)") - [Minit](https://store.steampowered.com/app/609490/Minit/) - [Chasm](https://bitkidinc.itch.io/chasm) - [Eliminator Boat Duel](https://www.youtube.com/watch?v=NM7ZNX0GUBI) - [The Eternal Castle](http://www.theeternalcastle.net/) - [DOOM]("https://en.wikipedia.org/wiki/Doom_(1993_video_game)") - [DOOM II](https://en.wikipedia.org/wiki/Doom_II) - [Heretic]("https://en.wikipedia.org/wiki/Heretic_(video_game)") - [Dark Forces](https://en.wikipedia.org/wiki/Star_Wars:_Dark_Forces) - [Marathon]("https://en.wikipedia.org/wiki/Marathon_(video_game)") - [Ultima Underworld](https://en.wikipedia.org/wiki/Ultima_Underworld:_The_Stygian_Abyss) - [Loom]("https://en.wikipedia.org/wiki/Loom_(video_game)") - [Day of the Tentacle](https://en.wikipedia.org/wiki/Day_of_the_Tentacle) - [Pilotwings]("https://en.wikipedia.org/wiki/Pilotwings_(video_game)") - [Super Mario Kart](https://en.wikipedia.org/wiki/Super_Mario_Kart) - [Sonic Drift](https://en.wikipedia.org/wiki/Sonic_Drift) - [F-Zero]("https://en.wikipedia.org/wiki/F-Zero_(video_game)") - [Space Harrier](https://en.wikipedia.org/wiki/Space_Harrier)
These are games that would fit the restrictions of except they use native vector displays in their original implementations: - [Asteroids]("https://en.wikipedia.org/wiki/Asteroids_(video_game)") - [Battlezone]("https://en.wikipedia.org/wiki/Battlezone_(1980_video_game)") - [Space War](https://en.wikipedia.org/wiki/Spacewar!) - [Tempest]("https://en.wikipedia.org/wiki/Tempest_(video_game)") These are games whose style and gameplay would fit, but that would exceed the resolution limitations of : - [Axiom Verge](http://www.axiomverge.com/) - [Into the Breach](https://subsetgames.com/itb.html) Programming Model ------------------------------------------------------------------------------------ The **PyxlScript** programming language is designed to make compact, readable games. It blends Python, Lua, and JavaScript syntax. Indentation and newlines signify blocks and ends of statements. The syntax is based on math notation and looks like pseudocode. Variables are explicitly declared and dynamically typed. There's lexical scope, first class functions, and literal expressions for objects and (zero-based) arrays. It is decidedly function-based instead of object-oriented. Most of PyxlScript programming is typical of any imperative, high-level language. It is similar to Python, Lua, JavaScript, C#, and Java. There are three special parts of the PyxlScript programming model that are designed to simplify programming arcade games. These special features are [*modes*](#modes), [*2.5D graphics*](#2.5dgraphics), and [*frame hooks*](#framehooks). ### Modes Your program consists of one or more *modes* which are game states that the player will experience. Common modes are "Play", "Title", "Inventory", "CutScene", and "GameOver". Use `set_mode()` to change which mode the program is in. Clicking on the "Modes" label on the left side of the IDE draws a diagram showing the modes and transitions for your program. ![Mode diagram for the Quadpaddle sample game, as shown in the IDE.](modes.png width=50% style="image-rendering:auto") For each mode, you specify what code runs every *frame*. This is the body of the inner loop. Modes have capitalized names and are defined in a script file with the same name. The simplest implementation of a mode is just this per-frame code. The `mode_frames` variable tracks the number of frames since this mode started. `set_mode()` changes the mode and leaves a note in the debugging output explaining why the mode changed. It is also used by the IDE to visualize the state machine of your program and label the transitions, as in the diagram above. `get_mode()` returns the current mode, which acts like a special constant. You can also separate the mode file into different sections: - Top level: runs once when the game is first started. Usually just variable and function declarations. - `enter`: event that runs when this mode becomes active by another executing `push_mode()` or `set_mode()`. `enter` may be followed by an argument list that will be filled with the values passed to `set_mode()` or `push_mode()`. - `frame`: event that runs every frame while this mode is active. Put your drawing and simulation code here. - `leave`: event that runs when the game changes to another mode by executing `pop_mode()` or `set_mode()` itself. - `pop_mode`: event that runs when the game returns to this mode from another by `pop_mode()`. Note that `leave` and `pop_mode` do not run on `reset_game()` or `quit_game()`. You can think of these as callbacks, although they are not regular functions and can only be invoked directly by the console. You may be familiar with the concept of a [state machine](https://en.wikipedia.org/wiki/Finite-state_machine), also called a finite automata or flow chart. Your program is a state machine with the modes as the states. !!! Note: Transition Hints The IDE automatically constructs the mode diagram from your program. It is [impossible](https://en.wikipedia.org/wiki/Undecidable_problem) to do this for all programs, but it can usually do a good job. When the automatic algorithm misses a link, you can give it a hint using a commented out `set_mode()` call in your mode file. For example, `// set_mode(GameOver) because "0 lives"` will add a link from the current mode to the `GameOver` mode. The syntax of a mode with multiple sections is similar to [Markdown](https://en.wikipedia.org/wiki/Markdown) format: 1. The name of the mode on the first line 2. A line of `=`, `═`, or `⚌` characters to underline the mode name 3. The top-level code 4. The sections, each with a name underlined by `-`, `-`, `─`, `—`, `━`, or `⎯` characters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript Menu ═══════════════════════════════════════════════════════════ // A good place to declare state only used by this mode let selection const choices = ["Continue", "Show Map", "Abandon Mission"] enter(value) ─────────────────────────────────────────────────────────── // Can also be enter without any arguments or parentheses // Reset any state selection = 0 play_sound(menuOpen) frame ─────────────────────────────────────────────────────────── set_background(rgb(1, 1, 0)) // Up and down to change the choice selection = (selection + 3 + gamepad_array[0].y) mod 3 // Draw the menu let pos = screen_size / 2 - xy(0, 50) draw_text(menuFont, "→", pos + xy(-50, 16 selection), #00F) for c in choices: draw_text(menu_font, c, pos, #000) pos.y += 16 // Button press to choose if gamepad_array[0].aa: if selection == 0: set_mode(Play) because "Chose play" else if selection == 1: set_mode(Map) because "Chose map" else: push_mode(ConfirmDialog) because "Chose abandon" pop_mode(confirmed) from ConfirmDialog ─────────────────────────────────────────────────────────── if confirmed: set_mode(Title) because "Confirmed abandon" leave ─────────────────────────────────────────────────────────── play_sound(menuClose) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [A sample in-game menu mode using all of the optional mode sections.] If you're accustomed to object-oriented languages, then you might think of the whole program as: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Java public class Menu extends Mode { // These variables are the top-level code protected int selection; public void enter(int value) { selection = 0; play_sound(menuOpen); } public void frame() { selection = (selection + gamepad.y + 3) % 3; } drawChoices(selection); if (gamepad.a) { if (selection == 0) { set_mode(play); } ... } } public void pop_mode(boolean confirmed, Mode from) { if (from == confirmDialog) { ... } } public void leave() { play_sound(menuClose); } } Mode menu = new Menu(); ... menu.enter(); while (get_mode() == menu) { menu.frame(); } menu.leave(); ... ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [This is the rough equivalent of the menu mode PyxlScript code in an object-oriented language. You don't write all of this boilerplate in PyxlScript!] #### Unmatched Push and Pop Mode Push and pop on modes aren't always paired, because sometimes an exceptional change to state flow occurs. ********************************************************************* * .-----. push_mode(B) .-----. set_mode(C) .-----. * ( A )--------------->( B )-------------->( C ) * '-----' '-----' '-----' ********************************************************************* [Actual code executed with unmatched push/pop mode calls.] For example, if mode `A` executes `push_mode(B)`, and then `B` executes `set_mode(C)` (instead of popping), the order of events triggered will be: 1. `B: enter` 2. `B: leave` 3. `A: pop_mode from B` 4. `C: enter` This is as if `B` had run the `pop_mode` and then `A` directly transitioned to C. ********************************************************************* * push_mode(B) * .-----.----------------->.--+--. .-----. * ( A ) ( B ) ( C ) * '--+--'<-----------------'-----' '-----' * | pop_mode() ^ * | | * '----------------------------------------------' * set_mode(C) ********************************************************************* [Equivalent behavior triggered by the actual code, so that PyxlScript can ensure matched push/pop] ### 2.5D Graphics Graphics in PyxlScript are similar to other 2D rendering APIs. You can set the background color, draw basic shapes and text, and render sprites with some features such as flipping and rotation. There's special support for drawing whole tile maps as well. All drawing commands support an [*alpha channel*](https://en.wikipedia.org/wiki/Alpha_compositing) for transparency. What is special about the 2.5D graphics in PyxlScript is that each drawing command has an associated *z value*. If two commands are at the same z value (after camera and transform are applied), then whichever comes second will draw on top of the other one. Commands that have explicit, different z values will render from low to high values. The z value for a drawing command can be set using the `z` argument. If the `z` argument is not specified, then the `pos.z` argument is used. If there is no `pos.z` value, then the pre-transform value of 0 is used. This means that you can issue drawing commands in whatever order is convenient and let the rendering system organize them as you wish. It also makes it easy to render orthographic games such as [_Double Dragon_]("https://en.wikipedia.org/wiki/Double_Dragon_(video_game)"), isometric games such as [_Populous_]("https://en.wikipedia.org/wiki/Populous_(video_game)"), and other games with semi-3D effects. The included [_Speed Street_](#playable) game demonstrates this. Although the screen is cleared to the background color every frame, all of the drawing commands are stored for one previous frame. This allows dialogue and inventory screens to re-render the previous mode's last frame using `draw_previous_mode()` so that they can render on top of them. This rendering retains z-order, so you can even inject new graphics in between objects from a previous mode's frozen image. ### Frame Hooks In addition to the mode's frame code, you can dynamically register hook functions to run at the beginning of every frame using `add_frame_hook()`. These are useful for timers and animations. For example, you can use this to draw an explosion every frame until it dissipates or to shake the screen for three frames. Frame hooks can be tied to only run in a specific mode or to run in all modes. You can also specify a separate hook to run each frame from the one to run when it expires. The closest relative to frame hooks is JavaScript's `setTimeout()` function, although it is much more limited. ### Network Multiplayer All games automatically support *shared-screen* network multiplayer through the built-in system menu's Online options. The programming model is identical for local and network games and the runtime handles everything for you. There are no explicit synchronization messages or connections required per game. See the Network section of the API for ways that games can customize online play. This includes simplifying the user experience for creating and joining games and *private per-player views*. Coming from... ------------------------------------------------------------------------------------ Here are some tips to get you started quickly when you're coming to PyxlScript from other popular programming languages. ### PICO-8 If you're used to programming in PICO-8 Lua, make these changes for the PyxlScript: - Create a *mode* for your project. - Put your top-level and `_init` code into the top level - Put your `_update` and `_draw` combined into the `frame` section - Declare variables with `let` and `const` instead of `local` - Arrays start at 0 - Use `size(x)` instead of `#x` - Use `:` between the beginning of a block and its body - Blocks have no `end` statements and indentation is significant - Use `:` within object literals instead of `=` - It is safe to mutate containers inside iterator loops - You must specify a font for `draw_text()`. [Several](assets.md.html#fonts) are provided. - Enjoy the overloaded operators, array functions, z-order, and alpha blend ### Python The PyxlScript language feels a lot like Python, with these changes: - Comments begin with `//` instead of `#` - Explicitly declare variables with `let` and `const` - Use `size(x)` instead of `len(x)` - Use `debug_print` instead of `print` - Use `for low <= i < high` instead of `for i in range(low, high)` - The `==` operator is pointer equality (Python `is`). Use `equivalent` for object structural equality tests. - Variables have block scope instead of function scope - Function variables have lexical scope (no `global` statement required) - No integer division (use `floor`) - No comparison operator chaining - Use `keys(obj)` and `values(obj)` instead of `obj.keys()` and `obj.values()` (although you almost never need either due to the `for` loop syntax) - Use `for value at key in obj:` instead of `for key, value in obj.items():` - Use `iterate()` instead of `map()`, `filter()`, or list comprehensions - Use `x = if t then a else b` for Python `x = a if t else b` - Use `true`, `false`, `nil` instead of `True`, `False`, `None` - Use `def foo(a, b, ...rest)` instead of `def foo(a, b, *rest)` - Use `foo(a, b, ...rest)` instead of `foo(a, b, *rest)` - Use `[...array1, ...array2]` or `extended(array1, array2)` instead of `array1 + array2` - Use `{...objA, ...objB}` for Python `listA + listB` or `{**objA, **objB}` - Use `extend(a, b)` for Python `objA.update(objB)` and `listA.extend(listB)` - Use `extend(array1, array2)` instead of `array1 += array2` - Use `def foo(x default y)` instead of `def foo(x = y)` - Default function parameters are evaluated once per call instead of once per program, so they do not capture and share values ### JavaScript The PyxlScript language feels a lot like JavaScript strict mode, with these changes: - Use PyxlScript `==` in place of JavaScript `===` - Use `:` and indentation instead of `{}` for defining blocks - Use `def` instead of `function` - No `;` required, except for multiple statements on the same line - Use `size(x)` instead of `x.length` - Use `for v at k in object:` instead of `for (k in object)` - Use `for v in array:` instead of `for (v of array)` - Use `for k < size(array):` instead of `for (k in array)` - It is safe to mutate containers inside iterator loops - There are no anonymous functions - Function names are not lifted to the containing function's scope - Use `x = if t then a else b` for JavaScript `x = t ? a : b` - Use `debug_print` instead of `console.log` - Use `a default b` instead of `a ?? b` - Enjoy the overloaded operators for vectors Online Multiplayer ==================================================================================== Quadplay supports safe online play for shared-screen multiplayer games over the internet. This is similar to the Steam "Remote Play Together" feature. Your quadplay *Host Code* is a series of six short words generated by quadplay. This secures your gaming session and lets your friends connect. Only share your Host Code with people you want to play with. You can change to a new, random Host Code at any time. It is almost impossible for someone to guess your Host Code--they would be more likely to win the lottery 100 times first. Online play requires the hosting player to send their secret Host Code to the other player _outside_ of the game. You can do this by text, email, telephone, Discord, Slack, and so on. The hosting player starts hosting either from the quadplay launcher's Online tab or from the pause menu's Online entry. The other players then connect from the launcher's Online tab or the pause menu's Online entry. Your computer will remember the Host Codes of other computers you have connected to recently. Although quadplay already restricts play to people you have trusted to exchange a Host Code with, for privacy and further safety for children, there is no in-game text or voice chat in quadplay. Use a third-party gaming chat service such as Discord, SteamChat, Overtone, TeamSpeak3, Mumble, or Slack if you wish to have text and voice chat. You can configure a 7-letter, all capital Online Name for your computer that is used to identify you. This will help you to know which Host Code goes with which person, and tells the host who you are when you connect. When running a kiosk or setting up a local development station on which you wish to prevent players from accessing the online multiplayer feature, launch the quadplay script with the `--offline` flag. Tools ==================================================================================== Development Environment ----------------------------------------------------------------------------- The browser development environment supports several alternative layouts, which are selected from the buttons on the upper right of the screen. I expect most experienced programmers to use external editors such as Visual Studio Code and Photoshop and put the development environment in Full Screen or Test layout. Most newer programmers will directly use the built-in integrated development environment within the browser in the Develop and Debug modes. ![](../console/icons/ui-fullscreen.png width=32) Full Screen : Play your game full screen. Good for playtesting. ![](../console/icons/ui-touchscreen.png width=32) Touch Screen : Play your game with single-player mobile touch controls. ![](../console/icons/ui-test.png width=32) Test : Run with a large emulator and the debugger visible. This is good for using on the left or right half of the screen when running an external editor on the other half. ![](../console/icons/ui-debug.png width=32) Debug : Pixel-doubled emulator and compressed text editor. Good for debugging graphics code and tuning constants. ![](../console/icons/ui-develop.png width=32) Develop : 1:1 pixel emulator with text editor and debugger. Good for working on gameplay code. ![](../console/icons/ui-edit.png width=32) Edit : Full-screen code editor. Good for writing lots of code. ![](../console/icons/ui-ghost.png width=32) Ghost : Show-off mode for livestreaming, with the full screen code editor and the game running behind it full screen, like a ghost. Press the Play button to switch input to the game in the background. Game touch input is obviously not available in this layout. There's also a special "kiosk" mode that the `quadplay` script can run in for removing the top control bar when running on an arcade machine or other public display mode. Emulator ------------------------------------------------------------------------------------ The main tool is the console emulator, which you can launch with the `quadplay` command at the command line. The emulator accepts keyboard and game controller input, as well as directly pressing its buttons on screen for mobile. On a gamepad, you must press a button before directional input will register in a web browser. Pressing the "Play" button on the editor automatically transfers keyboard focus to the emulator. The emulator has special keyboard keys for: - F5: run - F6: screenshot - F8 or Ctrl+G: start/stop recording GIF [^safariGIF] - F10: step one frame - Ctrl+Break or Ctrl+C - Ctrl+R or Ctrl+F5: reload On macOS, either Control or Command can be used for "Ctrl". [^safariGIF]: On Safari, you have to explicitly allow popups for the website for this to work. Go to Preferences --> Websites --> Pop-up Windows and check "allow" for `localhost` (or whatever address you access from). ### Controls presents virtual controllers for four players. You can use the on-screen buttons on mobile and touch screens for a single player, a keyboard for up to two players, and connect most USB or Bluetooth game controllers for up to four players. Physical controllers work on all operating systems and mobile devices. For two-player keyboard input, you can use two USB or Bluetooth keyboards on the same computer, or have both players share a single keyboard. The order of physical controllers can be changed from the Controls panel in the quadplay debugger. This is needed when configuring an arcade kiosk with physical positions for the players, or to allow mixed keyboard and physical controller play.
![](../console/xbox_controller.png style="border:none; image-rendering:auto" width=50%) ![](../console/gamepad.png style="border:none; image-rendering:auto" width=50%) ![](../console/keyboard.png style="border:none; image-rendering:auto" width=75%)

Physical controllers give the best experience. We have successfully tested the following, and most are autodetected by quadplay:
![](sn30-pro.jpg style="image-rendering:auto")
[8BitDo SN30 Pro](https://amzn.to/35XGEl9) (SN or Classic)
![](sn30.jpg style="image-rendering:auto")
[8BitDo SN30](https://amzn.to/38ay83F)
![](zero2.jpg style="image-rendering:auto")
[8BitDo Zero 2](https://amzn.to/2Ny5nWN)

![](xboxone.jpg style="image-rendering:auto")
[Microsoft Xbox One Wired Controller](https://amzn.to/36Y7CdR)
![](xboxone.jpg style="image-rendering:auto")
[Microsoft Xbox One Wireless
Controller for Windows 10](https://amzn.to/35U36vz)
![](xbox360.jpg style="image-rendering:auto")
[Microsoft Xbox 360 Wired Controller](https://www.microsoft.com/accessories/en-ww/products/gaming/xbox-360-controller-for-windows/52a-00004)

![](joy-con.jpg style="image-rendering:auto")
[Nintendo Joy-Con](https://amzn.to/30recqP)
![](switch-pro.jpg style="image-rendering:auto")
[Nintendo Switch Pro Controller](https://www.amazon.com/Nintendo-Switch-Pro-Controller/dp/B01NAWKYZ0)
![](thrustmaster.jpg style="image-rendering:auto")
[Thrustmaster T-Flight HOTAS X Flight Stick](https://amzn.to/2Tvf8J4)

![](xarcade-dual.jpg style="image-rendering:auto")
[X-Arcade Dual](https://shop.xgaming.com/collections/arcade-joysticks/products/x-arcade-dual-joystick-usb-included)
![](genesis.jpg style="image-rendering:auto")
[retro bit Sega Genesis](http://retro-bit.com/sega-collaboration)
![](m30.jpg style="image-rendering:auto")
[8bitdo M30 2.4G](https://www.8bitdo.com/m30/)

![](dualshock4.jpg style="image-rendering:auto")
[PlayStation DualShock 4 Wireless Controller](https://www.playstation.com/en-us/explore/accessories/gaming-controllers/dualshock-4/)
When trying to get a new controller working, first use https://html5gamepad.com/ to verify that your OS and web browser can see it. Then use the controller with quadplay. 8BitDo controllers work in wired, wireless, and [XPad](https://support.8bitdo.com/xpad.html) mode. They seem most reliable on Windows and Linux. Be sure to install the latest firmware update from the 8BitDo website. Left and right Joy-Cons work individually in horizontal mode. They cannot be joined as on Switch. The Switch Pro controller and the SNES and NES controllers work normally. On macOS, Xbox wired controllers require a free, [open source driver](https://github.com/360Controller/360Controller/issues). Xbox wireless controllers do not work on Mac. Different controllers work with different browsers on macOS. For example, Xbox 360 controllers work well with Chrome and the Nintendo SNES controller works with Safari. On macOS PS4 controllers work in Bluetooth mode (Xbox wireless controllers may also work in Bluetooth mode). Press the PS and SHARE buttons simultaneously to pair. If you have any problems, delete `/Library/Preferences/com.apple.Bluetooth.plist`, shut down and then start (do not warm reboot) and then re-pair. Stadia and [Logitech](https://www.logitechg.com/en-ca/products/gamepads/f310-gamepad.html) controllers are intended to work but I have not tested them. Please let me know if you are using them successfully. All browsers have controller support, but Chrome seems to support the most different controllers reliably on all platforms. Input | Player 1 Key| Player 2 Key| Xbox | Playstation| SNES | Switch Pro :----------:|:-----------:|:-----------:|:----------:|:----------:|:------:|:-----------: | W or ↑ | I | ▲ | ▲ | ▲ | ▲ | A or ← | J | ◀ | ◀ | ◀ | ◀ | S or ↓ | K | ▼ | ▼ | ▼ | ▼ | D or → | L | ▶ | ▶ | ▶ | ▶ ⓐ | B or space | / | Ⓐ | ╳ | Ⓑ | Ⓑ ⓑ | H or enter | ' | Ⓑ | ◯ | Ⓐ | Ⓐ ⓒ | V | . | Ⓧ | ▢ | Ⓨ | Ⓨ ⓓ | G | ; | Ⓨ | △ | Ⓧ | Ⓧ ⓔ | L.shift | N | LB | L1 | L | L ⓕ | C or R.shift| alt/option | RB | R1 | R | R ⓠ | 1 or Q | 7 || Share | Select | ⓟ | 4 or P | 0 | ☰ | Options | Start | [Sample controller mappings. supports many more, including JoyCons and flight sticks.] After launching a quadplay game, you must press a button on the controller for the web browser to allow access to it. quadplay will automatically detect the controller and change to appropriate button prompts for most standard devices, including Xbox, PlayStation, Switch, Stadia, and Thrustmaster controllers. If your device is not mapped correctly, just enter the pause menu on any game and select "Set Controls". I've found that the easiest Bluetooth controller to pair on macOS is a Switch JoyCon. Turn on Bluetooth on your Mac and open the Bluetooth preferences. Hold the controller horizontally and hold the tiny sync button [you have to take off the black wrist strap holder temporarily to access this button]. You'll see the controller appear on your Mac. Click on its name and select "connect". When you want to re-pair the controller with your Switch, just slide it back onto the Switch and it will sync. 8BitDo controllers seem to work very reliably on Windows and Linux but are inconsistent on macOS. The [X-Arcade](https://shop.xgaming.com/) controllers work with quadplay. If you have a recent (2020 or later, or with the Tri-Mode PCB upgrade) X-Arcade controller, set XInput Mode 4 with the switch on the back. [Install the latest firmware](https://support.xgaming.com/support/solutions/articles/12000051593-x-arcade-tri-mode-firmware-update-download-instructions), version 19.4 or later. On macOS, also install the open source [Xbox360 controller](https://github.com/360Controller/360Controller/releases) driver. The Chromium-based browsers (Chrome, Edge, and Brave) provide the most reliable mapping. Safari and Firefox have inconsistent results. #### Arcade Controller Mappings The X-arcade default mapping is below. For other operating systems, browsers, or older controllers, just use keyboard mode and program the buttons to match the quadplay keyboard controls. ![On Windows, put your X-Arcade in X-input mode 4 and it will automatically provide this mapping.](xarcade-mapping.png style="border:none; image-rendering:auto" width=50%) ![When using an X-Arcade controller in keyboard mode 2, program it like this.](xarcade-keyboard.png style="border:none; image-rendering:auto" width=50%) Other ten-button arcade fightsticks have two common button layouts. The symmetric ECDF layout used by inexpensive Honcam and PXN sticks puts the "shoulder" buttons that correspond to ⓔ and ⓕ on the left and right of the other "face" buttons. The CDEF layout used by more expensive Mayflash, Hori, eTokki, Qanba, and other sticks puts the "shoulder" buttons to the right of the "face" buttons. The appropriate button prompts will appear in quadplay for each one, but you'll have to change which fingers you use to play games depending on the physical layout. ![Symmetric ECDF fightstick layout.
(Example only; this controller has not been tested with quadplay)](symmetric-arcade.jpg width=80% style="image-rendering:auto") ![Asymmetric CDEF fightstick layout.
(Example only; this controller has not been tested with quadplay)](right-arcade.jpg width=80% style="image-rendering:auto") #### Dual D-Pad When `dual_dpad` is enabled (in the game.json file or via the project checkbox), player 1's _physical_ controller right stick can override the the player 2 (`gamepad_array[1]`) _virtual_ controller D-pad. This allows you to make digital twin-stick games that work with both default keyboard controls and a single modern dual-stick controller. ### Analog Sticks Quadplay exposes system features beyond the standard quadplay capabilities using the `device_control("get_analog_axes", ...)` function for low-level system access. You can read analog sticks, steering wheels, foot pedals, flight sticks, etc. directly using this API. Games that use these features will not work on all quadplay devices. But, it is a great way to program for a specific platform that you have in mind or have built. ### Resolution The emulator screen tries to maintain an integer multiple of the native pixel resolution to prevent distortion of pixel art. When running on a very high resolution screen (greater than 2.5x), it relaxes this constraint to use the full screen effectively. As a result, the emulator is pixel-perfect on small displays and fills the screen on large ones. supports the following resolutions, all with 4:4:4:4 RGBA color and 60 Hz refresh: - 640x360 (½ 720p, ⅓ 1080p, ¼ 1440p) - 384x224x4 = 768x448, for network multiplayer private views - 320x180x4 = 640x360 (½ 720p), for network multiplayer private views - *384x224* (default and recommended) - 320x180 (¼ 720p, ⅙ 1080p, ⅛ 1440p) - 192x112 - 128x128 - 64x64 For reference when importing assets or designing games inspired by previous work, here are the resolutions of classic and retro machines: - 960x544 PlayStation Vita - 800x480 Raspberry Pi official 7" touchscreen - 640x480 PiBoy DMG - 480x320 Odroid Go Advance - 480x272 PlayStation Portable - 480x270 Hyperlight Drifter - 400x240 Playdate, 2DS, 3DS - 384x224 - 320x240 "240p", QVGA, TowerFall Ascension - 320x180 Celeste and other Switch pixel art games - 320x200 VGA, including Amiga 500 and Commodore 64 - 280x192 Apple II - 256x240 NES and TurboGrafx-16 - 256x224 SNES - 256x192 Nintendo DS and Sega Master System - 240x160 Game Boy Advance - 240x136 TIC-80 - 160x192 Atari 2600 - 160x144 Game Boy Color and Sega Game Gear - 160x102 Atari Lynx - 128x128 Pico 8 quantize✜ --------------------------------------------------------------------------------------------- [quantize✜](../tools/quantize.html target="_blank") is a standalone browser tool for converting any image format that your browser can load into a sRGB8 PNG for . While the console emulator can load any PNG file, if you convert them using quantize✜ you will have control over the color reduction process and be able to produce much smaller files for distribution. fontgen✜ --------------------------------------------------------------------------------------------- [fontgen✜](../tools/fontgen.html target="_blank") is a standalone browser tool for producing rough font images from fonts installed on your local computer. You can then screen capture these and edit them in your favorite pixel editor to clean them up. fontpack✜ --------------------------------------------------------------------------------------------- [fontpack✜](../tools/fontpack.html target="_blank") is a standalone browser tool for taking a font PNG file with regular tiles (such as the one produced by fontgen✜) and packing it tightly. It will generate the output PNG and the `.font.json` file for you. fontpack✜ can also show what the generated characters will look like when loaded by . If the generated ones look good, _do not put them in the .png_. Instead, store your file with lots of empty space so that it will compress better, and let regenerate them at load time. Only for ones where generation produces poor quality do you need to explicitly draw them. Usually large fonts will generate characters well but for very small fonts you must hand-tweak characters with accents or button prompts to get a readable result in a few pixels. scalepix✜ --------------------------------------------------------------------------------------------- [scalepix✜](../tools/scalepix.html target="_blank") is a standalone browser tool for upscaling a font or sprit PNG file to twice the resolution in each dimension, for upscaling existing assets or importing NES, PICO8, and Game Boy assets. (insert api.md.html here) Project Files ============================================================================================= When importing assets using the IDE as JSON or raw PNG, MP3, or TMX files, the subdirectories `journal`, `screenshots`, `metadata`, `backlot` and `graveyard` are excluded. !!! supports the extended [WorkJSON](https://github.com/morgan3d/workjson) format, which permits C++ style comments, hexadecimal numbers, IEEE 754 constants, raw decimals, leading plus signs, trailing commas, and multiline backquote JavaScript strings within JSON files for convenience when editing and debugging. This is not legal in strict JSON and may be incompatible with other tools. Game JSON --------------------------------------------------------------------------------------------- The project file is a JSON file describing all of the parts of the program. The project file and all of its dependencies are reloaded every time that you press play or reload in the IDE. All of the `.game.json` properties can be modified through the user interface in the IDE using buttons, textboxes, and sliders. Certain advanced changes require directly editing the JSON files with the built-in editor. External editors can also be used to edit any data and will automatically sync. An example project is below, annotated with comments explaining the options. Project files should have the double extension `.game.json`. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JavaScript { "title": "Your Game", "developer": "Your Name or Company", "copyright": "© Year Whomever", "license": "Distribution and source code license", // Not yet supported. These files will be loaded and inserted at the top // of your code, before the constant and asset definitions. "packages": ["https://someaccount.github.io/someproject/somefile.package.json", "somefileinyourproject.package.json"], // Documents for players or for yourself as a game developer, such // as a TODO list, game design doc, manual, or library specification. // These can be HTML, Markdeep (md.html), Markdown (.md), or text (.txt) format "docs": [ "gdd.md.html", "todo.md" ], // Files that form the global scope. Put most of your code in these. They are // loaded in order. "scripts": ["utils.pyxl", "juice.pyxl", "https://someaccount.github.io/someproject/inventory.pyxl"], // The name of the mode at which the program should begin. "start_mode": "Init", // These are the names of modes, which must start with a capital letter and // be legal identifiers in PyxlScript. These correspond to files that must // be stored in the same path as the project file. So, "Init" is in "Init.pyxl". // // Modes become global constants in your program. "modes": ["Init", "Play" "Credits"], // Assets are bound as global constants in your program. // They also properties of the global ASSETS object so that // they can be referenced by string names. "assets": { // Asset names must be legal PyxlScript identifiers "mysprite": "mysprite.sprite.json", "myfont": "arial.font.json", "boom": "boom.sound.json", "overworld": "overworld.map.json" }, // The text added after the date to screenshot filenames "screenshot_tag": "Your Game", "version": 1.0, "credits" : { // This is for major developer credits to appear // in a large font at the top. "main": [ "left", "right", ... ], // For code licenses, e.g., "wfc.pyxl" : "Copyright 2018 that dude" // Assets are automatically handled from their own .json files. The // credits renderer can aggregate files that share licenses when // presenting them. "code": { "file or element": "license", ...}, // e.g., "Dedicated to Wolfgang. Thanks to Fran for the catering." "extra": "" }, // Defined for build scripts, but not used directly by the IDE or // automatic credits in the current version of quadplay "links": { "game" : { // Main website for the game "www": "https://game...", // Arbitrary platforms "twitter": "@game...", "facebook": "...", "instagram": "..." }, // Same as above "developer" : ... } }, // Properties affecting your game description in the launcher program: "min_players": 1, "max_players": 1, "cooperative": false, "competitive": false, "achievements": false, "highscores": false, "description": "Description of your game in about 22 words.", // False by default. If true, P1's right analog stick can override P2's // D-pad. This is useful for making single player dual D-pad games that // automatically work on both keyboard (WASD + IJKL) and physical game // controller. You can also make controller-only dual analog stick // games using device_control("get_analog_axes", ...) "dual_dpad": false, // False by default. If true, this game will send MIDI sysex messages // and requires elevated permissions from the web browser that will // be prompted for at start. "midi_sysex": false, // Other global constants. You could define these in a global source code file // as well. The constants specified here have immutable *values*, not just immutable // bindings, and are available to editing tools. These are bound as global // variables and are also properties of the global CONSTANTS object so that // they can be referenced by string names. "constants": { // If the number value is in quotes it will be parsed. // This allows degrees, percents, infinity, and nan. // Raw numbers, strings, and booleans are permitted but // restricted to JSON limitations. "player_speed": {"type": "number", "value": "3"}, "background": {"type": "rgb", "value": {"r": {"type": "number", "value": 0.8}, "g": {"type": "number", "value": 0.8}, "b": {"type": "number", "value": 0.8}}, "data": {"type": "object", "value": { "height": {"type": "number", "value": 1}, "foo": {"type": "object", "value": {"a": {"type": "boolean", "value": true}}, "starmap": {"type": "raw", "url": "stars.yml"}, "name": "Destrier", "knight": {"type": "object", "value": { "upgrades": {"type": "array", "value": [10, 7, 3, 1]}, "hp": 50, "inventory": {"type": "object", "value": {"food": 4, "sword": "a sword"}}}}, "start_role": {"type": "reference", "value": "knight"}, "numbers": {"type": "array", "value": [ {"type": "number", "value": "10"}, {"type": "number", "value": "infinity"}]}, "level_table": {"type": "table", "url": "level_table.csv"}, "book_text": {"type": "string", "url": "alice.txt"} }, // The default Y axis increases down the screen, like old arcade games. // Set this to true for a modern vertical axis good for physics games. // You can also invoke set_transform() in code. "y_up": false, // Supported sizes: // // 384 x 224 (maximum, native, default) // 320 x 200 (VGA), // 192 x 112 (half resolution), // 128 x 128 (PICO-8), // 64 x 64 (lowrezjam and nano JAMMER) // // The non-native screen sizes may render with substantial black // borders on some implementations to ensure integer pixel scaling. "SCREEN_SIZE": {"x": 384, "y": 224}, } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Each game must also contain: - 64x64 `label64.png` box art image in the same folder as the `.game.json` file. This is used for display in the launcher. - 128x128 `label128.png` box art image in the same folder as the `.game.json` file. This is used for the open dialog. - 1152x1120 `preview.png` image in the same folder as the `.game.json` file that contains 6x10 frames of 192x112 video at 20 fps (3 seconds total) used for the preview video in the launcher. You can create these images however you wish, including manually drawing them. The IDE has some tools to automate the process. You can create the `label128.png` and `label64.png` images by pressing *Shift+F6* while your game is playing. It will capture a 128x128 region from the center of the screen and resize it using bilinear interpolation to create the 64x64 image, and then save them with the appropriate filenames in the game's directory. Edit and compress these images as you would a sprite. You can create the `preview.png` image by pressing *Shift+F8* while your game is playing. It will record 3s of video, process it to the correct resolution, and save it to disk. Compress it as you would a sprite. Seek to keep the total size around 100 kB for the image by choosing content that compresses well and using a good compression tool. If the image is much larger, then there will be delay for players browsing your game in the discover tab. Assets and constants are both data used by your program. Assets include standalone JSON metadata files of their own to make them easy to reuse between projects and are usually relatively large media files. Constants have metadata embedded directly in the game to reduce files and maintenance and are usualy relatively small individual data. The `table` and `raw` constant types can use external files as constants, but still have metadata embedded in the game itself and use raw data instead of media formats. ### Constants Unlike constants declared in code, the constants in the `.game.json` file appear in the IDE in the project list and have GUI editors. They are immutable at runtime by the program, but can be changed by the GUI editors for debugging and tuning at runtime. For some formats (e.g., tables, text), there are similarities between *constants* and *assets*. The distinction is that constants are treated as part of the game code for intellectual property purposes, and assets are treated as separate units that may have their own licenses, credits, and copyrights. Each constant has the format `"..name..": {"type": "..type..", "value": ..., "description": "..."}`. The `description` property is optional and the `raw` type permits a `url` instead of a `value`. Other types may have additional metadata used for the IDE. Explicit types allow support for the full PyxlScript literal syntax beyond that permitted natively in JSON, appropriate GUI editors, and annotations. The available constants types are: - `number` - Decimal number, with optional double quotes - Unicode fraction in double quotes - `"nan"` - `"infinity"`, `"-infinity"`, `"∞"`, `"-∞"` - Number ending in `deg`, `°`, or `%`, all in double quotes - `"π"` - `string` - inline or from a text file - `boolean` - `nil` (no `value` needed) - vectors - `xy` - `xz` - `xyz` - Each of these may have a `"nudge"` property, which is another vector of the same type that specifies the amount that the IDE should shift by along each axis when nudging. The nudge vector expects strings of numbers or numbers directly, rather than recursive properties with type and value elements. - `rgb` - `rgba` - `hsv` - `hsva` - `raw` - Loaded from a json or yml file or inline as a json value. When in an external file, must be at top level and not nested inside an array or object. - `distribution` - Like an `object`, but may contain only `number` valued properties - `object` - `array` - inline in the `value` field or from a CSV file `url` - `table` - from a CSV file, interpreted as an object of arrays, array of objects, object of objects, or object of arrays depending on options specified - `reference` - The value of a reference is the name of another constant, asset, or named sprite of the form `sprite_asset_name.sprite_name`. These can be recursive, but obviously cannot form a cycle. References must be defined at top level and not nested inside an array or object. ### `number` Constants Number contants may have the following additional parameters for controlling the IDE: - `min`: Smallest value (inclusive) - `max`: Smallest value (inclusive) - `quantum`: Values are rounded to `round(value, quantum)` - `scale`: Must be `"linear"` if specified - `format`: Values are shown in the IDE using `format_number(value, format)` ### `table` Constants A `table` entry in the game constants has additional properties: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ json { "type": "table", "transpose": "false", // Optional, default false "column_type": "object", // Optional, default "object" "ignore_first_column": false, // Optional, default to false. Removes the source 1st column if true "row_type": "array", // Optional, default "object" "ignore_first_row": false, // Optional, default to false. Removes the source 1st row if true "url": "somefile.csv", // Must use a "url" property, not "value". Must be a csv file. "trim": true // If true, trim extra whitespace. Default is true } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default, tables are indexed in-game as `table[x][y]` a.k.a. `table[col][row]`. To use `table[y][x]` a.k.a. `table[row][col]` indexing, set `transpose == true`. The `column_type`, `ignore_first_column`, `row_type`, and `ignore_first_row` refer to the data in the original format, before any transpose has occured. This is consistent with named animation indexing in sprite sheets. If `column_type` is `"object"` (the default), then the first row of the data is treated as property names. If `column_type` is `"array"`, then the first row is considered element 0 of the column. If `row_type` is `"object"` (the default), then the first column of the data is treated as property names. If `row_type` is `"array"`, then the first column is considered element 0 of the row. If both `row_type` and `column_type` are `"object"` (the default), then the top-left element is not used. Quadplay parses values in [CSV](https://tools.ietf.org/html/rfc4180) files with these additional rules for data types: CSV Value | Quadplay Type -------------------------|----------------- number | number number ending in `%` | number date | string `TRUE` or `FALSE` | boolean `$`, `¥`, `€`, `£`, or `§` + number | number (ignores currency unit) double-quoted text | string (without quotes) other text | string empty | (empty) string `""` in text | `"` `infinity` | number (`infinity`) `-infinity` | number (`-infinity`) `nil`, `null` | `nil` `nan` | `nan` number ending in `deg` | number (in radians, same as native quadplay constants) Double-quoted text may contain newlines. #### Object Examples Consider the following CSV files, produced using a spreadsheet program or by hand: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ state, bird, flower Louisiana, Brown pelican, Magnolia Maine, Chickadee, White pine cone and tassel Wisconsin, American robin, Wood violet ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [`state.csv`] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ slot, fighter, rogue, wizard main, sword, dagger, fireball off, shield, blackjack, teleport aux, rope, lockpick, potion ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [`rpg.csv`] The following examples show how the data can be accessed in code, depending on the properties set on the constants ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript // "state": {"type": "table", "url": "state.csv", "transpose": true} debug_print( state["Louisiana"]["bird"] ) // "Brown pelican" debug_print( state.Louisiana.bird ) // "Brown pelican" // "represent": {"type": "table", "url": "state.csv"} debug_print( represent.bird.Maine ) // "Chickadee" // "role": {"type": "table", "url": "rpg.csv"} debug_print( role.fighter.main ) // "slot": {"type": "table", "url": "rpg.csv", "transpose": true} debug_print( slot.main.fighter ) // "sword" debug_print( 'off-hand actions: ', slot.off) // {fighter:"shield", rogue:"blackjack", wizard:"teleport"} ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #### Array Examples Consider the following CSV files, produced using a spreadsheet program or by hand: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x,y 101,89 106,88 88,98 124,29 130,34 131,46 136,48 204,72 205,66 208,62 202,60 279,90 289,103 293,110 345,98 348,94 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [`positions.csv`] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ level, strength, hit_points, total_skills 0, 0, 0, 0 1, 16, 10, 1 2, 18, 15, 1 3, 18, 20, 2 4, 20, 25, 2 5, 21, 30, 2 6, 21, 35, 3 7, 22, 40, 3 8, 22, 45, 4 9, 22, 50, 5 10, 24, 55, 6 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [`level_table.csv`] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 0, 10, 20, 30 1, 11, 21, 31 2, 12, 22, 32 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [`grid.csv`] The following examples show how the data can be accessed in code, depending on the properties set on the constants ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript // "position_array": {"type": "table", "url": "positions.csv", "transpose": true, "column_type": "array"} for pos in position_array: draw_point(pos, #F00) // "level": {"type": "table", "url": "stats.csv", "transpose": true, "column_type": "array", "ignore_first_column": true} debug_print( level[1].strength ) // 16 // "grid": {"type": "table", "url": "grid.csv", "column_type": "array", "row_type": "array"} debug_print( grid[1][2] ) // 12 debug_print( grid[3][1] ) // 31 // "matrix": {"type": "table", "url": "grid.csv", "transpose": true, "column_type": "array", "row_type": "array"} debug_print( matrix[1][2] ) // 21 debug_print( matrix[3][1] ) // 13 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ### `raw` Format The `raw` format allows embedding a JSON-expressible value directly. `raw` format also supports the format {"type":"raw", "url": "your url"} instead of an inline value. The URL must be for a [JSON](https://www.json.org/) or [YAML](https://yaml.org/) file. Note that the `raw` format is restricted to values that are expressible in those interchange formats and does not support the full GUI editors and other format features. `raw` type constants with URLs must be top-level constants. They cannot be embedded within other constants. This is required in order to separate the asynchronous loading from constant evaluation in the implementation and to ensure that GUI editors can be built effectively for objects and arrays. Debug JSON --------------------------------------------------------------------------------------------- When working with multiple developers on a game, it is often useful to have slightly different settings for each developer. For example, one developer may want a `DEBUG_GRAPHICS` constant set to `true` while the usual value is false. Or the gameplay programmer may experiment with different player velocity than what is in the main game branch. One way to accomplish this is to explicitly use the built-in `IDE_USER` constant described in the Debugging section. Another solution is to use a `.debug.json` file. A debug JSON file must be in the same directory as the `.game.json` file and use the same basename. For example, if your game is `space.game.json`, then the debug file is `space.debug.json`. Typically you will _not_ add the debug file to version control, so that each developer may have their own. No `.debug.json` file is required, and if one is present it will be ignored unless running on a quadplay server using the IDE. The `.debug.json` format is: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ json { "start_mode": "Init", "start_mode_enabled": true, "constants" : { // Constants in the same format as in the .game.json. "player_speed": { "type": "number", "value": "3", // If false, then this debug value is // disabled in the IDE but remembered for later // enabling. "enabled": true } } } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The `start_mode` overrides the `.game.json` start mode, if it is present. The constants override the equivalent constants from the `.game.json` file (unless they are not enabled). Extra constants specified only in the `.debug.json` file are ignored; they are not mapped at runtime. Constant types that require external data are not supported in the `debug.json` file. These are `raw` constants loaded from a JSON or YAML url, `string` constants loaded from a text file url, and `table` constants loaded from a CSV url. Sprite JSON --------------------------------------------------------------------------------------------- The sprite sheet JSON descriptor looks like: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JavaScript { "url": "hero.png", // Optional. Defaults to the size of the png. "sprite_size": {"x": 8, "y": 8}, // Optional blank pixels between sprites on the bottom and right. Default is zero. "gutter": 0, // Optional. Default is false. If true, indexing is transposed // in game. "transpose": false, // Optional name of the file from which this PNG was generated. // This is the file that will be loaded with an external editor from the IDE // instead of the PNG when editing. This file will also be managed by version control // alongside the PNG nad JOSN. // // Useul if you export from another tool such as Aseprite, Krita, or Photoshop. "source_url": "hero.psd", // Default value for sprite.frames, the number of 1/60-second frames // to play a sprite during animation. "default_frames": 1, // Optional table mapping color value strings to strings. // The source values (keys) must be exactly (4-bit) #RGB values; they cannot have // alpha or use the 8-bit form #RRGGBB. They are applied to the source // image after loading to recolor it. // // The destination values can be either 4-bit #RGB or #RGBA values. // When remapping a color, the source alpha will be *bit masked* // (not multiplied) by the destination alpha. So, only values of // A = 1, 3, 7, and F are generally useful. "palette_swap": { "#F00": "#0F0", // Convert red to green "#DD0": "#C0D0" // Make #DD0 transparent }, // Optional. Applies to pre-transpose coordinates. Each becomes // an array on the spritesheet with the `extrapolate` property set. // A name cannot be `sprite_size` or any other spritesheet property. // // If an object with `x` and `y` properties is specified, creates a single sprite. // // If an object with `start` is specified, creates an array of sprites. // // The contents between `start` and `end` are treated as a 1D array. By default // they are row major. To switch to column-major set "major_axis": "y". // // To strip frames from the end of the array, set `ignore` to the number of frames to // ignore. // // If `extrapolate` is unspecified, defaults to looping. // `extrapolate` options are "loop", "oscillate", and "clamp" // // Each object may have its own `default_frames` that overrides the // spritesheet's default_frames, as well as a `frames` array with // one element per sprite in the animation. // // `end` defaults to `start`, and each property of `end` defaults // to the corresponding part of `start`. "names": { "idle": {"start": {"x": 0, "y": 0}, "end": {"x": 0, "y": 3}, "extrapolate": "oscillate", "frames": [1, 2, 2, 1]}, "run": {"start": {"x": 1, "y": 0}, "end": {"y": 3}, "frames": 2}, "jumpUp": {"start": {"x": 2, "y": 0}}, "jumpDown": {"start": {"x": 2, "y": 1}}, "dead": {"x": 3, "y": 0}, "parachute":{"start": {"x": 3, "y": 1}, "pivot": {"x": 4, "y": 4}, "is_item": true // Arbitrary properties can be attached here, using the // same syntax as game constants }, }, // Optional offset and extent. Using this conserves memory if the sprites // do not fill the entire PNG. "region": { "corner": {"x": 0, "y": 0}, // Optional, defaults to (0, 0) "size": {"x": 128, "y": 32} // Optional, defaults to the remainder of the sheet }, // Optional location on the sprites that aligns with (0, 0) in an entity's reference // frame. Defaults to the sprite size / 2. "pivot": {"x": 4, "y": 7}, // Optional license and copyright information. "license": "GPL 3.0 (c) 2025 Morgan McGuire" } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This will appear as the global variable `heroSpriteSheet` in memory, which is a 2D array of individual sprites. !!! I recommend a two-step process for compressing sprites to minimize loading time and game size. First, use the [Quantize✜](../tools/quantize.html) tool to reduce bit depth. Second, use [ImageOptim](https://imageoptim.com/mac) (macOS), [PNGGauntlet](https://pnggauntlet.com/) (Windows), [TriImage](https://trimage.org/) (Linux), or [PNG Crush online](https://richardassar.github.io/pngcrush.js/) to improve the PNG encoding. For a sprite sheet named `S`, individual sprites are accessed as `S[x][y]` in game. If the sprite sheet contains a single row, then `S[x]` is sufficient. If there's a single image, just use `S`. Setting `transpose` to true flips the indexing to `S[y][x]`, which is more convenient for some sprites. Animations appear to game code as arrays of sprites that have the following additional properties: `extrapolate` : Equal to `"clamp"`, `"loop"`, or `"oscillate"` as specified in the spritesheet. `frames` : Equal to `infinity` for `"loop"` and `"oscillate"` animations and the total number of 60 Hz game frames of animation for finite `"clamp"` animations. `period` : Equal to `nan` for `"clamp"` animations and the number of 60 Hz game frames for one repeat of a `"loop"` or `"oscillate"` animation. A game may use a total of **5,505,024 pixels** of sprite memory (10.5 MB) across **128 sprite sheets** (including fonts), each no larger than **1024x1024**. You can allocate this however you wish within those restrictions. That's equivalent to 5,376 sprites at 32x32, 21,504 sprites at 16x16, or 64 full-screen images. These are of course soft limits because you can modify the open source emulator to remove them. The intent of the limits is to guarantee that your programs will work on all future implementations of the runtime: web software, Raspberry Pi, WebGL, GLES, and desktop OpenGL and Vulkan. If the same sprite sheet is loaded by multiple `.sprite.json` files using the same URL then it only counts once towards the resource limits. This allows you to pack sprites with different sizes into the same physical sheet at no penalty and treat them as different logical sprite sheets. Multiple spritesheets loaded from the same sprite.json file will be pointers to the same object, which is immutable. There is no performance advantage or requirement for making sprites or spritesheets even sizes or power of two sizes, although many traditional sprites happen have sides of 8, 16, or 32 pixels. The screen resolution is designed to allow an integer number of such tiles. You can use the Python script `tools/sprite_json_generator.py` to generate these json blobs for you. Use the `--help` argument for more details. Font JSON --------------------------------------------------------------------------------------------- Fonts have a JSON descriptor file that looks like: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JavaScript { "url": "sundown-7x13.png", // Optional copyright and license information "license": "Public Domain 2018 by Danika Farheed", // Size of the glyph bounding boxes. Include gutter pixels in the // char_size. The loader repacks glyphs tightly, so extra padding // or horizontal alignment is irrelevant at runtime (but affects // distribution size and loading time). "char_size": {"x": 8, "y": 14}, // Optional, default is 0. The minimum width of a glyph for // layout purposes. This can be used to create fixed-width // (monospace) fonts, or to enforce minimum spacing around very // thin characters such as |. This is the width without an outline. "char_min_width": 8, // Spacing between letters and lines during rendering. If the // x spacing is zero (for script fonts), there will be no space between // letters or digits, but space will still be added around symbols. "letter_spacing": {"x": 2, "y": 3}, // Defaults to 1 pixel "shadow_size": 1, // Zero-based Y value of the baseline relative to char_size. // This is one less than the "height" of the baseline because // of the zero based value. "baseline": 11, // The current format. No other is accepted "format": "20211015" } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can generate a default version of this JSON with the [fontpack✜](../tools/fontpack.html) tool from a PNG image. You can generate a PNG template for the glyphs by taking a screenshot from the [fontgen✜](../tools/fontgen.html) tool. By convention, the image filename contains the maximum size of the individual glyphs, which is usually smaller than the char_size due to gutters. The file can have any name, however. The image must be grayscale, where white is the font character and black will become transparent. The current implementation does not allow shades of gray for antialiasing. The image tiles must follow this exact character format: ![Figure [figFont]: The font character layout. To save disk space/download time and font drawing time, characters shown in magenta will automatically be generated at load time if not present in the font sheet.](font-layout.png) At load time, many missing characters can be generated. This may not look ideal, so use the [fontpack✜](../tools/fontpack.html) tool when creating a font to inspect the generated characters, and hand-tune any for which the generation is not good enough. Also, many characters in the font sheet perform double duty and will be used for producing similar-looking unicode characters. Empty spaces are reserved for for future extensions. Fonts count 1/2 as much towards the spritesheet pixel limit because they are internally compressed to 8 bits per pixel. They still count fully towards the maximum spritesheet dimensions. Because a font is 32x14 glyphs, this means that the largest `char_size` can be is 29x70 pixels when the `shadow_size` is one pixel. Fonts enforce uniform width on all full-size digits 0..9 so that numbers print in aligned columns. Avoid drawing fonts with one digit that is larger than most of the others (usually the "4") because they result in extra padding around all digits. `font.line_height` : When loaded into memory, the font object exposes a `font.line_height` property that is the spacing used between consecutive baselines. This is chosen to make typical letters well spaced. It may leave some symbols overlapping on adjacent lines, especially for decorative fonts. This is the height of the actual characters plus the `char_size.y` value. Multiple fonts loaded from the same sound.json file will be pointers to the same object, which is immutable. Sound JSON --------------------------------------------------------------------------------------------- The sound JSON descriptor looks like: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JavaScript { "url": "boom.mp3", // Optional base pan offset on [-1, 1] "pan": 0, // Optional base volume multiplier "volume": 1, // Optional base rate multipler "rate": 1, // Optional base pitch multiplier "pitch": 1, // Optional license and copyright "license": "Released into the public domain by Edouard Gauthier" } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sounds must be in MP3 format. Multiple sounds loaded from the same sound.json file will be pointers to the same object, which is immutable. Map JSON --------------------------------------------------------------------------------------------- The easy way to create a map is within the IDE using the "Create new asset..." option in the project. Select asset type Map and set the map options as you wish. Once the map is added to the project, right-click on the map in the project to open it in Tiled for editing. ![Map creation dialog in the quadplay IDE](new-map-dialog.png width=50% style="image-rendering:auto") Multiple maps loaded from the same map.json file will produce different map objects in memory because maps are mutable. The spritesheets that they load will be shared if they come from the same sprite.json files. The map JSON descriptor looks like: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JavaScript { "url": "overworld.tmx", // Optional. Shift the map by this amount in pixels for all coordinate // transformations and rendering. Default = (0,0), which puts integer // map coordinates at the corners of tiles. "offset": {"x": 7.5, "y": 7.5}, // z value of the first layer. Optional, default = 0 "z_offset": 0, // Optional. Default = false. If true, flip the map // vertically during load so that (0,0) is the bottom // of the TMX map instead of the top. "y_up": false, // Optional. Default = false. "loop_x": false, // Optional. Default = false. "loop_y": false, // Optional. Default is 1.0. This is the z difference // between layers when rendering. Can be negative to // reverse layering direction. "z_scale": 2, // Optional license and copyright "license": "Released into the public domain by Edouard Gauthier", // Optional if the map only uses one spritesheet "sprite_url": "tiles.sprite.json" // If no sprite_url is specified, then an entire table // mapping each spritesheet referenced in the map to a sprite file "sprite_url_table": { "tiles": "tiles.sprite.json" } } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The url must reference a map file in [TMX format](https://doc.mapeditor.org/en/stable/reference/tmx-map-format/) and in files ending with `.tmx`. Only the following properties are currently supported: - `Orthogonal` or `Hexagonal (Staggered)` - `CSV` format - `Tile layers` (other layers are ignored) - `Embedded tileset` (a PNG reference in the TMX, _not_ a TSX file) - For an Orthogonal map, set the map tile size equal to the tileset tile size (e.g., 32x32) - For a Hexagonal map, set the map tile size to be 1.5x larger along the "pointy" direction (e.g., 32x48 for a 32x32 hex with points up and down) When accessing a Hexagonal map in memory, even rows should be rendered offset by 1/2 a tile width. ![Tiled program options for creating a new map.](tiled-new-map.png width=50% style="image-rendering:auto") The tile render order from the map is ignored. The easiest way to create these is using the open source [Tiled](https://www.mapeditor.org/) editor, which has pay-what-you-want precompiled binaries for Windows, macOS, and Linux. The sprite sheet from the map file will be used for visualization if it is available in the IDE, but map rendering is performed using a sprite sheet at runtime. For more sophisticated map drawing, [TileKit](https://rxi.itch.io/tilekit) is a US$20 Windows and Linux mapping program. When drawing, layer zero for `draw_map()` corresponds to the lowest layer in Tiled and the layer indices increase upwards. Data JSON --------------------------------------------------------------------------------------------- The special data asset type allows importing a large amount of data in an arbitrary format. It supports PNG, JPG, GIF, CSV, TXT, JSON, YAML, and XML. Unlike a CSV, JSON, or YAML _constant_, an asset can contain metadata, a license, and arbitrary extra properties. Unlike a sprite, a PNG, JPG, or GIF data asset presents the raw 8-bit per pixel data as a 2D array to the program instead of quantizing it to 4 bits per channel and only supporting PNG. The data JSON descriptor looks like: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JavaScript { // can be .png, .gif, .jpg, .csv, .txt, .json, or .yml "url": "heightfield.png", "license": "your license here", // all of the table constant options for if URL is a CSV file } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Images are loaded as `image.color[x][y]` where `color` can be `r`, `g`, `b`, or `a`. The values are on the range [0, 255]. Future support may extend that range when the input has 16 or 32 bits per channel. PyxlScript Language ========================================================================= Numbers ------------------------------------------------------------------------- Numbers are stored internally as double-precision (64-bit) IEEE floating point values. Integer operators truncate the decimal places of their arguments. The first 2^53=9007199254740992 contiguous positive integers are exactly representable. The largest finite representable number is about 1.7977x10^308. There are about seventeen decimal digits of precision on arbitrary numbers. `nan` and positive and negative infinity are also exactly representable. Finite decimal numeric literals contain an optional single plus or minus sign and one of: - a series of digits, followed by an optional decimal point and a series of digits - a decimal point followed by a series of digits - one of the following single-character fractions, representing one-half to one-tenth: `½ ⅓ ⅔ ¼ ¾ ⅕ ⅖ ⅗ ⅘ ⅙ ⅐ ⅛ ⅑ ⅒` Two optional suffixes are permitted: - Percent `%` divides the number by 100 - Degrees `deg` or `°` multiplies the number by `π/180` There are no _literals_ for not-a-number or infinity. See the Built-In Objects section for the constant bindings that produce these values. Hexadecimal constants are `0x` followed by series of digits 0-F or 0-f. Binary constants are `0b` followed by a series of 0s and 1s. Examples of legal numeric literals: ~~~~~~~~~~~~~~ PyxlScript 1 -21.4 0 -0 .0 0.5 -½ +72.41 15% // 0.15 90° // ½π 45deg // ¼π 0xFF0DC 0b11000101 ~~~~~~~~~~~~~~ No octal or exponential float notation are supported. Trailing decimal points are not allowed. For example, `0.` is illegal. Booleans -------------------------------------------------------------------- The boolean literals are `true` and `false`. Logical operators work with any values. For logical operators and flow control, `0`, `∅`, and empty string `""` act as `false`. Strings -------------------------------------------------------------------- String literals are enclosed in double quotes (`"..."`). They may not contain newlines or double quotes. To enter a double quotation mark or slash, escape it with a slash: `"\\"` is the \ character and `"\""` is the `"` character. The `+` operator performs string concatenation, coercing the right-hand argument if it is not a string. String characters are read-only substrings accessed using zero-based array syntax. Arrays -------------------------------------------------------------------- Arrays are ordered sets of values. They are zero-based and have dynamically typed elements. Array literals are surrounded in square brackets and separated by commas. Square brackets are also used for l-value and r-value dereference. Array examples: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript a = [8, 4, 1] a[0] = "hello" debug_print(a[0]) debug_print(size(a)) a₁ += 2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Negative indices are permitted on arrays but will not be reported in the length. Reading an out-of-bounds value from an Array returns `∅`. Objects --------------------------------------------------------------------------- Objects are unordered sets of keys. Objects (also known as dictionaries and tables in other languages) map these keys to values. Literals are created using key-value pairs separated by a colon, surrounded by curly braces, and delimited by commas. Object elements can be accessed using the string name of a property in square brackets or by a period. This allows them to act as objects/structs. Object examples: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript t = {x:3, y:10} t.x += 1 debug_print(t["x"]) debug_print(size(t)) debug_print(keyArray(t)) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Functions --------------------------------------------------------------------------- Functions are first-class values. They can be passed to and returned from other functions, stored in data structures, and bound to variables. Declare functions with `def` and return values from them with `return`. Separate the argument list from the body with `:`. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript // Multi-line function block def solve(a, b, c): return ½(sqrt(b² - 4 a * c) - b) / a // Inline function def foo(x): x += 1; return 2 x // Call a user function text(foo(3)) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Functions create a local scope for their variables and are lexically scoped (as in JavaScript) for free variables. Local functions can be declared anywhere a statement can appear, and local function definitions are efficient. It is common practice to declare little callbacks right before they are used, as in: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript def destroy(entity): play_sound(explosion_sound) spawn_particles(entity.pos) def cleanup(): remove_values(entity_array, entity) delay(cleanup, 20) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Some API functions that use callbacks are `iterate()`, `delay()`, `sequence()`, `add_frame_hook()`, `set_pause_menu()`, `ray_intersect_map()`, and `physics_add_contact_callback()`. There is no way to declare an anonymous function or use a function definition as an expression. Simply declare your functions globally or locally with a name as as statement. This improves readability. In PyxlScript syntax, a parameter shown with `default` after it, for example: `i default 0` indicates that this is an optional parameter and the value after `default` be used if none is supplied. Explicitly specifying `nil` for any parameter forces its default value. If a function body takes too long to execute, then the interpreter may terminate the program to prevent the game from becoming unresponsive. To call a function returned from an expression that ends in parentheses, use the `call` function. Variables --------------------------------------------------------------------------- Variable name identifiers consist of an optional leading underscore `_`, optional upper case delta `Δ`, a series of Roman letters or one of the unambiguous-looking Greek letters `αβγδζηθιλμρσϕχτψωΩ` by itself, and optionally trailing a series of digits and underscores. The identifier also must not be a reserved word. No other unicode characters are allowed in identifiers. Examples of legal identifiers: ~~~~~~~~~~~~~~~~~~~ none col screen long_variable longVariable L i damage Y2 Δx θ θ_in _health ~~~~~~~~~~~~~~~~~~~ Variables are declared to the scope of the containing block or mode. Scope blocks are denoted by a statement ending in a colon and zero or more indented lines. Use `let` to declare variable names that can later be re-bound. Use `const` to declare constants that cannot be rebound. Note that `const` marks the *binding* as constant, but the bound value itself may be mutated if it is an object or array. Note that only a single variable is permitted per `let` or `const` statement. Multiple statements may be chained using semi-colons on a single line, however. ~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript const x = 7 // x may not be rebound let y // Declare y let z = 9 // Declare and initialize z // Declare and initialize two variables on the same line: let a = [1, 2]; let b = "hello" ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The variables captured by `for` and `with` and the arguments to a `def` are bound to the scope of the body. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript const scale = 2 let y = 100 for i < 3: y += 1 sprite(hero[anim][0], xy(scale * i, y)) def applyScale(x): return x * scale // i is out of scope here ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Variables declared within a mode are local to that mode and reinitialized every frame. Variables declared in a global file are global to the entire program. It is an error to assign to a variable before it is declared. ### Reserved Words These are reserved tokens that cannot be used as variable names. Some of them are keywords or built-in syntax, and some of them do nothing now but are reserved for future use. `and`, `arguments`, `args`, `as`, `async`, `at`, `auto`, `await`, `assert`, `because`, `begin`, `bitand`, `bitnot`, `bitor`, `bitashl`, `bitshl`, `bitshr`, `break`, `catch`, `class`, `const`, `cont`, `coroutine`, `continue`, `constructor`, `deg`, `do`, `delete`, `def`, `default`, `debug_watch`, `debug_print`, `elif`, `end`, `extern`, `enum`, `final`, `finally`, `for`, `from`, `freeze`, `function`, `get`, `global`, `go`, `goto`, `has`, `HOST_CODE`, `if`, `in`, `implements`, `inherits`, `include`, `import`, `interface`, `launch_game`, `let`, `local`, `nan`, `NaN`, `new`, `nil`, `nonlocal`, `not`, `null`, `main`, `mod`, `module`, `mode`, `or`, `otherwise`, `public`, `private`, `protected`, `prototype`, `package`, `preserve`, `preserving_transform`, `quit_game`, `ret`, `return`, `reset_game`, `require`, `requires`, `seal`, `set`, `show`, `static`, `strict`, `SUB`, `swap`, `SOURCE_LOCATION`, `SCREEN_SIZE`, `to`, `todo`, `to_string`, `then`, `try`, `throw`, `template`, `touch`, `until`, `use`, `using`, `unless`, `var`, `VIEW_ARRAY`, `while`, `with`, `when`, `xor`, `yield`. Built-in functions such as `draw_rect` that are legal identifiers can be rebound. They are not reserved. `cos`, `sin`, and `tan` by themselves are bound to first-class functions, but the special syntax that allows them to be invoked without parentheses on single variables is tied to their exact names. If you rebind them, then the new functions that you have bound will be invoked instead. ### `with` Statement The `with` statement creates a local scope in which explicitly named keys from an object are variables aliased to the properties on the object. This allows compact syntax for repeated use of those keys. It fills the role that methods and member variables do within object-oriented languages. The local variables shadow any global (or enclosing `with` statement) variables. Example: ~~~~~~~~~~~~~~~~~~~~~~ PyxlScript G = {x:1, y:2, z:0} with x,y in G: x = 100 y = 0 z = 4 // G.x is now 100, G.y is now 0, G.z is still 0, // and global z is now 4 ~~~~~~~~~~~~~~~~~~~~~~ Within the body, the local variables and the properties are true aliases. So, `G.x` and `x` may be used interchangably within the body in the above example. The single-line version of the `with` statement is comparable to other single-line flow control: ~~~~~~~~~~~~~~~~~~~~~~ PyxlScript G = {x:1, y:2, z:0} with x, y in G: x = 100; y = 0; z = 4 ~~~~~~~~~~~~~~~~~~~~~~ `with` statements can make code clean but are expensive relative to the overhead of other statements, so avoid them for performance-sensitive inner loops. ### `local` Statement The `local` statement allows you to create a block without any other bindings or flow control. It is occasionally useful for creating variables with a tight scope inside a long function or mode. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript let x = 4 local: // This is a local scope and variables // can shadow those in the outer scopes let x = 7 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Comments ------------------------------------------------------------------- Comments follow C/C++/Java/JavaScript syntax of `//` for single line comments and `/*`...`*/` for multi-line comments. `because "reason"` : The special `because` statement allows a compile-time string comment to follow specific statements such as `set_mode()`. It acts as a machine-readable comment that the IDE will use to automatically annotate the program in the debug output at runtime and the mode diagram. Operators ------------------------------------------------------------------- Built-in arithmetic operators and functions are overloaded for numbers and objects. Some operators support a compact Unicode version and an ASCII version that is easier to type on a conventional keyboard. Unlike other languages, all mutating operators (for example, `++`, `+=`, and `=`) return `nil`. ASCII | Unicode | Meaning ---------|---------|-- `++` | | increment `--` | | decrement `=` | | assignment `==` |`≟` | equality test `!=` |`≠` | inequality test `+` | | addition and string concatenation `-` | | negation `+=` | | mutating addition, string concatenation `-=` | | mutating subtraction `<` | | less-than `<=` |`≤` | less-than or equal to `>` | | greater-than `>=` |`≥` | greater-than or equal to `in` |`∈` | `for` loop container element-of and `with` statement object `^` | | exponentiation (see also unicode exponents) `*` | | multiplication (see also implicit multiplication) `*=` | | mutating multiplication `/` | | floating-point division `/=` | | mutating division `mod` | | remainder (modulo) |`⌊⌋` | floor function |`⌈⌉` | ceiling function `||` | | absolute value |`‖‖` | magnitude (of a vector or array) `[]` | | object/array element reference, or array constructor `{}` | | object constructor (follows JavaScript syntax) `.` | | object property `()` | | grouping or function call `...` | `…` | spread operator for arrays and objects `bitand` |`∩` | bitwise AND (bit set intersection) |`∩=` | mutating bitwise AND `bitor` |`∪` | bitwise OR (bit set union) |`∪=` | mutating bitwise OR `or` | | logical OR `not` | | logical NOT `and` | | logical AND `bitxor` |`⊕` | bitwise XOR |`⊕=` | mutating bitwise XOR `bitnot` |`~` | bitwise NOT `~=` | | mutating bitwise NOT `<<` or `bitshl` |`◀` | arithmetic bit shift left `<<=` |`◀=` | mutating arithmetic bit shift left `>>` or `bitshr` |`▶` | arithmetic bit shift right `>>=` |`▶=` | mutating arithmetic bit shift right `if`...`then`...`else`| | conditional (C/Java `?:` a.k.a. "ternary" operator) `default`| | default value operator Assignment and mutating operators all return `nil`; they cannot be chained or used in expressions. There is no logical XOR or logical shift right. The `default` operator has the form: _expr1_ `default` _expr2_. If _expr1_ is not `nil`, then the value of the operation is the value of _expr1_. If it is `nil`, then _expr2_ is evaluated and the value of the expression is the value of _expr2_. Note that the second expression is not evaluated unless it is needed, as with `and`, `or`, and `if` operators. The `default` operator allows compact statements handling default arguments or out of bounds array accesses, for example: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript // Returns a transparent gray. Opacity is optional // and defaults to 1 def transparent_gray(g, opacity): return rgba(g, g, g, opacity default 1) // Print the first element of array matching target, or // the first element of the entire array if not found. debug_print(array[find(array, target) default 0]) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The conditional operator (distinct from the `if` block statement) has the form: `if testexpr then truevalue else falsevalue`. Note that there are no colons (:), because it does not create blocks. The conditional operator is convenient when initializing constants, or data structures. For example: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript const y = if x > 3 then "cat" else "dog" spawnMonster(if pos then pos else xy(0,0)) let data = {name: if i > 2 then "Peter" else "Amit", height: 7} setTarget(if ‖pos - P.pos‖ > 10 then ∅ else P) // Because "and" and "or" operate on non-booleans, the examples // above could have been written less clearly with alternative // logic: const y = (x > 3) and "cat" or "dog" spawnMonster(pos or xy(0,0)) let data = {name: (i > 2) and "Peter" or "Amit", height: 7} // Note that this one had to be restructured // because ∅ acts as false for "and" setTarget(‖pos - P.pos‖ ≤ 10 and P or ∅) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To nest conditionals, you must put parentheses around at least the `falsevalue`, for example `y = if x > 3 then "cat" else (if x > 2 then "dog" else "llama")`. `a default b` is equal to `a` if `a` is not `nil` and `b` otherwise. It does not evaluate `b` if `a` was not `nil`. This is equivalent to the JavaScript and C# [nullish](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator) `??` operator. Some common uses are: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript // Default value on load num_players = load_local("num_players") default 2 // Default value on missing object property height = character_height[name] default 170 // Default value on going out of array bounds vel = array[i + 2] default xy(2, 3) // Fallback value on find() failing debug_print(array[find(array, target) default 0]) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Spaces are not required around operators in most cases, so long as the text parses unambiguously. For example, `1or3` is equivalent to `1 or 3`. There is no ASCII operator for floor or ceiling; use the `floor` and `ceil` functions. There is no way to express mutating bitwise operators using ASCII. Behavior of these operators is consistent with the JavaScript definition of the operation (even though JavaScript uses different operators). For example, the logical-AND expression `4 and 1` evaluates to `1` because `4` is non-false and the result of non-false AND another value is the other value. The trigonometric functions `cos`, `sin`, and `tan` may be invoked as if they were operators when the order of operations is unambiguous. For example, `cos a` ==> `cos(a)`, `cosβ` ==> `cos(β)`, and `cosθ*sinφ` ==> `cos(θ) * sin(φ)`. To avoid confusion and reserve future syntax, they may not be invoked in this form when a high-precedence operator follows the argument. Those operators are `.`, `[`, and exponentiation. Note that the `call()` function can in most cases be replaced simply with the function expression followed by arguments: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript x = (if a then abs else cos)(θ) y = tbl["myfunc"](3, 4) z = f(a)(b) x = call(if a then abs else cos, θ) y = call(tbl["myfunc"], 3, 4) z = call(f(a), b) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ### Assignment and Equality As in Python and JavaScript, all assignment operators perform pointer assignment and all equality operators check for pointer equality. All function call parameters are passed by pointer. Strings, `nil`, numbers, and booleans are immutable and unique. This means that variables will be aliased after assignment: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript let v = xy(1, 2) let k = v k.x = 3 // v.x is 3 now ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Use `clone()`, `deep_clone()`, or one of the vector-specific helper functions for copying assignment: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript let v = xy(4, 5) let k = xy(v) // copy k = clone(v) // generic version ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A global game constant that is cloned becomes mutable. There is no way to declare immutable objects or arrays at runtime in code. `const` makes the _binding_ constant, but has no effect on the value (as in JavaScript). ### Exponents The characters `⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹ᵃᵝⁱʲᵏⁿᵘˣʸᶻ⁽⁾` can form exponent expressions that operate as if the `^` operator or `pow` function was invoked using those expressions as a second argument. The entire exponent expression is implicitly surrounded in parentheses. The variables `a`, `β`, `i`, `j`, `x`, `y`, `z`, `n`, `k`, and `u` may appear only as single-character variable names in exponents. Exponentiation, including via exponent characters, may not be performed on a negative number without parentheses. This follows the rules from JavaScript's `**` operator. For example, `x=y-1²` is legal, but `x=y+-1²` is illegal and must be rewritten as `x=y+(-1)²` or (the confusing and redundant expression, if that's really what was intended) `x=y+-(1²)`. ### Implicit Multiplication Multiplication is implicit wherever a number is immediately followed by a parenthesized expression or variable (including a function call). This also ocurrs when any exponent appears immediately preceeding a parenthesized expression or variable, and where two parenthesized expressions are back to back. For example, `2x`, `x²y`, `3(x+y)`, `(2 + x) y`. Implicit multiplication will not occur if the expression on the left of the pair ends in any bracket: `| ) ] ⌋ ⌉ ‖` and the expression on the right also begins with a bracket. For example, `(x + 1) (x + 2)` and `|y| x` are syntax errors, not implicit products. Implicit multiplication can be used within exponents. ### Spread Operator The spread operator (`…` or `...`) allows defining and calling functions with a variable number of arguments and creating an extended clone of an object or array. It is the same as `*rest` in Python and `...rest` in JavaScript, and similar to varargs in C++ and `...` in Java. In a function declaration it designates the final argument as an array of all further arguments. For example, `def foo(a, b, …rest):` takes any number of arguments and maps them to `a`, `b`, and then an array `rest` of the remainder. For example, `def bar(…args):` takes any number of arguments and maps them all into array `args`. In a function call or array literal, the spread operator expands an array as if its elements had been entered inline. So, `bar(…a)` calls a function with all of the arguments from array `a`. `[1, 2, …a, 10]` creates an array that begins with 1 and 2, then has all of the elements of `a`, followed by the number 10. For objects, `{a:1, b:2, …rest}` creates the object `{a:1, b:2}` and then creates a new extended version in which the properties of `rest` are added, overriding any with the same name in the original object (as with the `extended()` function). This is helpful for the case of calling a function with an args object: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript const text_args = {color: #FFF, x_align: "center", font: roman_font} draw_text({text: "Hello", pos: xy(10, 100), …text_args}) draw_text({text: "World", pos: xy(10, 120), …text_args}) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ### Built-in Objects The following are built-in constants and special zero-argument operators. They are not variable names and thus cannot be redefined. Several have both unicode and ASCII names. Literals (replaced by the parser at compile tile): Symbol | ASCII | Value ---------------|-----------------|---------------------------------------- `ε` | `epsilon` | `0.000001` `π` | `pi` | `3.141592653589793115997963468544185161590576171875` `∞` | `infinity` | infinity | `nan` | floating-point "not-a-number" value `∅` | `nil` | ("undefined") The following are lowercase because they contain values that can change without game code specifically triggering that: Symbol | ASCII | Value ---------------|-----------------|---------------------------------------- `ξ` | `random()` | random number on [0, 1), resampled every time the expression is used | `gamepad_array` | array gamepad of objects | `touch` | the object for tracking touch and mouse input | `joy` | shorthand for `gamepad_array[0]` (deprecated) These have values that will not change unless game code specifically invokes a routine to change them, such as `set_screen_size()` (or if the player uses the system GUI for `HOST_CODE`): Symbol | ASCII | Value ---------------|-----------------|---------------------------------------- | `CREDITS` | object of credit and license statements from assets and code | `HOST_CODE` | string that is the code guests can use to connect when hosting | `SCREEN_SIZE` | the size of the full screen in pixels, defaults to `xy(384, 224)`. See also `VIEW_ARRAY[0].size` | `VIEW_ARRAY` | array of corner rects for host and each guest's view in private view online multiplayer. See `set_screen_size()` | `CONSTANTS` | an object mapping all game-specific constants to their values | `ASSETS` | an object mapping all game-specific assets to their values | `SOURCE_LOCATION` | an object that has properties `{url:, filename:, line_number:}` from its location in the source code Loops and Flow Control -------------------------------------------------------------------------- Whitespace is significant, as in Python. A block is a line ending with a `:` whose body is indented. All blocks create a new scope line (unlike Python). The flow control statements are: - Blocks: - `if` conditional with optional `else` and `else if` clauses - `while` loop - `until` loop - `for` loop - `for-in` loop. _See also the `iterate()` function_. - `for-with` loop - `break` and `continue` within loops - `preserving_transform` and `local`, which do not have branching flow but do create local scopes - `def`, which creates a local scope for a function but does not execute immediately - Modes: - `set_mode(ModeName)` to switch game modes - `push_mode(ModeName)` and `pop_mode()` to temporarily enter a mode game mode - Game: - `reset_game()` to reset all state and restart the game. - `quit_game()` to end the current game and return to the launcher. - `launch_game(url)` to launch a different game. `set_mode()`, `push_mode()`, `pop_mode()`, `reset_game()`, `quit_game()`, and `launch_game()` may be followed by the keyword `because` and a reason that is a compile-time string. For example, `quit_game() because "lives = 0"`. The reason allows the IDE to label the transitions on the mode diagram for the game. There is no switch, do loop, goto, or try/catch. For flow control purposes, the empty string, 0, and `∅` are false. All other objects (including empty data structures) are true. Multi-line `if` statements may be followed by `else:` and `else if ...:` clauses. These must always be multi-line. Single-line `for`, `if`, `while`, and `until` statements are permitted. There can be multiple single-line expressions on the same line. No `else` is permitted with single-line `if` statement. In any loop, `break` immediately terminates the tightest enclosing loop and `continue` immediately begins the next iteration of the tightest enclosing loop. `while` and `until` loops evaluate the condition at the top of the loop for every iteration. The `until` loop is a `while` loop with an inverted test: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript until x == "end": x = find_next(x, data) // Is the same as: while x != "end": x = find_next(x, data) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ### `&` Elision Trivial blocks can increase indentation so much that code is hard to read because it extends too far to the right. All blocks except for `else` can be elided with an immediately-previous block to avoid excess indentation. To elide blocks, leave the trailing `:` off the outer block and begin the inner block with `&` at the same indentation level: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ for x < 3 & for y < 3: & for z < 3: debug_print(x, y, z) def player_update(player) & preserving_transform & with angle, pos, vel, gamepad in player: vel = lerp(vel, gamepad.xy, 10%) if ‖vel‖ > 0: angle = xy_to_angle(vel) pos = loop(pos + vel, xy(0, 0), SCREEN_SIZE) set_transform(pos) draw_sprite(arrow_sprite) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ### `for` Loop Numeric `for` loops increment by 1 each iteration and always count up. The loop variable must be on the left-side of a single comparison loop expression or the center of a multi-comparison expression. The iteration variable in any `for` loop is freshly bound for each iteration. This means that if it is captured by a function as in: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript let array = [] for i < 3 def capture(): return i push(array, capture) for f in array: debug_print(f()) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ that the value will reflect the time of the capture. This avoids many common bugs when using first class functions with loops. `for` loops evaluate the bounds once, before the first iteration. The Java/C++ equivalents of `for`-loop conditions are: | Java/C++ ----------------------|------------------------ `for i ≤ k` | `for (i = 0; i <= k; ++i)` `for i < k` | `for (i = 0; i < k; ++i)` `for 4 ≤ i < k` | `for (i = 4; i < k; ++i)` `for 4 < i < k` | `for (i = 5; i < k; ++i)` The comparison operators in a `for` loop declaration are an overloaded syntax for comparison operators (just as many languages overload the `=` operator for `for` loops). They have the lowest precedence. For example, `for i<a||b` is equivalent to `for i<(a||b)`. To use regular comparison operators within `for` loop preamble, you *must* use parentheses to make the parsing unambiguous, for example, `for i < (a < b)`. ### `for-in` Loop Container iteration `for` loops iterate over the keys of a object, the characters of a string, or the elements of an array. The main variations of a `for-in` loop are: - `for value in array:` - `for value in object:` - `for char in string:` - `for value at index in array:` - `for char at index in string:` - `for value at key in object:` The value variable is bound with `let`, so assignment to the value variable is legal but will not affect the underlying value in the array. `for-in` loops evaluate the container expression once, before the first iteration. It is illegal (and will usually result in an error) to add or remove values from the container of a `for-in` loop. This semantic avoids confusing situations for the programmer and is necessary for performance optimizations in the iterator implementation. Changing the order of values is legal, but discouraged because the iterator will touch each slot in order and will skip or duplicate values if they change slots. Writing to the current value is legal and frequently useful. If you would like to add or remove from the container, transform your code from `for x in container:` to `for x in clone(container):`. Cloning is fairly fast in PyxlScript. Or, use the syntax `let i = 0; while i < size(container): let x = container[i] ... ++i` and account for the index and size changes in your own iteration. The Java/C++ equivalents of `for-in`-loop conditions are: | Java/C++ ----------------------|------------------------ `for x ∈ S` | `for (x : S)` ### `for-with` Loop You can combine a `for` and `with` statement together by chaining `∈` expressions, in what is called a `for`-`with` loop. The properties exposed by the `with` are references to the actual values and can be mutated directly. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript for x,y ∈ point ∈ set: ... ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ is the same as: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript for point ∈ set: with x,y ∈ point: ... ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The `for`-loop variable (in this case, `point`) cannot be the same as any of the `with` variables (in this case, `x` and `y`). The variable list in a `for`-`with` or `with` expression can be split across multiple lines by breaking after commas: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript for health, power, stamina in character: health += stamina * power ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ### Examples #### `for` loop variations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript // Iterate over each current enemy once, destroying those // that overlap the missile for enemy in clone(enemy_array): if overlaps(enemy, missile): remove_value(enemy_array, enemy) // Regenerate hit points for every player for health, regen_rate in player in player_array: health += regen_rate // Position each equally-spaced satellite for satellite at i in satellite_array: const a = i * 360deg / size(planet_array) satellite.pos = xy(cos a, sin a) * ORBIT_RADIUS // Iterate over an object's keys for rating at stat in player.stats: draw_text(font, stat + ": " + format_number(rating, "%"), pos, #FFF) pos.y += font.line_height // Iterate over an object used as a set already_visited[current_room] = true for room_name in already_visited: draw_text(font, room_name, pos, #FFF) pos.y += font.line_height // Make a segmented-body centipede move if joy.x or joy.y: // Don't use += because the loop passes objects instead of copying them centipede[0] = centipede[0] + joy.xy for 0 < i < size(centipede): centipede[i] = centipede[i - 1] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [`for` loop examples] #### Array Iteration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript let names = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] // Iterate over array elements let pos = xy(½ SCREEN_SIZE.x, 10) for planet in names: pos.y += draw_text(font, planet, pos, #f) // Iterate over values and modify (this is safe, no elements will be // skipped. You can even remove elements ahead of the iterator and // they will still be processed) for planet in names: // Remove any name with an "a" in it if find(planet, "a") ≠ ∅: remove_values(names, planet) // Add some random moons...the current loop will not // iterate over these, but they are immediately visible // in the array. if random() > 50%: push(names, planet + " moon") // Iterate over array keys pos = xy(½ SCREEN_SIZE.x, 10) for i < size(names): pos.y += draw_text(font, names[i], pos, #f) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #### Object Iteration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript let poemBy = { Angelou: "Still I Rise", Frost: "Stopping by Woods on a Snowy Evening", Dickenson: "Because I could not stop for Death", Whitman: "Leaves of Grass", Hughes: "Montage of a Dream Deferred", Williams: "In the American Grain", Yeats: "Sailing to Byzantium", Browning: "How Do I Love Thee?" } // Iterate over array elements let pos = xy(½ SCREEN_SIZE.x, 10) for author in poemBy: pos.y += draw_text(font, poemBy[author] + " by " + author, pos, #f) // Iterate over values and modify (this is safe, no elements will be skipped) for author in poemBy: // Remove this entry if it contains an "a" in the value if find(poemBy[authpr], "a") ≠ ∅: remove_key(poemBy, author) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #### Array of Objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript let spots = [xy(1, 1), xy(100, 2), xy(10, 20), xy(50, 20)] for x, y in S in spots: // Remove any element that is higher than 15 if y > 15: remove_values(spots, S) let i = 0 while i < size(spots): // More efficient O(1) removal without preserving order // if spots is *really* long if spots[i].x > 15: fast_remove_key(spots, i) else: ++i ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Debugging --------------------------------------------------------- There are two special forms for debugging that can be enabled or disabled at runtime for efficiency on the Output pane. These are not functions and cannot be passed as values like functions or stored in data structures. `debug_print(msg)` sends text output to the IDE Output pane. It runs `unparse()` on `msg` if it is not already a string. These statements are automatically removed from the program when debug printing is disabled. `assert(condition)` or `assert(condition, msg)` halts program execution if `condition` is false. The `msg` is evaluated regardless of the value of condition. These statements are automatically removed from the program when assertions are disabled. Multiline Expressions --------------------------------------------------------------- Newlines end PyxlScript expressions, unless there are unbalanced brackets (), [], {}. This allows you to spread function calls, array literals, and object literals over multiple lines. For example: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript let player = { center: xy(100, 40), angle: 45deg, inventory: ["hat", "sword", "money", "watch", "apple"] } let x = (43 + 17) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If an error occurs in a multiline expression, the line number reported will be that of the beginning of the expression. If a line contains the end of a multiline expression, it may not also contain multiple statements. Symbols --------------------------------------------------------------- The IDE supports autocorrect for typing the optional special symbols such as `nil` --> `∅`, `1/2` --> `½`, and `>=` --> `≥`. To generate paired symbols, type the name of the corresponding function without arguments. For example: `floor()` --> `⌊⌋`, `ceil()` --> `⌈⌉`, `magnitude()` --> `‖‖`, and `abs()` --> `||`. For convenience when using an external editor or typing the full set of characters supported by fonts into strings on a US-EN keyboard, you can also copy and paste from here: ````````````````````````````````````````````````````` none big ∅ ° ½ ² ³ ξ ∞ ε ≠ ≤ ≥ π ∊ ⌊ ⌋ ⌈ ⌉ ‖ ≟ θ Mode ══════════════════════════════════════════ frame ────────────────────────────────────────── ````````````````````````````````````````````````````` [Most common symbols] ````````````````````````````````````````````````````` none big 1ˢᵗ 2ⁿᵈ 3ʳᵈ 4ᵗʰ 1ᵉʳ 1ʳᵉ 2ᵉ 2ᵈ 2ᵈᵉ 1ᵒ 1ᵃ ````````````````````````````````````````````````````` [Ordinals in different languages] ````````````````````````````````````````````````````` none big x⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁺ ⁻ ⁽ ⁾ yᵃ ᵝ ⁱ ʲ ᵏ ⁿ ᵘ ˣ ʸ ᶻ ````````````````````````````````````````````````````` [Superscripts. These are legal math in code and can be used in strings.] ````````````````````````````````````````````````````` none big ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₊ ₋ ₍ ₎ ₐ ᵦ ᵢ ⱼ ₓ ₖ ᵤ ₙ ````````````````````````````````````````````````````` [Subscript symbols, usable only in strings] ````````````````````````````````````````````````````` none big A B Γ Δ E Z H Θ I K Λ M N Ξ O Π P Σ T Φ X Ψ Ω α β γ δ ε ζ η θ ι κ λ μ ν ξ o π ρ σ τ ϕ χ ψ ω ````````````````````````````````````````````````````` [Greek alphabet] ````````````````````````````````````````````````````` none big ½ ⅓ ⅔ ¼ ¾ ⅕ ⅖ ⅗ ⅘ ⅙ ⅐ ⅛ ⅑ ⅒ ∩ ∪ ≟ ≠ ≤ ≥ ∊ ⌊ ⌋ ⌈ ⌉ ∅ ∞ ° ````````````````````````````````````````````````````` [Math symbols legal in code and strings.] ````````````````````````````````````````````````````` none big À à È è Ò ò Ì ì Ù ù Ł ł Ś Š Ş ś š ş Ø ẞ Б Д Ґ Á á É é Ó ó Í í Ú ú Ć ć Ž ž Ź ź Ż ż ø ß б д ґ  â Ê ê Ô ô Î î Û û Ñ ñ Ж З И Й Л П Ц Ч Ш Щ Є Ä ä Ë ë Ö ö Ï ï Ü ü Ń ń ж з и й л п ц ч ш щ є Å å Ř ř Ď ď Ý ý Ů ů Ň ň в н κ м т à ã Ę ę Õ õ Ў ў Ç ç Æ Œ Э Ю Я Ъ Ы Ь Ą ą Ě ě Ť ť Ğ ğ Č č æ œ э ю я ъ ы ь ````````````````````````````````````````````````````` [Accented and Cyrillic characters] ````````````````````````````````````````````````````` none big ¥ € £ § × ⊢ ¬ ≈ ⊗ ⊕ ⊖ ∫ ± « » ★ ☆ ○ ● ◻ ◼ △ ▲ ▼ ◀ ▶ ✓ ♠ ♥ ♡ ♣ ♦ ♢ … ∙ ¿ ↑ ↓ ← → ↖ ↗ ↙ ↘ ❖ ✜ ♆ © ````````````````````````````````````````````````````` [Others] ````````````````````````````````````````````````````` none big ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓜ ⓝ ⓟ ⓠ ⓤ ⓥ ⓧ ⓨ ⓩ ⍐ ⍇ ⍗ ⍈ Ⓠ Ⓩ Ⓦ Ⓐ Ⓢ Ⓓ Ⓘ Ⓙ Ⓚ Ⓛ ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⓪ ⊖⊕ Ⓞ ⍍ ▣ ⧉ ☰ ````````````````````````````````````````````````````` [Button prompts. Device-aware and full button prompts are available via the `prompt` property of `gamepad_array[]` elements] Note that the superscripts `ᵈᵉʰᵐᵒʳˢᵗ` are legal in text strings but not as code. These are present specfically to support ordinals such as "1ˢᵗ, 2ⁿᵈ, 3ʳᵈ, 4ᵗʰ, 1ᵉʳ, 1ʳᵉ, 2ᵉ, 2ᵈ, 2ᵈᵉ, 1ᵒ, 1ᵃ, …" in various languages. The encircled characters and arrows are intended for use as button prompts. The characters `Ⓞ ⍍ ▣ ⧉ ☰` are drawn in fonts to represent the buttons with approximately those symbols on PlayStation and Xbox controllers. The `ⓧ` character is drawn to ambiguously represent the "X"/cross buttons on PlayStation, Xbox, and Nintendo controllers. Note that the preferred way of generating button prompts is using the `gamepad.prompt` object with the string `replace()` function, rather than hardcoding. Version Control ==================================================================================== is designed to work well with version control systems such as git, svn, Perforce, etc. The metadata and project files are all simple JSON text files and assets are all common binary formats. No hidden directories or temporary files are generated that could confuse a version control system. If you use git in particular, there is additional built-in support for simplifying workflow. The "Git Sync" button appears at the bottom of the project tree when detects that your game is stored in a git repository. Pressing the "Git Sync" button will download changes by others from the repo on the current branch to your local machine. These will be merged into any changes you have made. If there was a substantial merge, then you should probably test the game before proceeding further. If you have changes of your own, will ask if you want to upload them. If you choose to upload, it will prompt for a commit message. Sometimes a merge will produce a conflict. This happens when both you and your collaborator changed the same line of a file in different ways, or edited the same art asset. If a merge conflict occurs, you will be presented with the following choices: - Prefer Mine: Merge everything, and on lines where there is a conflict replace their changes with yours. - Prefer Theirs: Merge everything, and on lines where there is a conflict replace your changes with theirs. - Undo Sync: Stop the merge and put all of the files back the way they were before you pressed "Sync". You can then choose to address the problem with command line tools or ask a collaborator for help. For advanced git users, all of the git commands used and their outputs are shown on screen. You can also freely mix git command line or other GUI git tools with the built-in git support (although don't try it _while_ a command is being processed). Advanced Tools ==================================================================================== In addition to the friendlier browser-based tools from the Tools menu, the distribution includes command-line tools for experienced programmers. These support external editors, running the server locally, and performing some IDE tasks at the command line. Unlike the IDE, some of these have additional dependencies and assume that you know how to install those on your own. Local Emulator ----------------------------------------------------------------------------- You can launch the IDE on your local machine. This allows you to run without a network connection and to work with local files instead of Google Drive ones for the GitHub-hosted IDE. Launch your program in the emulator and debugger from the command line on Windows, macOS, or Linux with the `quadplay` command: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bash # Load the games/quadpaddle/quadpaddle.game.json sample program quadplay quad://games/quadpaddle # Load a mygames/space/space.game.json game that you've made quadplay mygames/space # Load from the internet (server must support CORS) quadplay https://morgan3d.github.io/somegame/foo.game.json # Just run the emulator. You can modify the URL in your browser # to add "?game=" and the game's URL or relative path quadplay ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Run `quadplay --help` to see more options for the script. These examples assume that your directory structure is: ********************************************************** * * 📂 quadplay * | * +-- 📄 quadplay * | * +-- 📂 fonts * | | * | ⋮ * | * +-- 📂 games * | | * | +-- 📂 quadpaddle * | | | * | | +-- 📄 quadpaddle.game.json * | | | * | ⋮ ⋮ * | * +-- 📂 mygames * | | * | '-- 📂 space * | | * | +- 📄 space.game.json * | | * | ⋮ * | * +-- 📂 sprites * | | * ⋮ ⋮ ********************************************************** [Directory structure with the `quadplay` script in the root.] The directory and `.game.json` file do not have to have the same name. You can list the full path to the actual game file instead. Every time you push the play button (F5) or reload button (Ctrl+R) in the emulator, it will reload your code and assets from disk. By default, `quad://` assets are cached for fast reload peformance. To disable this behavior, force a reload of the entire page with shift+browser reload button, or remove `&fastReload=1` from the URL. Visual Studio Code ------------------------------------------------------------------------------------ Visual Studio Code natively supports the `.json` files. To configure it with PyxlScript file support as well: 1. Run Visual Studio Code 2. Press F1 and type `install code` and then press enter 3. Open a terminal and change directory to `quadplay/tools` 4. At the command line, run `code --install-extension vscode-pyxlscript.vsix` Vim ------------------------------------------------------------------------------------ To install the PyxlScript mode for Vim, you'll need to modify your .vimrc. If you're using a plugin manager like [dein](https://github.com/Shougo/dein.vim): ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ call dein#add("path/to/quadplay/tools/vim-pyxlscript-syntax", {'frozen':1}) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you want to directly copy the files into your ~/.vim directory, make sure you copy (or symlink) all the files into the corresponding directories like they structured in the plugin. [`pyxlscript.vim`](../tools/pyxlscript.vim) Emacs ------------------------------------------------------------------------------------ To install the PyxlScript mode for Emacs, add the following lines to your `~/.emacs` file: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Lisp ; Optional if you don't want to copy the files: (add-to-list 'load-path (expand-file-name "path/to/quadplay/tools")) (autoload 'rainbow-mode "rainbow-mode") (autoload 'pyxlscript-mode "pyxlscript-mode") (add-to-list 'auto-mode-alist '("\\.pyxl\\'" . pyxlscript-mode)) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Note that you have to put the real path in for the `"path/to/quadplay/tools"`, such as `~/quadplay/tools`, or wherever you installed quadplay to. Other Editors ------------------------------------------------------------------------------------ Fortunately, PyxlScript syntax is very close to Python syntax. If you tell your editor to treat `.pyxl` files as Python files, then it will probably handle indenting and most keywords correctly. Comments are the one element that will probably confuse your syntax highlighter. Command-Line Tools ------------------------------------------------------------------------------------ `quadplay` : Launches the emulator from the command line on any operating system. Requires Python. See the arguments above. (Depending on operating system, this command may run the `quadplay.cmd`, `quadplay.vbs`, or `quadplay` script.) `quaddepend.py` : Prints dependencies of a `.game.json` file, recursively. Run the script with `--help` to see the arguments. Run as `tools/quaddepend.py ...` or `python3 tools/quaddepend.py ...` from the main quadplay directory. `export.py` : Export a static HTML site for a single game, either lightweight by referencing `https://morgan3d.github.io/quadplay` or standalone by embedding a full copy of the emulator. Run the script with `--help` to see the arguments. Run as `tools/export.py ...` on Linux and macOS, or `py tools/export.py ...` on Windows from the main quadplay directory. This script itself depends on `tools/quaddepend.py`. `project_linter.py` : Linter for quadplay projects. Intended to help when finalling a project, it has a number of checks including: - unused assets - unused constants - missing licenses - missing quadplay metadata (preview.png, etc) - report of licenses used in a project (use the --license-audit flag) For a full list of features, use the --help argument. Run it in the same directory that containst the game.json for your project. `sprite_json_generator.py` : Generator for `.sprite.json` files. Requires Python and PIL. If you are exporting from aseprite, use the following settings with `sprite_json_generator.py`: - Sheet type: "by row" (quadplay doesn't support animations wrapping around image rows - Constraints: "None" - Sprite: - Layers: "visible layers" (quadplay doesn't support layers") - Frames: "all frames" (unless you only want some frames) - "split layers" should be unchecked (OFF) - "split tags" should be checked (ON) - Borders: - "spacing", "trim", and "extrude" should all be off (set to 0) - Output: - "export json": yes and under "meta" for this: - "layers" should be off - "frame tags" should be checked - "slices" should be off - "Array" (_not_ "Hash") mode - Output file and JSON Data should be checked (these are what you feed into sprite_json_generator) - "open generated sprite sheet" is up to you, if you want it to show you the sprite sheet when its done. If you export with these settings, you can pass the generated json file to the sprite_json_generator program and it will convert the tags and sprite size automatically from the file. Note that while aseprite supports sprites of varying size, quadplay does not. `quadplay.html` ------------------------------------------------------------------------------------ The `quadplay.html` application launched by the `quadplay` script accepts the following HTML query arguments: `IDE` : If set, load in development mode with access to the full IDE and stop and pause buttons. Cannot be set with `kiosk` `fastReload` : If `1`, do not force reloading of `quad://` files from disk but instead allow browser caching. Those resources are unlikely to change (unless you are modifying quadplay itself!), so this speeds up game reloading, especially for music. `game` : URL of a game to load, which can be relative to the `quadplay` script. `mode` : Initial mode to display: `Maximal`, `Emulator`,`IDE`, `Test`, `WideIDE`, `Editor`, `Ghost`, or `DefaultWindow`. The default is `IDE` when running the IDE and otherwise `Maximal` for non-touch screens and `Emulator` for touch screens. `DefaultWindow` reverts to the default but suppresses automatic full-screen on launch when not on a touch screen. `nofs` : If `1`, do not try to `kiosk` : If `1`, hide the entire menu bar. Cannot be set with `IDE` `nosleep` : If `1`, prevent sleeping in kiosk and normal mode, no matter what the autosleep setting is. `quit` : What the "quit to system" menu option in the pause menu and the `quit_game()` function do: - `launcher`: Return to the launcher or IDE. This is forced when `IDE=1`. Default when `game` is not specified. - `close`: Close the browser tab or standalone app. Default when `game` is specified and `IDE=0` and `kiosk=0`. Unless the page was opened by JavaScript from another page, browsers prohibit closing the tab from code and this will instead set the page to blank. - `none`: Do not offer a quit option to the player. Default when `game` is specified and `kiosk=1` - `reload`: Reload the iframe or tab that the game is in, sending it back to the welcome screen. This value prevents the game from launching in fullscreen until the first press, which is good for games embedded in a page and is the way that exported web games are configured. `nativeapp` : If `1` and running on a quadplay server, quitting the IDE shuts down the server. `update` : If `1` and running on a quadplay server, check for newer versions of quadplay. Physical Consoles --------------------------------------------------------------------------------------------- ### Build I expect most people will use on phones, laptops, and desktops as a _virtual_ console. But, if you have some maker experience you can make a _real_ console. If you want an arcade form factor, then a arcade cabinet can be built with some carpentry and any low-cost PC and display, or by purchasing some (expensive!) pre-built arcade cabinets. #### Processor Any Mac, Windows, or Linux computer can be used to power a physical build. An old laptop often works well. Alternatively, an embedded processor can be used. The [Rasberry Pi 400](https://www.raspberrypi.org/products/raspberry-pi-400/) is a complete computer for only $70 that can run and plug directly into a television or monitor. ![Cort Stratton's [Rec Room Xtension/(https://recroommasters.com/product/upright-4-player-arcade-machine/) arcade cabinet powered by Raspberry Pi 5.](postgoodism.jpg width=50%) With a 3D-printed case and some Bluetooth or wired game controllers, you can build your own retro mini console to connect to a TV or monitor. [Jetson Nano](https://developer.nvidia.com/embedded/jetson-nano-developer-kit) and [Raspberry Pi 4](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/) cost less than $100 and can power such a box using Linux and Chromium. Both will hit 60 Hz for most games, and can easily run at 30 Hz for all games ( handles framerate scaling for you seamlessly, so you won't even notice most of the time). ![Quadplay mini-console built by Morgan McGuire from a Jetson Nano with a 3D-printed case, USB Xbox 360 controller, and small external monitor.](nano-arcade.jpg width=50%) I've built Jetson Nano and Raspberry Pi consoles and the result is satisfyingly comparable to the Nintendo [NES Classic Edition](https://www.nintendo.com/nes-classic/), [Super NES Classic Edition](https://www.nintendo.com/super-nes-classic/), Sega [Genesis Mini](https://genesismini.sega.com/), and PlayStation [Classic](https://www.playstation.com/en-ca/explore/playstation-classic/) devices. The big difference from commercial mini retro consoles is that you can program your own quadplay console. The commercial consoles only play their own games. ![Self-contained quadplay console built by Morgan McGuire from a Raspberry Pi and standard screen case, with Bluetooth controllers.](rpi-arcade.jpg style="image-rendering:none" width=50%) My Raspberry Pi 4 software configuration from the default Raspberry Pi OS instalation is simply: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bash sudo apt install snapd sudo reboot # After reboot: sudo snap install core sudo snap install chromium sudo update-alternatives --install /usr/bin/x-www-browser x-www-browser /snap/bin/chromium 200 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is an alternative using the provided Debian package for automatic management, but gives an older version of Chromium that is slower: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bash sudo apt install chromium sudo update-alternatives --install /usr/bin/x-www-browser x-www-browser /usr/bin/chromium-browser ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For the Raspberry Pi 400 (the model built into a keyboard), I also [overclock to 2 GHz](https://www.raspberrypi-spy.co.uk/2020/11/overclocking-the-raspberry-pi-400/). A Jetson Xavier, Intel NUC, or old PC/Mac is a more expensive device for powering a mini console, but has a lot more graphics power than an embedded processor and will comfortably run all games at 60 Hz. #### Arcade Controller When you're color coding the physical controls for a full four-player setup, the standard arcade player colors and physical layout order is: P3 Yellow, P1 Magenta ("pink"), P2 Cyan ("blue"), P3 Lime ("green"). Graphically:
SCREEN
*P3*
#fd3
*P1*
#f5a
*P2*
#0af
*P4*
#4e4


There are a lot of physical [arcade controllers](https://shop.xgaming.com/products/x-arcade-dual-joystick-usb-included) available at reasonable prices for building out the controls without needing any custom electronics. Note that supports (but does not require) a touch screen or mouse, which will not be available on a normal cabinet setup. One alternative is to use a small trackpad mounted on top of the joystick controller. ![Arcade station built by Morgan McGuire running quadplay on a 2012 MacBook with an X-Arcade Dual controller.](xarcade-arcade.jpg style="image-rendering:none" width=50%) ![Arcade station custom built by Nick Porcino running quadplay on Raspberry Pi](arcade.jpg style="image-rendering:none" width=50%) For arcade configurations, the order of the physical controllers in the `gamepad_array[]` matters because they are at fixed locations. The default order of controllers is set by the operating system. From the Controllers screen of the in-game pause menu or the Controls tab of the IDE's debugger, you can bring up the quadplay dialog to change this order. When building your own controller, ensure that it maps correctly using the online tool at https://html5gamepad.com/. The specific mapping between HTML buttons and is: | HTML Gamepad :-------------------:|:-------------------------------: Analog x-axis | axis 0 (left is negative) Analog y-axis | axis 1 (forward is negative) ⓐ | button 0 ⓑ | button 1 ⓒ | button 2 ⓓ | button 3 ⓔ | button 4 ⓕ | button 5 ⓠ | button 8 ⓟ | button 9 D-pad | button 12 D-pad | button 13 D-pad | button 14 D-pad | button 15 automatically maps the analog stick into D-pad inputs as well as providing analog access under `device_control()`. See the Arcade Controller Mappings Section for more detail on some common arcade/fighting game controller mappings. ******************************************************** * | * ⓠ ⓟ | ⓠ ⓟ * ⍐ ⓓ ⓕ | ⍐ ⓔ ⓒ ⓓ ⓕ * ⍇ ⍈ ⓒ ⓑ | ⍇ ⍈ * ⍗ ⓔ ⓐ | ⍗ ⓔ ⓐ ⓑ ⓕ * | ******************************************************** [Sample layouts for physical controller buttons on arcade sticks] If you have the option to choose your button layout and mapping, then try to match one of the versions shown above. Specifically, games are designed for the ⓔ button to be placed where it can be easily held while using the main buttons and stick. For example, as a hat button on the stick or under the right thumb or pinkie. The ⓕ button should be placed where it can be used similarly, but ⓔ should be the more convenient of the two and favor the left side. Note the positioning of the player 1 and 2 ⓔ and ⓕ on the keyboard compared to on a NES-style gamepad...the keyboard controls place these buttons _lower_ than on a gamepad, so that they are in reach of the pinkies for ease holding down. They are not "shoulder" buttons, even though that is the best location for them on a physical controller. #### Display For a physical cabinet, a 24" screen will match a traditional arcade size. For a handheld or mini installation, the screen size is constrained by your form factor. Anything larger than 5" will be easily readable even for the smallest 4-pixel text (and most quadplay game text is 8 pixels high). has a default 16:9 screen resolution. However, aspect ratio is not as important to image quality as display that can be filled with integer multiple of 384x224 resolution. I recommend using a 1280x720 ("720p"), 1600x900, 1920x1200 ("16:10 WUXGA"), or 2560x1440 ("WQHD 2K") resolution display. A 2560x1440 display can be run at the OS level in 1280x720 resolution to reduce GPU load for a low end or embedded GPU. Any display will scale down to 60 Hz refresh, so refresh rate is not a consideration. Those resolutions can efficiently fill the screen with integer pixel scaling. For 27" monitors, these cost around $200 new as of 2025 but can often be found used for less (or free) as most people have upgraded their computers to higher resolutions. If you are not a purist, you can find a more inexpensive 1920x1080 display. This will use non-integer scaling where some of the pixels are 4 display pixels wide and most are 5 pixels wide, or leave a black border around the screen when using perfectly uniform 5x scaling. The autoscaler will adjust to fill the screen unless the display resolution is very low or only slightly above an integer ratio, in which case it will favor the integer ratio. Efficiency and scaling ratios when using 's default 384x224 resolution are: Screen | Integer | Used | Efficiency | Autoscaler | Used | Efficiency ----------:|------------:|----------:|------------:|-----------:|----------:|---------: 480x320 | 1x | 384x224 | 56% | 1.00x | 384x224 | 56% 640x360 | 1x | 384x224 | 37% | 1.00x | 384x224 | 37% 640x480 | 1x | 384x224 | 28% | 1.00x | 384x224 | 28% 800x480 | 2x | 768x448 | 72% | 2.00x | 768x448 | 72% 1024x768 | 2x | 768x448 | 44% | 2.00x | 768x448 | 44% *1280x720* | *3x* |*1152x672* | *84%* | 3.21x | 1234x720 | 96% 1280x800 | *3x* | 1152x672 | 76% | 3.33x | 1280x747 | 93% 1366x768 | 3x | 1152x672 | 74% | 3.41x | 1316x768 | 96% 1440x900 | 3x | 1152x672 | 60% | 3.75x | 1440x840 | 93% *1600x900* | *4x* |*1536x896* | *96%* | 4.02x | 1544x900 | 97% 1680x1050 | 4x | 1536x896 | 78% | 4.38x | 1680x980 | 93% 1920x1080 | 4x | 1536x896 | 66% | 4.82x | 1852x1080 | 96% *1920x1200*| *5x* |*1920x1120*| *93%* | 5.00x | 1920x1120 | 93% *2560x1440*| *6x* |*2304x1344*| *84%* | 6.42x | 2468x1440 | 96% ### Buy works on all Mac, Windows, and Linux devices, so any computer you buy can be a dedicated device for playing or making games. For a _handheld_ form factor there are a few options. We've tested on the Steam Deck, GPD Win 2, GPD Win Max, and GPD Win 3. All run well: - 1280x800 [Steam Deck](https://www.steamdeck.com/), SteamOS/Linux in "Desktop" mode = full-res 3x/3.33x scaling, 4x scaling of 320x180 - 1280x800 [GPD Win Max](http://gpd.hk/gpdwinmax), Windows and Linux = full-res 3x integer or 3.33x scaling, 4x scaling of 320x180 - 1280x720 [GPD Win 3](https://www.gpd.hk/gpdwin3) and [GPD Win 2](http://gpd.hk/gdpwin2), Windows and Linux = full-res 3x/3.33x scaling, 4x scaling of 320x180 ![Quadplay running on Steam Deck under Firefox in Desktop mode with no installation.](steamdeck.jpg style="image-rendering:none" width=50%) Some other physical handhelds that may be capable of running games include: - 2560x1600 [GPD Win Max 2](https://gpd.hk/gpdwinmax2) - 2560x1600 [OneXPlayer](https://onexplayerstore.com/) - 2560x1600 [AYN Odin](https://www.indiegogo.com/projects/odin-the-ultimate-gaming-handheld/x/27430815#/) with sideloaded Windows 10 - 1920x1080 [GPD Win 5](https://www.gpd.hk/gpdwin42025) - 1920x1080 [AYANEO Flip, Flip DS](https://store.ayaneo.com/) - 1920x1080 [OneXPlayer Mini](https://onexplayerstore.com/) - 1280x800 [AYANEO, Neo Pro, Neo Next](https://store.ayaneo.com/) - 1280x720 [Anbernic Win600](https://anbernic.com/products/new-anbernic-win600) - 1280x720 [DragonBox Pyra](https://pyra-handheld.com/boards/pages/pyra/), Linux = full-res 3x/3.33x scaling - 480x320 [Odroid-Go Advance Black](https://liliputing.com/2020/05/the-new-odroid-go-advance-adds-wifi-and-more-buttons-linux-handheld-game-console.html), Linux = full-res 1x scaling; poor fit ![GPD Win 3](gpd-win3.jpg style="image-rendering:none" width=50%) Mobile Development ===================================================== The quadplay IDE allows you to easily test your game on a mobile device while developing from a laptop or desktop computer. First, launch the `quadplay` script on your computer with the `--serve` flag. For example, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bash quadplay --serve games/quadpaddle/ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ (Use your own game, not the `quadpaddle` sample, of course). Second, in the web browser that opens on your computer, select the Project or Test view at the upper right. Click on the Mobile tab of the debugging pane on the lower right: ![The QR code in the Host tab of the IDE](mobile-tab.png) Third, _on your mobile device_, either scan the QR code and then open the URL it shows in a browser, or manually type the URL into your mobile browser. Your mobile device will now play the game direct from your computer. You can edit the game or assets on your computer and reload the browser page on your mobile device to see the changes. You do not need to re-scan the QR code for each change. !!! note Mobile Controls On a mobile device, quadplay games be played with the on-screen touch controls or a physical game controller paired over BlueTooth or plugged in directly over USB. Deploying Games ==================================================================================== Your games can be played on any major web browser on desktop/laptop, phone, tablet, or embedded processor such as Raspberry Pi. You can also distribute them as phone or desktop apps. You may charge money for your games and may distribute them as closed source. It is your responsibility to satisfy copyright law and any licensing fees or royalties for content that you use when you distribute your game. The content provided with is all royalty-free Creative Commons and contains its own licensing data. Content that you find elsewhere will have its own rules and restrictions. The code provided with is all Open Source (even though it permits you to use it with your own closed-source came). See the Credits section for more information. itch.io ------------------------------------------------------------------------------------ Deploying to [itch.io](https://itch.io/) as a Browser Game allows you to create a self contained online game that others can buy from you, or play for free if you choose. The steps are: 1. On Windows, run `py -3 path-to-quadplay\tools\export.py` inside the directory containing your `game.json` file. On macOS and Linux, use `path-to-quadplay/tools/export.py`. 2. [Click here](https://itch.io/game/new target="_blank") and in the tab that opens, enter the following settings: - Title: _your game name_ - Project URL: _your game name_ - Classification: Games - Kind of project: HTML - Embed Options: - How should your project be run in your page?: Embed in page, Manually set size - Mobile friendly: Yes - Orientation: Landscape - Automatically start on page load - Visibility & Access: Public 3. In the Uploads section on the itch.io game page, upload the zipfile generated in step 1. 4. Check "This file will be played in the browser" To update your game or upgrade the underlying distribution, repeat the process of creating the zipfile and then upload it as a replacement for the original file. ![](itch.png style="image-rendering:none" width=50%) GitHub ------------------------------------------------------------------------------------ ### Combined develop and deployment repo This is the easiest method to deploy your game. It also allows you to continuously update the game as well as gain the benefit of improvements to itself continuously. Store your game in a git repository on [github.com](https://github.com), or in a subdirectory of a GitHub repository. Do not fork quadplay into the repo because that will make it large and disrupt the user's saved per-console state. Just keep your game there in the repo by itself as you create it. Assume that your GitHub account is named `USER` and your repo is named `REPO`. To deploy, go to the settings page for your repo on GitHub. For example, `https://github.com/USER/REPO/settings`. In the section titled "GitHub Pages" on the settings page, select "main" from the Source section dropdown. The URL to play your game is then: `https://morgan3d.github.io/quadplay/console/quadplay.html?game=https://USER.github.io/REPO&mode=Maximal`. If you'd like to make a friendlier URL, then you can also create an `index.html` file in the root of your repo with the contents: (embed github-example.txt here) People visiting the URL `https://USER.github.io/REPO` will then be redirected to the correct page. ### Separate repos To keep your development repository private or just separate from the deployment one, create a second repository on GitHub. Clone both to your local machine and at the command line, enter the _development_ repo and run: ````````````````````````````````````````````````````````````` # macOS and Linux: tools/export.py -o ../PUBLIC_REPO path/to/game # Windows: py -3 tools\export.py -o ..\PUBLIC_REPO path\to\game ````````````````````````````````````````````````````````````` You can then add and commit the public repo. If you want your game to be standalone and not depend on the quadplay repo at runtime, just add the `--standalone` argument. This will generate an `index.html` file as well as copying all dependencies. Web Server ------------------------------------------------------------------------------------ You can host your game using only your own files on any web server that supports [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), or as a copy of all of on _any_ web server, regardless of whether it supports CORS. To deploy your game on a web server with CORS named `myserver.com` in the `mygame` directory, run just upload your own game files to the server. The URL for your game is: https://morgan3d.github.io/quadplay/console/quadplay.html?game=https://myserver.com/mygame To deploy a complete package that is independent of the current version of , run `tools/export.py -o path/to/output path/to/game` on macOS or Linus and `py -3 tools\export.py -o path\to\output path\to\game` on Windows. Then, upload your files from the output directory. Your URL will be just https://myserver.com/mygame. Phones ------------------------------------------------------------------------------------ Any game hosted on a web page using the methods from the previous sections can be played on a mobile phone. The emulator automatically creates on-screen buttons when it detects a phone, and it also supports gamepads connected to a phone. You can also deploy your game through a phone app store in order to sell it. This is an advanced process. You must wrap your game with a binary program. [Cordova](https://cordova.apache.org/) is a free tool that you can use to automate this process. It requires familiarity with [npm](https://www.npmjs.com/), web apps, the app store that you are deploying on, and the tools needed (such as Xcode for iOS) to compile on the platform. The basic idea is to use the method described in the itch.io section to create a standalone web app, and then use Cordova to create the phone app. Desktop ------------------------------------------------------------------------------------ [Electron](https://electronjs.org/) is a tool that turns web apps into desktop binary projects which can be distributed through the Windows or Mac App stores, Steam, the Epic Store, etc. It works by bundling an entire web browser with your application, wrapped so that it looks like a native app. The process is similar to that of using Cordova. Performance ==================================================================================== is designed to make it easy to write small games that have very readable source code. I recommend designing your code primarily for readability instead of performance. However, at some point you'll probably hit the performance limits and want to optimize at least some of the program. Here's where to focus your efforts. The emulator is fastest in Full Screen or Touch Screen mode. This eliminates the extra IDE content and avoids several milliseconds per frame of updates required for those. To avoid input lag, process game pad input _before_ rendering and simulation each frame. This allows the players' input to affect the current frame instead of being delayed one frame. Game pads (and browsers) have 1-2 frames of input lag in them. To conceal this, allow critical input such as a jump, block, or dodge to happen up to one or two frames _after_ the period in which it should have. For example, do not damage the player character until one frame after they are attacked and allow a late dodge or block to save them retroactively. In the "Performance" tab of the IDE, you can see the amount of time spent per frame. If time spent is less than 16 ms, then your program is running at 60 fps. If time spent is higher than 16 ms, then you may need to optimize. `draw_map()` is substantially faster than making the same `draw_sprite()` calls by yourself. It culls all off-screen sprites. Drawing a map with `camera.zoom = 1` or `z_scale = 0` is faster than with perspective and zoom. Tiles without alpha will cull everything behind them when not using perspective, so you don't have to leave out the hidden map tiles for performance when drawing maps. Maps with opaque tiles that are not rotated or scaled draw extremely fast. `draw_map_span()` is substantially faster than your own per-pixel rendering using `get_map_pixel_color()` and `draw_point()`. It is fastest when using opaque map tiles, a single layer of a map, and no override color. The Kart example program intentionally uses a slower case as a stress test. Built-in math functions are faster than math functions that you write to do equivalent operations because they are designed to minimize memory allocation internally. The intrinsics are maximum speed but can make code hard to read. There is generally no need to cull objects that are off screen. The rendering code is very good at removing those for you. `draw_sprite()` and `draw_map()` are fastest for `opacity=1` sprites that are not rotated or scaled and do not have alpha. It can directly copy the memory in this case. It can be substantially faster (but use more memory) to use pre-drawn transitions between map terrain tiles instead of layering an alpha-masked tile on top of another one to blend them. If you can tell that an object is covered by another object in `z`, remove it (this is called occlusion culling). The renderer must process both otherwise even though the object that is behind is unseen. All of the drawing calls are much faster for opaque colors than ones with fractional alpha values. Perfectly horizontal lines are much faster to draw than vertical lines. Vertical lines are slightly faster than diagonal lines. Sequential `draw_point()` calls that have the same `z` value are significantly optimized in order to enable efficient per-pixel graphics. Sequential `draw_sprite()` calls that have the same `z` value and the same clipping region are slightly optimized to enable efficient custom map rendering and particle systems. Numeric for loops, such as `for i < N` are slightly faster than iterator for loops such as `for x in A`. `with` statements are relatively slow, as are `for`-`with` loops on large arrays. Use them for readability on individual or small sets of objects but not on large arrays or in loops that repeat many times. It is slightly faster to perform math on individual elements than vectors. For example, `pos.x += vel.x; pos.y += vel.y` is slightly faster than `pos += vel`. The tradeoff of expanding out math is generally not worth the readability cost unless processing hundreds of vectors. The performance advantage diminishes as the objects grow larger. For example, `rgba` vectors or adding whole arrays to each other have relatively less overhead than `xy` vectors. The IDE is designed to collapse gracefully so that you can use an external editor. Here's one useful layout: ![](external-editor.png width=50% style="image-rendering:auto") The editor on the left is emacs running in a terminal. On the right is the browser reduced to the debugging output and emulator. Be careful to always end your *filenames* with _two_ extensions: `.game.json`, `.sprite.json`, `.map.json`, etc. This is how the IDE and compiler knows what type it is loading. Take note of the 4-bit sprite [quantizing tool](../tools/quantize.html). I find it better than the quality produced by Photoshop for color reduction. Vision ==================================================================================== The API is stable, and many people are using it to make hobby games, participate in game jams, and to learn/teach programming. The soul of as a console is described by: - Up to four players with a shared screen - Minimal time from launch to main play - 1992 output limitations, 2025 development power The overarching design principles of the quadplay IDE and language are: - *Make small game development friendly and fun* - Limitations that: - Allow games to run effectively on a wide range of displays, processors, and input devices without any per-platform work - Focus developers on gameplay and innovation - Help developers prevent scope creep - Allow developers to bypass limitations to customize for a specific platform or game - Support working wherever developers choose: - Entirely in a browser - Primarily in external tools - Or any combination! Credits ==================================================================================== was created by Morgan McGuire. Stephan Steinbach provided extensive testing and design feedback, Josh Minor helped with the networking design, and Mauricio Vives proofread the manual. The console's influences include Atari 2600, Commodore VIC-20, APL, Python, [PICO-8](https://www.lexaloffle.com/pico-8.php), [Shadertoy](https://www.shadertoy.com), [Arduboy](https://arduboy.com), and its predecessors, [codeheart.js](https://casual-effects.com/codeheart/) and [the nano JAMMER](https://morgan3d.github.io/nano). The IDE and compiler are copyright 2019-2025 Morgan McGuire. They are licensed under the [LGPL 3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html), but may be relicensed in the future. This includes the contents of: - [`quadplay.html`](../console/quadplay.html) - [`quadplay.css`](../console/quadplay.css) - [`quadplay-runtime-cpu.js`](../console/quadplay-runtime-cpu.js) - [`quadplay-runtime-gpu.js`](../console/quadplay-runtime-gpu.js) - [`quadplay-runtime-ai.js`](../console/quadplay-runtime-ai.js) - [`quadplay-runtime-physics.js`](../console/quadplay-runtime-physics.js) - [`quadplay-profiler.js`](../console/quadplay-profiler.js) - [`quadplay-browser.js`](../console/quadplay-browser.js) - [`quadplay-network.js`](../console/quadplay-network.js) - [`quadplay-ide.js`](../console/quadplay-ide.js) - [`quadplay-mode.js`](../console/quadplay-mode.js) - [`quadplay-edit-asset.js`](../console/quadplay-edit-asset.js) - [`quadplay-edit-code.js`](../console/quadplay-edit-code.js) - [`quadplay-edit-game.js`](../console/quadplay-edit-game.js) - [`quadplay-edit-constant.js`](../console/quadplay-edit-constant.js) - [`quadplay-vcs.js`](../console/quadplay-vcs.js) - [`quadplay-help.js`](../console/quadplay-help.js) - [`quadplay-font.js`](../console/quadplay-font.js) - [`quadplay-load.js`](../console/quadplay-load.js) - [`quadplay-debug.js`](../console/quadplay-debug.js) - [`pyxlscript-compiler.js`](../console/pyxlscript-compiler.js) and excludes the built-in PyxlScript examples, games, and content described below. The Vim mode is by [Stephan Steinbach](https://twitter.com/stephan_gfx). The VS Code extension is by [Josef Spjut](http://josef.spjut.me/). GIF support via [gif.js](https://github.com/jnordberg/gif.js) (c) 2013 Johan Nordberg, used under the [MIT license](https://github.com/jnordberg/gif.js/blob/master/LICENSE). It includes code by Kevin Weiner, Thibault Imbert, and Anthony Dekker. QR code support via [qrcodejs](https://github.com/davidshimjs/qrcodejs) (c) 2012 [@davidshimjs](https://twitter.com/davidshimjs) used under the [MIT license](https://raw.githubusercontent.com/davidshimjs/qrcodejs/master/LICENSE). [PeerJS](https://peerjs.com/) is Copyright 2015 Michelle Bu and Eric Zhang. It is used under the MIT license. The random number generator is derived from [an implementation](https://github.com/AndreasMadsen/xorshift/blob/master/xorshift.js) of xorshift (c) 2014 Andreas Madsen and Emil Bay used under the [MIT license](https://github.com/AndreasMadsen/xorshift/blob/master/LICENSE.md). The code editor for the IDE is [ace.js](https://ace.c9.io/) used under the [BSD license](https://github.com/ajaxorg/ace/blob/master/LICENSE). The physics engine is [matter.js](https://brm.io/matter-js/) and [poly-decomp.js](https://github.com/schteppe/poly-decomp.js), both used under the [MIT license](https://raw.githubusercontent.com/liabru/matter-js/master/LICENSE). The compiler uses [vectorify.js](https://github.com/morgan3d/misc/tree/main/jsvectorify) (c) 2018 Morgan McGuire under the MIT license, [recast](https://github.com/benjamn/recast) (c) 2012 Ben Newman under the [MIT license](https://github.com/benjamn/recast/blob/master/LICENSE), and [estraverse](https://github.com/estools/estraverse) (c) 2012-2016 Yusuke Suzuki under the [BSD 2-clause license](https://github.com/estools/estraverse/blob/master/LICENSE.BSD). [`LoadManager.js`](https://github.com/morgan3d/misc/tree/main/jsloadmanager) is (c) 2018 Morgan McGuire, [BSD-2-Clause](https://opensource.org/licenses/BSD-2-Clause) license. [`WorkJSON.js`](https://github.com/morgan3d/workjson) is (c) 2020 Morgan McGuire, [MIT](https://github.com/morgan3d/workjson/blob/main/LICENSE) license. [`js-yaml`](https://github.com/morgan3d/misc/tree/main/jsloadmanager) is (C) 2011-2015 by Vitaly Puzrin, [MIT](https://raw.githubusercontent.com/nodeca/js-yaml/master/LICENSE) license. [`dagre.js`](https://github.com/dagrejs/dagre) is (C) 2012-2014 Chris Pettitt, [MIT](https://github.com/dagrejs/dagre/blob/master/LICENSE) license. The boot screen font used in the emulator is [https://int10h.org/oldschool-pc-fonts](PxPlus_AmstradPC1512-2y) copyright 2016 [Int10h](https://int10h.org/), used under the [CC-BY 4.0](http://creativecommons.org/licenses/by-sa/4.0/) license. It is based on the Amstrad PC1512 BIOS font. All built-in assets are Creative Commons licensed have complete license and copyright information in the "license" property of the corresponding JSON file. Logos ------------------------------------------------------------------------------------- The following logos may be used for promoting or linking to : ![](logos/quadplay-logo-gradient-round.png) ![](logos/quadplay-logo-gradient.png) ![](logos/quadplay-logo-gray.png) Notes for Beta Testers ===================================================================================== The IDE will receive major new features throughout and after the beta phase. This includes editors for sprites and maps. The private view online multiplayer mode is very new and has several known limitations and bugs: - There is no way to send audio to only some of the players - Some of the system menus do not work in private screen mode (credits, controls menu, set controls menu, set controller order) - Only 384x224 and 128x128 screen resolution are supported (192x112 or 64x64 per player) Expect rasterization alignment rules for triangles to change slightly in order to better align for subpixel cases. The current map, disk, line, and rectangle rendering alignment is final. Sprite alignment is final but has a known 1/2 pixel bug that only manifests under rotation and scaling. I will add separate functions for watertight triangle and line rasterization, which give different results than what is typically useful for individual primitives. The `make_spline()` function using `"continue"` mode will change to be a third-order curve outside of the bounds instead of a line. The package manager is not implemented and may actually be removed instead of being implemented, depending on the needs that arise in the beta. You can load relative URLs for scripts and assets right now. So, the need for packages isn't urgent as you can reuse across projects already. What packages will provide are namespaces as well as assets bundled together with a script. manual✜