code: 9ferno

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

View raw version
implement Gatherengine;

include "sys.m";
	sys: Sys;
include "draw.m";
include "sets.m";
	sets: Sets;
	Set, set, A, B, All, None: 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;
	getcard: import cardlib;
	dTOP, dRIGHT, dLEFT, oRIGHT, oDOWN,
	aCENTRERIGHT, aCENTRELEFT, aUPPERRIGHT,
	EXPAND, FILLX, FILLY, Stackspec: import Cardlib;
include "../gather.m";

clique: ref Clique;

open: array of ref Object;		# [8]
cells: array of ref Object;		# [4]
acepiles: array of ref Object;	# [4]
txpiles: array of ref Object;	# [len open + len cells]
deck: ref Object;

fnames := array[] of {
"qua",
"quack",
"quackery",
"quad",
"quadrangle",
"quadrangular",
"quadrant",
"quadratic",
"quadrature",
"quadrennial",
};
dir(name: string, perm: int, owner: string): Sys->Dir
{
	d := Sys->zerodir;
	d.name = name;
	d.uid = owner;
	d.gid = owner;
	d.qid.qtype = (perm >> 24) & 16rff;
	d.mode = perm;
	# d.atime = now;
	# d.mtime = now;
	return d;
}


suitsout := array[4] of {* => -1};

mainmember: ref Cmember;

CLICK: 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";
}

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);
	sets = load Sets Sets->PATH;
	if (sets == nil) {
		sys->print("whist: cannot load %s: %r\n", Sets->PATH);
		return "bad module";
	}
	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);
	g.fcreate(0, -1, dir("data", 8r555|Sys->DMDIR, "spree"));
	for(i := 0; i < len fnames; i++)
		g.fcreate(i + 1, 0, dir(fnames[i], 8r444, "arble"));
	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)
{
sys->print("freecell: starting\n");
	if (archived) {
		archiveobj := cardlib->unarchive();
		open = cardlib->getarchivearray("open");
		cells = cardlib->getarchivearray("cells");
		acepiles = cardlib->getarchivearray("acepiles");
		txpiles = cardlib->getarchivearray("txpiles");
		deck = cardlib->getarchiveobj("deck");
		for (i := 0; i < len suitsout; i++)
			suitsout[i] = int archiveobj.getattr("suitsout" + string i);
		mainmember = Cmember.findid(int archiveobj.getattr("mainmember"));
		allow->unarchive(archiveobj);
		archiveobj.delete();
	} else {
		sys->print("freecell: starting afresh\n");
		mainmember = Cmember.join(members[0], -1);
		mainmember.layout.lay.setvisibility(All);
		startclique();
		movefree();
		allow->add(CLICK, members[0], "click %o %d");
	}
}

readfile(f: int, boffset: big, n: int): array of byte
{
	offset := int boffset;
	f--;
	if (f < 0 || f >= len fnames)
		return nil;
	data := array of byte fnames[f];
	if (offset >= len data)
		return nil;
	if (offset + n > len data)
		n = len data - offset;
	return data[offset:offset + n];
}

