Guide on minifying tweetcarts

So I’ve been randomly making tweetcarts for the past 2 years, and I’ve been always obsessed with how much cool stuff people can fit into a single tweet! I see a lot of guys out there who try to start with tweetcarts but struggle with fitting their doodles into such a tiny size margin.

So here is a guide on how to do it, and a few tricks that I use myself.

But what are tweetcarts?

In case you didn’t see this magic yet: tweet carts are PICO-8 doodles, that fit into a single tweet. That means that each of those masterpieces is not longer than 280 chars (bytes).

Tweetjam first started in 2016 on the PICO-8 forums, but quickly moved over to tweetjam hashtag on twitter.

If you’ve stuck with twitter for long enough, you remember, that tweet limitation on twitter was not always 280 chars. It all started out with just 140! So if you check out the early tweetcarts, they all are just a half of the modern ones, compared by the size!

How-to

Ok, so enough talking, let’s get minifying! In this part, I will quickly go over all the technics, but don’t worry, we will also take a look at a real example and optimize a small doodle!

Optimize your loop

So most of PICO-8 carts use _update() and _draw() for the game loop. Sadly, that wastes quite a few chars:

function _init()
 a = 0
end

function _update()
 a += 1
end

function _draw()
 print(a)
end

You can get rid of _init() and put all your initialization code right in the beginning of the cart, because you aren’t going to restart it anyway. And you can safely merge _update and _draw into a single function:

a = 0

function _draw()
 a += 1
 print(a)
end

But we can go even further than that. Did you know, that lua has goto statement? Wait, don’t close this post, we will use it only for good purposes:

a = 0

::_::
a += 1
print(a)
flip()
goto _

Fun fact: you can actually replace _ with things like stars (Shift+S = ★) and it still will work!

So for those who never saw goto‘s in lua before: you create a spot where you can jump later on with ::name:: and then just jump to it with goto name. I don’t recommend to use it too often, because it makes code harder to understand, but in our case, it’s pretty simple and helps to save some space!

Also, notice that flip() call before goto _. It’s really important, it makes the cart run at constant fps!

Shorten all your variables

Hey, remember how they taught you to name your variables well? Well, now we can have fun breaking that rule! I think it’s obvious, that renaming all your frame to f and so on will help you to optimize your cart.

Note: PICO-8 has a global t function, that you can call to get the current frame number. So we can fully remove the a variable from the previous example and replace print(a) with print(t())!

Also, if you use some api function a lot, let’s say circfill(), you can define a 1-char variable to save space:

c = circfill

c(32, 32, 8, 8)
c(64, 32, 8, 9)
c(32, 64, 8, 3)

In most cases, you also can remove local keyword and make all the variables global.
Except when you are dealing with fractals, or it will cause this.

Shorten your constants

Did you know, that PICO-8’s lua supports hex and binary numbers? It sounds kinda cool, and we use hex numbers all the time when poking with memory, but their representation is kind-of long. Let’s write number 8192 in all of them:

Hex:

0x2000

Binary:

0b10000000000000

Regular:

8192

Can you spot any difference? I totally can. So if you have a hex number or a binary number in your code, try to replace it with a simple number.

Sometimes the same constant (string or number) appears in code multiple times, and it might be worth it to replace it with a single-char global variable:

print(128,128,128,8)
circfill(128,128,8)

Becomes:

z=128
print(z,z,z,8)
circfill(z,z,8)

Shorten your function calls

I don’t think anyone uses functions too much in tweet carts, but here are a few weirdly useful tricks you can do with PICO-8’s lua:

do_thing({ a = 32 })

Can be replaced with:

do_thing{a = 32}

Yes, you can just remove () like that, don’t ask me why

Same applies for single string arguments:

do_thing("yay")

Can be replaced with:

do_thing"yay"

Recently we also got a shortcut for PICO-8’s print() function:

?"hello, world",x,y,color
?"cool"

Note: you need to place this statement on a separate line from everything else
It’s weird, but it works!

If you draw a lot of shapes with the same color, you don’t really need to pass the color argument all the time:

