code: purgatorio

ref: a920c765f2b4130590fb5971a50690b21664957a
dir: /appl/spree/clients/othello.b/

View raw version
implement Othello;
include "sys.m";
	sys: Sys;
include "draw.m";
	draw: Draw;
	Point, Rect: import draw;
include "tk.m";
	tk: Tk;
include "tkclient.m";
	tkclient: Tkclient;

SQ: con 30;		# Square size in pixels
N: con 8;

stderr: ref Sys->FD;

Othello: module {
	init:   fn(ctxt: ref Draw->Context, argv: list of string);
};

Black, White, Nocolour: con iota;
colours := array[] of {White => "white", Black => "black"};

win: ref Tk->Toplevel;
board: array of array of int;
notifypid := -1;
membername: string;
membernames := array[2] of string;

init(ctxt: ref Draw->Context, argv: list of string)
{
	sys = load Sys Sys->PATH;
	stderr = sys->fildes(2);
	draw = load Draw Draw->PATH;
	tk = load Tk Tk->PATH;
	tkclient = load Tkclient Tkclient->PATH;
	if (tkclient == nil) {
		sys->fprint(stderr, "othello: cannot load %s: %r\n", Tkclient->PATH);
		raise "fail:bad module";
	}
	tkclient->init();

	if (len argv >= 3) {		# argv: modname mnt dir ...
		membername = readfile(hd tl argv + "/name");
		sys->print("name is %s\n", membername);
	}
	client1(ctxt);
}

configcmds := array[] of {
"canvas .c -height " + string (SQ * N) + " -width " + string (SQ * N) + " -bg green",
"label .status -text {No clique in progress}",
"frame .f",
"label .f.l -text {watching} -bg white",
"label .f.turn -text {}",
"pack .f.l -side left -expand 1  -fill x",
"pack .f.turn -side left -fill x -expand 1",
"pack .c -side top",
"pack .status .f -side top -fill x",
"bind .c <ButtonRelease-1> {send cmd b1up %x %y}",
};

client1(ctxt: ref Draw->Context)
{
	cliquefd := sys->fildes(0);

	sys->pctl(Sys->NEWPGRP, nil);

	winctl: chan of string;
	(win, winctl) = tkclient->toplevel(ctxt, nil,
		"Othello", Tkclient->Appl);
	bcmd := chan of string;
	tk->namechan(win, bcmd, "cmd");
	for (i := 0; i < len configcmds; i++)
		cmd(win, configcmds[i]);

	for (i = 0; i < N; i++)
		for (j := 0; j < N; j++)
			cmd(win, ".c create rectangle " + r2s(square(i, j)));
	board = array[N] of {* => array[N] of {* => Nocolour}};
	tkclient->onscreen(win, nil);
	tkclient->startinput(win, "ptr"::"kbd"::nil);
	spawn updateproc(cliquefd);

	for (;;) alt {
	c := <-bcmd =>
		(n, toks) := sys->tokenize(c, " ");
		case hd toks {
		"b1up" =>
			(inboard, x, y) := boardpos((int hd tl toks, int hd tl tl toks));
			if (!inboard)
				break;
			othellocmd(cliquefd, "move " + string x + " " + string y);
			cmd(win, "update");
		}
	s := <-win.ctxt.kbd =>
		tk->keyboard(win, s);
	s := <-win.ctxt.ptr =>
		tk->pointer(win, *s);
	s := <-win.ctxt.ctl or
	s = <-win.wreq or
	s = <-winctl =>
		if (s == "exit")
			sys->write(cliquefd, array[0] of byte, 0);
		tkclient->wmctl(win, s);
	}
}

othellocmd(fd: ref Sys->FD, s: string): int
{
	if (sys->fprint(fd, "%s\n", s) == -1) {
		notify(sys->sprint("%r"));
		return 0;
	}
	return 1;
}

updateproc(cliquefd: ref Sys->FD)
{
	buf := array[Sys->ATOMICIO] of byte;
	while ((n := sys->read(cliquefd, buf, len buf)) > 0) {
		(nil, lines) := sys->tokenize(string buf[0:n], "\n");
		for (; lines != nil; lines = tl lines)
			applyupdate(hd lines);
		cmd(win, "update");
	}
	if (n < 0)
		sys->fprint(stderr, "othello: error reading updates: %r\n");
	sys->fprint(stderr, "othello: updateproc exiting\n");
}

