--!name Hearts
--!icon cards
--!category Games
local bb="!$%&()+,-0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnpqrstuvwx"local function N(d)return bb:sub(d+1,d+1)end local bm,bh,bc,a9,at,bg,be,ba=13,17,22,25,29,30,33,34 local bi={"2","3","4","5","6","7","8","9","10","J","Q","K","A"}local au={[0]="clubs","diamonds","spades","hearts"}local bj={[0]="club","diamond","spade","heart"}local J={[0]="You","Fox","Owl","Cub"}local as={[0]="left","right","across"}local bf={[0]=1,3,2}local av={50,100}local bl={2200,1300,700}local bk={"relaxed","normal","fast"}local v=36 local function Q(b)return b//13 end local function aR(b)return bi[b%13+1].." "..au[b//13]end local function aB(b)return b>=39 or b==v end local function bG(b)return b==v and 13 or(b>=39 and 1 or 0)end local B={target=2,speed=2}local k={hp=0,gp=0,gw=0,moon=0,best=999}local q={[0]={},{},{},{}}local u={[0]=0,0,0,0}local I={[0]=0,0,0,0}local Y={[0]=0,0,0,0}local r={}local af={}local g="none"local G,X,K,y,p,M,V,A=0,100,0,0,0,0,0,-1 local R=false local s,P={},{}local F={}local D,H,m="menu",1,1 local aa local function bH(h)for d=#h,2,-1 do local aU=math.random(d)h[d],h[aU]=h[aU],h[d]end end local function U(e)F[#F+1]=e if#F>4 then table.remove(F,1)end end local function bA()local e=""for d=#F,1,-1 do local h=e==""and F[d]or(F[d].." "..e)if#h>92 then break end e=h end return e end local function T(h,b)for d=1,#h do if h[d]==b then return d end end end local function ab()return(G-1)%4 end local function aF(a)return(a+bf[ab()])%4 end local function ag(a,i)return J[a].." "..i..(a==0 and""or"s")end local function S()return g=="play"and(p==4 or y~=0)end local function aT(f)local c={[0]=0,0,0,0}for E,n in ipairs(f)do c[n//13]=c[n//13]+1 end return c end local function a7(a,b)local f=q[a]if p==0 then if M==0 then return b~=0 and"Lead the 2 of clubs"or nil end if b>=39 and not R then for E,n in ipairs(f)do if n<39 then return"Hearts aren't broken yet"end end end return nil end local z=Q(r[K])if Q(b)==z then return nil end for E,n in ipairs(f)do if Q(n)==z then return"Follow suit: play a "..bj[z]end end if M==0 and aB(b)then for E,n in ipairs(f)do if not aB(n)then return"No points on the first trick"end end end return nil end local function al(a,b)return not a7(a,b)end local function ar()local f=q[0]if m>#f then m=#f end if m<1 then m=1 end if not(f[m]and al(0,f[m]))then for d=1,#f do if al(0,f[d])then m=d;break end end end end local function a2()for a=0,3 do if T(q[a],0)then K,y=a,a end end g,P="play",{}U(y==0 and"You hold the 2 of clubs: lead it."or(J[y].." leads the 2 of clubs."))if y==0 then m=1;ar()end end local function a_()local _={}for b=0,51 do _[b+1]=b end bH(_)for a=0,3 do local f={}for d=1,13 do f[d]=_[a*13+d]end table.sort(f)q[a],I[a]=f,0 end G=G+1 r,af,p,M,R,s,P={},{},0,0,false,{},{}F={}m=1 if ab()<3 then g="pass"U("Hand "..G..": pass three cards "..as[ab()].." to "..J[aF(0)]..".")else U("Hand "..G..": no passing this time.")a2()end end local function bw()A=-1 for a=0,3 do if I[a]==26 then A=a end end for a=0,3 do Y[a]=A<0 and I[a]or(a==A and 0 or 26)u[a]=u[a]+Y[a]end k.hp=k.hp+1 if A==0 then k.moon=k.moon+1 end if A>=0 then U(ag(A,"shoot").." the moon: 26 to everyone else!")end local ae,x=0,999 for a=0,3 do if u[a]>ae then ae=u[a]end if u[a]<x then x=u[a]end end g="done"if ae>=X then g="over"k.gp=k.gp+1 if u[0]==x then k.gw=k.gw+1 if x<k.best then k.best=x end end end end local function aK()local c=0 for a=0,3 do if r[a]then c=c+bG(r[a])end end return c end local function a5()local z,t=Q(r[K]),K for C=0,3 do local b=r[C]if b and Q(b)==z and b>r[t]then t=C end end return r[t],t end local function a1(a,d)local b=table.remove(q[a],d)if p==0 then K=a end r[a]=b af[b]=true p=p+1 U(J[a]..(p==1 and" led "or" played ")..aR(b)..".")if b>=39 and not R then R=true U("Hearts are broken.")end if p<4 then y=(a+1)%4 return end local E,t=a5()local c=aK()V,y=t,t I[t]=I[t]+c U(ag(t,"take").." the trick"..(c>0 and(": "..c..(c==1 and" point."or" points."))or"."))end local function bs()r,p,K={},0,V M=M+1 if M==13 then bw()end end local function bp(a)local f=q[a]local c=aT(f)local aA=T(f,v)local ap={}for d,b in ipairs(f)do local e,j=b//13,b%13 local i=j if e==3 then i=j+3 elseif b==v then i=c[2]>=5 and 0 or 30 elseif e==2 then if j>10 then i=(c[2]>=5 or(aA and c[2]>=4))and 2 or 20 else i=j-9 end elseif c[e]<=3 then i=j+9-3*c[e]end if b==0 then i=-9 end ap[d]=i+math.random()*0.5 end local aE={}for E=1,3 do local aj=1 for d=2,#f do if ap[d]>ap[aj]then aj=d end end aE[#aE+1]=f[aj]ap[aj]=-99 end return aE end local function bI(a)local ai=0 for C=0,3 do ai=ai+I[C]end if ai<8 then return nil end for C=0,3 do if C~=a and I[C]==ai then return C end end end local function bo(a)local f=q[a]local W={}for d,b in ipairs(f)do if al(a,b)then W[#W+1]=d end end if#W==1 then return W[1]end local c=aT(f)local aA=T(f,v)local aG=not af[v]and not aA local an=bI(a)local aO,aP=W[1],-1e9 local t,a8,z if p>0 then t,a8=a5()z=Q(t)end local bx=p>0 and Q(f[W[1]])==z local aV,bF=p==3,aK()for E,d in ipairs(W)do local b=f[d]local e,j=b//13,b%13 local i if p==0 then i=-j-(e==3 and 6 or 0)+(4-c[e])*0.3 if e==2 and aG and j<10 then i=i+8 end if e==2 and j>=10 and(aG or b==v)then i=i-30 end if an then i=j+(e==3 and 8 or 0)-(b==v and 40 or 0)end elseif bx then if an then i=b>t and(40-j)or-j elseif z==2 and b==v and t>v then i=100 elseif aV and bF==0 and b>t and b~=v then i=70+j elseif b<t then i=50+j elseif aV then i=j-(b==v and 60 or 0)else i=-j-(b==v and 60 or 0)end else i=j+(e==3 and 14 or 0)+(13-c[e])*0.2 if b==v then i=100 end if e==2 and j>10 and aG then i=i+40 end if an and an==a8 and aB(b)then i=-50-i end end i=i+math.random()*0.1 if i>aP then aO,aP=d,i end end return aO end local function ay(h)local e={}for d=1,#h do e[d]=string.char(65+h[d])end return table.concat(e)end local function ax(e)local h={}for d=1,#(e or"")do local b=e:byte(d)-65 if b<0 or b>51 then return nil end h[d]=b end return h end local function aH()local w={"H1",B.target,B.speed,k.hp,k.gp,k.gw,k.moon,k.best}if g~="none"then local o={g,G,X,K,y,p,M,V,A,R and 1 or 0}for a=0,3 do o[#o+1]=u[a]o[#o+1]=I[a]o[#o+1]=Y[a]o[#o+1]=r[a]or-1 o[#o+1]=ay(q[a])end o[#o+1]=ay(s)o[#o+1]=ay(P)o[#o+1]=table.concat(F,"|")for E,i in ipairs(o)do w[#w+1]=i end end snail.save(table.concat(w,";"))end local function bz()local e=snail.load()if type(e)~="string"or e:sub(1,3)~="H1;"then return end local w={}for i in(e..";"):gmatch("([^;]*);")do w[#w+1]=i end local function c(d,x,ae,bt)local n=math.tointeger(tonumber(w[d]))if not n or n<x or n>ae then return bt end return n end B.target,B.speed=c(2,1,2,2),c(3,1,3,2)k.hp,k.gp,k.gw,k.moon,k.best=c(4,0,1e9,0),c(5,0,1e9,0),c(6,0,1e9,0),c(7,0,1e9,0),c(8,0,999,999)local ac=w[9]if#w<41 or not(ac=="pass"or ac=="recv"or ac=="play"or ac=="done"or ac=="over")then return end G,X,K,y,p,M,V,A=c(10,1,1e6,1),c(11,50,100,100),c(12,0,3,0),c(13,0,3,0),c(14,0,4,0),c(15,0,13,0),c(16,0,3,0),c(17,-1,3,-1)R=w[18]=="1"local aJ,aq={},0 local function ao(b)if aJ[b]then aq=-999 end aJ[b]=true aq=aq+1 end r={}for a=0,3 do local l=19+a*5 u[a],I[a],Y[a]=c(l,0,1e6,0),c(l+1,0,26,0),c(l+2,0,26,0)local b=c(l+3,-1,51,-1)if b>=0 then r[a]=b;ao(b)end q[a]=ax(w[l+4])or{}for E,n in ipairs(q[a])do ao(n)end end s,P=ax(w[39])or{},ax(w[40])or{}local aD=0 for a=0,3 do if r[a]then aD=aD+1 end end if aq+M*4~=52 or aD~=p or(p>0 and not r[K])then g="none"return end af={}for b=0,51 do if not aJ[b]or(r[0]==b or r[1]==b or r[2]==b or r[3]==b)then af[b]=true end end F={}for h in(w[41]or""):gmatch("[^|]+")do F[#F+1]=h end g=ac if g=="play"and not S()then ar()end end local function aI()if D=="game"and S()then local aY=bl[B.speed]snail.tick(p==4 and aY*3//2 or aY)else snail.tick(0)end end local function Z()aH()aI()end local function aZ()X,G=av[B.target],0 for a=0,3 do u[a]=0 end a_()D="game"Z()end local function aN()if p==4 then bs()else a1(y,bo(y))end if g=="play"and not S()then ar()end Z()end local function bu()local ak={[0]=s}for a=1,3 do ak[a]=bp(a)end for a=0,3 do for E,b in ipairs(ak[a])do table.remove(q[a],T(q[a],b))end end local az=0 for a=0,3 do local C=aF(a)for E,b in ipairs(ak[a])do q[C][#q[C]+1]=b end table.sort(q[C])if C==0 then az=a end end P,s=ak[az],{}table.sort(P)local h={}for d,b in ipairs(P)do h[d]=aR(b)end U(J[az].." passed you "..table.concat(h,", ")..".")g="recv"m=T(q[0],P[1])end local function aW()local h={}if g~="none"then h[#h+1]={"Continue the game","resume"}end h[#h+1]={"New game","new"}h[#h+1]={"Play to: "..av[B.target].." points","target"}h[#h+1]={"Computer speed: "..bk[B.speed],"speed"}h[#h+1]={"Statistics","stats"}h[#h+1]={"How to play","help"}return h end local function bB(l)local O=aW()if H>#O then H=#O end if l=="up"then H=(H-2)%#O+1;return end if l=="down"then H=H%#O+1;return end local ad=O[H][2]local _=l=="left"and-1 or((l=="right"or l=="ok")and 1 or 0)if _==0 then return end if ad=="target"then B.target=3-B.target elseif ad=="speed"then B.speed=(B.speed-1+_)%3+1 elseif l=="ok"then if ad=="resume"then D="game";aI()elseif ad=="new"then aZ()else D=ad end return else return end aH()end local function bC(l)local f=q[0]local c=#f+1 if l=="left"then m=(m-2)%c+1 elseif l=="right"then m=m%c+1 elseif l=="ok"then if m==c then if#s==3 then bu();Z()else aa="Pick "..(3-#s).." more to pass"end return end local b=f[m]local d=T(s,b)if d then table.remove(s,d)elseif#s==3 then aa="Three picked: OK one again to put it back"return else s[#s+1]=b if#s==3 then m=c end end aH()end end function key(l)aa=nil if D=="menu"then return bB(l)end if D~="game"then if l=="top"or l=="ok"then D="menu"end return end if l=="top"then D,H="menu",1 snail.tick(0)return end if g=="done"or g=="over"then if l=="ok"then if g=="over"then aZ()else a_();Z()end end return end if g=="pass"then return bC(l)end if g=="recv"then if l=="ok"then a2();Z()end return end if S()then if l=="ok"then aN()end return end local f=q[0]if l=="left"then m=(m-2)%#f+1 elseif l=="right"then m=m%#f+1 elseif l=="ok"then local a6=a7(0,f[m])if a6 then aa=a6;return end a1(0,m)if g=="play"and not S()then ar()end Z()end end function tick()if D=="game"and S()then aN()else snail.tick(0)end end local aL={{3,0},{0,6},{3,12}}local aM={{4,0},{0,7},{4,12}}local ah={[0]={5,6},{3,4},{1,6},{3,8}}local function bq()local o={}for j=1,12 do o[j]={".",".",".",".",".",".",".",".",".",".",".",".","."}end local function L(j,b,e)o[j+1][b+1]=e end local function aQ(j,b,n,ao)L(j,b,N((n%13+1)%13))L(j+1,b,N((ao and bh or bm)+n//13))end local am=g=="play"local bn=am and(p==4 and V or y)or-1 for a=1,3 do L(aL[a][1],aL[a][2],N((bn==a and a9 or bc)+a))L(aM[a][1],aM[a][2],"{"..I[a].."}")end if R then L(0,0,N(ba))end for a=0,3 do if r[a]then aQ(ah[a][1],ah[a][2],r[a])end end if am and p==4 then L(ah[V][1]+2,ah[V][2],N(at))end local f=q[0]local aX=g=="pass"or(am and not S())if g=="pass"then local bJ=m>#f local j=bJ and 2 or 3 L(j,6,N(bg+ab()))L(j+1,6,N(be))if#s==3 then L(5,6,N(at))end end local br=(13-#f)//2 for d,n in ipairs(f)do local aS=br+d-1 local j=(aX and d==m)and 8 or 9 aQ(j,aS,n,(g=="pass"and T(s,n))or(g=="recv"and T(P,n)))if am and aX and al(0,n)then L(11,aS,N(at))end end for j=1,12 do o[j]=table.concat(o[j])end return table.concat(o,"/")end local function a3()if aa then return aa end if g=="over"then local x=999 for a=0,3 do if u[a]<x then x=u[a]end end if u[0]==x then return"You win the game with "..x.."!"end for a=1,3 do if u[a]==x then return J[a].." wins the game with "..x end end end if g=="done"then return A>=0 and(ag(A,"shoot").." the moon!")or("Hand "..G.." over: you took "..Y[0])end if g=="pass"then local a4=as[ab()].." to "..J[aF(0)]if#s==3 and m>#q[0]then return"OK passes your three cards "..a4 end return"Pick 3 to pass "..a4.." ("..#s.." picked)"end if g=="recv"then return"The raised cards are new: OK to play"end if p==4 then local c=aK()return ag(V,"take").." the trick"..(c>0 and(": +"..c)or"")end if y~=0 then return J[y].."'s turn..."end if p==0 then return M==0 and"Your lead: the 2 of clubs"or"Your lead"end local z=Q(r[K])for E,n in ipairs(q[0])do if Q(n)==z then return"Your turn: follow "..au[z]end end return"Your turn: no "..au[z]..", play any card"end local function aC(h,bE)local e={}for a=0,3 do e[#e+1]=J[a].." "..h[a]end return bE..table.concat(e,", ")end local function bv()local a0=ab()snail.title("Hearts - hand "..G..(a0<3 and(", pass "..as[a0])or", no pass"))snail.status(a3())snail.text(bA())local aw if g=="over"then aw=a3()elseif g=="done"then aw=A>=0 and(ag(A,"shoot").." the moon!")or("Hand "..G.." over")end snail.board(bq(),nil,aw)if g=="done"or g=="over"then local e={}for a=0,3 do e[#e+1]=J[a].." +"..Y[a]end snail.small("This hand: "..table.concat(e,", "))else snail.small(aC(I,"Taken: ")..(R and", hearts broken"or""))end snail.small(aC(u,"Game to "..X..": "))if g=="over"then snail.hint("BACK options OK new game")elseif g=="done"then snail.hint("BACK options OK next hand")elseif g=="pass"then snail.hint(m>#q[0]and"BACK options OK pass L/R card"or"BACK options OK pick L/R card")elseif g=="recv"then snail.hint("BACK options OK play")elseif S()then snail.hint("BACK options OK hurry")else snail.hint("BACK options OK play L/R card")end end local bd={"Take as few points as you can: each heart is 1, the queen of spades 13. Lowest score wins.","Pass three cards before each hand: left, right, across, then a hand with no passing.","The 2 of clubs leads. Follow suit if you can; the highest card of the suit led wins the trick.","Hearts can't be led until one has been played, unless you hold nothing else.","No hearts or queen of spades on the first trick, unless that's all you have.","Take all 26 points to shoot the moon: everyone else gets 26 instead.","The game ends when someone reaches the target score.","The raised card is the chosen one. Small arrows mark the cards you may play.",}function draw()if D=="game"then return bv()end if D=="stats"then snail.title("Statistics")local bD=k.gp>0 and(k.gw*100//k.gp)or 0 snail.text("Hands played: "..k.hp)snail.text("Moons you shot: "..k.moon)snail.gap()snail.text("Games played: "..k.gp)snail.text("Games won: "..k.gw.." ("..bD.."%)")snail.text("Best winning score: "..(k.best<999 and k.best or"none yet"))snail.hint("BACK back OK back")return end if D=="help"then snail.title("How to play")for E,e in ipairs(bd)do snail.text(e)end snail.hint("BACK back OK back")return end snail.title("Hearts")local O=aW()if H>#O then H=#O end for d,by in ipairs(O)do snail.row(by[1],d==H)end snail.gap()if g~="none"and X~=av[B.target]then snail.small("Play to takes effect in a new game.")elseif g~="none"then snail.small(aC(u,"Game to "..X..": "))else snail.small("Dodge the hearts and the queen of spades.")end snail.hint("BACK leave OK choose L/R change UP/DN move")end function start()math.randomseed(os.time())bz()if g~="none"then D="game"aI()end end
