shithub: purgatorio

ref: 01338076d1a3610858f41fcbcdc7e46fb1109b77
dir: /appl/spree/engines/liars.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;

MAXPLAYERS: con 32;

clique: ref Clique;

# each member is described by a state machine.
# a member progresses through the following states:
#
# Notplaying
# 	istart			-> Havedice
# 	otherstarts	-> Waiting
# Havedice
# 	declare		-> Waiting
# 	look			-> Looking
# Looking
# 	expose		-> Looking
# 	unexpose		-> Looking
# 	declare		-> Waiting
# 	roll			-> Rolled
# Rolled
# 	expose		-> Rolled
# 	unexpose		-> Rolled
# 	declare		-> Waiting
# Waiting
# 	queried		-> Queried
# 	lost			-> Havedice
# Queried
# 	reject,win		-> Waiting
# 	reject,lose	-> Havedice
# 	accept		-> Havedice


plate, cup, space, members: ref Object;
dice := array[5] of ref Object;

declared: int;

# member states
Notplaying, Havedice, Looking, Rolled, Waiting, Queried: con iota;

# info on a particular member
Info: adt {
	state:	int;
	id:		int;
	member:	ref Object;
	action:	ref Object;
};

info := array[MAXPLAYERS] of ref Info;
plorder := array[MAXPLAYERS] of int;	# map member id to their place around the table
nplaying := 0;
nmembers := 0;
turn := 0;

clienttype(): string
{
	return "none";
}

init(g: ref Clique, srvmod: Spree): string
{
	sys = load Sys Sys->PATH;
	clique = g;
	spree = srvmod;

	plate = clique.newobject(nil, ~0, "plate");
	cup = clique.newobject(plate, 0, "cup");
	space = clique.newobject(plate, ~0, "space");
	members = clique.newobject(nil, ~0, "members");

	for (i := 0; i < len dice; i++) {
		dice[i] = clique.newobject(cup, ~0, "die");
		dice[i].setattr("number", string rand(6), ~0);
	}

	return nil;
}

join(member: ref Member): string
{
	check();
	pmask := 1 << member.id;

	ord := nmembers++;
	inf := info[ord] = ref Info;
	inf.state = -1;
	inf.id = member.id;
	inf.action = clique.newobject(nil, pmask, "actions" + string member.id);
	plorder[member.id] = ord;
	setstate(ord, Notplaying);
	check();
	return nil;
}
	
leave(member: ref Member)
{
	check();

	ord := plorder[member.id];
	state := info[ord].state;
	info[ord] = nil;
	for (i := 0; i < nmembers; i++)
		if (i != ord)
			setstate(i, Notplaying);
	nmembers--;
	nplaying = 0;
	clique.action("say member " + string ord + " has left. the clique stops.", nil, nil, ~0);
	check();
}

