Here are some simple ways to make your PICO-8 code fit in 140 280 characters (as in the #tweetjam #tweetcart craze). I did not invent these, I merely observed and collected them from the tweetjam thread.
- Use single character variable names
- Use
x=.1
andx=.023
, notx=0.1
orx=0.023
x=1/3
is shorter thanx=.3333
- You don't need to separate everything with spaces or write them on their own lines, e.g.
circ(x,y,1)pset(z,q,7)
works just as well x=sin(t)*10+32 y=cos(t)*23+10
can be reordered and written asx=32+10*sin(t)y=10+23*cos(t)
to save a few characters- Don't use hexadecimals, 0xffff is one character longer than 65535
- An example encountered in the wild:
sin(y/x)-(cos(x/y)*2+5)
can be written assin(y/x)-cos(x/y)*2-5
a="01234567" v="0x"..sub(a,i,i)
might be shorter thana={0,1,2,3,4,5,6,7} v=a[i]
with long enough tables (useful for palettes)({1,2,3})[index]
is shorter thant={1,2,3}t[index]
- PICO-8 does weird preprocessor stuff not available on vanilla LUA: e.g.
x+=1
is shorter thanx=x+1
- but often you need to have a newline after that etc. ?0,x,y,color
(it's the same asprint(0,x,y,color)
) is perhaps the shortest way to draw more complex things on screen than pixels and circles (explore the character set)- Don't bother with the
_update()
/_draw()
structure, just use agoto
, e.g.::s:: print(rnd()) goto s
- You don't often even need
flip()
- the screen will automatically keep updating at some point (but you can't control it then, obv) - The default cart has one built in sprite you can use with
spr()
- Remember the mirrored screen modes for symmetrical stuff
- Starting from 0.1.11, you can use
t()
instead oftime()
x\1
is shorter thanflr(x)
(the\
operator divides by a number and rounds down)- "any arguments mid doesn't get are treated as 0, so
mid(0,x,127)
andmid(x,127)
do the same thing" - @WinslowJosiah
This is too long:
if(btn(0))x-=1
if(btn(1))x+=1
This is shorter:
b=btn()
if(b>0)x+=b*2-1
Either one of the following will extract the angle suitable for sin()
/cos()
from the cursor keys (amazing snippet stolen from @pancelor):
a=btn()*.6&.75
a=btn()*12\5/4
- If you use e.g. a number like 1000 in various places, try if you can define it as a variable and use that variable instead of the longer number
- 99 is almost as good as 100 but it's shorter.
x/99
is almost equal tox/100
- If you already have e.g.
x=10
and need e.g. to dividey
by 10000, usex
:y/=x^4
- Forget perfection. If you have an effect that uses 64 in one place and 60 in the other, try to use 60 in both. Or meet in the middle with 62. You might be able to save a few characters with the above variable substitution trick
- You should also try to substitute function names like
circfill
with shorter names e.g.c=circfill c(x,y,1) c(50,50,10)
if you use them a few times in the code (no point substituting e.g.cos()
if you only use it twice) - Forget about the usual
FOR X=0,127 DO FOR Y=0,127 DO ... END END
and usex=rnd(128) y=rnd(128)
, it will eventually cover all screen pixels when you do it again and again (this is called the Monte Carlo algorithm), this gives a distinct not-quite-random look for effects and is good for things that would never work fast enough if you tried to do it for every pixel every frame - You can reset the random seed each frame with
srand(0)
to get the same series of random numbers for e.g. a star field effect, you might want to experiment with the seed number to get the best selection of random numbers (maybe there is a nice melody somewhere in the randomness?)
Here is a simple fire routine:
t={0,1,9,2,7,2,10,4,8,9}
function _update()
for i=1,5000 do
x=rnd(128)
y=rnd(128)
c=pget(x,y+rnd(2))
if rnd(50)<c then c=t[c] end
circ(x,y,1,c)
pset(y,127,7)
end
end
-- 178 chars
The routine executes every frame, picks 5000 pixels randomly and draws a circle (with radius 1) at x,y
based on the pixel color at x,y+1
. There is a color map t
that is used to nicely fade out the colors (the table contains the next color for a color - i.e. 7 = white, t[7]=10 => yellow, t[10]=9 => orange, t[9]=8 => red etc.). pset() is used to draw a white line at the very bottom of the screen, which seeds the fire. It is is based on the random "Monte Carlo" way of doing things so we don't need a for-loop to iterate the whole screen.
First things first: ditch the _update() callback and simply make the routine draw as fast as possible with an infinite loop. The original routine does this 5000 times a frame but we can drop the for-loop, we just want to push as many pixels as fast as we can.
t={0,1,9,2,7,2,10,4,8,9}
::s::
x=rnd(128)
y=rnd(128)
c=pget(x,y+rnd(2))
if rnd(50)<c then c=t[c] end
circ(x,y,1,c)
pset(y,127,7)
goto s
-- 141 chars
Let's substitute the repeated 128's AND rnd()'s.
r=rnd
z=128
t={0,1,9,2,7,2,10,4,8,9}
::s::
x=r(z)
y=r(z)
c=pget(x,y+r(2))
if r(50)<c then c=t[c] end
circ(x,y,1,c)
pset(y,127,7)
goto s
-- 141 chars
We didn't gain any characters at the previous iteration! Don't worry. This is because of the unneeded whitespace the definitions for r=rnd()
and z=128
have, the newlines. We will take care of that whitespace later so this is still a useful step.
We have a 127 in the pset() call, why not simply use 127 in place of the 128's earlier in the code? Also, we can use the shorter PICO-8 if-then structure.
r=rnd
z=127
t={0,1,9,2,7,2,10,4,8,9}
::s::
x=r(z)
y=r(z)
c=pget(x,y+r(2))
if(r(50)<c)c=t[c]
circ(x,y,1,c)
pset(y,z,7)
goto s
-- 130 chars
Let's get rid of the whitespace (you can't put that shortened if-clause right after the pget(), it will be a syntax error, the preprocessor works like that).
r=rnd z=127 t={0,1,9,2,7,2,10,4,8,9}::s::x=r(z)y=r(z)c=pget(x,y+r(2))
if(r(50)<c)c=t[c]
circ(x,y,1,c)pset(y,z,7)goto s
-- 118 chars
We can reorder some of the variables so we don't need those whitespaces We will also move the definition of z inside the main loop - it's a tiny bit slower, of course, and z will be nil the first time we go through the loop but it saves a whitespace and it still works.
t={0,1,9,2,7,2,10,4,8,9}r=rnd::s::x=r(z)y=r(z)c=pget(x,y+r(2))z=127
if(r(50)<c)c=t[c]
circ(x,y,1,c)pset(y,z,7)goto s
-- 116 chars
And that's the shortest I can make it. It is short enough to tweet with a gif of it in action. :)
Actually, I noticed that
z=12x=45
usually works; specifically, it'll parse fine unless 'x' (letter following numeric) is one ofabcdef
, as that'll give "malformed number" something or other error (confuses it for hexadecimal i think). I actually tested this in PICO-8 and lua's online demo.Also, the use for this tip is probably less likely than the string-sub-table one, but: if you're calling a bunch of different functions with similar args, you could do something like