implement Gatherengine; include "sys.m"; sys: Sys; include "draw.m"; include "sets.m"; sets: Sets; Set, set, A, B, All, None: 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, Card: import cardlib; dTOP, dLEFT, oDOWN, EXPAND, FILLX, FILLY, aCENTRELEFT, Stackspec: import Cardlib; include "../gather.m"; clique: ref Clique; CLICK, SAY, SHOW: con iota; KING: con 12; NACES: con 7; # number of ace piles to fit across the board. Dmember: adt { pile, spare1, spare2: ref Object; open: array of ref Object; # [4] acepiles: array of ref Object; }; scores: array of int; scorelabel: ref Object; dmembers: array of ref Dmember; Openspec := Stackspec( "display", # style 4, # maxcards 0, # conceal "" # title ); Pilespec := Stackspec( "pile", # style 13, # maxcards 0, # conceal "pile" # title ); Untitledpilespec := Stackspec( "pile", # style 13, # maxcards 0, # conceal "" # title ); 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("whist: 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"; } cardlib->init(spree, clique); return nil; } maxmembers(): int { return 100; } readfile(nil: int, nil: big, nil: int): array of byte { return nil; } propose(members: array of string): string { if (len members < 3) return "need at least 3 members"; return nil; } archive() { archiveobj := cardlib->archive(); allow->archive(archiveobj); for (i := 0; i < len dmembers; i++) { dp := dmembers[i]; s := "d" + string i + "_"; cardlib->setarchivename(dp.spare1, s + "spare1"); cardlib->setarchivename(dp.spare2, s + "spare2"); cardlib->setarchivename(dp.pile, s + "pile"); cardlib->archivearray(dp.open, s + "open"); cardlib->archivearray(dp.acepiles, s + "acepiles"); } cardlib->setarchivename(scorelabel, "scorelabel"); s := ""; for (i = 0; i < len scores; i++) s += " " + string scores[i]; archiveobj.setattr("scores", s, None); } start(members: array of ref Member, archived: int) { if (archived) { archiveobj := cardlib->unarchive(); allow->unarchive(archiveobj); dmembers = array[len members] of ref Dmember; for (i := 0; i < len dmembers; i++) { dp := dmembers[i] = ref Dmember; s := "d" + string i + "_"; dp.spare1 = cardlib->getarchiveobj(s + "spare1"); dp.spare2 = cardlib->getarchiveobj(s + "spare2"); dp.pile = cardlib->getarchiveobj(s + "pile"); dp.open = cardlib->getarchivearray(s + "open"); dp.acepiles = cardlib->getarchivearray(s + "acepiles"); } scorelabel = cardlib->getarchiveobj("scorelabel"); s := archiveobj.getattr("scores"); (n, toks) := sys->tokenize(s, " "); scores = array[len members] of int; for (i = 0; toks != nil; toks = tl toks) scores[i++] = int hd toks; } else { pset := None; for (i := 0; i < len members; i++) { p := members[i]; Cmember.join(p, i); pset = pset.add(p.id); allow->add(CLICK, p, "click %o %d"); } Cmember.index(0).layout.lay.setvisibility(All.X(A&~B, pset).add(members[0].id)); layout(); deal(); allow->add(SAY, nil, "say &"); } } 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 "bad member"; case tag { CLICK => # click stack index stack := clique.objects[int hd tl toks]; nc := len stack.children; idx := int hd tl tl toks; sel := cp.sel; stype := stack.getattr("type"); d := dmembers[cp.ord]; if (sel.isempty() || sel.stack == stack) { # selecting a card to move if (nc == 0 && stype == "spare1") { cardlib->flip(d.spare2); d.spare2.transfer((0, len d.spare2.children), d.spare1, 0); return nil; } if (idx < 0 || idx >= len stack.children) return "invalid index"; if (owner(stack) != cp) return "not yours, don't touch!"; case stype { "spare2" or "pile" => select(cp, stack, (nc - 1, nc)); "open" => select(cp, stack, (idx, nc)); "spare1" => if ((n := nc) > 3) n = 3; for (i := 0; i < n; i++) { cardlib->setface(stack.children[nc - 1], 1); stack.transfer((nc - 1, nc), d.spare2, -1); nc--; } * => return "you can't move cards from there"; } } else { # selecting a stack to move to. frompile := sel.stack.getattr("type") == "pile"; case stype { "acepile" => if (sel.r.end != sel.r.start + 1) return "only one card at a time!"; card := getcard(sel.stack.children[sel.r.start]); if (nc == 0) { if (card.number != 0) return "aces only"; } else { top := getcard(stack.children[nc - 1]); if (card.number != top.number + 1) return "out of sequence"; if (card.suit != top.suit) return "wrong suit"; } sel.transfer(stack, -1); if (card.number == KING) # kings get flipped cardlib->setface(stack.children[len stack.children - 1], 0); "open" => if (owner(stack) != cp) return "not yours, don't touch!"; c := getcard(sel.stack.children[sel.r.start]); col := !isred(c); n := c.number + 1; for (i := sel.r.start; i < sel.r.end; i++) { c2 := getcard(sel.stack.children[i]); if (isred(c2) == col) return "bad colour sequence"; if (c2.number != n - 1) return "bad number sequence"; n = c2.number; col = isred(c2); } if (nc != 0) { c2 := getcard(stack.children[nc - 1]); if (isred(c2) == isred(c) || c2.number != c.number + 1) return "invalid move"; } sel.transfer(stack, -1); * => return "can't move there"; } if (frompile) { nc = len d.pile.children; if (nc == 0) { endround(); deal(); } else { cardlib->setface(d.pile.children[nc - 1], 1); d.pile.setattr("title", "pile [" + string nc + "]", All); } } } SAY => clique.action("say member " + string p.id + ": '" + joinwords(tl toks) + "'", nil, nil, All); SHOW => clique.show(nil); } return nil; } getcard(card: ref Object): Card { return cardlib->getcard(card); } isred(c: Card): int { return c.suit == Cardlib->DIAMONDS || c.suit == Cardlib->HEARTS; } select(cp: ref Cmember, stack: ref Object, r: Range) { if (cp.sel.isempty()) { cp.sel.set(stack); cp.sel.setrange(r); } else { if (cp.sel.r.start == r.start && cp.sel.r.end == r.end) cp.sel.set(nil); else cp.sel.setrange(r); } } owner(stack: ref Object): ref Cmember { parent := clique.objects[stack.parentid]; n := cardlib->nmembers(); for (i := 0; i < n; i++) { cp := Cmember.index(i); if (cp.obj == parent) return cp; } return nil; } layout() { n := cardlib->nmembers(); dmembers = array[n] of ref Dmember; for (i := 0; i < n; i++) { cp := Cmember.index(i); d := dmembers[i] = ref Dmember; d.spare1 = newstack(cp.obj, Untitledpilespec, "spare1"); d.spare2 = newstack(cp.obj, Untitledpilespec, "spare2"); d.pile = newstack(cp.obj, Pilespec, "pile"); d.open = array[4] of {* => newstack(cp.obj, Openspec, "open")}; d.acepiles = array[4] of {* => newstack(cp.obj, Untitledpilespec, "acepile")}; cardlib->makecards(d.spare1, (0, 13), string i); } entry := clique.newobject(nil, All, "widget entry"); entry.setattr("command", "say", All); cardlib->addlayobj(nil, nil, nil, dTOP|FILLX, entry); scores = array[n] of {* => 0}; scorelabel = clique.newobject(nil, All, "widget label"); setscores(); cardlib->addlayobj(nil, nil, nil, dTOP|FILLX, scorelabel); cardlib->addlayframe("arena", nil, nil, dTOP|EXPAND|FILLX|FILLY, dTOP); row := 0; col := 0; maketable("arena"); for (i = 0; i < n; i++) { d := dmembers[i]; f := "p" + string i; cardlib->addlayobj(nil, f, nil, dLEFT, d.spare1); cardlib->addlayobj(nil, f, nil, dLEFT, d.spare2); cardlib->addlayobj(nil, f, nil, dLEFT, d.pile); for (j := 0; j < len d.open; j++) cardlib->addlayobj(nil, f, nil, dLEFT|EXPAND|oDOWN, d.open[j]); for (j = 0; j < len d.acepiles; j++) { cardlib->addlayobj(nil, "a" + string row, nil, dLEFT|EXPAND, d.acepiles[j]); if (++col >= NACES) { col = 0; row++; } } } } setscores() { s := "Scores: "; n := cardlib->nmembers(); for (i := 0; i < n; i++) { s += Cmember.index(i).p.name + ": " + string scores[i]; if (i < n - 1) s[len s] = ' '; } scorelabel.setattr("text", s, All); } deal() { n := cardlib->nmembers(); for (i := 0; i < n; i++) { cp := Cmember.index(i); d := dmembers[i]; deck := d.spare1; cardlib->shuffle(deck); deck.transfer((0, 13), d.pile, 0); cardlib->setface(d.pile.children[12], 1); d.pile.setattr("title", "pile [13]", All); for (j := 0; j < len d.open; j++) { deck.transfer((0, 1), d.open[j], 0); cardlib->setface(d.open[j].children[0], 1); } } } endround() { # go through all the ace piles, moving cards back to the appropriate deck # and counting appropriately. # move all other cards back too. n := cardlib->nmembers(); for (i := 0; i < n; i++) { d := dmembers[i]; Cmember.index(i).sel.set(nil); for (j := 0; j < len d.acepiles; j++) { acepile := d.acepiles[j]; nc := len acepile.children; for (k := nc - 1; k >= 0; k--) { card := acepile.children[k]; back := int card.getattr("rear"); scores[back]++; if (getcard(card).number == KING) scores[back] += 5; cardlib->setface(card, 0); acepile.transfer((k, k + 1), dmembers[back].spare1, -1); } } if (len d.pile.children == 0) scores[i] += 10; # bonus for going out else scores[i] -= len d.pile.children; cardlib->discard(d.pile, d.spare1, 1); cardlib->discard(d.spare2, d.spare1, 1); for (j = 0; j < len d.open; j++) cardlib->discard(d.open[j], d.spare1, 1); } setscores(); } maketable(parent: string) { addlayframe: import cardlib; n := cardlib->nmembers(); na := ((n * 4) + (NACES - 1)) / NACES; for (i := 0; i < n; i++) { layout := Cmember.index(i).layout; # one frame for each member other than self; # then all the ace piles; then self. for (j := 0; j < n; j++) if (j != i) addlayframe("p" + string j, parent, layout, dTOP|EXPAND, dTOP); for (j = 0; j < na; j++) addlayframe("a" + string j, parent, layout, dTOP|EXPAND|aCENTRELEFT, dTOP); addlayframe("p" + string i, parent, layout, dTOP|EXPAND, dTOP); } } newstack(parent: ref Object, spec: Stackspec, stype: string): ref Object { stack := cardlib->newstack(parent, nil, spec); stack.setattr("type", stype, None); stack.setattr("actions", "click", All); return stack; } 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; } remark(s: string) { clique.action("remark " + s, nil, nil, All); }