Log In  

Cart #firroref-0 | 2021-10-15 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

Here's some code:

poke(0x5f5e,0x11) --only enable bitplane 1
sset(0,0,15) --edit sprite 0

I would expect the sset() call to set the spritesheet's corner to color 1 (dark blue) because of the bitplane setting. However, this instead sets the corner to 15 (tan).

I can workaround this for now by using pset and then memcopying the screen to the spritesheet, but that means my decompression code (which wants to call sset with bitplanes active) will need to either take less than a frame to run, or show artifacts onscreen while it runs.

P#98668 2021-10-15 01:19

I just found out that you can turn a specific sprite into the icon for the binary export: https://www.lexaloffle.com/dl/docs/pico-8_manual.html#Binary_Applications_ and whoa, this is really great! But afaict it's restricted to the standard palette. Is there some way to set a custom palette for the icon in the export? If not, adding some sort of palette flag might be a nice feature:

EXPORT -I 32 -C 12 -Z 0,132,4,140,134,6,135,7,8,137,139,11,138,130,13,131 MYGAME.BIN

or maybe reuse the -C flag, and a -1 entry means transparent?

EXPORT -I 32 -C 0,132,4,140,134,6,135,7,8,137,139,11,-1,130,13,131 MYGAME.BIN

(although that might be a bit awkward because using pal(12,-1,1) ingame means to map 12->0x8F, not 12->transparent...)

P#98352 2021-10-07 20:06

Cart #freecell1k-0 | 2021-09-28 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

Free Cell, in 1022 characters of code, and no sprites! twitter | itch


  • click and drag cards around
    • you cannot move stacks of cards; one at a time only!
  • stack descending cards of alternating colors in the main play area
    • any card may be placed in an empty column of the main area
  • store any card in the four "free cells" at the top left
  • make a stack of each suit A->K in the top right to win
  • reset the cart to start a new game


You can only move one card at a time; if you want to move a stack of cards you have to take it apart and put it back together manually. This is different from "standard" solitaire, and it makes Free Cell particularly interesting! It also makes the implementation a bit easier to fit into the tiny code-size constraint ;)


Congrats! Enjoy the win animation here: https://pancelor.itch.io/solitaire-win-animation (I wanted to add a "you win" animation to this game, but I didn't have the room to fit it in... so I made it as a separate cartridge)


The source is in the cart, of course, but it's here too. Remember to enable puny text mode (cmd-p) before pasting this into your local PICO-8 console:

