code: purgatorio

ref: a8083462e62459b2ae8a243dc4ba88416eba03b1
dir: /appl/spree/engines/othello.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 "objstore.m";
	objstore: Objstore;
include "../gather.m";

clique: ref Clique;

Black, White, Nocolour: con iota;		# first two must be 0 and 1.
N: con 8;

boardobj: ref Object;
board:	array of array of int;
pieces:	array of int;
turn		:= Nocolour;
members	:= array[2] of ref Member;			# member ids of those playing

Point: adt {
	x, y: int;
	add: fn(p: self Point, p1: Point): Point;
	inboard: fn(p: self Point): int;
};

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

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

	objstore = load Objstore Objstore->PATH;
	if (objstore == nil) {
		sys->print("othello: cannot load %s: %r", Objstore->PATH);
		return "bad module";
	}
	objstore->init(srvmod, g);

	return nil;
}

maxmembers(): int
{
	return 2;
}

readfile(nil: int, nil: big, nil: int): array of byte
{
	return nil;
}

propose(members: array of string): string
{
	if (len members != 2)
		return "need exactly two members";
	return nil;
}

archive()
{
	objstore->setname(boardobj, "board");
}		

start(pl: array of ref Member, archived: int)
{
	members = pl;
	board = array[N] of {* => array[N] of {* => Nocolour}};
	pieces = array[2] of {* => 0};
	if (archived) {
		objstore->unarchive();
		boardobj = objstore->get("board");
		for (i := 0; i < N; i++) {
			for (j := 0; j < N; j++) {
				a := boardobj.getattr(pt2attr((j, i)));
				if (a != nil) {
					piece := int a;
					board[j][i] = piece;
					if (piece != Nocolour)
						pieces[piece]++;
				}
			}
		}
		turn = int boardobj.getattr("turn");
	} else {
		boardobj = clique.newobject(nil, All, nil);
		boardobj.setattr("members", string members[Black].name + " " + string members[White].name, All);
		for (ps := (Black, (3, 3)) :: (Black, (4, 4)) :: (White, (3, 4)) :: (White, Point(4, 3)) :: nil;
				ps != nil;
				ps = tl ps) {
			(colour, p) := hd ps;
			setpiece(colour, p);
		}
		turn = Black;
		boardobj.setattr("turn", string Black, All);
	}
}

cliqueover()
{
	turn = Nocolour;
	boardobj.setattr("winner", string winner(), All);
	boardobj.setattr("turn", string turn, All);
}

command(member: ref Member, cmd: string): string
{
	{
		(n, toks) := sys->tokenize(cmd, " \n");
		assert(n > 0, "unknown command");
	
		case hd toks {
		"move" =>
			assert(n == 3, "bad command usage");
			assert(turn != Nocolour, "clique has finished");
			assert(member == members[White] || member == members[Black], "you are not playing");
			assert(member == members[turn], "it is not your turn");
			p := Point(int hd tl toks, int hd tl tl toks);
			assert(p.x >= 0 && p.x < N && p.y >= 0 && p.y < N, "invalid move position");
			assert(board[p.x][p.y] == Nocolour, "position is already occupied");
			assert(newmove(turn, p, 1), "cannot move there");
	
			turn = reverse(turn);
			if (!canplay()) {
				turn = reverse(turn);
				if (!canplay())
					cliqueover();
			}
			boardobj.setattr("turn", string turn, All);
			return nil;
		}
		sys->print("othello: unknown client command '%s'\n", hd toks);
		return "who knows";
	} exception e {
	"parse:*" =>
		return e[6:];
	}
}

Directions := array[] of {Point(0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1)};

setpiece(colour: int, p: Point)
{
	v := board[p.x][p.y];
	if (v != Nocolour)
		pieces[v]--;
	board[p.x][p.y] = colour;
	pieces[colour]++;
	boardobj.setattr(pt2attr(p), string colour, All);
}

pt2attr(pt: Point): string
{
	s := "  ";
	s[0] = pt.x + 'a';
	s[1] = pt.y + 'a';
	return  s;
}

# member colour has tried to place a piece at mp.
# return -1 if it's an illegal move, 0 otherwise.
# (in which case appropriate updates are sent out all round).
# if update is 0, just check for the move's validity
# (no change to the board, no updates sent)
newmove(colour: int, mp: Point, update: int): int
{
	totchanged := 0;
	for (i := 0; i < len Directions; i++) {
		d := Directions[i];
		n := 0;
		for (p := mp.add(d); p.inboard(); p = p.add(d)) {
			n++;
			if (board[p.x][p.y] == colour || board[p.x][p.y] == Nocolour)
				break;
		}
		if (p.inboard() && board[p.x][p.y] == colour && n > 1) {
			if (!update)
				return 1;
			totchanged += n - 1;
			for (p = mp.add(d); --n > 0; p = p.add(d))
				setpiece(reverse(board[p.x][p.y]), p);
		}
	}
	if (totchanged > 0) {
		setpiece(colour, mp);
		return 1;
	}
	return 0;
}

# who has most pieces?
winner(): int
{
	if (pieces[White] > pieces[Black])
		return White;
	else if (pieces[Black] > pieces[White])
		return Black;
	return Nocolour;
}

# is there any possible legal move?
canplay(): int
{
	for (y := 0; y < N; y++)
		for (x := 0; x < N; x++)
			if (board[x][y] == Nocolour && newmove(turn, (x, y), 0))
				return 1;
	return 0;
}

reverse(colour: int): int
{
	if (colour == Nocolour)
		return Nocolour;
	return !colour;
}

Point.add(p: self Point, p1: Point): Point
{
	return (p.x + p1.x, p.y + p1.y);
}

Point.inboard(p: self Point): int
{
	return p.x >= 0 && p.x < N && p.y >= 0 && p.y < N;
}

assert(b: int, err: string)
{
	if (b == 0)
		raise "parse:" + err;
}