currmember: ref Member;
currcmd: string;
command(member: ref Member, cmd: string): string
{
	check();
	e := ref Sys->Exception;
	if (sys->rescue("parse:*", e) == Sys->EXCEPTION) {
		sys->rescued(Sys->ONCE, nil);
		check();
		currmember = nil;
		currcmd = nil;
		return e.name[6:];
	}
	currmember = member;
	currcmd = cmd;
	(nlines, lines) := sys->tokenize(cmd, "\n");
	assert(nlines > 0, "unknown command");
	(n, toks) := sys->tokenize(hd lines, " ");
	assert(n > 0, "unknown command");
	pmask := 1 << member.id;
	ord := plorder[member.id];
	state := info[ord].state;
	case hd toks {
	"say" or
	"show" or
	"showme" =>
		case hd toks {
		"say" =>
			clique.action("say member " + string member.id + ": '" + (hd lines)[4:] + "'", nil, nil, ~0);
		"show" =>			# show [memberid]
			p: ref Member = nil;
			if (n == 2) {
				memberid := int hd tl toks;
				p = clique.member(memberid);
				assert(p != nil, "bad memberid");
			}
			clique.show(p);
		"showme" =>
			clique.show(member);
		}
		currmember = nil;
		currcmd = nil;
		return nil;
	}
	case state {
	Notplaying =>
		case hd toks {
		"start" =>
			assert(nplaying == 0, "clique is in progress");
			assert(nmembers > 1, "need at least two members");
			newinfo := array[len info] of ref Info;
			members.deletechildren((0, len members.children));
			j := 0;
			for (i := 0; i < len info; i++)
				if (info[i] != nil)
					newinfo[j++] = info[i];
			info = newinfo;
			nplaying = nmembers;
			for (i = 0; i < nplaying; i++) {
				info[i].member = clique.newobject(members, ~0, nil);
				info[i].member.setattr("id", string info[i].id, ~0);
			}
			turn = rand(nplaying);
			start();
		* =>
			assert(0, "you are not playing");
		}
	Havedice =>
		case hd toks {
		"declare" =>
			# declare hand
			declare(ord, tl toks);
		"look" =>
			cup.setattr("raised", "1", ~0);
			cup.setvisibility(pmask);
			setstate(ord, Looking);
		* =>
			assert(0, "bad command");
		}
	Looking =>
		case hd toks {
		"expose" or
		"unexpose" =>
			expose(n, toks);
		"declare" =>
			declare(ord, tl toks);
		"roll" =>
			# roll index...
			# XXX should be able to roll in the open too
			for (toks = tl toks; toks != nil; toks = tl toks) {
				index := int hd toks;
				checkrange((index, index), cup);
				cup.children[index].setattr("number", string rand(6), ~0);
			}
			setstate(ord, Rolled);
		* =>
			assert(0, "bad command");
		}
	Rolled =>
		case hd toks {
		"expose" or
		"unexpose" =>
			expose(n, toks);
		"declare" =>
			declare(ord, tl toks);
		* =>
			assert(0, "bad command");
		}
	Waiting =>
		assert(0, "not your turn");
	Queried =>
		case hd toks {
		"reject" =>
			# lift the cup!
			cup.transfer((0, len cup.children), space, len space.children);
			assert(len space.children == 5, "lost a die somewhere!");
			dvals := array[5] of int;
			for (i := 0; i < 5; i++)
				dvals[i] = int space.children[i].getattr("number");
			actval := value(dvals);
			if (actval >= declared) {
				# declaration was correct; rejector loses
				clique.action("say member " + string ord + " loses.", nil, nil, ~0);
				turn = ord;
				start();
			} else {
				# liar caught out. rejector wins.
				clique.action("say member " + string turn + " was lying...", nil, nil, ~0);
				start();
			}
		"accept" =>
			# dice accepted, turn moves on
			# XXX should allow for anticlockwise play
			newturn := (turn + 1) % nplaying;
			plate.setattr("owner", string newturn, ~0);
			setstate(ord, Havedice);
			setstate(turn, Waiting);
		}
	}
	check();
	currmember = nil;
	currcmd = nil;
	return nil;
}

expose(n: int, toks: list of string)
{
	# (un)expose index
	assert(n == 2, Eusage);
	(src, dest) := (cup, space);
	if (hd toks == "unexpose")
		(src, dest) = (space, cup);
	index := int hd tl toks;
	checkrange((index, index+1), cup);
	src.transfer((index, index+1), dest, len dest.children);
}

start()
{
	clique.action("start", nil, nil, ~0);
	space.transfer((0, len space.children), cup, len cup.children);
	cup.setvisibility(0);
	for (i := 0; i < len dice; i++)
		dice[i].setattr("number", string rand(6), ~0);

	plate.setattr("owner", string turn, ~0);
	for (i = 0; i < nplaying; i++) {
		if (i == turn)
			setstate(i, Havedice);
		else
			setstate(i, Waiting);
	}
	declared = 0;
}

declare(ord: int, toks: list of string)
{
	cup.setvisibility(0);
	assert(len toks == 1 && len hd toks == 5, "bad declaration");
	d := hd toks;
	v := array[5] of {* => 0};
	for (i := 0; i < 5; i++) {
		v[i] = (hd toks)[i] - '0';
		assert(v[i] >= 0 && v[i] <= 5, "bad declaration");
	}
	newval := value(v);
	assert(newval > declared, "declaration not high enough");
	declared = newval;

	setstate(turn, Waiting);
	setstate((turn + 1) % nplaying, Queried);
}

# check that range is valid for object's children
checkrange(r: Range, o: ref Object)
{
	assert(r.start >= 0 && r.start < len o.children &&
			r.end >= r.start && r.end >= 0 &&
			r.end <= len o.children,
			"index out of range");
}

setstate(ord: int, state: int)
{
	poss: string;
	case state {
	Notplaying =>
		poss = "start";
	Havedice =>
		poss = "declare look";
	Looking =>
		poss = "expose unexpose declare roll";
	Rolled =>
		poss = "expose unexpose declare";
	Waiting =>
		poss = "";
	Queried =>
		poss = "accept reject";
	* =>
		sys->print("liarclique: unknown state %d, member %d\n", state, ord);
		sys->raise("panic");
	}
	info[ord].action.setattr("actions", poss, 1<<info[ord].id);
	info[ord].state = state;
}

