--!name Habits
--!icon notes
--!category Tools
local q,z=120,6 local I={"Read","Exercise","Walk","Drink water","Meditate","Stretch","Sleep by 11","No phone in bed","Journal","Practice an instrument","Floss","Vitamins","Tidy up","Learn a language","Call family","Cook at home","No sugar","Steps goal","Yoga","Run","No alcohol","Make the bed","Plan tomorrow","Gratitude","Wake up early","No social media","Study","Draw or sketch","Water the plants","Skincare","Cold shower","Deep work"}local T={"January","February","March","April","May","June","July","August","September","October","November","December"}local S={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}local l,A,u={},{},{}local s={}local n=0 local j,i,G,r,k,x="today",1,0,0,1,1 local function y(p,ac,K)if p<ac then return ac end if p>K then return K end return p end local function aa(g)local c=os.date("*t",g)return os.time({year=c.year,month=c.month,day=c.day,hour=12})//86400 end local function H()local g=os.time()if not g or g<1700000000 then return nil end return aa(g)end local function O(c)local g=c*86400+43200 return os.date("*t",g+(c-aa(g))*86400)end local function v()return H()or n end local function P(c)local f=c-n+q if f>=1 and f<=q then return f end end local function ag(c)local g=O(c)return S[g.wday]:sub(1,3).." "..g.day.." "..T[g.month]:sub(1,3)end local function w(c,b)local f=P(c)return f~=nil and s[f]&(1<<(b-1))~=0 end local function D()local g={}for b=1,z do if l[b]>0 then g[#g+1]=b end end return g end local function F(b)return I[l[b]]or""end local function ah(b)local c=v()if not w(c,b)then c=c-1 end local d=0 while w(c,b)do d,c=d+1,c-1 end if d>0 and not P(c)and c<n then d=d+u[b]end return d end local function aB(b)local J,d=u[b],u[b]for c=n-q+1,n do if w(c,b)then d=d+1 if d>J then J=d end else d=0 end end return J end local function W(b,am,J)local d=0 for c=am,J do if w(c,b)then d=d+1 end end return d end local function Q()local o={}for f=1,q do o[f]=string.char(48+s[f])end snail.save(table.concat({"H1",n,table.concat(l,","),table.concat(A,","),table.concat(u,","),table.concat(o)},"|"))end local function aA()for b=1,z do l[b],A[b],u[b]=0,0,0 end for f=1,q do s[f]=0 end local b=snail.load()if type(b)~="string"or b:sub(1,3)~="H1|"then l[1],l[2],l[3]=1,4,3 return end local B={}for p in(b.."|"):gmatch("([^|]*)|")do B[#B+1]=p end n=y(tonumber(B[2])or 0,0,1e7)local a=0 for p in(B[3]or""):gmatch("%d+")do a=a+1;l[a]=y(tonumber(p),0,#I)end a=0 for p in(B[4]or""):gmatch("%d+")do a=a+1;A[a]=tonumber(p)end a=0 for p in(B[5]or""):gmatch("%d+")do a=a+1;u[a]=tonumber(p)end local o=B[6]or""for f=1,q do s[f]=y((o:byte(f)or 48)-48,0,63)end end local function af(c)local a=c-n if a<=0 then return end if n>0 then for f=1,math.min(a,q)do for b=1,z do u[b]=s[f]&(1<<(b-1))~=0 and u[b]+1 or 0 end end if a>q then for b=1,z do u[b]=0 end end end for f=1,q do s[f]=f+a<=q and s[f+a]or 0 end n=c end local function aj(c,b)local f=P(c)if not f or l[b]==0 or c>v()then return end s[f]=s[f]~(1<<(b-1))A[b]=math.max(A[b],aB(b))Q()end local U={"Week grid","Last 30 days","Summary","Choose habits"}local _={"week","month","summary","choose"}local function ad()snail.status("The clock isn't set")snail.text("Habits needs today's date. Connect to Wi-Fi once so the clock gets set, then come back.")snail.small("Nothing you've ticked off is lost.")end local function an(c,e)if c==e then return"Today"end if c==e-1 then return"Yesterday"end return ag(c)end local function as()snail.title("Habits")local e,h=H(),D()if e then local c=e-G local d=0 for al,b in ipairs(h)do if w(c,b)then d=d+1 end end local g=O(c)snail.status(an(c,e)..(G<2 and", "..S[g.wday].." "..g.day.." "..T[g.month]:sub(1,3)or"")..":  "..d.." of "..#h.." done")for a,b in ipairs(h)do local M=ah(b)snail.row((w(c,b)and"[x]  "or"[  ]  ")..F(b)..(M>1 and"   - "..M.." days"or""),i==a)end if#h==0 then snail.text("No habits yet. Choose up to six below.")end else ad()end snail.gap()local o=e and#h or 0 for a=1,#U do snail.row(U[a],i==o+a)end if G>0 then snail.small("Ticking off an earlier day. R comes back to today.")end if i<=o then snail.hint("BACK leave OK tick L/R day UP/DN move")else snail.hint("BACK leave OK open UP/DN move")end end local function az(h)local g={}for a,b in ipairs(h)do g[a]=a.." "..F(b)end local E=table.concat(g,"   ")return#E>95 and E:sub(1,92).."..."or E end local function at()local h=D()local Z=r-(O(r).wday+5)%7 local g=O(Z)snail.title("Week of "..g.day.." "..T[g.month])if#h==0 then snail.text("No habits yet. Choose some first.")snail.hint("BACK back")return end local b=h[k]snail.status(F(b)..", "..ag(r)..": "..(w(r,b)and"done"or"not done"))snail.small(az(h))local t,m={},{"."}for a=0,6 do local E=S[(a+1)%7+1]:sub(1,1)m[a+2]=Z+a==v()and"{"..E.."}"or E end t[1]=table.concat(m)for a,au in ipairs(h)do m={tostring(a)}for ab=0,6 do local c,N=Z+ab,"."if c<=v()and P(c)then N=w(c,au)and"d"or"e"if c==r and a==k then N=N:upper()end end m[ab+2]=N end t[#t+1]=table.concat(m)end snail.board(table.concat(t,"/"))snail.hint("BACK back OK tick L/R day UP/DN habit")end local function ap()local h,e=D(),v()snail.title("Last 30 days")local t={}for a,b in ipairs(h)do local d=W(b,e-29,e)snail.text(a.."  "..F(b)..":  "..d.." of 30 days  ("..(d*100+15)//30 .."%)")local m={tostring(a)}for c=e-29,e do m[#m+1]=w(c,b)and"x"or"y"end if a>1 then t[#t+1]="."end t[#t+1]=table.concat(m)end if#h==0 then snail.text("No habits yet. Choose some first.")end snail.small("Oldest on the left, today on the right.")if#t>0 then snail.board(table.concat(t,"/"))end snail.hint("BACK back OK back")end local function ar()local h,e=D(),v()snail.title("Summary")local V,L=0,0 for al,b in ipairs(h)do local M=ah(b)local d=W(b,e-29,e)V,L=V+d,L+30 snail.text(F(b))snail.small(M.." in a row, best "..math.max(A[b],M)..".  Last 7: "..W(b,e-6,e).."/7.  Last 30: "..(d*100+15)//30 .."%")end if L>0 then snail.gap()snail.text("All habits, last 30 days:  "..(V*100+L//2)//L.."%")else snail.text("No habits yet. Choose some first.")end snail.hint("BACK back OK back")end local function ao()snail.title("Choose habits")for b=1,z do snail.row(b.."   "..(l[b]>0 and F(b)or"(empty)"),i==b)end snail.gap()snail.small("Up to six habits. Changing one starts its history over.")snail.hint("BACK back OK change UP/DN move")end local function ai(C)if C==0 then return false end for b=1,z do if b~=k and l[b]==C then return true end end return false end local function aq()snail.title("Habit "..k..": pick one")local d=#I+1 local ak=y(x-6,1,math.max(1,d-11))for ae=ak,math.min(d,ak+11)do local C=ae-1 local Y=C==0 and"(no habit)"or I[C]..(ai(C)and"   (in use)"or"")if C==l[k]then Y=Y.."   - current"end snail.row(Y,ae==x)end snail.small(x.." of "..d)snail.hint("BACK cancel OK pick UP/DN move")end function draw()if(j=="week"or j=="month"or j=="summary")and v()==0 then snail.title("Habits")ad()snail.hint("BACK back")elseif j=="week"then at()elseif j=="month"then ap()elseif j=="summary"then ar()elseif j=="choose"then ao()elseif j=="pick"then aq()else as()end end local function R(m,d,a)if a=="up"then return m>1 and m-1 or d end return m<d and m+1 or 1 end local function X(av)j="today"local o=H()and#D()or 0 for a,p in ipairs(_)do if p==av then i=o+a end end end local function ax(a)local e,h=H(),D()local o=e and#h or 0 local d=o+#U i=y(i,1,d)if a=="up"or a=="down"then i=R(i,d,a)elseif e and(a=="left"or a=="right")then G=y(G+(a=="left"and 1 or-1),0,6)elseif a=="ok"and i<=o then aj(e-G,h[i])elseif a=="ok"then j=_[i-o]r,k,i=v(),1,1 end end local function ay(a)local h=D()if a=="top"then X("week")elseif#h==0 then return elseif a=="left"or a=="right"then local K=v()r=y(r+(a=="right"and 1 or-1),math.min(n-q+1,K),K)elseif a=="up"or a=="down"then k=R(y(k,1,#h),#h,a)elseif a=="ok"then aj(r,h[k])end end local function aw(a)local d=#I+1 if a=="up"or a=="down"then x=R(x,d,a)elseif a=="top"then j,i="choose",k elseif a=="ok"and not ai(x-1)then if l[k]~=x-1 then l[k],A[k],u[k]=x-1,0,0 local aC=~(1<<(k-1))for f=1,q do s[f]=s[f]&aC end Q()end j,i="choose",k end end function key(a)local e=H()if e and e>n then local aD=r==n af(e)Q()if aD then r=e end end if j=="today"then ax(a)elseif j=="week"then ay(a)elseif j=="choose"then if a=="up"or a=="down"then i=R(i,z,a)elseif a=="top"then X("choose")elseif a=="ok"then j,k,x="pick",i,l[i]+1 end elseif j=="pick"then aw(a)elseif a=="top"or a=="ok"then X(j)end end function start()aA()local e=H()if e and e>n then af(e);Q()end r=v()end
