--!name Spades
--!icon cards
--!category Games
local bA="!$%&()+,-0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnpqrstuvwxyz|~"local function ac(k)return bA:sub(k+1,k+1)end local bK,bE,a_,bB,bx,bF,bz,bL,by,bM=13,17,21,22,25,29,30,31,45,46 local D=2 local bG={"2","3","4","5","6","7","8","9","10","J","Q","K","A"}local aR={[0]="clubs","diamonds","spades","hearts"}local bH={[0]="club","diamond","spade","heart"}local V={[0]="You","Fox","Owl","Cub"}local cb={[0]="You and Owl","Fox and Cub"}local aS={250,300,500}local bJ={2200,1300,700}local bI={"relaxed","normal","fast"}local function ba(d)return bG[d%13+1].." "..aR[d//13]end local v={target=3,speed=2,blind=1}local o={hp=0,gp=0,gw=0,nt=0,nm=0,sets=0}local x={[0]={},{},{},{}}local r={[0]=-1,-1,-1,-1}local A={[0]=0,0,0,0}local J={[0]=0,0,0,0}local y={[0]=0,0}local K={[0]=0,0}local Q={[0]=0,0}local as={[0]="",""}local u,ae={},{}local aA={}local e="none"local Z,ah,Y,I,t,p,U,ai,S=0,500,0,0,0,0,0,0,-1 local ad,aj=false,false local R,ap=3,1 local L={}local G,O,B,az="menu",1,1,1 local H=false local aH local function b7(b)for k=#b,2,-1 do local bd=math.random(k)b[k],b[bd]=b[bd],b[k]end end local function ag(c)L[#L+1]=c if#L>4 then table.remove(L,1)end end local function b1()local c=""for k=#L,1,-1 do local b=c==""and L[k]or(L[k].." "..c)if#b>92 then break end c=b end return c end local function ax(b,d)for k=1,#b do if b[k]==d then return k end end end local function aO(a,l)return V[a].." "..l..(a==0 and""or"s")end local function aD(i)return i==0 and"nil"or tostring(i)end local function W()return(e=="bid"and t~=0)or(e=="play"and(p==4 or t~=0))end local function aT(n)local f={[0]=0,0,0,0}for M,q in ipairs(n)do f[q//13]=f[q//13]+1 end return f end local function bw(a,d)local n=x[a]if p==0 then if d//13==D and not ad then for M,q in ipairs(n)do if q//13~=D then return"Spades aren't broken yet"end end end return nil end local N=u[I]//13 if d//13==N then return nil end for M,q in ipairs(n)do if q//13==N then return"Follow suit: play a "..bH[N]end end return nil end local function aE(a,d)return not bw(a,d)end local function a5(E,i)if E//13==i//13 then return E>i end return E//13==D end local function bu()local j=I for m=0,3 do if u[m]and a5(u[m],u[j])then j=m end end return u[j],j end local function aC()local n=x[0]if B>#n then B=#n end if B<1 then B=1 end if not(n[B]and aE(0,n[B]))then for k=1,#n do if aE(0,n[k])then B=k;break end end end end local function aV(b)local i,j=0,0 for m=b,3,2 do if r[m]>0 then i,j=i+r[m],j+A[m]end end return i-j end local function aU(m)return r[m]==0 and A[m]==0 end local function bc(n)local f=aT(n)local aw={}for M,d in ipairs(n)do aw[d]=true end local w,z=0,f[D]local T=0 for c=0,3 do local h,aP,aQ,a1,bD=f[c],aw[c*13+12],aw[c*13+11],aw[c*13+10],aw[c*13+9]if c==D then if aP then T=T+1 end if aQ then T=T+(h>=2 and 1 or 0.4)end if a1 then T=T+(h>=3 and 0.8 or 0.2)end if bD and h>=4 then T=T+0.4 end w=w+T+math.max(0,h-4)*0.9 else local b2=h>=6 and 0.6 or 1 if aP then w=w+(h<=7 and 1 or 0.6)end if aQ then w=w+(h>=2 and 0.8 or 0.15)*b2 end if a1 and h>=3 and h<=5 then w=w+((aP or aQ)and 0.5 or 0.25)end end end local al=z-math.floor(T+0.5)for c=0,3 do if c~=D and al>0 then if f[c]==0 then local g=math.min(al,2)w,al=w+g*0.8,al-g elseif f[c]==1 then w,al=w+0.5,al-1 elseif f[c]==2 and al>=2 then w=w+0.2 end end end return w end local function b5(n)local f=aT(n)if f[D]>3 then return false end for M,d in ipairs(n)do local c,g=d//13,d%13 if g==12 or(c==D and g>=8)then return false end if g>=10 and f[c]<=2 then return false end end return true end local function bO(a)local n=x[a]local w=bc(n)local ak=r[(a+2)%4]if ak~=0 and w<1.3 and b5(n)then return 0 end local i=math.floor(w+0.6)if i<1 then i=1 end if ak>0 and ak+i>13 then i=math.max(1,13-ak)end return i end local function aq(d,a)for q=d+1,d//13*13+12 do if not aA[q]and not ax(x[a],q)then return false end end return true end local function bP(a)local n=x[a]local an={}for k,d in ipairs(n)do if aE(a,d)then an[#an+1]=k end end if#an==1 then return an[1]end local aa,b=(a+2)%4,a%2 local aJ,aK=(a+1)%4,(a+3)%4 local aW=aU(aJ)and aJ or(aU(aK)and aK or nil)local aG,bt=aV(b),aV(1-b)local ca=aG>0 or(bt>0 and bt<=13-U)local f=aT(n)local j,at,N if p>0 then j,at=bu()N=u[I]//13 end local ar=p==3 local P=ca and"win"or"duck"if r[a]==0 then P="nil"elseif aU(aa)and(p==0 or at==aa or not u[aa])then P="cover"elseif aW and(p==0 or at==aW or not u[aW])then P="set"end local aL=ar if p>0 and at==aa and not ar then aL=aq(j,a)if aL and j//13~=D then for X=1,3-p do local aX=(a+X)%4 if aX~=aa and J[aX]&(1<<N)~=0 and J[aX]&4==0 then aL=false end end end end local a6,a8=an[1],-1e9 for M,k in ipairs(an)do local d=n[k]local c,g=d//13,d%13 local z=c==D and 1 or 0 local l if p==0 then local a7=aq(d,a)if P=="nil"then l=-g-4*z elseif P=="cover"then l=(a7 and 80 or 0)+g elseif P=="set"or P=="duck"then l=30-g-5*z elseif a7 then l=z==1 and(f[D]>=3 and 55 or 45)or 60+g*0.1 if z==0 and(J[aJ]|J[aK])&(1<<c)~=0 then l=30 end else l=20-g*0.5+f[c]*0.5-25*z if(J[aJ]|J[aK])&(1<<c)~=0 then l=l-10 end if J[aa]&(1<<c)~=0 and J[aa]&4==0 then l=l+6 end if f[c]==1 and z==0 and f[D]>0 then l=l+4 end end else local am=a5(d,j)if P=="nil"then l=am and(ar and g or-g)or(100+g+5*z)elseif P=="cover"then if at==aa then l=am and(100-g+((not ar and aq(d,a))and 20 or 0))or-g else l=am and(50+g+(aq(d,a)and 20 or 0))or(-g-5*z)end elseif P=="set"then l=am and(-g-10*z)or(100+g-10*z)elseif P=="duck"then l=am and(ar and g-40 or-g-40)or(50+g)elseif at==aa then if aL then l=-g-20*z elseif am and aq(d,a)then l=50-g*0.1 else l=-g-20*z end elseif am then if ar then l=80-g-10*z elseif z==1 and N~=D then l=60-g elseif aq(d,a)then l=70-g*0.1 else l=40+g end else l=-g-20*z end end l=l+math.random()*0.1 if l>a8 then a6,a8=k,l end end return a6 end local function b9(b,F,aF)local c={}for m=b,3,2 do if r[m]==0 then c[#c+1]=V[m]..((aj and m==0)and" blind nil "or" nil ")..(A[m]==0 and"made"or"failed")end end local i,j=0,0 for m=b,3,2 do if r[m]>0 then i,j=i+r[m],j+A[m]end end if i>0 then c[#c+1]="bid "..i..", took "..j..(aF and""or" (set)")end return(F>=0 and"+"or"")..F..", "..table.concat(c,", ")end local function bX()for b=0,1 do local F,av,aF=0,0,true local i,j=0,0 for m=b,3,2 do if r[m]==0 then local l=(aj and m==0)and 200 or 100 F=F+(A[m]==0 and l or-l)+A[m]av=av+A[m]if m==0 then o.nt=o.nt+1 if A[m]==0 then o.nm=o.nm+1 end end else i,j=i+r[m],j+A[m]end end if i>0 then if j>=i then F,av=F+10*i+j-i,av+j-i else F,aF=F-10*i,false if b==1 then o.sets=o.sets+1 end end end K[b]=K[b]+av local bm=""while K[b]>=10 do K[b],F,bm=K[b]-10,F-100,", 10 bags -100"end Q[b]=F y[b]=y[b]+F as[b]=(b9(b,F,aF)..bm):sub(1,95)end o.hp=o.hp+1 e="done"local E,i=y[0],y[1]if(E>=ah or i>=ah)and E~=i then e="over"o.gp=o.gp+1 if E>i then o.gw=o.gw+1 end end end local function bp()e="play"I=(Y+1)%4 t=I ag(V[t]..(t==0 and" lead."or" leads."))if t==0 then B=1;aC()end end local function bl()for M=1,4 do if r[t]<0 then return true end t=(t+1)%4 end return false end local function bf(a,i)r[a]=i ag(aO(a,"bid").." "..aD(i)..".")t=(a+1)%4 if not bl()then bp()end end local function bk()local X={}for d=0,51 do X[d+1]=d end b7(X)for a=0,3 do local n={}for k=1,13 do n[k]=X[a*13+k]end table.sort(n)x[a],A[a],r[a],J[a]=n,0,-1,0 end Z,Y=Z+1,(Y+1)%4 u,ae,aA,p,U,ad,aj,S={},{},{},0,0,false,false,-1 L={}t=(Y+1)%4 ag("Hand "..Z..": "..(Y==0 and"your deal."or(V[Y].." deals.")))R=math.min(13,math.max(1,math.floor(bc(x[0])+0.6)))e="bid"if v.blind==2 and y[0]+100<=y[1]then e,ap="blind",1 end end local function bn(a,k)local d=table.remove(x[a],k)if p==0 then I=a elseif d//13~=u[I]//13 then J[a]=J[a]|(1<<(u[I]//13))end u[a]=d aA[d]=true p=p+1 ag(V[a]..(p==1 and" led "or" played ")..ba(d)..".")if d//13==D and not ad then ad=true ag("Spades are broken.")end if p<4 then t=(a+1)%4 return end local M,j=bu()ai,t=j,j A[j]=A[j]+1 ag(aO(j,"take").." the trick.")if r[j]==0 and A[j]==1 then ag(j==0 and"Your nil is broken."or(V[j].."'s nil is broken."))end end local function bS()ae,S=u,ai u,p,I={},0,ai U=U+1 if U==13 then bX()end end local function bW(b)local c={}for k=1,#b do c[k]=string.char(65+b[k])end return table.concat(c)end local function bT(c)local b={}for k=1,#(c or"")do local d=c:byte(k)-65 if d<0 or d>51 then return nil end b[k]=d end return b end local function bo()local C={"S1",v.target,v.speed,v.blind,o.hp,o.gp,o.gw,o.nt,o.nm,o.sets}if e~="none"then local s={e,Z,ah,Y,I,t,p,U,ai,S,ad and 1 or 0,aj and 1 or 0,R,y[0],y[1],K[0],K[1],Q[0],Q[1]}for a=0,3 do s[#s+1]=r[a]s[#s+1]=A[a]s[#s+1]=J[a]s[#s+1]=u[a]or-1 s[#s+1]=ae[a]or-1 s[#s+1]=bW(x[a])end s[#s+1]=as[0]s[#s+1]=as[1]s[#s+1]=table.concat(L,"|")for M,l in ipairs(s)do C[#C+1]=l end end snail.save(table.concat(C,";"))end local function b0()local c=snail.load()if type(c)~="string"or c:sub(1,3)~="S1;"then return end local C={}for l in(c..";"):gmatch("([^;]*);")do C[#C+1]=l end local function f(k,b_,bY,bU)local q=math.tointeger(tonumber(C[k]))if not q or q<b_ or q>bY then return bU end return q end v.target,v.speed,v.blind=f(2,1,3,3),f(3,1,3,2),f(4,1,2,1)o.hp,o.gp,o.gw,o.nt,o.nm,o.sets=f(5,0,1e9,0),f(6,0,1e9,0),f(7,0,1e9,0),f(8,0,1e9,0),f(9,0,1e9,0),f(10,0,1e9,0)local af=C[11]if#C<56 or not(af=="blind"or af=="bid"or af=="play"or af=="done"or af=="over")then return end Z,ah,Y,I,t,p=f(12,1,1e6,1),f(13,250,500,500),f(14,0,3,0),f(15,0,3,0),f(16,0,3,0),f(17,0,4,0)U,ai,S=f(18,0,13,0),f(19,0,3,0),f(20,-1,3,-1)ad,aj,R=C[21]=="1",C[22]=="1",f(23,0,13,3)y[0],y[1],K[0],K[1]=f(24,-1e6,1e6,0),f(25,-1e6,1e6,0),f(26,0,9,0),f(27,0,9,0)Q[0],Q[1]=f(28,-1e4,1e4,0),f(29,-1e4,1e4,0)local aN,aB={},0 u,ae={},{}local aI=0 for a=0,3 do local h=30+a*6 r[a],A[a],J[a]=f(h,-1,13,-1),f(h+1,0,13,0),f(h+2,0,15,0)local d=f(h+3,-1,51,-1)if d>=0 then u[a]=d aI=aI+1 if aN[d]then aB=-999 end aN[d]=true end d=f(h+4,-1,51,-1)if d>=0 then ae[a]=d end x[a]=bT(C[h+5])or{}for M,q in ipairs(x[a])do if aN[q]then aB=-999 end aN[q]=true aB=aB+1 end end as[0],as[1]=C[54]or"",C[55]or""local aY=0 for a=0,3 do aY=aY+A[a]end local ay=aB+aI+U*4==52 and aI==p and(p==0 or u[I])ay=ay and aY==U+(p==4 and 1 or 0)if af=="bid"or af=="blind"then ay=ay and U==0 and p==0 end if not ay then e="none"return end aA={}for d=0,51 do if not(ax(x[0],d)or ax(x[1],d)or ax(x[2],d)or ax(x[3],d))then aA[d]=true end end L={}for b in(C[56]or""):gmatch("[^|]+")do L[#L+1]=b end e=af if e=="play"and not W()then aC()end end local function aM()if G=="game"and W()and not H then local bh=bJ[v.speed]snail.tick(p==4 and bh*3//2 or bh)else snail.tick(0)end end local function ao()bo()aM()end local function bj()ah,Z=aS[v.target],0 for b=0,1 do y[b],K[b],Q[b]=0,0,0 end Y=math.random(0,3)bk()G,H="game",false ao()end local function a4()if e=="bid"then bf(t,bO(t))elseif p==4 then bS()else bn(t,bP(t))end if e=="play"and not W()then aC()end ao()end local function bg()local b={}if e~="none"then b[#b+1]={"Continue the game","resume"}end b[#b+1]={"New game","new"}b[#b+1]={"Play to: "..aS[v.target].." points","target"}b[#b+1]={"Blind nil: "..(v.blind==2 and"allowed"or"off"),"blind"}b[#b+1]={"Computer speed: "..bI[v.speed],"speed"}b[#b+1]={"Statistics","stats"}b[#b+1]={"How to play","help"}return b end local function b4(h)local _=bg()if O>#_ then O=#_ end if h=="up"then O=(O-2)%#_+1;return end if h=="down"then O=O%#_+1;return end local E=_[O][2]local X=h=="left"and-1 or((h=="right"or h=="ok")and 1 or 0)if X==0 then return end if E=="target"then v.target=(v.target-1+X)%3+1 elseif E=="blind"then v.blind=3-v.blind elseif E=="speed"then v.speed=(v.speed-1+X)%3+1 elseif h=="ok"then if E=="resume"then G="game";aM()elseif E=="new"then bj()else G,az=E,1 end return else return end bo()end function key(h)aH=nil if G=="menu"then return b4(h)end if G~="game"then if G=="help"and(h=="left"or h=="right")then az=3-az;return end if h=="top"or h=="ok"then G="menu"end return end if h=="top"then G,O,H="menu",1,false snail.tick(0)return end if H then H=false aM()return end if h=="up"and(e=="play"or e=="done")and S>=0 then H=true snail.tick(0)return end if e=="done"or e=="over"then if h=="ok"then if e=="over"then bj()else bk();ao()end end return end if e=="blind"then if h=="left"or h=="right"then ap=3-ap elseif h=="ok"then e="bid"if ap==2 then aj=true r[0]=0 ag("You bid blind nil.")end if not bl()then bp()end ao()end return end if W()then if h=="ok"then a4()end return end if e=="bid"then if h=="left"then R=(R-1)%14 elseif h=="right"then R=(R+1)%14 elseif h=="ok"then bf(0,R)if e=="play"and not W()then aC()end ao()end return end local n=x[0]if h=="left"then B=(B-2)%#n+1 elseif h=="right"then B=B%#n+1 elseif h=="ok"then local bv=bw(0,n[B])if bv then aH=bv;return end bn(0,B)if e=="play"and not W()then aC()end ao()end end function tick()if G=="game"and W()and not H then a4()else snail.tick(0)end end local aZ={[0]={6,2},{2,0},{0,5},{2,12}}local a0={[0]={6,3},{2,1},{0,6},{2,11}}local a3={[0]={6,4},{2,2},{0,7},{2,10}}local a2={[0]={5,6},{3,4},{1,6},{3,8}}local function bQ()local s={}for g=1,11 do s[g]={".",".",".",".",".",".",".",".",".",".",".",".","."}end local function ab(g,d,c)s[g+1][d+1]=c end local function a9(g,d,q,b3)if q<0 then ab(g,d,ac(a_))ab(g+1,d,ac(a_+1))else ab(g,d,ac((q%13+1)%13))ab(g+1,d,ac((b3 and bE or bK)+q//13))end end local be=e=="play"and not H local bN=(e=="bid"or be)and(p==4 and ai or t)or-1 for a=0,3 do ab(aZ[a][1],aZ[a][2],ac(a==0 and bM or(bN==a and bx or bB)+a))if r[a]>=0 then ab(a0[a][1],a0[a][2],ac((aj and a==0)and by or bL+r[a]))end if e~="bid"and e~="blind"then ab(a3[a][1],a3[a][2],"{"..A[a].."}")end end if ad then ab(0,0,ac(bz))end local b,j=u,(p==4 and ai or-1)if H then b,j=ae,S end for a=0,3 do if b[a]then a9(a2[a][1],a2[a][2],b[a],a==j)end end local n=x[0]local aG=be and not W()local bR=(13-#n)//2 for k,q in ipairs(n)do local bb=bR+k-1 a9((aG and k==B)and 7 or 8,bb,e=="blind"and-1 or q)if aG and aE(0,q)then ab(10,bb,ac(bF))end end for g=1,11 do s[g]=table.concat(s[g])end return table.concat(s,"/")end local function b8(b)local c={}for m=b,3,2 do if r[m]>=0 then c[#c+1]=r[m]==0 and"nil"or r[m]end end return table.concat(c,"+")end local function bq()if aH then return aH end if H then return S>=0 and("Last trick: "..aO(S,"take").." it")or""end if e=="over"then local j=y[0]>y[1]and 0 or 1 return(j==0 and"You and Owl win "or"Fox and Cub win ")..y[j].." to "..y[1-j]end if e=="done"then return"Hand "..Z..": us "..(Q[0]>=0 and"+"or"")..Q[0]..", them "..(Q[1]>=0 and"+"or"")..Q[1]end if e=="blind"then return ap==2 and"Bid blind nil, unseen, for 200?"or"Look at your cards, then bid"end if e=="bid"then if t~=0 then return V[t].." is bidding..."end local ak=r[2]return"Your bid: "..aD(R)..(ak>=0 and(", Owl bid "..aD(ak))or"")end if p==4 then return aO(ai,"take").." the trick"end if t~=0 then return V[t].."'s turn..."end if p==0 then return ad and"Your lead"or"Your lead: spades not broken"end local N=u[I]//13 for M,q in ipairs(x[0])do if q//13==N then return"Your turn: follow "..aR[N]end end return"Your turn: no "..aR[N]..", trump or throw"end local function bs(b)local c=(b==0 and"Us "or"Them ")..y[b].." ("..K[b]..(K[b]==1 and" bag)"or" bags)")if(e=="done"or e=="over")and not H then return(c..": "..as[b]):sub(1,95)end local br=b8(b)if br==""then return c..": no bids yet"end c=c..": bid "..br if e=="play"then local i,bi=0,aV(b)for m=b,3,2 do if r[m]>0 then i=i+r[m]end end if i>0 then c=c..(bi>0 and(", need "..bi)or", made")end end return c end local function bV()snail.title("Spades - hand "..Z..", game to "..ah)snail.status(bq())if H then local c={}for a=0,3 do if ae[a]then c[#c+1]=V[a].." "..ba(ae[a])end end snail.text(table.concat(c,", ")..".")else snail.text(b1())end local au if e=="over"then au=bq()elseif e=="done"and not H then au="Hand "..Z.." over"elseif e=="bid"and t==0 then au="< Bid "..aD(R).." >"elseif e=="blind"then au=ap==2 and"< Blind nil >"or"< Look at cards >"end snail.board(bQ(),nil,au)snail.small(bs(0))snail.small(bs(1))if H then snail.hint("BACK options OK return")elseif e=="over"then snail.hint("BACK options OK new game")elseif e=="done"then snail.hint("BACK options OK next hand UP last trick")elseif e=="blind"then snail.hint("BACK options OK choose L/R change")elseif W()then snail.hint(e=="play"and S>=0 and"BACK options OK hurry UP last trick"or"BACK options OK hurry")elseif e=="bid"then snail.hint("BACK options OK bid L/R change")else snail.hint(S>=0 and"BACK options OK play L/R card UP last trick"or"BACK options OK play L/R card")end end local bC={{"You and Owl (across) play Fox and Cub. The first team to reach the target score wins.","Each player bids the tricks they expect to take. A team must take its two bids added up.","Follow suit if you can. The highest spade wins the trick, else the highest card led.","Spades can't be led until one has been played, unless you hold nothing else.","Made: 10 a trick bid, plus 1 for each extra trick (a bag). Short: minus 10 a trick bid.","Every 10 bags collected cost your team 100 points.",},{"Nil: bid to take no tricks at all. Score 100 if you manage it, lose 100 if you don't.","A nil bidder's tricks don't count toward the partner's bid; they score 1 each, as bags.","Blind nil (an option, when 100 behind): bid nil before looking at your cards, for 200.","The bid starts at a suggested number. The small arrows mark the cards you may play.","Black tags show bids, white boxes the tricks taken. UP shows the last trick.",},}function draw()if G=="game"then return bV()end if G=="stats"then snail.title("Statistics")local b6=o.gp>0 and(o.gw*100//o.gp)or 0 snail.text("Hands played: "..o.hp)snail.text("Your nils made: "..o.nm.." of "..o.nt)snail.text("Times you set Fox and Cub: "..o.sets)snail.gap()snail.text("Games played: "..o.gp)snail.text("Games won: "..o.gw.." ("..b6.."%)")snail.hint("BACK back OK back")return end if G=="help"then snail.title("How to play ("..az.." of 2)")for M,c in ipairs(bC[az])do snail.text(c)end snail.hint("BACK back OK back L/R page")return end snail.title("Spades")local _=bg()if O>#_ then O=#_ end for k,bZ in ipairs(_)do snail.row(bZ[1],k==O)end snail.gap()if e~="none"and ah~=aS[v.target]then snail.small("Play to takes effect in a new game.")elseif e~="none"then snail.small("Score: us "..y[0]..", them "..y[1]..", game to "..ah)else snail.small("You and Owl against Fox and Cub. Bid, then make it.")end snail.hint("BACK leave OK choose L/R change UP/DN move")end function start()math.randomseed(os.time())b0()if e~="none"then G="game"aM()end end
