Disco Mouse
I loved the Rodent's Revenge game from Windows 3.1 so I wanted a PICO-8 demake.
I'm not really sure where the disco idea came from, but Moskau is a perfectly ridiculous song.

Features
- Disco Music (that you can turn off)
- Cheese
- 12 Levels
- Beat-synchronized Cats
- Teeny Tiny Sprites
- Challenging Gameplay
How to Play
Push gray block. Red blocks don't move.
Trap cats in blocks and they'll turn into cheese.
Cats are sneaky and can move diagonally.
Collect cheese before the level timer is up.
If you fall in a hole, you're stuck for 10 seconds.
If you touch a mouse trap, you'll lose a life.
Beat the high score and post your screenshot.
![]() |
[8x8] |
I was only able to find a few examples of rotating sprites using tlines on the forums. While the code worked, I couldn't really make sense of all the variables and magic numbers.
After spending quite a lot of time reading, debugging, and sketching hundreds of triangle, I think I have a decent understanding of the math and how it works. SOH-CAH-TOA haunts my dreams.
At some point, I'd like to make a more detailed post, but for now, I've written a tile rotating function with a lot of comments.
Hopefully it explains the math and the concept well enough that you can modify the code to suit your projects.
This code is intentionally not optimized or minified.
-- rtile -- draw rotated map tile -- x,y: output will be centered here -- tx,ty: map coords to the -- top,left tile to draw -- ang: rotation angle -- scale: scale factor -- default:1 -- tw,th: tiles to read -- default:1 -- tcxo,tcyo: -- tile center offsets -- in tile units -- default:0 -- nb: leave space around the -- tiles to rotate on the map -- or adjacent tiles may end -- up in the rotated output. function rtile(x,y,tx,ty,ang, scale,tw,th,tcxo,tcyo) local scale,tw,th= scale or 1,tw or 1,th or 1 local tcxo,tcyo= tcxo or 0,tcyo or 0 -- 8 is base tile->pixel scale scale*=8 -- precalculate a few values -- we need. using -sin because -- pico-8's sin func is inverted. local ca,sa=cos(ang),-sin(ang) -- there are two coordinate -- domains here, the input -- domain is in tile units -- and each tile has a domain -- of [0..1). the output domain -- is in pixels. we'll use "t" -- and "p" prefixes. -- what we're doing here -- is reading the source tile -- as a bunch of slices that -- stack up to form a square. -- we rotate that "reading" -- square and it changes how -- the input is read. the -- way we do this is with a -- rotation matrix that we -- apply to each point along -- one side of the "read" -- square. -- we need to know the center -- of the square for computing -- rotations. apply the center -- offsets here. -- tile center coords local tcx,tcy= tx+tcxo+tw/2,ty+tcyo+th/2 -- we're going to read the tile -- in a rotating square. if we -- use radius=tw/2, the corners -- will be clipped. we need the -- length from center to corner -- to use as the radius. we -- need to find the larger -- of the two radii to ensure -- the output fits. -- todo: make more efficient. -- tile radius local trad=max( sqrt((tx-tcx)^2+(ty-tcy)^2), sqrt((tx+tw-tcx)^2+(ty+th-tcy)^2)) -- to output the texture we -- draw a bunch of horizontal -- tlines. think of it as -- drawing scan lines on a -- display. using the p -- prefix to indicate "pixel". -- in order for the output to -- be the correct scale and -- not clip, we need to have -- a output space which can -- hold the rotated output. -- scale trad by 'scale' to -- get a pixel radius that -- will hold everything we -- need to draw. -- pixel radius local prad=scale*trad -- line scale is the ratio -- of read len/write len. -- this just makes a few -- calculations clearer. local lns=1/scale -- remember there are two domains. -- as we draw the output lines -- 0 to 2*prad, we increment -- from 0 to 2*trad proportionally. -- for every 1 pixel y in the -- output, we increment toffy -- by tstep. -- toffy range is -trad,trad local tstep,toffy=lns,-trad -- we also need to tell tline -- the step of the read lines. -- this controls how "far" -- each line reads in the tile -- domain. we want the length -- of the "read" line to be -- scaled to the length of an -- output line. local tdx,tdy=ca*lns,sa*lns -- now lets actually rotate the -- "read" lines. here's the formula. -- [cos, -sin][tx] -- [sin, cos][ty] -- because tx is not varied in -- the draw loop, we can pre- -- compute some values here. -- -trad is the "left" side -- of the read line. it's an -- x offset from the tile center. local costx=ca*-trad local sintx=sa*-trad -- visualize the output space. rect(x-prad,y-prad,x+prad,y+prad,5) -- we're going to draw a rect -- line by line, top down. -- because we're centering on -- x and y, add and subtract -- the pixel radius. for poffy=-prad,prad do -- now compute the ty values -- which is just the ty offset local costy=ca*(toffy) local sinty=sa*(toffy) tline( x-prad,y+poffy, x+prad,y+poffy, tcx+costx-sinty, tcy+sintx+costy, tdx,tdy) toffy+=tstep end end |
Here's the same code without comments if you just want to copy/paste in your project.
-- draw rotated map tile -- x,y: output will be centered here -- tx,ty: map coords to the -- top,left tile to draw -- ang: rotation angle -- scale: scale factor -- default:1 -- tw,th: tiles to read -- default:1 -- tcxo,tcyo: -- tile center offsets -- in tile units -- default:0 function rtile(x,y,tx,ty,ang, scale,tw,th,tcxo,tcyo) local scale,tw,th= scale or 1,tw or 1,th or 1 local tcxo,tcyo= tcxo or 0,tcyo or 0 scale*=8 local ca,sa=cos(ang),-sin(ang) local tcx,tcy= tx+tcxo+tw/2,ty+tcyo+th/2 local trad=max( sqrt((tx-tcx)^2+(ty-tcy)^2), sqrt((tx+tw-tcx)^2+(ty+th-tcy)^2)) local prad=scale*trad local lns=1/scale local tstep,toffy=lns,-trad local tdx,tdy=ca*lns,sa*lns local costx=ca*-trad local sintx=sa*-trad for poffy=-prad,prad do local costy=ca*(toffy) local sinty=sa*(toffy) tline( x-prad,y+poffy, x+prad,y+poffy, tcx+costx-sinty, tcy+sintx+costy, tdx,tdy) toffy+=tstep end end |
Bubble Trouble is a homage to Frozen Bubble and Puzzle Bobble.
Frozen Bubble got me though some long lectures in college so I thought it would be a perfect game for PICO-8. Now it can get me though some long meetings instead.