obj(ext: int): ref Object
{
	assert((o := currmember.obj(ext)) != nil, "bad object");
	return o;
}

Eusage: con "bad command usage";

assert(b: int, err: string)
{
	if (b == 0) {
		sys->print("cardclique: error '%s' on %s", err, currcmd);
		sys->raise("parse:" + err);
	}
}

checkobj(o: ref Object, what: string)
{
	if (o != nil && o.id == -1) {
		clique.show(currmember);
		sys->print("object %d has been deleted unexpectedly (%s)\n", o.id, what);
		sys->raise("panic");
	}
}

check()
{
}

NOTHING, PAIR, TWOPAIRS, THREES, LOWSTRAIGHT,
FULLHOUSE, HIGHSTRAIGHT, FOURS, FIVES: con iota;

what := array[] of {
NOTHING => "nothing",
PAIR => "pair",
TWOPAIRS => "twopairs",
THREES => "threes",
LOWSTRAIGHT => "lowstraight",
FULLHOUSE => "fullhouse",
HIGHSTRAIGHT => "highstraight",
FOURS => "fours",
FIVES => "fives"
};
	
same(dice: array of int): int
{
	x := dice[0];
	for (i := 0; i < len dice; i++)
		if (dice[i] != x)
			return 0;
	return 1;
}

val(hi, lo: int): int
{
	return hi * 100000 + lo;
}

D: con 10;

value(dice: array of int): int
{
	mergesort(dice, array[5] of int);

	for (i := 0; i < 5; i++)
		sys->print("%d ", dice[i]);
	sys->print("\n");

	# five of a kind
	x := dice[0];
	if (same(dice))
		return val(FIVES, dice[0]);

	# four of a kind
	if (same(dice[1:]))
		return val(FOURS, dice[0] + dice[1]*D);
	if (same(dice[0:4]))
		return val(FOURS, dice[4] + dice[0]*D);

	# high straight
	if (dice[0] == 1 && dice[1] == 2 && dice[2] == 3 &&
			dice[3] == 4 && dice[4] == 5)
		return val(HIGHSTRAIGHT, 0);

	# full house
	if (same(dice[0:3]) && same(dice[3:5]))
		return val(FULLHOUSE, dice[0]*D + dice[4]);
	if (same(dice[0:2]) && same(dice[2:5]))
		return val(FULLHOUSE, dice[4]*D + dice[0]);

	# low straight
	if (dice[0] == 0 && dice[1] == 1 && dice[2] == 2 &&
			dice[3] == 3 && dice[4] == 4)
		return val(LOWSTRAIGHT, 0);
	# three of a kind
	if (same(dice[0:3]))
		return val(THREES, dice[3] + dice[4]*D + dice[0]*D*D);
	if (same(dice[1:4]))
		return val(THREES, dice[0] + dice[4]*D + dice[1]*D*D);
	if (same(dice[2:5]))
		return val(THREES, dice[0] + dice[1]*D + dice[2]*D*D);

	for (i = 0; i < 4; i++)
		if (same(dice[i:i+2]))
			break;
	case i {
	4 =>
		return val(NOTHING, dice[0] + dice[1]*D + dice[2]*D*D +
				dice[3]*D*D*D + dice[4]*D*D*D*D);
	3 =>
		return val(PAIR, dice[0] + dice[1]*D + dice[2]*D*D + dice[3]*D*D*D);
	2 =>
		return val(PAIR, dice[0] + dice[1]*D + dice[4]*D*D + dice[2]*D*D*D);
	}
	h := array[5] of int;
	h[0:] = dice;
	if (i == 1)
		(h[0], h[2]) = (h[2], h[0]);
	# pair is in first two dice
	if (same(h[2:4]))
		return val(TWOPAIRS, h[4] + h[2]*D + h[0]*D*D);
	if (same(h[3:5]))
		return val(TWOPAIRS, h[2] + h[0]*D + h[4]*D*D);
	return val(PAIR, dice[2] + dice[3]*D + dice[4]*D*D + dice[0]*D*D*D);
}

mergesort(a, b: array of int)
{
	r := len a;
	if (r > 1) {
		m := (r-1)/2 + 1;
		mergesort(a[0:m], b[0:m]);
		mergesort(a[m:], b[m:]);
		b[0:] = a;
		for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
			if (b[i] > b[j])
				a[k] = b[j++];
			else
				a[k] = b[i++];
		}
		if (i < m)
			a[k:] = b[i:m];
		else if (j < r)
			a[k:] = b[j:r];
	}
}