diff options
Diffstat (limited to 'appl/wm/c4.b')
| -rw-r--r-- | appl/wm/c4.b | 718 |
1 files changed, 718 insertions, 0 deletions
diff --git a/appl/wm/c4.b b/appl/wm/c4.b new file mode 100644 index 00000000..185b807b --- /dev/null +++ b/appl/wm/c4.b @@ -0,0 +1,718 @@ +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", +}; |
