code: purgatorio

ref: 2459e34d77e2e21ef829c0dfaafd99433899494f
dir: /appl/wm/sweeper.b/

View raw version
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",
};