--!name Cribbage
--!icon cards
--!category Games
local br="!$%&()+,-0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnpqrstuvwx"local function N(a)return br:sub(a+1,a+1)end local bz,bu,aU,aW,aZ,aV,bA,aY=13,17,21,23,25,26,28,39 local bv={"A","2","3","4","5","6","7","8","9","10","J","Q","K"}local bw={[0]="clubs","diamonds","spades","hearts"}local aG={[0]="You","Fox"}local ag={[0]="your","Fox's"}local aX={"a","two","three","four","five","six","seven","eight"}local bt={"a pair","two pairs","pair royal","pair royal and a pair","","double pair royal"}local by={2200,1300,700}local bx={"relaxed","normal","fast"}local ab=121 local function F(c)return c%13+1 end local function M(c)return c%13<10 and c%13+1 or 10 end local function aI(c)return bv[c%13+1].." "..bw[c//13]end local function a6(H,ai)return H%13*4+H//13<ai%13*4+ai//13 end local aq={speed=2}local l={gp=0,gw=0,skunk=0,best=0,hp=0}local x={[0]=0,0}local p={[0]={},{}}local r={[0]={},{}}local C,w={},{}local o,aa={},{}local S,af,az={},{},0 local g="none"local q,s,n,P,O,A,am=1,0,0,-1,-1,0,-1 local al,aB=0,""local as=0 local E={}local G,I,m="menu",1,1 local ap local function bW(d)for a=#d,2,-1 do local X=math.random(a)d[a],d[X]=d[X],d[a]end end local function _(b)E[#E+1]=b if#E>4 then table.remove(E,1)end end local function bQ()local b=""for a=#E,1,-1 do local d=b==""and E[a]or(E[a].." "..b)if#d>92 then break end b=d end return b end local function an(d,c)for a=1,#d do if d[a]==c then return a end end end local function aA()return g=="peg"and s~=0 end local function aM(f)return f..(f==1 and" point"or" points")end local function bm(t)local v,J=0,1 for h=1,14 do local f=t[h]or 0 if f>0 then v,J=v+1,J*f else if v>=3 then return v,J end v,J=0,1 end end return 0,1 end local function bG(i,c,ao)local a8={i[1],i[2],i[3],i[4],c}local k=0 for bR=1,31 do local b=0 for a=1,5 do if bR&(1<<(a-1))~=0 then b=b+M(a8[a])end end if b==15 then k=k+1 end end local t,T={},0 for a=1,5 do local h=F(a8[a])T=T+(t[h]or 0)t[h]=(t[h]or 0)+1 end local v,J=bm(t)local aC,U=0,i[1]//13 if i[2]//13==U and i[3]//13==U and i[4]//13==U then if c//13==U then aC=5 elseif not ao then aC=4 end end local bi=0 for a=1,4 do if F(i[a])==11 and i[a]//13==c//13 then bi=1 end end local D,aR,L={},{},0 local function ah(f,bO,d)L=L+f d[#d+1]=bO.." "..L end for ac=1,k do ah(2,"fifteen",D)end L=0 if k>0 then ah(2*k,aX[k]..(k==1 and" fifteen"or" fifteens"),aR)end local function bl(d)if T>0 then ah(2*T,bt[T],d)end if v>0 then ah(v*J,(J==1 and""or(aX[J].." ")).."run"..(J==1 and""or"s").." of "..v,d)end if aC>0 then ah(aC,"flush",d)end if bi>0 then ah(1,"nobs",d)end end bl(aR)L=2*k bl(D)if L==0 then return 0,"Nineteen: no points."end local b=table.concat(D,", ")if#b>90 then b=table.concat(aR,", ")end return L,b:sub(1,1):upper()..b:sub(2).."."end local function bk()local f=#o local e,d=0,{}if n==15 then e=2;d[#d+1]="fifteen for 2"end if n==31 then e=e+2;d[#d+1]="31 for 2"end local j=1 while j<f and F(o[f-j])==F(o[f])do j=j+1 end if j>=2 then e=e+j*(j-1)d[#d+1]=({"a pair for 2","pair royal for 6","double pair royal for 12"})[j-1]end for v=f,3,-1 do local Y,ay,bn,aD=99,0,{},true for a=f-v+1,f do local h=F(o[a])if bn[h]then aD=false;break end bn[h]=true if h<Y then Y=h end if h>ay then ay=h end end if aD and ay-Y==v-1 then e=e+v d[#d+1]="run of "..v.." for "..v break end end return e,d end local function at(e,f)if f<=0 or g=="over"then return end x[e]=x[e]+f if x[e]>=ab then x[e]=ab g,as="over",e l.gp=l.gp+1 if e==0 then l.gw=l.gw+1 if x[1]<91 then l.skunk=l.skunk+1 end end end end local function bL(Q,bY)local t,aE,ar,aF={},{},{[0]=1},{[0]=0,0,0,0}for h=1,13 do t[h],aE[h]=0,4 end for b=1,15 do ar[b]=0 end for ac,c in ipairs(bY)do aE[F(c)]=aE[F(c)]-1 aF[c//13]=aF[c//13]+1 end local T,ax=0,0 for ac,c in ipairs(Q)do local y,h=M(c),F(c)for b=15,y,-1 do ar[b]=ar[b]+ar[b-y]end T=T+t[h]t[h]=t[h]+1 if h==11 then ax=ax+(13-aF[c//13])/46 end end local U=Q[1]//13 if Q[2]//13==U and Q[3]//13==U and Q[4]//13==U then ax=ax+4+(13-aF[U])/46 end local L=0 for h=1,13 do local f=aE[h]if f>0 then local y=h<10 and h or 10 t[h]=t[h]+1 local v,J=bm(t)t[h]=t[h]-1 L=L+f*(2*(ar[15]+ar[15-y])+2*(T+t[h])+v*J)end end return L/46+ax end local function bH(H,ai)local aN,aO=F(H),F(ai)local B=0 if aN==aO then B=B+2 end if M(H)+M(ai)==15 then B=B+2 end if aN==5 then B=B+1.5 end if aO==5 then B=B+1.5 end local u=math.abs(aN-aO)if u==1 then B=B+1 elseif u==2 then B=B+0.5 end if H//13==ai//13 then B=B+0.2 end return B end local function bC()local i=p[1]local bX=q==1 and 1 or-1 local av,a2,a4=1,2,-1e9 for a=1,5 do for X=a+1,6 do local Q={}for j=1,6 do if j~=a and j~=X then Q[#Q+1]=i[j]end end local y=bL(Q,i)+bX*bH(i[a],i[X])+math.random()*0.01 if y>a4 then av,a2,a4=a,X,y end end end return av,a2 end local function bD()local i=r[1]local av,a3=nil,-1e9 for a,c in ipairs(i)do local y=M(c)local R=n+y if R<=31 then o[#o+1]=c n=R local e=bk()o[#o]=nil n=R-y local b=e*10+y*0.1+math.random()*0.05 if R==5 or R==21 then b=b-6 end if R>4 and R<15 then b=b-2 end if R>20 and R<31 then b=b-1 end if n==0 then if y<5 then b=b+3 end if y==5 then b=b-5 end end if#o>0 then local u=math.abs(F(c)-F(o[#o]))if u>0 and u<=2 and e==0 then b=b-1 end end if b>a3 then av,a3=a,b end end end return av end local function a7(e)for ac,c in ipairs(r[e])do if n+M(c)<=31 then return true end end return false end local function aT()local i=r[0]if m>#i then m=#i end if m<1 then m=1 end if not(i[m]and n+M(i[m])<=31)then for a=1,#i do if n+M(i[a])<=31 then m=a;break end end end end local function bf()q=1-q local u={}for c=0,51 do u[c+1]=c end bW(u)for e=0,1 do local i={}for a=1,6 do i[a]=u[e*6+a]end table.sort(i,a6)p[e],r[e]=i,{}end C,w,o,aa,S,af={},{},{},{},{},{}n,P,O,A,am,az=0,-1,-1,0,-1,0 g,m="discard",1 l.hp=l.hp+1 E={}_((q==0 and"Your deal"or"Fox deals")..": choose two cards for "..ag[q].." crib.")end local function aK()S,af,az=o,aa,n o,aa,n,am={},{},0,-1 end local function bB(e)local ae=1-e if n==31 then aK()end if#r[0]+#r[1]==0 then if n>0 then _(aG[P]..(P==0 and" take"or" takes").." 1 for the last card.")at(P,1)aK()end s=2 return end if a7(ae)then s=ae;return end if a7(e)then if am~=ae then _(ae==0 and(#r[0]>0 and"You can't play: go."or"You're out of cards.")or(#r[1]>0 and"Fox says go."or"Fox is out of cards."))am=ae end s=e return end _(aG[P]..(P==0 and" take"or" takes").." 1 for the go.")at(P,1)aK()s=#r[ae]>0 and ae or e end local function bj(e,a)local c=table.remove(r[e],a)S,af={},{}o[#o+1]=c aa[#o]=e n=n+M(c)P=e local f,d=bk()_(aG[e].." played "..aI(c).." ("..n..")"..(f>0 and(": "..table.concat(d,", "))or"")..".")at(e,f)if g=="over"then return end bB(e)if s==0 then aT()end end local function aS(b)if b==3 then return q,C,true end local e=b==1 and 1-q or q return e,p[e],false end local function aL(bM)local e,i,ao=aS(A)al,aB=bG(i,O,ao)if bM then if e==0 and al>l.best then l.best=al end _(ag[e]:sub(1,1):upper()..ag[e]:sub(2)..(ao and" crib: "or" hand: ")..aM(al)..".")at(e,al)end end local function bZ()g,A="count",1 aL(true)end local function b0()for ac,c in ipairs(w)do table.remove(p[0],an(p[0],c))C[#C+1]=c end local a,X=bC()C[#C+1]=table.remove(p[1],X)C[#C+1]=table.remove(p[1],a)table.sort(C,a6)w={}local u={}for c=0,51 do if not(an(p[0],c)or an(p[1],c)or an(C,c))then u[#u+1]=c end end O=u[math.random(#u)]_("The cut: "..aI(O)..".")g="peg"if F(O)==11 then _("His heels: "..(q==0 and"you score 2."or"Fox scores 2."))at(q,2)if g=="over"then return end end for e=0,1 do r[e]={}for j,c in ipairs(p[e])do r[e][j]=c end end s=1-q if s==0 then m=1;aT()end end local function W(d)local b={}for a=1,#d do b[a]=string.char(65+d[a])end return table.concat(b)end local function V(b)local d={}for a=1,#(b or"")do local c=b:byte(a)-65 if c<0 or c>51 then return{}end d[a]=c end return d end local function bb(d)local b={}for a=1,#d do b[a]=d[a]end return table.concat(b)end local function bq(b)local d={}for a=1,#(b or"")do d[a]=b:byte(a)==49 and 1 or 0 end return d end local function aP()local k={"C1",aq.speed,l.gp,l.gw,l.skunk,l.best,l.hp}if g~="none"then local ad={g,q,x[0],x[1],s,n,P,O,A,am,az,as,W(p[0]),W(p[1]),W(r[0]),W(r[1]),W(C),W(w),W(o),bb(aa),W(S),bb(af),table.concat(E,"|")}for ac,y in ipairs(ad)do k[#k+1]=y end end snail.save(table.concat(k,";"))end local function bP()local b=snail.load()if type(b)~="string"or b:sub(1,3)~="C1;"then return end local k={}for y in(b..";"):gmatch("([^;]*);")do k[#k+1]=y end local function f(a,Y,ay,bI)local z=math.tointeger(tonumber(k[a]))if not z or z<Y or z>ay then return bI end return z end aq.speed=f(2,1,3,2)l.gp,l.gw,l.skunk,l.best,l.hp=f(3,0,1e9,0),f(4,0,1e9,0),f(5,0,1e9,0),f(6,0,29,0),f(7,0,1e9,0)local Z=k[8]if#k<30 or not(Z=="discard"or Z=="peg"or Z=="count"or Z=="over")then return end q,x[0],x[1],s,n,P=f(9,0,1,0),f(10,0,ab,0),f(11,0,ab,0),f(12,0,2,0),f(13,0,31,0),f(14,-1,1,-1)O,A,am,az,as=f(15,-1,51,-1),f(16,0,3,0),f(17,-1,1,-1),f(18,0,31,0),f(19,0,1,0)p[0],p[1],r[0],r[1]=V(k[20]),V(k[21]),V(k[22]),V(k[23])C,w,o,aa=V(k[24]),V(k[25]),V(k[26]),bq(k[27])S,af=V(k[28]),bq(k[29])local bh=#p[0]+#p[1]+#C local aD=Z=="discard"and bh==12 or(Z~="discard"and bh==12 and#C==4 and O>=0)if not aD or#aa~=#o or#af~=#S or(Z=="count"and A<1)then g="none"return end E={}for d in(k[30]or""):gmatch("[^|]+")do E[#E+1]=d end g=Z if g=="count"or(g=="over"and A>0)then aL(false)end if g=="peg"and s==0 then aT()end end local function aQ()if G=="game"and aA()then snail.tick(by[aq.speed])else snail.tick(0)end end local function au()aP()aQ()end local function bg()x[0],x[1]=0,0 q=math.random(0,1)bf()table.insert(E,1,q==0 and"You cut low and deal first."or"Fox cuts low and deals first.")G="game"au()end local function a0()if s==2 then bZ()else bj(1,bD())end au()end local function be()local d={}if g~="none"then d[#d+1]={"Continue the game","resume"}end d[#d+1]={"New game","new"}d[#d+1]={"Computer speed: "..bx[aq.speed],"speed"}d[#d+1]={"Statistics","stats"}d[#d+1]={"How to play","help"}return d end local function bT(j)local D=be()if I>#D then I=#D end if j=="up"then I=(I-2)%#D+1;return end if j=="down"then I=I%#D+1;return end local H=D[I][2]local u=j=="left"and-1 or((j=="right"or j=="ok")and 1 or 0)if u==0 then return end if H=="speed"then aq.speed=(aq.speed-1+u)%3+1 aP()elseif j=="ok"then if H=="resume"then G="game";aQ()elseif H=="new"then bg()else G=H end end end local function bJ(j)local i=p[0]local f=#i+1 if j=="left"then m=(m-2)%f+1 elseif j=="right"then m=m%f+1 elseif j=="ok"then if m==f then if#w==2 then b0();au()else ap="Pick "..(2-#w).." more for the crib"end return end local c=i[m]local a=an(w,c)if a then table.remove(w,a)elseif#w==2 then ap="Two picked: OK one again to put it back"return else w[#w+1]=c if#w==2 then m=f end end aP()end end function key(j)ap=nil if G=="menu"then return bT(j)end if G~="game"then if j=="top"or j=="ok"then G="menu"end return end if j=="top"then G,I="menu",1 snail.tick(0)return end if g=="over"then if j=="ok"then bg()end return end if g=="discard"then return bJ(j)end if g=="count"then if j=="ok"then if A<3 then A=A+1 aL(true)else bf()end au()end return end if aA()then if j=="ok"then a0()end return end local i=r[0]if j=="left"then m=(m-2)%#i+1 elseif j=="right"then m=m%#i+1 elseif j=="ok"then local c=i[m]if n+M(c)>31 then ap=aI(c).." would take the count past 31"return end bj(0,m)au()end end function tick()if G=="game"and aA()then a0()else snail.tick(0)end end local function bE()local ad={}for h=1,12 do ad[h]={".",".",".",".",".",".",".",".",".",".",".",".","."}end local function K(h,c,b)ad[h+1][c+1]=b end local function aj(h,c,z,bS)if z<0 then K(h,c,N(aU))K(h+1,c,N(aU+1))else K(h,c,N(z%13))K(h+1,c,N((bS and bu or bz)+z//13))end end for e=0,1 do local a_=g=="peg"and s==e K(e,0,N(e==0 and(a_ and aY+1 or aY)or(a_ and aW+1 or aW)))for j=0,11 do local k=x[e]-10*j K(e,j+1,N(bA+(k<0 and 0 or(k>10 and 10 or k))))end end local ak=g=="count"or(g=="over"and A>0)local ba,bF,aJ=aS(A)local bc=ak and g=="over"local bd=g=="peg"and r[1]or p[1]if not(bc or ak and ba==1 and not aJ)then local aH=(11-#bd)//2 for a,z in ipairs(bd)do aj(3,aH+a-1,ak and z or-1)end end local aw=q==0 and 9 or 3 local bo=g=="discard"and m>#p[0]if not(ak and aJ)then K(bo and aw-1 or aw,12,N(aV))K((bo and aw-1 or aw)+1,12,N(aV+1))if g=="discard"and#w==2 then K(aw+2,12,N(aZ))end end if ak then local h=g=="over"and 7 or 6 for a=1,4 do aj(h,2+a,bF[a])end aj(h,8,O)else local b,a5=o,aa if#b==0 then b,a5=S,af end for a,z in ipairs(b)do aj(a5[a]==1 and 5 or 6,a-1,z)end if g=="peg"then K(6,9,"{"..(#o==0 and#S>0 and az or n).."}")end aj(5,11,O)end local i=g=="peg"and r[0]or p[0]if not(bc or ak and ba==0 and not aJ)then local bU=g=="discard"or(g=="peg"and s==0)local aH=(11-#i)//2 for a,z in ipairs(i)do local a9=aH+a-1 aj((bU and a==m)and 8 or 9,a9,z,g=="discard"and an(w,z))if g=="peg"and s==0 and n+M(z)<=31 then K(11,a9,N(aZ))end end end for h=1,12 do ad[h]=table.concat(ad[h])end return table.concat(ad,"/")end local function bp()local e,ac,ao=aS(A)local b=ag[e]..(ao and" crib"or" hand")return b:sub(1,1):upper()..b:sub(2)end local function b_()if ap then return ap end if g=="over"then local Y=x[1-as]return(as==0 and"You win "or"Fox wins ")..ab.." to "..Y..(Y<91 and": skunk!"or"!")end if g=="discard"then if#w==2 and m>#p[0]then return"OK puts them in "..ag[q].." crib"end return"Pick 2 for "..ag[q].." crib ("..#w.." picked)"end if g=="count"then return bp()..": "..aM(al)end if s==2 then return"The play is over: the count is next"end if#o==0 and#S>0 then return(s==0 and"Your lead"or"Fox leads")..": the count starts again"end if s==1 then return"Fox's turn... count "..n end return"Your turn: count "..n end local function bK()snail.title("Cribbage - "..(q==0 and"your deal"or"Fox's deal")..", to "..ab)snail.status(b_())if g=="count"then snail.text(aB)elseif g=="over"and A>0 then local b=bp()..": "..aB snail.text(#b>95 and aB or b)else snail.text(bQ())end local a1 if g=="over"then a1=as==0 and"You win the game!"or"Fox wins the game"end snail.board(bE(),nil,a1)snail.small("Score: you "..x[0]..", Fox "..x[1]..". "..(q==0 and"Your crib."or"Fox's crib."))if g=="over"then snail.hint("BACK options OK new game")elseif g=="discard"then snail.hint(m>#p[0]and"BACK options OK crib L/R card"or"BACK options OK pick L/R card")elseif g=="count"then snail.hint(A<3 and"BACK options OK next"or"BACK options OK deal")elseif s==2 then snail.hint("BACK options OK count")elseif aA()then snail.hint("BACK options OK hurry")else snail.hint("BACK options OK play L/R card")end end local bs={"First to 121 wins. Six cards each; both put two face down in the dealer's crib.","A card is cut and shared by every hand. A jack cut scores 2 for the dealer (his heels).","Play: lay cards in turn, adding up to 31. Make 15 or 31 for 2, a pair 2, three alike 6.","Three or more in a run score one a card. Can't play? Go: the last card under 31 scores 1.","Then the hands count with the cut card: the non-dealer first, then the dealer, then the crib.","Fifteens 2 each, pairs 2, runs a card each, four of a suit 4 (the crib needs all five), nobs 1.","Nobs: a jack of the cut card's suit. The raised card is chosen; arrows mark cards you can play.",}function draw()if G=="game"then return bK()end if G=="stats"then snail.title("Statistics")local bV=l.gp>0 and(l.gw*100//l.gp)or 0 snail.text("Deals played: "..l.hp)snail.text("Best hand or crib: "..aM(l.best))snail.gap()snail.text("Games played: "..l.gp)snail.text("Games won: "..l.gw.." ("..bV.."%)")snail.text("Skunks (Fox under 91): "..l.skunk)snail.hint("BACK back OK back")return end if G=="help"then snail.title("How to play")for ac,b in ipairs(bs)do snail.text(b)end snail.hint("BACK back OK back")return end snail.title("Cribbage")local D=be()if I>#D then I=#D end for a,bN in ipairs(D)do snail.row(bN[1],a==I)end snail.gap()if g~="none"then snail.small("Score: you "..x[0]..", Fox "..x[1]..", to "..ab)else snail.small("Fifteen two, fifteen four... first to 121 against Fox.")end snail.hint("BACK leave OK choose L/R change UP/DN move")end function start()math.randomseed(os.time())bP()if g~="none"then G="game"aQ()end end
