shithub: purgatorio

ref: 01338076d1a3610858f41fcbcdc7e46fb1109b77
dir: /appl/wm/c4.b/

View raw version
implement Connect;

#
# Copyright © 2000 Vita Nuova Limited. All rights reserved.
#

include "sys.m";
	sys: Sys;
include "draw.m";
	draw: Draw;
	Point, Rect, Image, Font, Context, Screen, Display: import draw;
include "tk.m";
	tk: Tk;
	Toplevel: import tk;
include "tkclient.m";
	tkclient: Tkclient;
include "daytime.m";
	daytime: Daytime;
include "rand.m";
	rand: Rand;

# adtize and modularize

stderr: ref Sys->FD;

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

nosleep, printout, auto: int;
display: ref Draw->Display;

init(ctxt: ref Draw->Context, argv: list of string)
{
	sys = load Sys Sys->PATH;
	draw = load Draw Draw->PATH;
	tk = load Tk Tk->PATH;
	tkclient = load Tkclient Tkclient->PATH;
	daytime = load Daytime Daytime->PATH;
	rand = load Rand Rand->PATH;

	argv = tl argv;
	while(argv != nil){
		s := hd argv;
		if(s != nil && s[0] == '-'){
			for(i := 1; i < len s; i++){
				case s[i]{
					'a' => auto = 1;
					'p' => printout = 1;
					's' => nosleep = 1;
				}
			}
		}
		argv = tl argv;
	}
	stderr = sys->fildes(2);
	rand->init(daytime->now());
	daytime = nil;

	if(ctxt == nil)
		fatal("wm not running");
	display = ctxt.display;
	tkclient->init();
	(win, wmcmd) := tkclient->toplevel(ctxt, "", "Connect", Tkclient->Resize | Tkclient->Hide);
	mainwin = win;
	sys->pctl(Sys->NEWPGRP, nil);
	cmdch := chan of string;
	tk->namechan(win, cmdch, "cmd");
	for(i := 0; i < len win_config; i++)
		cmd(win, win_config[i]);
	pid := -1;
	sync := chan of int;
	mvch := chan of (int, int);
	initboard();
	setimage();
	spawn game(sync, mvch);
	pid = <- sync;
	tkclient->onscreen(win, nil);
	tkclient->startinput(win, "kbd"::"ptr"::nil);

	for(;;){
		alt{
			s := <-win.ctxt.kbd =>
				tk->keyboard(win, s);
			s := <-win.ctxt.ptr =>
				tk->pointer(win, *s);
			c := <-win.ctxt.ctl or
			c = <-win.wreq or
			c = <-wmcmd =>
				case c{
					"exit" =>
						if(pid != -1)
							kill(pid);
						exit;
					* =>
						e := tkclient->wmctl(win, c);
						if(e == nil && c[0] == '!'){
							setimage();
							drawboard();
						}
					}
			c := <- cmdch =>
				(nil, toks) := sys->tokenize(c, " ");
				case hd toks{
					"b1" or "b2" or "b3" =>
						alt{
							mvch <-= (int hd tl toks, int hd tl tl toks) => ;
							* => ;
						}
					"bh" or "bm" or "wh" or "wm" =>
						colour := BLACK;
						knd := HUMAN;
						if((hd toks)[0] == 'w')
							colour = WHITE;
						if((hd toks)[1] == 'm')
							knd = MACHINE;
						kind[colour] = knd;
					"blev" or "wlev" =>
						colour := BLACK;
						e := "be";
						if((hd toks)[0] == 'w'){
							colour = WHITE;
							e = "we";
						}
						sk := int cmd(win, ".f0." + e + " get");
						if(sk > MAXPLIES)
							sk = MAXPLIES;
						if(sk >= 0)
							skill[colour] = sk;
					* =>
						;
				}
			<- sync =>
				pid = -1;
				# exit;
				spawn game(sync, mvch);
				pid = <- sync;
		}
	}
}

WIDTH: con 400;
HEIGHT: con 400;

