code: 9ferno

ref: fcccaf74c54ecd44b7e8bf45a7b0c99572f6a949
dir: /appl/spree/engines/hearts.b/

View raw version
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;
}