ref: a8083462e62459b2ae8a243dc4ba88416eba03b1
dir: /appl/spree/engines/whist.b/
implement Gatherengine; include "sys.m"; sys: Sys; include "draw.m"; include "sets.m"; sets: Sets; Set, All, None, A, B: import sets; include "../spree.m"; spree: Spree; Attributes, Range, Object, Clique, Member, rand: import spree; include "allow.m"; allow: Allow; include "cardlib.m"; cardlib: Cardlib; Selection, Cmember: import cardlib; dTOP, dLEFT, oRIGHT, EXPAND, FILLX, FILLY, Stackspec: import Cardlib; include "tricks.m"; tricks: Tricks; Trick: import tricks; include "../gather.m"; clique: ref Clique; CLICK, SAY: con iota; scores: ref Object; deck, pile: ref Object; hands, taken: array of ref Object; leader, turn: ref Cmember; trick: ref Trick; Trickpilespec := Stackspec( "display", # style 4, # maxcards 0, # conceal "trick pile" # title ); Handspec := Stackspec( "display", 13, 1, "" ); Takenspec := Stackspec( "pile", 52, 0, "tricks" ); clienttype(): string { return "cards"; } init(srvmod: Spree, g: ref Clique, nil: list of string, nil: int): string { sys = load Sys Sys->PATH; clique = g; spree = srvmod; allow = load Allow Allow->PATH; if (allow == nil) { sys->print("whist: cannot load %s: %r\n", Allow->PATH); return "bad module"; } allow->init(spree, clique); sets = load Sets Sets->PATH; if (sets == nil) { sys->print("spit: cannot load %s: %r\n", Sets->PATH); return "bad module"; } sets->init(); cardlib = load Cardlib Cardlib->PATH; if (cardlib == nil) { sys->print("whist: cannot load %s: %r\n", Cardlib->PATH); return "bad module"; } tricks = load Tricks Tricks->PATH; if (tricks == nil) { sys->print("hearts: cannot load %s: %r\n", Tricks->PATH); return "bad module"; } return nil; } maxmembers(): int { return 4; } readfile(nil: int, nil: big, nil: int): array of byte { return nil; } propose(members: array of string): string { if (len members < 2) return "need at least two members"; if (len members > 4) return "too many members"; return nil; } archive() { archiveobj := cardlib->archive(); allow->archive(archiveobj); cardlib->setarchivename(scores, "scores"); cardlib->setarchivename(deck, "deck"); cardlib->setarchivename(pile, "pile"); cardlib->archivearray(hands, "hands"); cardlib->archivearray(taken, "taken"); if (leader != nil) archiveobj.setattr("leader", string leader.ord, None); if (turn != nil) archiveobj.setattr("turn", string turn.ord, None); trick.archive(archiveobj, "trick"); } start(members: array of ref Member, archived: int) { cardlib->init(spree, clique); tricks->init(spree, clique, cardlib); if (archived) { archiveobj := cardlib->unarchive(); allow->unarchive(archiveobj); scores = cardlib->getarchiveobj("scores"); deck = cardlib->getarchiveobj("deck"); pile = cardlib->getarchiveobj("pile"); hands = cardlib->getarchivearray("hands"); taken = cardlib->getarchivearray("taken"); o := archiveobj.getattr("leader"); if (o != nil) leader = Cmember.index(int o); o = archiveobj.getattr("turn"); if (o != nil) turn = Cmember.index(int o); trick = Trick.unarchive(archiveobj, "trick"); } else { pset := None; for (i := 0; i < len members; i++) { Cmember.join(members[i], i); pset = pset.add(members[i].id); } # member 0 layout visible to member 0 and everyone else but other member. # could be All.del(members[1].id) but doing it this way extends to many-member cliques. Cmember.index(0).layout.lay.setvisibility(All.X(A&~B, pset).add(members[0].id)); deck = clique.newobject(nil, All, "stack"); cardlib->makecards(deck, (0, 13), nil); cardlib->shuffle(deck); scores = clique.newobject(nil, All, "scoretable"); startclique(); n := cardlib->nmembers(); leader = Cmember.index(rand(n)); starthand(); titles := ""; for (i = 0; i < n; i++) titles += members[i].name + " "; clique.newobject(scores, All, "score").setattr("score", titles, All); } } command(p: ref Member, cmd: string): string { (err, tag, toks) := allow->action(p, cmd); if (err != nil) return err; cp := Cmember.find(p); if (cp == nil) return "you're only watching"; case tag { CLICK => # click stackid index stack := p.obj(int hd tl toks); if (stack != trick.hands[cp.ord]) return "not yours"; err = trick.play(cp.ord, int hd tl tl toks); if (err != nil) return err; turn = turn.next(1); if (turn == leader) { # come full circle winner := Cmember.index(trick.winner); remark(sys->sprint("%s won the trick", winner.p.name)); cardlib->discard(pile, taken[winner.ord], 0); nmembers := cardlib->nmembers(); taken[winner.ord].setattr("title", string (len taken[winner.ord].children / nmembers) + " tricks", All); o := winner.obj; trick = nil; s := ""; for (i := 0; i < nmembers; i++) { if (i == winner.ord) s += "1 "; else s += "0 "; } clique.newobject(scores, All, "score").setattr("score", s, All); if (len hands[winner.ord].children > 0) { leader = turn = winner; trick = Trick.new(pile, -1, hands, nil); } else { remark("one round down, some to go"); leader = turn = nil; # XXX this round over } } canplay(turn); SAY => clique.action("say member " + string p.id + ": '" + joinwords(tl toks) + "'", nil, nil, All); } return nil; } startclique() { entry := clique.newobject(nil, All, "widget entry"); entry.setattr("command", "say", All); cardlib->addlayobj("entry", nil, nil, dTOP|FILLX, entry); cardlib->addlayframe("arena", nil, nil, dTOP|EXPAND|FILLX|FILLY, dTOP); cardlib->maketable("arena"); pile = cardlib->newstack(nil, nil, Trickpilespec); cardlib->addlayobj(nil, "public", nil, dTOP|oRIGHT, pile); n := cardlib->nmembers(); hands = array[n] of ref Object; taken = array[n] of ref Object; tt := clique.newobject(nil, All, "widget menu"); tt.setattr("text", "hello", All); for (ml := "one" :: "two" :: "three" :: nil; ml != nil; ml = tl ml) { o := clique.newobject(tt, All, "menuentry"); o.setattr("text", hd ml, All); o.setattr("command", hd ml, All); } for (i := 0; i < n; i++) { cp := Cmember.index(i); hands[i] = cardlib->newstack(cp.obj, cp.p, Handspec); taken[i] = cardlib->newstack(cp.obj, cp.p, Takenspec); p := "p" + string i; cardlib->addlayframe(p + ".f", p, nil, dLEFT|oRIGHT, dTOP); cardlib->addlayobj(nil, p + ".f", cp.layout, dTOP, tt); cardlib->addlayobj(nil, p + ".f", nil, dTOP, hands[i]); cardlib->addlayobj(nil, "p" + string i, nil, dLEFT|oRIGHT, taken[i]); } } joinwords(v: list of string): string { if (v == nil) return nil; s := hd v; for (v = tl v; v != nil; v = tl v) s += " " + hd v; return s; } suitrank := array[] of { Cardlib->CLUBS => 0, Cardlib->DIAMONDS => 1, Cardlib->SPADES => 2, Cardlib->HEARTS => 3 }; starthand() { cardlib->deal(deck, 13, hands, 0); for (i := 0; i < len hands; i++) cardlib->sort(hands[i], nil, suitrank); trick = Trick.new(pile, -1, hands, nil); turn = leader; canplay(turn); } canplay(cp: ref Cmember) { allow->del(CLICK, nil); for (i := 0; i < cardlib->nmembers(); i++) { ccp := Cmember.index(i); v := None.add(ccp.p.id); ccp.obj.setattr("status", nil, v); hands[i].setattr("actions", nil, v); } if (cp != nil && cp.ord != -1) { allow->add(CLICK, cp.p, "click %d %d"); v := None.add(cp.p.id); cp.obj.setattr("status", "Your turn", v); hands[cp.ord].setattr("actions", "click", v); } } remark(s: string) { clique.action("remark " + s, nil, nil, All); }