function F(i)S=s[i]J=i\W I=i\8O=1-I U=I*(i-8+J)*14+O*i*Z+2V=O*max(#S*6+22,28)+5end
for i=0,51do s[i]={m=i\W}A(B,{x=i,y=400,k=i+i\w*3},rnd(i+1)+1)end
q(-15-😐,264,2043,4,3843)D=rectfill::_::L=T%8T+=1K=B[T]N=not btn(5)X=stat(32)-6Y=stat(33)-8C=fillp
for i=15,0,-1do
C(β–’)a=5+O*28D(U,a,U+W,a+Z,2)C()for _ENV in all(S)do x=u+3*x+.5>>2y=v+3*y+.5>>2end
if(H and N and(J+G+#S<1or Z-I|k+G==Z|1+H.k^^32or H.k==J+k|G))K=H L=i
for r in all(B)do
q(63-😐,244)D(x-1,y-1,x+W,y+Z,4)q(61-😐,-1,-1)D(x,y,x+W,y+Z,3)a=r.k%Z+1?(a==10and"³f|³f0 ³b"or sub("a23456789|jqk",a,a).." ³d")..split("♥,β—†,◆⁡8f..³aᢜ3.,◆⁡8fニ")[r.k\Z+1],x+1,y+1,r.k\32
for i=0,77do
if(M< Q/β§—)Q=0?"⁷ceg4"
goto _

(there's an extra space in there near the end because the BBS text editor seems to choke on the < symbol)

An earlier, more readable, and much longer version of this code can be found here


Some of the more bizarre tricks I used to squeeze every bit of functionality out of my 1024-character budget:

  • set the palette with poke2(-15-😐,264,2043,4,3843)
  • update card positions with for _ENV in all(S)do x=u+3*x+.5>>2y=v+3*y+.5>>2end
  • dynamically cast shadows with a very particular palette and poke2(63-😐,244)rectfill(x-1,y-1,x+W,y+Z,4)poke2(61-😐,-1,-1)
  • draw the 4 suit icons with split("♥,β—†,◆⁡8f..³aᢜ3.,◆⁡8fニ")[suit_id]
  • check if you can drop your held card with i\12+G+#S<1or 16-i\8|k+G==16|1+H.k^^32or H.k==i\12+k|G
  • wait until the next frame and clear the screen with ?"⁢1⁢c6"

    • (thanks to zep for pointing out that \^ can be written as ⁢!)
  • auto-move cards to the top right by tracking the minimum stack height with bitshifting (search for M (and m) to see the relevant code)
  • check whether the game is won by taking advantage of the fact that 2^12<β§— and β§—<2^13

I'M SORRY, "poke2(63-😐,244)"???

Yeah! 63-😐 is 24414.5, which is the address I need to poke to get those slick shadows! Check out this post for more info.

P#97939 2021-09-28 22:10 ( Edited 2021-09-30 04:04)

Cart #constantcompanion-5 | 2021-09-29 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA


This is a niche tool to help you save characters when writing carts that are codesize-constrained.

  • type in a number; press enter
  • then press up/down and ctrl-c/enter to copy a code
  • navigate back up (or press backspace) to start typing a new number

Example: 0x6000 can be written as 0x6000 (6 chars), 24576 (5 chars), 6^13 (4 chars) or βŒ‚-🐱 (3 chars!)

The tool isn't too smart, but it sorts the results by character count, so the top results are your best bet.


While I was making Free Cell 1K (itch | twitter | bbs) for the #Pico1K jam, I realized I could save characters by taking advantage of the built-in constants. I needed to poke a few things to various addresses (for setting the palette, drawing dynamic shadows with bitplanes, and enabling the mouse) so I had code that looked like this:

-- 94 chars (not including comments or newlines)
poke(0x5F10,8,1,251,7,4,0,3,15) --palette
poke(0x5F2D,3) --mouse
poke(0x5F5C,-1) --disable btnp repeat
poke(0x5F5E,0xF4) --shadows on
poke(0x5F5E,0xFF) --shadows off

Each of those poke addresses uses 6 characters; we can do better! The first thing I did to save characters was this:

-- 84 chars (not including comments or newlines)

But then I saw @zep tweet a trick for writing sqrt(x) as x^β–ˆ instead (because β–ˆ (shift+A) is defined to have a value of 0.5), and I realized I could do even better: the 😐 (shift+M) character is defined to have a value of -24351.5, so I did this:

-- 95 chars (not including comments or newlines)

95 chars is not an improvement... yet! However, poke() ignores fractional addresses, so those .5s aren't necessary. Also, I was able to combine the 0x5F5C and 0x5F5E pokes into a single poke, which nullifies some of the relative advantage of the A=24365 technique. In the end, this was the shortest code I could find:

-- 64 chars (not including comments or newlines)

This saves 2 characters over the equivalent version that uses A=24365 instead of the moon face. (or maybe just 1 character, if the newline after q=poke2 can't be removed)

how did you know moon face was the one to use?!

I didn't! I wrote a program to brute-force try all the built-in symbols and see if any were useful for my needs. Check out tab 5 of the cart ("analysis") to see the brute-force algorithm I used.

I've cleaned that program up and posted it here for you. It might be less useful for tweetcarts (because stuff like 😐 takes up 2 characters on twitter) but it saved me 1~2 entire characters (genuinely very helpful!) during the Pico1K jam, and I hope it helps you too.

Leave a message here or tag me on twitter if you found it useful; I'd like to see what you make!


  • v5: show options (and allow them to be copied) as soon as they're found. (no longer need to wait for the entire search to complete)
P#97937 2021-09-28 21:48 ( Edited 2021-09-29 19:14)

Cart #ocelotsafari-0 | 2021-04-29 | Code ▽ | Embed ▽ | No License

welcome to the ocelot safari!

enjoy the ocelots, and do let us know if you find any long-lost relics deep in the jungle :)


  • hold Z to drag things
  • arrow keys to move
  • retrieve the lost gemstone of Tezcatlipoca! some say it’s as far as fvkgl-sbhe meters deep in the jungle!


  • we'll leave you some new tools at the initial drop point, if the ocelots steal your gear
  • ocelots can crawl through vines and trees -- they're tricksters!
  • be sure to bring some matches; the nights are long and dark, and who knows what lurks in the jungle...


I didn't make the time to make an interactive tutorial, so here's a video instead:

And here's a gif showing how to use each tool:

(light a fire by bumping matches into wood)

good luck in there!


  • This was made for the Ludum Dare 48 compo in 48 hours. (plus minor bugfixes; read changelog here and here). Rate my entry! https://ldjam.com/events/ludum-dare/48/ocelot-safari
  • My initial goal was to make a game exploring how items feel if you have no inventory system or "use item" button, and I'm happy with the results. Sometimes movement can feel a bit awkward, especially at the start, but that's what the whole game is built around, so I think it's fine.
  • I really like how the nighttime and the ocelots make it a visceral struggle to advance deeper and deeper into the jungle.
P#91318 2021-04-29 22:01

In the 0.2.2 release, zep made a small tool for creating custom fonts:
https://www.lexaloffle.com/bbs/?tid=41544 (search for "Custom Fonts")

It's a great tool, but if you only have a font snippet (and no longer have the spritesheet), it's hard to edit that font again. So, I extended the tool to also support font importing!

You can get my font tool from the pico-8 console:


Instructions on how to use the tool are in tab 0 of the cart. happy fonting!

P#90878 2021-04-21 09:29

This code:

x+=x<0 and -100 or 100

doesn't work the way I'd expect; it sets x to either -100 or 100

I assume this happens because that line gets preprocessed to this:

x=x+x<0 and -100 or 100

which gets interpreted like this:

x=(x+x<0) and -100 or 100

but I wish it would be preprocessed to this instead:

x=x+(x<0 and -100 or 100)

Here's a test cart; it currently (0.2.2c) fails the tests:

Cart #zemazebosi-0 | 2021-04-19 | Code ▽ | Embed ▽ | No License

P#90818 2021-04-19 22:01

I hang out in the pico-8 discord #help channel, and a lot of the questions people ask boil down to "my code isn't doing what I want it to do... I don't know what to do. help?" If you feel completely lost when your code doesn't work, this tutorial is for you!

PRINTH is the single most helpful tool I know for debugging. When you're programming, often your head will get out of sync with the computer, and you won't understand what it's doing. The best way I know to bring them back in sync is to slap down some PRINTHs all over the place! Print out what your code is actually doing, and you'll eventually figure out what wrong assumption you're making.

To get PRINTH working for you, you'll need to be able to see the pico-8's output console. That's why we set up a shortcut to pico8.exe (at the start of the video). You can move this shortcut anywhere you want!

PRINTH itself can be a bit annoying to use, so I built a function PQ that makes it more friendly -- check out the source code! you're free to copy that second tab into any of your projects. if you find my functions helpful and you'd like to credit me, I'd appreciate it! but that's not required; feel free to use the functions for whatever you want

Here's the cart:

Cart #printh_helpers-0 | 2021-04-08 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

Let me know if you found this helpful!

P#90153 2021-04-08 01:39

There's a strange issue happening when i try to edit the bottom two visible rows of the map using the new "fullscreen" map editor mode (shift+tab). Basically, the bottom two rows are uneditable; I assume pico8 is mistakenly trying to prevent me from editing the area underneath the red chrome that shows up in nonfullscreen mode

It seems to fix itself if you pan around in the normal map editor?

P#89369 2021-03-22 20:19

Cart #fitahinigu-3 | 2021-02-23 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

There's something weird going on here; using ..= along with a single-line if-then-end statement causes a syntax error that makes no sense:

syntax error line 16 (tab 0)
if x<2 then bad..="1" end
')' expected near 'end'

I've included 4 similar test cases that don't trigger this bug

My pico-8 version is 0.2.2 (stat(5)==30)

P#88031 2021-02-23 00:17

Cart #remains-0 | 2021-02-19 | Code ▽ | Embed ▽ | No License


  • arrow keys: move

A short little game. I'm quite proud of how it turned out

P#87870 2021-02-19 01:17

Cart #typefight-1 | 2021-02-19 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

a standard normal typing game


  • a: a
  • arrow keys: move/menu select/etc
  • b: b
  • backspace
  • c: c
  • d: d
  • dash: -
  • e: e
  • enter: confirm/begin/etc
  • f: f
  • g: g
  • h: h
  • i: i
  • j: j
  • k: k
  • l: l
  • m: m
  • n: n
  • o: o
  • p: p
  • q: q
  • r: r
  • s: s
  • space:
  • t: t
  • u: u
  • v: v
  • w: w
  • x: x
  • y: y
  • z: z
P#87864 2021-02-19 00:14 ( Edited 2021-02-19 21:56)

A question came up yesterday in the pico-8 discord: are there any nice ways of doing default arguments? For example,

function foo(a,b,c)
  a=a or default_for_a
  b=b or default_for_b
  c=c or default_for_c
  -- do stuff

That's five tokens spent per argument; can we do better?


My first thought was doing something like this:

function new_actor(fields)
 return merge(parse"x=64,y=64,w=8,h=8",fields)

This is a common pattern I use in my games; merge(A,B) is a custom thing I have that merges tables A and B together by creating a new table, copying all of A's contents into it, and then copying all of B's contents into it. Note that if A and B share any keys, the value from table B will be used for the result, which makes this handy for setting defaults in a table. (and parse is another custom thing that does what you'd expect, turning a string into a table)

However, this wasn't quite what the questioner was asking for; I can set default values for table members with this method, but I can't set local variables. This method wouldn't work to save tokens if you had to say locals.myvar throughout your function instead of just myvar!

Time for some brain thinking... hmm, I recently found out that lua's _ENV table is accessible through pico-8 (although you have to spell ENV with pico-8's puny text mode, funnily enough). This is a bit of a special table that all variables are implicitly accessed through, so typing tonum(myvar) actually looks up the values of tonum and myvar in the _ENV table. To give a concrete example, you can get/set the value of a variable A by using _ENV.A (or _ENV["A"]) instead

One stroke of madness inspiration later, I came up with this positively arcane incantation:
(this is a working pico-8 cart; paste it into an empty cart to see it in action!)

-- defaultargs, by pancelor
function defaultargs(str,args)
 local locals=setmetatable({},{__index=env_backup})
 for i,str2 in ipairs(split(str)) do
  local parts=split(str2,"=")
  local k,v=unpack(parts)
  locals[k]=args[i] or tonum(v)
 return locals

function myrect(...)
 local _𝘦𝘯𝘷=defaultargs(

function _init()
 poke(0x5f2d,1) --enable mouse
function _draw()
 local mousex,mousey=stat(32),stat(33)
 assert(x==nil) -- test to make sure the function's local args didn't leak out into global scope

It's certainly Something but hey it gets the job done! And it does it with ~60 tokens for defaultargs plus 6 tokens per function that uses it (which very quickly becomes better than the 5~6 tokens needed per argument in the 'a=a or default' way)

So... how does it work? Well, calling 'myrect(mousex,mousey)' ends up calling 'defaultargs(
"x=64,y=64,w=32,h=32",{mousex,mousey})'. The defaultargs function creates a new table called locals that myrect will later set to it's _ENV. So, if we can somehow make locals equal to {x=mousex,y=mousey,w=32,h32}, then when myrect sets it to _ENV, the variables x, y, w, and h will be available in the scope of myrect. However, we also want to have other global variables in scope too, like the rectfill function, so we do some metatable/__index shenanigans to make that work.

Understanding that is the most complicated bit; the rest of defaultargs does the natural thing that sets locals to have the members we want inside of it. (if 'unpack' confuses you, just pretend that line says 'local k,v=parts[1],parts[2]' instead; it does the same thing)

This one code snippet uses 'unpack', '...', metatables and fallback indexing, required puny text, and lua's _ENV table... wow. I'm not exactly sure why making a backup reference to _ENV is necessary; I assume it's something to do with how _ENV is a bit special in lua?

Time for a nap.

Happy token saving!


P#85041 2020-12-04 21:27

Who's the biggest fish?

Cart #fishy-0 | 2020-11-11 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA


  • arrow keys: move
  • x: restart

This was my entry to TweetTweetJam 5; check it out on the jam page here if you like. Also, I wrote a short blog post about it here!

And clearly, a tweetcart needs to be tweeted: here it is

P#84105 2020-11-11 05:23

you've received a mysterious invitation to a party. but it's not just any party...

it's the πŸ’€monster mashπŸ’€

better find somebody to go with!

Cart #monstermash-1 | 2020-10-30 | Code ▽ | Embed ▽ | No License


  • arrow keys to move
  • x to interact
  • z to jump

(made by @princryss and me! she did the design + art + sound, I did the programming)

P#83514 2020-10-30 19:45

Cart #escalatorworld-0 | 2020-08-31 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

Welcome to Escalator World! You can only go up from here!


  • up/down/X to menu. (the rest is explained in the tutorial)

A tribute to one of my favorite games from the old yoyogames forums.

P#81385 2020-08-31 01:03

Cart #woruyudutu-0 | 2020-08-28 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

I think I've found a bug with how music and sfx channels are used; I've attached a cartridge that shows off the issue. Basically, if you call sfx() and then call music() afterward, the music won't be heard until the sfx() finishes, even though there are 3 available unused sound channels for the music to play on.

Am I misunderstanding how this is supposed to work? it seems like the music() command should figure out that it has 3 free channel slots to play in... I do see the channelmask parameter (https://pico-8.fandom.com/wiki/Music) but calling music(4,0,0b1111) instead of music(4) doesn't fix the issue. Even if it did, sometimes you want to start playing music after sound is already playing, so how are you supposed to set up in advance which channels the music has priority to play on?

I can see workarounds for this (e.g. call sfx(8,-2) to stop the sound, or shim in a custom version of sfx() that only ever plays sound effects on channels 2 and 3, leaving the other channels open for music... but these workarounds seem weird/awkward enough that I'm thinking this is just a bug in pico-8

P#81315 2020-08-28 22:29 ( Edited 2020-08-29 00:17)

When I press shift+9 in the music editor (but not the sound editor) on any pattern EXCEPT for pattern 0, pico8 crashes.

My OS is windows 7, and my pico8 version is 29, as shown by print(stat(5))

P#81224 2020-08-26 19:00

I wish I knew exactly how to reproduce this; it doesn't happen all the time. But decently often (~4 times in the last ~40 hours of runtime? idk just a ballpark guess) when I open a new tab in the code editor and press ctrl+v to paste in code, pico8.exe immediately exits.

The last 3 or 4 times this has happened, I've relaunched pico8.exe and tried to do exactly the same keypresses etc (ctrl+a to copy all text from one tab, ctrl+t out of muscle memory to create a new tab (does not work in pico8), mouse click to create a new tab for real, ctrl+v to paste code), and it works the second time without crashing. Maybe it only happens when the console has been on for long enough? (>4 hours, generally). Maybe I'm not reproducing my steps exactly? idk

My pico8 version is 29, as shown by print(stat(5))

My OS is windows 7

(Is there some sort of crash logs folder or anything like that that I should attach to this post?)

P#81222 2020-08-26 18:46 ( Edited 2020-08-26 18:48)

This cartridge fails the assertion, which normally I would write off as a floating point bug, but pico-8 uses fixed point representation so this seems like an actual bug in pico-8 to me?

function _init()
 local x=0.6
 local y=0.3
 local sum=x+y

function assert_equal(a,b)
 local s=a.." ("..tostr(a,true)..") does not equal "..b.." ("..tostr(b,true)..")"

function _draw()

The assertion fails, saying:
0.9 (0x0000.e665) does not equal 0.9 (0x0000.e666)

This is just one example, but this happens to me more often than not when trying to compare decimal numbers :(

P#80489 2020-08-08 04:46

View Older Posts
Follow Lexaloffle:        
Generated 2021-10-28 15:29:03 | 0.136s | Q:76