archive()
{
	sys->print("freecell: archiving\n");
	archiveobj := cardlib->archive();
	cardlib->archivearray(open, "open");
	cardlib->archivearray(cells, "cells");
	cardlib->archivearray(acepiles, "acepiles");
	cardlib->archivearray(txpiles, "txpiles");
	cardlib->setarchivename(deck, "deck");
	for (i := 0; i < len suitsout; i++)
		archiveobj.setattr("suitsout" + string i, string suitsout[i], None);
	archiveobj.setattr("mainmember", string mainmember.id, 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 (idx < 0 || idx >= len stack.children)
				return "invalid index";
			case stype {
			"cell" or
			"open" =>
				select(cp, stack, (idx, 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!";
				addtoacepile(sel.stack);
				sel.set(nil);
				movefree();
			"open" =>
				c := getcard(sel.stack.children[sel.r.start]);
				col := !isred(c.suit);
				n := c.number + 1;
				for (i := sel.r.start; i < sel.r.end; i++) {
					c2 := getcard(sel.stack.children[i]);
					if (isred(c2.suit) == col)
						return "bad colour sequence";
					if (c2.number != n - 1)
						return "bad number sequence";
					n = c2.number;
					col = isred(c2.suit);
				}
				if (nc != 0) {
					c2 := getcard(stack.children[nc - 1]);
					if (isred(c2.suit) == isred(c.suit) || c2.number != c.number + 1)
						return "opposite colours, descending, only";
				}
				r := sel.r;
				selstack := sel.stack;
				sel.set(nil);
				fc := freecells(stack);
				if (r.end - r.start - 1 > len fc)
					return "not enough free cells";
				n = 0;
				for (i = r.end - 1; i >= r.start + 1; i--)
					selstack.transfer((i, i + 1), fc[n++], -1);
				selstack.transfer((i, i + 1), stack, -1);
				while (--n >= 0)
					fc[n].transfer((0, 1), stack, -1);
				movefree();
			"cell" =>
				if (sel.r.end - sel.r.start > 1 || nc > 0)
					return "only one card allowed there";
				sel.transfer(stack, -1);
				movefree();
			* =>
				return "can't move there";
			}
		}
	}
	return nil;
}

freecells(dest: ref Object): array of ref Object
{
	fc := array[len txpiles] of ref Object;
	n := 0;
	for (i := 0; i < len txpiles; i++)
		if (len txpiles[i].children == 0 && txpiles[i] != dest)
			fc[n++] = txpiles[i];
	return fc[0:n];
}

# move any cards that can be moved.
movefree()
{
	nmoved := 1;
	while (nmoved > 0) {
		nmoved = 0;
		for (i := 0; i < len txpiles; i++) {
			pile := txpiles[i];
			nc := len pile.children;
			if (nc == 0)
				continue;
			card := getcard(pile.children[nc - 1]);
			if (suitsout[card.suit] != card.number - 1)
				continue;
			# card can be moved; now make sure there's no card out
			# that might be moved onto this card
			for (j := 0; j < len suitsout; j++)
				if (isred(j) != isred(card.suit) && card.number > 1 && suitsout[j] < card.number - 1)
					break;
			if (j == len suitsout) {
				addtoacepile(pile);
				nmoved++;
			}
		}
	}
}

addtoacepile(pile: ref Object)
{
	nc := len pile.children;
	if (nc == 0)
		return;
	card := getcard(pile.children[nc - 1]);
	for (i := 0; i < len acepiles; i++) {
		anc := len acepiles[i].children;
		if (anc == 0) {
			if (card.number == 0)
				break;
			continue;
		}
		acard := getcard(acepiles[i].children[anc - 1]);
		if (acard.suit == card.suit && acard.number == card.number - 1)
			break;
	}
	if (i < len acepiles) {
		pile.transfer((nc - 1, nc), acepiles[i], -1);
		suitsout[card.suit] = card.number;
	}
}

startclique()
{
	addlayobj, addlayframe: import cardlib;

	open = array[8] of {* => newstack(nil, Openspec, "open", nil)};
	acepiles = array[4] of {* => newstack(nil, Untitledpilespec, "acepile", nil)};
	cells = array[4] of {* => newstack(nil, Untitledpilespec, "cell", "cell")};
	for (i := 0; i < len cells; i++)
		cells[i].setattr("showsize", "0", All);

	txpiles = array[12] of ref Object;
	txpiles[0:] = open;
	txpiles[len open:] = cells;
	deck = clique.newobject(nil, All, "stack");

	cardlib->makecards(deck, (0, 13), nil);

	addlayframe("arena", nil, nil, dTOP|EXPAND|FILLX|FILLY, dTOP);
	addlayframe("top", "arena", nil, dTOP|EXPAND, dTOP);
	addlayframe("bot", "arena", nil, dTOP|EXPAND, dTOP);
	for (i = 0; i < 4; i++)
		addlayobj(nil, "top", nil, dRIGHT, acepiles[i]);
	for (i = 0; i < 4; i++)
		addlayobj(nil, "top", nil, dLEFT, cells[i]);
	for (i = 0; i < len open; i++)
		addlayobj(nil, "bot", nil, dLEFT|oDOWN|EXPAND, open[i]);
	deal();
}

deal()
{
	cardlib->shuffle(deck);
	cardlib->deal(deck, 7, open, 0);
}

newstack(parent: ref Object, spec: Stackspec, stype, title: string): ref Object
{
	stack := cardlib->newstack(parent, nil, spec);
	stack.setattr("type", stype, None);
	stack.setattr("actions", "click", All);
	stack.setattr("title", title, All);
	return stack;
}

isred(suit: int): int
{
	return suit == Cardlib->DIAMONDS || 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);
	}
}

#randstate := 1;
#srand(seed: int)
#{
#        randstate = seed;
#}
#
#rand(): int
#{
#	randstate = randstate * 214013 + 2531011;
#	return (randstate >> 16) & 0x7fff;
#}
##From: jimh@MICROSOFT.com (Jim Horne)
##
##I'm happy to share the card shuffle algorithm, but I warn you,
##it does depend on the rand() and srand() function built into MS
##compilers.  The good news is that I believe these work the same
##for all our compilers.
##
##I use cards.dll which has it's own mapping of numbers (0-51) to
##cards.  The following will give you the idea.  Play around with
##this and you'll be able to generate all the cliques.
##
##Go ahead and post the code.  People might as well have fun with it.
##Please keep me posted on anything interesting that comes of it.  
##Thanks.
#
#msdeal(cliquenumber: int): array of array of Card
#{
#	deck := array[52] of Card;
#	for (i := 0; i < len deck; i++)	# put unique card in each deck loc.
#		deck[i] = Card(i % 4, i / 4, 0);
#	wleft := 52;				# cards left to be chosen in shuffle
#	cards := array[8] of {* => array[7] of Card};
#	max := array[8] of {* => 0};
#	srand(cliquenumber);
#	for (i = 0; i < 52; i++)	{
#		j := rand() % wleft;
#		card[i % 8][i / 8] = deck[j];
#		max[i % 8] = i / 8;
#		deck[j] = deck[--wleft];
#	}
#	for (i = 0; i < len cards; i++)
#		cards[i] = cards[i][0:max[i]];
#	return cards;
#}