--!name Calcudoku
--!icon puzzle
--!category Puzzles
local bo="!$%&()+,-0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnpqrstuvwxyz|~"local function az(a)return bo:sub(a+1,a+1)end local by,bz,bn,bq,aX,aW,a_,aZ,aY,bw,bZ,br,a0=4,5,7,8,15,21,28,29,30,31,32,33,34 local aC={[4]={"6CLG4kni03J5O4vfE0x8qWxA000gB1OHAPr02BCKWgNu0L5b8EHQm0srHL2K8e0JD4Y8vcj0N6nOZAYW08hC2PKKm0nAi1mB0ouGEpW8usG0NICXiARG0m0y3AHvW0wmrLAJY00A6KWiBhu0wSn8HSgI0A1HS1qc00M54QCuTG0m0aPYEI40QSJm4unW0m3Jm0u580gWn58Khc0abunA99C02Kg0xBi80JCq1mBkF0MmgGnA5e04Fb8HPVW0LQk0i5XC0Qcmo4QXO0","pSKbg2P80WD5OCQ300v8i3A3NL0o7Gc9x8000la1mhUu0qQjS0yG00sL68nB2O0q9n8HwMe0YiQ2iAJ0087AICQU000+i68qtK09Fb59A4W0m0pK1E2K0Qqn2RA980mB4IGuhn0sRm6WTG00mBa2udA00QpBGHwGm0WonM1KrG00NCM9ON00J8JmEJu00wcn1B3eT0Aai3GQao0BDc8nBEG0BKi2CKisugB6Xo2bW04PXC9Ss00P7gWv6S002N3CfE8G0WiCYucK00","RIC6CEHA01RHbB3nm0I2q5eAzE00Fb8Hx8G0HOjH4RmW0o8r8J2Lm097b8CIQ00mMbXOMA00ZSj8ReW00pTX59KKu0s15L0Rbo0aAu3AIc00AmnM4uHG0sDc4fKPW0qQqY4u8W0QSHXGvM50sWiOnA5W0OJ4QGzf00ZOa8myum0L2m6Gug90c54Xm6dk0OL6Omv2G0LFaSXA4G0nGiQEHW006HHS1eKG0mii70TbW08Mn6DAyG0aD4U8uwe0MoH8ZL8rGcouo3NEW0"},[5]={"c+MGM0DO0k7pDYR62oQ948CDDnLGC8Ryq9YZ1ZY0W0YSiA2P46H6cagWYQAQq8b2oYgqW0FUg3HY4HOb1kA0BCRLaGZm2ZHfC0BUWop8D4AS2600CrOBaH8n5SE70099sKYYf0b2+500UV0SH6GiHS8YO0R70EHf0mfkY000OLN56eB25iLQ00YPHoMWHaYPlX004c5IYCOWgm2a9WP9eX5e2i2DwJ80bPgI6AGO8sfuQ0GctMH294nQO280GfeqlGXCGTOsm0N6fAT06CIQF9002sMaBK12OCubl07IGR8aB64E36+0XMF8gC674TJ0003KXjZY3ZY1fs00ckoCCYaPm2feA04DO34B618L+ne0aLaY5m0XGcGtM22AGqCWYn8aio6u0P968cH8Xrox004WmSCHWaI2eLQi72Goq2U85iTo00M03yDM2nI2Wyy0Z4hIC4YBO2YpF8CXCS9GWvWcgmm0K+e5euqL84aL40aHGQj27C4OEKJWAaIhCWwi6DsM00JH08mOWkHDNS00KI8XXa1Q8HP4JS0Y3cYH6TWI05W08vYabY4X64LIYuNVYvcY2AH5aEG02gRLO4E5O1K5GWF24Ci09387G9wb7M1HSWIOecDbW0N2ICIH1OGidevO591+K9Ia1j0QW0IwKCp1L18PKpK0AQKSDb248s04W0QjKsYGo0v1Ks-K","Cm65Y4HWn3vBG0PKOp46AOKf9NmG29X7ZI4v16JJW0cEqY9GrWGwKqS0697yQG8Zif2w00OGaffmXIB4qPu04WLdZn95CYjGu0UIdCWH8nWqgnY0YWgZ386oHOADWmKrMM8qN837MRG0Z4a+XifI8Db5W0CcHSHc0FWcwd90Y9b2f38SeIEMg0HHecEh1YaTEC00XMHY7Aa2BQYK00abb5YCKiICZm00TO4qKU8Cb8i000WigbbJMG8b7EW0UYvRY4mIiD2Ia0DHMGgHLH8cIEW0WKAs558p0wDPW0YuOJ8Z942k5h509E2GgBG5g1apG08D1M6CGcw6iw0088wieP4O9iEw006Hez8Z26GFDIAeJIbKPYb84LeQ80BCjfd1AmY1PAO0CwHKgGcJ0hwYG0OjOB6vWi0McMa0859HOfGekucoS011dJ4OOeGT5La0858pYBBmH0dON04SpPcWDW6nSUA0d0XACmIQ994T808uB4aOOfGn9Qn0SHbmp8iK8I-VG0d8GjWR9M14j2M0INPXN8WWunK100R22zOYf22kCgTm7QJM8imICILDX0Gw4LBK42L6Z75WZEOISgK95A6I00M6L4jI92To3S00SMbHo8s0qLIC00R61vemw0YoKyu0asKLmGp8aIhE00CrA8N08Se5LMs01PBOcGOriQ3gW0d949SW9YCAbve0","MzWiGMHGewi900cEa0Oml03kdeO06gQkJT50yIm0002oXYBGcJ0wPb00WjWN9i4bGc0fW0IqXkZonI9iiqW071AL6j4OHoOIW0YL8s9kWXuWwfm0IXYbXIn8bc2CW0bSD56j5e2gGC00HCfM6PWGvbS400SKHang1mnL1W00Usg14L8YOL93c0FCAME4CIeCXGW0cT1DCrG18fmwg0CDgg742CgI9oi0Ys6CBn0bPS2XG0MygEoXAm9yXh00F4uPeZY3HB6000AY2IDc14tGeiG02eCZ4OXGrWgkYiKHQCcOSeGb22W04DXJZIXYIXCDm0AgGz4PmXZ4iYS0M8bpKMWJi8oW00GPdR4YLWCoR2W0AYccCiA8wIuc00d6mdW6OP0y4eW0GGefYZ16B4jhBeL5rS8POK9BPW00HEANA489cLGMW06z2geCE5E4c2a0EYvT4OemIZ3K80K0WtHIXP4L89002j0z4eYp1EciW0ST3HACmImfGW004KcJ6SAGd2RIW04rHu8r8oeqmaW0VLHiQH5cB4fGe03Of+Ah1Xb5G1W0BUH6H64CPC5P00UudOOf4X4FE0b0TE10o1aLY7KaZ071oY8ncHCJK880SyXfgGd1a3H1G0C18-HCgIf8Q100cQ2RMHHOeP1Em0D8bKLY6HCO9I80UTKInMOAK9Gd40dPI9Gt0787NLH0"},[6]={"Q3C3EWY2oo0Qm1Xakl3RG0JQ5TJHPZ0oXHeM2MuX+000gs2d9LaGQ5AZ22mg8kA800WY5WLCc8aGZ895A9hKLiW0YYK79INA12HAf48g5G6e00HGZjDHR1XSe5CX6Qc6n000aWke79CP89c4YCb408o000OAOUALdA0fm8B62q2Pw000WqeOupMSGDD4GU5D4cm000Iu1SeFGeKDOR228sjNi000BesWAlh5ON2Z0XJK1qp000HQKMeR4J68a66aYX6rUoW0Gq3Sd56OWHp48nOQff7G00JcbCfIj47B08XkB9MYK000QMY4Ql4I8aC66Ab8EZnSW0iOq4BFif51XO8HN8KP6000h2uLf5YK2CmaY8iGgm0zE0YZ1D1rp899X9Z0OwUKa000g39Em4KDX0f46gY21Nab00K0XsXImpWHYGIAC16SYcu0BqaWU4Z2f5oGJ9HY20e000Bf18kl35Y53IHJ0L3EOkW0OYavNv8Qo0GuGA9j51BG00Xug2LAYDGcM015OZLBXM00OmjFCTKHrn2KI2H6mHPM00CawCHCeXqAH312ADDiow409Qy0gG33M04I6Z4w887A00SI8GxF956BH2YCKRvPe000Yq12YHnBX496XcGrG21m00OP5ggg88n4Q7YWWUlqh500f8MC43Q8E927YWGT3KYN0038EI4BKyK8ZWGYin8y6O00JwI9eL6Y3AH7322BKnH000h0UiaPh4HK8HCi2eZf2200GMnU29H6bY11YnYJJAKK00KIoFqYHPO56R19f4wKS000i2mZBaeHuG8OGHM8d8-U8mR74OcgecGXWX55Z4C4O-W0i4UOPmMA525P06YLrlxFW01h4U0NaBS02IYCCP83H5G0Kn2d2cqB61muW9HKq8XQ00R0KWyfOKGXWPGra9535e001RE39IQAD2CYYjW3TaLW00OcK6j2qA6b322PFJYKO000O2SqKZOMAG28n4M4-g9d5WaWjuYYGvO8qGYC2giXS8003Yd6CL1YmOb6a0LaOdiN00h4jkfSOf0oIAP16sPz8000isIKe+Pe1HLW1SWkKRWW00Pv35DWrXWX8SY2M8LxgyI0OOuK18sPH72X2BC9gF8000Q6mHaeMA6B20b68MnJsm00G2TQ0kh42Aj4349An0wI008sQD0DaOLAX0YSGRQHZ000embBIEig6PXAee4DgF3400ZPC3Av2qoWiG8YkO3G4N00QqPibKAq4S261Sg0-9fU00AO6uHMfhY764LM2L0qm000PWcu3Pe7631X16behSZbm0GJ28KLf3M0568K99f9RnG0iWY63BZ5328JY1ZbF5B000eQ6BhFR4yK0nKOWHf9Ye00H2XNBSYD45Y90eWrPgXzg0KecG9bWPYmGiC88opCwN00Q0TZbPU9IXqGM8kuX50000RcudaoXCSCE426p6AmO000H6FanbIiG8Gw4Ca2rHLOS03QLxGiecK4Y721HIObjOO0Z05orKBG3Zf2CkYu5AZ000PcHBCCc8YA1aG+8DBxI000XQMJ4kQ68XXf08YL3af5W0AaXOrtsJI33k23b2mmOW00XYK8iEw8mkm0Kc4KonbI004qLI2TfY0qY8KF8Abe4v00fWH6MTiPY9KKo4OvK4u0001977x9eMm4LQ00XiyvNw00P8CCLv2jWCGc8n2m28cm00XefYuLPHCOW8nm9axvJ000BAQLeWmKcO2DXCAefyqW00IqTW1LkH1mmCP01Aho9OW0fs4gACenYCP8CS4Q8LO000WW0KaNEHI72fG9GyCI-W00P2rUgmbk0XO8JeHK07S000f4K2Y6gj4LmfmYGh34KW00e62WYkj3XGeC540Max9900XYNU94uq5Oei464qRtOC00Hb9abYPP0mKg40er7vyK00SwTGoGB4OcGZc4YWDRjF00GfFM4tOsH1L81OL9wAg800g65Gs6nEe3n8WcGsPn2G00","OGyX4NGk4U221gX9nstW00A2SFAnYmm8YvA83fpGk0001r2BWgM8GkX30Z5aYgIS00R6BGeOcSY4IYA29NCrbm00XOv2iKcCWZamGM5KL4-m00AI85EDqBO2H0oA45cvfJu0W4HBbDsOn3YGZ0LS4Bo4001WLNC2l41ZiM447xAZG0001wWsKHvK8d226Y4L6Ia+00GquSXIGnK42p2ZkKKff000PMNIAYsKGXnGNO4IUUr000Xsf428H5eWH6eXEOfV0G00iwUYL1aObCWiW4HAmGSLq0QAYfACcQH5bq1792MFN000C4a45Up4HgHg62nJEG1m00H8KzIqRaWQv08AKsBAe000h48u2YovK83Q84naF0n000PxDbbXXQu58ImI8M4SLO00Kaj29bqCLXAiHA8ZFKWW0096uK8hMXgm26i08j4vi180OmWk5AuLw0YZ45XHSW3H00fBES00rY9D8Wf58jhX1400Wuak19SW661aqKegIfMW00HIOiCHfqf45NW49MKf4000XcApO69PWG7SCCAD6ImW00f9066oJmqOZ2Y1H6WEaa00Kqc0zXfhf28cGo5m6AfG00SYsHaeHZq135M2hG0u9G00A4eIb5AZGnWGn68w30im008472ahx4HYAK8Z4AhmA1A0WMPCaHLY11OOA18bDFLOT0i6IiQ6vK5J3cGGKiTa0000AW0QLhaKSQ2WWPBGqep000Q2+9JOWZIvH90g5iUFM000fg1J5JQOm3bGXGbyLCWW00ZY28CViH8XWiX691mUi3O09QWdIMHDn095YCHaj84W002r8DD2KY51eOOM6PX8Gu00Cocs4gugCLQ48j84pJC0003wa78fcbCX4M23M0l5ADe0G3CciD5MeY43HXKP2Ysz00C558mse6nHOWGjW4ssOSm0e86N4qv56A0pKG2oAubD20igZg0MH26j05d4ZDzGa000eQs10qL64DX50nXQ0xWxe0J6v0iKAMYn0Q8nmWuZr000Z2pBHhDXnWOOwPWR02mW009Q6tQe15bWWva0n5K0Gx00C6EHKpE8abKWYmbCI770004ub54AQ9i8894B1Q3XHGa0S70bEG2jGOAAGIGFI5Ia003MCfwp55MW2O8Cx0328000Ct9koK4OYCq4oWEd5U20000PAa+pKCmcGWMYb3YSs000i4J32R6J326IcG3NU2m000QGEes4cYa8XBA1HJAE8000QafQYRMCH4rh08feGuW000iMMd8wYYi84AY0XqIWYwN0AmCe9an4nA92OYGyQgkF00Pskb5GaIh0fB84q6l1B800KZ14n3rY0nY96HIP261ID03u4uXEv58KmXKmYIaes000RQmXnJyM62j18mu9aI8000BmG603N8f4WP2p4AqTo000ZfBKY3H4Y6eeKAhHFuL600R6L9H0WR09o8Ce4rie3K00XhCX60Q3B0sX0fW5kJuo00AB5LomedC4CZ387v2E0000esgQ0keoOK2Yf06bSXra00PY1ZaMYR88pLH89K0OO0003f642lXYCKY3C9ZAGWe000HMEJSDLe4HM4Gc64qdcU00Yw44j6qIMImCS26OmSW000KQae2nsR28SYb28rCa40009qAMDF2p1emre8aZJ0k0004KQCCGOOIIIQ835aNCAE003AG8IZqHYXGbOQAe5u100016uNk1AiGSi08xJ8dBX000PR799YY7C15M03O0fHJbN4QYqhMDYSo2HX0s8TjND0001q9MUAceh56WYvL0ObC000Qs-k50J3LW2rN20L5AAe00P99OGSQCq3bWZHIgXMO000PQ5a2bAH1P01N8XajQG3W0eGrQINsp8H4HLO2MKtwS000sf5tIJCH8P25I14KaH7GihcDwnCqG5QWJ695SSrXW008Y6b4dbYX2M8GiO4zILK00ObA2cCnKrh13Z90gfvJu00Gmz146gK1Qm0ammcyEXO00","O42MEY9XWf8YIiG6pt49CWQ68EyaI6gmGOPD52qomW00J0oddB8OnCT8H64Ubb2u00GoPIaZB4LP8C8BkAN5OW001Y4MahucK78WHCAD030000GGMhWaP78f8ZHeav160000Yg8g4rqyK6GaYBC8g1f0002Ke7GQ8T25ek8X9q6p10009L2PuXh527Q1YBJAE02000PsY6KFR6nOm8eP4JgacQ00IcnObLM6n28C3k0j0vG000Ab50Bf6XemSe4HOYm6h000RqG8ILdAfWWR4ACib4n000HK1EGCgYgmGeCrWLH0OW004seHGfXOnEXB68IBh3u000QAMPL555mWHsY2mg9M2000RKi46JXMW0XhC1AL5LJI00hr5TIcOnwHXmgDH9a2G000H8KKIJxHeKCbGWJ6jGd000CI+WwrHfcOHAnI5LC00000CwpKfJJ78OoKH2fH9GhG00YI8iYqfceu8HKSYNVff000RwbYnId8ZgICC54QaWY000BwvoeILOR2pGCHA7OJ8C00WL7arMSMYA1Yum4e0gGu00euGoyHcYgm0yB659IX5W00GgwB5-ED1711WZCPhStG00fmJ2WehBP0eaOa3R80W000H0DYXlWTGcKI4IbPP4u000G9CKAOZbWXP5DaHBXQG000OWRbA-JF11BL8CsW800000PYG1KDy55qZ61UeAc2g0002GXTNAk8RG1M8G8fLLNX4e2vEY1IPYeu46PH8c78K000BWLzLTXfH5XHf6DwXL2G00K10KxXAs02n68MH5FZsb002oAT9XEP1M26JIiDH7000020+MwkQZ2j149b4c2Pi000h5DL21yY4iWCmc8Ucjb400Zw6BWxCHXLGI8gkX6+Kr009J09QSPH99ZCHImIWoa000ZoX09ht6X2ncG0big5W000AQ66DE8mmKeGnmGboiwK008YhcL4MYZYKOa8SiWI40008OwgGzuy574A5AYam8M000I2OdAT8YH4eCiaWQbB1300f4PG16KMiH64XHMXIqf000ZOMDaFy5CKa9i8Yga40000WdBwWQ9eenXomIDgWHW000G6zU755WiR9WiG9EP6y000X43t9yMOZ4HQm4TohKb000IGSPhInGMa1g0nbav1j000RpF0X1C4CiYaWYhW8Zom00Iee875YEI2omICk4A17000OKHSRJTSGfOAAY0Nnqi000RWCSkjK9rX4TSYj145m000fQWbg5aq4OemmWNH79qG00PK+GUaAAx0Wb378ciufW00QM8gCcgAvWKYGO8REvW000ZYhG2D5f0rZ10t86OtA000HZ04rNAAuo69579YG9C0000enqDeXOmGc651cuGEX000iYgr8x5654h42AqBO2Gm002fEUXbEB1Iu98Wv1G0fW00ZGXPXiga0Ih0IAJ4ivMkO09cIj4d6AY8pJ3OiAyWG000Bal44iiPb4aaWT0P57Aq00Cec35IRWXcuOP4QAOYQ000KsSg9nYYiH8f2d25sczr00SWAAId5YWGYfBD5Cijc000POHEUsgAAn1WiC96HpcM80QWmD4Cb6p8L6XGONG45W00iJ12pKfOWhG12k5COsjm00SMDoGomqQ699XAnLHcI0001nAdwbH6MAmWaiYeGq1000iMRbBL3eMAZ0evAIWXO000B03P1r4IUA4ZWf8IZ290002ZArbIBb0La4Q34Qm8ee00COcEQGh48sGJ4euZZ05000BuMC2Vo8H5eX35K1E9P1G0RBAV57aKYZCH7916wWWW00fOo3bXeYPE82hD15Sw0g00WsjAyAD7LOAE033uWBE0009h6WDGj44RZ0ZZfLBGY0008m98QZs9YWp223njKIvG00YK63qnPt91PYAXHo4CW000aABiHREP190rWfGMLAAG00BGTqsQqTbWYM2GvY0EW000PaO4rYqKOaK6C634Zc2W000AjgXJepmCTeGqc5L2tW00"}}local bx={[4]=(9),[5]=(14),[6]=(22)}local ag={"Easy","Medium","Hard"}local bu,bv="+-x:","+x"local aV="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-"local aD={}for a=1,64 do aD[aV:byte(a)]=a-1 end local function bR(h)local aO,ai,Y=1,0,0 return function(e)while Y<e do ai=(ai<<6)|(aD[h:byte(aO)]or 0)aO,Y=aO+1,Y+6 end Y=Y-e local n=ai>>Y ai=ai&((1<<Y)-1)return n end end local function bY(c)local aB=0 while(1<<aB)<c do aB=aB+1 end return aB end local function bI(e,bS)local aj=bR(bS)local D,ar,aq={},{},{}for c=0,e-1 do ar[c],aq[c]=0,0 end for a=0,e*e-1 do local z,y=a//e,a%e local P=~(ar[z]|aq[y])&((1<<e)-1)local X=0 for c=0,e-1 do X=X+(P>>c&1)end local aH=aj(bY(X))for c=0,e-1 do if P>>c&1==1 then if aH==0 then D[a]=c+1;break end aH=aH-1 end end ar[z],aq[y]=ar[z]|(1<<(D[a]-1)),aq[y]|(1<<(D[a]-1))end local aA={}for a=0,e*e-1 do aA[a]=a end local function aF(j)while aA[j]~=j do j=aA[j]end return j end local function bc(l,g)l,g=aF(l),aF(g)if l~=g then aA[math.max(l,g)]=math.min(l,g)end end for a=0,e*e-1 do if a%e<e-1 and aj(1)==1 then bc(a,a+1)end if a//e<e-1 and aj(1)==1 then bc(a,a+e)end end local x,T,s,ae={},{},{},{}for a=0,e*e-1 do local aR=aF(a)if not T[aR]then T[aR]=#s+1;s[#s+1]=0 end x[a]=T[aR]s[x[a]]=s[x[a]]+1 end for c=1,#s do if s[c]==1 then ae[c]="="elseif s[c]==2 then local j=aj(2)+1;ae[c]=bu:sub(j,j)else local j=aj(1)+1;ae[c]=bv:sub(j,j)end end return D,x,ae end local function bH(f)local i=os.date("*t",f)return os.time({year=i.year,month=i.month,day=i.day,hour=12})//86400 end local function ap()local f=os.time()if not f or f<1700000000 then return nil end return bH(f)end local bA={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}local bt={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}local bp={63,51,52,61,62,53,62}local function a8(al)return os.date("*t",al*86400+43200)end local function a9(al)local i=a8(al)return bA[i.wday].." "..bt[i.month].." "..i.day end local function a7(al)local j=bp[a8(al).wday]return j//10,j%10 end local d=6 local D,p,u,K,H={},{},{},{},{}local G={}local b local R={}local k={}for a=1,31 do k[a]=0 end local ac={}for a=1,9 do ac[a]=1 end local s=6 local Z,am=false,false local S={}local o,M,w,be,V=0,"grid",1,1,false local q,B,a2="menu",1,"menu"local J,as,au=nil,nil,nil local function ao(h,i)return(h-4)*3+i end local function _(h)h=math.floor(h or 0)if h>=3600 then return string.format("%d:%02d:%02d",h//3600,h//60%60,h%60)end return string.format("%d:%02d",h//60,h%60)end local function at(F)local h=F.size.."x"..F.size.." "if F.kind=="d"then return"Daily "..h..a9(F.num)end return h..ag[F.diff].." #"..F.num end local function av(bd,t,C,W)local a3,ax=aC[t][C],bx[t]local X=#a3//ax local O,f if bd=="d"then O,f=(W*37+11)%X,W%8 else O=(W-1)%X f=((W-1)//X+O*5)%8 end local bT,bD,ae=bI(t,a3:sub(O*ax+1,O*ax+ax))d=t local T={}G={}for z=0,d-1 do for y=0,d-1 do local an,ak=z,y if f&4~=0 then ak=d-1-ak end if f&2~=0 then an=d-1-an end if f&1~=0 then an,ak=ak,an end local bi,a=an*d+ak,z*d+y D[a]=bT[bi]local c=bD[bi]if not T[c]then G[#G+1]={cells={},op=ae[c]}T[c]=#G end H[a]=T[c]local a6=G[T[c]].cells a6[#a6+1]=a end end for ah,x in ipairs(G)do local ad,n=x.op,{}for r,a in ipairs(x.cells)do n[r]=D[a]end local j if ad=="="then j=n[1]elseif ad=="+"then j=0;for ah,i in ipairs(n)do j=j+i end elseif ad=="x"then j=1;for ah,i in ipairs(n)do j=j*i end elseif ad=="-"then j=math.abs(n[1]-n[2])else j=math.max(n[1],n[2])//math.min(n[1],n[2])end x.target=j x.label=ad=="="and tostring(j)or(j..ad)end for a=0,d*d-1 do K[a]=#G[H[a]].cells==1 p[a],u[a]=K[a]and D[a]or 0,0 end b={kind=bd,size=t,diff=C,num=W,el=0,hints=0,done=false}S,M,V,am,J={},"grid",false,false,nil o=0 for a=0,d*d-1 do if not K[a]then o=a;break end end end local function bU()local n,P,a1={},{},false for a=0,d*d-1 do n[a+1]=(K[a]and#G[H[a]].cells>1)and string.char(96+p[a])or tostring(p[a])P[a+1]=aV:sub(u[a]+1,u[a]+1)if u[a]~=0 then a1=true end end return table.concat({b.kind,b.size,b.diff,b.num,b.el,b.hints,b.done and 1 or 0,table.concat(n),a1 and table.concat(P)or""},",")end local function bk(h)local m={}for j in(h..","):gmatch("([^,]*),")do m[#m+1]=j end local t,C,W=tonumber(m[2]),tonumber(m[3]),tonumber(m[4])if(m[1]~="d"and m[1]~="b")or not aC[t]or not C or C<1 or C>3 or not W then return end av(m[1],t,C,W)b.el,b.hints,b.done=tonumber(m[5])or 0,tonumber(m[6])or 0,m[7]=="1"local n,P=m[8]or"",m[9]or""for a=0,d*d-1 do local I=n:byte(a+1)or 48 if not K[a]then if I>=97 and I<=96+d then p[a],K[a]=I-96,true elseif I>=49 and I<=48+d then p[a]=I-48 end end u[a]=(aD[P:byte(a+1)or 48]or 0)&((1<<d)-1)end return true end local function N()if b then R[b.kind]=bU()end snail.save(table.concat({"CD1",table.concat(k,","),table.concat(ac,","),Z and 1 or 0,s,b and b.kind or"",R.d or"",R.b or""},";"))end local function bN()local h=snail.load()if type(h)~="string"or h:sub(1,4)~="CD1;"then return end local m={}for j in(h..";"):gmatch("([^;]*);")do m[#m+1]=j end local c=0 for j in(m[2]or""):gmatch("[^,]+")do c=c+1 if c<=31 then k[c]=tonumber(j)or 0 end end c=0 for j in(m[3]or""):gmatch("[^,]+")do c=c+1 if c<=9 then ac[c]=math.max(1,tonumber(j)or 1)end end Z=m[4]=="1"s=aC[tonumber(m[5])]and tonumber(m[5])or 6 if m[7]and m[7]~=""then R.d=m[7]end if m[8]and m[8]~=""then R.b=m[8]end local aE=m[6]if aE and R[aE]then local aM,aQ=pcall(bk,R[aE])if not(aM and aQ)then b=nil end end end local function aw(v)S[#S+1]=v if#S>60 then table.remove(S,1)end end local function aS(a,n,v)v=v or{}v[#v+1],v[#v+2],v[#v+3]=a,p[a],u[a]p[a],u[a]=n,0 if n>0 then local a4=1<<(n-1)for r=0,d*d-1 do if u[r]&a4~=0 and(r//d==a//d or r%d==a%d)then v[#v+1],v[#v+2],v[#v+3]=r,p[r],u[r]u[r]=u[r]&~a4 end end end return v end local function aK()local e=0 for a=0,d*d-1 do if p[a]>0 and p[a]~=D[a]then e=e+1 end end return e end local function bb()local aG=true for a=0,d*d-1 do if p[a]==0 then aG=false end end if aK()>0 or not aG then if aG then J="The grid is full, but not right yet"end return end b.done,M,am=true,"grid",false local g=(ao(b.size,b.diff)-1)*3 k[g+1]=k[g+1]+1 if k[g+2]==0 or b.el<k[g+2]then k[g+2]=b.el end k[g+3]=k[g+3]+b.el if b.kind=="d"and k[31]~=b.num then k[28]=k[28]+1 k[29]=k[31]==b.num-1 and k[29]+1 or 1 if k[29]>k[30]then k[30]=k[29]end k[31]=b.num end end local function bX()local F=table.remove(S)if not F then return false end for c=#F-2,1,-3 do local r=F[c]if not K[r]then p[r],u[r]=F[c+1],F[c+2]end end o,M=F[1],"grid"return true end local function bL()local a=o if K[a]or p[a]==D[a]then a=nil for r=0,d*d-1 do if p[r]>0 and p[r]~=D[r]then a=r;break end end if not a then local a5=99 for ah,x in ipairs(G)do local ab={}for ah,r in ipairs(x.cells)do if p[r]==0 then ab[#ab+1]=r end end if#ab>0 and#ab<a5 then a,a5=ab[1],#ab end end end end if not a then return end aw(aS(a,D[a]))K[a],o=true,a b.hints=b.hints+1 J="Revealed a "..D[a]bb()end local function ay()local i=ap()if i and k[31]<i-1 then return 0 end return k[29]end local function Q()q,au,B="play",os.time(),1 end local function bm(t,C)if b then N()end local c=ao(t,C)local e=ac[c]ac[c]=e+1 av("b",t,C,e)Q()N()end local function bV(i)if b then N()end b=nil if R.d then local aM,aQ=pcall(bk,R.d)if not(aM and aQ)or b.num~=i then b=nil end end if not b then local t,C=a7(i)av("d",t,C,i)end Q()N()end local function bG()local bh=os.time()if b and q=="play"and au and not b.done then local ba=bh-au if ba>0 then b.el=b.el+math.min(ba,600)end end au=bh end local function bf()local f={}if b and not b.done then f[#f+1]={"Continue: "..at(b).." (".._(b.el)..")","cont"}end local i=ap()if i then local t,C=a7(i)local h="Daily puzzle: "..a9(i)..", "..t.."x"..t.." "..ag[C]if k[31]==i then h=h.." (solved)"end f[#f+1]={h,"daily"}else f[#f+1]={"Daily puzzle: needs the clock","noclock"}end f[#f+1]={"Grid size: "..s.."x"..s.." (OK changes)","size"}for c=1,3 do f[#f+1]={"New "..s.."x"..s.." "..ag[c].." puzzle (#"..ac[ao(s,c)]..")",c}end f[#f+1]={"Statistics","stats"}f[#f+1]={"How to play","help"}return f end local function bj()return{{"Back to the puzzle","cont"},{#S>0 and("Undo ("..#S..(#S==1 and" move)"or" moves)"))or"Undo (nothing to undo)","undo"},{"Hint: reveal a cell","hint"},{"Check for mistakes","check"},{"Always show mistakes: "..(Z and"on"or"off"),"wrong"},{"Restart this puzzle","restart"},{"New puzzle","menu"},{"Statistics","stats"},{"How to play","help"},}end local function bF(l)if l=="cont"then Q()elseif l=="undo"then if bX()then Q()else J="Nothing to undo"end N()elseif l=="hint"then if b.done then Q()else Q();bL();N()end elseif l=="check"then Q()local e=aK()am=e>0 J=e==0 and"No mistakes so far"or(e==1 and"1 mistake, struck through"or e.." mistakes, struck through")elseif l=="wrong"then Z=not Z;N()elseif l=="restart"then av(b.kind,b.size,b.diff,b.num)Q()N()elseif l=="menu"then q,B="menu",1 elseif l=="size"then s=s==6 and 4 or s+1;N()elseif l=="daily"then bV(ap())elseif type(l)=="number"then if b and b.kind=="b"and not b.done and as~=l then as,J=l,"Press OK again to leave "..at(b)else as=nil bm(s,l)end else a2,q=q,l end end local function bg(c)local z,y=o//d,o%d if c=="up"then z=(z+d-1)%d elseif c=="down"then z=(z+1)%d elseif c=="left"then y=(y+d-1)%d elseif c=="right"then y=(y+1)%d end o=z*d+y end local function bP()if w==d+1 then V=not V return end if w==d+2 then if p[o]~=0 or u[o]~=0 then aw(aS(o,0))end M="grid"elseif V then aw({o,p[o],u[o]})p[o]=0 u[o]=u[o]~(1<<(w-1))else be=w aw(aS(o,p[o]==w and 0 or w))M="grid"bb()end N()end local function bQ(c)if b.done then if c=="top"then q,B="opts",1 elseif c=="ok"then if b.kind=="b"then bm(b.size,b.diff)else q,B="menu",1 end end return end local aI=d+2 if M=="pad"then if c=="top"then M="grid"elseif c=="left"then w=(w+aI-2)%aI+1 elseif c=="right"then w=w%aI+1 elseif c=="ok"then bP()else M="grid";bg(c)end return end if c=="top"then q,B="opts",1 N()elseif c=="ok"then if K[o]then J=#G[H[o]].cells==1 and"A one-cell cage: its number is given"or"That number was revealed"else M="pad"w=p[o]>0 and p[o]or math.min(be,d)end else bg(c)end end function key(c)bG()J,am=nil,false if q=="play"then bQ(c)return end if q=="menu"or q=="opts"then local U=q=="menu"and bf()or bj()if c~="ok"then as=nil end if c=="up"then B=(B-2)%#U+1 elseif c=="down"then B=B%#U+1 elseif c=="ok"then bF(U[math.min(B,#U)][2])elseif c=="top"and q=="menu"and b and not b.done then q,B="opts",1 end return end if c=="top"or c=="ok"then q=a2 end end local function bO(P)local f={}for i=1,d do if P>>(i-1)&1==1 then f[#f+1]=i end end return f end local function bC()local aP=b.done local O=d==6 and 37 or 0 local aU,aL={},0 local function E(a)local I=az(O+a)if not aU[I]then aU[I],aL=true,aL+1 end return I end local A,L,bl={},{},{}for z=0,d-1 do local l,g={},{}for y=0,d-1 do local a=z*d+y local c=H[a]local aT=(z==0 or H[a-d]~=c)and 1 or 0 local aJ=(y==0 or H[a-1]~=c)and 1 or 0 if not bl[c]then bl[c]=true l[#l+1]="{"..G[c].label.."}"elseif aT+aJ>0 then l[#l+1]=E(aT*2+aJ)else local bW=H[a-d-1]~=H[a-d]or H[a-d-1]~=H[a-1]l[#l+1]=E(bW and by or 0)end l[#l+1]=E(bz+aT)g[#g+1]=E(bn+aJ)local n=p[a]local bB=not aP and not K[a]and n>0 and n~=D[a]and(Z or am)if a==o and not aP then g[#g+1]=E(aX+n)elseif bB then g[#g+1]="?";L[#L+1]=#A+2;L[#L+1]=#g;L[#L+1]=n elseif n>0 then g[#g+1]=E(bq+n)elseif u[a]~=0 then local f=bO(u[a])g[#g+1]="{"..(#f<=3 and table.concat(f)or(f[1]..f[2].."+")).."}"else g[#g+1]="."end end l[#l+1]=E(a_)g[#g+1]=E(a_)A[#A+1]=l A[#A+1]=g end E(aZ)E(aY)local af if not aP then af={}local aN=M=="pad"for i=1,d do af[i]=(aN and w==i)and E(aX+i)or("{"..i.."}")end af[d+1]=E(((aN and w==d+1)and a0 or bw)+(V and 1 or 0))af[d+2]=E((aN and w==d+2)and a0+2 or br)end for c=1,#L,3 do local I=az(O+aW+L[c+2])A[L[c]][L[c+1]]=(aU[I]or aL<32)and E(aW+L[c+2])or"@"end for c,f in ipairs(A)do A[c]=table.concat(f)end A[#A+1]=string.rep(az(O+aZ),2*d)..az(O+aY)if af then A[#A+1]="."A[#A+1]=string.rep(".",(d-1)//2)..table.concat(af)end return table.concat(A,"/")end local function bE(a)local x=G[H[a]]local e=#x.cells if e==1 then return"Given "..x.target end local aa=({["+"]="sum",["-"]="difference",x="product",[":"]="quotient"})[x.op]return"Cage "..x.label.." ("..aa..", "..e.." cells)"end local function bJ()snail.title("Calcudoku "..at(b))local h local g=(ao(b.size,b.diff)-1)*3 if b.done then h="Solved! Best time ".._(k[g+2])if b.hints>0 then h=h..", "..b.hints..(b.hints==1 and" hint"or" hints")end elseif J then h=J else h=_(b.el).."  "..bE(o)if Z then local e=aK()if e>0 then h=h.."  "..e.." wrong"end end if V then h=h.."  Notes on"end end snail.status(h:sub(1,47))snail.board(bC(),nil,b.done and("Solved in ".._(b.el))or nil)if b.done then snail.text(b.size.."x"..b.size.." "..ag[b.diff].." solved: "..k[g+1]..", average ".._(k[g+3]/math.max(1,k[g+1])))if b.kind=="d"then snail.text("Daily streak: "..ay()..(ay()==1 and" day"or" days"))end snail.hint(b.kind=="b"and"BACK options OK next puzzle"or"BACK options OK new puzzle")elseif M=="pad"then local aa="place"if w==d+1 then aa=V and"notes off"or"notes on"elseif w==d+2 then aa="erase"elseif V then aa="mark"elseif p[o]==w then aa="clear"end snail.hint("BACK grid OK "..aa.." L/R number UP/DN grid")else snail.hint("BACK options OK enter L/R move UP/DN move")end end local function bK()snail.title("Calcudoku statistics")for t=4,6 do local f={}for i=1,3 do local g=(ao(t,i)-1)*3 f[i]=ag[i].." "..k[g+1]..(k[g+1]>0 and(" (best ".._(k[g+2])..")")or"")end snail.text(t.."x"..t..": "..table.concat(f,", "))end snail.gap()snail.text("Daily puzzles solved: "..k[28])snail.text("Daily streak "..ay()..", best "..k[30])snail.hint("BACK back OK back")end local bs={"Fill the grid so every row and column holds each number once.","Each outlined cage makes the target in its corner with its operation.","12+ adds up to 12, 3- is a difference of 3, 30x a product of 30 and 2: divides to 2.","Arrows move. OK opens the number pad: pick with LEFT/RIGHT, then OK.","The pencil key turns notes on; the last key erases.","BACK opens options: undo, a hint, checking for mistakes and new puzzles.",}function draw()if q=="play"and b then bJ()return end if q=="stats"then bK()return end if q=="help"then snail.title("How to play")for ah,h in ipairs(bs)do snail.text(h)end snail.hint("BACK back OK back")return end if q=="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 other puzzles work any time.")snail.hint("BACK back OK back")return end local U=q=="menu"and bf()or bj()if B>#U then B=#U end snail.title(q=="menu"and"Calcudoku"or("Calcudoku "..at(b)))if J then snail.status(J)end for a,bM in ipairs(U)do snail.row(bM[1],a==B)end snail.gap()if q=="menu"then local e=0 for c=1,9 do e=e+k[c*3-2]end snail.small("Daily streak "..ay()..", "..e..(e==1 and" puzzle"or" puzzles").." solved.")else snail.small("Time ".._(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")bN()if b and not b.done and not(b.kind=="d"and ap()and b.num~=ap())then Q()else q,B="menu",1 end end
