1# Copyright © 1999 Roger Peppe. All rights reserved. 2implement Tetris; 3 4include "sys.m"; 5 sys: Sys; 6 stderr: ref Sys->FD; 7include "draw.m"; 8 draw: Draw; 9 Point, Rect: import draw; 10include "tk.m"; 11 tk: Tk; 12include "tkclient.m"; 13 tkclient: Tkclient; 14include "rand.m"; 15 rand: Rand; 16include "scoretable.m"; 17 scoretab: Scoretable; 18include "arg.m"; 19include "keyboard.m"; 20 Up, Down, Right, Left: import Keyboard; 21 22include "keyring.m"; 23include "security.m"; # for random seed 24 25Tetris: module { 26 init: fn(ctxt: ref Draw->Context, argv: list of string); 27}; 28 29SCORETABLE: con "/lib/scores/tetris"; 30LOCKPORT: con 18343; 31 32# number of pieces across and down board. 33BOARDWIDTH: con 10; 34BOARDHEIGHT: con 22; 35 36awaitingscore := 1; 37 38Row: adt { 39 tag: string; 40 delete: int; 41}; 42 43Board: adt { 44 new: fn(top: ref Tk->Toplevel, w: string, 45 blocksize: int, maxsize: Point): ref Board; 46 makeblock: fn(bd: self ref Board, colour: string, p: Point): string; 47 moveblock: fn(bd: self ref Board, b: string, p: Point); 48 movecurr: fn(bd: self ref Board, delta: Point); 49 delrows: fn(bd: self ref Board, rows: list of int); 50 landedblock: fn(bd: self ref Board, b: string, p: Point); 51 setnextshape: fn(bd: self ref Board, colour: string, spec: array of Point); 52 setscore: fn(bd: self ref Board, score: int); 53 setlevel: fn(bd: self ref Board, level: int); 54 setnrows: fn(bd: self ref Board, level: int); 55 gameover: fn(bd: self ref Board); 56 update: fn(bd: self ref Board); 57 58 state: array of array of byte; 59 w: string; 60 dx: int; 61 win: ref Tk->Toplevel; 62 rows: array of Row; 63 maxid: int; 64}; 65 66Piece: adt { 67 shape: int; 68 rot: int; 69}; 70 71Shape: adt { 72 coords: array of array of Point; 73 colour: string; 74 score: array of int; 75}; 76 77Game: adt { 78 new: fn(bd: ref Board): ref Game; 79 move: fn(g: self ref Game, dx: int); 80 rotate: fn(g: self ref Game, clockwise: int); 81 tick: fn(g: self ref Game): int; 82 drop: fn(g: self ref Game); 83 84 bd: ref Board; 85 level: int; 86 delay: int; 87 score: int; 88 nrows: int; 89 pieceids: array of string; 90 pos: Point; 91 next, 92 curr: Piece; 93}; 94 95badmod(path: string) 96{ 97 sys->fprint(stderr, "tetris: cannot load %s: %r\n", path); 98 raise "fail: bad module"; 99} 100 101usage() 102{ 103 sys->fprint(stderr, "usage: tetris [-b blocksize]\n"); 104 raise "fail:usage"; 105} 106 107init(ctxt: ref Draw->Context, argv: list of string) 108{ 109 sys = load Sys Sys->PATH; 110 stderr = sys->fildes(2); 111 draw = load Draw Draw->PATH; 112 tk = load Tk Tk->PATH; 113 if (tk == nil) 114 badmod(Tk->PATH); 115 tkclient = load Tkclient Tkclient->PATH; 116 if (tkclient == nil) 117 badmod(Tkclient->PATH); 118 tkclient->init(); 119 rand = load Rand Rand->PATH; 120 if (rand == nil) 121 badmod(Rand->PATH); 122 arg := load Arg Arg->PATH; 123 if (arg == nil) 124 badmod(Arg->PATH); 125 if (ctxt == nil) 126 ctxt = tkclient->makedrawcontext(); 127 blocksize := 17; # preferred block size 128 arg->init(argv); 129 while ((opt := arg->opt()) != 0) { 130 case opt { 131 'b' => 132 if ((b := arg->arg()) == nil || int b <= 0) 133 usage(); 134 blocksize = int b; 135 * => 136 usage(); 137 } 138 } 139 if (arg->argv() != nil) 140 usage(); 141 142 sys->pctl(Sys->NEWPGRP|Sys->FORKNS, nil); 143 scoretab = load Scoretable Scoretable->PATH; 144 scorech := chan of int; 145 spawn scoresrvwait(scorech); 146 (win, winctl) := tkclient->toplevel(ctxt, "", "Tetris",Tkclient->Hide); 147 seedrand(); 148 fromuser := chan of string; 149 tk->namechan(win, fromuser, "user"); 150 cmd(win, "bind . <Key> {send user k %s}"); 151 cmd(win, "bind . <ButtonRelease-1> {focus .}"); 152 cmd(win, "bind .Wm_t <ButtonRelease-1> +{focus .}"); 153 cmd(win, "focus ."); 154 155 maxsize := Point(10000, 10000); 156 if (ctxt.display.image != nil) { 157 img := ctxt.display.image; 158 wsz := wsize(win, "."); 159 maxsize.y = img.r.dy() - wsz.y; 160 maxsize.x = img.r.dx(); 161 } 162 163 tkclient->onscreen(win, nil); 164 tkclient->startinput(win, "kbd"::"ptr"::nil); 165 for (;;) { 166 bd := Board.new(win, ".f", blocksize, maxsize); 167 if (bd == nil) { 168 sys->fprint(stderr, "tetris: couldn't make board\n"); 169 return; 170 } 171 cmd(win, "bind .f.c <ButtonRelease-1> {send user m %x %y}"); 172 cmd(win, "pack .f -side top"); 173 cmd(win, "update"); 174 g := Game.new(bd); 175 (finished, rank) := rungame(g, win, fromuser, winctl, scorech); 176 if (finished) 177 break; 178 cmd(win, "pack propagate . 0"); 179 if (scoretab != nil) { 180 cmd(win, "destroy .f"); 181 if (showhighscores(win, fromuser, winctl, rank) == 0) 182 break; 183 } else 184 cmd(win, "destroy .f"); 185 } 186} 187 188wsize(win: ref Tk->Toplevel, w: string): Point 189{ 190 bd := int cmd(win, w + " cget -bd"); 191 return (int cmd(win, w + " cget -width") + bd * 2, 192 int cmd(win, w + " cget -height") + bd * 2); 193} 194 195rungame(g: ref Game, win: ref Tk->Toplevel, fromuser: chan of string, winctl: chan of string, scorech: chan of int): (int, int) 196{ 197 tickchan := chan of int; 198 spawn ticker(g, tickchan); 199 paused := 0; 200 tch := chan of int; 201 202 gameover := 0; 203 rank := -1; 204 bdsize := wsize(win, ".f.c"); 205 boundy := bdsize.y * 2 / 3; 206 id := cmd(win, ".f.c create line " + p2s((0, boundy)) + " " + p2s((bdsize.x, boundy)) + 207 " -fill white"); 208 cmd(win, ".f.c lower " + id); 209 for (;;) alt { 210 s := <-win.ctxt.kbd => 211 tk->keyboard(win, s); 212 s := <-win.ctxt.ptr => 213 tk->pointer(win, *s); 214 s := <-fromuser => 215 key: int; 216 if (s[0] == 'm') { 217 (nil, toks) := sys->tokenize(s, " "); 218 p := Point(int hd tl toks, int hd tl tl toks); 219 if (p.y > boundy) 220 key = ' '; 221 else { 222 x := p.x / (bdsize.x / 3); 223 case x { 224 0 => 225 key = '7'; 226 1 => 227 key = '8'; 228 2 => 229 key = '9'; 230 * => 231 break; 232 } 233 } 234 } else if (s[0] == 'k') 235 key = int s[1:]; 236 else 237 sys->print("oops (%s)\n", s); 238 if (gameover) 239 return (key == 'q', rank); 240 if (paused) { 241 paused = 0; 242 (tickchan, tch) = (tch, tickchan); 243 if (key != 'q') 244 continue; 245 } 246 case key { 247 '9' or 'c' or Right => 248 g.move(1); 249 '7' or 'z' or Left => 250 g.move(-1); 251 '8' or 'x' or Up => 252 g.rotate(0); 253 ' ' or Down => 254 g.drop(); 255 'p' => 256 paused = 1; 257 (tickchan, tch) = (tch, tickchan); 258 'q' => 259 g.delay = -1; 260 while (<-tickchan) 261 ; 262 return (1, rank); 263 } 264 s := <-win.ctxt.ctl or 265 s = <-win.wreq or 266 s = <-winctl => 267 tkclient->wmctl(win, s); 268 n := <-tickchan => 269 if (g.tick() == -1) { 270 while (n) 271 n = <-tickchan; 272 if (awaitingscore && !<-scorech) { 273 awaitingscore = 0; 274 scoretab = nil; 275 } 276 if (scoretab != nil) 277 rank = scoretab->setscore(g.score, sys->sprint("%d %d %bd", g.nrows, g.level, 278 big readfile("/dev/time") / big 1000000)); 279 gameover = 1; 280 } 281 ok := <-scorech => 282 awaitingscore = 0; 283 if (!ok) 284 scoretab = nil; 285 } 286} 287 288tablerow(win: ref Tk->Toplevel, w, bg: string, relief: string, vals: array of string, widths: array of string) 289{ 290 cmd(win, "frame " + w + " -bd 2 -relief " + relief); 291 for (i := 0; i < len vals; i++) { 292 cw := cmd(win, "label " + w + "." + string i + " -text " + tk->quote(vals[i]) + " -width " + widths[i] + bg); 293 cmd(win, "pack " + cw + " -side left -anchor w"); 294 } 295 cmd(win, "pack " + w + " -side top"); 296} 297 298showhighscores(win: ref Tk->Toplevel, fromuser: chan of string, winctl: chan of string, rank: int): int 299{ 300 widths := array[] of {"10w", "7w", "7w", "5w"}; # user, score, level, rows 301 cmd(win, "frame .f -bd 4 -relief raised"); 302 cmd(win, "label .f.title -text {High Scores}"); 303 cmd(win, "pack .f.title -side top -anchor n"); 304 tablerow(win, ".f.h", nil, "raised", array[] of {"User", "Score", "Level", "Rows"}, widths); 305 sl := scoretab->scores(); 306 n := 0; 307 while (sl != nil) { 308 s := hd sl; 309 bg := ""; 310 if (n == rank) 311 bg = " -bg white"; 312 f := ".f.f" + string n++; 313 nrows := level := ""; 314 (nil, toks) := sys->tokenize(s.other, " "); 315 if (toks != nil) 316 (nrows, toks) = (hd toks, tl toks); 317 if (toks != nil) 318 level = hd toks; 319 tablerow(win, f, bg, "sunken", array[] of {s.user, string s.score, level, nrows}, widths); 320 sl = tl sl; 321 } 322 cmd(win, "button .f.b -text {New game} -command {send user s}"); 323 cmd(win, "pack .f.b -side top"); 324 cmd(win, "pack .f -side top"); 325 cmd(win, "update"); 326 for (;;) alt { 327 s := <-win.ctxt.kbd => 328 tk->keyboard(win, s); 329 s := <-win.ctxt.ptr => 330 tk->pointer(win, *s); 331 s := <-fromuser => 332 if (s[0] == 'k') { 333 cmd(win, "destroy .f"); 334 return int s[1:] != 'q'; 335 } else if (s[0] == 's') { 336 cmd(win, "destroy .f"); 337 return 1; 338 } 339 s := <-win.ctxt.ctl or 340 s = <-win.wreq or 341 s = <-winctl => 342 tkclient->wmctl(win, s); 343 } 344} 345 346scoresrvwait(ch: chan of int) 347{ 348 if (scoretab == nil) { 349 ch <-= 0; 350 return; 351 } 352 (ok, err) := scoretab->init(LOCKPORT, readfile("/dev/user"), "tetris", SCORETABLE); 353 if (ok != -1) 354 ch <-= 1; 355 else { 356 if (err != "timeout") 357 sys->fprint(stderr, "tetris: scoretable error: %s\n", err); 358 else 359 sys->fprint(stderr, "tetris: timed out trying to connect to score server\n"); 360 ch <-= 0; 361 } 362} 363 364readfile(f: string): string 365{ 366 fd := sys->open(f, Sys->OREAD); 367 if (fd == nil) 368 return nil; 369 buf := array[Sys->ATOMICIO] of byte; 370 n := sys->read(fd, buf, len buf); 371 if (n <= 0) 372 return nil; 373 return string buf[0:n]; 374} 375 376ticker(g: ref Game, c: chan of int) 377{ 378 c <-= 1; 379 while (g.delay >= 0) { 380 sys->sleep(g.delay); 381 c <-= 1; 382 } 383 c <-= 0; 384} 385 386seedrand() 387{ 388 random := load Random Random->PATH; 389 if (random == nil) { 390 sys->fprint(stderr, "tetris: cannot load %s: %r\n", Random->PATH); 391 return; 392 } 393 seed := random->randomint(Random->ReallyRandom); 394 rand->init(seed); 395} 396 397Game.new(bd: ref Board): ref Game 398{ 399 g := ref Game; 400 g.bd = bd; 401 g.level = 0; 402 g.pieceids = array[4] of string; 403 g.score = 0; 404 g.delay = delays[g.level]; 405 g.nrows = 0; 406 g.next = randompiece(); 407 newpiece(g); 408 bd.update(); 409 return g; 410} 411 412randompiece(): Piece 413{ 414 p: Piece; 415 p.shape = rand->rand(len shapes); 416 p.rot = rand->rand(len shapes[p.shape].coords); 417 return p; 418} 419 420Game.move(g: self ref Game, dx: int) 421{ 422 np := g.pos.add((dx, 0)); 423 if (canmove(g, g.curr, np)) { 424 g.bd.movecurr((dx, 0)); 425 g.bd.update(); 426 g.pos = np; 427 } 428} 429 430Game.rotate(g: self ref Game, clockwise: int) 431{ 432 inc := 1; 433 if (!clockwise) 434 inc = -1; 435 npiece := g.curr; 436 coords := shapes[npiece.shape].coords; 437 nrots := len coords; 438 npiece.rot = (npiece.rot + inc + nrots) % nrots; 439 if (canmove(g, npiece, g.pos)) { 440 c := coords[npiece.rot]; 441 for (i := 0; i < len c; i++) 442 g.bd.moveblock(g.pieceids[i], g.pos.add(c[i])); 443 g.curr = npiece; 444 g.bd.update(); 445 } 446} 447 448Game.tick(g: self ref Game): int 449{ 450 if (canmove(g, g.curr, g.pos.add((0, 1)))) { 451 g.bd.movecurr((0, 1)); 452 g.pos.y++; 453 } else { 454 c := shapes[g.curr.shape].coords[g.curr.rot]; 455 max := g.pos.y; 456 min := g.pos.y + 4; 457 for (i := 0; i < len c; i++) { 458 p := g.pos.add(c[i]); 459 if (p.y < 0) { 460 g.delay = -1; 461 g.bd.gameover(); 462 g.bd.update(); 463 return -1; 464 } 465 if (p.y > max) 466 max = p.y; 467 if (p.y < min) 468 min = p.y; 469 g.bd.landedblock(g.pieceids[i], p); 470 } 471 full: list of int; 472 for (i = min; i <= max; i++) { 473 for (x := 0; x < BOARDWIDTH; x++) 474 if (g.bd.state[i][x] == byte 0) 475 break; 476 if (x == BOARDWIDTH) 477 full = i :: full; 478 } 479 if (full != nil) { 480 g.bd.delrows(full); 481 g.nrows += len full; 482 g.bd.setnrows(g.nrows); 483 level := g.nrows / 10; 484 if (level != g.level) { 485 g.bd.setlevel(level); 486 g.level = level; 487 if (level >= len delays) 488 level = len delays - 1; 489 g.delay = delays[level]; 490 } 491 } 492 g.score += shapes[g.curr.shape].score[g.curr.rot]; 493 g.bd.setscore(g.score); 494 newpiece(g); 495 } 496 g.bd.update(); 497 return 0; 498} 499 500Game.drop(g: self ref Game) 501{ 502 p := g.pos.add((0, 1)); 503 while (canmove(g, g.curr, p)) 504 p.y++; 505 p.y--; 506 g.bd.movecurr((0, p.y - g.pos.y)); 507 g.pos = p; 508 g.bd.update(); 509} 510 511canmove(g: ref Game, piece: Piece, p: Point): int 512{ 513 c := shapes[piece.shape].coords[piece.rot]; 514 for (i := 0; i < len c; i++) { 515 q := p.add(c[i]); 516 if (q.x < 0 || q.x >= BOARDWIDTH || q.y >= BOARDHEIGHT) 517 return 0; 518 if (q.y >= 0 && int g.bd.state[q.y][q.x]) 519 return 0; 520 } 521 return 1; 522} 523 524newpiece(g: ref Game) 525{ 526 g.curr = g.next; 527 g.next = randompiece(); 528 g.bd.setnextshape(shapes[g.next.shape].colour, shapes[g.next.shape].coords[g.next.rot]); 529 shape := shapes[g.curr.shape]; 530 coords := shape.coords[g.curr.rot]; 531 g.pos = (3, -4); 532 for (i := 0; i < len coords; i++) 533 g.pieceids[i] = g.bd.makeblock(shape.colour, g.pos.add(coords[i])); 534} 535 536p2s(p: Point): string 537{ 538 return string p.x + " " + string p.y; 539} 540 541Board.new(top: ref Tk->Toplevel, w: string, blocksize: int, maxsize: Point): ref Board 542{ 543 cmd(top, "frame " + w); 544 cmd(top, "canvas " + w + ".c -borderwidth 2 -relief sunken -width 1 -height 1"); 545 cmd(top, "frame " + w + ".f"); 546 cmd(top, "canvas " + w + ".f.ns -width 1 -height 1"); 547 makescorewidget(top, w + ".f.scoref", "Score"); 548 makescorewidget(top, w + ".f.levelf", "Level"); 549 makescorewidget(top, w + ".f.rowsf", "Rows"); 550 cmd(top, "pack " + w + ".c -side left"); 551 cmd(top, "pack " + w + ".f -side top"); 552 cmd(top, "pack " + w + ".f.ns -side top"); 553 cmd(top, "pack " + w + ".f.scoref -side top -fill x"); 554 cmd(top, "pack " + w + ".f.levelf -side top -fill x"); 555 cmd(top, "pack " + w + ".f.rowsf -side top -fill x"); 556 557 sz := wsize(top, w); 558 avail := Point(maxsize.x - sz.x, maxsize.y); 559 avail.x /= BOARDWIDTH; 560 avail.y /= BOARDHEIGHT; 561 dx := avail.x; 562 if (avail.y < avail.x) 563 dx = avail.y; 564 if (dx <= 0) 565 return nil; 566 if (dx > blocksize) 567 dx = blocksize; 568 cmd(top, w + ".f.ns configure -width " + string(4 * dx + 1 - 2*2) + 569 " -height " + string(4 * dx + 1 - 2*2)); 570 cmd(top, w + ".c configure -width " + string(dx * BOARDWIDTH + 1) + 571 " -height " + string(dx * BOARDHEIGHT + 1)); 572 bd := ref Board(array[BOARDHEIGHT] 573 of {* => array[BOARDWIDTH] of {* => byte 0}}, 574 w, dx, top, array[BOARDHEIGHT] of {* => Row(nil, 0)}, 1); 575 return bd; 576} 577 578makescorewidget(top: ref Tk->Toplevel, w, title: string) 579{ 580 cmd(top, "frame " + w); 581 cmd(top, "label " + w + ".title -text " + tk->quote(title)); 582 cmd(top, "label " + w + 583 ".val -bd 2 -relief sunken -width 5w -text 0 -anchor e"); 584 cmd(top, "pack " + w + ".title -side left -anchor w"); 585 cmd(top, "pack " + w + ".val -side right -anchor e"); 586} 587 588blockrect(bd: ref Board, p: Point): string 589{ 590 p = p.mul(bd.dx); 591 q := p.add((bd.dx, bd.dx)); 592 return string p.x + " " + string p.y + " " + string q.x + " " + string q.y; 593} 594 595Board.makeblock(bd: self ref Board, colour: string, p: Point): string 596{ 597 tag := cmd(bd.win, bd.w + ".c create rectangle " + blockrect(bd, p) + " -fill " + colour + " -tags curr"); 598 if (tag != nil && tag[0] == '!') 599 return nil; 600 return tag; 601} 602 603Board.moveblock(bd: self ref Board, b: string, p: Point) 604{ 605 cmd(bd.win, bd.w + ".c coords " + b + " " + blockrect(bd, p)); 606} 607 608Board.movecurr(bd: self ref Board, delta: Point) 609{ 610 delta = delta.mul(bd.dx); 611 cmd(bd.win, bd.w + ".c move curr " + string delta.x + " " + string delta.y); 612} 613 614Board.landedblock(bd: self ref Board, b: string, p: Point) 615{ 616 cmd(bd.win, bd.w + ".c dtag " + b + " curr"); 617 rs := cmd(bd.win, bd.w + ".c coords " + b); 618 if (rs != nil && rs[0] == '!') 619 return; 620 (nil, toks) := sys->tokenize(rs, " "); 621 if (len toks != 4) { 622 sys->fprint(stderr, "bad coords for block %s\n", b); 623 return; 624 } 625 y := int hd tl toks / bd.dx; 626 if (y < 0) 627 return; 628 if (y >= BOARDHEIGHT) { 629 sys->fprint(stderr, "block '%s' too far down (coords %s)\n", b, rs); 630 return; 631 } 632 rtag := bd.rows[y].tag; 633 if (rtag == nil) 634 rtag = bd.rows[y].tag = "r" + string bd.maxid++; 635 cmd(bd.win, bd.w + ".c addtag " + rtag + " withtag " + b); 636 if (p.y >= 0) 637 bd.state[p.y][p.x] = byte 1; 638} 639 640Board.delrows(bd: self ref Board, rows: list of int) 641{ 642 while (rows != nil) { 643 r := hd rows; 644 bd.rows[r].delete = 1; 645 rows = tl rows; 646 } 647 j := BOARDHEIGHT - 1; 648 for (i := BOARDHEIGHT - 1; i >= 0; i--) { 649 if (bd.rows[i].delete) { 650 cmd(bd.win, bd.w + ".c delete " + bd.rows[i].tag); 651 bd.rows[i] = (nil, 0); 652 bd.state[i] = nil; 653 } else { 654 if (i != j && bd.rows[i].tag != nil) { 655 dy := (j - i) * bd.dx; 656 cmd(bd.win, bd.w + ".c move " + bd.rows[i].tag + " 0 " + string dy); 657 bd.rows[j] = bd.rows[i]; 658 bd.rows[i] = (nil, 0); 659 bd.state[j] = bd.state[i]; 660 bd.state[i] = nil; 661 } 662 j--; 663 } 664 } 665 for (i = 0; i < BOARDHEIGHT; i++) 666 if (bd.state[i] == nil) 667 bd.state[i] = array[BOARDWIDTH] of {* => byte 0}; 668} 669 670Board.update(bd: self ref Board) 671{ 672 cmd(bd.win, "update"); 673} 674 675Board.setnextshape(bd: self ref Board, colour: string, spec: array of Point) 676{ 677 cmd(bd.win, bd.w + ".f.ns delete all"); 678 min := Point(4,4); 679 max := Point(0,0); 680 for (i := 0; i < len spec; i++) { 681 if (spec[i].x > max.x) max.x = spec[i].x; 682 if (spec[i].x < min.x) min.x = spec[i].x; 683 if (spec[i].y > max.y) max.y = spec[i].y; 684 if (spec[i].y < min.y) min.y = spec[i].y; 685 } 686 o: Point; 687 o.x = (4 - (max.x - min.x + 1)) * bd.dx / 2 - min.x * bd.dx; 688 o.y = (4 - (max.y - min.y + 1)) * bd.dx / 2 - min.y * bd.dx; 689 for (i = 0; i < len spec; i++) { 690 br := Rect(o.add(spec[i].mul(bd.dx)), o.add(spec[i].add((1,1)).mul(bd.dx))); 691 cmd(bd.win, bd.w + ".f.ns create rectangle " + 692 string br.min.x + " " + string br.min.y + " " + string br.max.x + " " + string br.max.y + 693 " -fill " + colour); 694 } 695} 696 697Board.setscore(bd: self ref Board, score: int) 698{ 699 cmd(bd.win, bd.w + ".f.scoref.val configure -text " + string score); 700} 701 702Board.setlevel(bd: self ref Board, level: int) 703{ 704 cmd(bd.win, bd.w + ".f.levelf.val configure -text " + string level); 705} 706 707Board.setnrows(bd: self ref Board, nrows: int) 708{ 709 cmd(bd.win, bd.w + ".f.rowsf.val configure -text " + string nrows); 710} 711 712Board.gameover(bd: self ref Board) 713{ 714 cmd(bd.win, "label " + bd.w + ".gameover -text {Game over} -bd 4 -relief ridge"); 715 p := Point(BOARDWIDTH * bd.dx / 2, BOARDHEIGHT * bd.dx / 3); 716 cmd(bd.win, bd.w + ".c create window " + string p.x + " " + string p.y + " -window " + bd.w + ".gameover"); 717} 718 719cmd(top: ref Tk->Toplevel, s: string): string 720{ 721 e := tk->cmd(top, s); 722# sys->print("%s\n", s); 723 if (e != nil && e[0] == '!') 724 sys->fprint(stderr, "tetris: tk error on '%s': %s\n", s, e); 725 return e; 726} 727 728VIOLET: con "#ffaaff"; 729CYAN: con "#93ddf1"; 730 731delays := array[] of {300, 250, 200, 150, 100, 80}; 732 733shapes := array[] of { 734Shape( 735 # #### 736 array[] of { 737 array[] of {Point(0,1), Point(1,1), Point(2,1), Point(3,1)}, 738 array[] of {Point(1,0), Point(1,1), Point(1,2), Point(1,3)}, 739 }, 740 "red", 741 array[] of {5, 8}), 742Shape( 743 # ## 744 # ## 745 array[] of { 746 array[] of {Point(0,0), Point(0,1), Point(1,0), Point(1,1)}, 747 }, 748 "orange", 749 array[] of {6}), 750Shape( 751 # # 752 # ## 753 # # 754 array[] of { 755 array[] of {Point(1,0), Point(0,1), Point(1,1), Point(2,1)}, 756 array[] of {Point(1,0), Point(1,1), Point(2,1), Point(1,2)}, 757 array[] of {Point(0,1), Point(1,1), Point(2,1), Point(1,2)}, 758 array[] of {Point(1,0), Point(0,1), Point(1,1), Point(1,2)}, 759 }, 760 "yellow", 761 array[] of {5,5,6,5}), 762Shape( 763 # ## 764 # ## 765 array[] of { 766 array[] of {Point(0,0), Point(1,0), Point(1,1), Point(2,1)}, 767 array[] of {Point(1,0), Point(0,1), Point(1,1), Point(0,2)}, 768 }, 769 "green", 770 array[] of {6,7}), 771Shape( 772 # ## 773 # ## 774 array[] of { 775 array[] of {Point(1,0), Point(2,0), Point(0,1), Point(1,1)}, 776 array[] of {Point(0,0), Point(0,1), Point(1,1), Point(1,2)}, 777 }, 778 "blue", 779 array[] of {6,7}), 780Shape( 781 # ### 782 # # 783 array[] of { 784 array[] of {Point(2,0), Point(0,1), Point(1,1), Point(2,1)}, 785 array[] of {Point(0,0), Point(0,1), Point(0,2), Point(1,2)}, 786 array[] of {Point(0,0), Point(1,0), Point(2,0), Point(0,1)}, 787 array[] of {Point(0,0), Point(1,0), Point(1,1), Point(1,2)}, 788 }, 789 CYAN, 790 array[] of {6,7,6,7}), 791Shape( 792 # # 793 # ### 794 array[] of { 795 array[] of {Point(0,0), Point(1,0), Point(2,0), Point(2,1)}, 796 array[] of {Point(1,0), Point(1,1), Point(0,2), Point(1,2)}, 797 array[] of {Point(0,0), Point(0,1), Point(1,1), Point(2,1)}, 798 array[] of {Point(0,0), Point(1,0), Point(0,1), Point(0,2)}, 799 }, 800 VIOLET, 801 array[] of {6,7,6,7} 802), 803}; 804 805