This is a game created for the TweetTweetJam 10 to make a game in 500 characters of code or less.

This was my first attempt at sizecoding in Pico-8/Lua and I found this quite a challenge. I attempted to balance my ambition (make a dungeon crawler) within the constraints. That said, the game is closer to something like the old Chase / Daleks / Robots game  than a true roguelike.

How to play

You are a cat descending levels in the dungeon. Try to reach the stairs (yellow cross-hatch symbol) to go down one level. You will die if you get eaten by any of the red demons. See how many levels of the dungeon you can descend before getting eaten! That's it!

Your only control is the arrow keys. Control-R (Reload the page) to begin a new game.

High scores

15 and above: God-level.

10 and above: True catpunk.

5 - 10: Not bad, kittypunk.

Less than 5: You're not too good at this.

Code

v5: Current code as of 2026-05-21

r,x,y,f=rnd,3,3,1::i::e={}a=r(9)\1b=r(8)\1for i=1,5do repeat g=r(8)\1v=r(8)\1until g-x|v-y!=0e[i]={x=g,y=v}end::m::d=16for y=0,114,d do for x=0,114,d do
fillp(r(♥))rectfill(x,y,x+d,y+d,2)end end
?"▒",a*d+4,b*d+5,9
?"🐱",x*d+4,y*d+5
?f,2,2,6
for j in all(e)do 
z=r(4)\1/4j.x+=cos(z)j.y+=sin(z)?"😐",j.x*d+4,j.y*d+5,8 if(j.x==x and j.y==y)stop() if(x==a and y==b)f+=1goto i end
repeat flip()v=btnp()until v>0 x+=v\2%2-v%2y+=v\8%2-v\4%2goto m

Comments/questions

If you have any further sizecoding suggestions, or better yet, some solutions to the following bugs/unfair things that would keep me within 500 characters, let me know!

Known bugs/limitations:

  • The monster spawner is buggy. When spawning new level, it knows not to spawn enemies on top of player, but then the monsters take one step before player gets first move, so could kill the player before they've had a chance to move.
  • Technically the monsters can walk offscreen and then back on, so they could kill you if you're on the outer ledge and they walk into you. I think this is ok!
  • The monsters can move 8 directions orthogonally while the player cannot. Again, this was a limitation based on my inability to fit in an either/or X/Y movement mechanism for the monster within the code length limitations. Update: solved! Thanks @pancelor.
  • The left-behind screen residue/artifacts are a result of not redrawing the screen background, and I think are both visually interesting and make the game slightly harder. I like it!
  • Ability to restart game with button press, or at least display last score and then start new game rather than requiring hard re-loading game.
Updated 14 days ago
Published 21 days ago
StatusReleased
PlatformsHTML5
Authornotapipe
GenreAdventure
Made withPICO-8
TagsDungeon Crawler, PICO-8, tweetcart, tweettweetjam

Development log

Comments

Log in with itch.io to leave a comment.

(6 edits)

best score: 17. I love the visuals! like a nice purple quilt

and you’ve nerdsniped me; here have some sizecoding tips:

  • a!=x and b!=y becomes a-x|b-y!=0 (basically, check that a-x and b-x both don’t have any bits set, i.e. both are zero) This can save a few chars in 3 different places

  • enemy movement: If you want them to only move in 4 directions, this should do it:

R=rnd(4)\1/4
j.x+=cos(R)
j.y+=sin(R)

(costs 12 chars ish)

  • stop() can be run(), saving a char. this makes the game autorestart instead of freezing – might be nicer? hard to see your score tho

  • you can save some chars in init with temporary vars: for i=1,5do repeat A=r(8)\1B=r(8)\1until A-x|B-y!=0e[i]={x=A,y=B}end m()end

  • input code:

::m::
-- TODO delete m() and put the code inside it here
repeat
  flip()
  v=btnp()
until v>0
x+=v\2%2-v%2 --extract the x bits
y+=v\8%2-v\4%2 --extract the y bits
goto m

Its arcane but it works! it will let the player move diagonally tho. I also have an incredibly cursed snippet for 4-way movement if you prefer

  • goto – I agree with Kamencesc, I think you can remove all functions from this cart and use goto instead

“Can you explain more about how goto works?” ummm let’s see, basically these two carts are equivalent:

function _draw()
  --stuff
end

and

::d::
--stuff
flip()
goto d

searching for “lua goto” might help too.

I don’t know how to explain it succinctly, but here’s an example where I used multiple goto/labels to have an “init” phase and a “draw” phase, sorta like what you have here.

Thanks! This was helpful. Now I’m currently on my v4 and used your great tips for shrinking setting the enemy positions and having them move 4-ways only. I also used your tip to set the temp vars in init too.

