summaryrefslogtreecommitdiff
path: root/appl/wm/snake.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/wm/snake.b')
-rw-r--r--appl/wm/snake.b373
1 files changed, 373 insertions, 0 deletions
diff --git a/appl/wm/snake.b b/appl/wm/snake.b
new file mode 100644
index 00000000..a3c8c6a2
--- /dev/null
+++ b/appl/wm/snake.b
@@ -0,0 +1,373 @@
+implement Snake;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Display, Point, Screen, Image, Rect: import draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "keyboard.m";
+include "rand.m";
+ rand: Rand;
+include "scoretable.m";
+ scoretable: Scoretable;
+
+Snake: module{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Tick: adt{
+ dt: int;
+};
+
+DX: con 30;
+DY: con 30;
+Size: int;
+
+EMPTY, SNAKE, FOOD, CRASH: con iota;
+HIGHSCOREFILE: con "/lib/scores/snake";
+
+board: array of array of int;
+win: ref Tk->Toplevel;
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "snake: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ if(tkclient == nil){
+ sys->print("sys->fildes(2), couldn't load %s: %r\n", Tkclient->PATH);
+ raise "fail:bad module";
+ }
+ tkclient->init();
+ tk = load Tk Tk->PATH;
+ rand = load Rand Rand->PATH;
+ if(rand == nil){
+ sys->fprint(sys->fildes(2), "snake: cannot load %s: %r\n", Rand->PATH);
+ raise "fail:bad module";
+ }
+ scoretable = load Scoretable Scoretable->PATH;
+ if (scoretable != nil) {
+ (ok, err) := scoretable->init(-1, readfile("/dev/user"), "snake", HIGHSCOREFILE);
+ if (ok == -1) {
+ sys->fprint(sys->fildes(2), "snake: cannot init scoretable: %s\n", err);
+ scoretable = nil;
+ }
+ }
+
+ sys->pctl(Sys->NEWPGRP, nil);
+ ctlchan: chan of string;
+ (win, ctlchan) = tkclient->toplevel(ctxt, nil, "Snake", Tkclient->Hide);
+
+ tk->namechan(win, kch := chan of string, "kch");
+
+ cmd(win, "canvas .c -bd 2 -relief ridge");
+ cmd(win, "label .scoret -text Score:");
+ cmd(win, "label .score -text 0");
+ cmd(win, "frame .f");
+ if (scoretable != nil) {
+ cmd(win, "label .hight -text High:");
+ cmd(win, "label .high -text 0");
+ cmd(win, "pack .hight .high -in .f -side left");
+ }
+ cmd(win, "pack .score .scoret -in .f -side right");
+ cmd(win, "pack .f -side top -fill x");
+ cmd(win, "pack .c");
+ cmd(win, "bind .c <Key> {send kch %s}");
+ cmd(win, "bind . <ButtonRelease-1> {focus .c}");
+ cmd(win, "bind .Wm_t <ButtonRelease-1> +{focus .c}");
+ cmd(win, "focus .c");
+
+ Size = int cmd(win, ".c cget -actheight") / DY;
+ cmd(win, ".c configure -width " + string (Size * DX) + " -height " + string (Size * DY));
+
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+
+ spawn winctl(ctlchan);
+ if (len argv > 1)
+ game(kch, hd tl argv);
+
+ for(;;){
+ game(kch, nil);
+ cmd(win, ".c delete all");
+ }
+}
+
+winctl(ctlchan: chan of string)
+{
+ for(;;) alt {
+ s := <-win.ctxt.kbd =>
+ tk->keyboard(win, s);
+ s := <-win.ctxt.ptr =>
+ tk->pointer(win, *s);
+ s := <-win.ctxt.ctl or
+ s = <-win.wreq or
+ s = <-ctlchan =>
+ tkclient->wmctl(win, s);
+ }
+}
+
+board2s(board: array of array of int): string
+{
+ s := string DX + "." + string DY + ".";
+ for (y := 0; y < DY; y++)
+ for (x := 0; x < DX; x++)
+ s[len s] = board[x][y] + '0';
+ return s;
+}
+
+replayproc(replay: string, kch: chan of string, tick: chan of int, nil: ref Tick)
+{
+ i := 0;
+ while(i < len replay){
+ n := 0;
+ while(i < len replay && replay[i] >= '0' && replay[i] <= '9') {
+ n = n*10 + replay[i] - '0';
+ i++;
+ }
+ for (t := 0; t < n; t++) {
+ tick <-= 1;
+ sys->sleep(0);
+ }
+ if (i == len replay)
+ break;
+ kch <-= string replay[i];
+ i++;
+ }
+ tick <-= 1;
+ tick <-= 0;
+}
+
+game(realkch: chan of string, replay: string)
+{
+ scores := scoretable->scores();
+ if (scores != nil)
+ cmd(win, ".high configure -text " + string (hd scores).score);
+ cmd(win, ".score configure -text {0}");
+ board = array[DX] of { * => array[DY] of{* => EMPTY}};
+
+ seed := rand->rand(16r7fffffff);
+ if (replay != nil) {
+ seed = int replay;
+ for (i := 0; i < len replay; i++)
+ if (replay[i] == '.')
+ break;
+ if (i<len replay)
+ replay = replay[i+1:];
+ }
+ rand->init(seed);
+ p := Point(DX/2, DY/2);
+ dir := Point(1, 0);
+ lkey := 'r';
+ snake := array[5] of Point;
+ for(i := 0; i < len snake; i++){
+ snake[i] = p.add(dir.mul(i));
+ make(snake[i]);
+ }
+ placefood();
+ p = p.add(dir.mul(i));
+ ticki := ref Tick(100);
+ realtick := chan of int;
+
+ userkch: chan of string;
+ if(replay != nil) {
+ (userkch, realkch) = (realkch, chan of string);
+ spawn replayproc(replay, realkch, realtick, ticki);
+ } else {
+ userkch = chan of string;
+ spawn ticker(realtick, ticki);
+ }
+ cmd(win, "update");
+
+ score := 0;
+ leaveit := 0;
+ paused := 0;
+
+ log := "";
+ nticks := 0;
+ odir := dir;
+
+ dummykch := chan of string;
+ kch := realkch;
+
+ dummytick := chan of int;
+ tick := realtick;
+ for(;;){
+ alt{
+ c := <-kch =>
+ if(paused){
+ paused = 0;
+ tick = realtick;
+ }
+ kch = dummykch;
+ ndir := dir;
+ case int c{
+ Keyboard->Up =>
+ ndir = (0, -1);
+ Keyboard->Down =>
+ ndir = (0, 1);
+ Keyboard->Left =>
+ ndir = (-1, 0);
+ Keyboard->Right =>
+ ndir = (1, 0);
+ 'q' =>
+ tkclient->wmctl(win, "exit");
+ 'p' =>
+ paused = 1;
+ tick = dummytick;
+ kch = realkch;
+ }
+ if (!ndir.eq(dir) && !ndir.eq(dir.mul(-1))) { # don't allow 180° turn.
+ lkey = int c;
+ dir = ndir;
+ }
+ <-tick =>
+ if(!odir.eq(dir)) {
+ log += string nticks;
+ log[len log] = lkey;
+ nticks = 0;
+ odir = dir;
+ }
+ nticks++;
+ if(leaveit){
+ ns := array[len snake + 1] of Point;
+ ns[0:] = snake;
+ snake = ns;
+ leaveit = 0;
+ } else{
+ destroy(snake[0]);
+ snake[0:] = snake[1:];
+ }
+ np := snake[len snake - 2].add(dir);
+ np.x = (np.x + DX) % DX;
+ np.y = (np.y + DY) % DY;
+ snake[len snake - 1] = np;
+ wasfood := board[np.x][np.y] == FOOD;
+ if(!make(np)){
+ cmd(win, ".c create oval " + r2s(square(np).inset(-5)) + " -fill yellow");
+ cmd(win, "update");
+ if (scoretable != nil && replay == nil) {
+ board[np.x][np.y] = CRASH;
+ log += string nticks;
+ sys->print("%d.%s\n", seed, log);
+ scoretable->setscore(score, string seed + "." + log + " " + board2s(board));
+ }
+ ticki.dt = -1;
+ while(<-tick)
+ ;
+ sys->sleep(750);
+ absorb(realkch);
+ if(int <-realkch == 'q')
+ tkclient->wmctl(win, "exit");
+ return;
+ }
+ if(wasfood){
+ score++;
+ #if(score % 10 == 0){
+ # if(ticki.dt > 0)
+ # ticki.dt -= 5;
+ #}
+ cmd(win, ".score configure -text " + string score);
+ leaveit = 1;
+ placefood();
+ }
+ cmd(win, "update");
+ kch = realkch;
+ }
+ }
+}
+
+placefood()
+{
+ for(;;)
+ if(makefood((rand->rand(DX), rand->rand(DY))))
+ return;
+}
+
+make(p: Point): int
+{
+ # b := board[p.x][p.y];
+ if(board[p.x][p.y] == SNAKE)
+ return 0;
+ cmd(win, ".c create rectangle " + r2s(square(p)) +
+ " -fill blue -outline {} -tags b." + string p.x + "." + string p.y);
+ board[p.x][p.y] = SNAKE;
+ return 1;
+}
+
+makefood(p: Point): int
+{
+ b := board[p.x][p.y];
+ if(b == SNAKE)
+ return 0;
+ cmd(win, ".c create oval " + r2s(square(p).inset(-2)) +
+ " -fill red -tags b." + string p.x + "." + string p.y);
+ board[p.x][p.y] = FOOD;
+ return 1;
+}
+
+destroy(p: Point)
+{
+ board[p.x][p.y] = 0;
+ cmd(win, ".c delete b." + string p.x + "." + string p.y);
+}
+
+square(p: Point): Rect
+{
+ p = p.mul(Size);
+ return (p, p.add((Size, Size)));
+}
+
+ticker(tick: chan of int, ticki: ref Tick)
+{
+ while((dt := ticki.dt) >= 0){
+ sys->sleep(dt);
+ tick <-= 1;
+ }
+ tick <-= 0;
+}
+
+absorb(c: chan of string)
+{
+ for(;;){
+ alt{
+ <-c =>
+ ;
+ * =>
+ return;
+ }
+ }
+}
+
+r2s(r: Rect): string
+{
+ return sys->sprint("%d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
+}
+
+readfile(f: string): string
+{
+ fd := sys->open(f, Sys->OREAD);
+ if (fd == nil)
+ return nil;
+ buf := array[Sys->ATOMICIO] of byte;
+ n := sys->read(fd, buf, len buf);
+ if (n <= 0)
+ return nil;
+ return string buf[0:n];
+}
+
+cmd(win: ref Tk->Toplevel, s: string): string
+{
+ r := tk->cmd(win, s);
+ if(len r > 0 && r[0] == '!'){
+ sys->print("error executing '%s': %s\n", s, r[1:]);
+ }
+ return r;
+}