code: 9ferno

ref: b5959ac65bcd0d673e4e850f5a1dced6742536a8
dir: /appl/spree/engines/canfield.b/

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