SZW: con 7;
SZH: con 6;
SZC: con 4;
SZS: con 1024;
PIECES: con SZW*SZH;

BLACK, WHITE, EMPTY: con iota;
MACHINE, HUMAN: con iota;
SKILLB : con 8;
SKILLW : con 0;
MAXPLIES: con 10;

board: array of array of int;	# for display
brd: array of array of int;		# for calculations
col: array of int;
pieces: array of int;
val: array of int;
kind: array of int;
skill: array of int;
name: array of string;
lines: array of array of int;
line: array of array of list of int;

mainwin: ref Toplevel;
brdimg: ref Image;
brdr: Rect;
brdx, brdy: int;

black, white, bg: ref Image;

movech: chan of (int, int);

setimage()
{
	brdw := int tk->cmd(mainwin, ".p cget -actwidth");
	brdh := int tk->cmd(mainwin, ".p cget -actheight");
	brdr = Rect((0,0), (brdw, brdh));
	brdimg = display.newimage(brdr, display.image.chans, 0, Draw->White);
	if(brdimg == nil)
		fatal("not enough image memory");
	tk->putimage(mainwin, ".p", brdimg, nil);
}

game(sync: chan of int, mvch: chan of (int, int))
{
	sync <-= sys->pctl(0, nil);
	movech = mvch;
	initbrd();
	play();
	sync <-= 0;
}

initboard()
{
	i, j, k: int;

	board = array[SZW] of array of int;
	brd = array[SZW] of array of int;
	line = array[SZW] of array of list of int;
	col = array[SZW] of int;
	for(i = 0; i < SZW; i++){
		board[i] = array[SZH] of int;
		brd[i] = array[SZH] of int;
		line[i] = array[SZH] of list of int;
	}
	pieces = array[2] of int;
	val = array[2] of int;
	kind = array[2] of int;
	kind[BLACK] = MACHINE;
	if(auto)
		kind[WHITE] = MACHINE;
	else
		kind[WHITE] = HUMAN;
	skill = array[2] of int;
	skill[BLACK] = SKILLB;
	skill[WHITE] = SKILLW;
	name = array[2] of string;
	name[BLACK] = "black";
	name[WHITE] = "white";
	black = display.color(Draw->Black);
	white = display.color(Draw->White);
	bg = display.color(Draw->Yellow);
	n := SZW*(SZH-SZC+1)+SZH*(SZW-SZC+1)+2*(SZH-SZC+1)*(SZW-SZC+1);
	lines = array[n] of array of int;
	for(i = 0; i < n; i++)
		lines[i] = array[2] of int;
	m := 0;
	for(i = 0; i < SZW; i++){
		for(j = 0; j <= SZH-SZC; j++){
			for(k = 0; k < SZC; k++){
				line[i][j+k] = m :: line[i][j+k];
			}
			m++;
		}
	}
	for(i = 0; i < SZH; i++){
		for(j = 0; j <= SZW-SZC; j++){
			for(k = 0; k < SZC; k++){
				line[j+k][i] = m :: line[j+k][i];
			}
			m++;
		}
	}
	for(i = 0; i <= SZW-SZC; i++){
		for(j = 0; j <= SZH-SZC; j++){
			for(k = 0; k < SZC; k++){
				line[i+k][j+k] = m :: line[i+k][j+k];
			}
			m++;
		}
	}
	for(i = 0; i <= SZW-SZC; i++){
		for(j = 0; j <= SZH-SZC; j++){
			for(k = 0; k < SZC; k++){
				line[SZW-1-i-k][j+k] = m :: line[SZW-1-i-k][j+k];
			}
			m++;
		}
	}
	if(m != n)
		fatal(sys->sprint("%d != %d\n", m, n));		
}

initbrd()
{
	i, j: int;

	for(i = 0; i < SZW; i++){
		col[i] = 0;
		for(j = 0; j < SZH; j++)
			board[i][j] = brd[i][j] = EMPTY;
	}
	pieces[BLACK] = pieces[WHITE] = 0;
	val[BLACK] = val[WHITE] = 0;
	drawboard();
	n := len lines;
	for(i = 0; i < n; i++)
		lines[i][0] = lines[i][1] = 0;
}

