--!name Killer Sudoku
--!icon puzzle
--!category Puzzles
local bj="!$%&()+,-0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnpqrstuvwxyz|~"local function av(a)return bj:sub(a+1,a+1)end local bq,br,bi,bl,aQ,aP,aU,aT,aS,bp,bm,aV=8,9,12,14,24,33,43,44,45,46,48,49 local bh={"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"}local am=(35)local W={"Easy","Medium","Hard"}local aO="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-"local aR={}for a=1,64 do aR[aO:byte(a)]=a-1 end local function bd(f)local aI,F,u=1,0,0 return function(i)while u<i do F=(F<<6)|(aR[f:byte(aI)]or 0)aI,u=aI+1,u+6 end u=u-i local p=F>>u F=F&((1<<u)-1)return p end end local function az(a)return(a//27)*3+(a%9)//3 end local function bB(bL)local ab=bd(bL)local A,al,ak,an={},{},{},{}for c=0,8 do al[c],ak[c],an[c]=0,0,0 end for a=0,80 do local v,t,d=a//9,a%9,az(a)local N=~(al[v]|ak[t]|an[d])&511 local X,ax=0,0 for c=0,8 do X=X+(N>>c&1)end while(1<<ax)<X do ax=ax+1 end local aC=ab(ax)for c=0,8 do if N>>c&1==1 then if aC==0 then A[a]=c+1;break end aC=aC-1 end end local Z=1<<(A[a]-1)al[v],ak[t],an[d]=al[v]|Z,ak[t]|Z,an[d]|Z end local aw={}for a=0,80 do aw[a]=a end local function aA(B)while aw[B]~=B do B=aw[B]end return B end local function a5(j,d)j,d=aA(j),aA(d)if j~=d then aw[math.max(j,d)]=math.min(j,d)end end for a=0,80 do if a%3<2 and ab(1)==1 then a5(a,a+1)end if a//9%3<2 and ab(1)==1 then a5(a,a+9)end end local y={}for a=0,80 do y[a]=aA(a)end return A,y end local function bA(e)local g=os.date("*t",e)return os.time({year=g.year,month=g.month,day=g.day,hour=12})//86400 end local function ah()local e=os.time()if not e or e<1700000000 then return nil end return bA(e)end local bs={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}local bo={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}local bk={3,1,1,2,2,2,3}local function a1(ad)return os.date("*t",ad*86400+43200)end local function a2(ad)local g=a1(ad)return bs[g.wday].." "..bo[g.month].." "..g.day end local function a0(ad)return bk[a1(ad).wday]end local A,l,s,I,C={},{},{},{},{}local G={}local b local P={}local h={}for a=1,13 do h[a]=0 end local af={1,1,1}local Y,ae=false,false local Q={}local m,L,r,a7,S=0,"grid",1,1,false local o,x,aY="menu",1,"menu"local E,ao,ar=nil,nil,nil local function V(f)f=math.floor(f or 0)if f>=3600 then return string.format("%d:%02d:%02d",f//3600,f//60%60,f%60)end return string.format("%d:%02d",f//60,f%60)end local function aq(J)if J.kind=="d"then return"Daily, "..a2(J.num)end return W[J.diff].." #"..J.num end local function as(a6,H,T)local aZ=bh[H]local X=#aZ//am local aa,e if a6=="d"then aa,e=(T*37+11)%X,T%8 else aa=(T-1)%X e=((T-1)//X+aa*5)%8 end local bM,bv=bB(aZ:sub(aa*am+1,aa*am+am))local ap={}G={}for v=0,8 do for t=0,8 do local ag,ac=v,t if e&4~=0 then ac=8-ac end if e&2~=0 then ag=8-ag end if e&1~=0 then ag,ac=ac,ag end local bb,a=ag*9+ac,v*9+t A[a]=bM[bb]local c=bv[bb]if not ap[c]then G[#G+1]={cells={},sum=0}ap[c]=#G end local y=G[ap[c]]C[a]=ap[c]y.cells[#y.cells+1]=a y.sum=y.sum+A[a]end end for a=0,80 do I[a]=#G[C[a]].cells==1 l[a],s[a]=I[a]and A[a]or 0,0 end b={kind=a6,diff=H,num=T,el=0,hints=0,done=false}Q,L,S,ae,E={},"grid",false,false,nil m=0 for a=0,80 do if not I[a]then m=a;break end end end local function bI()local e,F,u,aX={},0,0,false for a=0,81 do local N=s[a]or 0 if N~=0 then aX=true end F,u=(F<<9)|N,u+9 while u>=6 do u=u-6 local p=F>>u e[#e+1]=aO:sub(p+1,p+1)F=F&((1<<u)-1)end end return aX and table.concat(e)or""end local function bO()local p={}for a=0,80 do p[a+1]=(I[a]and#G[C[a]].cells>1)and string.char(96+l[a])or tostring(l[a])end return table.concat({b.kind,b.diff,b.num,b.el,b.hints,b.done and 1 or 0,table.concat(p),bI()},",")end local function be(f)local k={}for B in(f..","):gmatch("([^,]*),")do k[#k+1]=B end local H,T=tonumber(k[2]),tonumber(k[3])if(k[1]~="d"and k[1]~="b")or not H or H<1 or H>3 or not T then return end as(k[1],H,T)b.el,b.hints,b.done=tonumber(k[4])or 0,tonumber(k[5])or 0,k[6]=="1"local p=k[7]or""for a=0,80 do local D=p:byte(a+1)or 48 if not I[a]then if D>=97 and D<=105 then l[a],I[a]=D-96,true elseif D>=49 and D<=57 then l[a]=D-48 end end end if k[8]and k[8]~=""then local ab=bd(k[8])for a=0,80 do s[a]=ab(9)end end return true end local function M()if b then P[b.kind]=bO()end snail.save(table.concat({"KS1",table.concat(h,","),table.concat(af,","),Y and 1 or 0,b and b.kind or"",P.d or"",P.b or""},";"))end local function bG()local f=snail.load()if type(f)~="string"or f:sub(1,4)~="KS1;"then return end local k={}for B in(f..";"):gmatch("([^;]*);")do k[#k+1]=B end local c=0 for B in(k[2]or""):gmatch("[^,]+")do c=c+1 if c<=13 then h[c]=tonumber(B)or 0 end end c=0 for B in(k[3]or""):gmatch("[^,]+")do c=c+1 if c<=3 then af[c]=math.max(1,tonumber(B)or 1)end end Y=k[4]=="1"if k[6]and k[6]~=""then P.d=k[6]end if k[7]and k[7]~=""then P.b=k[7]end local ay=k[5]if ay and P[ay]then local aG,aK=pcall(be,P[ay])if not(aG and aK)then b=nil end end end local function bN(a,n)return a//9==n//9 or a%9==n%9 or az(a)==az(n)end local function at(q)Q[#Q+1]=q if#Q>60 then table.remove(Q,1)end end local function aL(a,p,q)q=q or{}q[#q+1],q[#q+2],q[#q+3]=a,l[a],s[a]l[a],s[a]=p,0 if p>0 then local Z=1<<(p-1)for n=0,80 do if s[n]&Z~=0 and bN(a,n)then q[#q+1],q[#q+2],q[#q+3]=n,l[n],s[n]s[n]=s[n]&~Z end end end return q end local function aE()local i=0 for a=0,80 do if l[a]>0 and l[a]~=A[a]then i=i+1 end end return i end local function a4()local aB=true for a=0,80 do if l[a]==0 then aB=false end end if aE()>0 or not aB then if aB then E="The grid is full, but not right yet"end return end b.done,L,ae=true,"grid",false local d=(b.diff-1)*3 h[d+1]=h[d+1]+1 if h[d+2]==0 or b.el<h[d+2]then h[d+2]=b.el end h[d+3]=h[d+3]+b.el if b.kind=="d"and h[13]~=b.num then h[10]=h[10]+1 h[11]=h[13]==b.num-1 and h[11]+1 or 1 if h[11]>h[12]then h[12]=h[11]end h[13]=b.num end end local function bR()local J=table.remove(Q)if not J then return false end for c=#J-2,1,-3 do local n=J[c]if not I[n]then l[n],s[n]=J[c+1],J[c+2]end end m,L=J[1],"grid"return true end local function bE()local a=m if I[a]or l[a]==A[a]then a=nil for n=0,80 do if l[n]>0 and l[n]~=A[n]then a=n;break end end if not a then local a_=99 for aW,y in ipairs(G)do local _={}for aW,n in ipairs(y.cells)do if l[n]==0 then _[#_+1]=n end end if#_>0 and#_<a_ then a,a_=_[1],#_ end end end end if not a then return end at(aL(a,A[a]))I[a],m=true,a b.hints=b.hints+1 E="Revealed a "..A[a]a4()end local function bz()local i={}for g=1,9 do i[g]=0 end for a=0,80 do if l[a]>0 then i[l[a]]=i[l[a]]+1 end end return i end local function au()local g=ah()if g and h[13]<g-1 then return 0 end return h[11]end local function O()o,ar,x="play",os.time(),1 end local function bg(H)if b then M()end local i=af[H]af[H]=i+1 as("b",H,i)O()M()end local function bP(g)if b then M()end b=nil if P.d then local aG,aK=pcall(be,P.d)if not(aG and aK)or b.num~=g then b=nil end end if not b then as("d",a0(g),g)end O()M()end local function by()local ba=os.time()if b and o=="play"and ar and not b.done then local a3=ba-ar if a3>0 then b.el=b.el+math.min(a3,600)end end ar=ba end local function a8()local e={}if b and not b.done then e[#e+1]={"Continue: "..aq(b).." ("..V(b.el)..")","cont"}end local g=ah()if g then local f="Daily puzzle: "..a2(g)..", "..W[a0(g)]if h[13]==g then f=f.." (solved)"end e[#e+1]={f,"daily"}else e[#e+1]={"Daily puzzle: needs the clock","noclock"}end for c=1,3 do e[#e+1]={"New "..W[c].." puzzle (#"..af[c]..")",c}end e[#e+1]={"Statistics","stats"}e[#e+1]={"How to play","help"}return e end local function bc()return{{"Back to the puzzle","cont"},{#Q>0 and("Undo ("..#Q..(#Q==1 and" move)"or" moves)"))or"Undo (nothing to undo)","undo"},{"Hint: reveal a cell","hint"},{"Check for mistakes","check"},{"Always show mistakes: "..(Y and"on"or"off"),"wrong"},{"Restart this puzzle","restart"},{"New puzzle","menu"},{"Statistics","stats"},{"How to play","help"},}end local function bx(j)if j=="cont"then O()elseif j=="undo"then if bR()then O()else E="Nothing to undo"end M()elseif j=="hint"then if b.done then O()else O();bE();M()end elseif j=="check"then O()local i=aE()ae=i>0 E=i==0 and"No mistakes so far"or(i==1 and"1 mistake, struck through"or i.." mistakes, struck through")elseif j=="wrong"then Y=not Y;M()elseif j=="restart"then as(b.kind,b.diff,b.num)O()M()elseif j=="menu"then o,x="menu",1 elseif j=="daily"then bP(ah())elseif type(j)=="number"then if b and b.kind=="b"and not b.done and ao~=j then ao,E=j,"Press OK again to leave "..aq(b)else ao=nil bg(j)end else aY,o=o,j end end local function a9(c)local v,t=m//9,m%9 if c=="up"then v=(v+8)%9 elseif c=="down"then v=(v+1)%9 elseif c=="left"then t=(t+8)%9 elseif c=="right"then t=(t+1)%9 end m=v*9+t end local function bJ()if r==10 then S=not S return end if r==11 then if l[m]~=0 or s[m]~=0 then at(aL(m,0))end L="grid"elseif S then at({m,l[m],s[m]})l[m]=0 s[m]=s[m]~(1<<(r-1))else a7=r at(aL(m,l[m]==r and 0 or r))L="grid"a4()end M()end local function bK(c)if b.done then if c=="top"then o,x="opts",1 elseif c=="ok"then if b.kind=="b"then bg(b.diff)else o,x="menu",1 end end return end if L=="pad"then if c=="top"then L="grid"elseif c=="left"then r=(r+9)%11+1 elseif c=="right"then r=r%11+1 elseif c=="ok"then bJ()else L="grid";a9(c)end return end if c=="top"then o,x="opts",1 M()elseif c=="ok"then if I[m]then E=#G[C[m]].cells==1 and"A one-cell cage: its number is given"or"That number was revealed"else L="pad"r=l[m]>0 and l[m]or a7 end else a9(c)end end function key(c)by()E,ae=nil,false if o=="play"then bK(c)return end if o=="menu"or o=="opts"then local R=o=="menu"and a8()or bc()if c~="ok"then ao=nil end if c=="up"then x=(x-2)%#R+1 elseif c=="down"then x=x%#R+1 elseif c=="ok"then bx(R[math.min(x,#R)][2])elseif c=="top"and o=="menu"and b and not b.done then o,x="opts",1 end return end if c=="top"or c=="ok"then o=aY end end local function bH(N)local e={}for g=1,9 do if N>>(g-1)&1==1 then e[#e+1]=g end end return e end local function bu()local aJ=b.done local aN,aF={},0 local function z(a)local D=av(a)if not aN[D]then aN[D],aF=true,aF+1 end return D end local w,K,bf={},{},{}for v=0,8 do local j,d={},{}for t=0,8 do local a=v*9+t local c=C[a]local aM=v%3==0 and 2 or(C[a-9]~=c and 1 or 0)local aD=t%3==0 and 2 or(C[a-1]~=c and 1 or 0)if not bf[c]then bf[c]=true j[#j+1]="{"..G[c].sum.."}"elseif aM+aD>0 then j[#j+1]=z(aM*3+aD)else local bQ=C[a-10]~=C[a-9]or C[a-10]~=C[a-1]j[#j+1]=z(bQ and bq or 0)end j[#j+1]=z(br+aM)d[#d+1]=z(bi+aD)local p=l[a]local bt=not aJ and not I[a]and p>0 and p~=A[a]and(Y or ae)if a==m and not aJ then d[#d+1]=z(aQ+p)elseif bt then d[#d+1]="?";K[#K+1]=#w+2;K[#K+1]=#d;K[#K+1]=p elseif p>0 then d[#d+1]=z(bl+p)elseif s[a]~=0 then local e=bH(s[a])d[#d+1]="{"..(#e<=2 and table.concat(e)or(e[1].."+")).."}"else d[#d+1]="."end end j[#j+1]=z(aU)d[#d+1]=z(aU)w[#w+1]=j w[#w+1]=d end z(aT)z(aS)local U if not aJ then U={}local aH,i=L=="pad",bz()for g=1,9 do if aH and r==g then U[g]=z(aQ+g)elseif i[g]>=9 then U[g]="{}"else U[g]="{"..g.."}"end end U[10]=z(((aH and r==10)and aV or bp)+(S and 1 or 0))U[11]=z((aH and r==11)and aV+2 or bm)end for c=1,#K,3 do local D=av(aP+K[c+2])w[K[c]][K[c+1]]=(aN[D]or aF<32)and z(aP+K[c+2])or"@"end for c,e in ipairs(w)do w[c]=table.concat(e)end w[#w+1]=string.rep(av(aT),18)..av(aS)if U then w[#w+1]="."w[#w+1]="...."..table.concat(U)end return table.concat(w,"/")end local function bw(a)local y=G[C[a]]local i=#y.cells if i==1 then return"Given "..y.sum end local ai={}for N=1,511 do local f,c,e=0,0,{}for g=1,9 do if N>>(g-1)&1==1 then f,c,e[#e+1]=f+g,c+1,g end end if c==i and f==y.sum then ai[#ai+1]=table.concat(e,"+")end end local f="Cage "..y.sum.." in "..i if#ai<=2 then return f..": "..table.concat(ai," or ")end return f..", "..#ai.." ways"end local function bC()snail.title("Killer Sudoku - "..aq(b))local f local d=(b.diff-1)*3 if b.done then f="Solved! Best "..W[b.diff].." time "..V(h[d+2])if b.hints>0 then f=f..", "..b.hints..(b.hints==1 and" hint"or" hints")end elseif E then f=E else f=V(b.el).."  "..bw(m)if Y then local i=aE()if i>0 then f=f.."  "..i.." wrong"end end if S then f=f.."  Notes on"end end snail.status(f:sub(1,47))snail.board(bu(),nil,b.done and("Solved in "..V(b.el))or nil)if b.done then snail.text(W[b.diff].." puzzles solved: "..h[d+1]..", average "..V(h[d+3]/math.max(1,h[d+1])))if b.kind=="d"then snail.text("Daily streak: "..au()..(au()==1 and" day"or" days"))end snail.hint(b.kind=="b"and"BACK options OK next puzzle"or"BACK options OK new puzzle")elseif L=="pad"then local aj="place"if r==10 then aj=S and"notes off"or"notes on"elseif r==11 then aj="erase"elseif S then aj="mark"elseif l[m]==r then aj="clear"end snail.hint("BACK grid OK "..aj.." L/R number UP/DN grid")else snail.hint("BACK options OK enter L/R move UP/DN move")end end local function bD()snail.title("Killer Sudoku statistics")for c=1,3 do local d=(c-1)*3 local i=h[d+1]if i>0 then snail.text(W[c]..": "..i.." solved, best "..V(h[d+2])..", average "..V(h[d+3]/i))else snail.text(W[c]..": none solved yet")end end snail.gap()snail.text("Daily puzzles solved: "..h[10])snail.text("Daily streak "..au()..", best "..h[12])snail.hint("BACK back OK back")end local bn={"Fill the grid so every row, column and 3x3 box holds 1 to 9 once each.","Each outlined cage adds up to the sum in its corner, with no number twice inside it.","Heavy lines mark the 3x3 boxes.","Arrows move. OK opens the number pad: pick with LEFT/RIGHT, then OK.","The pencil key turns notes on; the last key erases.","The status line shows the cage you're on and, when there are only one or two, its sums.","BACK opens options: undo, a hint, checking for mistakes and new puzzles.",}function draw()if o=="play"and b then bC()return end if o=="stats"then bD()return end if o=="help"then snail.title("How to play")for aW,f in ipairs(bn)do snail.text(f)end snail.hint("BACK back OK back")return end if o=="noclock"then snail.title("Daily puzzle")snail.text("The daily puzzle needs the date. Connect to Wi-Fi once so the clock gets set, then come back.")snail.text("The Easy to Hard puzzles work any time.")snail.hint("BACK back OK back")return end local R=o=="menu"and a8()or bc()if x>#R then x=#R end snail.title(o=="menu"and"Killer Sudoku"or("Killer Sudoku - "..aq(b)))if E then snail.status(E)end for a,bF in ipairs(R)do snail.row(bF[1],a==x)end snail.gap()if o=="menu"then local i=h[1]+h[4]+h[7]snail.small("Daily streak "..au()..", "..i..(i==1 and" puzzle"or" puzzles").." solved.")else snail.small("Time "..V(b.el)..(b.hints>0 and(", hints "..b.hints)or""))end snail.hint("BACK leave OK choose UP/DN move")end function start()snail.ink("fast")bG()if b and not b.done and not(b.kind=="d"and ah()and b.num~=ah())then O()else o,x="menu",1 end end
