Skip to content

Instantly share code, notes, and snippets.

@rostok
Created September 21, 2025 17:31
Show Gist options
  • Select an option

  • Save rostok/30458a36214f57528d453509809d557d to your computer and use it in GitHub Desktop.

Select an option

Save rostok/30458a36214f57528d453509809d557d to your computer and use it in GitHub Desktop.
Caverns of the Souleater
pico-8 cartridge // http://www.pico-8.com
version 43
__lua__
-- "Caverns of the Souleater"
-- Father told you these caverns hold a great treasure. But all you've found are endless tunnels and venomous snakes. As your torch burns low, you wonder if the gold was ever real. And why he didn't share this with your brother?!
-- compress code into rom with v1.2.5c shrinko8 --input-count -c -m -oc -f tiny-rom caves-20.p8 caves-20.rom
-- RENDERMAP should output grid that creates a full map, use image magick to stich it
-- magick map-*.png -transpose miff:- | magick montage - -tile 5x9 -geometry +0+0 miff:- | magick - -transpose miff:- | magick - caves-map.png
-- RENDERMAP=true if(RENDERMAP)map_min_x,map_min_y,map_max_x,map_max_x=0,0,0,0
local L,O,S,T,H,F,X,Y,u,v,k,c,a,m,o,x,y={},{},{},1,1,1
-- L lookup table with ellipse filled coordinates, moving outwards and then counterclockwise, used for wall detection and shadows
-- O table of walkable ovals
-- S table of snakes, with their position, last known hero/heading position, and subtable of tail positions
-- T torch state 1 on, -1 off
-- F torch fuel
-- H health/game state 1 hero alive, 0 hero dead, 2 hero found exit
-- X,Y hero position
-- u,v hero walk direction
-- rest are temp vars, using local for speedup
srand()
for j=0,15 do -- create the cave layout
x,y=cos(j/16)<<9,sin(j/16)<<8 -- start by creating 16 endpoints and move towards center turning occasionally, keey 1/8 of angle to match hero movement vector
a=j\2/8+.5 -- randomize the float position
for i=0,31 do -- make ice float path
o=add(O,{x=x,y=y,r=rnd(16)+8})
m=rnd()
if(rnd()<.15)add(S,{x=x,y=y+m*4,r=12*m,u=x,v=y,a=x,p={}}) -- occasionally create snakes
-- if(RENDERMAP)map_min_x,map_min_y,map_max_x,map_max_y=min(map_min_x,o.x)\1,min(map_min_y,o.y)\1,max(map_max_x,o.x+64)\1,max(map_max_y,o.y+64)\1
if(i>8)a+=rnd{.25,0,0,0,0,-.25}/2 -- twist and turn
r=rnd(8)+o.r -- set step
-- x+=cos(a)*r -- move to the next position
-- y+=sin(a)*r/2
x,y=cos(a)*r+x,y+sin(a)*r/2 -- move to the next position
end
end
X,Y=o.x,o.y -- set hero position to last walkable oval
-- create lookup table for shadows and wall finding
for a=0,255 do
for r=0,40,.1 do
x,y=cos(a>>8)*r\1,sin(a>>8)*r\2
if(sget(64+x,y+64)<1)add(L,{x=x,y=y,r=r*r>>8}) -- keep and store in spritesheet as it is cleared on reboot and we wont have fractional duplicatas in lookup
sset(64+x,y+64,1)
sset(a/2,r,a/255*r/4*(1-r\29/5)+rnd()) -- create a randomized gradinet for wall drawing, bottom is extra darkened to make a distinctive line where wall meets floor
end
end
while 1 do -- while compresses better than label/goto
?"\^1\^c0\^!5f110β–ˆπŸ±β–‘49:77" -- flip() cls(1) and set palette
u=@24396\2%2*2-@24396%2*2
v=@24396\8%2-@24396\4%2 -- set u,v movement vector
if(btnp(4))T=-T
-- if(RENDERMAP) do M,N=X,Y for P=map_min_x,map_max_x,128 do for Q=map_min_y,map_max_y,128 do X,Y=P,Q T=-1 ?"\^c0\^!5f11β–ˆ"
-- if(RENDERMAP and P==map_min_x and Q==map_min_y)camera()?"map_min_x\t"..map_min_x.."\n".."map_max_x\t"..map_max_x.."\n".."map_min_y\t"..map_min_y.."\n".."map_max_y\t"..map_max_y.."\n".."width\t\t"..(map_max_x-map_min_x).."\n".."height\t\t"..(map_max_y-map_min_y),15
-- render
camera(X-63,Y-63)
-- draw floor, no filtering needed as it gives no real speed gain
for o in all(O) do
ovalfill(o.x-o.r,o.y-o.r/2,o.x+o.r,o.y+o.r/2,1)
end
-- srand(t())
-- move the hero and check for collisions, if wall (black pixel) is found turning the vector left or right to force movement
x,y=u+X,Y+v
if(pget(x,y)==1)X,Y=x,y
if(pget(x,y)<1)do m=atan2(u,v)+rnd{.25,-.25}x,y=cos(m)+X,Y+sin(m)if(pget(x,y)==1)X,Y=x,y end
-- update snakes logic
for o in all(S) do
x,y=o.x,o.y
o.s=.9
k=abs(X-x)+abs(Y-y)*2 -- diamod distance shape, lower byte count
if(k<6)H&=2 -- kill the hero if too close to snake
if(k<26)o.s-=.2
if(k<64) do -- update only if whithin hero's screen radius
if(T>0)o.u,o.v=X,Y o.s-=.2 -- if light save hero's known position
a=atan2(o.u-x,o.v-y) -- point at known position
if(T>0 and k<26)a+=.5 -- turn back if too close to the light
if(T>0 and k>26 and k<26+o.r)a+=rnd{.25,-.25} -- circle around the light
a+=sin(t()*64)/8 -- wiggle the snake
if(pget(x,y)<1)a+=.5 -- if snake hits the solid reverse
o.a+=(((a-o.a+.5)%1)-.5)/4 -- adjust snake angle gradually
x+=cos(o.a)
y+=sin(o.a)/2
if(pget(x,y)==1)o.x,o.y=x,y add(o.p,{x=x,y=y},1) deli(o.p,24) -- if head can move then update position and body segments
end
end
-- render floor with shadows and find walls
srand(t()\.2)
W={}
i=1
while i<=#L*T do -- iterate over lookup, inner is radius, outer is angle
o=L[i]
i+=1
x,y=o.x+X,Y+o.y
c=max(6-o.r)*F+rnd(.6) -- torch fuel affects lightning
k=pget(x,y)
if(k==1)pset(x,y,1+c) -- 1 is walkable ground
if(k<1) while i<=#L and L[i].r>o.r do i+=1 end -- if we hit wall then this ray is discarded and we move to next one in lookup table
if(k==1 and pget(x,y-2)<1)add(W,{x=x,y=y,i=c}) -- if this is a floor but above is wall then add position to wall rendering, i field determines randomized gradient
end
-- render snakes
for o in all(S) do
for o in all(o.p) do pset(o.x,o.y,0)end
end
-- render walls using 1x32 sprite lines
for o in all(W) do
sspr(o.i*19,0,1,32,o.x,o.y-32)
end
-- if(RENDERMAP) X,Y=M,N ?"\^o0ffexit",-200-3,-260-6,10
-- render exit
ovalfill(-199,-260,-194,-247,9)
-- check if hero reached exit
if(pget(X,Y-8)==9)H=2
-- if(RENDERMAP) X,Y=M,N ?"\^o0ffstart\n\n .",o.x-9,o.y-16,10
-- if(RENDERMAP) X,Y=M,N ?"\^o0ffstart\n\n .",X-9,Y-16,10
-- render legs
for i=-1,1,2 do
line(i+X,Y-4,i+i*sin(t()*3)*u+X,Y+i*sin(t()*3)*v*2,0)end
?"\^.@H(、、、、、",X-3,Y-12
-- render torch
if(T>0)pset(X+3,Y-14,8)pset(X+3-u/2,Y-14-t()*3%2)F-=.0003--?F\1,-u+X-63,Y-63-v,5
-- no fuel no light
if(F<0)T=0
-- render snake hisses overlay
for o in all(S) do
if(sin(t()/4+o.r)>o.s)?"\^o0ffsSS",o.x,o.y-10,8
-- if(RENDERMAP)?"\^o0ffsnake\n .",o.x-9,o.y-10,14
end
-- if(RENDERMAP) h_blocks=(map_max_x-map_min_x)\128+1 v_blocks=(map_max_y-map_min_y)\128+1 x=(P-map_min_x)\128 y=(Q-map_min_y)\128 extcmd('set_filename','map/map-'.. x..'-'..y..'--'..h_blocks..'x'..v_blocks) extcmd('screen')
-- end end end if(RENDERMAP) RENDERMAP=false
-- based on state render ending
camera()
if(H<1)?"\^c0soul lost"
if(H>1)?"\^c0saved"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment