--!name Mahjong Solitaire
--!icon game
--!category Puzzles
local aF="!$%&()+,-0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefg"local o,ae=13,8 local T=o*ae local aH={"Dots","Bamboo","Characters"}local S={{name="Bridge",level="easy",map="11.........11".."121.......121".."1211.111.1121".."11..12321..11".."11..12321..11".."1211.111.1121".."121.......121".."11.........11"},{name="Pyramid",level="medium",map="...1111111...".."..111111111..".."..112222211.."..".1112333211..".."..1123332111.".."..112222211..".."..111111111..".."...1111111..."},{name="Fortress",level="hard",map="3211111111123".."21.........12".."1...11111...1".."1...12221...1".."1...12221...1".."1...11111...1".."21.........12".."3211111111123"},}local O,q=1,0 local ah,w,f,G,Q,y={},{},{},{},{},{}local m,E={},{}local e,i,j=1,0,0 local J=false local A,B,s="menu",1,nil local K={0,0,0,0,0,0}local function am(t)O=t local aP=S[t].map q=0 y={}for a=1,T do local g=tonumber(aP:sub(a,a))or 0 ah[a],w[a],f[a]=g,q+1,f[a]or 0 for ap=1,g do Q[q+ap]=a end q=q+g if g>0 then y[#y+1]=a end end for b=q+1,#G do G[b],Q[b]=nil,nil end end local function r(a)local g=f[a]if g==0 then return false end local l=(a-1)%o return(l==0 or f[a-1]<g)or(l==o-1 or f[a+1]<g)end local function N(a)return G[w[a]+f[a]-1]end local function ac(a)local d=N(a)-1 return d%9+1 .." "..aH[d//9+1]end local function aC()local v,u={},0 for M,a in ipairs(y)do if r(a)then local d=N(a)local g=(v[d]or 0)+1 v[d]=g if g%2==0 then u=u+1 end end end return u end local function ay(aA)local b={}for h=1,#aA do b[h]=("%02x"):format(aA[h])end return table.concat(b)end local function ab()local d={}if J then for h=1,q do d[h]=string.char(96+G[h])end end snail.save(table.concat({"J1",O,e,i,J and 1 or 0,table.concat(K,","),table.concat(d),J and ay(m)or"",J and ay(E)or""},";"))end local function aO()local z=snail.load()if type(z)~="string"or z:sub(1,3)~="J1;"then return end local C={}for g in(z..";"):gmatch("([^;]*);")do C[#C+1]=g end local h=0 for g in(C[6]or""):gmatch("%d+")do h=h+1 if h<=6 then K[h]=tonumber(g)end end am(math.max(1,math.min(#S,tonumber(C[2])or 1)))local p=C[7]or""if C[5]~="1"or#p~=q then return end for b=1,q do G[b]=p:byte(b)-96 end for a=1,T do f[a]=ah[a]end m,j={},q for l in(C[8]or""):gmatch("%x%x")do local b=tonumber(l,16)local a=Q[b]if not a or w[a]+f[a]-1~=b then break end m[#m+1]=b f[a]=f[a]-1 j=j-1 end if#m%2==1 then local a=Q[m[#m]]f[a]=f[a]+1 m[#m],j=nil,j+1 end E={}for l in(C[9]or""):gmatch("%x%x")do local b=tonumber(l,16)if b<1 or b>q then E={};break end E[#E+1]=b end J=true e=math.max(1,math.min(T,tonumber(C[3])or 1))i=tonumber(C[4])or 0 if i<1 or i>T or not r(i)then i=0 end end local function an(b)for h=#b,2,-1 do local n=math.random(h)b[h],b[n]=b[n],b[h]end return b end local function aS()local x,R,D={},{},{}local Z=0 for a=1,T do x[a]=f[a]Z=Z+f[a]end while Z>0 do local u,L,v=0,0,0 for M,a in ipairs(y)do local g=x[a]if g>0 then local l=(a-1)%o if l==0 or l==o-1 or x[a-1]<g or x[a+1]<g then u=u+1 R[u]=a if g>L then L,v=g,1 elseif g==L then v=v+1 end end end end if u<2 then return nil end local h=math.random(u)if math.random(2)==1 then local d=math.random(v)for n=1,u do if x[R[n]]==L then d=d-1 if d==0 then h=n;break end end end end local c=R[h]R[h]=R[u]local k=R[math.random(u-1)]D[#D+1]=w[c]+x[c]-1 D[#D+1]=w[k]+x[k]-1 x[c],x[k]=x[c]-1,x[k]-1 Z=Z-2 end return D end local function aq(p)for M=1,#p>15 and 3 or 12 do local D=aS()if D then an(p)for n=1,#p do G[D[2*n-1]]=p[n]G[D[2*n]]=p[n]end E=D return true end end return false end local function V(ak)local aX,aY=(ak-1)%o,(ak-1)//o local U,at=nil,1e9 for M,a in ipairs(y)do if r(a)then local I=math.abs((a-1)%o-aX)+2*math.abs((a-1)//o-aY)if I<at then U,at=a,I end end end return U or ak end local function aw(t)am(t)for a=1,T do f[a]=ah[a]end local af={}for d=1,27 do af[d]=d end an(af)local p={}for n=1,q//2 do p[n]=af[(n-1)//2+1]end if not aq(p)then an(p)for b=1,q do G[b]=p[(b+1)//2]end E={}end m,j,i,s={},q,0,nil J=true e=V(4*o+7)K[2*t-1]=K[2*t-1]+1 A="play"ab()end local function aD()local v,p={},{}for M,a in ipairs(y)do for ap=1,f[a]do local d=G[w[a]+ap-1]v[d]=(v[d]or 0)+1 if v[d]%2==0 then p[#p+1]=d end end end if aq(p)then s="Shuffled: there is a way through"else s="These can't be shuffled; try Undo"end i=0 e=V(e)ab()end local function aU(c,k)for M,a in ipairs({c,k})do m[#m+1]=w[a]+f[a]-1 f[a]=f[a]-1 end j=j-2 i=0 if j==0 then K[2*O]=K[2*O]+1 else e=V(e)end end local function aV()if#m==0 then s="Nothing to undo";return end for M=1,2 do local b=m[#m]m[#m]=nil local a=Q[b]f[a]=f[a]+1 end j=j+2 i=0 s="Took back the last pair"if not r(e)then e=V(e)end end local function aR()for n=1,#E-1,2 do local c,k=E[n],E[n+1]local W,X=Q[c],Q[k]local az,aM=c-w[W]<f[W],k-w[X]<f[X]if az~=aM then return end if az then if c==w[W]+f[W]-1 and k==w[X]+f[X]-1 and r(W)and r(X)then return W,X end return end end end local function aK()local ar,as,Y=nil,nil,-1 for h,c in ipairs(y)do if r(c)then local d=N(c)for n=h+1,#y do local k=y[n]if r(k)and N(k)==d and f[c]+f[k]>Y then ar,as,Y=c,k,f[c]+f[k]end end end end return ar,as end local function aL()local c,k=aR()if not c then c,k=aK()end if c then i,e=c,k s="Hint: "..ac(c)..", OK takes them"else s="No pairs are open"end end local function aQ(ax,aJ)local au,av=(e-1)%o,(e-1)//o local U,Y=nil,1e9 for M,a in ipairs(y)do if a~=e and r(a)then local l,ad=(a-1)%o,(a-1)//o local z if ax~=0 then local c=(l-au)*ax if c<=0 then c=c+o end z=c+8*math.abs(ad-av)else local c=(ad-av)*aJ if c<=0 then c=c+ae end z=4*c+math.abs(l-au)end if z<Y then U,Y=a,z end end end if U then e=U end end local function aj()return J and j>0 end local function aB()local b={}if J then b[#b+1]={j>0 and"Back to the game"or"Back to the cleared board","resume"}end if aj()then b[#b+1]={"Hint: show an open pair","hint"}if#m>0 then b[#b+1]={"Undo the last pair","undo"}end b[#b+1]={"Shuffle the tiles left","shuffle"}end for t,H in ipairs(S)do if not H.n then H.n=0 for I in H.map:gmatch("%d")do H.n=H.n+I end end b[#b+1]={"New "..H.name..": "..H.n.." tiles, "..H.level,t}end b[#b+1]={"How to play","help"}return b end local aG={left={-1,0},right={1,0},up={0,-1},down={0,1}}function key(d)if A=="menu"then local F=aB()if B>#F then B=#F end if d=="up"then B=(B-2)%#F+1 elseif d=="down"then B=B%#F+1 elseif d=="ok"then local c=F[B][2]s=nil if c=="help"then A="help";return end if type(c)=="number"then aw(c);return end A="play"if c=="hint"then aL()elseif c=="undo"then aV();ab()elseif c=="shuffle"then aD()end end return end if A=="help"then if d=="top"or d=="ok"then A="menu"end return end s=nil if d=="top"then A,B="menu",1 return end local I=aG[d]if I then if j>0 then aQ(I[1],I[2])end return end if d~="ok"then return end if j==0 then aw(O);return end if aC()==0 then aD();return end if not r(e)then e=V(e);return end if i==0 then i=e elseif i==e then i=0 s="Put it back"elseif N(i)==N(e)then aU(i,e)else s="No match: picked "..ac(e).." instead"i=e end ab()end local function ai(h)return aF:sub(h+1,h+1)end local function aI()local _,L,aa={},{},{}for ad=0,ae-1 do for l=1,o do local a=ad*o+l local g=f[a]if g>0 then local d=N(a)-1 local aE=d//9 local ao=a==e and 1 or(a==i and 2 or 0)L[l]=ai(ao*9+d%9)if ao==0 then aa[l]=ai(27+(aE*3+g-1)*2+(r(a)and 0 or 1))else aa[l]=ai((ao==1 and 45 or 54)+aE*3+g-1)end else L[l],aa[l]=" "," "end end _[#_+1]=table.concat(L)_[#_+1]=table.concat(aa)end return table.concat(_,"/")end local function aT(P)if s then return s end if j==0 then return"Every tile matched. Well done!"end if P==0 then return"No pairs open. OK shuffles the rest."end if i>0 and i~=e then return"Picked "..ac(i)..": find its twin"end local z=ac(e)if i==e then return"Picked "..z..": find its twin"end return z..", layer "..f[e]..(r(e)and""or" (blocked)")end local function al(t)local a,aW=K[2*t-1],K[2*t]return S[t].name..": won "..aW.." of "..a end function draw()if A=="menu"then snail.title("Mahjong Solitaire")local F=aB()if B>#F then B=#F end for h,aN in ipairs(F)do snail.row(aN[1],h==B)end snail.gap()if aj()then snail.small(S[O].name.." in progress: "..j.." tiles left.")else snail.small("Every deal can be cleared.")end snail.small(al(1)..". "..al(2)..".")snail.small(al(3)..".")snail.hint("BACK leave OK choose")return end if A=="help"then snail.title("How to play")snail.text("Match pairs of identical free tiles to clear the board.")snail.text("A tile is free when nothing is on it and its left or right side is open.")snail.text("Free tiles are white below; blocked ones are shaded. Plates under a tile count its layer.")snail.text("Arrows jump between free tiles. OK picks a tile, OK on its twin takes both.")snail.text("BACK opens the menu: hint, undo, shuffle and new deals. Your game is kept.")snail.text("Every deal can be cleared. Hints follow a way through while you keep to it.")snail.hint("BACK back OK back")return end local P=aC()snail.title(S[O].name.."   "..j.." tiles   "..P..(P==1 and" pair"or" pairs"))snail.status(aT(P))local ag if j==0 then ag="Board cleared!"elseif P==0 then ag="No moves left"end snail.board(aI(),nil,ag)if j==0 then snail.hint("BACK menu OK new deal")elseif P==0 then snail.hint("BACK menu OK shuffle")elseif i>0 then snail.hint("BACK menu OK match L/R move")else snail.hint("BACK menu OK pick L/R move")end end function start()math.randomseed(os.time())snail.ink("fast")am(1)aO()if aj()then A="play"if not r(e)then e=V(e)end else A="menu"end end
