--!name Stackfall
--!icon game
--!category Games
local az="!$%&()+,-0123456789:;<=>?ABCDEF"local aA,aC,aB,aE=7,17,29,30 local function x(b)return az:sub(b+1,b+1)end local f,s=10,20 local aR="IOTSZJL"local ad={{4,0,1,1,1,2,1,3,1},{2,0,0,1,0,0,1,1,1},{3,1,0,0,1,1,1,2,1},{3,1,0,2,0,0,1,1,1},{3,0,0,1,0,1,1,2,1},{3,0,0,0,1,1,1,2,1},{3,2,0,0,1,1,1,2,1},}local X={0,0,-1,0,1,0,0,-1,-2,0,2,0}local aD={100,300,500,800}local r={}local i,m,k,j=1,0,3,0 local p,t={},{}local n,C,z,I=0,0,1,1 local M,O,v=0,0,{}local B,l,u,D=false,false,nil,false local U=false local o,q="menu",1 local aI=1000 local function Y(A,L,c)local g=ad[A]local h,e,a=g[1],g[2*c],g[2*c+1]for ae=1,L%4 do e,a=h-1-a,e end return e,a end local function G(A,L,aP,aQ)for c=1,4 do local e,a=Y(A,L,c)e,a=aP+e,aQ+a if e<0 or e>=f or a>=s then return false end if a>=0 and r[a*f+e+1]~=0 then return false end end return true end local function aw()return math.max(450,1000-55*(z-1))end local function K()if o=="play"and D and not l then aI=snail.tick(aw())or aw()else snail.tick(0)end end local function _()if#t==0 then for b=1,7 do t[b]=b end for b=7,2,-1 do local an=math.random(b)t[b],t[an]=t[an],t[b]end end return table.remove(t)end local function H()local ay={}for b=1,f*s do ay[b]=r[b]end snail.save(table.concat({"F1",n,C,z,I,M,O,table.concat(v,","),D and 1 or 0,l and 1 or 0,i,m,k,j,table.concat(p),table.concat(t),table.concat(ay)},";"))end local function aK()local g=snail.load()if type(g)~="string"or g:sub(1,3)~="F1;"then return end local F={}for Q in(g..";"):gmatch("([^;]*);")do F[#F+1]=Q end local h=function(b)return tonumber(F[b])or 0 end n,C,z,I,M,O=h(2),h(3),math.max(1,h(4)),math.max(1,h(5)),h(6),h(7)for Q in(F[8]or""):gmatch("%d+")do v[#v+1]=tonumber(Q)end local R=F[17]or""if h(9)~=1 or#R~=f*s then return end for b=1,f*s do r[b]=R:byte(b)-48 end i,m,k,j=math.max(0,math.min(7,h(11))),h(12),h(13),h(14)for w in(F[15]or""):gmatch("%d")do p[#p+1]=tonumber(w)end for w in(F[16]or""):gmatch("%d")do t[#t+1]=tonumber(w)end while#p<3 do p[#p+1]=_()end D,l=true,h(10)==1 end local function au()U=n>M if U then M=n end v[#v+1]=n table.sort(v,function(J,R)return J>R end)while#v>5 do table.remove(v)end end local function V()i=table.remove(p,1)p[#p+1]=_()m,B=0,false k=i==2 and 4 or 3 j=i==1 and-1 or 0 if not G(i,m,k,j)then l=true au()K()end end local function ar()for b=1,f*s do r[b]=0 end p,t={},{}for b=1,3 do p[b]=_()end n,C,z=0,0,I l,u,D,U=false,nil,true,false O=O+1 o="play"V()K()H()end local function al()local d={}for a=0,s-1 do local ak=true for e=1,f do if r[a*f+e]==0 then ak=false;break end end if ak then d[#d+1]=a end end return d end local function aj()local P={}for ae,a in ipairs(u)do P[a]=true end local N=s-1 for a=s-1,0,-1 do if not P[a]then if N~=a then for e=1,f do r[N*f+e]=r[a*f+e]end end N=N-1 end end for a=N,0,-1 do for e=1,f do r[a*f+e]=0 end end u=nil V()H()end local function ao()for c=1,4 do local e,a=Y(i,m,c)e,a=k+e,j+a if a<0 then l=true au()K()H()return end r[a*f+e+1]=i end local E=al()if#E>0 then n=n+aD[#E]*z C=C+#E local ap=math.max(I,C//10+1)if ap~=z then z=ap;K()end u=E i=0 H()else V()H()end end local function ah()local a=j while G(i,m,k,a+1)do a=a+1 end return a end local function av(ai)if G(i,m,k+ai,j)then k=k+ai if B and G(i,m,k,j+1)then B=false end end end local function ax(aG)local L=(m+aG)%4 for b=1,#X,2 do local e,a=k+X[b],j+X[b+1]if G(i,L,e,a)then m,k,j=L,e,a if B and G(i,m,k,j+1)then B=false end return end end end function tick()if o~="play"or l or not D then return end if u then aj();return end if G(i,m,k,j+1)then j=j+1 B=false elseif B then ao()else B=true end end local function aq()local d={}if D and not l then d[#d+1]={"Resume","resume"}end d[#d+1]={"New game","new"}d[#d+1]={"Start at level "..I.."  (L/R)","level"}d[#d+1]={"High scores","scores"}d[#d+1]={"How to play","help"}return d end local function aM()o,q="menu",1 K()H()end function key(c)if o=="menu"then local y=aq()if q>#y then q=#y end local J=y[q][2]if c=="up"then q=(q-2)%#y+1 elseif c=="down"then q=q%#y+1 elseif J=="level"and(c=="left"or c=="right"or c=="ok")then I=(I-1+(c=="left"and-1 or 1))%10+1 H()elseif c=="ok"then if J=="resume"then o="play";K()elseif J=="new"then ar()else o=J end end return end if o~="play"then if c=="top"or c=="ok"then o="menu"end return end if c=="top"then aM();return end if l then if c=="ok"then ar()end return end if u then aj()end if l then return end if c=="left"then av(-1)elseif c=="right"then av(1)elseif c=="up"then ax(1)elseif c=="ok"then ax(-1)elseif c=="down"then local a=ah()n=n+2*(a-j)j=a ao()end end local W="....."local function Z(h)local g=tostring(h)local c=false if#g>5 then g,c=tostring(h//1000),true end local d={}for b=1,#g do d[b]=x(aA+g:byte(b)-48)end if c then d[#d+1]=x(aB)end local as=(5-#d)//2 return("."):rep(as)..table.concat(d)..("."):rep(5-as-#d)end local function T(b)local ab=aC+3*b return"."..x(ab)..x(ab+1)..x(ab+2).."."end local function aN(A,aO)local g=ad[A]local aa=9 for c=1,4 do aa=math.min(aa,g[2*c+1])end local d={".",".",".",".","."}for c=1,4 do if g[2*c+1]-aa==aO then d[g[2*c]+2]=x(A-1)end end return table.concat(d)end local function aL(a)if a==0 then return T(0)end for at=1,3 do local ac=1+(at-1)*3 if a==ac or a==ac+1 then local A=p[at]return A and aN(A,a-ac)or W end end if a==10 then return T(1)end if a==11 then return Z(n)end if a==13 then return T(2)end if a==14 then return Z(z)end if a==16 then return T(3)end if a==17 then return Z(C)end return W end local function aF()local af={}local am={}if i>0 and not l then local aH=ah()for c=1,4 do local e,a=Y(i,m,c)af[(j+a)*f+k+e+1]=true am[(aH+a)*f+k+e+1]=true end end local P={}if u then for ae,a in ipairs(u)do P[a]=true end end local E={}for a=0,s-1 do local d={"#"}for e=1,f do local b=a*f+e local w="."if P[a]then w="*"elseif af[b]then w=x(i-1)elseif r[b]~=0 then w=x(r[b]-1)elseif am[b]then w=x(aE)end d[e+1]=w end d[f+2]="#"d[f+3]=aL(a)E[a+1]=table.concat(d)end E[s+1]=("#"):rep(f+2)..W return table.concat(E,"/")end local function S(h)local g=tostring(h)while true do local d,c=g:gsub("^(%d+)(%d%d%d)","%1,%2")g=d if c==0 then return g end end end function draw()if o=="menu"then snail.title("Stackfall")local y=aq()if q>#y then q=#y end for b,aJ in ipairs(y)do snail.row(aJ[1],b==q)end snail.gap()if D and not l then snail.small("Paused: score "..S(n)..", level "..z..", "..C.." lines.")end snail.small("Best score "..S(M)..".")snail.hint("BACK leave OK choose UP/DN move")return end if o=="scores"then snail.title("High scores")if#v==0 then snail.text("No games finished yet.")end for b,Q in ipairs(v)do snail.text(b..".  "..S(Q))end snail.gap()snail.small("Games played: "..O..".")snail.hint("BACK back OK back")return end if o=="help"then snail.title("How to play")snail.text("Pieces fall one row at a time. Fill a whole row to clear it.")snail.text("LEFT and RIGHT move. UP turns clockwise, OK spins the other way.")snail.text("DOWN drops the piece straight onto its shadow.")snail.text("A piece that lands by itself waits a tick: slide it under a ledge.")snail.text("1-4 rows at once: 100, 300, 500, 800 points times the level.")snail.text("Every 10 rows the level goes up and pieces fall faster.")snail.text("BACK pauses the game. The panel shows the next three pieces.")snail.hint("BACK back OK back")return end snail.title("Stackfall")local ag if l then ag=(U and"New best! "or"Game over - ")..S(n)end snail.board(aF(),nil,ag)if l then snail.hint("BACK options OK play again")else snail.hint("BACK pause OK spin L/R move UP turn DN drop")end end function start()math.randomseed(os.time())snail.ink("fast")for b=1,f*s do r[b]=0 end aK()if D and not l and i==0 then u=al()if#u==0 then u=nil;V()end end o,q="menu",1 snail.tick(0)end
