summaryrefslogtreecommitdiff
path: root/appl/spree/engines/spit.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/spree/engines/spit.b')
-rw-r--r--appl/spree/engines/spit.b483
1 files changed, 483 insertions, 0 deletions
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);
+}