Created
September 21, 2025 17:31
-
-
Save rostok/30458a36214f57528d453509809d557d to your computer and use it in GitHub Desktop.
Caverns of the Souleater
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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