I updated the code above.

I’m still having some issues with the GOTO jumps after reading a bit but getting stuck on the limitations with nested functions. I only want the enemies to move only one step rather than continuously and wasn’t able to get this to work correctly.

mhm! goto has some rules about jumping into other scopes (like functions). but if you remove all the functions then it works. here’s a minimally edited version: (a lot of spaces can be removed)

r,x,y,f=rnd,3,3,1

::i::
e={}a=r(8)\1b=r(8)\1
for i=1,5do repeat g=r(8)\1v=r(8)\1until g-x|v-y!=0e[i]={x=g,y=v}end

::m::
d=16for y=0,114,d do for x=0,114,d do
fillp(r(♥))rectfill(x,y,x+d,y+d,2)end end
?"▒",a*d+4,b*d+5,9
?"🐱",x*d+4,y*d+5
?f,2,2,6
for j in all(e)do
z=r(4)\1/4
j.x+=cos(z)j.y+=sin(z)?"😐",j.x*d+4,j.y*d+5,8
if(j.x==x and j.y==y)stop()
if(x==a and y==b)f+=1goto i
end

repeat
  flip()
  v=btnp()
until v>0
x+=v\2%2-v%2
y+=v\8%2-v\4%2
goto m

I noticed that the spawn check isn’t working – the enemies won’t spawn on the player, but the enemies get to move once after spawning!

(+1)

Thanks, this helped me get down to 439 chars but I ran out of bandwidth trying to chase down a solution with all of the gotos to render spawning at the right time. might come back another time to fix that part. thanks again.

I love it, all the different patterns, looks so nice. Great job.

There's some suggestions to shrink your code.

Semicolons (;) are'nt needed

if(v==2)x+=1;m() -> if(v==2)x+=1m()

If it did'nt work, use ; or space otherway.

Your r() function take more characters than use r(8)\1 each time.

function + call it 4 times = 40 chars
function l()return r(8)\1endr()r()r()r()
6 chars * 4 times = 24 chars
r(8)\1r(8)\1r(8)\1r(8)\1

Maybe you can change _init and _draw with labels ::i:: and ::d::, you can change both or only _draw. You can't call a label that's outside of the function.

This can be write in another way (11 chars to 8 chars).

(r(3)-1)\1) -> r(3)\1-1

With this, try to correct the monsters spawn when a new level is created.

(1 edit)

Thanks, this helps a lot. I’ve implemented all of these except the GOTOs. Can you explain more about how that works?

I added in checks to make sure monsters don’t start on top of player when new level drawn.

Current code, 507 chars:

r,x,y,f=rnd,3,3,1function _init()
e={}a=r(8)\1b=r(8)\1
for i=1,5do repeat e[i]={x=r(8)\1,y=r(8)\1}until e[i].x!=x and e[i].y!=y end m()end
function _draw()v=btnp()
if(v==2)x+=1m()
if(v==1)x-=1m()
if(v==4)y-=1m()
if(v==8)y+=1m()end function m()c=114d=16for y=0,c,d do for x=0,c,d do
fillp(r(9999))rectfill(x,y,x+d,y+d,2)end end
?"▒",a*d+4,b*d+5,9
?"🐱",x*d+4,y*d+5
?f,2,2
for j in all(e)do
j.x+=r(3)\1-1
j.y+=r(3)\1-1
?"😐",j.x*d+4,j.y*d+5,8
if(j.x==x and j.y==y)f=1stop()
if(x==a and y==b)f+=1_init()
end end

Okay, I’ve shaved off a bit more. In the end, I moved the score in the location of the ‘cat’s tail’ (let me shave off the last 2 chars.

r,x,y,f=rnd,3,3,1function _init()
e={}a=r(8)\1b=r(8)\1
for i=1,5do repeat e[i]={x=r(8)\1,y=r(8)\1}until e[i].x!=x or e[i].y!=y end m()end
function _draw()v=btnp()
if(v==2)x+=1m()
if(v==1)x-=1m()
if(v==4)y-=1m()
if(v==8)y+=1m()end function m()d=16for y=0,114,d do for x=0,114,d do
fillp(r(9999))rectfill(x,y,x+d,y+d,2)end end
?"▒",a*d+4,b*d+5,9
?"🐱"..f,x*d+4,y*d+5
for j in all(e)do
j.x+=r(3)\1-1
j.y+=r(3)\1-1
?"😐",j.x*d+4,j.y*d+5,8
if(j.x==x and j.y==y)stop()
if(x==a and y==b)f+=1_init()
end end

I made a third fix and restored the score to the top left corner.