ref: 866d74c0c4bb50e85e9e8bb95140c10d409e53be
dir: /appl/wm/reversi.b/
implement Reversi;
#
# 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;
Reversi: 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;
tkclient->init();
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)
ctxt = tkclient->makedrawcontext();
display = ctxt.display;
(win, wmctl) := tkclient->toplevel(ctxt, "", "Reversi", 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]);
fittoscreen(win);
pid := -1;
sync := chan of int;
mvch := chan of (int, int, int);
initboard();
setimage();
spawn game(sync, mvch, 0);
pid = <- sync;
tkclient->onscreen(win, nil);
tkclient->startinput(win, "kbd"::"ptr"::nil);
lasts := 1;
for(;;){
alt{
s := <-win.ctxt.kbd =>
tk->keyboard(win, s);
s := <-win.ctxt.ptr =>
tk->pointer(win, *s);
s := <-win.ctxt.ctl or
s = <-win.wreq =>
tkclient->wmctl(win, s);
c := <- wmctl =>
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 <-= (SQUARE, int hd tl toks, int hd tl tl toks) => lasts = 1;
* => ;
}
"bh" or "bm" or "wh" or "wm" =>
col := BLACK;
knd := HUMAN;
if((hd toks)[0] == 'w')
col = WHITE;
if((hd toks)[1] == 'm')
knd = MACHINE;
kind[col] = knd;
"blev" or "wlev" =>
col := BLACK;
e := "be";
if((hd toks)[0] == 'w'){
col = WHITE;
e = "we";
}
sk := int cmd(win, ".f0." + e + " get");
if(sk > MAXPLIES)
sk = MAXPLIES;
if(sk >= 0)
skill[col] = sk;
"last" =>
alt{
mvch <-= (REPLAY, lasts, 0) => lasts++;
* => ;
}
* =>
;
}
<- sync =>
pid = -1;
# exit;
spawn game(sync, mvch, 0);
pid = <- sync;
}
}
}
SQUARE, REPLAY: con iota;
WIDTH: con 400;
HEIGHT: con 400;
SZB: con 8; # must be even
SZF: con SZB+2;
MC1: con SZB/2;
MC2: con MC1+1;
PIECES: con SZB*SZB;
SQUARES: con PIECES-4;
MAXMOVES: con 3*PIECES/2;
NOMOVE: con SZF*SZF - 1;
BLACK, WHITE, EMPTY, BORDER: con iota;
MACHINE, HUMAN: con iota;
SKILLB : con 6;
SKILLW : con 0;
MAXPLIES: con 6;
moves: array of int;
board: array of array of int; # for display
brd: array of array of int; # for calculations
val: array of array of int;
order: array of (int, int);
pieces: array of int;
value: array of int;
kind: array of int;
skill: array of int;
name: array of string;
mainwin: ref Toplevel;
brdimg: ref Image;
brdr: Rect;
brdx, brdy: int;
black, white, green: ref Image;
movech: chan of (int, int, int);
setimage()
{
brdw := int tk->cmd(mainwin, ".p cget -actwidth");
brdh := int tk->cmd(mainwin, ".p cget -actheight");
# if (brdw > display.image.r.dx())
# brdw = display.image.r.dx() - 4;
# if (brdh > display.image.r.dy())
# brdh = display.image.r.dy() - 40;
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, int), again: int)
{
sync <-= sys->pctl(0, nil);
movech = mvch;
initbrd();
drawboard();
if(again)
replay(moves);
else
play();
sync <-= 0;
}
ordrect()
{
i, j : int;
n := 0;
for(i = 1; i <= SZB; i++){
for(j = 1; j <= SZB; j++){
if(i < SZB/2 || j < SZB/2 || i > SZB/2+1 || j > SZB/2+1)
order[n++] = (i, j);
}
}
for(k := 0; k < SQUARES-1; k++){
for(l := k+1; l < SQUARES; l++){
(i, j) = order[k];
(a, b) := order[l];
if(val[i][j] > val[a][b])
(order[k], order[l]) = (order[l], order[k]);
}
}
}
initboard()
{
i, j, k: int;
moves = array[MAXMOVES+1] of int;
board = array[SZF] of array of int;
brd = array[SZF] of array of int;
for(i = 0; i < SZF; i++){
board[i] = array[SZF] of int;
brd[i] = array[SZF] of int;
}
val = array[SZF] of array of int;
s := -pow(-1, SZB/2);
for(i = 0; i < SZF; i++){
val[i] = array[SZF] of int;
val[i][0] = val[i][SZF-1] = 0;
for(j = 1; j <= SZB; j++){
for(k = SZB/2; k > 0; k--){
if(i == k || i == SZB+1-k || j == k || j == SZB+1-k){
val[i][j] = s*pow(-7, SZB/2-k);
break;
}
}
}
}
order = array[SQUARES] of (int, int);
ordrect();
pieces = array[2] of int;
value = 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);
green = display.color(Draw->Green);
}
initbrd()
{
i, j: int;
for(i = 0; i < SZF; i++)
for(j = 0; j < SZF; j++)
brd[i][j] = EMPTY;
for(i = 0; i < SZF; i++)
brd[i][0] = brd[i][SZF-1] = BORDER;
for(j = 0; j< SZF; j++)
brd[0][j] = brd[SZF-1][j] = BORDER;
brd[MC1][MC1] = brd[MC2][MC2] = BLACK;
brd[MC1][MC2] = brd[MC2][MC1] = WHITE;
for(i = 0; i < SZF; i++)
for(j = 0; j < SZF; j++)
board[i][j] = brd[i][j];
pieces[BLACK] = pieces[WHITE] = 2;
value[BLACK] = value[WHITE] = -2;
}
plays := 0;
bscore := 0;
wscore := 0;
bwins := 0;
wwins := 0;
play()
{
n := 0;
for(i := 0; i <= MAXMOVES; i++)
moves[i] = NOMOVE;
if(plays&1)
(first, second) := (WHITE, BLACK);
else
(first, second) = (BLACK, WHITE);
if(printout)
sys->print("%d\n", first);
moves[n++] = first;
m1 := m2 := 1;
for(;;){
if(pieces[BLACK]+pieces[WHITE] == PIECES)
break;
m2 = m1;
m1 = move(first, second);
if(printout)
sys->print("%d\n", m1);
moves[n++] = m1;
if(!m1 && !m2)
break;
(first, second) = (second, first);
}
if(auto)
sys->print("score: %d-%d\n", pieces[BLACK], pieces[WHITE]);
bscore += pieces[BLACK];
wscore += pieces[WHITE];
if(pieces[BLACK] > pieces[WHITE])
bwins++;
else if(pieces[BLACK] < pieces[WHITE])
wwins++;
plays++;
if(auto)
sys->print(" black: %d white: %d draw: %d total: (%d-%d)\n", bwins, wwins, plays-bwins-wwins, bscore, wscore);
puts(sys->sprint("black %d:%d white", pieces[BLACK], pieces[WHITE]));
sleep(2000);
puts(sys->sprint("black %d:%d white", bwins, wwins));
sleep(2000);
}
replay(moves: array of int)
{
n := 0;
first := moves[n++];
second := BLACK+WHITE-first;
m1 := m2 := 1;
while (pieces[BLACK]+pieces[WHITE] < PIECES){
m2 = m1;
m1 = moves[n++];
if(m1 == NOMOVE)
break;
if(m1 != 0)
makemove(m1/SZF, m1%SZF, first, second, 1, 0);
if(!m1 && !m2)
break;
(first, second) = (second, first);
}
# sys->print("score: %d-%d\n", pieces[BLACK], pieces[WHITE]);
}
lastmoves(p: int, moves: array of int)
{
initbrd();
k := MAXMOVES+1;
for(i := 0; i <= MAXMOVES; i++){
if(moves[i] == NOMOVE){
k = i;
break;
}
}
if(k-p < 1)
p = k-1;
for(i = k-p; i < k; i++)
if(moves[i] == 0)
p++;
if(k-p < 1)
p = k-1;
n := 0;
me := moves[n++];
you := BLACK+WHITE-me;
while(n < k-p){
m := moves[n++];
if(m != 0)
makemove(m/SZF, m%SZF, me, you, 1, 1);
(me, you) = (you, me);
}
for(i = 0; i < SZF; i++)
for(j := 0; j < SZF; j++)
board[i][j] = brd[i][j];
drawboard();
sleep(1000);
while(n < k){
m := moves[n++];
if(m != 0)
makemove(m/SZF, m%SZF, me, you, 1, 0);
if(n < k)
sleep(500);
(me, you) = (you, me);
}
}
move(me: int, you: int): int
{
if(kind[me] == MACHINE){
puts("machine " + name[me] + " move");
m := genmove(me, you);
if(!m){
puts("machine " + name[me] + " cannot go");
sleep(2000);
}
return m;
}
else{
m, n: int;
mvs := findmoves(me, you);
if(mvs == nil){
puts("human " + name[me] + " cannot go");
sleep(2000);
return 0;
}
for(;;){
puts("human " + name[me] + " move");
(m, n) = getmove();
if(m < 1 || n < 1 || m > SZB || n > SZB)
continue;
if(brd[m][n] == EMPTY)
(valid, nil) := makemove(m, n, me, you, 0, 0);
else
valid = 0;
if(valid)
break;
puts("illegal move");
sleep(2000);
}
makemove(m, n, me, you, 1, 0);
return m*SZF+n;
}
}
fullsrch: int;
genmove(me: int, you: int): int
{
m, n, v: int;
mvs := findmoves(me, you);
if(mvs == nil)
return 0;
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);
}
fullsrch = plies == left;
visits = leaves = 0;
(v, (m, n)) = minimax(me, you, plies, ∞, 1);
if(0){
# if((m==2&&n==2&&brd[1][1]!=BLACK) ||
# (m==2&&n==7&&brd[1][8]!=BLACK) ||
# (m==7&&n==2&&brd[8][1]!=BLACK) ||
# (m==7&&n==7&&brd[8][8]!=BLACK)){
while(mvs != nil){
(a, b) := hd mvs;
(nil, sqs) := makemove(a, b, me, you, 1, 1);
(v0, nil) := minimax(you, me, plies-1, ∞, 1);
sys->print(" (%d, %d): %d\n", a, b, v0);
undomove(a, b, me, you, sqs);
mvs = tl mvs;
}
if(!fullsrch){
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, 1, 0);
return m*SZF+n;
}
findmoves(me: int, you: int): list of (int, int)
{
mvs: list of (int, int);
for(k := 0; k < SQUARES; k++){
(i, j) := order[k];
if(brd[i][j] == EMPTY){
(valid, nil) := makemove(i, j, me, you, 0, 0);
if(valid)
mvs = (i, j) :: mvs;
}
}
return mvs;
}
makemove(m: int, n: int, me: int, you: int, move: int, gen: int): (int, list of (int, int))
{
sqs: list of (int, int);
if(move){
pieces[me]++;
value[me] += val[m][n];
brd[m][n] = me;
if(!gen){
board[m][n] = me;
drawpiece(m, n, me, 1);
panelupdate();
sleep(1000);
}
}
valid := 0;
for(i := -1; i < 2; i++){
for(j := -1; j < 2; j++){
if(i != 0 || j != 0){
v: int;
(v, sqs) = dirmove(m, n, i, j, me, you, move, gen, sqs);
valid |= v;
if (valid && !move)
return (1, sqs);
}
}
}
if(!valid && move)
fatal(sys->sprint("bad makemove call (%d, %d)", m, n));
return (valid, sqs);
}
dirmove(m: int, n: int, dx: int, dy: int, me: int, you: int, move: int, gen: int, sqs: list of (int, int)): (int, list of (int, int))
{
p := 0;
m += dx;
n += dy;
while(brd[m][n] == you){
m += dx;
n += dy;
p++;
}
if(p > 0 && brd[m][n] == me){
if(move){
pieces[me] += p;
pieces[you] -= p;
m -= p*dx;
n -= p*dy;
while(--p >= 0){
brd[m][n] = me;
value[me] += val[m][n];
value[you] -= val[m][n];
if(gen)
sqs = (m, n) :: sqs;
else{
board[m][n] = me;
drawpiece(m, n, me, 0);
# sleep(500);
panelupdate();
}
m += dx;
n += dy;
}
}
return (1, sqs);
}
return (0, sqs);
}
undomove(m: int, n: int, me: int, you: int, sqs: list of (int, int))
{
brd[m][n] = EMPTY;
pieces[me]--;
value[me] -= val[m][n];
for(; sqs != nil; sqs = tl sqs){
(x, y) := hd sqs;
brd[x][y] = you;
pieces[me]--;
pieces[you]++;
value[me] -= val[x][y];
value[you] += val[x][y];
}
}
getmove(): (int, int)
{
k, x, y: int;
(k, x, y) = <- movech;
if(k == REPLAY){
lastmoves(x, moves);
return getmove();
}
return (x/brdx+1, y/brdy+1);
}
drawboard()
{
brdx = brdr.dx()/SZB;
brdy = brdr.dy()/SZB;
brdimg.draw(brdr, green, nil, (0, 0));
for(i := 1; i < SZB; i++)
drawline(lmap(i, 0), lmap(i, SZB));
for(j := 1; j < SZB; j++)
drawline(lmap(0, j), lmap(SZB, j));
for(i = 1; i <= SZB; i++){
for(j = 1; j <= SZB; j++){
if (board[i][j] == BLACK || board[i][j] == WHITE)
drawpiece(i, j, board[i][j], 0);
}
}
panelupdate();
}
drawpiece(m, n, p, flash: int)
{
if(p == BLACK)
src := black;
else
src = white;
if(0 && flash && kind[p] == MACHINE){
for(i := 0; i < 4; i++){
brdimg.fillellipse(cmap(m, n), 3*brdx/8, 3*brdy/8, src, (0, 0));
panelupdate();
sys->sleep(250);
brdimg.fillellipse(cmap(m, n), 3*brdx/8, 3*brdy/8, green, (0, 0));
panelupdate();
sys->sleep(250);
}
}
brdimg.fillellipse(cmap(m, n), 3*brdx/8, 3*brdy/8, src, (0, 0));
}
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)
{
brdimg.line(p0, p1, Draw->Endsquare, Draw->Endsquare, 0, brdimg.display.black, (0, 0));
}
cmap(m, n: int): Point
{
return brdr.min.add((m*brdx-brdx/2, n*brdy-brdy/2));
}
lmap(m, n: int): Point
{
return brdr.min.add((m*brdx, n*brdy));
}
∞: con (1<<30);
MAXVISITS: con 1024;
visits, leaves : int;
minimax(me: int, you: int, plies: int, αβ: int, mv: int): (int, (int, int))
{
if(plies == 0){
visits++;
leaves++;
if(visits == MAXVISITS){
visits = 0;
sys->sleep(0);
}
return (eval(me, you), (0, 0));
}
mvs := findmoves(me, you);
if(mvs == nil){
if(mv)
(v, nil) := minimax(you, me, plies, ∞, 0);
else
(v, nil) = minimax(you, me, plies-1, ∞, 0);
return (-v, (0, 0));
}
bestv := -∞;
bestm := (0, 0);
e := 0;
for(; mvs != nil; mvs = tl mvs){
(m, n) := hd mvs;
(nil, sqs) := makemove(m, n, me, you, 1, 1);
(v, nil) := minimax(you, me, plies-1, -bestv, 1);
v = -v;
undomove(m, n, me, you, sqs);
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
{
d := pieces[me]-pieces[you];
if(fullsrch)
return d;
n := pieces[me]+pieces[you];
v := 0;
for(i := 1; i <= SZB; i += SZB-1)
for(j := 1; j <= SZB; j += SZB-1)
v += line(i, j, me, you);
return (PIECES-n)*(value[me]-value[you]+v) + n*d;
}
line(m: int, n: int, me: int, you: int): int
{
if(brd[m][n] == EMPTY)
return 0;
dx := dy := -1;
if(m == 1)
dx = 1;
if(n == 1)
dy = 1;
return line0(m, n, 0, dy, me, you) +
line0(m, n, dx, 0, me, you) +
line0(m, n, dx, dy, me, you);
}
line0(m: int, n: int, dx: int, dy: int, me: int, you: int): int
{
v := 0;
p := brd[m][n];
i := val[1][1];
while(brd[m][n] == p){
v += i;
m += dx;
n += dy;
}
if(p == you)
return -v;
if(p == me)
return v;
return v;
}
pow(n: int, m: int): int
{
p := 1;
while(--m >= 0)
p *= n;
return p;
}
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, "reversi: 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");
}
fittoscreen(win: ref Tk->Toplevel)
{
Point: import draw;
if (display.image == nil)
return;
r := display.image.r;
scrsize := Point(r.dx(), r.dy());
bd := int cmd(win, ". cget -bd");
winsize := Point(int cmd(win, ". cget -actwidth") + bd * 2, int cmd(win, ". cget -actheight") + bd * 2);
if (winsize.x > scrsize.x)
cmd(win, ". configure -width " + string (scrsize.x - bd * 2));
if (winsize.y > scrsize.y)
cmd(win, ". configure -height " + string (scrsize.y - bd * 2));
actr: Rect;
actr.min = Point(int cmd(win, ". cget -actx"), int cmd(win, ". cget -acty"));
actr.max = actr.min.add((int cmd(win, ". cget -actwidth") + bd*2,
int cmd(win, ". cget -actheight") + bd*2));
(dx, dy) := (actr.dx(), actr.dy());
if (actr.max.x > r.max.x)
(actr.min.x, actr.max.x) = (r.max.x - dx, r.max.x);
if (actr.max.y > r.max.y)
(actr.min.y, actr.max.y) = (r.max.y - dy, r.max.y);
if (actr.min.x < r.min.x)
(actr.min.x, actr.max.x) = (r.min.x, r.min.x + dx);
if (actr.min.y < r.min.y)
(actr.min.y, actr.max.y) = (r.min.y, r.min.y + dy);
cmd(win, ". configure -x " + string actr.min.x + " -y " + string actr.min.y);
cmd(win, "update");
}
win_config := array[] of {
"frame .f",
"button .f.last -text {last move} -command {send cmd last}",
"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",
"pack .f.last -side top",
"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 .f0.be <Key-\n> {send cmd blev}",
"bind .f0.we <Key-\n> {send cmd wlev}",
"update",
};