summaryrefslogtreecommitdiff
path: root/appl/spree/engines
diff options
context:
space:
mode:
Diffstat (limited to 'appl/spree/engines')
-rw-r--r--appl/spree/engines/afghan.b302
-rw-r--r--appl/spree/engines/bounce.b258
-rw-r--r--appl/spree/engines/canfield.b340
-rw-r--r--appl/spree/engines/chat.b60
-rw-r--r--appl/spree/engines/debug.b163
-rw-r--r--appl/spree/engines/freecell.b428
-rw-r--r--appl/spree/engines/gather.b267
-rw-r--r--appl/spree/engines/hearts.b300
-rw-r--r--appl/spree/engines/liars.b490
-rw-r--r--appl/spree/engines/liars.y132
-rw-r--r--appl/spree/engines/lobby.b389
-rw-r--r--appl/spree/engines/othello.b242
-rw-r--r--appl/spree/engines/racingdemon.b464
-rw-r--r--appl/spree/engines/snap.b241
-rw-r--r--appl/spree/engines/spider.b259
-rw-r--r--appl/spree/engines/spit.b483
-rw-r--r--appl/spree/engines/whist.b305
17 files changed, 5123 insertions, 0 deletions
diff --git a/appl/spree/engines/afghan.b b/appl/spree/engines/afghan.b
new file mode 100644
index 00000000..0ca2ca49
--- /dev/null
+++ b/appl/spree/engines/afghan.b
@@ -0,0 +1,302 @@
+implement Gatherengine;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sets.m";
+ 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: import cardlib;
+ dTOP, dLEFT, oLEFT, oRIGHT, EXPAND, FILLX, FILLY, aUPPERCENTRE,
+ Stackspec: import Cardlib;
+include "../gather.m";
+
+CLICK, REDEAL: con iota;
+
+clique: ref Clique;
+rows: array of ref Object; # [10]
+central: array of ref Object; # [4]
+chokey, deck: ref Object;
+direction := 0;
+nredeals := 0;
+
+Rowpilespec := Stackspec(
+ "display", # style
+ 10, # maxcards
+ 0, # conceal
+ nil # title
+);
+
+Centralpilespec := Stackspec(
+ "pile",
+ 13,
+ 0,
+ nil
+);
+
+clienttype(): string
+{
+ return "cards";
+}
+
+maxmembers(): int
+{
+ return 1;
+}
+
+readfile(nil: int, nil: big, nil: int): array of byte
+{
+ return nil;
+}
+
+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);
+ 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;
+}
+
+propose(members: array of string): string
+{
+ if (len members != 1)
+ return "one member only";
+ return nil;
+}
+
+archive()
+{
+ archiveobj := cardlib->archive();
+ allow->archive(archiveobj);
+ cardlib->archivearray(rows, "rows");
+ cardlib->archivearray(central, "central");
+ cardlib->setarchivename(chokey, "chokey");
+ cardlib->setarchivename(deck, "deck");
+ archiveobj.setattr("direction", string direction, None);
+ archiveobj.setattr("nredeals", string nredeals, None);
+}
+
+start(members: array of ref Member, archived: int)
+{
+ if (archived) {
+ archiveobj := cardlib->unarchive();
+ allow->unarchive(archiveobj);
+ rows = cardlib->getarchivearray("rows");
+ central = cardlib->getarchivearray("central");
+ chokey = cardlib->getarchiveobj("chokey");
+ deck = cardlib->getarchiveobj("deck");
+ direction = int archiveobj.getattr("direction");
+ nredeals = int archiveobj.getattr("nredeals");
+ } else {
+ p := members[0];
+ Cmember.join(p, -1).layout.lay.setvisibility(All);
+ startclique();
+ allow->add(CLICK, p, "click %o %d");
+ }
+}
+
+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 are not playing";
+
+ case tag {
+ REDEAL =>
+ if (nredeals >= 3)
+ return "no more redeals";
+ redeal();
+ nredeals++;
+ 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");
+
+ if (sel.isempty() || sel.stack == stack) {
+ # selecting a card to move
+ if (idx < 0 || idx >= len stack.children)
+ return "invalid index";
+ case stype {
+ "row" or
+ "chokey" =>
+ select(cp, stack, (nc - 1, nc));
+ * =>
+ return "you can't move cards from there";
+ }
+ } else {
+ # selecting a stack to move to.
+ card := cardlib->getcard(sel.stack.children[sel.r.start]);
+ case stype {
+ "central" =>
+ top := cardlib->getcard(stack.children[nc - 1]);
+ if (direction == 0) {
+ if (card.number != (top.number + 1) % 13 &&
+ card.number != (top.number + 12) % 13)
+ return "out of sequence";
+ if (card.suit != top.suit)
+ return "wrong suit";
+ direction = card.number - top.number;
+ } else {
+ if (card.number != (top.number + direction + 13) % 13)
+ return "out of sequence";
+ if (card.suit != top.suit)
+ return "wrong suit";
+ }
+ "row" =>
+ if (nc == 0 || sel.stack.getattr("type") == "chokey")
+ return "you wish!";
+ top := cardlib->getcard(stack.children[nc - 1]);
+ if (card.suit != top.suit)
+ return "wrong suit";
+ if (card.number != (top.number + 1) % 13 &&
+ card.number != (top.number + 12) % 13)
+ return "out of sequence";
+ "chokey" =>
+ if (nc != 0)
+ return "only one card allowed there";
+ * =>
+ return "can't move there";
+ }
+ sel.transfer(stack, -1);
+ }
+ }
+ return nil;
+}
+
+startclique()
+{
+ addlayobj, addlayframe: import cardlib;
+
+ entry := clique.newobject(nil, All, "widget entry");
+ entry.setattr("command", "say", All);
+
+ but := clique.newobject(nil, All, "widget button");
+ but.setattr("text", "Redeal", All);
+ but.setattr("command", "redeal", All);
+ allow->add(REDEAL, Cmember.index(0).p, "redeal");
+
+ addlayframe("topf", nil, nil, dTOP|EXPAND|FILLX|aUPPERCENTRE, dTOP);
+ addlayobj(nil, "topf", nil, dLEFT, but);
+ addlayobj(nil, "topf", nil, dLEFT|EXPAND|FILLX, entry);
+
+ addlayframe("arena", nil, nil, dTOP|EXPAND|FILLX|FILLY, dTOP);
+
+ addlayframe("left", "arena", nil, dLEFT|EXPAND, dTOP);
+ addlayframe("central", "arena", nil, dLEFT|EXPAND, dTOP);
+ addlayframe("right", "arena", nil, dLEFT|EXPAND, dTOP);
+
+ rows = array[10] of {* => newstack(nil, Rowpilespec, "row")};
+ central = array[4] of {* => newstack(nil, Centralpilespec, "central")};
+ chokey = newstack(nil, Centralpilespec, "chokey");
+
+ deck = clique.newobject(nil, All, "stack");
+ cardlib->makecards(deck, (0, 13), nil);
+ cardlib->shuffle(deck);
+
+ for (i := 0; i < 5; i++)
+ addlayobj(nil, "left", nil, dTOP|oRIGHT, rows[i]);
+ for (i = 5; i < 10; i++)
+ addlayobj(nil, "right", nil, dTOP|oRIGHT, rows[i]);
+ for (i = 0; i < 4; i++)
+ addlayobj(nil, "central", nil, dTOP, central[i]);
+ addlayobj(nil, "central", nil, dTOP, chokey);
+
+ for (i = 0; i < 52; i++)
+ cardlib->setface(deck.children[i], 1);
+ # get top card from deck for central piles.
+ c := deck.children[len deck.children - 1];
+ v := cardlib->getcard(c);
+ j := 0;
+ for (i = len deck.children - 1; i >= 0; i--) {
+ w := cardlib->getcard(deck.children[i]);
+ if (w.number == v.number)
+ deck.transfer((i, i + 1), central[j++], -1);
+ }
+ for (i = 0; i < 10; i += 5) {
+ for (j = i; j < i + 4; j++)
+ deck.transfer((0, 5), rows[j], -1);
+ deck.transfer((0, 4), rows[j], -1);
+ }
+}
+
+redeal()
+{
+ for (i := 0; i < len rows; i++)
+ cardlib->discard(rows[i], deck, 0);
+ cardlib->shuffle(deck);
+
+ i = 0;
+ while ((n := len deck.children) > 0) {
+ l, r: int;
+ if (n >= 10)
+ l = r = 5;
+ else {
+ l = n / 2;
+ r = n - l;
+ }
+ deck.transfer((0, l), rows[i], 0);
+ deck.transfer((0, r), rows[i + 5], 0);
+ i++;
+ }
+
+ n = cardlib->nmembers();
+ for (i = 0; i < n; i++)
+ Cmember.index(i).sel.set(nil);
+}
+
+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;
+}
+
+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);
+ }
+}
+
+archivearray(a: array of ref Object, name: string)
+{
+ for (i := 0; i < len a; i++)
+ cardlib->setarchivename(a[i], name + string i);
+}
+
+unarchivearray(a: array of ref Object, name: string)
+{
+ for (i := 0; i < len a; i++)
+ a[i] = cardlib->getarchiveobj(name + string i);
+}
diff --git a/appl/spree/engines/bounce.b b/appl/spree/engines/bounce.b
new file mode 100644
index 00000000..05e476d4
--- /dev/null
+++ b/appl/spree/engines/bounce.b
@@ -0,0 +1,258 @@
+implement Gatherengine;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Point, Rect: import draw;
+include "sets.m";
+ sets: Sets;
+ Set, All, None, A, B: import sets;
+include "../spree.m";
+ spree: Spree;
+ Attributes, Range, Object, Clique, Member: import spree;
+include "../gather.m";
+
+clique: ref Clique;
+
+W, H: con 500;
+INSET: con 20;
+D: con 30;
+BATLEN: con 100.0;
+GOALSIZE: con 0.1;
+
+MAXPLAYERS: con 32;
+nmembers := 0;
+
+Line: adt {
+ p1, p2: Point;
+ seg: fn(l: self Line, s1, s2: real): Line;
+};
+
+Dmember: adt {
+ p: ref Member;
+ score: int;
+ bat: ref Object;
+};
+
+Eusage: con "bad command usage";
+colours := array[4] of {"blue", "orange", "yellow", "white"};
+batpos: array of Line;
+borderpos: array of Line;
+
+members: array of Dmember;
+arena: ref Object;
+clienttype(): string
+{
+ return "bounce";
+}
+
+maxmembers(): int
+{
+ return 4;
+}
+
+readfile(nil: int, nil: big, nil: int): array of byte
+{
+ return nil;
+}
+
+init(srvmod: Spree, g: ref Clique, nil: list of string, nil: int): string
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ clique = g;
+ spree = srvmod;
+
+ sets = load Sets Sets->PATH;
+ if (sets == nil) {
+ sys->print("spit: cannot load %s: %r\n", Sets->PATH);
+ return "bad module";
+ }
+ sets->init();
+
+ r := Rect((0, 0), (W, H));
+ walls := sides(r.inset(INSET));
+ addlines(segs(walls, 0.0, 0.5 - GOALSIZE), nil);
+ addlines(segs(walls, 0.5 + GOALSIZE, 1.0), nil);
+
+ batpos = l2a(segs(sides(r.inset(INSET + 50)), 0.1, 0.9));
+ borderpos = l2a(sides(r.inset(-1)));
+
+ arena = clique.newobject(nil, All, "arena");
+ arena.setattr("arenasize", string W + " " + string H, All);
+
+ 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()
+{
+}
+
+start(pl: array of ref Member, archived: int)
+{
+ if (archived) {
+ } else {
+ members = array[len pl] of Dmember;
+ for (i := 0; i < len pl; i++) {
+ p := pl[i];
+ bat := addline(batpos[i], nil);
+ bat.setattr("pos", "10 " + string (10.0 + BATLEN), All);
+ bat.setattr("owner", p.name, All);
+ addline(borderpos[i], ("owner", p.name) :: nil);
+ arena.setattr("member" + string i, p.name + " " + colours[i], All);
+ members[i] = (p, 0, bat);
+ }
+ r := Rect((0, 0), (W, H)).inset(INSET + 1);
+ goals := l2a(sides(r));
+ for (i = len members; i < len batpos; i++) {
+ addline(goals[i], nil);
+ addline(borderpos[i], ("owner", pl[0].name) :: nil);
+ }
+ }
+}
+
+addline(lp: (Point, Point), attrs: list of (string, string)): ref Object
+{
+ (p1, p2) := lp;
+ l := clique.newobject(nil, All, "line");
+ l.setattr("coords", p2s(p1) + " " + p2s(p2), All);
+ l.setattr("id", string l.id, All);
+ for (; attrs != nil; attrs = tl attrs) {
+ (attr, val) := hd attrs;
+ l.setattr(attr, val, All);
+ }
+ return l;
+}
+
+
+p2s(p: Point): string
+{
+ return string p.x + " " + string p.y;
+}
+
+command(member: ref Member, cmd: string): string
+{
+ ord := order(member);
+ sys->print("cmd: %s", cmd);
+ {
+ (n, toks) := sys->tokenize(cmd, " \n");
+ assert(n > 0, "unknown command");
+ case hd toks {
+ "newball" =>
+ # newball batid p.x p.y v.x v.y speed
+ assert(n == 7, Eusage);
+ bat := member.obj(int hd tl toks);
+ assert(bat != nil, "no such bat");
+ ball := clique.newobject(nil, All, "ball");
+ ball.setattr("state", string bat.id + " " + string ord +
+ " " + concat(tl tl toks) + " " + string sys->millisec(), All);
+ "lost" =>
+ # lost ballid
+ assert(n == 2, Eusage);
+ o := member.obj(int hd tl toks);
+ assert(o != nil, "bad object");
+ assert(o.getattr("state") != nil, "can only lose balls");
+ o.delete();
+ "state" =>
+ # state ballid lasthit owner p.x p.y v.x v.y s time
+ assert(n == 10, Eusage);
+ assert(ord >= 0, "you are not playing");
+ o := member.obj(int hd tl toks);
+ assert(o != nil, "object does not exist");
+ o.setattr("state", concat(tl tl toks), All);
+ members[ord].score++;
+ arena.setattr("score" + string ord, string members[ord].score, All);
+ "bat" =>
+ # bat pos
+ assert(n == 2, Eusage);
+ s1 := real hd tl toks;
+ members[ord].bat.setattr("pos", hd tl toks + " " + string (s1 + BATLEN), All);
+ "time" =>
+ # time millisec
+ assert(n == 2, Eusage);
+ tm := int hd tl toks;
+ offset := sys->millisec() - tm;
+ clique.action("time " + string offset + " " + string tm, nil, nil, None.add(member.id));
+ * =>
+ assert(0, "bad command");
+ }
+ } exception e {
+ "parse:*" =>
+ return e[6:];
+ }
+ return nil;
+}
+
+order(p: ref Member): int
+{
+ for (i := 0; i < len members; i++)
+ if (members[i].p == p)
+ return i;
+ return -1;
+}
+
+assert(b: int, err: string)
+{
+ if (b == 0)
+ raise "parse:" + err;
+}
+
+concat(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;
+}
+
+Line.seg(l: self Line, s1, s2: real): Line
+{
+ (dx, dy) := (l.p2.x - l.p1.x, l.p2.y - l.p1.y);
+ return (((l.p1.x + int (s1 * real dx)), l.p1.y + int (s1 * real dy)),
+ ((l.p1.x + int (s2 * real dx)), l.p1.y + int (s2 * real dy)));
+}
+
+sides(r: Rect): list of Line
+{
+ return ((r.min.x, r.min.y), (r.min.x, r.max.y)) ::
+ ((r.max.x, r.min.y), (r.max.x, r.max.y)) ::
+ ((r.min.x, r.min.y), (r.max.x, r.min.y)) ::
+ ((r.min.x, r.max.y), (r.max.x, r.max.y)) :: nil;
+}
+
+addlines(ll: list of Line, attrs: list of (string, string))
+{
+ for (; ll != nil; ll = tl ll)
+ addline(hd ll, attrs);
+}
+
+segs(ll: list of Line, s1, s2: real): list of Line
+{
+ nll: list of Line;
+ for (; ll != nil; ll = tl ll)
+ nll = (hd ll).seg(s1, s2) :: nll;
+ ll = nil;
+ for (; nll != nil; nll = tl nll)
+ ll = hd nll :: ll;
+ return ll;
+}
+
+l2a(ll: list of Line): array of Line
+{
+ a := array[len ll] of Line;
+ for (i := 0; ll != nil; ll = tl ll)
+ a[i++] = hd ll;
+ return a;
+}
diff --git a/appl/spree/engines/canfield.b b/appl/spree/engines/canfield.b
new file mode 100644
index 00000000..dbf3734f
--- /dev/null
+++ b/appl/spree/engines/canfield.b
@@ -0,0 +1,340 @@
+implement Gatherengine;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sets.m";
+ All, None: import Sets;
+include "../spree.m";
+ spree: Spree;
+ Attributes, Range, Object, Clique, Member: import spree;
+include "allow.m";
+ allow: Allow;
+include "cardlib.m";
+ cardlib: Cardlib;
+ Selection, Cmember, Card: import cardlib;
+ dTOP, dRIGHT, dLEFT, oDOWN,
+ aCENTRELEFT, aUPPERRIGHT,
+ EXPAND, FILLX, FILLY, Stackspec: import Cardlib;
+include "../gather.m";
+
+clique: ref Clique;
+
+sevens: array of ref Object; # [7]
+spare1, spare2: ref Object;
+acepiles: array of ref Object; # [4]
+top2botcount := 3;
+top2bot: ref Object;
+
+CLICK, TOP2BOT, REDEAL, SHOW: con iota;
+
+Openspec := Stackspec(
+ "display", # style
+ 19, # maxcards
+ 0, # conceal
+ "" # title
+);
+
+Pilespec := Stackspec(
+ "pile", # style
+ 19, # maxcards
+ 0, # conceal
+ "pile" # title
+);
+
+Untitledpilespec := Stackspec(
+ "pile", # style
+ 13, # maxcards
+ 0, # conceal
+ "" # title
+);
+
+clienttype(): string
+{
+ return "cards";
+}
+
+rank := array[] of {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+
+maxmembers(): int
+{
+ return 1;
+}
+
+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);
+ 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;
+}
+
+propose(members: array of string): string
+{
+ if (len members != 1)
+ return "one member only";
+ return nil;
+}
+
+start(members: array of ref Member, archived: int)
+{
+ allow->add(SHOW, nil, "show");
+ if (archived) {
+ archiveobj := cardlib->unarchive();
+ sevens = cardlib->getarchivearray("sevens");
+ acepiles = cardlib->getarchivearray("acepiles");
+ spare1 = cardlib->getarchiveobj("spare1");
+ spare2 = cardlib->getarchiveobj("spare2");
+ top2bot = cardlib->getarchiveobj("top2bot");
+ top2botcount = int archiveobj.getattr("top2botcount");
+
+ allow->unarchive(archiveobj);
+ archiveobj.delete();
+ } else {
+ p := members[0];
+ Cmember.join(p, -1).layout.lay.setvisibility(All);
+ startclique();
+ allow->add(CLICK, p, "click %o %d");
+ allow->add(TOP2BOT, p, "top2bot");
+ allow->add(REDEAL, p, "redeal");
+ }
+}
+
+archive()
+{
+ archiveobj := cardlib->archive();
+ cardlib->archivearray(sevens, "sevens");
+ cardlib->archivearray(acepiles, "acepiles");
+ cardlib->setarchivename(spare1, "spare1");
+ cardlib->setarchivename(spare2, "spare2");
+ cardlib->setarchivename(top2bot, "top2bot");
+ archiveobj.setattr("top2botcount", string top2botcount, None);
+ allow->archive(archiveobj);
+}
+
+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 are not playing";
+
+ 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");
+ if (sel.isempty() || sel.stack == stack) {
+ if (nc == 0 && stype == "spare1") {
+ cardlib->flip(spare2);
+ spare2.transfer((0, len spare2.children), spare1, 0);
+ return nil;
+ }
+ if (idx < 0 || idx >= len stack.children)
+ return "invalid index";
+ case stype {
+ "spare2" or
+ "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), spare2, -1);
+ nc--;
+ }
+ * =>
+ return "you can't move cards from there";
+ }
+ } else {
+ from := sel.stack;
+ 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);
+ "open" =>
+ 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 (c2.face == 0)
+ return "cannot move face-down cards";
+ 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";
+ } else if (c.number != 12)
+ return "only kings allowed there";
+ sel.transfer(stack, -1);
+ * =>
+ return "can't move there";
+ }
+ if (from.getattr("type") == "open" && len from.children > 0)
+ cardlib->setface(from.children[len from.children - 1], 1);
+ }
+ TOP2BOT =>
+ if (len spare2.children != 0)
+ return "can only top-to-bottom on the whole pile";
+ if (top2botcount <= 0)
+ return "too late";
+ nc := len spare1.children;
+ if (nc > 0) {
+ spare1.transfer((nc - 1, nc), spare1, 0);
+ top2botcount--;
+ settop2bottext();
+ }
+ REDEAL =>
+ clearup();
+ cardlib->shuffle(spare1);
+ deal();
+ top2botcount = 3;
+ settop2bottext();
+ SHOW =>
+ clique.show(nil);
+ }
+ return nil;
+}
+
+readfile(nil: int, nil: big, nil: int): array of byte
+{
+ return nil;
+}
+
+settop2bottext()
+{
+ top2bot.setattr("text",
+ sys->sprint("top to bottom (%d left)", top2botcount), All);
+}
+
+startclique()
+{
+ addlayobj, addlayframe: import cardlib;
+
+ entry := clique.newobject(nil, All, "widget entry");
+ entry.setattr("command", "say", All);
+ addlayobj("entry", nil, nil, dTOP|FILLX, entry);
+ addlayframe("arena", nil, nil, dTOP|EXPAND|FILLX|FILLY, dTOP);
+
+ addlayframe("top", "arena", nil, dTOP|EXPAND, dTOP);
+ addlayframe("mid", "arena", nil, dTOP|EXPAND, dTOP);
+ addlayframe("bot", "arena", nil, dTOP|EXPAND, dTOP);
+
+ sevens = array[7] of {* => newstack(nil, Openspec, "open")};
+ acepiles = array[4] of {* => newstack(nil, Untitledpilespec, "acepile")};
+ spare1 = newstack(nil, Untitledpilespec, "spare1");
+ spare2 = newstack(nil, Untitledpilespec, "spare2");
+
+ cardlib->makecards(spare1, (0, 13), nil);
+
+ for (i := 0; i < 4; i++)
+ addlayobj(nil, "top", nil, dRIGHT, acepiles[i]);
+ for (i = 0; i < len sevens; i++)
+ addlayobj(nil, "mid", nil, dLEFT|oDOWN|EXPAND, sevens[i]);
+ addlayframe("buts", "bot", nil, dLEFT|EXPAND|aUPPERRIGHT, dTOP);
+ top2bot = newbutton("top2bot", "top to bottom");
+ addlayobj(nil, "buts", nil, dTOP, top2bot);
+ addlayobj(nil, "buts", nil, dTOP, newbutton("redeal", "redeal"));
+ addlayobj(nil, "bot", nil, dLEFT, spare1);
+ addlayobj(nil, "bot", nil, dLEFT|EXPAND|aCENTRELEFT, spare2);
+ deal();
+ settop2bottext();
+}
+
+clearup()
+{
+ for (i := 0; i < len sevens; i++)
+ cardlib->discard(sevens[i], spare1, 1);
+ for (i = 0; i < len acepiles; i++)
+ cardlib->discard(acepiles[i], spare1, 1);
+ cardlib->discard(spare2, spare1, 1);
+}
+
+deal()
+{
+ cardlib->shuffle(spare1);
+
+ for (i := 0; i < 7; i++) {
+ spare1.transfer((0, i + 1), sevens[i], 0);
+ cardlib->setface(sevens[i].children[i], 1);
+ }
+
+}
+
+newbutton(cmd, text: string): ref Object
+{
+ but := clique.newobject(nil, All, "widget button");
+ but.setattr("command", cmd, All);
+ but.setattr("text", text, All);
+ return but;
+}
+
+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;
+}
+
+getcard(card: ref Object): Card
+{
+ c := cardlib->getcard(card);
+ c.number = rank[c.number];
+ return c;
+}
+
+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);
+ }
+}
diff --git a/appl/spree/engines/chat.b b/appl/spree/engines/chat.b
new file mode 100644
index 00000000..c409b272
--- /dev/null
+++ b/appl/spree/engines/chat.b
@@ -0,0 +1,60 @@
+implement Engine;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "../spree.m";
+ spree: Spree;
+ Attributes, Range, Object, Clique, Member: import spree;
+
+clique: ref Clique;
+
+clienttype(): string
+{
+ return "chat";
+}
+
+init(g: ref Clique, srvmod: Spree): string
+{
+ sys = load Sys Sys->PATH;
+ clique = g;
+ spree = srvmod;
+ return nil;
+}
+
+join(nil: ref Member): string
+{
+ return nil;
+}
+
+leave(nil: ref Member)
+{
+}
+
+Eusage: con "bad command usage";
+
+command(member: ref Member, cmd: string): string
+{
+ e := ref Sys->Exception;
+ if (sys->rescue("parse:*", e) == Sys->EXCEPTION) {
+ sys->rescued(Sys->ONCE, nil);
+ return e.name[6:];
+ }
+ (n, toks) := sys->tokenize(cmd, " \n");
+ assert(n > 0, "unknown command");
+ case hd toks {
+ "say" =>
+ # say something
+ assert(n == 2, Eusage);
+ clique.action("say " + string member.id + " " + hd tl toks, nil, nil, ~0);
+ * =>
+ assert(0, "bad command");
+ }
+ return nil;
+}
+
+assert(b: int, err: string)
+{
+ if (b == 0)
+ sys->raise("parse:" + err);
+}
diff --git a/appl/spree/engines/debug.b b/appl/spree/engines/debug.b
new file mode 100644
index 00000000..96acede0
--- /dev/null
+++ b/appl/spree/engines/debug.b
@@ -0,0 +1,163 @@
+implement Engine;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "../spree.m";
+ spree: Spree;
+ Attributes, Range, Object, Clique, Member: import spree;
+
+clique: ref Clique;
+
+init(g: ref Clique, srvmod: Spree): string
+{
+ sys = load Sys Sys->PATH;
+ clique = g;
+ spree = srvmod;
+ return nil;
+}
+
+join(nil: ref Member): string
+{
+ return nil;
+}
+
+leave(nil: ref Member)
+{
+}
+
+number := 0;
+currmember: ref Member;
+
+obj(ext: int): ref Object
+{
+ o := currmember.obj(ext);
+ if (o == nil)
+ sys->raise("parse:bad object");
+ return o;
+}
+
+Eusage: con "bad command usage";
+
+assert(b: int, err: string)
+{
+ if (b == 0)
+ sys->raise("parse:" + err);
+}
+
+command(member: ref Member, cmd: string): string
+{
+ e := ref Sys->Exception;
+ if (sys->rescue("parse:*", e) == Sys->EXCEPTION) {
+ sys->rescued(Sys->ONCE, nil);
+ currmember = nil;
+ return e.name[6:];
+ }
+ currmember = member;
+ (nlines, lines) := sys->tokenize(cmd, "\n");
+ assert(nlines > 0, "unknown command");
+ (n, toks) := sys->tokenize(hd lines, " ");
+ assert(n > 0, "unknown command");
+ case hd toks {
+ "new" => # new parent visibility\nvisibility attr val\nvisibility attr val...
+ assert(n == 3, Eusage);
+ setattrs(clique.newobject(obj(int hd tl toks), int hd tl tl toks), tl lines);
+ "deck" =>
+ stack := clique.newobject(nil, ~0);
+ stack.setattr("type", "stack", ~0);
+ for (i := 0; i < 6; i++) {
+ o := clique.newobject(stack, ~0);
+ o.setattr("face", "down", ~0);
+ o.setattr("number", string number++, 0);
+ }
+ "flip" =>
+ # flip objid start [end]
+ assert(n == 2 || n == 3 || n == 4, Eusage);
+ o := obj(int hd tl toks);
+ if (n > 2) {
+ start := int hd tl tl toks;
+ end := start + 1;
+ if (n == 4)
+ end = int hd tl tl tl toks;
+ assert(start >= 0 && start < len o.children &&
+ end >= start && end >= 0 && end <= len o.children, "index out of range");
+ for (; start < end; start++)
+ flip(o.children[start]);
+ } else
+ flip(o);
+
+ "set" => # set objid attr val
+ assert(n == 4, Eusage);
+ obj(int hd tl toks).setattr(hd tl tl toks, hd tl tl tl toks, ~0);
+ "vis" => # vis objid flags
+ assert(n == 3, Eusage);
+ obj(int hd tl toks).setvisibility(int hd tl tl toks);
+ "attrvis" => # attrvis objid attr flags
+ assert(n == 4, Eusage);
+ o := obj(int hd tl toks);
+ name := hd tl tl toks;
+ attr := o.attrs.get(name);
+ assert(attr != nil, "attribute not found");
+ o.setattrvisibility(name, int hd tl tl tl toks);
+ "show" => # show [memberid]
+ p: ref Member = nil;
+ if (n == 2) {
+ memberid := int hd tl toks;
+ p = clique.member(memberid);
+ assert(p != nil, "bad memberid");
+ }
+ clique.show(p);
+ "del" or "delete" => # del obj
+ assert(n == 2, Eusage);
+ obj(int hd tl toks).delete();
+ "tx" => # tx src from to dest [index]
+ assert(n == 5 || n == 6, Eusage);
+ src, dest: ref Object;
+ r: Range;
+ (src, toks) = (obj(int hd tl toks), tl tl toks);
+ (r.start, toks) = (int hd toks, tl toks);
+ (r.end, toks) = (int hd toks, tl toks);
+ (dest, toks) = (obj(int hd toks), tl toks);
+ index := len dest.children;
+ if (n == 6)
+ index = int hd toks;
+ assert(r.start >= 0 && r.start < len src.children &&
+ r.end >= 0 && r.end <= len src.children && r.end >= r.start,
+ "bad range");
+ src.transfer(r, dest, index);
+ * =>
+ assert(0, "bad command");
+ }
+ currmember = nil;
+ return nil;
+}
+
+
+flip(o: ref Object)
+{
+ face := o.getattr("face");
+ if (face == "down") {
+ face = "up";
+ o.setattrvisibility("number", ~0);
+ } else {
+ face = "down";
+ o.setattrvisibility("number", 0);
+ }
+ o.setattr("face", face, ~0);
+}
+
+setattrs(o: ref Object, lines: list of string): string
+{
+ for (; lines != nil; lines = tl lines) {
+ # attr val [visibility]
+ (n, toks) := sys->tokenize(hd lines, " ");
+ if (n != 2 && n != 3)
+ return "bad attribute line";
+ vis := 0;
+ if (n == 3)
+ vis = int hd tl tl toks;
+ o.setattr(hd toks, hd tl toks, vis);
+ }
+ return nil;
+}
+
diff --git a/appl/spree/engines/freecell.b b/appl/spree/engines/freecell.b
new file mode 100644
index 00000000..8005926c
--- /dev/null
+++ b/appl/spree/engines/freecell.b
@@ -0,0 +1,428 @@
+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;
+ getcard: import cardlib;
+ dTOP, dRIGHT, dLEFT, oRIGHT, oDOWN,
+ aCENTRERIGHT, aCENTRELEFT, aUPPERRIGHT,
+ EXPAND, FILLX, FILLY, Stackspec: import Cardlib;
+include "../gather.m";
+
+clique: ref Clique;
+
+open: array of ref Object; # [8]
+cells: array of ref Object; # [4]
+acepiles: array of ref Object; # [4]
+txpiles: array of ref Object; # [len open + len cells]
+deck: ref Object;
+
+fnames := array[] of {
+"qua",
+"quack",
+"quackery",
+"quad",
+"quadrangle",
+"quadrangular",
+"quadrant",
+"quadratic",
+"quadrature",
+"quadrennial",
+};
+dir(name: string, perm: int, owner: string): Sys->Dir
+{
+ d := Sys->zerodir;
+ d.name = name;
+ d.uid = owner;
+ d.gid = owner;
+ d.qid.qtype = (perm >> 24) & 16rff;
+ d.mode = perm;
+ # d.atime = now;
+ # d.mtime = now;
+ return d;
+}
+
+
+suitsout := array[4] of {* => -1};
+
+mainmember: ref Cmember;
+
+CLICK: con iota;
+
+Openspec := Stackspec(
+ "display", # style
+ 19, # maxcards
+ 0, # conceal
+ "" # title
+);
+
+Pilespec := Stackspec(
+ "pile", # style
+ 19, # maxcards
+ 0, # conceal
+ "pile" # title
+);
+
+Untitledpilespec := Stackspec(
+ "pile", # style
+ 13, # maxcards
+ 0, # conceal
+ "" # title
+);
+
+clienttype(): string
+{
+ return "cards";
+}
+
+maxmembers(): int
+{
+ return 1;
+}
+
+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";
+ }
+ 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);
+ g.fcreate(0, -1, dir("data", 8r555|Sys->DMDIR, "spree"));
+ for(i := 0; i < len fnames; i++)
+ g.fcreate(i + 1, 0, dir(fnames[i], 8r444, "arble"));
+ return nil;
+}
+
+propose(members: array of string): string
+{
+ if (len members != 1)
+ return "one member only";
+ return nil;
+}
+
+start(members: array of ref Member, archived: int)
+{
+sys->print("freecell: starting\n");
+ if (archived) {
+ archiveobj := cardlib->unarchive();
+ open = cardlib->getarchivearray("open");
+ cells = cardlib->getarchivearray("cells");
+ acepiles = cardlib->getarchivearray("acepiles");
+ txpiles = cardlib->getarchivearray("txpiles");
+ deck = cardlib->getarchiveobj("deck");
+ for (i := 0; i < len suitsout; i++)
+ suitsout[i] = int archiveobj.getattr("suitsout" + string i);
+ mainmember = Cmember.findid(int archiveobj.getattr("mainmember"));
+ allow->unarchive(archiveobj);
+ archiveobj.delete();
+ } else {
+ sys->print("freecell: starting afresh\n");
+ mainmember = Cmember.join(members[0], -1);
+ mainmember.layout.lay.setvisibility(All);
+ startclique();
+ movefree();
+ allow->add(CLICK, members[0], "click %o %d");
+ }
+}
+
+readfile(f: int, boffset: big, n: int): array of byte
+{
+ offset := int boffset;
+ f--;
+ if (f < 0 || f >= len fnames)
+ return nil;
+ data := array of byte fnames[f];
+ if (offset >= len data)
+ return nil;
+ if (offset + n > len data)
+ n = len data - offset;
+ return data[offset:offset + n];
+}
+
+archive()
+{
+ sys->print("freecell: archiving\n");
+ archiveobj := cardlib->archive();
+ cardlib->archivearray(open, "open");
+ cardlib->archivearray(cells, "cells");
+ cardlib->archivearray(acepiles, "acepiles");
+ cardlib->archivearray(txpiles, "txpiles");
+ cardlib->setarchivename(deck, "deck");
+ for (i := 0; i < len suitsout; i++)
+ archiveobj.setattr("suitsout" + string i, string suitsout[i], None);
+ archiveobj.setattr("mainmember", string mainmember.id, None);
+ allow->archive(archiveobj);
+}
+
+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 are not playing";
+ 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");
+ if (sel.isempty() || sel.stack == stack) {
+ if (idx < 0 || idx >= len stack.children)
+ return "invalid index";
+ case stype {
+ "cell" or
+ "open" =>
+ select(cp, stack, (idx, nc));
+ * =>
+ return "you can't move cards from there";
+ }
+ } else {
+ from := sel.stack;
+ case stype {
+ "acepile" =>
+ if (sel.r.end != sel.r.start + 1)
+ return "only one card at a time!";
+ addtoacepile(sel.stack);
+ sel.set(nil);
+ movefree();
+ "open" =>
+ c := getcard(sel.stack.children[sel.r.start]);
+ col := !isred(c.suit);
+ n := c.number + 1;
+ for (i := sel.r.start; i < sel.r.end; i++) {
+ c2 := getcard(sel.stack.children[i]);
+ if (isred(c2.suit) == col)
+ return "bad colour sequence";
+ if (c2.number != n - 1)
+ return "bad number sequence";
+ n = c2.number;
+ col = isred(c2.suit);
+ }
+ if (nc != 0) {
+ c2 := getcard(stack.children[nc - 1]);
+ if (isred(c2.suit) == isred(c.suit) || c2.number != c.number + 1)
+ return "opposite colours, descending, only";
+ }
+ r := sel.r;
+ selstack := sel.stack;
+ sel.set(nil);
+ fc := freecells(stack);
+ if (r.end - r.start - 1 > len fc)
+ return "not enough free cells";
+ n = 0;
+ for (i = r.end - 1; i >= r.start + 1; i--)
+ selstack.transfer((i, i + 1), fc[n++], -1);
+ selstack.transfer((i, i + 1), stack, -1);
+ while (--n >= 0)
+ fc[n].transfer((0, 1), stack, -1);
+ movefree();
+ "cell" =>
+ if (sel.r.end - sel.r.start > 1 || nc > 0)
+ return "only one card allowed there";
+ sel.transfer(stack, -1);
+ movefree();
+ * =>
+ return "can't move there";
+ }
+ }
+ }
+ return nil;
+}
+
+freecells(dest: ref Object): array of ref Object
+{
+ fc := array[len txpiles] of ref Object;
+ n := 0;
+ for (i := 0; i < len txpiles; i++)
+ if (len txpiles[i].children == 0 && txpiles[i] != dest)
+ fc[n++] = txpiles[i];
+ return fc[0:n];
+}
+
+# move any cards that can be moved.
+movefree()
+{
+ nmoved := 1;
+ while (nmoved > 0) {
+ nmoved = 0;
+ for (i := 0; i < len txpiles; i++) {
+ pile := txpiles[i];
+ nc := len pile.children;
+ if (nc == 0)
+ continue;
+ card := getcard(pile.children[nc - 1]);
+ if (suitsout[card.suit] != card.number - 1)
+ continue;
+ # card can be moved; now make sure there's no card out
+ # that might be moved onto this card
+ for (j := 0; j < len suitsout; j++)
+ if (isred(j) != isred(card.suit) && card.number > 1 && suitsout[j] < card.number - 1)
+ break;
+ if (j == len suitsout) {
+ addtoacepile(pile);
+ nmoved++;
+ }
+ }
+ }
+}
+
+addtoacepile(pile: ref Object)
+{
+ nc := len pile.children;
+ if (nc == 0)
+ return;
+ card := getcard(pile.children[nc - 1]);
+ for (i := 0; i < len acepiles; i++) {
+ anc := len acepiles[i].children;
+ if (anc == 0) {
+ if (card.number == 0)
+ break;
+ continue;
+ }
+ acard := getcard(acepiles[i].children[anc - 1]);
+ if (acard.suit == card.suit && acard.number == card.number - 1)
+ break;
+ }
+ if (i < len acepiles) {
+ pile.transfer((nc - 1, nc), acepiles[i], -1);
+ suitsout[card.suit] = card.number;
+ }
+}
+
+startclique()
+{
+ addlayobj, addlayframe: import cardlib;
+
+ open = array[8] of {* => newstack(nil, Openspec, "open", nil)};
+ acepiles = array[4] of {* => newstack(nil, Untitledpilespec, "acepile", nil)};
+ cells = array[4] of {* => newstack(nil, Untitledpilespec, "cell", "cell")};
+ for (i := 0; i < len cells; i++)
+ cells[i].setattr("showsize", "0", All);
+
+ txpiles = array[12] of ref Object;
+ txpiles[0:] = open;
+ txpiles[len open:] = cells;
+ deck = clique.newobject(nil, All, "stack");
+
+ cardlib->makecards(deck, (0, 13), nil);
+
+ addlayframe("arena", nil, nil, dTOP|EXPAND|FILLX|FILLY, dTOP);
+ addlayframe("top", "arena", nil, dTOP|EXPAND, dTOP);
+ addlayframe("bot", "arena", nil, dTOP|EXPAND, dTOP);
+ for (i = 0; i < 4; i++)
+ addlayobj(nil, "top", nil, dRIGHT, acepiles[i]);
+ for (i = 0; i < 4; i++)
+ addlayobj(nil, "top", nil, dLEFT, cells[i]);
+ for (i = 0; i < len open; i++)
+ addlayobj(nil, "bot", nil, dLEFT|oDOWN|EXPAND, open[i]);
+ deal();
+}
+
+deal()
+{
+ cardlib->shuffle(deck);
+ cardlib->deal(deck, 7, open, 0);
+}
+
+newstack(parent: ref Object, spec: Stackspec, stype, title: string): ref Object
+{
+ stack := cardlib->newstack(parent, nil, spec);
+ stack.setattr("type", stype, None);
+ stack.setattr("actions", "click", All);
+ stack.setattr("title", title, All);
+ return stack;
+}
+
+isred(suit: int): int
+{
+ return suit == Cardlib->DIAMONDS || 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);
+ }
+}
+
+#randstate := 1;
+#srand(seed: int)
+#{
+# randstate = seed;
+#}
+#
+#rand(): int
+#{
+# randstate = randstate * 214013 + 2531011;
+# return (randstate >> 16) & 0x7fff;
+#}
+##From: jimh@MICROSOFT.com (Jim Horne)
+##
+##I'm happy to share the card shuffle algorithm, but I warn you,
+##it does depend on the rand() and srand() function built into MS
+##compilers. The good news is that I believe these work the same
+##for all our compilers.
+##
+##I use cards.dll which has it's own mapping of numbers (0-51) to
+##cards. The following will give you the idea. Play around with
+##this and you'll be able to generate all the cliques.
+##
+##Go ahead and post the code. People might as well have fun with it.
+##Please keep me posted on anything interesting that comes of it.
+##Thanks.
+#
+#msdeal(cliquenumber: int): array of array of Card
+#{
+# deck := array[52] of Card;
+# for (i := 0; i < len deck; i++) # put unique card in each deck loc.
+# deck[i] = Card(i % 4, i / 4, 0);
+# wleft := 52; # cards left to be chosen in shuffle
+# cards := array[8] of {* => array[7] of Card};
+# max := array[8] of {* => 0};
+# srand(cliquenumber);
+# for (i = 0; i < 52; i++) {
+# j := rand() % wleft;
+# card[i % 8][i / 8] = deck[j];
+# max[i % 8] = i / 8;
+# deck[j] = deck[--wleft];
+# }
+# for (i = 0; i < len cards; i++)
+# cards[i] = cards[i][0:max[i]];
+# return cards;
+#}
diff --git a/appl/spree/engines/gather.b b/appl/spree/engines/gather.b
new file mode 100644
index 00000000..59b7bfb2
--- /dev/null
+++ b/appl/spree/engines/gather.b
@@ -0,0 +1,267 @@
+implement Engine;
+
+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;
+ archives: Archives;
+ Attributes, Range, Object, Clique, Member, rand: import spree;
+include "daytime.m";
+ daytime: Daytime;
+include "../gather.m";
+
+clique: ref Clique;
+
+started := 0;
+halted := 0;
+suspended: Set; # set of members currently suspended from the clique.
+count := 0;
+nmembers := 0;
+title := "unknown";
+cliquemod: Gatherengine;
+
+members: Set;
+watchers: Set;
+
+invited: list of string;
+
+# options:
+# <n> cliquemodule opts
+init(srvmod: Spree, g: ref Clique, argv: list of string): string
+{
+ sys = load Sys Sys->PATH;
+ clique = g;
+ spree = srvmod;
+ sets = load Sets Sets->PATH;
+ if (sets == nil) {
+ sys->print("gather: cannot load %s: %r\n", Sets->PATH);
+ return "bad module";
+ }
+ sets->init();
+ daytime = load Daytime Daytime->PATH;
+ if (daytime == nil) {
+ sys->print("gather: cannot load %s: %r\n", Daytime->PATH);
+ return "bad module";
+ }
+ archives = load Archives Archives->PATH;
+ if (archives == nil) {
+ sys->print("gather: cannot load %s: %r\n", Archives->PATH);
+ return "bad module";
+ }
+ archives->init(srvmod);
+ argv = tl argv;
+ n := len argv;
+ if (n < 2)
+ return "bad init options";
+ count = int hd argv;
+ if (count != -1 && count <= 0)
+ return "bad gather count";
+ argv = tl argv;
+ if (count < len clique.archive.members)
+ count = len clique.archive.members;
+ cliquemod = load Gatherengine "/dis/spree/engines/" + hd argv + ".dis";
+ if (cliquemod == nil)
+ return sys->sprint("bad module: %r");
+ title = concat(argv);
+ e := cliquemod->init(srvmod, clique, tl argv, len clique.archive.members > 0);
+ if (e != nil)
+ return e;
+ if (len clique.archive.members > 0) {
+ for (i := 0; i < len clique.archive.members; i++)
+ invited = clique.archive.members[i] :: invited;
+ } else
+ invited = clique.owner() :: nil;
+ for (inv := invited; inv != nil; inv = tl inv)
+ clique.notify(clique.parentid, "invite " + hd inv);
+ clique.notify(clique.parentid, "title (" + title + ")");
+ return nil;
+}
+
+join(p: ref Member, cmd: string, susp: int): string
+{
+sys->print("gather: %s[%d] joining '%s' (suspended: %d)\n", p.name, p.id, cmd, susp);
+ case cmd {
+ "join" =>
+ if (started) {
+ if (!susp || !halted)
+ return "clique has already started";
+ suspended = suspended.del(p.id);
+ if (suspended.eq(None)) {
+ halted = 0;
+ # XXX inform participants that clique is starting again
+ }
+ pset := None.add(p.id);
+ clique.action("clienttype " + cliquemod->clienttype(), nil, nil, pset);
+ clique.breakmsg(pset);
+ return nil;
+ }
+ for (inv := invited; inv != nil; inv = tl inv)
+ if (hd inv == p.name || hd inv == "all")
+ break;
+ if (inv == nil)
+ return "you have not been invited";
+ if (nmembers >= cliquemod->maxmembers() || (count != -1 && nmembers >= count))
+ return "too many members already";
+ if (len clique.archive.members > 0) {
+ for (i := 0; i < len clique.archive.members; i++)
+ if (p.name == clique.archive.members[i])
+ break;
+ if (i == len clique.archive.members)
+ return "you are not part of that clique";
+ }
+ nmembers++;
+ members = members.add(p.id);
+ clique.notify(clique.parentid, "join " + p.name);
+ s := None.add(p.id);
+ # special case for single member cliques: don't need a gather client as we can start right now.
+ if (cliquemod->maxmembers() == 1)
+ return startclique();
+ clique.action("clienttype gather", nil, nil, s);
+ clique.breakmsg(s);
+ clique.action("title " + title, nil, nil, s);
+ clique.action("join " + p.name, nil, nil, All);
+ "watch" =>
+ if (susp)
+ return "you cannot watch if you are playing";
+ watchers = watchers.add(p.id);
+ s := None.add(p.id);
+ if (started)
+ clique.action("clienttype " + cliquemod->clienttype(), nil, nil, s);
+ else
+ clique.action("clienttype gather", nil, nil, s);
+ clique.breakmsg(s);
+ if (!started)
+ clique.action("watch " + p.name, nil, nil, All);
+ * =>
+ return "unknown join request";
+ }
+ return nil;
+}
+
+leave(p: ref Member): int
+{
+ if (members.holds(p.id)) {
+ if (started) {
+ suspended = suspended.add(p.id);
+ if (suspended.eq(members)) {
+ cliquemod->archive();
+ name := spree->newarchivename();
+ e := archives->write(clique,
+ ("title", concat(tl tl clique.archive.argv)) ::
+ ("date", string daytime->now()) :: nil,
+ name, members);
+ if (e != nil)
+ sys->print("warning: cannot archive clique: %s\n", e);
+ else
+ clique.notify(clique.parentid, "archived " + name);
+ clique.hangup();
+ return 1;
+ } else {
+ halted = 1;
+ return 0;
+ }
+ }
+
+ members = members.del(p.id);
+ nmembers--;
+ clique.notify(clique.parentid, "leave " + p.name);
+ if (nmembers == 0)
+ clique.hangup();
+ } else {
+ watchers = watchers.del(p.id);
+ clique.action("unwatch " + p.name, nil, nil, All);
+ }
+ return 1;
+}
+
+notify(nil: int, note: string)
+{
+ (n, toks) := sys->tokenize(note, " ");
+ case hd toks {
+ "invite" =>
+ invited = hd tl toks :: invited;
+ "uninvite" =>
+ inv := invited;
+ for (invited = nil; inv != nil; inv = tl inv)
+ if (hd inv != hd tl toks)
+ invited = hd inv :: invited;
+ * =>
+ sys->print("gather: unknown notification '%s'\n", note);
+ }
+}
+
+command(p: ref Member, cmd: string): string
+{
+ if (halted)
+ return "clique is halted for the time being";
+ if (started) {
+ if (!members.holds(p.id)) {
+sys->print("members (%s) doesn't hold %s[%d]\n", members.str(), p.name, p.id);
+ return "you are only watching";
+ }
+ return cliquemod->command(p, cmd);
+ }
+
+ (n, toks) := sys->tokenize(cmd, " \n");
+ if (n == 0)
+ return "bad command";
+ case hd toks {
+ "start" =>
+ if (len clique.archive.members == 0 && p.name != clique.owner())
+ return "only the owner can start a clique";
+ if (count != -1 && nmembers != count)
+ return "need " + string count + " members";
+ return startclique();
+ "chat" =>
+ clique.action("chat " + p.name + " " + concat(tl toks), nil, nil, All);
+ * =>
+ return "unknown command";
+ }
+ return nil;
+}
+
+startclique(): string
+{
+ # XXX could randomly shuffle members here
+
+ pa := array[nmembers] of ref Member;
+ names := array[nmembers] of string;
+ j := nmembers;
+ for (i := members.limit(); i >= 0; i--)
+ if (members.holds(i)) {
+ pa[--j] = clique.member(i);
+ names[j] = pa[j].name;
+ }
+ e := cliquemod->propose(names);
+ if (e != nil)
+ return e;
+ clique.action("clienttype " + cliquemod->clienttype(), nil, nil, All);
+ clique.breakmsg(All);
+ cliquemod->start(pa, len clique.archive.members > 0);
+ clique.start();
+ started = 1;
+ clique.notify(clique.parentid, "started");
+ clique.notify(clique.parentid, "title " + concat(tl tl clique.archive.argv));
+ return nil;
+}
+
+readfile(f: int, offset: big, n: int): array of byte
+{
+ if (!started)
+ return nil;
+ return cliquemod->readfile(f, offset, n);
+}
+
+concat(l: list of string): string
+{
+ if (l == nil)
+ return nil;
+ s := hd l;
+ for (l = tl l; l != nil; l = tl l)
+ s += " " + hd l;
+ return s;
+}
diff --git a/appl/spree/engines/hearts.b b/appl/spree/engines/hearts.b
new file mode 100644
index 00000000..759f07e2
--- /dev/null
+++ b/appl/spree/engines/hearts.b
@@ -0,0 +1,300 @@
+implement Engine;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "../spree.m";
+ spree: Spree;
+ Attributes, Range, Object, Clique, Member, rand: import spree;
+include "allow.m";
+ allow: Allow;
+include "cardlib.m";
+ cardlib: Cardlib;
+ dTOP, dLEFT, oLEFT, oRIGHT, EXPAND, FILLX, FILLY, Stackspec: import Cardlib;
+include "tricks.m";
+ tricks: Tricks;
+ Trick: import tricks;
+clique: ref Clique;
+CLICK, START, SAY: con iota;
+
+started := 0;
+
+buttons: ref Object;
+scores: ref Object;
+deck, pile: ref Object;
+hands, taken, passon: array of ref Object;
+
+MINPLAYERS: con 2;
+MAXPLAYERS: con 4;
+
+leader, turn: int;
+trick: ref Trick;
+
+Trickpilespec := Stackspec(
+ "display", # style
+ 4, # maxcards
+ 0, # conceal
+ "trick pile" # title
+);
+
+Handspec := Stackspec(
+ "display",
+ 13,
+ 1,
+ ""
+);
+
+Passonspec := Stackspec(
+ "display",
+ 3,
+ 0,
+ "pass on"
+);
+
+Takenspec := Stackspec(
+ "pile",
+ 52,
+ 0,
+ "tricks"
+);
+
+clienttype(): string
+{
+ return "cards";
+}
+
+init(g: ref Clique, srvmod: Spree): string
+{
+ sys = load Sys Sys->PATH;
+ clique = g;
+ spree = srvmod;
+
+ allow = load Allow Allow->PATH;
+ if (allow == nil) {
+ sys->print("hearts: cannot load %s: %r\n", Allow->PATH);
+ return "bad module";
+ }
+ allow->init(spree, clique);
+ allow->add(SAY, nil, "say &");
+
+ cardlib = load Cardlib Cardlib->PATH;
+ if (cardlib == nil) {
+ sys->print("hearts: cannot load %s: %r\n", Cardlib->PATH);
+ return "bad module";
+ }
+ cardlib->init(spree, clique);
+
+ tricks = load Tricks Tricks->PATH;
+ if (tricks == nil) {
+ sys->print("hearts: cannot load %s: %r\n", Tricks->PATH);
+ return "bad module";
+ }
+ tricks->init(spree, clique, cardlib);
+
+ deck = clique.newobject(nil, ~0, "stack");
+ cardlib->makecards(deck, (0, 13), 1);
+ cardlib->shuffle(deck);
+ buttons = clique.newobject(nil, ~0, "buttons");
+ scores = clique.newobject(nil, ~0, "scoretable");
+
+ return nil;
+}
+
+join(p: ref Member): string
+{
+ sys->print("%s(%d) joining\n", p.name(), p.id);
+ if (!started && cardlib->nmembers() < MAXPLAYERS) {
+ (nil, err) := cardlib->join(p, -1);
+ if (err == nil) {
+ if (cardlib->nmembers() == MINPLAYERS) {
+ mkbutton("Start", "start");
+ allow->add(START, nil, "start");
+ }
+ } else
+ sys->print("error on join: %s\n", err);
+ }
+ return nil;
+}
+
+leave(p: ref Member)
+{
+ cardlib->leave(p);
+ started == 0;
+ if (cardlib->nmembers() < MINPLAYERS) {
+ buttons.deletechildren((0, len buttons.children));
+ allow->del(START, nil);
+ }
+}
+
+command(p: ref Member, cmd: string): string
+{
+ e := ref Sys->Exception;
+ if (sys->rescue("parse:*", e) == Sys->EXCEPTION) {
+ sys->rescued(Sys->ONCE, nil);
+ return e.name[6:];
+ }
+ (err, tag, toks) := allow->action(p, cmd);
+ if (err != nil)
+ return err;
+ ord := cardlib->order(p);
+ case tag {
+ START =>
+ buttons.deletechildren((0, len buttons.children));
+ allow->del(START, nil);
+ startclique();
+ n := cardlib->nmembers();
+ leader = rand(n);
+ starthand();
+ titles := "";
+ for (i := 0; i < n; i++)
+ titles += cardlib->info(i).p.name() + " ";
+ clique.newobject(scores, ~0, "score").setattr("score", titles, ~0);
+
+ CLICK =>
+ # click stackid index
+ hand := hands[ord];
+ if (int hd tl toks != hand.id)
+ return "can't click there";
+ index := int hd tl tl toks;
+ if (index < 0 || index >= len hand.children)
+ return "index out of range";
+ cardlib->setsel(hands[ord], (index, len hands[ord].children), p);
+ break;
+ err := trick.play(cardlib->order(p), int hd tl toks);
+ if (err != nil)
+ return err;
+
+ turn = next(turn); # clockwise
+ if (turn == leader) { # come full circle
+ winner := trick.winner;
+ inf := cardlib->info(winner);
+ remark(sys->sprint("%s won the trick", inf.p.name()));
+ cardlib->discard(pile, taken[winner], 0);
+ taken[winner].setattr("title",
+ string (len taken[winner].children / cardlib->nmembers()) +
+ " " + "tricks", ~0);
+ o := cardlib->info(winner).obj;
+ trick = nil;
+ s := "";
+ for (i := 0; i < cardlib->nmembers(); i++) {
+ if (i == winner)
+ s += "1 ";
+ else
+ s += "0 ";
+ }
+ clique.newobject(scores, ~0, "score").setattr("score", s, ~0);
+ if (len hands[winner].children > 0) {
+ leader = turn = winner;
+ trick = Trick.new(pile, -1, hands);
+ } else {
+ remark("one round down, some to go");
+ leader = turn = -1; # XXX this round over
+ }
+ }
+ canplay(turn);
+ SAY =>
+ clique.action("say member " + string p.id + ": '" + joinwords(tl toks) + "'", nil, nil, ~0);
+ }
+ return nil;
+}
+
+startclique()
+{
+ cardlib->startclique();
+ entry := clique.newobject(nil, ~0, "widget entry");
+ entry.setattr("command", "say", ~0);
+ 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|oLEFT, pile);
+ n := cardlib->nmembers();
+ hands = array[n] of ref Object;
+ taken = array[n] of ref Object;
+ passon = array[n] of ref Object;
+ tt := clique.newobject(nil, ~0, "widget menu");
+ tt.setattr("text", "hello", ~0);
+ for (ml := "one" :: "two" :: "three" :: nil; ml != nil; ml = tl ml) {
+ o := clique.newobject(tt, ~0, "menuentry");
+ o.setattr("text", hd ml, ~0);
+ o.setattr("command", hd ml, ~0);
+ }
+ for (i := 0; i < n; i++) {
+ inf := cardlib->info(i);
+ hands[i] = cardlib->newstack(inf.obj, inf.p, Handspec);
+ taken[i] = cardlib->newstack(inf.obj, inf.p, Takenspec);
+ passon[i] = cardlib->newstack(inf.obj, inf.p, Passonspec);
+ p := "p" + string i;
+ cardlib->addlayframe(p + ".f", p, nil, dLEFT|oLEFT, dTOP);
+ cardlib->addlayobj(nil, p + ".f", inf.layout, dTOP, tt);
+ cardlib->addlayobj(nil, p + ".f", nil, dTOP|oLEFT, hands[i]);
+ cardlib->addlayobj(nil, p, nil, dLEFT|oLEFT, taken[i]);
+ cardlib->addlayobj(nil, p, nil, dLEFT|oLEFT, passon[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;
+}
+
+starthand()
+{
+ cardlib->deal(deck, 13, hands, 0);
+ trick = Trick.new(pile, -1, hands);
+ turn = leader;
+ canplay(turn);
+}
+
+canplay(ord: int)
+{
+ allow->del(CLICK, nil);
+ for (i := 0; i < cardlib->nmembers(); i++) {
+ inf := cardlib->info(i);
+ inf.obj.setattr("status", nil, 1<<inf.p.id);
+ hands[i].setattr("actions", nil, 1<<inf.p.id);
+ }
+ if (ord != -1) {
+ allow->add(CLICK, member(ord), "click %o %d");
+ inf := cardlib->info(ord);
+ inf.obj.setattr("status", "It's your turn to play", 1<<inf.p.id);
+ hands[ord].setattr("actions", "click", 1<<inf.p.id);
+ }
+}
+
+memberobj(p: ref Member): ref Object
+{
+ return cardlib->info(cardlib->order(p)).obj;
+}
+
+member(ord: int): ref Member
+{
+ return cardlib->info(ord).p;
+}
+
+next(i: int): int
+{
+ i++;
+ if (i >= cardlib->nmembers())
+ i = 0;
+ return i;
+}
+
+remark(s: string)
+{
+ clique.action("remark " + s, nil, nil, ~0);
+}
+
+mkbutton(text, cmd: string): ref Object
+{
+ but := clique.newobject(buttons, ~0, "button");
+ but.setattr("text", text, ~0);
+ but.setattr("command", cmd, ~0);
+ return but;
+}
diff --git a/appl/spree/engines/liars.b b/appl/spree/engines/liars.b
new file mode 100644
index 00000000..488e993b
--- /dev/null
+++ b/appl/spree/engines/liars.b
@@ -0,0 +1,490 @@
+implement Engine;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "../spree.m";
+ spree: Spree;
+ Attributes, Range, Object, Clique, Member, rand: import spree;
+
+MAXPLAYERS: con 32;
+
+clique: ref Clique;
+
+# each member is described by a state machine.
+# a member progresses through the following states:
+#
+# Notplaying
+# istart -> Havedice
+# otherstarts -> Waiting
+# Havedice
+# declare -> Waiting
+# look -> Looking
+# Looking
+# expose -> Looking
+# unexpose -> Looking
+# declare -> Waiting
+# roll -> Rolled
+# Rolled
+# expose -> Rolled
+# unexpose -> Rolled
+# declare -> Waiting
+# Waiting
+# queried -> Queried
+# lost -> Havedice
+# Queried
+# reject,win -> Waiting
+# reject,lose -> Havedice
+# accept -> Havedice
+
+
+plate, cup, space, members: ref Object;
+dice := array[5] of ref Object;
+
+declared: int;
+
+# member states
+Notplaying, Havedice, Looking, Rolled, Waiting, Queried: con iota;
+
+# info on a particular member
+Info: adt {
+ state: int;
+ id: int;
+ member: ref Object;
+ action: ref Object;
+};
+
+info := array[MAXPLAYERS] of ref Info;
+plorder := array[MAXPLAYERS] of int; # map member id to their place around the table
+nplaying := 0;
+nmembers := 0;
+turn := 0;
+
+clienttype(): string
+{
+ return "none";
+}
+
+init(g: ref Clique, srvmod: Spree): string
+{
+ sys = load Sys Sys->PATH;
+ clique = g;
+ spree = srvmod;
+
+ plate = clique.newobject(nil, ~0, "plate");
+ cup = clique.newobject(plate, 0, "cup");
+ space = clique.newobject(plate, ~0, "space");
+ members = clique.newobject(nil, ~0, "members");
+
+ for (i := 0; i < len dice; i++) {
+ dice[i] = clique.newobject(cup, ~0, "die");
+ dice[i].setattr("number", string rand(6), ~0);
+ }
+
+ return nil;
+}
+
+join(member: ref Member): string
+{
+ check();
+ pmask := 1 << member.id;
+
+ ord := nmembers++;
+ inf := info[ord] = ref Info;
+ inf.state = -1;
+ inf.id = member.id;
+ inf.action = clique.newobject(nil, pmask, "actions" + string member.id);
+ plorder[member.id] = ord;
+ setstate(ord, Notplaying);
+ check();
+ return nil;
+}
+
+leave(member: ref Member)
+{
+ check();
+
+ ord := plorder[member.id];
+ state := info[ord].state;
+ info[ord] = nil;
+ for (i := 0; i < nmembers; i++)
+ if (i != ord)
+ setstate(i, Notplaying);
+ nmembers--;
+ nplaying = 0;
+ clique.action("say member " + string ord + " has left. the clique stops.", nil, nil, ~0);
+ check();
+}
+
+currmember: ref Member;
+currcmd: string;
+command(member: ref Member, cmd: string): string
+{
+ check();
+ e := ref Sys->Exception;
+ if (sys->rescue("parse:*", e) == Sys->EXCEPTION) {
+ sys->rescued(Sys->ONCE, nil);
+ check();
+ currmember = nil;
+ currcmd = nil;
+ return e.name[6:];
+ }
+ currmember = member;
+ currcmd = cmd;
+ (nlines, lines) := sys->tokenize(cmd, "\n");
+ assert(nlines > 0, "unknown command");
+ (n, toks) := sys->tokenize(hd lines, " ");
+ assert(n > 0, "unknown command");
+ pmask := 1 << member.id;
+ ord := plorder[member.id];
+ state := info[ord].state;
+ case hd toks {
+ "say" or
+ "show" or
+ "showme" =>
+ case hd toks {
+ "say" =>
+ clique.action("say member " + string member.id + ": '" + (hd lines)[4:] + "'", nil, nil, ~0);
+ "show" => # show [memberid]
+ p: ref Member = nil;
+ if (n == 2) {
+ memberid := int hd tl toks;
+ p = clique.member(memberid);
+ assert(p != nil, "bad memberid");
+ }
+ clique.show(p);
+ "showme" =>
+ clique.show(member);
+ }
+ currmember = nil;
+ currcmd = nil;
+ return nil;
+ }
+ case state {
+ Notplaying =>
+ case hd toks {
+ "start" =>
+ assert(nplaying == 0, "clique is in progress");
+ assert(nmembers > 1, "need at least two members");
+ newinfo := array[len info] of ref Info;
+ members.deletechildren((0, len members.children));
+ j := 0;
+ for (i := 0; i < len info; i++)
+ if (info[i] != nil)
+ newinfo[j++] = info[i];
+ info = newinfo;
+ nplaying = nmembers;
+ for (i = 0; i < nplaying; i++) {
+ info[i].member = clique.newobject(members, ~0, nil);
+ info[i].member.setattr("id", string info[i].id, ~0);
+ }
+ turn = rand(nplaying);
+ start();
+ * =>
+ assert(0, "you are not playing");
+ }
+ Havedice =>
+ case hd toks {
+ "declare" =>
+ # declare hand
+ declare(ord, tl toks);
+ "look" =>
+ cup.setattr("raised", "1", ~0);
+ cup.setvisibility(pmask);
+ setstate(ord, Looking);
+ * =>
+ assert(0, "bad command");
+ }
+ Looking =>
+ case hd toks {
+ "expose" or
+ "unexpose" =>
+ expose(n, toks);
+ "declare" =>
+ declare(ord, tl toks);
+ "roll" =>
+ # roll index...
+ # XXX should be able to roll in the open too
+ for (toks = tl toks; toks != nil; toks = tl toks) {
+ index := int hd toks;
+ checkrange((index, index), cup);
+ cup.children[index].setattr("number", string rand(6), ~0);
+ }
+ setstate(ord, Rolled);
+ * =>
+ assert(0, "bad command");
+ }
+ Rolled =>
+ case hd toks {
+ "expose" or
+ "unexpose" =>
+ expose(n, toks);
+ "declare" =>
+ declare(ord, tl toks);
+ * =>
+ assert(0, "bad command");
+ }
+ Waiting =>
+ assert(0, "not your turn");
+ Queried =>
+ case hd toks {
+ "reject" =>
+ # lift the cup!
+ cup.transfer((0, len cup.children), space, len space.children);
+ assert(len space.children == 5, "lost a die somewhere!");
+ dvals := array[5] of int;
+ for (i := 0; i < 5; i++)
+ dvals[i] = int space.children[i].getattr("number");
+ actval := value(dvals);
+ if (actval >= declared) {
+ # declaration was correct; rejector loses
+ clique.action("say member " + string ord + " loses.", nil, nil, ~0);
+ turn = ord;
+ start();
+ } else {
+ # liar caught out. rejector wins.
+ clique.action("say member " + string turn + " was lying...", nil, nil, ~0);
+ start();
+ }
+ "accept" =>
+ # dice accepted, turn moves on
+ # XXX should allow for anticlockwise play
+ newturn := (turn + 1) % nplaying;
+ plate.setattr("owner", string newturn, ~0);
+ setstate(ord, Havedice);
+ setstate(turn, Waiting);
+ }
+ }
+ check();
+ currmember = nil;
+ currcmd = nil;
+ return nil;
+}
+
+expose(n: int, toks: list of string)
+{
+ # (un)expose index
+ assert(n == 2, Eusage);
+ (src, dest) := (cup, space);
+ if (hd toks == "unexpose")
+ (src, dest) = (space, cup);
+ index := int hd tl toks;
+ checkrange((index, index+1), cup);
+ src.transfer((index, index+1), dest, len dest.children);
+}
+
+start()
+{
+ clique.action("start", nil, nil, ~0);
+ space.transfer((0, len space.children), cup, len cup.children);
+ cup.setvisibility(0);
+ for (i := 0; i < len dice; i++)
+ dice[i].setattr("number", string rand(6), ~0);
+
+ plate.setattr("owner", string turn, ~0);
+ for (i = 0; i < nplaying; i++) {
+ if (i == turn)
+ setstate(i, Havedice);
+ else
+ setstate(i, Waiting);
+ }
+ declared = 0;
+}
+
+declare(ord: int, toks: list of string)
+{
+ cup.setvisibility(0);
+ assert(len toks == 1 && len hd toks == 5, "bad declaration");
+ d := hd toks;
+ v := array[5] of {* => 0};
+ for (i := 0; i < 5; i++) {
+ v[i] = (hd toks)[i] - '0';
+ assert(v[i] >= 0 && v[i] <= 5, "bad declaration");
+ }
+ newval := value(v);
+ assert(newval > declared, "declaration not high enough");
+ declared = newval;
+
+ setstate(turn, Waiting);
+ setstate((turn + 1) % nplaying, Queried);
+}
+
+# check that range is valid for object's children
+checkrange(r: Range, o: ref Object)
+{
+ assert(r.start >= 0 && r.start < len o.children &&
+ r.end >= r.start && r.end >= 0 &&
+ r.end <= len o.children,
+ "index out of range");
+}
+
+setstate(ord: int, state: int)
+{
+ poss: string;
+ case state {
+ Notplaying =>
+ poss = "start";
+ Havedice =>
+ poss = "declare look";
+ Looking =>
+ poss = "expose unexpose declare roll";
+ Rolled =>
+ poss = "expose unexpose declare";
+ Waiting =>
+ poss = "";
+ Queried =>
+ poss = "accept reject";
+ * =>
+ sys->print("liarclique: unknown state %d, member %d\n", state, ord);
+ sys->raise("panic");
+ }
+ info[ord].action.setattr("actions", poss, 1<<info[ord].id);
+ info[ord].state = state;
+}
+
+obj(ext: int): ref Object
+{
+ assert((o := currmember.obj(ext)) != nil, "bad object");
+ return o;
+}
+
+Eusage: con "bad command usage";
+
+assert(b: int, err: string)
+{
+ if (b == 0) {
+ sys->print("cardclique: error '%s' on %s", err, currcmd);
+ sys->raise("parse:" + err);
+ }
+}
+
+checkobj(o: ref Object, what: string)
+{
+ if (o != nil && o.id == -1) {
+ clique.show(currmember);
+ sys->print("object %d has been deleted unexpectedly (%s)\n", o.id, what);
+ sys->raise("panic");
+ }
+}
+
+check()
+{
+}
+
+NOTHING, PAIR, TWOPAIRS, THREES, LOWSTRAIGHT,
+FULLHOUSE, HIGHSTRAIGHT, FOURS, FIVES: con iota;
+
+what := array[] of {
+NOTHING => "nothing",
+PAIR => "pair",
+TWOPAIRS => "twopairs",
+THREES => "threes",
+LOWSTRAIGHT => "lowstraight",
+FULLHOUSE => "fullhouse",
+HIGHSTRAIGHT => "highstraight",
+FOURS => "fours",
+FIVES => "fives"
+};
+
+same(dice: array of int): int
+{
+ x := dice[0];
+ for (i := 0; i < len dice; i++)
+ if (dice[i] != x)
+ return 0;
+ return 1;
+}
+
+val(hi, lo: int): int
+{
+ return hi * 100000 + lo;
+}
+
+D: con 10;
+
+value(dice: array of int): int
+{
+ mergesort(dice, array[5] of int);
+
+ for (i := 0; i < 5; i++)
+ sys->print("%d ", dice[i]);
+ sys->print("\n");
+
+ # five of a kind
+ x := dice[0];
+ if (same(dice))
+ return val(FIVES, dice[0]);
+
+ # four of a kind
+ if (same(dice[1:]))
+ return val(FOURS, dice[0] + dice[1]*D);
+ if (same(dice[0:4]))
+ return val(FOURS, dice[4] + dice[0]*D);
+
+ # high straight
+ if (dice[0] == 1 && dice[1] == 2 && dice[2] == 3 &&
+ dice[3] == 4 && dice[4] == 5)
+ return val(HIGHSTRAIGHT, 0);
+
+ # full house
+ if (same(dice[0:3]) && same(dice[3:5]))
+ return val(FULLHOUSE, dice[0]*D + dice[4]);
+ if (same(dice[0:2]) && same(dice[2:5]))
+ return val(FULLHOUSE, dice[4]*D + dice[0]);
+
+ # low straight
+ if (dice[0] == 0 && dice[1] == 1 && dice[2] == 2 &&
+ dice[3] == 3 && dice[4] == 4)
+ return val(LOWSTRAIGHT, 0);
+ # three of a kind
+ if (same(dice[0:3]))
+ return val(THREES, dice[3] + dice[4]*D + dice[0]*D*D);
+ if (same(dice[1:4]))
+ return val(THREES, dice[0] + dice[4]*D + dice[1]*D*D);
+ if (same(dice[2:5]))
+ return val(THREES, dice[0] + dice[1]*D + dice[2]*D*D);
+
+ for (i = 0; i < 4; i++)
+ if (same(dice[i:i+2]))
+ break;
+ case i {
+ 4 =>
+ return val(NOTHING, dice[0] + dice[1]*D + dice[2]*D*D +
+ dice[3]*D*D*D + dice[4]*D*D*D*D);
+ 3 =>
+ return val(PAIR, dice[0] + dice[1]*D + dice[2]*D*D + dice[3]*D*D*D);
+ 2 =>
+ return val(PAIR, dice[0] + dice[1]*D + dice[4]*D*D + dice[2]*D*D*D);
+ }
+ h := array[5] of int;
+ h[0:] = dice;
+ if (i == 1)
+ (h[0], h[2]) = (h[2], h[0]);
+ # pair is in first two dice
+ if (same(h[2:4]))
+ return val(TWOPAIRS, h[4] + h[2]*D + h[0]*D*D);
+ if (same(h[3:5]))
+ return val(TWOPAIRS, h[2] + h[0]*D + h[4]*D*D);
+ return val(PAIR, dice[2] + dice[3]*D + dice[4]*D*D + dice[0]*D*D*D);
+}
+
+mergesort(a, b: array of int)
+{
+ r := len a;
+ if (r > 1) {
+ m := (r-1)/2 + 1;
+ mergesort(a[0:m], b[0:m]);
+ mergesort(a[m:], b[m:]);
+ b[0:] = a;
+ for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
+ if (b[i] > b[j])
+ a[k] = b[j++];
+ else
+ a[k] = b[i++];
+ }
+ if (i < m)
+ a[k:] = b[i:m];
+ else if (j < r)
+ a[k:] = b[j:r];
+ }
+}
diff --git a/appl/spree/engines/liars.y b/appl/spree/engines/liars.y
new file mode 100644
index 00000000..1e191076
--- /dev/null
+++ b/appl/spree/engines/liars.y
@@ -0,0 +1,132 @@
+%{
+
+
+YYSTYPE: adt {
+};
+
+YYLEX: adt {
+ lval: YYSTYPE;
+ lex: fn(l: self ref YYLEX): int;
+ error: fn(l: self ref YYLEX, err: string);
+ toks: list of string;
+};
+%}
+
+%module Sh {
+ # module definition is in shell.m
+}
+%token A ALL AND BROKEN FIVE FOUR FUCK FULL HIGH
+%token HOUSE KIND LOW NOTHING OF ON PAIR PAIRS STRAIGHT THREE TWO VALUE
+
+%start phrase
+%%
+phrase: nothing
+ | pair
+ | twopairs
+ | threes
+ | lowstraight
+ | fullhouse
+ | highstraight
+ | fours
+ | fives
+
+pair: PAIR
+ | PAIR ofsomething ',' extras
+
+nothing: NOTHING
+ | BROKEN STRAIGHT
+ | FUCK ALL
+
+twopairs: TWO PAIRS moretuppers
+ | TWO VALUE optcomma TWO VALUE and_a VALUE
+ | PAIR OF VALUE ',' PAIR OF VALUE and_a VALUE
+
+moretuppers:
+ | ',' VALUE ',' VALUE and_a VALUE
+
+threes: THREE OF A KIND extras
+ | THREE VALUE extras
+
+lowstraight: LOW STRAIGHT
+
+fullhouse: FULL HOUSE
+ | FULL HOUSE optcomma VALUE
+ | FULL HOUSE optcomma VALUE ON VALUE
+ | FULL HOUSE optcomma VALUE HIGH
+
+highstraight: HIGH STRAIGHT
+
+fours: FOUR OF A KIND extras
+ | FOUR VALUE extras
+
+fives: FIVE OF A KIND
+ | FIVE VALUE
+and_a: # null
+ | AND A
+optcomma:
+ | ','
+extras: VALUE
+ | extras VALUE
+%%
+
+Tok: adt {
+ s: string;
+ tok: int;
+ val: int;
+};
+
+known := array of {
+Tok("an", A, -1),
+Tok("a", A, -1),
+Tok("all", ALL, -1),
+Tok("and", AND, -1),
+Tok("broken", BROKEN, -1),
+Tok(",", ',', -1),
+Tok("five", FIVE, -1),
+Tok("5", FIVE, -1),
+Tok("four", FOUR, -1),
+Tok("4", FOUR, -1),
+Tok("fuck", FUCK, -1),
+Tok("full", FULL, -1),
+Tok("high", HIGH, -1),
+Tok("house", HOUSE, -1),
+Tok("kind", KIND, -1),
+Tok("low", LOW, -1),
+Tok("nothing", NOTHING, -1),
+Tok("of", OF, -1),
+Tok("on", ON, -1),
+Tok("pair", PAIR, -1),
+Tok("pairs", PAIRS, -1),
+Tok("straight", STRAIGHT, -1),
+Tok("three", THREE, -1),
+Tok("3", THREE, -1),
+Tok("two", TWO, -1),
+Tok("2", TWO, -1),
+
+Tok("A", VALUE, 5),
+Tok("K", VALUE, 4),
+Tok("Q", VALUE, 3),
+Tok("J", VALUE, 2),
+Tok("10", VALUE, 1),
+Tok("9", VALUE, 0),
+
+Tok("ace"
+};
+
+YYLEX.lex(l: self ref YYLEX): int
+{
+ if (l.toks == nil)
+ return -1;
+ t := hd l.toks;
+ for (i := 0; i < len known; i++) {
+ if (known[i].t0 == t)
+ return known[i].t1;
+
+ case hd l.toks {
+
+
+%token A ALL AND BROKEN FIVE FOUR FUCK FULL HIGH
+%token HOUSE KIND LOW NOTHING OF ON PAIR PAIRS STRAIGHT THREE TWO VALUE
+%token END
+
+} \ No newline at end of file
diff --git a/appl/spree/engines/lobby.b b/appl/spree/engines/lobby.b
new file mode 100644
index 00000000..119922e2
--- /dev/null
+++ b/appl/spree/engines/lobby.b
@@ -0,0 +1,389 @@
+implement Engine;
+
+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;
+ archives: Archives;
+ Archive: import Archives;
+ Attributes, Range, Object, Clique, Member, rand: import spree;
+include "readdir.m";
+ readdir: Readdir;
+
+# what the lobby provides:
+# a list of cliques it's started
+# name of clique
+# current members
+# list of members inside the lobby.
+# name
+# invites
+# how does a gather engine know who's been invited?
+# as the lobby's the only place with the knowledge of who's around to invite.
+# could allow lobby to communicate with the cliques it's started...
+# but clique also needs to communicate with the lobby
+# (e.g. to say clique has started, no more invites necessary or allowed)
+#
+# list of available engines
+# title
+# clienttype(s?)
+#
+# understands commands:
+# chat message
+# invite
+# new name params
+#
+# question: how do we know about archives?
+# answer: maybe we don't... could have another module
+# that does, or maybe an option to gather ("gather unarchive"?)
+#
+# the one that's started the clique is always invited.
+# start clique.
+# clique says to parent "invite x, y and z" (perhaps they were in the archive)
+# how should we deal with recursive invocation?
+# could queue up requests to other clique engines,
+# and deliver them after the current request has been processed.
+# no return available (one way channel) but maybe that's good,
+# as if sometime in the future engines do run in parallel, we will
+# need to avoid deadlock.
+# Clique.notify(clique: self ref Clique, cliqueid: int, note: string);
+# when a request has been completed, we run notify requests
+# for all the cliques that have been notified, and repeat
+# until no more. (could keep a count to check for infinite loop).
+# don't allow communication between unrelated cliques.
+
+clique: ref Clique;
+
+members: ref Object;
+sessions: ref Object;
+available: ref Object;
+archiveobj: ref Object;
+
+ARCHIVEDIR: con "./archive";
+
+init(srvmod: Spree, g: ref Clique, nil: list of string): string
+{
+ sys = load Sys Sys->PATH;
+ clique = g;
+ spree = srvmod;
+ sets = load Sets Sets->PATH;
+ if (sets == nil) {
+ sys->print("lobby: cannot load %s: %r\n", Sets->PATH);
+ return "bad module";
+ }
+ readdir = load Readdir Readdir->PATH;
+ if (readdir == nil) {
+ sys->print("lobby: cannot load %s: %r\n", Readdir->PATH);
+ return "bad module";
+ }
+ archives = load Archives Archives->PATH;
+ if (archives == nil) {
+ sys->print("lobby: cannot load %s: %r\n", Archives->PATH);
+ return "bad module";
+ }
+ archives->init(srvmod);
+ members = clique.newobject(nil, All, "members");
+ sessions = clique.newobject(nil, All, "sessions");
+ available = clique.newobject(nil, All, "available");
+ o := clique.newobject(available, All, "sessiontype");
+ o.setattr("name", "freecell", All);
+ o.setattr("title", "Freecell", All);
+ o.setattr("clienttype", "cards", All);
+ o.setattr("start", "gather 1 freecell", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Lobby", All);
+ o.setattr("name", "lobby", All);
+ o.setattr("clienttype", "lobby", All);
+ o.setattr("start", "lobby", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Spit", All);
+ o.setattr("name", "spit", All);
+ o.setattr("clienttype", "cards", All);
+ o.setattr("start", "gather 2 spit", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Canfield", All);
+ o.setattr("name", "canfield", All);
+ o.setattr("clienttype", "cards", All);
+ o.setattr("start", "gather 1 canfield", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Afghan", All);
+ o.setattr("name", "afghan", All);
+ o.setattr("clienttype", "cards", All);
+ o.setattr("start", "gather 1 afghan", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Spider", All);
+ o.setattr("name", "spider", All);
+ o.setattr("clienttype", "cards", All);
+ o.setattr("start", "gather 1 spider", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Racing Demon", All);
+ o.setattr("name", "racingdemon", All);
+ o.setattr("clienttype", "cards", All);
+ o.setattr("start", "gather 3 racingdemon", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Othello", All);
+ o.setattr("name", "othello", All);
+ o.setattr("clienttype", "othello", All);
+ o.setattr("start", "gather 2 othello", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Whist", All);
+ o.setattr("name", "whist", All);
+ o.setattr("clienttype", "whist", All);
+ o.setattr("start", "gather 4 whist", All);
+
+ getarchives();
+
+ clique.start();
+
+ return nil;
+}
+
+join(p: ref Member, cmd: string, nil: int): string
+{
+ sys->print("%s joins '%s'\n", p.name, cmd);
+ clique.notify(clique.parentid, "join " + p.name);
+ s := None.add(p.id);
+ clique.action("clienttype lobby", nil, nil, s);
+ clique.breakmsg(s);
+ clique.action("name " + p.name, nil, nil, s);
+ o := clique.newobject(members, All, "member");
+ o.setattr("name", p.name, All);
+ return nil;
+}
+
+leave(p: ref Member): int
+{
+ clique.notify(clique.parentid, "leave " + p.name);
+ deletename(members, p.name, "member");
+ sys->print("%s leaves\n", p.name);
+ return 1;
+}
+
+readfile(nil: int, nil: big, nil: int): array of byte
+{
+ return nil;
+}
+
+command(p: ref Member, cmd: string): string
+{
+ sys->print("%s: '%s'\n", p.name, cmd);
+ (n, toks) := sys->tokenize(cmd, " \n");
+ if (n == 0)
+ return "bad command";
+ case hd toks {
+ "kick" =>
+ getarchives();
+ return nil;
+ "chat" =>
+ clique.action("chat " + p.name + " " + concat(tl toks), nil, nil, All);
+ return nil;
+ "start" =>
+ # start engine [params]
+ if (n >= 2) {
+ (gid, fname, err) := clique.new(
+ ref Archive(tl toks, nil, nil, nil),
+ p.name);
+ if (gid == -1)
+ return err;
+ s := addname(sessions, string gid, "session");
+ s.setattr("title", concat(tl toks), All);
+ s.setattr("filename", fname, All);
+ s.setattr("cliqueid", string gid, None);
+ s.setattr("owner", p.name, All);
+ return nil;
+ }
+ return "bad start params";
+ "invite" or
+ "uninvite"=>
+ # invite sessionid name
+ if (n == 3) {
+ (what, sessionid, name) := (hd toks, int hd tl toks, hd tl tl toks);
+ if ((s := p.obj(sessionid)) == nil)
+ return "bad object id";
+ if (s.objtype != "session")
+ return "bad session type " + s.objtype;
+ if (s.getattr("owner") != p.name)
+ return "permission denied";
+ clique.notify(int s.getattr("cliqueid"), what + " " + name);
+ if (hd toks == "invite")
+ addname(s, name, "invite");
+ else
+ deletename(s, name, "invite");
+ return nil;
+ }
+ return "bad invite params";
+ "unarchive" =>
+ # unarchive object
+ if (n == 2) {
+ o := p.obj(int hd tl toks);
+ if (o == nil || o.objtype != "archive")
+ return "bad archive object";
+ # archive object contains:
+ # name name of clique
+ # members members of the clique
+ # file filename of archive
+
+ aname := o.getattr("file");
+ (archive, err) := archives->read(aname);
+ if (archive == nil)
+ return sys->sprint("cannot load archive: %s", err);
+ for (i := 0; i < len archive.members; i++)
+ if (p.name == archive.members[i])
+ break;
+ if (i == len archive.members)
+ return "you did not participate in that session";
+ (gid, fname, err2) := clique.new(archive, p.name);
+ if (gid == -1)
+ return err2;
+ s := addname(sessions, string gid, "session");
+ s.setattr("title", concat(archive.argv), All);
+ s.setattr("filename", fname, All);
+ s.setattr("cliqueid", string gid, None);
+ s.setattr("owner", p.name, All);
+
+ o.delete();
+ (ok, d) := sys->stat(aname);
+ if (ok != -1) {
+ d.name += ".old";
+ sys->wstat(aname, d);
+ }
+ # XXX delete old archive file?
+ return nil;
+ }
+ return "bad unarchive params";
+ * =>
+ return "bad command";
+ }
+}
+
+notify(srcid: int, note: string)
+{
+ sys->print("lobby: note from %d: %s\n", srcid, note);
+ s := findname(sessions, string srcid);
+ if (s == nil) {
+ sys->print("cannot find srcid %d\n", srcid);
+ return;
+ }
+ if (note == nil) {
+ s.delete();
+ return;
+ }
+ if (srcid == clique.parentid)
+ return;
+ (n, toks) := sys->tokenize(note, " ");
+ case hd toks {
+ "join" =>
+ p := addname(s, hd tl toks, "member");
+ "leave" =>
+ deletename(s, hd tl toks, "member");
+ "invite" =>
+ addname(s, hd tl toks, "invite");
+ "uninvite" =>
+ deletename(s, hd tl toks, "invite");
+ "title" =>
+ s.setattr("title", concat(tl toks), All);
+ "archived" =>
+ # archived filename
+ arch := clique.newobject(archiveobj, All, "archive");
+ arch.setattr("name", s.getattr("title"), All);
+ pnames := "";
+ for (i := 0; i < len s.children; i++)
+ if (s.children[i].objtype == "member")
+ pnames += " " + s.children[i].getattr("name");
+ if (pnames != nil)
+ pnames = pnames[1:];
+ arch.setattr("members", pnames, All);
+ arch.setattr("file", hd tl toks, None);
+ * =>
+ sys->print("unknown note from %d: %s\n", srcid, note);
+ }
+}
+
+addname(o: ref Object, name: string, otype: string): ref Object
+{
+ x := clique.newobject(o, All, otype);
+ x.setattr("name", name, All);
+ return x;
+}
+
+findname(o: ref Object, name: string): ref Object
+{
+ c := o.children;
+ for (i := 0; i < len c; i++)
+ if (c[i].getattr("name") == name)
+ return c[i];
+ return nil;
+}
+
+deletename(o: ref Object, name: string, objtype: string)
+{
+ c := o.children;
+ for (i := 0; i < len c; i++)
+ if (c[i].objtype == objtype && c[i].getattr("name") == name) {
+ o.deletechildren((i, i+1));
+ break;
+ }
+}
+
+getarchives()
+{
+ if (archiveobj == nil)
+ archiveobj = clique.newobject(nil, All, "archives");
+ else
+ archiveobj.deletechildren((0, len archiveobj.children));
+ for (names := spree->archivenames(); names != nil; names = tl names) {
+ fname := hd names;
+ (a, err) := archives->readheader(fname);
+ if (a == nil) {
+ sys->print("lobby: cannot read archive header on %s: %s\n", fname, err);
+ continue;
+ }
+ title := "";
+ for (inf := a.info; inf != nil; inf = tl inf) {
+ if ((hd inf).t0 == "title") {
+ title = (hd inf).t1;
+ break;
+ }
+ }
+ if (title == nil)
+ title = concat(a.argv);
+ arch := clique.newobject(archiveobj, All, "archive");
+ arch.setattr("name", title, All);
+ arch.setattr("members", concatarray(a.members), All);
+ arch.setattr("file", fname, None);
+ j := 0;
+ for (info := a.info; info != nil; info = tl info)
+ arch.setattr("info" + string j++, (hd info).t0 + " " + (hd info).t1, All);
+ }
+}
+
+concat(l: list of string): string
+{
+ if (l == nil)
+ return nil;
+ s := hd l;
+ for (l = tl l; l != nil; l = tl l)
+ s += " " + hd l;
+ return s;
+}
+
+concatarray(a: array of string): string
+{
+ if (len a == 0)
+ return nil;
+ s := a[0];
+ for (i := 1; i < len a; i++)
+ s += " " + a[i];
+ return s;
+}
diff --git a/appl/spree/engines/othello.b b/appl/spree/engines/othello.b
new file mode 100644
index 00000000..2f36c47b
--- /dev/null
+++ b/appl/spree/engines/othello.b
@@ -0,0 +1,242 @@
+implement Gatherengine;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sets.m";
+ All, None: import Sets;
+include "../spree.m";
+ spree: Spree;
+ Attributes, Range, Object, Clique, Member: import spree;
+include "objstore.m";
+ objstore: Objstore;
+include "../gather.m";
+
+clique: ref Clique;
+
+Black, White, Nocolour: con iota; # first two must be 0 and 1.
+N: con 8;
+
+boardobj: ref Object;
+board: array of array of int;
+pieces: array of int;
+turn := Nocolour;
+members := array[2] of ref Member; # member ids of those playing
+
+Point: adt {
+ x, y: int;
+ add: fn(p: self Point, p1: Point): Point;
+ inboard: fn(p: self Point): int;
+};
+
+clienttype(): string
+{
+ return "othello";
+}
+
+init(srvmod: Spree, g: ref Clique, nil: list of string, nil: int): string
+{
+ sys = load Sys Sys->PATH;
+ clique = g;
+ spree = srvmod;
+
+ objstore = load Objstore Objstore->PATH;
+ if (objstore == nil) {
+ sys->print("othello: cannot load %s: %r", Objstore->PATH);
+ return "bad module";
+ }
+ objstore->init(srvmod, g);
+
+ return nil;
+}
+
+maxmembers(): int
+{
+ return 2;
+}
+
+readfile(nil: int, nil: big, nil: int): array of byte
+{
+ return nil;
+}
+
+propose(members: array of string): string
+{
+ if (len members != 2)
+ return "need exactly two members";
+ return nil;
+}
+
+archive()
+{
+ objstore->setname(boardobj, "board");
+}
+
+start(pl: array of ref Member, archived: int)
+{
+ members = pl;
+ board = array[N] of {* => array[N] of {* => Nocolour}};
+ pieces = array[2] of {* => 0};
+ if (archived) {
+ objstore->unarchive();
+ boardobj = objstore->get("board");
+ for (i := 0; i < N; i++) {
+ for (j := 0; j < N; j++) {
+ a := boardobj.getattr(pt2attr((j, i)));
+ if (a != nil) {
+ piece := int a;
+ board[j][i] = piece;
+ if (piece != Nocolour)
+ pieces[piece]++;
+ }
+ }
+ }
+ turn = int boardobj.getattr("turn");
+ } else {
+ boardobj = clique.newobject(nil, All, nil);
+ boardobj.setattr("members", string members[Black].name + " " + string members[White].name, All);
+ for (ps := (Black, (3, 3)) :: (Black, (4, 4)) :: (White, (3, 4)) :: (White, Point(4, 3)) :: nil;
+ ps != nil;
+ ps = tl ps) {
+ (colour, p) := hd ps;
+ setpiece(colour, p);
+ }
+ turn = Black;
+ boardobj.setattr("turn", string Black, All);
+ }
+}
+
+cliqueover()
+{
+ turn = Nocolour;
+ boardobj.setattr("winner", string winner(), All);
+ boardobj.setattr("turn", string turn, All);
+}
+
+command(member: ref Member, cmd: string): string
+{
+ {
+ (n, toks) := sys->tokenize(cmd, " \n");
+ assert(n > 0, "unknown command");
+
+ case hd toks {
+ "move" =>
+ assert(n == 3, "bad command usage");
+ assert(turn != Nocolour, "clique has finished");
+ assert(member == members[White] || member == members[Black], "you are not playing");
+ assert(member == members[turn], "it is not your turn");
+ p := Point(int hd tl toks, int hd tl tl toks);
+ assert(p.x >= 0 && p.x < N && p.y >= 0 && p.y < N, "invalid move position");
+ assert(board[p.x][p.y] == Nocolour, "position is already occupied");
+ assert(newmove(turn, p, 1), "cannot move there");
+
+ turn = reverse(turn);
+ if (!canplay()) {
+ turn = reverse(turn);
+ if (!canplay())
+ cliqueover();
+ }
+ boardobj.setattr("turn", string turn, All);
+ return nil;
+ }
+ sys->print("othello: unknown client command '%s'\n", hd toks);
+ return "who knows";
+ } exception e {
+ "parse:*" =>
+ return e[6:];
+ }
+}
+
+Directions := array[] of {Point(0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1)};
+
+setpiece(colour: int, p: Point)
+{
+ v := board[p.x][p.y];
+ if (v != Nocolour)
+ pieces[v]--;
+ board[p.x][p.y] = colour;
+ pieces[colour]++;
+ boardobj.setattr(pt2attr(p), string colour, All);
+}
+
+pt2attr(pt: Point): string
+{
+ s := " ";
+ s[0] = pt.x + 'a';
+ s[1] = pt.y + 'a';
+ return s;
+}
+
+# member colour has tried to place a piece at mp.
+# return -1 if it's an illegal move, 0 otherwise.
+# (in which case appropriate updates are sent out all round).
+# if update is 0, just check for the move's validity
+# (no change to the board, no updates sent)
+newmove(colour: int, mp: Point, update: int): int
+{
+ totchanged := 0;
+ for (i := 0; i < len Directions; i++) {
+ d := Directions[i];
+ n := 0;
+ for (p := mp.add(d); p.inboard(); p = p.add(d)) {
+ n++;
+ if (board[p.x][p.y] == colour || board[p.x][p.y] == Nocolour)
+ break;
+ }
+ if (p.inboard() && board[p.x][p.y] == colour && n > 1) {
+ if (!update)
+ return 1;
+ totchanged += n - 1;
+ for (p = mp.add(d); --n > 0; p = p.add(d))
+ setpiece(reverse(board[p.x][p.y]), p);
+ }
+ }
+ if (totchanged > 0) {
+ setpiece(colour, mp);
+ return 1;
+ }
+ return 0;
+}
+
+# who has most pieces?
+winner(): int
+{
+ if (pieces[White] > pieces[Black])
+ return White;
+ else if (pieces[Black] > pieces[White])
+ return Black;
+ return Nocolour;
+}
+
+# is there any possible legal move?
+canplay(): int
+{
+ for (y := 0; y < N; y++)
+ for (x := 0; x < N; x++)
+ if (board[x][y] == Nocolour && newmove(turn, (x, y), 0))
+ return 1;
+ return 0;
+}
+
+reverse(colour: int): int
+{
+ if (colour == Nocolour)
+ return Nocolour;
+ return !colour;
+}
+
+Point.add(p: self Point, p1: Point): Point
+{
+ return (p.x + p1.x, p.y + p1.y);
+}
+
+Point.inboard(p: self Point): int
+{
+ return p.x >= 0 && p.x < N && p.y >= 0 && p.y < N;
+}
+
+assert(b: int, err: string)
+{
+ if (b == 0)
+ raise "parse:" + err;
+}
diff --git a/appl/spree/engines/racingdemon.b b/appl/spree/engines/racingdemon.b
new file mode 100644
index 00000000..f839e29a
--- /dev/null
+++ b/appl/spree/engines/racingdemon.b
@@ -0,0 +1,464 @@
+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);
+}
diff --git a/appl/spree/engines/snap.b b/appl/spree/engines/snap.b
new file mode 100644
index 00000000..ff7166cb
--- /dev/null
+++ b/appl/spree/engines/snap.b
@@ -0,0 +1,241 @@
+implement Engine;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "../spree.m";
+ spree: Spree;
+ Attributes, Range, Object, Clique, Member, rand: import spree;
+include "allow.m";
+ allow: Allow;
+include "cardlib.m";
+ cardlib: Cardlib;
+ publicstack: import cardlib;
+ VERT, HORIZ, TOP, BOTTOM, LEFT, RIGHT, Stackspec: import Cardlib;
+
+clique: ref Clique;
+PLAY, START, SAY, SNAP: con iota;
+
+started := 0;
+
+buttons: ref Object;
+scores: ref Object;
+deck: ref Object;
+
+HAND, PILE: con iota;
+
+hands := array[2] of ref Object;
+piles := array[2] of ref Object;
+
+publicspec: array of Stackspec;
+
+privatespec := array[] of {
+ HAND => Stackspec(Cardlib->sPILE,
+ 52,
+ 0,
+ "hand",
+ HORIZ,
+ BOTTOM),
+ PILE => Stackspec(Cardlib->sPILE,
+ 52,
+ 0,
+ "pile",
+ HORIZ,
+ TOP),
+};
+
+oneplayed := 0; # true if only one member's put down a card so far
+
+MINPLAYERS: con 2;
+MAXPLAYERS: con 2;
+
+clienttype(): string
+{
+ return "cards";
+}
+
+init(g: ref Clique, srvmod: Spree): 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);
+ allow->add(SAY, nil, "say &");
+
+ cardlib = load Cardlib Cardlib->PATH;
+ if (cardlib == nil) {
+ sys->print("whist: cannot load %s: %r\n", Cardlib->PATH);
+ return "bad module";
+ }
+
+ cardlib->init(clique, spree);
+ deck = clique.newobject(nil, ~0, "stack");
+ cardlib->makepack(deck, (0, 52), 1);
+ cardlib->shuffle(deck);
+ buttons = clique.newobject(nil, ~0, "buttons");
+ scores = clique.newobject(nil, ~0, "scoretable");
+
+ return nil;
+}
+
+join(p: ref Member): string
+{
+ sys->print("%s(%d) joining\n", p.name(), p.id);
+ if (!started && cardlib->nmembers() < MAXPLAYERS) {
+ (nil, err) := cardlib->join(p, -1);
+ if (err == nil) {
+ if (cardlib->nmembers() == MINPLAYERS) {
+ mkbutton("Start", "start");
+ allow->add(START, nil, "start");
+ }
+ } else
+ sys->print("error on join: %s\n", err);
+ }
+ return nil;
+}
+
+leave(p: ref Member)
+{
+ cardlib->leave(p);
+ started == 0;
+ if (cardlib->nmembers() < MINPLAYERS) {
+ buttons.deletechildren((0, len buttons.children));
+ allow->del(START, nil);
+ }
+}
+
+command(p: ref Member, cmd: string): string
+{
+ e := ref Sys->Exception;
+ if (sys->rescue("parse:*", e) == Sys->EXCEPTION) {
+ sys->rescued(Sys->ONCE, nil);
+ return e.name[6:];
+ }
+ (err, tag, toks) := allow->action(p, cmd);
+ if (err != nil)
+ return err;
+ case tag {
+ START =>
+ buttons.deletechildren((0, len buttons.children));
+ allow->del(START, nil);
+ allow->add(SNAP, nil, "snap");
+ mkbutton("Snap!", "snap");
+ cardlib->startclique(publicspec, privatespec);
+ for (i := 0; i < 2; i++) {
+ hands[i] = cardlib->info(i).stacks[HAND];
+ piles[i] = cardlib->info(i).stacks[PILE];
+ }
+ deck.transfer((0, 26), hands[0], 0);
+ deck.transfer((0, 26), hands[1], 0);
+ canplay(0);
+ canplay(1);
+
+ PLAY =>
+ # click index
+ ord := cardlib->order(p);
+ inf := cardlib->info(ord);
+
+ hand := hands[ord];
+ pile := piles[ord];
+ hand.transfer((len hand.children - 1, len hand.children), pile, len pile.children);
+ cardlib->setface(pile.children[len pile.children - 1], 1);
+ cantplay(ord);
+ oneplayed = !oneplayed;
+ if (!oneplayed || len hands[!ord].children == 0) {
+ for (i := 0; i < 2; i++)
+ if (len hands[i].children > 0)
+ canplay(i);
+ }
+ SNAP =>
+ # snap
+ ord := cardlib->order(p);
+ inf := cardlib->info(ord);
+ if (oneplayed) # XXX allow for case where one person has no cards.
+ return "must wait for two cards to be put down";
+ if (len piles[0].children == 0 || len piles[1].children == 0)
+ return "no cards";
+ c0 := cardlib->getcard(piles[0].children[len piles[0].children - 1]);
+ c1 := cardlib->getcard(piles[1].children[len piles[0].children - 1]);
+ if (c0.number != c1.number) {
+ remark(p.name() + " said snap wrongly!");
+ return "cards must be the same";
+ } else {
+ transferall(piles[!ord], piles[ord], len piles[ord].children);
+ flipstack(piles[ord]);
+ transferall(piles[ord], hands[ord], 0);
+ if (len hands[!ord].children == 0)
+ remark(p.name() + " has won!");
+ oneplayed = 0;
+ for (i := 0; i < 2; i++)
+ if (len hands[i].children > 0)
+ canplay(i);
+ else
+ cantplay(i);
+ }
+ SAY =>
+ clique.action("say member " + string p.id + ": '" + joinwords(tl toks) + "'", nil, nil, ~0);
+ }
+ return nil;
+}
+
+transferall(stack, into: ref Object, idx: int)
+{
+ stack.transfer((0, len stack.children), into, idx);
+}
+
+flipstack(stack: ref Object)
+{
+ for (i := 0; i < len stack.children; i++) {
+ card := stack.children[i];
+ cardlib->setface(card, ! int card.getattr("face"));
+ }
+}
+
+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;
+}
+
+canplay(ord: int)
+{
+ inf := cardlib->info(ord);
+ allow->del(PLAY, inf.p);
+ allow->add(PLAY, inf.p, "click %d");
+ inf.stacks[HAND].setattr("actions", "click", 1<<inf.p.id);
+}
+
+cantplay(ord: int)
+{
+ inf := cardlib->info(ord);
+ allow->del(PLAY, inf.p);
+ inf.stacks[HAND].setattr("actions", nil, 1<<inf.p.id);
+}
+
+member(ord: int): ref Member
+{
+ return cardlib->info(ord).p;
+}
+
+remark(s: string)
+{
+ clique.action("remark " + s, nil, nil, ~0);
+}
+
+mkbutton(text, cmd: string): ref Object
+{
+ but := clique.newobject(buttons, ~0, "button");
+ but.setattr("text", text, ~0);
+ but.setattr("command", cmd, ~0);
+ return but;
+}
diff --git a/appl/spree/engines/spider.b b/appl/spree/engines/spider.b
new file mode 100644
index 00000000..08576ff0
--- /dev/null
+++ b/appl/spree/engines/spider.b
@@ -0,0 +1,259 @@
+implement Gatherengine;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sets.m";
+ 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;
+ getcard: import cardlib;
+ dTOP, dRIGHT, dLEFT, oRIGHT, oDOWN,
+ aCENTRERIGHT, aCENTRELEFT, aUPPERRIGHT, aUPPERCENTRE,
+ EXPAND, FILLX, FILLY, Stackspec: import Cardlib;
+include "../gather.m";
+
+clique: ref Clique;
+
+open: array of ref Object; # [10]
+deck: ref Object;
+discard: ref Object;
+dealbutton: ref Object;
+
+CLICK, MORECARDS: con iota;
+
+Openspec := Stackspec(
+ "display", # style
+ 19, # maxcards
+ 0, # conceal
+ "" # title
+);
+
+clienttype(): string
+{
+ return "cards";
+}
+
+maxmembers(): int
+{
+ return 1;
+}
+
+readfile(nil: int, nil: big, nil: int): array of byte
+{
+ return nil;
+}
+
+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);
+ cardlib = load Cardlib Cardlib->PATH;
+ if (cardlib == nil) {
+ sys->print("whist: cannot load %s: %r\n", Cardlib->PATH);
+ return "bad module";
+ }
+ return nil;
+}
+
+propose(members: array of string): string
+{
+ if (len members != 1)
+ return "one member only";
+ return nil;
+}
+
+archive()
+{
+ archiveobj := cardlib->archive();
+ allow->archive(archiveobj);
+ cardlib->archivearray(open, "open");
+ cardlib->setarchivename(deck, "deck");
+ cardlib->setarchivename(discard, "discard");
+ cardlib->setarchivename(dealbutton, "dealbutton");
+}
+
+start(members: array of ref Member, archived: int)
+{
+ cardlib->init(spree, clique);
+ if (archived) {
+ archiveobj := cardlib->unarchive();
+ allow->unarchive(archiveobj);
+ open = cardlib->getarchivearray("open");
+ discard = cardlib->getarchiveobj("discard");
+ deck = cardlib->getarchiveobj("deck");
+ dealbutton = cardlib->getarchiveobj("dealbutton");
+ } else {
+ p := members[0];
+ Cmember.join(p, -1).layout.lay.setvisibility(All);
+ startclique();
+ allow->add(CLICK, p, "click %o %d");
+ allow->add(MORECARDS, p, "morecards");
+ }
+}
+
+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 are not playing";
+ 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");
+ if (sel.isempty() || sel.stack == stack) {
+ if (idx < 0 || idx >= len stack.children)
+ return "invalid index";
+ case stype {
+ "open" =>
+ select(cp, stack, (idx, nc));
+ * =>
+ return "you can't move cards from there";
+ }
+ } else {
+ from := sel.stack;
+ case stype {
+ "open" =>
+ c := getcard(sel.stack.children[sel.r.start]);
+ n := c.number + 1;
+ for (i := sel.r.start; i < sel.r.end; i++) {
+ c2 := getcard(sel.stack.children[i]);
+ if (c2.face == 0)
+ return "cannot move face down cards";
+ if (c2.number != n - 1)
+ return "bad number sequence";
+ n = c2.number;
+ }
+ if (nc != 0) {
+ c2 := getcard(stack.children[nc - 1]);
+ if (c2.number != c.number + 1)
+ return "descending, only";
+ }
+ srcstack := sel.stack;
+ sel.transfer(stack, -1);
+ turntop(srcstack);
+
+ nc = len stack.children;
+ if (nc >= 13) {
+ c = getcard(stack.children[nc - 1]);
+ suit := c.suit;
+ for (i = 0; i < 13; i++) {
+ c = getcard(stack.children[nc - i - 1]);
+ if (c.suit != suit || c.number != i)
+ break;
+ }
+ if (i == 13) {
+ stack.transfer((nc - 13, nc), discard, -1);
+ turntop(stack);
+ }
+ }
+ * =>
+ return "can't move there";
+ }
+ }
+ MORECARDS =>
+ for (i := 0; i < 10; i++)
+ if (len open[i].children == 0)
+ return "spaces must be filled before redeal";
+ for (i = 0; i < 10; i++) {
+ if (len deck.children == 0)
+ break;
+ cp.sel.set(nil);
+ cardlib->setface(deck.children[0], 1);
+ deck.transfer((0, 1), open[i], -1);
+ }
+ setdealbuttontext();
+ }
+ return nil;
+}
+
+setdealbuttontext()
+{
+ dealbutton.setattr("text", sys->sprint("deal more (%d left)", len deck.children), All);
+}
+
+turntop(stack: ref Object)
+{
+ if (len stack.children > 0)
+ cardlib->setface(stack.children[len stack.children - 1], 1);
+}
+
+startclique()
+{
+ addlayobj, addlayframe: import cardlib;
+ open = array[10] of {* => newstack(nil, Openspec, "open", nil)};
+ deck = clique.newobject(nil, All, "stack");
+ discard = clique.newobject(nil, All, "stack");
+ cardlib->makecards(deck, (0, 13), "0");
+ cardlib->makecards(deck, (0, 13), "1");
+ addlayframe("arena", nil, nil, dTOP|EXPAND|FILLX|FILLY, dTOP);
+ addlayframe("top", "arena", nil, dTOP|EXPAND|FILLX|FILLY, dTOP);
+
+ for (i := 0; i < 10; i++)
+ addlayobj(nil, "top", nil, dLEFT|oDOWN|EXPAND|aUPPERCENTRE, open[i]);
+ addlayframe("bot", "arena", nil, dTOP, dTOP);
+ dealbutton = newbutton("morecards", "deal more");
+ addlayobj(nil, "bot", nil, dLEFT, dealbutton);
+ deal();
+ setdealbuttontext();
+}
+
+deal()
+{
+ cardlib->shuffle(deck);
+ for (i := 0; i < 10; i++) {
+ deck.transfer((0, 4), open[i], 0);
+ turntop(open[i]);
+ }
+}
+
+newstack(parent: ref Object, spec: Stackspec, stype, title: string): ref Object
+{
+ stack := cardlib->newstack(parent, nil, spec);
+ stack.setattr("type", stype, None);
+ stack.setattr("actions", "click", All);
+ stack.setattr("title", title, All);
+ return stack;
+}
+
+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);
+ }
+}
+
+newbutton(cmd, text: string): ref Object
+{
+ but := clique.newobject(nil, All, "widget button");
+ but.setattr("command", cmd, All);
+ but.setattr("text", text, All);
+ return but;
+}
+
diff --git a/appl/spree/engines/spit.b b/appl/spree/engines/spit.b
new file mode 100644
index 00000000..2c42cb95
--- /dev/null
+++ b/appl/spree/engines/spit.b
@@ -0,0 +1,483 @@
+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, Card: import cardlib;
+ dTOP, dLEFT, dBOTTOM, oDOWN, EXPAND, FILLX, FILLY, aCENTRELEFT, Stackspec: import Cardlib;
+include "../gather.m";
+
+clique: ref Clique;
+CLICK, SPIT, SAY, SHOW: con iota;
+playing := 0;
+dealt := 0;
+deck: ref Object;
+buttons: ref Object;
+winner: ref Member;
+
+Dmember: adt {
+ spare: ref Object;
+ row: array of ref Object;
+ centre: ref Object;
+};
+
+dmembers := array[2] 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("spit: 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("spit: cannot load %s: %r\n", Cardlib->PATH);
+ return "bad module";
+ }
+ cardlib->init(spree, clique);
+
+ return nil;
+}
+
+maxmembers(): int
+{
+ return 2;
+}
+
+readfile(nil: int, nil: big, nil: int): array of byte
+{
+ return nil;
+}
+
+propose(members: array of string): string
+{
+ if (len members != 2)
+ return "need exactly two 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.spare, s + "spare");
+ cardlib->setarchivename(dp.centre, s + "centre");
+ for (j := 0; j < len dp.row; j++)
+ cardlib->setarchivename(dp.row[j], s + "row" + string j);
+ }
+ archiveobj.setattr("playing", string playing, None);
+ archiveobj.setattr("dealt", string dealt, None);
+ cardlib->setarchivename(deck, "deck");
+}
+
+start(members: array of ref Member, archived: int)
+{
+ cardlib->init(spree, clique);
+ if (archived) {
+ archiveobj := cardlib->unarchive();
+ allow->unarchive(archiveobj);
+ playing = int archiveobj.getattr("playing");
+ dealt = int archiveobj.getattr("dealt");
+ deck = cardlib->getarchiveobj("deck");
+ for (i := 0; i < len dmembers; i++) {
+ dp := dmembers[i] = ref Dmember;
+ s := "d" + string i + "_";
+ dp.spare = cardlib->getarchiveobj(s + "spare");
+ dp.centre = cardlib->getarchiveobj(s + "centre");
+ dp.row = array[4] of ref Object;
+ for (j := 0; j < len dp.row; j++)
+ dp.row[j] = cardlib->getarchiveobj(s + "row" + string j);
+ }
+ } else {
+ buttons = clique.newobject(nil, All, "buttons");
+ 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));
+ layout();
+ deal();
+ dealt = 1;
+ playing = 0;
+ allow->add(SPIT, nil, "spit");
+ allow->add(SAY, nil, "say &");
+ allow->add(SHOW, nil, "show");
+ }
+}
+
+command(p: ref Member, cmd: string): string
+{
+ (err, tag, toks) := allow->action(p, cmd);
+ if (err != nil){
+ if(winner != nil){
+ if(winner == p)
+ return "game has finished: you have won";
+ return "game has finished: you have lost";
+ }
+ return err;
+ }
+ cp := Cmember.find(p);
+ if (cp == nil)
+ return "you're only watching";
+ case tag {
+ SPIT =>
+ if (!dealt) {
+ deal();
+ dealt = 1;
+ } else if (!playing) {
+ go();
+ allow->add(CLICK, nil, "click %o %d");
+ playing = 1;
+ } else if (!canplay(!cp.ord)) {
+ go();
+ } else
+ return "it is possible to play";
+
+ CLICK =>
+ 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 (idx < 0 || idx >= len stack.children)
+ return "invalid index";
+ if (owner(stack) != cp)
+ return "not yours, don't touch!";
+ case stype {
+ "row" =>
+ card := getcard(stack.children[nc - 1]);
+ if (card.face == 0)
+ cardlib->setface(stack.children[nc - 1], 1);
+ else
+ select(cp, stack, (nc - 1, nc));
+ * =>
+ return "you can't move cards from there";
+ }
+ } else {
+ # selecting a stack to move to.
+ case stype {
+ "centre" =>
+ card := getcard(sel.stack.children[sel.r.start]);
+ onto := getcard(stack.children[nc - 1]);
+ if ((card.number + 1) % 13 != onto.number &&
+ (card.number + 12) % 13 != onto.number) {
+ sel.set(nil);
+ return "out of sequence";
+ }
+ sel.transfer(stack, -1);
+ for (i := 0; i < len d.row; i++)
+ if (len d.row[i].children > 0)
+ break;
+ if (i == len d.row) {
+ if (len d.spare.children == 0) {
+ remark(p.name + " has won");
+ winner = p;
+ allow->del(CLICK, nil);
+ allow->del(SPIT, nil);
+ clearsel();
+ } else
+ finish(cp);
+ }
+ "row" =>
+ if (owner(stack) != cp) {
+ sel.set(nil);
+ return "not yours, don't touch!";
+ }
+ if (nc != 0) {
+ sel.set(nil);
+ return "cannot stack cards";
+ }
+ sel.transfer(stack, -1);
+ * =>
+ sel.set(nil);
+ return "can't move there";
+ }
+ }
+
+ SAY =>
+ clique.action("say member " + string p.id + ": '" + concat(tl toks) + "'", nil, nil, All);
+
+ SHOW =>
+ clique.show(nil);
+ }
+ return nil;
+}
+
+canplay(ord: int): int
+{
+ d := dmembers[ord];
+ nmulti := nfree := 0;
+ for (j := 0; j < len d.row; j++) {
+ s1 := d.row[j];
+ if (len s1.children > 0) {
+ nmulti += len s1.children > 1;
+ card1 := getcard(s1.children[len s1.children - 1]);
+ for (k := 0; k < 2; k++) {
+ s2 := dmembers[k].centre;
+ if (len s2.children > 0) {
+ card2 := getcard(s2.children[len s2.children - 1]);
+ if ((card1.number + 1) % 13 == card2.number ||
+ (card1.number + 12) % 13 == card2.number)
+ return 1;
+ }
+ }
+ } else
+ nfree++;
+ }
+ return nmulti > 0 && nfree > 0;
+}
+
+bottomdiscard(src, dst: ref Object)
+{
+ cardlib->flip(src);
+ for (i := 0; i < len src.children; i++)
+ cardlib->setface(src.children[i], 0);
+ src.transfer((0, len src.children), dst, 0);
+}
+
+finish(winner: ref Cmember)
+{
+ loser := dmembers[!winner.ord];
+ for (i := 0; i < 2; i++) {
+ d := dmembers[i];
+ bottomdiscard(d.centre, loser.spare);
+ for (j := 0; j < len d.row; j++)
+ bottomdiscard(d.row[j], loser.spare);
+ }
+ playing = 0;
+ dealt = 0;
+ allow->del(CLICK, nil);
+ allow->add(SPIT, nil, "spit");
+ clearsel();
+}
+
+go()
+{
+ for (i := 0; i < 2; i++) {
+ d := dmembers[i];
+ n := len d.spare.children;
+ if (n > 0)
+ d.spare.transfer((n - 1, n), d.centre, -1);
+ else if ((m := len dmembers[!i].spare.children) > 0)
+ dmembers[!i].spare.transfer((m - 1, m), d.centre, -1);
+ else {
+ # both members' spare piles are used up; use central piles instead
+ for (j := 0; j < 2; j++) {
+ cardlib->discard(dmembers[j].centre, dmembers[j].spare, 0);
+ cardlib->flip(dmembers[j].spare);
+ }
+ go();
+ return;
+ }
+ cardlib->setface(d.centre.children[len d.centre.children - 1], 1);
+ }
+}
+
+getcard(card: ref Object): Card
+{
+ return cardlib->getcard(card);
+}
+
+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()
+{
+ for (i := 0; i < 2; i++) {
+ cp := Cmember.index(i);
+ d := dmembers[i] = ref Dmember;
+ d.spare = newstack(cp.obj, Untitledpilespec, "spare");
+ d.row = array[4] of {* => newstack(cp.obj, Openspec, "row")};
+ d.centre = newstack(cp.obj, Untitledpilespec, "centre");
+ }
+ deck = clique.newobject(nil, All, "stack");
+ cardlib->makecards(deck, (0, 13), "0");
+ cardlib->shuffle(deck);
+
+ entry := clique.newobject(nil, All, "widget entry");
+ entry.setattr("command", "say", All);
+ cardlib->addlayobj(nil, nil, nil, dTOP|FILLX, entry);
+
+ cardlib->addlayframe("arena", nil, nil, dTOP|EXPAND|FILLX|FILLY, dTOP);
+ maketable("arena");
+ spitbutton := newbutton("spit", "Spit!");
+ for (i = 0; i < 2; i++) {
+ d := dmembers[i];
+ f := "p" + string i;
+
+ subf := "f" + string i;
+ cardlib->addlayframe(subf, f, nil, dLEFT, dTOP);
+ cardlib->addlayobj(nil, subf, Cmember.index(i).layout, dTOP, spitbutton);
+ cardlib->addlayobj(nil, subf, nil, dTOP, d.spare);
+ for (j := 0; j < len d.row; j++)
+ cardlib->addlayobj(nil, f, nil, dLEFT|EXPAND|oDOWN, d.row[j]);
+ cardlib->addlayobj(nil, "centre", nil, dLEFT|EXPAND, d.centre);
+ }
+}
+
+newbutton(cmd, text: string): ref Object
+{
+ but := clique.newobject(nil, All, "widget button");
+ but.setattr("command", cmd, All);
+ but.setattr("text", text, All);
+ return but;
+}
+
+settopface(stack: ref Object, face: int)
+{
+ n := len stack.children;
+ if (n > 0)
+ cardlib->setface(stack.children[n - 1], face);
+}
+
+transfertop(src, dst: ref Object, index: int)
+{
+ n := len src.children;
+ src.transfer((n - 1, n), dst, index);
+}
+
+deal()
+{
+ clearsel();
+ n := len deck.children;
+ if (n > 0) {
+ deck.transfer((0, n / 2), dmembers[0].spare, 0);
+ deck.transfer((0, len deck.children), dmembers[1].spare, 0);
+ }
+
+ for (i := 0; i < 2; i++) {
+ d := dmembers[i];
+loop: for (j := 0; j < len d.row; j++) {
+ for (k := j; k < len d.row; k++) {
+ if (len d.spare.children == 0)
+ break loop;
+ transfertop(d.spare, d.row[k], -1);
+ }
+ }
+ for (j = 0; j < len d.row; j++)
+ settopface(d.row[j], 1);
+ }
+}
+
+maketable(parent: string)
+{
+ addlayframe: import cardlib;
+
+ for (i := 0; i < 2; i++) {
+ layout := Cmember.index(i).layout;
+ addlayframe("p" + string !i, parent, layout, dTOP|EXPAND, dBOTTOM);
+ addlayframe("p" + string i, parent, layout, dBOTTOM|EXPAND, dTOP);
+ addlayframe("centre", 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;
+}
+
+concat(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);
+}
+
+clearsel()
+{
+ n := cardlib->nmembers();
+ for (i := 0; i < n; i++)
+ Cmember.index(i).sel.set(nil);
+}
diff --git a/appl/spree/engines/whist.b b/appl/spree/engines/whist.b
new file mode 100644
index 00000000..ca0c26f9
--- /dev/null
+++ b/appl/spree/engines/whist.b
@@ -0,0 +1,305 @@
+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);
+}