plays := 0;
bwins := 0;
wwins := 0;

play()
{
	if(plays&1)
		(first, second) := (WHITE, BLACK);
	else
		(first, second) = (BLACK, WHITE);
	for(;;){
		if(pieces[BLACK]+pieces[WHITE] == PIECES)
			break;
		m1 := move(first, second);
		if(printout)
			sys->print("%s: %d %d %d\n", name[first], m1, val[BLACK], val[WHITE]);
		if(win(first))
			break;
		if(pieces[BLACK]+pieces[WHITE] == PIECES)
			break;
		m2 := move(second, first);
		if(printout)
			sys->print("%s: %d %d %d\n", name[second], m2, val[BLACK], val[WHITE]);
		if(win(second))
			break;
	}
	if(win(BLACK)){
		bwins++;
		puts("black wins");
		highlight(BLACK);
	}
	else if(win(WHITE)){
		wwins++;
		puts("white wins");
		highlight(WHITE);
	}
	else
		puts("draw");
	sleep(2500);
	plays++;
	puts(sys->sprint("black %d:%d white", bwins, wwins));
	sleep(2500);
	if(printout)
		sys->print("\n");
}

move(me: int, you: int): int
{
	if(kind[me] == MACHINE){
		puts("machine " + name[me] + " move");
		return genmove(me, you);
	}
	else{
		m, n: int;

		# mvs := findmoves();
		for(;;){
			puts("human " + name[me] + " move");
			m = getmove();
			if(m < 0 || m >= SZW)
				continue;
			n = col[m];
			valid := n >= 0 && n < SZH;
			if(valid && brd[m][n] != EMPTY)
				fatal("! EMPTY");
			if(valid)
				break;
			puts("illegal move");
			sleep(2500);
		}
		makemove(m, n, me, you, 0);
		return m*SZS+n;
	}
}

genmove(me: int, you: int): int
{
	m, n, v: int;

	mvs := findmoves();
	if(skill[me] == 0){
		l := len mvs;
		r := rand->rand(l);
		# r = 0;
		while(--r >= 0)
			mvs = tl mvs;
		(m, n) = hd mvs;
	}
	else{
		plies := skill[me];
		left := PIECES-(pieces[BLACK]+pieces[WHITE]);
		if(left < plies)		# limit search
			plies = left;
		else if(left < 2*plies)	# expand search to end
			plies = left;
		else{				# expand search nearer end of game
			k := left/plies;
			if(k < 3)
				plies = ((k+2)*plies)/(k+1);
		}
		visits = leaves = 0;
		(v, (m, n)) = minimax(me, you, plies, ∞);
		if(0){
			while(mvs != nil){
				v0: int;
				(a, b) := hd mvs;
				makemove(a, b, me, you, 1);
				(v0, (m, n)) = minimax(you, me, plies-1, ∞);
				sys->print("	(%d, %d): %d\n", a, b, -v0);
				undomove(a, b, me, you);
				mvs = tl mvs;
			}
			sys->print("best move is %d, %d\n", m, n);
			kind[WHITE] = HUMAN;
		}
		if(auto)		
			sys->print("eval = %d plies=%d goes=%d visits=%d\n", v, plies, len mvs, leaves);
	}
	makemove(m, n, me, you, 0);
	return m*SZS+n;
}

findmoves(): list of (int, int)
{
	mvs: list of (int, int);

	for(i := 0; i < SZW; i++){
		if((j := col[i]) < SZH)
			mvs = (i, j) :: mvs;
	}
	return mvs;
}