rect(1, 1, 32, 32, 8)
circ(64, 64, 7) -- Will be drawn with color 8!

Shorten your if statements

PICO-8 is a bit different from standard lua, and thanks to that we have += and all that cool stuff! And we also have a really neat shorter version of the if statement.

if happy then
 sing()
end

Can be replaced with:

if(happy)sing()

Please note, that it is must be a single line statement, and the only one on the line!
It takes some time to get used to it, but after that, it will become your best friend!

Remove whitespace

So whitespaces in lua serve only as a way for us, humans, to make the code more readable. We can safely remove most of them, once we are done with coding!

Do this only once you are done with every other step! It becomes super hard to read the code after doing this!

By whitespaces, I mean tabs, new lines, and spaces (\t, \n and ).
So lets take this example:

function _draw()
 cls() 
 for i = 1, 32 do
  print(i, nil, nil, i)
 end
end

First thing I would do is to select all the code (Ctrl+A) and then de-indent it (Shift+Tab) a few times until everything becomes this:

function _draw()
cls() 
for i = 1, 32 do
print(i, nil, nil, i)
end
end

You can also remove all the tabs by hand, but why would you want to do that?

Now let’s remove all spaces, that we don’t need. I’ve been using a lot of them to make the code look pretty on the blog, but in PICO-8 editor I usually avoid most of them (because the display is so tiny!). I think it’s pretty straight-forward:

function _draw()
cls() 
for i=1,32 do
print(i,nil,nil,i)
end
end

We can’t remove space between for and i, because it would result in an identifier fori that does not exist, and we also can’t remove the space between 32 and do, because how lua parses numbers.

But we can remove every single space in the print() call. It takes some practice to understand what you can remove and what you need to leave, I recommend just trying to remove every space 1-by-1, look into lua errors, and fix the ones that the compiler did not like.

Last but not least, we can remove the new lines. You might think, that those won’t count, but they are represented by invisible \n char.

The rule of thumb for me, when I remove whitespaces, is that I try to remove every whitespace, that doesn’t separate identifiers or numbers.

So by applying this simple rule, here is what I ended up with:

function _draw()cls()for i=1,32 do
print(i,nil,nil,i)end
end

Sometimes I rearrange my expressions a bit to save an extra \n.

Can’t remove the new line after "32" in this case, because throw an error. We need whitespace between identifier and number to separate them!

a=cos(d)*32
print(a)

But if we do this, we can remove the new line, because now "32" is in another place, and "d" with "print" are separated by ")"!

a=32*cos(d)print(a)

Practice

So for the sake of example, I wrote this doodle:

local t = 0
local amount = 16

function _draw()
 cls()
 t += 1

 for i = 1, amount do
  local distance = cos(t / 60+i / 2) * 16 + 32
  local angle = i / amount + t / 120
  local x = cos(angle)*distance+64
  local y = sin(angle)*distance+64
  local size = cos(t / 40 + i / 4) * 3 + 4

  circfill(x, y, size, i % 8 + 8)
  circfill(x, y, size / 2, 0)
 end
end

Circles...

The doodle is simple, I tried to make the code clean and readable.
Now it’s your turn! How much can you reduce it?

Have fun!

You might also find interesting

Join the discussion!

Zikun 3 years ago
I love this content! However, I found hard to reduce the local x and y. Since they are both using the same formula with the only difference being sin and cos, is it possible to reduce the redundancy here?
Reply
egordorichev 3 years ago

Hm, if it was a bit of a more complex formula, something like cos(angle) * distance + 32 / t() * sin(whatever) for sure you could take the distance + 32 / t() * sin(whatever) part and make a variable out of it, saving up some char space. I don't think you could really optimize this part out.

@2Darray did a great job, as can be seen in his tweet:

c=circfill::_::cls()p=t()for i=1,16 do
r=cos(p/2+i/2)*16+32g=i/16+p/4x=cos(g)*r+64y=sin(g)*r+64s=4+3*cos(p+i/4)c(x,y,s,i%8+8)c(x,y,s/2,0)end
flip()goto _
Reply