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 {send kch %s}"); cmd(win, "bind . {focus .c}"); cmd(win, "bind .Wm_t +{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 (iinit(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; }