diff options
Diffstat (limited to 'appl/spree/engines/hearts.b')
| -rw-r--r-- | appl/spree/engines/hearts.b | 300 |
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; +} |