Features
- 20 hand-crafted levels
- Randomly generated levels
- Precision aiming
- One-Handed mode (on the pause menu)
- Bubbles
- A penguin with a bubble cannon
- Epic sprite rotation
- Music that loops (but not every 6 seconds)
- Explosions
I hope you enjoy!
![]() |
[0x0] |
Grave Matters
I'm proud to release my first PICO-8 game just in time for Halloween.


It's a short adventure platformer with spooky music inspired by Beethoven and Andrew Gold.
Enter into a graveyard full of spiders and bats.
Help a ghost find its lost gem and witch brew her potions.
If you're having trouble with the bats, select "Easy Mode" from the pause menu.
Happy Halloween!
![]() |
[8x8] |
Updates:
- Add menu option to show the game timer.
I wrote a blog post about making a generic method dispatch helper for 'foreach'.
It's more of a novelty than something really worth adding to code. It's better to just use 'for x in all(XS)' instead.
Here's the mini summary:
function callm(method, ...) local params={...} return function(o) if type(o)=="table" then local m=o[method] if type(m)=="function" then m(o,unpack(params)) end end end end function _draw() cls() foreach(drawable, callm("draw", 11)) end |
Check it out: https://kallanreed.com/2021/10/17/pico-8-generic-dispatch-with-parameters/
I'm tying to build GOL on PICO-8, but the per-pixel manipulation is just too slow. Anything more than about 48x48 is unacceptably slow.
Initially I was reading/writing into temporary tables but that was really slow. This approach keeps all the state on the screen and in a sprite so there's about a quarter of the work from the table approach, but it's still slow.
I've started another experiment with peek4/poke4 but the coordinate wrapping is a total pain.
My first attempt at a PICO-8 game.
I've never tried anything like this before, so it's been a fun learning experience.
This is my spooky side scroller hopefully wrapped up by Halloween.
Update 4)
- Adding a fade on death as well as some basic palette twiddling to make it look a little nicer.
- More music because I'm kind of tired of hearing it at this point.
- Spikes and death! Although the collision detection is going to need some major overhaul.
Update 5)
- More interactions
- Starting enemies
- More music
- More map
Update 6)
- Bats! Spiders! Spikes!
- Death!
- Pixel collision!
- If you can get the "potion", you've "won" for now!
Update 7)
- Health!
- End-game and win condition
- Spooky Scary Music!
Update 8)
- Add Easy Mode
- Calling this one ready for Halloween
Here's the "Release Post" https://www.lexaloffle.com/bbs/?tid=45134