applyupdate(s: string)
{
	(nt, toks) := sys->tokenize(s, " ");
	case hd toks {
	"create" =>
		; # ignore - there's only one object (the board)
	"set" =>
		# set objid attr val
		toks = tl tl toks;
		(attr, val) := (hd toks, hd tl toks);
		case attr {
		"members" =>
			membernames[Black] = hd tl toks;
			membernames[White] = hd tl tl toks;
			status(membernames[Black]+ "(Black) vs. " + string membernames[White] + "(White)");
			if (membername == membernames[Black])
				cmd(win, ".f.l configure -text Black");
			else if (membername == membernames[White])
				cmd(win, ".f.l configure -text White");
		"turn" =>
			turn := int val;
			if (turn != Nocolour) {
				if (membername == membernames[turn])
					cmd(win, ".f.turn configure -text {(Your turn)}");
				else if (membername == membernames[!turn])
					cmd(win, ".f.turn configure -text {}");
			}
		"winner" =>
			text := "it was a draw";
			winner := int val;
			if (winner != Nocolour)
				text = colours[int val] + " won.";
			status("clique over. " + text);
			cmd(win, ".f.l configure -text {watching}");
		* =>
			(x, y) := (attr[0] - 'a', attr[1] - 'a');
			set(x, y, int val);
		}
	* =>
		sys->fprint(stderr, "othello: unknown update message '%s'\n", s);
	}
}

status(s: string)
{
	cmd(win, ".status configure -text '" + s);
}

itemopts(colour: int): string
{
	return "-fill " + colours[colour] +
		" -outline " + colours[!colour];
}

set(x, y, colour: int)
{
	id := piece(x, y);
	if (colour == Nocolour)
		cmd(win, ".c delete " + id);
	else if (board[x][y] != Nocolour)
		cmd(win, ".c itemconfigure " + id + " " + itemopts(colour));
	else
		cmd(win, ".c create oval " + r2s(square(x, y)) + " " +
			itemopts(colour) +
			" -tags {piece " + id + "}");
	board[x][y] = colour;
}

notify(s: string)
{
	kill(notifypid);
	sync := chan of int;
	spawn notifyproc(s, sync);
	notifypid = <-sync;
}

notifyproc(s: string, sync: chan of int)
{
	sync <-= sys->pctl(0, nil);
	cmd(win, ".c delete notify");
	id := cmd(win, ".c create text 0 0 -anchor nw -fill red -tags notify -text '" + s);
	bbox := cmd(win, ".c bbox " + id);
	cmd(win, ".c create rectangle " + bbox + " -fill #ffffaa -tags notify");
	cmd(win, ".c raise " + id);
	cmd(win, "update");
	sys->sleep(750);
	cmd(win, ".c delete notify");
	cmd(win, "update");
	notifypid = -1;
}

boardpos(p: Point): (int, int, int)
{
	(x, y) := (p.x / SQ, p.y / SQ);
	if (x < 0 || x > N - 1 || y < 0 || y > N - 1)
		return (0, 0, 0);
	return (1, x, y);
}

square(x, y: int): Rect
{
	return ((SQ*x, SQ*y), (SQ*(x + 1), SQ*(y + 1)));
}

piece(x, y: int): string
{
	return "p" + string x + "." + string y;
}

cmd(top: ref Tk->Toplevel, s: string): string
{
	e := tk->cmd(top, s);
	if (e != nil && e[0] == '!')
		sys->fprint(stderr, "tk error %s on '%s'\n", e, s);
	return e;
}

r2s(r: Rect): string
{
	return sys->sprint("%d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
}

kill(pid: int)
{
	if ((fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE)) != nil)
		sys->write(fd, array of byte "kill", 4);
}

readfile(f: string): string
{
	if ((fd := sys->open(f, Sys->OREAD)) == nil)
		return nil;
	a := array[8192] of byte;
	n := sys->read(fd, a, len a);
	if (n <= 0)
		return nil;
	return string a[0:n];
}