--!name Crate Pusher
--!icon puzzle
--!category Puzzles
local ak="7#|#@-$-.#|7#;7#|#.4-#|#--$--#|#3-@-#|7#;8#|#--.3-#|#-$$-@-#|#--.3-#|8#;-6#|-#--.-#|-#-$#-#|-#--$@#|-#.3-#|-6#;--5#|3#3-#|#.-$#-#|#--$@-#|#.--3#|5#;6#|#+3-##|#-##$-#|#-#3-#|#5-#|#4-##|6#;-5#|-#@--#|-#-$-#|##-5#|#--#3-#|#6-#|#4-.-#|8#;-5#|-#+--3#|##5-#|#--##$-#|#3-#--#|#3-4#|5#;8#|#--#@--#|#--#3-#|#3-$.##|#--#.-#|##-#$##|-#3-#|-5#;--4#|--#@-3#|--#.-$-#|3#-#--#|#-$3-##|#.5-#|3#4-#|--6#;-7#|-#@4-#|##-##$-#|#--#-$-#|#--#3-#|#-3#--#|#.4-.#|8#;--6#|-##-.--#|##4-.#|#@$#--##|#-$3-#|3#3-#|--5#;-7#|##@4-#|#.3-#-#|#-#3-.#|#-$-$--#|5#--#|4-4#;8#|#3-#@-#|#-#-#-.#|#-#-##.#|#--$3-#|#-$-#--#|##4-##|-6#;8#|#6-#|#--##$-#|##--#@##|#3-#$-#|#.5-#|#.5-#|8#;--6#|-##@.--#|##-$3-#|#-$-#--#|#3-#.-#|#3-4#|5#;7#|#+4-#|#4-$##|#.##3-#|#-$4-#|3#--3#|--4#;8#|#@3-.-#|#6-#|#--#-.-#|#--3#-#|##-$-$-#|-#4-##|-6#;7#|#.4-3#|#-#--$--#|#..-#3-#|#$-3#--#|#-$@--3#|##4-#|-6#;8#|#+--#--#|#3-#$-#|#.-##--#|#6-#|##--$#-#|-#--.$-#|-7#;-8#|-#--#--.#|##$4-.#|#@-##--.#|#-$--$-##|3#4-#|--6#;--7#|--#@3-.#|3#$#-$-#|#-$-#-#-#|#3-#-#.#|##6-#|-#.-#--##|-7#;8#|#@.#.--#|#4-#-#|#--#3-#|#.--#$-#|##-$-$-#|-#5-#|-7#;8#|#--##+-#|#-$##--#|#3-#.-##|#3-$--.#|3#$4-#|--#--#--#|--7#;--5#|3#-.-#|#5-3#|#-$--#--#|##$3-.-#|-#@$#3-#|-#--#--.#|-8#;-5#|-#@--#|-#--$##|##-.--##|#3-#*-#|#3-$-.#|#3-#--#|8#;-8#|-#--##@-#|##--##--#|#--$#3-#|#3-$*-.#|##--#--##|-#--#-.#|-7#;-7#|-#3-*-##|-#--$.$-#|##-#@#.-#|#--#$-.-#|#--#3-##|#--#3-#|8#;8#|#@5-#|#--.-$$##|#-#-#*--#|#.--#3-#|#3-##--#|##--#-$##|-#-.3-#|-7#;8#|#-.$@--##|#--$3-.#|3#--##.#|#--*-#--#|#--$4-#|3#5-#|--7#;8#|#.5-##|#-#-3$@#|#-##--$.#|#-##-4#|#-#3-#|#--.-.#|7#;5#|#3-3#|#3-.-3#|#.--.-$@#|#--3#$-#|#-.##3-#|##-#-$$-#|-#5-##|-7#;10#|#--3#3-#|#-.3#-#-#|#.3-$3-#|#3-$.4#|3#*$-#|--#@-##|--4#;--8#|--#3-.--#|3#-3#-.#|#3-#3-.#|#-$#-$3-#|#-$@$-#.-#|#--##4-#|10#;-7#|##@4-#|#.$#3-3#|#--4#--#|#--#-$3-#|#-*-.--$-#|#--##.--##|9#;--7#|--#@4-#|-##3$--#|##.3-3#|#--$-#--#|#.--##-.#|#3-.3-#|3#5-#|--7#;8#|#--.3-#|#.#.#-$##|#3-##@-#|#3-#-$-#|#5-$.#|#--3#$##|##5-#|-7#;-9#|-#@--#3-#|##-.5-#|#--##$$-.#|#--$--#$.#|#-$-#4-#|3#-#-#.-#|#5-#--#|#5-#.-#|10#;-9#|##@-##-.-#|#-$-$4-#|#-3#-$-##|#-#--#--##|#-#$-##$##|#4-#-.-#|#-.--#3-#|3#-..3-#|--8#;3-6#|--##@3-#|3#.--#-3#|#.-$3#-$-#|#-.$-$4-#|#--#--##--#|4#-$#3-#|3-##-.-.-#|4-7#;10#|#@4-#--#|#-##$-#--#|#.##-3#.#|#-##-3#-#|#-$3-#--#|#.3$4-#|##3-#.--#|-##5-.#|--8#;8#|#@-##--#|#-.3-.#|#-.7#|#--##4-#|#--$.4-#|#--#-$#$-#|##-#$$--##|-#3-#-.#|-8#;--7#|--#@4-#|3#$$-$-3#|#3-3#-*-#|#4-$-##-#|#--#-#-##.#|#-.--.4-#|#4-.4-#|11#;5#|#@-.4#|#5-.#|##--##-3#|#3-#4-#|#3-#$#-.#|#4-$3-#|##-##3$-#|-#-.--.3#|-7#;10#|#-$-.4-#|#--##--$-#|#--##--3#|#--3#$@##|#-3#-$--#|#*#3-#--#|#3.5-#|10#"local aj,al="WFGBCPQ","wfgbcpq"local T={u={0,-1},d={0,1},l={-1,0},r={1,0}}local Y={up="u",down="d",left="l",right="r"}local L={}for r in ak:gmatch("[^;]+")do L[#L+1]=r end local j=#L local g,x=0,0 local S,t,s,v={},{},{},{}local p,N,n=0,0,0 local y,c,C={},0,0 local h,w=1,false local l={}local i,m,k="menu",1,1 local z,I=nil,nil for f=1,j do l[f]=0 end local function at(r)local q={}for D in r:gmatch("[^|]+")do local a={}for e,d in D:gmatch("(%d*)(%D)")do a[#a+1]=d:rep(tonumber(e)or 1)end q[#q+1]=table.concat(a)end return q end local function V(e)h=e local q=at(L[e])x,g=#q,0 for am,D in ipairs(q)do if#D>g then g=#D end end S,t,s,v={},{},{},{}N,n=0,0 for K=1,x do local D=q[K]for F=1,g do local d=D:sub(F,F)local f=(K-1)*g+F S[f]=d=="#"if d=="."or d=="*"or d=="+"then t[f]=true;N=N+1 end if d=="$"or d=="*"then s[f]=true end if d=="*"then n=n+1 end if d=="@"or d=="+"then p=f end end end local Q={p}v[p]=true while#Q>0 do local f=table.remove(Q)for am,o in pairs(T)do local B=f+o[2]*g+o[1]if B>=1 and B<=g*x and not v[B]and not S[B]then v[B]=true Q[#Q+1]=B end end end y,c,C,w,z={},0,0,false,nil end local function ag(d)local o=T[d]local G=o[2]*g+o[1]local a=p+G if not v[a]then return false end if s[a]then local R=a+G if not v[R]or s[R]then return false end s[a],s[R]=nil,true if t[a]then n=n-1 end if t[R]then n=n+1 end C=C+1 d=d:upper()end p=a c=c+1 y[c]=d return true end local function ai()if c==0 then return false end local d=y[c]y[c]=nil c=c-1 local o=T[d:lower()]local G=o[2]*g+o[1]if d~=d:lower()then local M=p+G s[M],s[p]=nil,true if t[M]then n=n-1 end if t[p]then n=n+1 end C=C-1 end p=p-G return true end local function U(e)if e<=3 or l[e]>0 then return true end for b=e-3,e-1 do if l[b]>0 then return true end end return false end local function af()local r=0 for f=1,j do if l[f]>0 then r=r+1 end end return r end local function E()local Z=""if i~="levels"and not w and c>0 and c<=760 then Z=table.concat(y)end snail.save(table.concat({"CP1",h,table.concat(l,","),Z},"|"))end local function ar()local r=snail.load()if type(r)~="string"or r:sub(1,4)~="CP1|"then return false end local H={}for o in(r.."|"):gmatch("([^|]*)|")do H[#H+1]=o end local b=0 for o in(H[3]or""):gmatch("%d+")do b=b+1 if b<=j then l[b]=tonumber(o)end end h=math.max(1,math.min(j,tonumber(H[2])or 1))V(h)for d in(H[4]or""):gmatch("[udlrUDLR]")do ag(d:lower())end return c>0 end local function ac()for b=1,j do local e=(h+b-1)%j+1 if l[e]==0 and U(e)then return e end end return h%j+1 end local function as(d)if not ag(d)then return end if n==N then w=true local J=l[h]if J==0 or c<J then l[h]=c z=J==0 and("Solved in "..c.." moves!")or("New best: "..c.." moves (was "..J..")")else z="Solved in "..c.." moves (best "..J..")"end E()elseif y[c]~=y[c]:lower()or c%8==0 then E()end end local function P(e)V(e)i="play"E()end local function aa()local a={}if w then a[#a+1]={"Next level","next"}a[#a+1]={"Play level "..h.." again","restart"}else a[#a+1]={(c>0 and"Back to level "or"Play level ")..h,"resume"}if c>0 then a[#a+1]={"Take back a move","undo"}a[#a+1]={"Restart level "..h,"restart"}end end a[#a+1]={"Choose a level","levels"}a[#a+1]={"How to play","help"}return a end function key(b)if i=="menu"then local u=aa()if m>#u then m=#u end if b=="up"then m=(m-2)%#u+1 elseif b=="down"then m=m%#u+1 elseif b=="ok"then local A=u[m][2]if A=="resume"then i="play"elseif A=="undo"then ai();i="play";E()elseif A=="restart"then P(h)elseif A=="next"then P(ac())elseif A=="levels"then i,k="levels",h else i=A end end return end if i=="levels"then I=nil if b=="top"then i="menu"elseif b=="up"then k=(k-2)%j+1 elseif b=="down"then k=k%j+1 elseif b=="left"then k=math.max(1,k-10)elseif b=="right"then k=math.min(j,k+10)elseif b=="ok"then if U(k)then P(k)else I="Solve one of the three before it first"end end return end if i=="help"then if b=="top"or b=="ok"then i="menu"end return end if b=="top"then i,m="menu",1 E()return end if w then if b=="ok"then P(ac())end return end z=nil if b=="ok"then if ai()then E()else z="Nothing to take back"end elseif Y[b]then as(Y[b])end end local function an()local ae,O=aj,16 if g<=8 and x<=9 then O=8 elseif g<=10 and x<=12 then ae,O=al,10 end local _=(O-g)//2 local q={}for K=1,x do local a={("."):rep(_)}for F=1,g do local f=(K-1)*g+F local b if S[f]then local ab=false for ap=-1,1 do for ao=-1,1 do local W,X=F+ao,K+ap if W>=1 and W<=g and X>=1 and X<=x and v[(X-1)*g+W]then ab=true end end end b=ab and 1 or nil elseif v[f]then if f==p then b=t[f]and 7 or 6 elseif s[f]then b=t[f]and 5 or 4 else b=t[f]and 3 or 2 end end a[#a+1]=b and ae:sub(b,b)or"."end a[#a+1]=("."):rep(O-g-_)q[#q+1]=table.concat(a)end return table.concat(q,"/")end function draw()if i=="menu"then snail.title("Crate Pusher")local u=aa()if m>#u then m=#u end for f,aq in ipairs(u)do snail.row(aq[1],f==m)end snail.gap()snail.small(af().." of "..j.." levels solved.")snail.hint("BACK leave OK choose UP/DN move")return end if i=="levels"then snail.title("Choose a level")if I then snail.status(I)end local ad=I and 12 or 13 local ah=math.max(1,math.min(k-6,j-ad+1))for e=ah,math.min(j,ah+ad-1)do local a="Level "..e if l[e]>0 then a=a.."  -  solved, best "..l[e].." moves"elseif not U(e)then a=a.."  (locked)"elseif e==h and c>0 then a=a.."  -  in progress"end snail.row(a,e==k)end snail.small(af().." of "..j.." solved. Solving one opens the next three.")snail.hint("BACK back OK play L/R jump 10 UP/DN move")return end if i=="help"then snail.title("How to play")snail.text("Push every crate onto a target (the rings). Walk with the arrows.")snail.text("You can push one crate at a time, and never pull one.")snail.text("A crate in a corner is stuck for good: OK takes back moves, as many as you like.")snail.text("Black crates are on a target. BACK has restart and the level list.")snail.text("Fewer moves is better: each level remembers your best.")snail.hint("BACK back OK back")return end snail.title("Crate Pusher - Level "..h.." of "..j)if w then snail.status(z or("Solved in "..c.." moves"))else local M=l[h]>0 and("  Best "..l[h])or""snail.status(z or("Moves "..c.."  Pushes "..C..M))end snail.board(an(),nil,w and"Solved!"or nil)if w then snail.hint("BACK options OK next level")else snail.hint("BACK options OK undo L/R move UP/DN move")end end function start()if ar()then i="play"elseif g==0 then V(1)end m,k=1,h end
