implement TicTacToe; include "sys.m"; sys: Sys; Dir: import sys; Connection : import Sys; include "draw.m"; draw: Draw; Screen, Display, Image, Context, Point, Rect: import draw; include "tk.m"; tk: Tk; Toplevel: import tk; include "tkclient.m"; tkclient: Tkclient; Hide: import tkclient; include "dialog.m"; dialog : Dialog; TicTacToe : module { init: fn(ctxt: ref Draw->Context, argv: list of string); }; PORT : con "2000"; WINBUT : con Hide; BSZ : con 20; BSZI : con BSZ + 2; EMPTY, PLAYER1, PLAYER2 : con iota; # board[frame][button] board := array[BSZI] of { * => array[BSZI] of {* => EMPTY} }; ctxt: ref Draw->Context; mainwin : ref Tk->Toplevel; workerpid : int; cmd : chan of string; gamecmd : chan of string; localcmd : chan of string; remotecmd : chan of string; init(xctxt: ref Draw->Context, nil: list of string) { sys = load Sys Sys->PATH; if (xctxt == nil) { sys->fprint(sys->fildes(2), "Tic-Tac-Toe: no window context\n"); raise "fail:bad context"; } ctxt = xctxt; draw = load Draw Draw->PATH; tk = load Tk Tk->PATH; tkclient = load Tkclient Tkclient->PATH; dialog = load Dialog Dialog->PATH; sys->pctl(Sys->NEWPGRP, nil); tkclient->init(); dialog->init(); wmctl : chan of string; (mainwin, wmctl) = tkclient->toplevel(ctxt, nil, "Tic-Tac-Toe", WINBUT); if(mainwin == nil) { sys->fprint(sys->fildes(2), "Tic_Tac-Toe: creation of toplevel window failed\n"); raise "fail:creation of toplevel window failed"; } gamecmd = chan of string; cmd = chan of string; tk->namechan(mainwin, cmd, "cmd"); localcmd = chan of string; tk->namechan(mainwin, localcmd, "pcmd"); remotecmd = chan of string; display_board(); center(mainwin); tkclient->onscreen(mainwin, "exact"); tkclient->startinput(mainwin, "kbd"::"ptr"::nil); for(;;) alt { s := <- mainwin.ctxt.kbd => tk->keyboard(mainwin, s); s := <- mainwin.ctxt.ptr => tk->pointer(mainwin, *s); s := <- mainwin.ctxt.ctl or s = <- mainwin.wreq or s = <- wmctl => case s { "exit" => fd := sys->open("#p/" + string workerpid +"/ctl", sys->OWRITE); if(fd != nil) sys->fprint(fd, "kill"); tkclient->wmctl(mainwin, "exit"); * => tkclient->wmctl(mainwin, s); } menucmd := <- cmd => case menucmd { "host" => reset_board(); (n, conn) := sys->announce("tcp!*!" + PORT); if (n < 0) { tk->cmd(mainwin,".ft.ls configure -text {Hosting of a game failed}"); tk->cmd(mainwin, "update"); } else { spawn listenthread(conn); tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state disabled"); tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state disabled"); tk->cmd(mainwin,".ft.ls configure -text {Waiting for opponent}"); tk->cmd(mainwin, "update"); } "join" => reset_board(); address := "tcp!" + dialog->getstring(ctxt,mainwin.image, "Enter host's address") + "!" + PORT; (ok, conn) := sys->dial(address, ""); if (ok < 0) { tk->cmd(mainwin,".ft.ls configure -text {Connection failed}"); tk->cmd(mainwin, "update"); } else { tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state disabled"); tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state disabled"); tk->cmd(mainwin,".ft.ls configure -text {Opponent's turn}"); tk->cmd(mainwin, "update"); spawn workerthread(conn); spawn runclient(conn); } } } } runserver(conn : Connection) { absorb(localcmd); localch := localcmd; dummych := chan of string; moves := 0; for(;;) { alt { game := <- gamecmd => case game { "close" => tk->cmd(mainwin,".ft.ls configure -text {Opponent closed the connection}"); tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal"); tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal"); tk->cmd(mainwin, "update"); exit; } local := <-localch => localch = dummych; (n, tokens) := sys->tokenize(local, " "); if(n >= 3) case hd tokens { "b" => row := int hd tl tokens; column := int hd tl tl tokens; if(board[row][column] == EMPTY) { message := local + "\r\n"; wdfd := sys->open(conn.dir + "/data", Sys->OWRITE); sys->write(wdfd, array of byte message, len array of byte message); board[row][column] = PLAYER1; moves ++; tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text {X}", row, column)); if(testrows(row, column, 5, PLAYER1) == 1) { tk->cmd(mainwin,".ft.ls configure -text {You won!}"); tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal"); tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal"); tk->cmd(mainwin, "update"); fd := sys->open("#p/" + string workerpid +"/ctl", sys->OWRITE); if(fd != nil) sys->fprint(fd, "kill"); exit; } tk->cmd(mainwin,".ft.ls configure -text {Opponent's turn}"); tk->cmd(mainwin, "update"); } else localch = localcmd; * => localch = localcmd; } else localch = localcmd; remote := <-remotecmd => (n, tokens) := sys->tokenize(remote, " "); if(n >= 3) case hd tokens { "b" => row := int hd tl tokens; column := int hd tl tl tokens; board[row][column] = PLAYER2; moves ++; tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text {0}", row, column)); if (testrows(row, column, 5, PLAYER2) == 1) { tk->cmd(mainwin,".ft.ls configure -text {Opponent won!}"); tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal"); tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal"); tk->cmd(mainwin, "update"); fd := sys->open("#p/" + string workerpid +"/ctl", sys->OWRITE); if(fd != nil) sys->fprint(fd, "kill"); exit; } tk->cmd(mainwin,".ft.ls configure -text {Your turn}"); tk->cmd(mainwin, "update"); absorb(localcmd); localch = localcmd; } } if (moves >= BSZ * BSZ) { tk->cmd(mainwin,".ft.ls configure -text {Draw!}"); tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal"); tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal"); tk->cmd(mainwin, "update"); fd := sys->open("#p/" + string workerpid +"/ctl", sys->OWRITE); if(fd != nil) sys->fprint(fd, "kill"); exit; } } } runclient(conn : Connection) { dummych := chan of string; localch := dummych; moves := 0; for(;;) { alt { game := <- gamecmd => case game { "close" => tk->cmd(mainwin,".ft.ls configure -text {Opponent closed the connection}"); tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal"); tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal"); tk->cmd(mainwin, "update"); exit; } local := <-localch => localch = dummych; (n, tokens) := sys->tokenize(local, " "); if(n >= 3) case hd tokens { "b" => row := int hd tl tokens; column := int hd tl tl tokens; if(board[row][column] == EMPTY) { message := local + "\r\n"; sys->write(conn.dfd, array of byte message, len array of byte message); board[row][column] = PLAYER2; moves ++; tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text {0}", row, column)); if(testrows(row, column, 5, PLAYER2) == 1) { tk->cmd(mainwin,".ft.ls configure -text {You won!}"); tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal"); tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal"); tk->cmd(mainwin, "update"); fd := sys->open("#p/" + string workerpid +"/ctl", sys->OWRITE); if(fd != nil) sys->fprint(fd, "kill"); exit; } tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text {0}", row, column)); tk->cmd(mainwin,".ft.ls configure -text {Opponent's turn}"); tk->cmd(mainwin, "update"); } else localch = localcmd; * => localch = localcmd; } else localch = localcmd; remote := <-remotecmd => (n, tokens) := sys->tokenize(remote, " "); if(n >= 3) case hd tokens { "b" => row := int hd tl tokens; column := int hd tl tl tokens; board[row][column] = PLAYER1; moves ++; tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text {X}", row, column)); if(testrows(row, column, 5, PLAYER1) == 1) { tk->cmd(mainwin,".ft.ls configure -text {Opponent won!}"); tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal"); tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal"); tk->cmd(mainwin, "update"); fd := sys->open("#p/" + string workerpid +"/ctl", sys->OWRITE); if(fd != nil) sys->fprint(fd, "kill"); exit; } tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text {X}", row, column)); tk->cmd(mainwin,".ft.ls configure -text {Your turn}"); tk->cmd(mainwin, "update"); absorb(localcmd); localch = localcmd; } } if (moves >= BSZ * BSZ) { tk->cmd(mainwin,".ft.ls configure -text {Draw!}"); tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal"); tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal"); tk->cmd(mainwin, "update"); fd := sys->open("#p/" + string workerpid +"/ctl", sys->OWRITE); if(fd != nil) sys->fprint(fd, "kill"); exit; } } } center(t: ref Tk->Toplevel) { org: Point; ir := tk->rect(t, ".", Tk->Border|Tk->Required); org.x = t.screenr.dx() / 2 - ir.dx() / 2; org.y = t.screenr.dy() / 2 - ir.dy() / 2; if (org.y < 0) { org.y = 0; } tk->cmd(t, ". configure -x " + string org.x + " -y " + string org.y); } display_board() { i, j: int; pack: string; tk->cmd(mainwin, "frame .f"); tk->cmd(mainwin, "pack .f -fill x"); tk->cmd(mainwin, "menubutton .f.menu -text Game -menu .f.menu.gm"); tk->cmd(mainwin, "menu .f.menu.gm"); tk->cmd(mainwin, ".f.menu.gm add command -label {host game} -command {send cmd host}"); tk->cmd(mainwin, ".f.menu.gm add command -label {join game} -command {send cmd join}"); tk->cmd(mainwin, "pack .f.menu -side left"); for (i = 1; i <= BSZ; i++) { tk->cmd(mainwin, sys->sprint("frame .f%d", i)); pack = ""; for (j = 1; j <= BSZ; j++) { pack += sys->sprint(" .f%d.b%d", i, j); tk->cmd(mainwin, sys->sprint("button .f%d.b%d -text { } -width 14 -command {send pcmd b %d %d}", i, j, i, j)); } tk->cmd(mainwin, sys->sprint("pack %s -side left", pack)); tk->cmd(mainwin, sys->sprint("pack .f%d -side top -fill x", i)); } tk->cmd(mainwin, "frame .ft"); tk->cmd(mainwin, "label .ft.li -text {Status: }"); tk->cmd(mainwin, "label .ft.ls -text {Not connected}"); tk->cmd(mainwin, "pack .ft.li .ft.ls -side left -fill x"); tk->cmd(mainwin, "pack .ft -side bottom -fill x"); tk->cmd(mainwin, "update"); } listenthread(conn : Connection) { (ok, c) := sys->listen(conn); if (ok < 0) { sys->fprint(sys->fildes(2), "Server: listen failed\n"); raise "fail:listen failed"; } tk->cmd(mainwin,".ft.ls configure -text {Your turn}"); tk->cmd(mainwin, "update"); spawn runserver(c); spawn workerthread(c); } workerthread(conn : Connection) { workerpid = sys->pctl(0, nil); buf := array [1] of byte; rdfd := sys->open(conn.dir + "/data", Sys->OREAD); output := ""; while( (n := sys->read(rdfd, buf, len buf ) ) > 0 ) { output[len output] = int buf[0]; if(len output >= 2) { if(output[len output - 2:] == "\r\n") { remotecmd <- = output[:len output - 2]; output = ""; } } } gamecmd <- = "close"; } absorb(ch : chan of string) { for(;;) { alt { <- ch => ; * => return; } } } length(row, column, drow, dcolumn, item: int): int { l := 0; while(board[row][column] == item) { row += drow; column += dcolumn; l++; } return l; } testrows(row, column, items, item: int) : int { horizontal := (length(row, column, 0, -1, item) + length(row, column, 0, 1, item) - 1); if(horizontal >= items) { mark_board(row, column, 0, -1, item); mark_board(row, column, 0, 1, item); return 1; } vertical := (length(row, column, -1, 0, item) + length(row, column, 1, 0, item) - 1); if(vertical >= items) { mark_board(row, column, -1, 0, item); mark_board(row, column, 1, 0, item); return 1; } dir1 := (length(row, column, -1, -1, item) + length(row, column, 1, 1, item) - 1); if(dir1 >= items) { mark_board(row, column, -1, -1, item); mark_board(row, column, 1, 1, item); return 1; } dir2 := (length(row, column, -1, 1, item) + length(row, column, 1, -1, item) - 1); if(dir2 >= items) { mark_board(row, column, -1, 1, item); mark_board(row, column, 1, -1, item); return 1; } return 0; } mark_board(row, column, drow, dcolumn, item : int) { while(board[row][column] == item) { tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -bg olive -activebackground olive", row, column)); row += drow; column += dcolumn; } } reset_board() { for (i := 0; i < BSZI; i++) for (j := 0; j < BSZI; j++) board[i][j] = EMPTY; for (i = 1; i <= BSZ; i++) for (j = 1; j <= BSZ; j++) tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text { } -bg #dddddd -activebackground #eeeeee", i, j)); tk->cmd(mainwin, "update"); }