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 {send cmd b1 %x %y}", "bind .p {send cmd b2 %x %y}", "bind .p {send cmd b3 %x %y}", # "bind .c {send cmd b1r %x %y}", # "bind .c {send cmd b2r %x %y}", # "bind .c {send cmd b3r %x %y}", "bind .f0.be {send cmd blev}", "bind .f0.we {send cmd wlev}", "update", };