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