implement Clientmod; include "sys.m"; sys: Sys; include "draw.m"; draw: Draw; Point, Rect: import draw; include "tk.m"; tk: Tk; include "wmlib.m"; wmlib: Wmlib; include "../gameclient.m"; 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); }; client(ctxt: ref Draw->Context, argv: list of string, nil: int) { sys = load Sys Sys->PATH; stderr = sys->fildes(2); draw = load Draw Draw->PATH; tk = load Tk Tk->PATH; wmlib = load Wmlib Wmlib->PATH; if (wmlib == nil) { sys->fprint(stderr, "othello: cannot load %s: %r\n", Wmlib->PATH); sys->raise("fail:bad module"); } wmlib->init(); client1(ctxt); } configcmds := array[] of { "canvas .c -height " + string (SQ * N) + " -width " + string (SQ * N) + " -bg green", "label .status -text {No game 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 {send cmd b1up %x %y}", }; Black, White, None: con iota; colours := array[] of {White => "white", Black => "black"}; win: ref Tk->Toplevel; board: array of array of int; notifypid := -1; playerid := -1; playerids := array[2] of int; client1(ctxt: ref Draw->Context) { gamefd := sys->fildes(0); sys->pctl(Sys->NEWPGRP, nil); winctl: chan of string; (win, winctl) = wmlib->titlebar(ctxt.screen, nil, "Othello", Wmlib->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 {* => None}}; cmd(win, "update"); spawn updateproc(gamefd); 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(gamefd, "move " + string x + " " + string y); cmd(win, "update"); } c := <-winctl => if (c == "exit") sys->write(gamefd, array[0] of byte, 0); wmlib->titlectl(win, c); } } 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(gamefd: ref Sys->FD) { buf := array[Sys->ATOMICIO] of byte; while ((n := sys->read(gamefd, buf, len buf)) > 0) { (nil, lines) := sys->tokenize(string buf[0:n], "\n"); sys->print("othello: received %d updates\n", len lines); 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 { "players" => playerids[Black] = int hd tl toks; playerids[White] = int hd tl tl toks; status(string playerids[Black]+ "(Black) vs. " + string playerids[White] + "(White)"); if (playerid == playerids[Black]) cmd(win, ".f.l configure -text Black"); else if (playerid == playerids[White]) cmd(win, ".f.l configure -text White"); "turn" => turn := int val; if (turn != None) { if (playerid == playerids[turn]) cmd(win, ".f.turn configure -text {(Your turn)}"); else if (playerid == playerids[!turn]) cmd(win, ".f.turn configure -text {}"); } "winner" => text := "it was a draw"; winner := int val; if (winner != None) text = colours[int val] + " won."; status("game over. " + text); cmd(win, ".f.l configure -text {watching}"); * => (x, y) := (attr[0] - 'a', attr[1] - 'a'); set(x, y, int val); } "playerid" => # playerid clientid playerid name playerid = int hd tl tl toks; * => 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 == None) cmd(win, ".c delete " + id); else if (board[x][y] != None) 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); }