ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/wm/sweeper.b/
implement Sweeper;
#
# michael@vitanuova.com
#
# Copyright © 2000 Vita Nuova Limited. All rights reserved.
# Copyright © 2001 Vita Nuova Holdings 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;
stderr: ref Sys->FD;
Sweeper: module
{
init: fn(ctxt: ref Draw->Context, argv: list of string);
};
mainwin: ref Toplevel;
score: int;
mines: int;
WIDTH: con 220;
HEIGHT: con 220;
EASY: con 20;
SZB: con 10;
SZI: con SZB+2; # internal board is 2 larger than visible board
Cell: adt {
mine, state: int;
};
board: array of array of Cell;
UNSELECTED, SELECTED, MARKED: con (1<<iota);
init(ctxt: ref Draw->Context, nil: 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;
stderr = sys->fildes(2);
rand->init(daytime->now());
daytime = nil;
tkclient->init();
if(ctxt == nil)
ctxt = tkclient->makedrawcontext();
(win, wmcmd) := tkclient->toplevel(ctxt, "", "Mine Sweeper", Tkclient->Hide);
mainwin = win;
sys->pctl(Sys->NEWPGRP, nil);
cmdch := chan of string;
tk->namechan(win, cmdch, "cmd");
display_board();
pid := -1;
finished := 0;
init_board();
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 => # wm commands
case c {
"exit" =>
if(pid != -1)
kill(pid);
exit;
* =>
tkclient->wmctl(win, c);
}
c := <- cmdch => # tk commands
(nil, toks) := sys->tokenize(c, " ");
case hd toks {
"b" =>
x := int hd tl toks;
y := int hd tl tl toks;
i := board_check(x, y);
case i {
-1 =>
display_mines();
display_lost();
finished = 1;
0 to 8 =>
if (finished)
break;
score++;
board[x][y].state = SELECTED;
display_square(x, y, sys->sprint("%d", i), "olive");
if (i == 0) { # check all adjacent zeros
display_zeros(x, y);
}
display_score();
if (score+mines == SZB*SZB) {
display_mines();
display_win();
finished = 1;
}
* =>
;
}
cmd(mainwin, "update");
"b3" =>
x := int hd tl toks;
y := int hd tl tl toks;
mark_square(x, y);
cmd(mainwin, "update");
"restart" =>
init_board();
display_score();
reset_display();
finished = 0;
* =>
sys->fprint(stderr, "%s\n", c);
}
}
}
}
display_board() {
i, j: int;
pack: string;
for(i = 0; i < len win_config; i++)
cmd(mainwin, win_config[i]);
for (i = 1; i <= SZB; i++) {
cmd(mainwin, sys->sprint("frame .f%d", i));
pack = "";
for (j = 1; j <= SZB; j++) {
pack += sys->sprint(" .f%d.b%dx%d", i, i, j);
cmd(mainwin, sys->sprint("button .f%d.b%dx%d -text { } -width 14 -command {send cmd b %d %d}", i, i, j, i, j));
cmd(mainwin, sys->sprint("bind .f%d.b%dx%d <ButtonRelease-3> {send cmd b3 %d %d}", i, i, j, i, j));
}
cmd(mainwin, sys->sprint("pack %s -side left", pack));
cmd(mainwin, sys->sprint("pack .f%d -side top -fill x", i));
}
for (i = 0; i < len win_config2; i++)
cmd (mainwin, win_config2[i]);
}
reset_display()
{
for (i := 1; i <= SZB; i++) {
for (j := 1; j <= SZB; j++) {
s := sys->sprint(".f%d.b%dx%d configure -text { } -bg #dddddd -activebackground #eeeeee", i, i, j);
cmd(mainwin, s);
}
}
cmd(mainwin, "update");
}
init_board()
{
i, j: int;
score = 0;
mines = 0;
board = array[SZI] of array of Cell;
for (i = 0; i < SZI; i++)
board[i] = array[SZI] of Cell;
# initialize board
for (i = 0; i < SZI; i++)
for (j =0; j < SZI; j++) {
board[i][j].mine = 0;
board[i][j].state = UNSELECTED;
}
# place mines
for (i = 0; i < EASY; i++) {
j = rand->rand(SZB*SZB);
if (board[(j/SZB)+1][(j%SZB)+1].mine == 0) { # rand could yield same result twice
board[(j/SZB)+1][(j%SZB)+1].mine = 1;
mines++;
}
}
cmd(mainwin, "update");
}
display_score()
{
cmd(mainwin, ".f.l configure -text {Score: "+ sys->sprint("%d", score)+ "}");
}
display_win()
{
cmd(mainwin, ".f.l configure -text {You have Won}");
}
display_lost()
{
cmd(mainwin, ".f.l configure -text {You have Lost}");
}
display_mines()
{
for (i := 1; i <= SZB; i++)
for (j := 1; j <= SZB; j++)
if (board[i][j].mine == 1)
display_square(i, j, "M", "red");
}
display_square(i, j: int, v: string, c: string) {
cmd(mainwin, sys->sprint(".f%d.b%dx%d configure -text {%s} -bg %s -activebackground %s", i, i, j, v, c, c));
cmd(mainwin, "update");
}
mark_square(i, j: int) {
case board[i][j].state {
UNSELECTED =>
board[i][j].state = MARKED;
display_square(i, j, "?", "orange");
MARKED =>
board[i][j].state = UNSELECTED;
display_square(i, j, " ", "#dddddd");
}
}
board_check(i, j: int) : int
{
if (board[i][j].mine == 1)
return -1;
if (board[i][j].state&(SELECTED|MARKED))
return -2;
c := 0;
for (x := i-1; x <= i+1; x++)
for (y := j-1; y <= j+1; y++)
if (board[x][y].mine == 1)
c++;
return c;
}
display_zeros(i, j: int)
{
for (x := i-1; x <= i+1; x++) {
for (y := j-1; y <= j+1; y++) {
if (x <1 || x>SZB || y<1 || y>SZB)
continue;
if (board_check(x, y) == 0) {
score++;
board[x][y].state = SELECTED;
display_square(x, y, "0", "olive");
display_zeros(x, y);
}
}
}
}
fatal(s: string)
{
sys->fprint(stderr, "%s\n", s);
exit;
}
sleep(t: int)
{
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, "sweeper: tk error on '%s': %s\n", s, e);
return e;
}
win_config := array[] of {
"frame .f -width 220 -height 220",
"menubutton .f.sz -text Options -menu .f.sz.sm",
"menu .f.sz.sm",
".f.sz.sm add command -label restart -command { send cmd restart }",
"pack .f.sz -side left",
"label .f.l -text {Score: }",
"pack .f.l -side right",
"frame .ft",
"label .ft.l -text { }",
"pack .ft.l -side left",
"pack .f -side top -fill x",
"pack .ft -side top -fill x",
};
win_config2 := array[] of {
"pack propagate . 0",
"update",
};