Dark Mode

Picotron User Manual

Picotron v0.1.0g
https://www.picotron.net
(c) Copyright 2024 Lexaloffle Games LLP
Author: Joseph White // [email protected]

Picotron is built with:
 
  SDL2 http://www.libsdl.org
  Lua 5.4 http://www.lua.org  // see license.txt
  lz4 by Yann Collet https://www.lz4.org // see license.txt
  z8lua by Sam Hocevar https://github.com/samhocevar/z8lua

Latest version of this manual (as html, txt):

https://www.lexaloffle.com/picotron.php?page=resources

1. Getting Started
  1.1 Drive Storage
  1.2 Shortcuts
  1.3 Creating a Cartridge
  1.4 Adding Graphics
  1.5 Saving a Cartridge
  1.6 Terminal Commands
  1.7 Using External Editors
  1.8 Creating Windowed Programs
  1.9 Uploading a Cartridge to the BBS
2. Customising your Machine
  2.1 Desktop Customisation
  2.2 Custom Commands
  2.3 Keyboard Layout
  2.4 Defaults Apps
3. Anywhen
4. Editors
  4.1 Code Editor
  4.2 GFX Editor
  4.3 Map Editor
  4.4 SFX Editor
5. API Reference
  5.1 File System
  5.2 System
  5.3 Graphics
  5.4 Table Functions
  5.5 Input
  5.6 Map
  5.7 Audio
  5.8 Userdata

▨ Welcome to Picotron

Picotron is a Fantasy Workstation for making pixelart games, animations, music, demos and other curiosities. It can also be used to create tools that run inside Picotron itself, and to customise things like live wallpapers and screensavers. It has a toy operating system designed to be a cosy creative space, but runs on top of Windows, MacOS or Linux. Picotron apps can be made with built-in tools, and shared with other users in a special 256k png cartridge format.

▨ Specifications

Display:  480x270 / 240x135 64 definable colours
Graphics: Blend tables, tline3d, stencil clipping
Audio:    64-node synth and 8-channel tracker
Code:     Lua 5.4 w/ PICO-8 compat features
CPU:      8M vm insts / second
Cart:     .p64.png (256k) / .p64 (unlimited)

Getting Started

1.1 Drive Storage

The first time Picotron is run, it will automatically create a configuration file that specifies where to store files if one does not already exist:

Windows: C:/Users/Yourname/AppData/Roaming/Picotron/picotron_config.txt
OSX:     /Users/Yourname/Library/Application Support/Picotron/picotron_config.txt
Linux:   ~/.lexaloffle/Picotron/picotron_config.txt

The default configuration:

mount / C:/Users/Yourname/AppData/Roaming/Picotron/drive

From inside Picotron, you can open any non-ram folder in the file navgiator with "Open Host OS Folder", or by typing "folder" from terminal. Picotron's filenav gui is a work in progress, so you might want to do any complex file management from the host OS for now!

1.2 Shortcuts

ALT+ENTER:       Toggle Fullscreen
ALT+F4:          Fast Quit (Windows)
CTRL-Q:          Fast Quit (Mac, Linux) -- need to enable in settings
CTRL-R:          Reload / Run / Restart cartridge
CTRL-S:          Quick-Save working cartridge
ALT+LEFT/RIGHT   Change Workspace (use option on Mac)
ESCAPE:          Toggle between desktop / terminal
ENTER:           Pause menu (fullscreen apps)

1.3 Creating a Cartridge

Picotron is a cartridge-orientated workstation. A cartridge is like an application bundle, or a project folder: it is a collection of Lua source code files, graphics, audio and any other data files the cartridge needs to run. On the host operating system cartridges are a single .p64 file, but inside picotron they are treated like folders. The current working cartridge is always in a RAM folder named /ram/cart.

Click on the code editor workspace at top right, which looks like this: ()

Paste in a program (select here, ctrl+c, and then ctrl+v inside Picotron)

function _init()
  bunny = unpod(
  "b64:bHo0AEIAAABZAAAA-wpweHUAQyAQEAQgF1AXQDcwNzAHHxcHM"..
  "AceBAAHc7cwFwFnAQcGAPAJZx8OJ0CXcF8dkFeQFy4HkBcuB5AXEBdA"
  )
  x = 232
  y = 127
  hflip = false
end
 