makemove(m: int, n: int, me: int, you: int, gen: int)
{
	pieces[me]++;
	brd[m][n] = me;
	col[m]++;
	for(l := line[m][n]; l != nil; l = tl l){
		i := hd l;
		a := lines[i][me];
		b := lines[i][you];
		lines[i][me]++;
		if(a+b >= SZC)
			fatal("makemove a+b");
		if(b == 0){
			val[me] += 2*a+1;
			if(a == SZC-1)
				val[me] += WIN;
		}
		else if(a == 0)
			val[you] -= b*b;
	}
	if(!gen){
		board[m][n] = me;
		drawpiece(m, n, me);
		panelupdate();
		# sleep(1000);
	}
}

undomove(m: int, n: int, me: int, you: int)
{
	brd[m][n] = EMPTY;
	pieces[me]--;
	col[m]--;
	for(l := line[m][n]; l != nil; l = tl l){
		i := hd l;
		a := lines[i][me];
		b := lines[i][you];
		lines[i][me]--;
		if(a == 0 || a+b > SZC)
			fatal("undomove a+b");
		if(b == 0){
			val[me] -= 2*a-1;
			if(a == SZC)
				val[me] -= WIN;
		}
		else if(a == 1)
			val[you] += b*b;
	}
}

win(me: int): int
{
	return val[me] > WIN/2;
}

highlight(me: int)
{
	n := len lines;
	for(i := 0; i < n; i++){
		if(lines[i][me] == SZC){
			for(j := 0; j < SZW; j++){
				for(k := 0; k < SZH; k++){
					for(l := line[j][k]; l != nil; l = tl l){
						if(i == hd l)
							highpiece(j, k, board[j][k]);
					}
				}
			}
		}
	}
}

getmove(): int
{
	(x, nil) := <- movech;
	return x/brdx;
}

drawboard()
{
	brdx = brdr.dx()/SZW;
	brdy = brdr.dy()/SZH;
	brdimg.draw(brdr, bg, nil, (0, 0));
	for(i := 1; i < SZW; i++)
		drawline(lmap(i, 0), lmap(i, SZH), nil);
	for(j := 1; j < SZH; j++)
		drawline(lmap(0, j), lmap(SZW, j), nil);
	for(i = 0; i < SZW; i++){
		for(j = 0; j < SZH; j++){
			if (board[i][j] == BLACK || board[i][j] == WHITE)
				drawpiece(i, j, board[i][j]);
		}
	}
	panelupdate();
}

drawpiece(m, n, p: int)
{
	if(p == BLACK)
		src := black;
	else if(p == WHITE)
		src = white;
	else
		src = bg;
	brdimg.fillellipse(cmap(m, n), 3*brdx/8, 3*brdy/8, src, (0, 0));
}

highpiece(m, n, p: int)
{
	if(p == BLACK)
		src := white;
	else if(p == WHITE)
		src = black;
	else
		src = bg;
	pt := cmap(m, n);
	rx := (3*brdx/8, 0);
	ry := (0, 3*brdy/8);
	drawline(pt.add(rx), pt.sub(rx), src);
	drawline(pt.add(ry), pt.sub(ry), src);
}

panelupdate()
{
	tk->cmd(mainwin, sys->sprint(".p dirty %d %d %d %d", brdr.min.x, brdr.min.y, brdr.max.x, brdr.max.y));
	tk->cmd(mainwin, "update");
}

drawline(p0, p1: Point, c: ref Image)
{
	if(c == nil)
		c = black;
	brdimg.line(p0, p1, Draw->Endsquare, Draw->Endsquare, 0, c, (0, 0));
}

cmap(m, n: int): Point
{
	return brdr.min.add((m*brdx+brdx/2, (SZH-1-n)*brdy+brdy/2));
}

lmap(m, n: int): Point
{
	return brdr.min.add((m*brdx, n*brdy));
}

∞: con (1<<30);
WIN: con (1<<20);
MAXVISITS: con 1024;

visits, leaves : int;

