--!name Nonograms
--!icon puzzle
--!category Puzzles
local aM="!$%&()+,-0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnpqrstuvwxyz|~"local function D(b)return aM:sub(b+1,b+1)end local at,aO,au,aQ,aL,aP,aN,aV=16,20,24,25,28,31,34,35 local P={5,10,15}local as={"h+lm0zw4808xv708wf200sxG0v-l+01o-G0TNv70Z+lt08xwd08xxY08pWFW8vto0TyX60L-xY0CKdE09zoeW","G9Xd+tkjTxSv-bwBG30U3yFm-7+Vx-m030007W-hEhKfRDtl-007X-ktpF-o48GX3y003yFmX248GXETxtlCOG9Xd+--U--yzpZy7W20C0u7m-7y23-t+Fm30I0mFmC0m37Ct+7W7W-7+RPpZy7W01u30GAXK2BH-l-VwXQ50000E3zT----Vu-Gu0030U3yVx-q2MfObYVu9182G03+Fw-d+dm-y30U184WU1uVnjao307W-7+---t+FmC0mFm70+3S-0-7z-z-Zy001087S-x----z-d+Cm30-F+-+QGW2084WC0FWu72uVWk0u1mZy7W","e2d0Swhd-q-+7ku-+7-my7d0Su3d--000000000G0701K0Vm7-1-yV-p-+003-+BMXQq5j0-u0000G0300y0Fm3+0-mF+0200G7--V-n-y7-000000821WmEE1-mV-3EOV-3xuFU0-WF+J-wV-Z-uDs01y0-uDzZlkTzplkDzW-u3+08W140FW1y0FW000O0tWF+3-w-xk-w-VNnwy7N1wyVNprUDLW200000000e020-WV-7Vz--l-+V-nWE73WVu1+0000000m0F07+1-ul-TsvTxhlTEtH-w7-0Vm7-0000006C0nW6C0nW6C1-uV-ZViV-ZvyFF0-mF-3-ySpW0G0701y0DW140DW1y0FW3+0tOExXdC0e0Q02I00u0FW1y1FWTyZlkTzp-kFzmF-1-mFW1y0-u3+00001y0Vm772tQcZKqvdRCSnnyF00--F-x--0000G070nKPginyOVm1y08W140Vm3+0GG630-u--u1y0-uE-Zx+Vjt-t--x-+FNWIG1K0700u0FW7-0000-0F+3-uT-Z-yU-Zd-S-pbwSkJbICgGcs0001y0Vm7N0tO7N1uyC1ZWEO0p06O0nWC7-1mS000O0pWEF-Z-+uu+23oIU23uuxw+Vln-yF-W-u630"}local T={}for a in("Crown|Flag|Pine tree|Arrow|Fish|Mug|Key|Mushroom|Cat|House|Diamond|Sailboat|Bell|Umbrella|Heart|Note|Star|Owl|Bell|Snail|Mushroom|Notes|Cat|Sailboat|Anchor|Light bulb|Crab|Fish|House|Coffee|Rocket|Tree|Duck|Apple|Umbrella|Moon|Castle|Cupcake|Ship|Cat|Balloon|Butterfly|Whale|Teapot|Rabbit|Rocket|Cactus|Snail|Lighthouse|Tree|Elephant|Penguin|Owl"):gmatch("[^|]+")do T[#T+1]=a end local av={"Small","Medium","Large"}local aS={3,5,5}local aU={0,36,0}local aT={1,1,1}local ad="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-"local V={}for b=1,64 do V[ad:byte(b)]=b-1 end local x,F,H={},{},0 for e=1,3 do local j=P[e]x[e]=#as[e]//((j*j+5)//6)F[e]=H H=H+x[e]end local function aG(e,l)local j=P[e]local ah=(j*j+5)//6 local bh=as[e]:sub((l-1)*ah+1,l*ah)local d,g={},0 for b=1,ah do local h=V[bh:byte(b)]or 0 for aW=5,0,-1 do g=g+1 if g<=j*j then d[g]=h>>aW&1 end end end return d end local function aq(i)for e=3,1,-1 do if i>F[e]then return e,i-F[e]end end end local f,K,u,O=5,3,1,1 local C,n,_,W={},{},{},{}local c local z={}for b=1,H do z[b]=0 end local t={}local E,r,X=false,nil,false local G={}local q=1 local o,w,ax="menu",1,"menu"local s,y=1,1 local J,Y=nil,nil local function S(a)a=math.floor(a or 0)if a>=3600 then return string.format("%d:%02d:%02d",a//3600,a//60%60,a%60)end return string.format("%d:%02d",a//60,a%60)end local function U(e)return P[e].."x"..P[e]end local function ai(p,a,Q,h)local d,k={},0 for b=0,f-1 do if p[a+b*Q]==h then k=k+1 elseif k>0 then d[#d+1]=k;k=0 end end if k>0 then d[#d+1]=k end return d end local function aj(e,l)u,O=e,l f,K=P[e],aS[e]C,n,_,W=aG(e,l),{},{},{}for b=1,f*f do n[b]=0 end for k=0,f-1 do _[k+1]=ai(C,k*f+1,1,1)end for m=0,f-1 do W[m+1]=ai(C,m+1,f,1)end c={id=F[e]+l,el=0,hints=0,done=false}q,G,r,X,J=1,{},nil,false,nil end local function be()local d={}for b=1,f*f,3 do local h=(n[b]<<4)|((n[b+1]or 0)<<2)|(n[b+2]or 0)d[#d+1]=ad:sub(h+1,h+1)end return table.concat(d)end local function aK(a,j)local d={}for b=1,j*j do local h=V[a:byte((b+2)//3)]or 0 local ac=h>>(4-2*((b-1)%3))&3 d[b]=ac<=2 and ac or 0 end return d end local function al(i)for aw,v in ipairs(t)do if v.id==i then return v end end end local function aH(v)local i,a3,a6,m,a=v.s:match("^(%d+),(%d+),(%d+),(%d+),(.*)$")local e,l=aq(tonumber(i))aj(e,l)c.el,c.hints=tonumber(a3),tonumber(a6)q=math.max(1,math.min(f*f,tonumber(m)))n=aK(a,f)return true end local function a4(h)h=math.max(0,math.min(262143,math.floor(h)))local d={}for b=2,0,-1 do local Q=h>>(6*b)&63 d[#d+1]=ad:sub(Q+1,Q+1)end return table.concat(d)end local function bk()for b=1,f*f do if n[b]~=0 then return true end end end local function N()if c then for b=#t,1,-1 do if t[b].id==c.id then table.remove(t,b)end end if not c.done and bk()then table.insert(t,1,{id=c.id,s=table.concat({c.id,c.el,c.hints,q,be()},",")})end end while#t>8 do table.remove(t)end local d={}for i=1,H do d[i]=a4(z[i])end local a7="NG1|"..table.concat(d).."|"..(E and 1 or 0)local a repeat local af={a7}for aw,v in ipairs(t)do af[#af+1]=v.s end a=table.concat(af,"|")if#a>1020 then table.remove(t)end until#a<=1020 snail.save(a)end local function bb()local a=snail.load()if type(a)~="string"or a:sub(1,4)~="NG1|"then return end local L={}for ac in(a.."|"):gmatch("([^|]*)|")do L[#L+1]=ac end local bj=L[2]or""for i=1,H do local h=0 for g=i*3-2,i*3 do h=(h<<6)|(V[bj:byte(g)or 48]or 0)end z[i]=h end E=L[3]=="1"for b=4,#L do local i=tonumber(L[b]:match("^(%d+),"))if i and i>=1 and i<=H then t[#t+1]={id=i,s=L[b]}end end end local function aC(a,Q,ay)local d=ai(n,a,Q,1)if#d~=#ay then return false end for b=1,#d do if d[b]~=ay[b]then return false end end return true end local function ar(b)return(n[b]==1 and C[b]==0)or(n[b]==2 and C[b]==1)end local function bc()local j=0 for b=1,f*f do if ar(b)then j=j+1 end end return j end local function am(b,h)if n[b]==h then return end G[#G+1]=b*4+n[b]if#G>200 then table.remove(G,1)end n[b]=h end local function aB()for b=1,f*f do if(n[b]==1 and 1 or 0)~=C[b]then return end end c.done,r=true,nil local d=z[c.id]if d==0 or c.el<d then z[c.id]=math.max(1,c.el)end end local function R(e)local j=0 for g=1,x[e]do if z[F[e]+g]>0 then j=j+1 end end return j end local function Z(e,a5)for g=1,x[e]do local l=(a5+g-1)%x[e]+1 if z[F[e]+l]==0 then return l end end end local function a8()for g=0,f*f-1 do local b=(q-1+g)%(f*f)+1 if ar(b)or(C[b]==1 and n[b]~=1)then am(b,C[b]==1 and 1 or 2)q=b c.hints=c.hints+1 J=C[b]==1 and"That square is filled"or"That square is empty"aB()return end end end local function M()o,Y="play",os.time()end local function an(e,l)if c then N()end c=nil local v=al(F[e]+l)if v and not pcall(aH,v)then c=nil end if not c then aj(e,l)end M()end local function aa(e)s=e y=(c and u==e and O)or Z(e,0)or 1 o="list"end local function a_()local aE=os.time()if c and o=="play"and Y and not c.done then local aA=aE-Y if aA>0 then c.el=c.el+math.min(aA,600)end end Y=aE end local function ag()return U(u).." #"..O end local function aD()local d={}if c and not c.done then d[1]={"Continue: "..ag().." ("..S(c.el)..")","cont"}elseif t[1]then local e,l=aq(t[1].id)d[1]={"Continue: "..U(e).." #"..l,"resume"}end for e=1,3 do d[#d+1]={av[e].." "..U(e)..": "..R(e).." of "..x[e].." solved",e}end d[#d+1]={"Drag painting: "..(E and"on"or"off"),"drag"}d[#d+1]={"How to play","help"}return d end local function aF()return{{"Back to the puzzle","cont"},{#G>0 and("Undo ("..#G..(#G==1 and" change)"or" changes)"))or"Undo (nothing to undo)","undo"},{"Check for mistakes","check"},{"Hint: fix a square","hint"},{"Drag painting: "..(E and"on"or"off"),"drag"},{"Restart this puzzle","restart"},{"Puzzle list","list"},{"Main menu","menu"},{"How to play","help"},}end local function aZ(p)if p=="cont"then M()elseif p=="resume"then local e,l=aq(t[1].id)an(e,l)elseif p=="undo"then local ab=table.remove(G)if ab then n[ab//4],q=ab%4,ab//4 M()N()else J="Nothing to undo"end elseif p=="check"then M()local j=bc()X=j>0 J=j==0 and"No mistakes so far"or(j==1 and"1 mistake, marked"or j.." mistakes, marked")elseif p=="hint"then M()if not c.done then a8();N()end elseif p=="drag"then E,r=not E,nil N()elseif p=="restart"then aj(u,O)M()N()elseif p=="list"then aa(u)elseif p=="menu"then o,w="menu",1 elseif type(p)=="number"then aa(p)else ax,o=o,p end end local function bd(g)local k,m=(q-1)//f,(q-1)%f if g=="up"then k=k>0 and k-1 or(r and 0 or f-1)elseif g=="down"then k=k<f-1 and k+1 or(r and k or 0)elseif g=="left"then m=m>0 and m-1 or(r and 0 or f-1)elseif g=="right"then m=m<f-1 and m+1 or(r and m or 0)end q=k*f+m+1 end local function bg(g)if c.done then if g=="top"then aa(u)elseif g=="ok"then local l=Z(u,O)if l then an(u,l)else aa(u)end end return end if g=="top"then r=nil o,w="opts",1 N()return end if g=="ok"then if E and r then r=nil return end local h=(n[q]+1)%3 am(q,h)if E then r=h end else bd(g)if r then am(q,r)else return end end aB()N()end local function ba(g)if g=="up"then y=(y-2)%x[s]+1 elseif g=="down"then y=y%x[s]+1 elseif g=="left"or g=="right"then s=(s+(g=="left"and 1 or 0))%3+1 y=Z(s,0)or 1 elseif g=="ok"then an(s,y)elseif g=="top"then o,w="menu",1 end end function key(g)a_()J,X=nil,false if o=="play"and c then bg(g)elseif o=="list"then ba(g)elseif o=="menu"or o=="opts"then local I=o=="menu"and aD()or aF()if g=="up"then w=(w-2)%#I+1 elseif g=="down"then w=w%#I+1 elseif g=="ok"then aZ(I[math.min(w,#I)][2])end elseif g=="top"or g=="ok"then o=ax end end local function az(ae,d,A)local h if#ae==0 then if d==K then h=0 end else h=ae[#ae-(K-d)]end return h and D(A+h)or"."end local function aX()local A=aU[u]local B={}local a0,aY=(q-1)//f,(q-1)%f for d=1,K do local a={string.rep(".",K)}for m=1,f do a[#a+1]=az(W[m],d,A)end a[#a+1]="."B[d]=table.concat(a)end for k=0,f-1 do local a={}for d=1,K do a[d]=az(_[k+1],d,A)end for m=0,f-1 do local b=k*f+m+1 local h=n[b]if X and ar(b)then a[#a+1]="@"elseif b==q then a[#a+1]=D(A+aQ+h)elseif h==1 then a[#a+1]=D(A+au)else a[#a+1]=D(A+(h==2 and aO or at)+(m%5==0 and 1 or 0)+(k%5==0 and 2 or 0))end end a[#a+1]=D(A+aL+((k==a0 and 2)or(aC(k*f+1,1,_[k+1])and 1)or 0))B[#B+1]=table.concat(a)end local a={string.rep(".",K)}for m=0,f-1 do a[#a+1]=D(A+aP+((m==aY and 2)or(aC(m+1,f,W[m+1])and 1)or 0))end a[#a+1]=D(A+aN)B[#B+1]=table.concat(a)return table.concat(B,"/")end local function ak(j,p,bi,bf)local B={}local aI=string.rep(".",bf)for k=0,j-1 do local a={aI}for m=0,j-1 do local h=p[k*j+m+1]if bi then a[#a+1]=h==1 and D(aV)or"."else a[#a+1]=h==1 and D(au)or D(at+(m%5==0 and 1 or 0)+(k%5==0 and 2 or 0))end end a[#a+1]=aI B[#B+1]=table.concat(a)end return table.concat(B,"/")end local function a2()snail.title("Nonograms - "..ag())if c.done then local i=c.id snail.status((T[i].."! Solved in "..S(c.el)..(c.hints>0 and(", "..c.hints..(c.hints==1 and" hint"or" hints"))or"")):sub(1,47))snail.board(ak(f,C,true,aT[u]))snail.center(true)snail.text(T[i])snail.small("Best time "..S(z[i])..". "..R(u).." of "..x[u].." "..U(u).." pictures solved.")snail.hint(Z(u,O)and"BACK puzzle list OK next puzzle"or"BACK puzzle list OK puzzle list")return end local a=J if not a then a=S(c.el)if c.hints>0 then a=a.."  Hints "..c.hints end if r then a=a.."  Painting: "..(r==1 and"fill"or r==2 and"cross"or"clear")end end snail.status(a:sub(1,47))snail.board(aX())local ap if E then ap=r and"lift pen"or({"paint fill","paint cross","paint clear"})[(n[q]+1)%3+1]else ap=({"fill","cross","clear"})[(n[q]+1)%3+1]end snail.hint("BACK options OK "..ap.." L/R move UP/DN move")end local function a1()snail.title("Nonograms - "..av[s].." "..U(s))snail.status(R(s).." of "..x[s].." solved")local j=x[s]local aJ=math.max(1,math.min(y-3,j-6))for l=aJ,math.min(j,aJ+6)do local i=F[s]+l local a if z[i]>0 then a=l..". "..T[i].." ("..S(z[i])..")"elseif al(i)or(c and c.id==i and not c.done)then a=l..". In progress"else a=l..". Not solved yet"end snail.row(a,l==y)end snail.gap()local i,ao=F[s]+y,P[s]if z[i]>0 then snail.board(ak(ao,aG(s,y),true,0),"small")else local p if c and c.id==i then p=n else local v=al(i)p=v and aK(v.s:match("([^,]*)$"),ao)or{}end snail.board(ak(ao,p,false,0),"small")end snail.hint("BACK menu OK play L/R size UP/DN move")end local aR={"Each number is a run of filled squares in that row or column, in order, with gaps between.","Arrows move. OK cycles a square: filled, crossed out, empty. Crosses are just your notes.","A tick at the edge means that line matches its clue. Finish to see what the picture is.","BACK opens options: undo, check for mistakes, a hint, and drag painting.","Drag painting: OK puts the pen down, the arrows paint as you move, OK lifts it.",}function draw()if o=="play"and c then a2()return end if o=="list"then a1()return end if o=="help"then snail.title("How to play")for aw,a in ipairs(aR)do snail.text(a)end snail.hint("BACK back OK back")return end local I=o=="menu"and aD()or aF()if w>#I then w=#I end snail.title(o=="menu"and"Nonograms"or("Nonograms - "..ag()))if J then snail.status(J)end for b,a9 in ipairs(I)do snail.row(a9[1],b==w)end snail.gap()if o=="menu"then local j=R(1)+R(2)+R(3)snail.small(j.." of "..H.." pictures solved.")else snail.small("Time "..S(c.el)..(c.hints>0 and(", hints "..c.hints)or""))end snail.hint("BACK leave OK choose UP/DN move")end function start()snail.ink("fast")bb()if t[1]and pcall(aH,t[1])and c then M()else c,o,w=nil,"menu",1 end end