function _draw()
  cls(3)   -- clear the screen to colour 3 (green)
  srand(0) -- reset the random number generator sequence
  for i=1,50 do
    print("\\|/",rnd(480),rnd(270),19) -- grass
    print("*",rnd(480),rnd(270),rnd{8,10,14,23}) -- flower
  end
  ovalfill(x+2,y+15,x+12,y+18, 19) -- shadow
  spr(bunny, x, y - (flr(hop)&2), hflip) -- bunny
end
 
function _update()
  if (btn(0)) x -= 2 hflip = true
  if (btn(1)) x += 2 hflip = false
  if (btn(2)) y -= 2
  if (btn(3)) y += 2
  if (btn()>0) then
    hop += .2 -- any button pressed -> hop
  else
    hop = 0
  end
end

Now, press CTRL+R to run it. CTRL+R runs whatever is in /ram/cart, and the entry point in the cartridge is always main.lua (the file you were editing).

After hopping around with the cursor keys, you can halt the program by pressing ESCAPE, and then ESCAPE once more to get back to the code editor.

There is a lot going on here! The _init() function is always called once when the program starts, and here it creates an image stored as text (a "pod"), and then sets the bunny's initial x,y position.

_draw() is called whenever a frame is drawn (at 60fps or 30fps if there isn't enough available cpu). _update() is always called at 60fps, so is a good place to put code that updates the world at a consistent speed.

1.4 Adding Graphics

Normally graphics are stored in .gfx files included in the cartridge at gfx/*.gfx

Click on the second workspace to draw a sprite. By default, the sprite editor has /ram/cart/gfx/0.gfx open which is a spritebank that is automatically loaded when a cartridge is run.

Now, instead of using the "bunny" image, the index of the sprite can be used:

spr(0, x, y, hflip) -- hflip controls horizontal flipping

The map works the same way: map/0.map is automatically loaded, and can be drawn with:

map()

To add a camera that follow the player around, try this at the start of _draw():

camera(x - 240, y - 135)

1.5 Saving a Cartridge

To save a cartridge to disk, open a terminal from the picotron menu (top left), and type:

save mycart.p64

(or just "save mycart" ~ the .p64 extension will be added automatically)

The save command here simply copies the contents of /ram/cart to mycart.p64.

Note that you can not save the cart while you are inside /ram/cart (e.g. after you press escape to halt a program). That would mean copying a folder somewhere inside itself! Also, saving to anything inside /ram or /system will only save to memory which disappears the next time Picotron is restarted.

Once a cartridge has been saved, the filename is set as the "present working cartridge", and subsequent saves can be issued with the shortcut: CTRL-S. To get information about the current cartridge, type "info" at the terminal prompt.

When editing code and graphics files inside a cartridge, those individual files are auto-saved to /ram/cart so that ctrl+R will run the current version (there's no need to save before each run).

When using an editor to edit a file that is outside /ram/cart, CTRL+S saves that individual file.

Picotron is still in alpha -- please keep plenty of backups and save often! If an editor crashes but the window manager is still running, there is a good chance you can still save to disk with CTRL+S.

1.6 Terminal Commands

Some handy commands:

ls           list the current directory (folder)
cd foo       change directory (e.g. cd /desktop)
mkdir foo    create a folder
folder       open the current folder in your Host OS
open .       open the current folder in filenav
open fn      open a file with an associated editor
rm filename  remove a file or folder (be careful!)
info         information about the current cartridge
load cart    load a cartridge into /ram/cart
save cart    save a cartridge

To create your own commands, put .p64 or .lua files in /appdata/system/util.

1.7 Using External Editors

The simplest way to use a text editor is to store the files outside of a cartidge and then include() them:

cd("/myproj/src")
include("src/draw.lua")
include("src/monster.lua")

Just remember to copy them back inside the cartridge (and comment out the "cd(...)") before releasing it!

cp /myproj/src /ram/cart/src

As a general rule, cartridges should be self-contained and not depend on anything except for /system.

1.8 Creating Windowed Programs

Each process can have a single window. To set the window:

window(320,200)

Additional attributes can be passed in using a table:

window{
  width  = 320,
  height = 200,
  resizeable = false,
  title = "Bunny Example"
}

To disable the pause menu coming up in fullscreen apps when Enter is pressed:

window{
  pauseable = false
}

1.9 Uploading a Cartridge to the BBS

Cartridges can be shared on the lexaloffle BBS:

https://www.lexaloffle.com/bbs/?cat=8

First, capture a label while your cart is running with ctrl+7. For windowed programs, the label will include a screenshot of your desktop, so make sure you don't have anything personal lying around!

You can give the cartridge some metadata (title, version, author, notes) using about:

> about /ram/cart

Then save a copy of your cartridge in .p64.png by copying it:

> cp mycart.p64 releaseable.p64.png

The label will be printed on the front along with the title, author and version metadata if it exists. You can check the output by opening the folder you saved to, and then double clicking on releaseable.p64.png (it is just a regular png)

> folder .

Finally, choose the 'Submit' on the BBS webpage to upload the cartridge. Cartridges are not publically visible until a BBS post has been made including the cartridge (unless someone can guess the cartridge id that you give it). Cartridges can be loaded directly from the BBS using:

> load #bbs_cart_id

Or a specific version of the cart with the revision suffix:

> load #bbs_cart_id-0

Customising your Machine

2.1 Desktop Customisation

Open the system settings via the Picotron menu (top left) or by typing "settings" at the prompt.

To create your own lists of themes, wallpapers and screensavers, create the following folders:

/appdata/system/themes
/appdata/system/wallpapers
/appdata/system/screensavers

Wallpapers and screensavers are regular .p64 cartridges -- you can copy anything in there that runs in fullscreen.

Widgets are programs that run in the slide-out tooltray (pull the toolbar down from the top), and are windowed programs that are not moveable and do not have a frame. See /system/startup.lua for examples of how to launch widgets. Additional widgets and other programs can be launched at startup by creating /appdata/system/startup.lua (which is run after /system/startup.lua).

2.2 Custom Commands

To create your own commands, put .p64 or .lua files in /appdata/system/util.

When a command is used from commandline (e.g. "ls"), terminal first looks for it in /system/util and /system/apps, before looking in /appdata/system/util and finally the current directory for a matching .lua or .p64 file.

2.3 Keyboard Layout

Text input (using peektext() / readtext()) defaults to the host OS keyboard layout / text entry method.

Key states used for things like ctrl+key shortcuts (e.g. key("ctrl") and keyp("c")) are also mapped to the host OS keyboard layout by default, but can be further configured by creating a file called /appdata/system/keycodes.pod which assigns each keyname to a new scancode. The raw names of keys (same as US layout) can alternatively be used on the RHS of each assignment, as shown in this example that patches a US layout with AZERTY mappings:

store("/appdata/system/keycodes.pod", {a="q",z="w",q="a",w="z",m=";",[";"]=",",[","]="m"})

note for 0.1.0d: you probably don't need need to do this! The default layout should work in most cases.

Raw scancodes themselves can also be remapped in a similar way using /appdata/system/scancodes.pod, but is normally not needed. The raw mapping is used in situations where the physical location of the key mappers, such as the piano-like keyboard layout in the tracker. See /system/lib/events.lua for more details.

Data persistence is not supported under web yet.

2.4 Defaults Apps

When opening a file via filenav or the open command, an application to open it with is selected based on the extension. To change or add the default application for an extension, use the default_app command. The following will associate files ending with ".sparkle" with the program "/apps/tools/sparklepaint.p64":

default_app sparkle /apps/tools/sparklepaint.p64

The table of associations is stored in: /appdata/system/default_apps.pod

Anywhen

Anywhen is a tool for reverting to earlier versions of cartridges that are saved to disk from inside Picotron. Every time a file is changed, Picotron records a delta between the last known version and the current one, and is able to fetch any earlier version of a cartridge as long as anywhen was activated at that point in time. It can be turned off via settings.p64, but it is recommended during early alpha (0.1.*) to leave it running as it might be helpful in recovering lost data caused by bugs or lack of safety features (e.g. there is currently no confirmation required when saving over files).

To load an earlier version of a cartridge even after it has been deleted or moved, use a timestamp (written as local time) at the end of the load command:

load foo.p64@2024-04-10_12:00:00

An underscore is used between the date and time parts as spaces are not allowed in location strings.

When an earlier version from the same (local) day is needed, the date and optionally seconds parts can be omitted:

load foo.p64@12:00

Anywhen only stores changes made to files from within Picotron; it does not proactively look for changes made in external editors except when generating the first log file per day. Also, it only stores changes made to files saved to disk, and not to /ram.

The anywhen data is stored in the same folder as the default picotron_config.txt location (type "folder /" and then go up one folder in the host OS). The history is orgnised by month, and it is safe to delete earlier month folders if they are no longer needed (but they normally shouldn't take up too much space).

Anywhen and Picotron itself are both still in early alpha; please also back up your work using traditional methods!

Editors

4.1 Code Editor

  Hold shift to select (or click and drag with mouse)
  CTRL-X, C, V to cut copy or paste selected
  CTRL-Z, Y to undo, redo
  CTRL-F to search for text in the current tab
  CTRL-LEFT, RIGHT to jump by word
  TAB to indent a selection (shift to un-indent)
  Double click to select a word

4.2 GFX Editor

The second workspace is a sprite editor. Each .gfx file contains up to 256 sprites, and if the filename starts with a number, it can be indexed by the map editor.

Don't forget to save your cartridge after drawing something -- the default filenames all point to /ram/cart and isn't actually stored to disk until you use the save command (or CTRL-S to save the current cartridge)

S          shortcut for the select tool (hold down)
SPACE      shortcut for the pan tool
CTRL-A     select all
ENTER      deselect
CURSORS    move selection
BACKSPACE  delete selection
CTRL-C     copy selection
CTRL-V     paste to current sprite
CTRL-B     paste big (2x2)
TAB        toggle RH pane
-,+        navigate sprites
CTRL       modify some draw tools (fill)
RMB        pick up colour
F/V        flip selection horizontally or vertically

Draw sprites from your program with spr(index, x, y). 0.gfx is loaded automatically.

4.3 Map Editor

Changes made to /ram/cart/gfx/0.gfx automatically show up in the map editor.

The map editor uses similar shortcuts, with a few changes in meaning. For example, F and V also flip selected tiles, but also set special bits on those tiles to indicate that the tile itself should also be drawn flipped. The map() command also observes those bits.

4.4 SFX Editor

[coming soon]

WIP specs: https://www.lexaloffle.com/dl/docs/picotron_synth.html

API Reference

5.1 File System


store(filename, obj, [metadata])

store a lua object (tables, strings, userdata, booleans and numbers are allowed) as a file.

When metadata is given, each field is added to the file's metadata without clobbering any existing fields.

store("foo.pod", {x=3,y=5})
a = fetch("foo.pod")
?a.x -- 3

When a cartridge needs to persist data (settings, high scores etc), it can use store() to write to /appdata:

store("/appdata/mygame_highscores.pod", highscore_tbl)

If the cartridge needs to store more than one ot two files, a folder can be created:

mkdir("/appdata/mygamename")
store("/appdata/mygamename/highscores.pod", highscore_tbl)

Either method is fine -- in future versions, cartridges running directly from BBS will be sandboxed, in which case these folders will automatically be mapped to something like /appdata/bbs/cart_id/ (and not be able to clobber data written by other bbs carts).


fetch(filename)

Return a lua object stored in a given file. Returns the object and metadata as a table.


fetch_metadata(filename)
store_metadata(filename, metadata)

Fetch and store just the metadata fork of a file. This can be faster in some cases.


mkdir(name)

Create a directory


ls([path])

list files and folders in given path relative to the current directory.


cp(src, dest)

Copy a file from src to dest. Folders are copied recursively, and dest is overwritten.


mv(src, dest)

Move a file from src to dest. Folders are moved recursively, and dest is overwritten.


rm(file)

Delete a file or folder (recursive).

Mount points are also deleted, but the contents of their origin folder are not deleted unless explicitly given as a parameter to rm.


fullpath(file)

Resolve a filename to its canonical path based on the present working directory (pwd()).

5.2 System


env()

Returns a table of environment variables given to the process at the time of creation.

?pod(env()) -- view contents of env()


stop([message])

stop the cart and optionally print a message


assert(condition, [message])

if condition is false, stop the program and print message if it is given. this can be useful for debugging cartridges, by assert()'ing that things that you expect to be true are indeed true.

assert(actor)      --  actor should exist and be a table
actor.x += 1       --  definitely won't get a "referencing nil" error


printh(str)

print a string to the host operating system's console for debugging.


time()
t()

Returns the number of seconds elapsed since the cartridge was run.

This is not the real-world time, but is calculated by counting the number of times _update60 is called. multiple calls of time() from the same frame return the same result.


date(format, t, delta)

Returns the current day and time formatted using Lua's standard date strings.

format: specifies the output string format, and defaults to "!%Y-%m-%d %H:%M:%S" (UTC) when not given. Picotron timestamps stored in file metadata are stored in this format.

t: specifies the moment in time to be encoded as a string, and can be either an integer (epoch timestamp) or a string indicating UTC in the format: "!%Y-%m-%d %H:%M:%S". When t is not given, the current time is used.

delta: number of seconds to add to t.

-- show the current UTC time (use this for timestamps)
?date()
 
-- show the current time (UTC)
?date("%Y-%m-%d %H:%M:%S")
 
-- convert a UTC date to local time
?date("%Y-%m-%d %H:%M:%S", "2024-03-14 03:14:00")
 
-- local time 1 hour ago
?date("%Y-%m-%d %H:%M:%S", nil, -60*60)

5.3 Graphics

// https://www.lexaloffle.com/dl/docs/picotron_gfx_pipeline.html


clip(x, y, w, h, [clip_previous])

sets the clipping rectangle in pixels. all drawing operations will be clipped to the rectangle at x, y with a width and height of w,h.

clip() to reset.

when clip_previous is true, clip the new clipping region by the old one.


pset(x, y, [col])

sets the pixel at x, y to colour index col (0..15).

when col is not specified, the current draw colour is used.

for y=0,127 do
  for x=0,127 do
    pset(x, y, x*y/8)
  end
end


pget(x, y)

returns the colour of a pixel on the screen at (x, y).

while (true) do
  x, y = rnd(128), rnd(128)
  dx, dy = rnd(4)-2, rnd(4)-2
  pset(x, y, pget(dx+x, dy+y))
end

when x and y are out of bounds, pget returns 0. a custom return value can be specified with:

poke(0x5f36, 0x10)
poke(0x5f5b, newval)


sget(x, y)
sset(x, y, [col])

get or set the colour (col) of a sprite sheet pixel.

when x and y are out of bounds, sget returns 0. a custom value can be specified with:

poke(0x5f36, 0x10)
poke(0x5f59, newval)


fget(n, [f])
fset(n, [f], val)

get or set the value (val) of sprite n's flag f.

f is the flag index 0..7.

val is true or false.

the initial state of flags 0..7 are settable in the sprite editor, so can be used to create custom sprite attributes. it is also possible to draw only a subset of map tiles by providing a mask in map().

when f is omitted, all flags are retrieved/set as a single bitfield.

fset(2, 1 | 2 | 8)   -- sets bits 0,1 and 3
fset(2, 4, true)     -- sets bit 4
print(fget(2))       -- 27 (1 | 2 | 8 | 16)


print(str, x, y, [col])
print(str, [col])

print a string str and optionally set the draw colour to col.

shortcut: written on a single line, ? can be used to call print without brackets:

?"hi"

when x, y are not specified, a newline is automatically appended. this can be omitted by ending the string with an explicit termination control character:

?"the quick brown fox\0"

additionally, when x, y are not specified, printing text below 122 causes the console to scroll. this can be disabled during runtime with poke(0x5f36,0x40).

print returns the right-most x position that occurred while printing. this can be used to find out the width of some text by printing it off-screen:

w = print("hoge", 0, -20) -- returns 16


cursor(x, y, [col])

set the cursor position.

if col is specified, also set the current colour.


color([col])

set the current colour to be used by drawing functions.

if col is not specified, the current colour is set to 6


cls([col])

clear the screen and reset the clipping rectangle.

col defaults to 0 (black)


camera([x, y])

set a screen offset of -x, -y for all drawing operations

camera() to reset


circ(x, y, r, [col])
circfill(x, y, r, [col])

draw a circle or filled circle at x,y with radius r

if r is negative, the circle is not drawn.


oval(x0, y0, x1, y1, [col])
ovalfill(x0, y0, x1, y1, [col])

draw an oval that is symmetrical in x and y (an ellipse), with the given bounding rectangle.


line(x0, y0, [x1, y1, [col]])

draw a line from (x0, y0) to (x1, y1)

if (x1, y1) are not given, the end of the last drawn line is used.

line() with no parameters means that the next call to line(x1, y1) will only set the end points without drawing.

function _draw()
  cls()
  line()
  for i=0,6 do
    line(64+cos(t()+i/6)*20, 64+sin(t()+i/6)*20, 8+i)
  end
end


rect(x0, y0, x1, y1, [col])
rectfill(x0, y0, x1, y1, [col])

draw a rectangle or filled rectangle with corners at (x0, y0), (x1, y1).


pal(c0, c1, [p])

pal() swaps colour c0 for c1 for one of three palette re-mappings (p defaults to 0):

0: draw palette

The draw palette re-maps colours when they are drawn. For example, an orange flower sprite can be drawn as a red flower by setting the 9th palette value to 8:

pal(9,8)     -- draw subsequent orange (colour 9) pixels as red (colour 8)
spr(1,70,60) -- any orange pixels in the sprite will be drawn with red instead

Changing the draw palette does not affect anything that was already drawn to the screen.

1: display palette

The display palette re-maps the whole screen when it is displayed at the end of a frame.


palt(c, is_transparent)

Set transparency for colour index c to is_transparent (boolean) transparency is observed by spr(), sspr(), map() and tline3d()

palt(8, true) -- red pixels not drawn in subsequent sprite/tline draw calls

When c is the only parameter, it is treated as a bitfield used to set all 64 values. for example: to set colours 0 and 1 as transparent:

-- set colours 0,1 and 4 as transparent
palt(0x13)

palt() resets to default: all colours opaque except colour 0. Same as palt(1)


spr(s, x, y, [flip_x], [flip_y])

Draw sprite s at position x,y

s can be either a userdata (type "u8") or sprite index (0..255 for bank 0, 256..511 for bank 1 etc).

Colour 0 drawn as transparent by default (see palt())

When flip_x is true, flip horizontally. When flip_y is true, flip vertically.


sspr(s, sx, sy, sw, sh, dx, dy, [dw, dh], [flip_x], [flip_y]]

Stretch a source rectangle of sprite s (sx, sy, sw, sh) to a destination rectangle on the screen (dx, dy, dw, dh). In both cases, the x and y values are coordinates (in pixels) of the rectangle's top left corner, with a width of w, h.

s can be either a sprite index or the sprite userdata.

Colour 0 drawn as transparent by default (see palt())

dw, dh defaults to sw, sh.

When flip_x is true, flip horizontally. When flip_y is true, flip vertically.


fillp(p)

Set a 4x4 fill pattern using PICO-8 style fill patterns. p is a bitfield in reading order starting from the highest bit.

Observed by circ() circfill() rect() rectfill() oval() ovalfill() pset() line()

Fill patterns in Picotron are 64-bit specified 8 bytes from 0x5500, where each byte is a row (top to bottom) and the low bit is on the left. To define an 8x8 with high bits on the right (so that binary numbers visually match), fillp can be called with 8 parameters:

fillp(
  0b10000000,
  0b01011110,
  0b00101110,
  0b00010110,
  0b00001010,
  0b00000100,
  0b00000010,
  0b00000001
)
circfill(240,135,50,9)

Two different colours can be specified in the last parameter

circfill(320,135,50,0x1208) -- draw with colour 18 and 8

To get transparency while drawing shapes, the shape target mask should be set:

poke(0x550b,0x3f)
palt()
--> black pixels won't be drawn

5.4 Table Functions

With the exception of pairs(), the following functions and the # operator apply only to tables that are indexed starting from 1 and do not have NIL entries. All other forms of tables can be considered as hash maps or sets, rather than arrays that have a length.


add(tbl, val, [index])

Add value val to the end of table tbl. Equivalent to:

tbl[#tbl + 1] = val

If index is given then the element is inserted at that position:

foo={}        -- create empty table
add(foo, 11)
add(foo, 22)
print(foo[2]) -- 22


del(tbl, val)

Delete the first instance of value VAL in table TBL. The remaining entries are shifted left one index to avoid holes.

Note that val is the value of the item to be deleted, not the index into the table. (To remove an item at a particular index, use deli instead). del() returns the deleted item, or returns no value when nothing was deleted.

a={1,10,2,11,3,12}
for item in all(a) do
  if (item < 10) then del(a, item) end
end
foreach(a, print) -- 10,11,12
print(a[3])       -- 12


deli(tbl, [index])

Like del(), but remove the item from table tbl at index. When index is not given, the last element of the table is removed and returned.


count(tbl, [val])

Returns the length of table t (same as #tbl) When val is given, returns the number of instances of VAL in that table.


all(tbl)

Used in for loops to iterate over all items in a table (that have a 1-based integer index), in the order they were added.

t = {11,12,13}
add(t,14)
add(t,"hi")
for v in all(t) do print(v) end -- 11 12 13 14 hi
print(#t) -- 5


foreach(tbl, func)

For each item in table tbl, call function func with the item as a single parameter.

> foreach({1,2,3}, print)


pairs(tbl)

Used in for loops to iterate over table tbl, providing both the key and value for each item. Unlike all(), pairs() iterates over every item regardless of indexing scheme. Order is not guaranteed.

t = {["hello"]=3, [10]="blah"}
t.blue = 5;
for k,v in pairs(t) do
  print("k: "..k.."  v:"..v)
end

Output:

k: 10  v:blah
k: hello  v:3
k: blue  v:5

5.5 Input


btn([b], [pl])

Returns the state of button b for player index pl (default 0 -- means Player 1)

0 1 2 3     LEFT RIGHT UP DOWN
5 6         Buttons: O X
7           MENU
8           reserved
9 10 11 12  Secondary Stick L,R,U,D
12 13       Buttons (not named yet!)
14 15       SL SR

A secondary stick is not guaranteed on all platforms! It is preferable to offer an alternative control scheme that does not require it, if possible.

The return value is false when the button is not pressed (or the stick is in the deadzone), and a number between 1..255 otherwise. To get the X axis of the primary stick:

local dx = (btn(1) or 0) - (btn(0) or 0)

Stick values are processed by btn so that the return values are only physically possible positions of a circular stick: the magnitude is clamped to 1.0 (right + down) even with digital buttons gives values of 181 for btn(1) and btn(3), and it is impossible for e.g. LEFT and RIGHT to be held at the same time. To get raw controller values, use peek(0x5400 + player_index*16 + button_index).

Keyboard controls are currently hard-coded:

0~5     Cursors, Z/X
6       Enter  -- disable with window{pauseable=false}
8~11    ADWS
12,13   F,G
14,15   Q,E


btnp(b, [pl])

btnp is short for "Button Pressed"; Instead of being true when a button is held down, btnp returns true when a button is down and it was not down the last frame. It also repeats after 30 frames, returning true every 8 frames after that. This can be used for things like menu navigation or grid-wise player movement.

The state that btnp() reads is reset at the start of each call to _update60, so it is preferable to use btnp only from inside that call and not from _draw(), which might be called less frequently.

Custom delays (in frames 60fps) can be set by poking the following memory addresses:

poke(0x5f5c, delay) -- set the initial delay before repeating. 255 means never repeat.
poke(0x5f5d, delay) -- set the repeating delay.

In both cases, 0 can be used for the default behaviour (delays 30 and 8)


key(k, [raw])
keyp(k, [raw])

returns the state of key k

function _draw()
  cls(1)
  -- draw when either shift key is held down
  if (key("shift")) circfill(100,100,40,12)
end

The name of each k is the same as the character it produces on a US keyboard with some exceptions: "space", "delete", "enter", "tab", "ctrl", "shift", "alt", "pageup", "pagedown".

By default, key() uses the local keyboard layout; On an AZERTY keyboard, key("a") is true when the key to the right of Tab is pressed. To get the raw layout, use true as the second parameter to indicate that k should be the name of the raw scancode. For example, key("a", true) will be true when the key to the right of capslock is held, regardless of local keyboard layout.

if (key"ctrl" and keyp"a") printh("CTRL-A Pressed")

keyp() has the same behaviour key(), but true when the key is pressed or repeating.


peektext()
readtext([clear])

To read text from the keyboard via the host operating system's text entry system, peektext() can be used to find out if there is some text waiting, and readtext() can be used to consume the next piece of text:

while (peektext()) c = readtext() printh("read text: "..c) end


mouse()

Returns mouse_x, mouse_y, mouse_b, wheel_x, wheel_y

mouse_b is a bitfield: 0x1 means left mouse button, 0x2 right mouse button


mouselock(lock, event_sensitivity, move_sensitivity)

when lock is true, Picotron makes a request to the host operating system's window manager to capture the mouse, allowing it to control sensitivity and movement speed.

returns dx,dy: the relative position since the last frame

event_sensitivity in a number between 0..4 that determines how fast dx, dy change (1.0 means once per picotron pixel)

move_sensitivity in a number between 0..4: 1.0 means the cursor continues to move at the same speed.

local size, col = 20, 16
function _draw()
  cls()
  circfill(240, 135, size*4, col)
  local _,_,mb = mouse()
  dx,dy = mouselock(mb > 0, 0.05, 0) -- dx,dy change slowly, stop mouse moving
  size += dx  --  left,right to control size
  col  += dy  --  up,down to control colour
end

mouselock is not implemented under web yet

5.6 Map


mget(x, y)
mset(x, y, val)

Get or set map value (val) at x,y for the current map (loaded from the first layer of map/0.map by default).

To set the current map that mget, mset operate on:

memmap(0x100000, my_map)


map(tile_x, tile_y, [sx, sy], [tile_w, tile_h], [layers])

draw section of map (starting from tile_x, tile_y) at screen position sx, sy (pixels).

to draw a 4x2 blocks of tiles starting from 0,0 in the map, to the screen at 20,20:

map(0, 0, 20, 20, 4, 2)

tile_w and tile_h default to the entire map.

map() is often used in conjunction with camera(). to draw the map so that a player object (at pl.x in pl.y in pixels) is centered in fullscreen (480x270):

camera(pl.x - 240, pl.y - 135)
map()

layer is a bitfield. When given, only sprites with matching sprite flags are drawn. For example, when layers is 0x5, only sprites with flag 0 and 2 are drawn.


tline3d(src, x0, y0, x1, y1, u0, v0, u1, v1, w0, w1, [flags])

Draw a textured line from (x0,y0) to (x1,y1), sampling colour values from userdata src. When src is type u8, it is considered to be a single texture image, and the coordinates are in pixels. When src is type i16 it is considered to be a map, and coordinates are in tiles.

The width and height of src must be powers of 2.

u0, v0, u1, v1 are coordinates to sample from, given in pixels for sprites, or tiles for maps. Colour values are sampled from the sprite present at each map tile.

w0, w1 are used to control perspective and mean 1/z0 and 1/z1. Default values are 1,1 (gives a linear interpolation between uv0 and uv1).

experimental flags useful for polygon rendering / rotated sprites: 0x100 to skip drawing the last pixel, 0x200 to perform sub-pixel texture coordinate adjustment.

5.7 Audio


sfx(n, [channel], [offset], [length])

Play sfx (track) n (0..63) on channel (0..15) from note offset (0..63 in notes) for length notes.

Giving -1 as the channel automatically chooses a channel that is not being used.

Giving -1 as the track index stops playing sound on that channel.


music(n, [fade_len], [channel_mask])

Play music starting from pattern n.
n -1 to stop music
 
fade_len is in ms (default: 0). so to fade pattern 0 in over 1 second:

music(0, 1000)

channel_mask specifies which channels to reserve for music only. for example, to play only on channels 0..2:

music(0, nil, 7) -- 1 | 2 | 4

Reserved channels can still be used to play sound effects on, but only when that channel index is explicitly requested by sfx().

5.8 Userdata

Userdata in Picotron can be thought of as a fixed-size, 1d or 2d array of typed data. Sprites and maps are represented using u8 (unsigned 8-bit) and i16 (signed 16-bit) values respectively, and so can be manipulated with the following functions.


userdata(data_type, width, height, [data])

Create a userdata with a data type: "u8", "i16", "i32", "i64", or "f64".

data is a string of hexvalues encoding the initial values.

A 2d 8-bit userdata can also be created by passing a PICO-8 [gfx] snippet as a string (copy and paste from PICO-8):

s = userdata("[gfx]08080400004000444440044ffff094f1ff1944fff9f4044769700047770000a00a00[/gfx]")
spr(s, 200, 100)


ud:width()
ud:height()

returns the width, height of a userdata


ud:attribs()

returns the width, height, type and dimensionality of a userdata.


ud:get(x, n)
ud:get(x, y, n)

Return n values starting at x (or x, y for 2d userdata), or 0 if out of range.


ud:set(x, val)
ud:set(x, y, val)

Set a value at x (or x, y for 2d userdata). When out of range, has no effect.