minimax(me: int, you: int, plies: int, αβ: int): (int, (int, int))
{
	v: int;

	if(plies == 0){
		visits++;
		leaves++;
		if(visits == MAXVISITS){
			visits = 0;
			sys->sleep(0);
		}
		return (eval(me, you), (0, 0));
	}
	mvs := findmoves();
	if(mvs == nil){
		fatal("mvs==nil");
		# if(mv)
		# 	(v, nil) := minimax(you, me, plies, ∞);
		# else
		#	(v, nil) = minimax(you, me, plies-1, ∞);
		# return (-v, (0, 0));
	}
	bestv := -∞;
	bestm := (0, 0);
	e := 0;
	for(; mvs != nil; mvs = tl mvs){
		(m, n) := hd mvs;
		makemove(m, n, me, you, 1);
		if(win(me))
			v = eval(me, you);
		else{
			(v, nil) = minimax(you, me, plies-1, -bestv);
			v = -v;
		}
		undomove(m, n, me, you);
		if(v > bestv || (v == bestv && rand->rand(++e) == 0)){
			if(v > bestv)
				e = 1;
			bestv = v;
			bestm = (m, n);
			if(bestv >= αβ)
				return (∞, (0, 0));
		}
	}
	return (bestv, bestm);
}
	
eval(me: int, you: int): int
{
	return val[me]-val[you];
}

fatal(s: string)
{
	sys->fprint(stderr, "%s\n", s);
	exit;
}

sleep(t: int)
{
	if(nosleep)
		sys->sleep(0);
	else
		sys->sleep(t);
}

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

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

# swidth: int;
# sfont: ref Font;

# gettxtattrs()
# {
#	swidth = int cmd(mainwin, ".f1.txt cget -width");	# always initial value ?
#	f := cmd(mainwin, ".f1.txt cget -font");
#	sfont = Font.open(brdimg.display, f);
# }
	
puts(s: string)
{
	# while(sfont.width(s) > swidth)
	#	s = s[0: len s -1];
	cmd(mainwin, ".f1.txt configure -text {" + s + "}");
	cmd(mainwin, "update");
}
					
win_config := array[] of {
	"frame .f",
	"menubutton .f.bk -text Black -menu .f.bk.bm",
	"menubutton .f.wk -text White -menu .f.wk.wm",
	"menu .f.bk.bm",
	".f.bk.bm add command -label Human -command { send cmd bh }",
	".f.bk.bm add command -label Machine -command { send cmd bm }",
	"menu .f.wk.wm",
	".f.wk.wm add command -label Human -command { send cmd wh }",
	".f.wk.wm add command -label Machine -command { send cmd wm }",
	"pack .f.bk -side left",
	"pack .f.wk -side right",

	"frame .f0",
	"label .f0.bl -text {Black level}",
	"label .f0.wl -text {White level}",
	"entry .f0.be -width 32",
	"entry .f0.we -width 32",
	".f0.be insert 0 {" + string SKILLB+"}",
	".f0.we insert 0 {" + string SKILLW+"}",
	"pack .f0.bl -side left",
	"pack .f0.be -side left",
	"pack .f0.wl -side right",
	"pack .f0.we -side right",

	"frame .f1",
	"label .f1.txt -text { } -width " + string WIDTH,
	"pack .f1.txt -side top -fill x",

	"panel .p -width " + string WIDTH + " -height " + string HEIGHT,

	"pack .f -side top -fill x",
	"pack .f0 -side top -fill x",
	"pack .f1 -side top -fill x",
	"pack .p -side bottom -fill both -expand 1",
	"pack propagate . 0",

	"bind .p <Button-1> {send cmd b1 %x %y}",
	"bind .p <Button-2> {send cmd b2 %x %y}",
	"bind .p <Button-3> {send cmd b3 %x %y}",
	# "bind .c <ButtonRelease-1> {send cmd b1r %x %y}",
	# "bind .c <ButtonRelease-2> {send cmd b2r %x %y}",
	# "bind .c <ButtonRelease-3> {send cmd b3r %x %y}",
	"bind .f0.be <Key-\n> {send cmd blev}",
	"bind .f0.we <Key-\n> {send cmd wlev}",
	"update",
};