俄罗斯方块源码

View Code
   1 /*
   2  * PROJECT:  JsTetris
   3  * VERSION:  1.19
   4  * LICENSE:  BSD (revised)
   5  * AUTHOR:  (c) 2004-2009 Cezary Tomczak
   6  * LINK:  http://www.gosu.pl/tetris/
   7  *
   8  * This script can be used freely as long as all
   9  * copyright messages are intact.
  10  */
  11 
  12 /**
  13  * Tetris Game
  14  * Initializes the buttons automatically, no additional actions required
  15  *
  16  * Score:
  17  * 1) puzzle speed = 80+700/level
  18  * 2) if puzzles created in current level >= 10+level*2 then increase level
  19  * 3) after puzzle falling score is increased by 1000*level*linesRemoved
  20  * 4) each down action increases score by 5+level
  21  *
  22  * API:
  23  *
  24  * public - method can be called outside of the object
  25  * event - method is used as event, "this" refers to html object, "self" refers to javascript object
  26  *
  27  * class Tetris
  28  * ------------
  29  * public event void start()
  30  * public event void reset()
  31  * public event void pause()
  32  * public event void gameOver()
  33  * public event void up()
  34  * public event void down()
  35  * public event void left()
  36  * public event void right()
  37  * public event void space()
  38  *
  39  * class Window
  40  * ------------
  41  * event void activate()
  42  * event void close()
  43  * public bool isActive()
  44  *
  45  * class Keyboard
  46  * --------------
  47  * public void set(int key, function func)
  48  * event void event(object e)
  49  *
  50  * class Stats
  51  * -----------
  52  * public void start()
  53  * public void stop()
  54  * public void reset()
  55  * public event void incTime()
  56  * public void setScore(int i)
  57  * public void setLevel(int i)
  58  * public void setLines(int i)
  59  * public void setPuzzles(int i)
  60  * public void setActions(int i)
  61  * public int getScore()
  62  * public int getLevel()
  63  * public int getLines()
  64  * public int getPuzzles()
  65  * public int getActions()
  66  *
  67  * class Area
  68  * ----------
  69  * public Constructor(int unit, int x, int y, string id)
  70  * public void destroy()
  71  * public int removeFullLines()
  72  * public bool isLineFull(int y)
  73  * public void removeLine(int y)
  74  * public mixed getBlock(int y, int x)
  75  * public void addElement(htmlObject el)
  76  *
  77  * class Puzzle
  78  * ------------
  79  * public Constructor(object area)
  80  * public void reset()
  81  * public bool isRunning()
  82  * public bool isStopped()
  83  * public int getX()
  84  * public int getY()
  85  * public bool mayPlace()
  86  * public void place()
  87  * public void destroy()
  88  * private array createEmptyPuzzle(int y, int x)
  89  * event void fallDown()
  90  * public event void forceMoveDown()
  91  * public void stop()
  92  * public bool mayRotate()
  93  * public void rotate()
  94  * public bool mayMoveDown()
  95  * public void moveDown()
  96  * public bool mayMoveLeft()
  97  * public void moveLeft()
  98  * public bool mayMoveRight()
  99  * public void moveRight()
 100  *
 101  * class Highscores
 102  * ----------------
 103  * public Constructor(maxscores)
 104  * public void load()
 105  * public void save()
 106  * public bool mayAdd(int score)
 107  * public void add(string name, int score)
 108  * public array getScores()
 109  * public string toHtml()
 110  * private void sort()
 111  *
 112  * class Cookie
 113  * ------------
 114  * public string get(string name)
 115  * public void set(string name, string value, int seconds, string path, string domain, bool secure)
 116  * public void del(string name)
 117  *
 118  * TODO:
 119  * document.getElementById("tetris-nextpuzzle") cache ?
 120  *
 121  */
 122 function Tetris()
 123 {
 124     var self = this;
 125     this.stats = new Stats();
 126     this.puzzle = null;
 127     this.area = null;
 128 
 129     this.unit  = 20; // unit = x pixels
 130     this.areaX = 20; // area width = x units
 131     this.areaY = 20; // area height = y units
 132 
 133     this.highscores = new Highscores(10);
 134     this.paused = false;
 135 
 136     /**
 137      * @return void
 138      * @access public event
 139      */
 140     this.start = function()
 141     {
 142         if (self.puzzle && !confirm('Are you sure you want to start a new game ?')) return;
 143         self.reset();
 144         self.stats.start();
 145         document.getElementById("tetris-nextpuzzle").style.display = "block";
 146         document.getElementById("tetris-keys").style.display = "none";
 147         self.area = new Area(self.unit, self.areaX, self.areaY, "tetris-area");
 148         self.puzzle = new Puzzle(self, self.area);
 149         if (self.puzzle.mayPlace()) {
 150             self.puzzle.place();
 151         } else {
 152             self.gameOver();
 153         }
 154     };
 155 
 156     /**
 157      * @return void
 158      * @access public event
 159      */
 160     this.reset = function()
 161     {
 162         if (self.puzzle) {
 163             self.puzzle.destroy();
 164             self.puzzle = null;
 165         }
 166         if (self.area) {
 167             self.area.destroy();
 168             self.area = null;
 169         }
 170         document.getElementById("tetris-gameover").style.display = "none";
 171         document.getElementById("tetris-nextpuzzle").style.display = "none";
 172         document.getElementById("tetris-keys").style.display = "block";
 173         self.stats.reset();
 174         self.paused = false;
 175         document.getElementById('tetris-pause').style.display = 'block';
 176         document.getElementById('tetris-resume').style.display = 'none';
 177     };
 178 
 179     /**
 180      * Pause / Resume.
 181      * @return void
 182      * @access public event
 183      */
 184     this.pause = function()
 185     {
 186         if (self.puzzle == null) return;
 187         if (self.paused) {
 188             self.puzzle.running = true;
 189             self.puzzle.fallDownID = setTimeout(self.puzzle.fallDown, self.puzzle.speed);
 190             document.getElementById('tetris-pause').style.display = 'block';
 191             document.getElementById('tetris-resume').style.display = 'none';
 192             self.stats.timerId = setInterval(self.stats.incTime, 1000);
 193             self.paused = false;
 194         } else {
 195             if (!self.puzzle.isRunning()) return;
 196             if (self.puzzle.fallDownID) clearTimeout(self.puzzle.fallDownID);
 197             document.getElementById('tetris-pause').style.display = 'none';
 198             document.getElementById('tetris-resume').style.display = 'block';
 199             clearTimeout(self.stats.timerId);
 200             self.paused = true;
 201             self.puzzle.running = false;
 202         }
 203     };
 204 
 205     /**
 206      * End game.
 207      * Stop stats, ...
 208      * @return void
 209      * @access public event
 210      */
 211     this.gameOver = function()
 212     {
 213         self.stats.stop();
 214         self.puzzle.stop();
 215         document.getElementById("tetris-nextpuzzle").style.display = "none";
 216         document.getElementById("tetris-gameover").style.display = "block";
 217         if (this.highscores.mayAdd(this.stats.getScore())) {
 218             var name = prompt("Game Over !\nEnter your name:", "");
 219             if (name && name.trim().length) {
 220                 this.highscores.add(name, this.stats.getScore());
 221             }
 222         }
 223     };
 224 
 225     /**
 226      * @return void
 227      * @access public event
 228      */
 229     this.up = function()
 230     {
 231         if (self.puzzle && self.puzzle.isRunning() && !self.puzzle.isStopped()) {
 232             if (self.puzzle.mayRotate()) {
 233                 self.puzzle.rotate();
 234                 self.stats.setActions(self.stats.getActions() + 1);
 235             }
 236         }
 237     };
 238 
 239     /**
 240      * @return void
 241      * @access public event
 242      */
 243     this.down = function()
 244     {
 245         if (self.puzzle && self.puzzle.isRunning() && !self.puzzle.isStopped()) {
 246             if (self.puzzle.mayMoveDown()) {
 247                 self.stats.setScore(self.stats.getScore() + 5 + self.stats.getLevel());
 248                 self.puzzle.moveDown();
 249                 self.stats.setActions(self.stats.getActions() + 1);
 250             }
 251         }
 252     };
 253 
 254     /**
 255      * @return void
 256      * @access public event
 257      */
 258     this.left = function()
 259     {
 260         if (self.puzzle && self.puzzle.isRunning() && !self.puzzle.isStopped()) {
 261             if (self.puzzle.mayMoveLeft()) {
 262                 self.puzzle.moveLeft();
 263                 self.stats.setActions(self.stats.getActions() + 1);
 264             }
 265         }
 266     };
 267 
 268     /**
 269      * @return void
 270      * @access public event
 271      */
 272     this.right = function()
 273     {
 274         if (self.puzzle && self.puzzle.isRunning() && !self.puzzle.isStopped()) {
 275             if (self.puzzle.mayMoveRight()) {
 276                 self.puzzle.moveRight();
 277                 self.stats.setActions(self.stats.getActions() + 1);
 278             }
 279         }
 280     };
 281 
 282     /**
 283      * @return void
 284      * @access public event
 285      */
 286     this.space = function()
 287     {
 288         if (self.puzzle && self.puzzle.isRunning() && !self.puzzle.isStopped()) {
 289             self.puzzle.stop();
 290             self.puzzle.forceMoveDown();
 291         }
 292     };
 293 
 294     // windows
 295     var helpwindow = new Window("tetris-help");
 296     var highscores = new Window("tetris-highscores");
 297 
 298     // game menu
 299     document.getElementById("tetris-menu-start").onclick = function() { helpwindow.close(); highscores.close(); self.start(); this.blur(); };
 300 
 301     // document.getElementById("tetris-menu-reset").onclick = function() { helpwindow.close(); highscores.close(); self.reset(); this.blur(); };
 302 
 303     document.getElementById("tetris-menu-pause").onclick = function() { self.pause(); this.blur(); };
 304     document.getElementById("tetris-menu-resume").onclick = function() { self.pause(); this.blur(); };
 305 
 306     // help
 307     document.getElementById("tetris-menu-help").onclick = function() { highscores.close(); helpwindow.activate(); this.blur(); };
 308     document.getElementById("tetris-help-close").onclick = helpwindow.close;
 309 
 310     // highscores
 311     document.getElementById("tetris-menu-highscores").onclick = function()
 312     {
 313         helpwindow.close();
 314         document.getElementById("tetris-highscores-content").innerHTML = self.highscores.toHtml();
 315         highscores.activate();
 316         this.blur();
 317     };
 318     document.getElementById("tetris-highscores-close").onclick = highscores.close;
 319 
 320     // keyboard - buttons
 321     //document.getElementById("tetris-keyboard-up").onclick = function() { self.up(); this.blur(); };
 322     //document.getElementById("tetris-keyboard-down").onclick = function() { self.down(); this.blur(); };
 323     //document.getElementById("tetris-keyboard-left").onclick = function () { self.left(); this.blur(); };
 324     //document.getElementById("tetris-keyboard-right").onclick = function() { self.right(); this.blur(); };
 325 
 326     // keyboard
 327     var keyboard = new Keyboard();
 328     keyboard.set(keyboard.n, this.start);
 329     //keyboard.set(keyboard.r, this.reset);
 330     keyboard.set(keyboard.p, this.pause);
 331     keyboard.set(keyboard.up, this.up);
 332     keyboard.set(keyboard.down, this.down);
 333     keyboard.set(keyboard.left, this.left);
 334     keyboard.set(keyboard.right, this.right);
 335     keyboard.set(keyboard.space, this.space);
 336     document.onkeydown = keyboard.event;
 337 
 338     /**
 339      * Window replaces game area, for example help window
 340      * @param string id
 341      */
 342     function Window(id)
 343     {
 344         this.id = id;
 345         this.el = document.getElementById(this.id);
 346         var self = this;
 347 
 348         /**
 349          * Activate or deactivate a window - update html
 350          * @return void
 351          * @access event
 352          */
 353         this.activate = function()
 354         {
 355             self.el.style.display = (self.el.style.display == "block" ? "none" : "block");
 356         };
 357 
 358         /**
 359          * Close window - update html
 360          * @return void
 361          * @access event
 362          */
 363         this.close = function()
 364         {
 365             self.el.style.display = "none";
 366         };
 367 
 368         /**
 369          * @return bool
 370          * @access public
 371          */
 372         this.isActive = function()
 373         {
 374             return (self.el.style.display == "block");
 375         };
 376     }
 377 
 378     /**
 379      * Assigning functions to keyboard events
 380      * When key is pressed, searching in a table if any function has been assigned to this key, execute the function.
 381      */
 382     function Keyboard()
 383     {
 384         this.up = 38;
 385         this.down = 40;
 386         this.left = 37;
 387         this.right = 39;
 388         this.n = 78;
 389         this.p = 80;
 390         this.r = 82;
 391         this.space = 32;
 392         this.f12 = 123;
 393         this.escape = 27;
 394 
 395         this.keys = [];
 396         this.funcs = [];
 397 
 398         var self = this;
 399 
 400         /**
 401          * @param int key
 402          * @param function func
 403          * @return void
 404          * @access public
 405          */
 406         this.set = function(key, func)
 407         {
 408             this.keys.push(key);
 409             this.funcs.push(func);
 410         };
 411 
 412         /**
 413          * @param object e
 414          * @return void
 415          * @access event
 416          */
 417         this.event = function(e)
 418         {
 419             if (!e) { e = window.event; }
 420             for (var i = 0; i < self.keys.length; i++) {
 421                 if (e.keyCode == self.keys[i]) {
 422                     self.funcs[i]();
 423                 }
 424             }
 425         };
 426     }
 427 
 428     /**
 429      * Live game statistics
 430      * Updating html
 431      */
 432     function Stats()
 433     {
 434         this.level;
 435         this.time;
 436         this.apm;
 437         this.lines;
 438         this.score;
 439         this.puzzles; // number of puzzles created on current level
 440 
 441         this.actions;
 442 
 443         this.el = {
 444             "level": document.getElementById("tetris-stats-level"),
 445             "time":  document.getElementById("tetris-stats-time"),
 446             "apm":  document.getElementById("tetris-stats-apm"),
 447             "lines": document.getElementById("tetris-stats-lines"),
 448             "score": document.getElementById("tetris-stats-score")
 449         }
 450 
 451         this.timerId = null;
 452         var self = this;
 453 
 454         /**
 455          * Start counting statistics, reset stats, turn on the timer
 456          * @return void
 457          * @access public
 458          */
 459         this.start = function()
 460         {
 461             this.reset();
 462             this.timerId = setInterval(this.incTime, 1000);
 463         };
 464 
 465         /**
 466          * Stop counting statistics, turn off the timer
 467          * @return void
 468          * @access public
 469          */
 470         this.stop = function()
 471         {
 472             if (this.timerId) {
 473                 clearInterval(this.timerId);
 474             }
 475         };
 476 
 477         /**
 478          * Reset statistics - update html
 479          * @return void
 480          * @access public
 481          */
 482         this.reset = function()
 483         {
 484             this.stop();
 485             this.level = 1;
 486             this.time  = 0;
 487             this.apm   = 0;
 488             this.lines = 0;
 489             this.score = 0;
 490             this.puzzles = 0;
 491             this.actions = 0;
 492             this.el.level.innerHTML = this.level;
 493             this.el.time.innerHTML = this.time;
 494             this.el.apm.innerHTML = this.apm;
 495             this.el.lines.innerHTML = this.lines;
 496             this.el.score.innerHTML = this.score;
 497         };
 498 
 499         /**
 500          * Increase time, update apm - update html
 501          * This func is called by setInterval()
 502          * @return void
 503          * @access public event
 504          */
 505         this.incTime = function()
 506         {
 507             self.time++;
 508             self.el.time.innerHTML = self.time;
 509             self.apm = parseInt((self.actions / self.time) * 60);
 510             self.el.apm.innerHTML = self.apm;
 511         };
 512 
 513         /**
 514          * Set score - update html
 515          * @param int i
 516          * @return void
 517          * @access public
 518          */
 519         this.setScore = function(i)
 520         {
 521             this.score = i;
 522             this.el.score.innerHTML = this.score;
 523         };
 524 
 525         /**
 526          * Set level - update html
 527          * @param int i
 528          * @return void
 529          * @access public
 530          */
 531         this.setLevel = function(i)
 532         {
 533             this.level = i;
 534             this.el.level.innerHTML = this.level;
 535         };
 536 
 537         /**
 538          * Set lines - update html
 539          * @param int i
 540          * @return void
 541          * @access public
 542          */
 543         this.setLines = function(i)
 544         {
 545             this.lines = i;
 546             this.el.lines.innerHTML = this.lines;
 547         };
 548 
 549         /**
 550          * Number of puzzles created on current level
 551          * @param int i
 552          * @return void
 553          * @access public
 554          */
 555         this.setPuzzles = function(i)
 556         {
 557             this.puzzles = i;
 558         };
 559 
 560         /**
 561          * @param int i
 562          * @return void
 563          * @access public
 564          */
 565         this.setActions = function(i)
 566         {
 567             this.actions = i;
 568         };
 569 
 570         /**
 571          * @return int
 572          * @access public
 573          */
 574         this.getScore = function()
 575         {
 576             return this.score;
 577         };
 578 
 579         /**
 580          * @return int
 581          * @access public
 582          */
 583         this.getLevel = function()
 584         {
 585             return this.level;
 586         };
 587 
 588         /**
 589          * @return int
 590          * @access public
 591          */
 592         this.getLines = function()
 593         {

 594             return this.lines;
 595         };
 596 
 597         /**
 598          * Number of puzzles created on current level
 599          * @return int
 600          * @access public
 601          */
 602         this.getPuzzles = function()
 603         {
 604             return this.puzzles;
 605         };
 606 
 607         /**
 608          * @return int
 609          * @access public
 610          */
 611         this.getActions = function()
 612         {
 613             return this.actions;
 614         };
 615     }
 616 
 617     /**
 618      * Area consists of blocks (2 dimensional board).
 619      * Block contains "0" (if empty) or Html Object.
 620      * @param int x
 621      * @param int y
 622      * @param string id
 623      */
 624     function Area(unit, x, y, id)
 625     {
 626         this.unit = unit;
 627         this.x = x;
 628         this.y = y;
 629         this.el = document.getElementById(id);
 630 
 631         this.board = [];
 632 
 633         // create 2-dimensional board
 634         for (var y = 0; y < this.y; y++) {
 635             this.board.push(new Array());
 636             for (var x = 0; x < this.x; x++) {
 637                 this.board[y].push(0);
 638             }
 639         }
 640 
 641         /**
 642          * Removing html elements from area.
 643          * @return void
 644          * @access public
 645          */
 646         this.destroy = function()
 647         {
 648             for (var y = 0; y < this.board.length; y++) {
 649                 for (var x = 0; x < this.board[y].length; x++) {
 650                     if (this.board[y][x]) {
 651                         this.el.removeChild(this.board[y][x]);
 652                         this.board[y][x] = 0;
 653                     }
 654                 }
 655             }
 656         };
 657 
 658         /**
 659          * Searching for full lines.
 660          * Must go from the bottom of area to the top.
 661          * Returns the number of lines removed - needed for Stats.score.
 662          * @see isLineFull() removeLine()
 663          * @return void
 664          * @access public
 665          */
 666         this.removeFullLines = function()
 667         {
 668             var lines = 0;
 669             for (var y = this.y - 1; y > 0; y--) {
 670                 if (this.isLineFull(y)) {
 671                     this.removeLine(y);
 672                     lines++;
 673                     y++;
 674                 }
 675             }
 676             return lines;
 677         };
 678 
 679         /**
 680          * @param int y
 681          * @return bool
 682          * @access public
 683          */
 684         this.isLineFull = function(y)
 685         {
 686             for (var x = 0; x < this.x; x++) {
 687                 if (!this.board[y][x]) { return false; }
 688             }
 689             return true;
 690         };
 691 
 692         /**
 693          * Remove given line
 694          * Remove html objects
 695          * All lines that are above given line move down by 1 unit
 696          * @param int y
 697          * @return void
 698          * @access public
 699          */
 700         this.removeLine = function(y)
 701         {
 702             for (var x = 0; x < this.x; x++) {
 703                 this.el.removeChild(this.board[y][x]);
 704                 this.board[y][x] = 0;
 705             }
 706             y--;
 707             for (; y > 0; y--) {
 708                 for (var x = 0; x < this.x; x++) {
 709                     if (this.board[y][x]) {
 710                         var el = this.board[y][x];
 711                         el.style.top = el.offsetTop + this.unit + "px";
 712                         this.board[y+1][x] = el;
 713                         this.board[y][x] = 0;
 714                     }
 715                 }
 716             }
 717         };
 718 
 719         /**
 720          * @param int y
 721          * @param int x
 722          * @return mixed 0 or Html Object
 723          * @access public
 724          */
 725         this.getBlock = function(y, x)
 726         {
 727             if (y < 0) { return 0; }
 728             if (y < this.y && x < this.x) {
 729                 return this.board[y][x];
 730             } else {
 731                 throw "Area.getBlock("+y+", "+x+") failed";
 732             }
 733         };
 734 
 735         /**
 736          * Add Html Element to the area.
 737          * Find (x,y) position using offsetTop and offsetLeft
 738          * @param object el
 739          * @return void
 740          * @access public
 741          */
 742         this.addElement = function(el)
 743         {
 744             var x = parseInt(el.offsetLeft / this.unit);
 745             var y = parseInt(el.offsetTop / this.unit);
 746             if (y >= 0 && y < this.y && x >= 0 && x < this.x) {
 747                 this.board[y][x] = el;
 748             } else {
 749                 // not always an error ..
 750             }
 751         };
 752     }
 753 
 754     /**
 755      * Puzzle consists of blocks.
 756      * Each puzzle after rotating 4 times, returns to its primitive position.
 757      */
 758     function Puzzle(tetris, area)
 759     {
 760         var self = this;
 761         this.tetris = tetris;
 762         this.area = area;
 763 
 764         // timeout ids
 765         this.fallDownID = null;
 766         this.forceMoveDownID = null;
 767 
 768         this.type = null; // 0..6
 769         this.nextType = null; // next puzzle
 770         this.position = null; // 0..3
 771         this.speed = null;
 772         this.running = null;
 773         this.stopped = null;
 774 
 775         this.board = []; // filled with html elements after placing on area
 776         this.elements = [];
 777         this.nextElements = []; // next board elements
 778 
 779         // (x,y) position of the puzzle (top-left)
 780         this.x = null;
 781         this.y = null;
 782 
 783         // width & height must be the same
 784         this.puzzles = [
 785             [
 786                 [0,0,1],
 787                 [1,1,1],
 788                 [0,0,0]
 789             ],
 790             [
 791                 [1,0,0],
 792                 [1,1,1],
 793                 [0,0,0]
 794             ],
 795             [
 796                 [0,1,1],
 797                 [1,1,0],
 798                 [0,0,0]
 799             ],
 800             [
 801                 [1,1,0],
 802                 [0,1,1],
 803                 [0,0,0]
 804             ],
 805             [
 806                 [0,1,0],
 807                 [1,1,1],
 808                 [0,0,0]
 809             ],
 810             [
 811                 [1,1],
 812                 [1,1]
 813             ],
 814             [
 815                 [0,0,0,0],
 816                 [1,1,1,1],
 817                 [0,0,0,0],
 818                 [0,0,0,0]
 819             ]
 820         ];
 821 
 822         /**
 823          * Reset puzzle. It does not destroy html elements in this.board.
 824          * @return void
 825          * @access public
 826          */
 827         this.reset = function()
 828         {
 829             if (this.fallDownID) {
 830                 clearTimeout(this.fallDownID);
 831             }
 832             if (this.forceMoveDownID) {
 833                 clearTimeout(this.forceMoveDownID);
 834             }
 835             this.type = this.nextType;
 836             this.nextType = random(this.puzzles.length);
 837             this.position = 0;
 838             this.speed = 80 + (700 / this.tetris.stats.getLevel());
 839             this.running = false;
 840             this.stopped = false;
 841             this.board = [];
 842             this.elements = [];
 843             for (var i = 0; i < this.nextElements.length; i++) {
 844                 document.getElementById("tetris-nextpuzzle").removeChild(this.nextElements[i]);
 845             }
 846             this.nextElements = [];
 847             this.x = null;
 848             this.y = null;
 849         };
 850 
 851         this.nextType = random(this.puzzles.length);
 852         this.reset();
 853 
 854         /**
 855          * Check whether puzzle is running.
 856          * @return bool
 857          * @access public
 858          */
 859         this.isRunning = function()
 860         {
 861             return this.running;
 862         };
 863 
 864         /**
 865          * Check whether puzzle has been stopped by user. It happens when user clicks
 866          * "down" when puzzle is already at the bottom of area. The puzzle may still
 867          * be running with event fallDown(). When puzzle is stopped, no actions will be
 868          * performed when user press a key.
 869          * @return bool
 870          * @access public
 871          */
 872         this.isStopped = function()
 873         {
 874             return this.stopped;
 875         };
 876 
 877         /**
 878          * Get X position of puzzle (top-left)
 879          * @return int
 880          * @access public
 881          */
 882         this.getX = function()
 883         {
 884             return this.x;
 885         };
 886 
 887         /**
 888          * Get Y position of puzzle (top-left)
 889          * @return int
 890          * @access public
 891          */
 892         this.getY = function()
 893         {
 894             return this.y;
 895         };
 896 
 897         /**
 898          * Check whether new puzzle may be placed on the area.
 899          * Find (x,y) in area where beginning of the puzzle will be placed.
 900          * Check if first puzzle line (checking from the bottom) can be placed on the area.
 901          * @return bool
 902          * @access public
 903          */
 904         this.mayPlace = function()
 905         {
 906             var puzzle = this.puzzles[this.type];
 907             var areaStartX = parseInt((this.area.x - puzzle[0].length) / 2);
 908             var areaStartY = 1;
 909             var lineFound = false;
 910             var lines = 0;
 911             for (var y = puzzle.length - 1; y >= 0; y--) {
 912                 for (var x = 0; x < puzzle[y].length; x++) {
 913                     if (puzzle[y][x]) {
 914                         lineFound = true;
 915                         if (this.area.getBlock(areaStartY, areaStartX + x)) { return false; }
 916                     }
 917                 }
 918                 if (lineFound) {
 919                     lines++;
 920                 }
 921                 if (areaStartY - lines < 0) {
 922                     break;
 923                 }
 924             }
 925             return true;
 926         };
 927 
 928         /**
 929          * Create empty board, create blocks in area - html objects, update puzzle board.
 930          * Check puzzles on current level, increase level if needed.
 931          * @return void
 932          * @access public
 933          */
 934         this.place = function()
 935         {
 936             // stats
 937             this.tetris.stats.setPuzzles(this.tetris.stats.getPuzzles() + 1);
 938             if (this.tetris.stats.getPuzzles() >= (10 + this.tetris.stats.getLevel() * 2)) {
 939                 this.tetris.stats.setLevel(this.tetris.stats.getLevel() + 1);
 940                 this.tetris.stats.setPuzzles(0);
 941             }
 942             // init
 943             var puzzle = this.puzzles[this.type];
 944             var areaStartX = parseInt((this.area.x - puzzle[0].length) / 2);
 945             var areaStartY = 1;
 946             var lineFound = false;
 947             var lines = 0;
 948             this.x = areaStartX;
 949             this.y = 1;
 950             this.board = this.createEmptyPuzzle(puzzle.length, puzzle[0].length);
 951             // create puzzle
 952             for (var y = puzzle.length - 1; y >= 0; y--) {
 953                 for (var x = 0; x < puzzle[y].length; x++) {
 954                     if (puzzle[y][x]) {
 955                         lineFound = true;
 956                         var el = document.createElement("div");
 957                         el.className = "block" + this.type;
 958                         el.style.left = (areaStartX + x) * this.area.unit + "px";
 959                         el.style.top = (areaStartY - lines) * this.area.unit + "px";
 960                         this.area.el.appendChild(el);
 961                         this.board[y][x] = el;
 962                         this.elements.push(el);
 963                     }
 964                 }
 965                 if (lines) {
 966                     this.y--;
 967                 }
 968                 if (lineFound) {
 969                     lines++;
 970                 }
 971             }
 972             this.running = true;
 973             this.fallDownID = setTimeout(this.fallDown, this.speed);
 974             // next puzzle
 975             var nextPuzzle = this.puzzles[this.nextType];
 976             for (var y = 0; y < nextPuzzle.length; y++) {
 977                 for (var x = 0; x < nextPuzzle[y].length; x++) {
 978                     if (nextPuzzle[y][x]) {
 979                         var el = document.createElement("div");
 980                         el.className = "block" + this.nextType;
 981                         el.style.left = (x * this.area.unit) + "px";
 982                         el.style.top = (y * this.area.unit) + "px";
 983                         document.getElementById("tetris-nextpuzzle").appendChild(el);
 984                         this.nextElements.push(el);
 985                     }
 986                 }
 987             }
 988         };
 989 
 990         /**
 991          * Remove puzzle from the area.
 992          * Clean some other stuff, see reset()
 993          * @return void
 994          * @access public
 995          */
 996         this.destroy = function()
 997         {
 998             for (var i = 0; i < this.elements.length; i++) {
 999                 this.area.el.removeChild(this.elements[i]);
1000             }
1001             this.elements = [];
1002             this.board = [];
1003             this.reset();
1004         };
1005 
1006         /**
1007          * @param int y
1008          * @param int x
1009          * @return array
1010          * @access private
1011          */
1012         this.createEmptyPuzzle = function(y, x)
1013         {
1014             var puzzle = [];
1015             for (var y2 = 0; y2 < y; y2++) {
1016                 puzzle.push(new Array());
1017                 for (var x2 = 0; x2 < x; x2++) {
1018                     puzzle[y2].push(0);
1019                 }
1020             }
1021             return puzzle;
1022         };
1023 
1024         /**
1025          * Puzzle fall from the top to the bottom.
1026          * After placing a puzzle, this event will be called as long as the puzzle is running.
1027          * @see place() stop()
1028          * @return void
1029          * @access event
1030          */
1031         this.fallDown = function()
1032         {
1033             if (self.isRunning()) {
1034                 if (self.mayMoveDown()) {
1035                     self.moveDown();
1036                     self.fallDownID = setTimeout(self.fallDown, self.speed);
1037                 } else {
1038                     // move blocks into area board
1039                     for (var i = 0; i < self.elements.length; i++) {
1040                         self.area.addElement(self.elements[i]);
1041                     }
1042                     // stats
1043                     var lines = self.area.removeFullLines();
1044                     if (lines) {
1045                         self.tetris.stats.setLines(self.tetris.stats.getLines() + lines);
1046                         self.tetris.stats.setScore(self.tetris.stats.getScore() + (1000 * self.tetris.stats.getLevel() * lines));
1047                     }
1048                     // reset puzzle
1049                     self.reset();
1050                     if (self.mayPlace()) {
1051                         self.place();
1052                     } else {
1053                         self.tetris.gameOver();
1054                     }
1055                 }
1056             }
1057         };
1058 
1059         /**
1060          * After clicking "space" the puzzle is forced to move down, no user action is performed after
1061          * this event is called. this.running must be set to false. This func is similiar to fallDown()
1062          * Also update score & actions - like Tetris.down()
1063          * @see fallDown()
1064          * @return void
1065          * @access public event
1066          */
1067         this.forceMoveDown = function()
1068         {
1069             if (!self.isRunning() && !self.isStopped()) {
1070                 if (self.mayMoveDown()) {
1071                     // stats: score, actions
1072                     self.tetris.stats.setScore(self.tetris.stats.getScore() + 5 + self.tetris.stats.getLevel());
1073                     self.tetris.stats.setActions(self.tetris.stats.getActions() + 1);
1074                     self.moveDown();
1075                     self.forceMoveDownID = setTimeout(self.forceMoveDown, 30);
1076                 } else {
1077                     // move blocks into area board
1078                     for (var i = 0; i < self.elements.length; i++) {
1079                         self.area.addElement(self.elements[i]);
1080                     }
1081                     // stats: lines
1082                     var lines = self.area.removeFullLines();
1083                     if (lines) {
1084                         self.tetris.stats.setLines(self.tetris.stats.getLines() + lines);
1085                         self.tetris.stats.setScore(self.tetris.stats.getScore() + (1000 * self.tetris.stats.getLevel() * lines));
1086                     }
1087                     // reset puzzle
1088                     self.reset();
1089                     if (self.mayPlace()) {
1090                         self.place();
1091                     } else {
1092                         self.tetris.gameOver();
1093                     }
1094                 }
1095             }
1096         };
1097 
1098         /**
1099          * Stop the puzzle falling
1100          * @return void
1101          * @access public
1102          */
1103         this.stop = function()
1104         {
1105             this.running = false;
1106         };
1107 
1108         /**
1109          * Check whether puzzle may be rotated.
1110          * Check down, left, right, rotate
1111          * @return bool
1112          * @access public
1113          */
1114         this.mayRotate = function()
1115         {
1116             for (var y = 0; y < this.board.length; y++) {
1117                 for (var x = 0; x < this.board[y].length; x++) {
1118                     if (this.board[y][x]) {
1119                         var newY = this.getY() + this.board.length - 1 - x;
1120                         var newX = this.getX() + y;
1121                         if (newY >= this.area.y) { return false; }
1122                         if (newX < 0) { return false; }
1123                         if (newX >= this.area.x) { return false; }
1124                         if (this.area.getBlock(newY, newX)) { return false; }
1125                     }
1126                 }
1127             }
1128             return true;
1129         };
1130 
1131         /**
1132          * Rotate the puzzle to the left.
1133          * @return void
1134          * @access public
1135          */
1136         this.rotate = function()
1137         {
1138             var puzzle = this.createEmptyPuzzle(this.board.length, this.board[0].length);
1139             for (var y = 0; y < this.board.length; y++) {
1140                 for (var x = 0; x < this.board[y].length; x++) {
1141                     if (this.board[y][x]) {
1142                         var newY = puzzle.length - 1 - x;
1143                         var newX = y;
1144                         var el = this.board[y][x];
1145                         var moveY = newY - y;
1146                         var moveX = newX - x;
1147                         el.style.left = el.offsetLeft + (moveX * this.area.unit) + "px";
1148                         el.style.top = el.offsetTop + (moveY * this.area.unit) + "px";
1149                         puzzle[newY][newX] = el;
1150                     }
1151                 }
1152             }
1153             this.board = puzzle;
1154         };
1155 
1156         /**
1157          * Check whether puzzle may be moved down.
1158          * - is any other puzzle on the way ?
1159          * - is it end of the area ?
1160          * If false, then true is assigned to variable this.stopped - no user actions will be performed to this puzzle,
1161          * so this func should be used carefully, only in Tetris.down() and Tetris.puzzle.fallDown()
1162          * @return bool
1163          * @access public
1164          */
1165         this.mayMoveDown = function()
1166         {
1167             for (var y = 0; y < this.board.length; y++) {
1168                 for (var x = 0; x < this.board[y].length; x++) {
1169                     if (this.board[y][x]) {
1170                         if (this.getY() + y + 1 >= this.area.y) { this.stopped = true; return false; }
1171                         if (this.area.getBlock(this.getY() + y + 1, this.getX() + x)) { this.stopped = true; return false; }
1172                     }
1173                 }
1174             }
1175             return true;
1176         };
1177 
1178         /**
1179          * Move the puzzle down by 1 unit.
1180          * @return void
1181          * @access public
1182          */
1183         this.moveDown = function()
1184         {
1185             for (var i = 0; i < this.elements.length; i++) {
1186                 this.elements[i].style.top = this.elements[i].offsetTop + this.area.unit + "px";
1187             }
1188             this.y++;
1189         };
1190 
1191         /**
1192          * Check whether puzzle may be moved left.
1193          * - is any other puzzle on the way ?
1194          * - is the end of the area
1195          * @return bool
1196          * @access public
1197          */
1198         this.mayMoveLeft = function()
1199         {
1200             for (var y = 0; y < this.board.length; y++) {
1201                 for (var x = 0; x < this.board[y].length; x++) {
1202                     if (this.board[y][x]) {
1203                         if (this.getX() + x - 1 < 0) { return false; }
1204                         if (this.area.getBlock(this.getY() + y, this.getX() + x - 1)) { return false; }
1205                     }
1206                 }
1207             }
1208             return true;
1209         };
1210 
1211         /**
1212          * Move the puzzle left by 1 unit
1213          * @return void
1214          * @access public
1215          */
1216         this.moveLeft = function()
1217         {
1218             for (var i = 0; i < this.elements.length; i++) {
1219                 this.elements[i].style.left = this.elements[i].offsetLeft - this.area.unit + "px";
1220             }
1221             this.x--;
1222         };
1223 
1224         /**
1225          * Check whether puzle may be moved right.
1226          * - is any other puzzle on the way ?
1227          * - is the end of the area
1228          * @return bool
1229          * @access public
1230          */
1231         this.mayMoveRight = function()
1232         {
1233             for (var y = 0; y < this.board.length; y++) {
1234                 for (var x = 0; x < this.board[y].length; x++) {
1235                     if (this.board[y][x]) {
1236                         if (this.getX() + x + 1 >= this.area.x) { return false; }
1237                         if (this.area.getBlock(this.getY() + y, this.getX() + x + 1)) { return false; }
1238                     }
1239                 }
1240             }
1241             return true;
1242         };
1243 
1244         /**
1245          * Move the puzzle right by 1 unit.
1246          * @return void
1247          * @access public
1248          */
1249         this.moveRight = function()
1250         {
1251             for (var i = 0; i < this.elements.length; i++) {
1252                 this.elements[i].style.left = this.elements[i].offsetLeft + this.area.unit + "px";
1253             }
1254             this.x++;
1255         };
1256     }
1257 
1258     /**
1259      * Generates random number that is >= 0 and < i
1260      * @return int
1261      * @access private
1262      */
1263     function random(i)
1264     {
1265         return Math.floor(Math.random() * i);
1266     }
1267 
1268     /**
1269      * Store highscores in cookie.
1270      */
1271     function Highscores(maxscores)
1272     {
1273         this.maxscores = maxscores;
1274         this.scores = [];
1275 
1276         /**
1277          * Load scores from cookie.
1278          * Note: it is automatically called when creating new instance of object Highscores.
1279          * @return void
1280          * @access public
1281          */
1282         this.load = function()
1283         {
1284             var cookie = new Cookie();
1285             var s = cookie.get("tetris-highscores");
1286             this.scores = [];
1287             if (s.length) {
1288                 var scores = s.split("|");
1289                 for (var i = 0; i < scores.length; ++i) {
1290                     var a = scores[i].split(":");
1291                     this.scores.push(new Score(a[0], Number(a[1])));
1292                 }
1293             }
1294         };
1295 
1296         /**
1297          * Save scores to cookie.
1298          * Note: it is automatically called after adding new score.
1299          * @return void
1300          * @access public
1301          */
1302         this.save = function()
1303         {
1304             var cookie = new Cookie();
1305             var a = [];
1306             for (var i = 0; i < this.scores.length; ++i) {
1307                 a.push(this.scores[i].name+":"+this.scores[i].score);
1308             }
1309             var s = a.join("|");
1310             cookie.set("tetris-highscores", s, 3600*24*1000);
1311         };
1312 
1313         /**
1314          * Is the score high enough to be able to add ?
1315          * @return bool
1316          * @access public
1317          */
1318         this.mayAdd = function(score)
1319         {
1320             if (this.scores.length < this.maxscores) { return true; }
1321             for (var i = this.scores.length - 1; i >= 0; --i) {
1322                 if (this.scores[i].score < score) { return true; }
1323             }
1324             return false;
1325         };
1326 
1327         /**
1328          * @param string name
1329          * @param int score
1330          * @return void
1331          * @access public
1332          */
1333         this.add = function(name, score)
1334         {
1335             name = name.replace(/[;=:|]/g, "?");
1336             name = name.replace(/</g, "&lt;").replace(/>/g, "&gt;");
1337             if (this.scores.length < this.maxscores) {
1338                 this.scores.push(new Score(name, score));
1339             } else {
1340                 for (var i = this.scores.length - 1; i >= 0; --i) {
1341                     if (this.scores[i].score < score) {
1342                         this.scores.removeByIndex(i);
1343                         this.scores.push(new Score(name, score));
1344                         break;
1345                     }
1346                 }
1347             }
1348             this.sort();
1349             this.save();
1350         };
1351 
1352         /**
1353          * Get array of scores.
1354          * @return array [Score, Score, ..]
1355          * @access public
1356          */
1357         this.getScores = function()
1358         {
1359             return this.scores;
1360         };
1361 
1362         /**
1363          * All highscores returned in html friendly format.
1364          * @return string
1365          * @access public
1366          */
1367         this.toHtml = function()
1368         {
1369             var s = '<table cellspacing="0" cellpadding="2"><tr><th></th><th>Name</th><th>Score</th></tr>';
1370             for (var i = 0; i < this.scores.length; ++i) {
1371                 s += '<tr><td>?.</td><td>?</td><td>?</td></tr>'.format(i+1, this.scores[i].name, this.scores[i].score);
1372             }
1373             s += '</table>';
1374             return s;
1375         };
1376 
1377         /**
1378          * Sort table with scores.
1379          * @return void
1380          * @access private
1381          */
1382         this.sort = function()
1383         {
1384             var scores = this.scores;
1385             var len = scores.length;
1386             this.scores = [];
1387             for (var i = 0; i < len; ++i) {
1388                 var el = null, index = null;
1389                 for (var j = 0; j < scores.length; ++j) {
1390                     if (!el || (scores[j].score > el.score)) {
1391                         el = scores[j];
1392                         index = j;
1393                     }
1394                 }
1395                 scores.removeByIndex(index);
1396                 this.scores.push(el);
1397             }
1398         };
1399 
1400         /* Simple score object. */
1401         function Score(name, score)
1402         {
1403             this.name = name;
1404             this.score = score;
1405         }
1406 
1407         this.load();
1408     }
1409 
1410     /**
1411      * Managing cookies.
1412      */
1413     function Cookie()
1414     {
1415         /**
1416          * @param string name
1417          * @return string
1418          * @access public
1419          */
1420         this.get = function(name)
1421         {
1422             var cookies = document.cookie.split(";");
1423             for (var i = 0; i < cookies.length; ++i) {
1424                 var a = cookies[i].split("=");
1425                 if (a.length == 2) {
1426                     a[0] = a[0].trim();
1427                     a[1] = a[1].trim();
1428                     if (a[0] == name) {
1429                         return unescape(a[1]);
1430                     }
1431                 }
1432             }
1433             return "";
1434         };
1435 
1436         /**
1437          * @param string name
1438          * @param string value (do not use special chars like ";" "=")
1439          * @param int seconds
1440          * @param string path
1441          * @param string domain
1442          * @param bool secure
1443          * @return void
1444          * @access public
1445          */
1446         this.set = function(name, value, seconds, path, domain, secure)
1447         {
1448             this.del(name);
1449             if (!path) path = '/';
1450 
1451             var cookie = (name + "=" + escape(value));
1452             if (seconds) {
1453                 var date = new Date(new Date().getTime()+seconds*1000);
1454                 cookie += ("; expires="+date.toGMTString());
1455             }
1456             cookie += (path    ? "; path="+path : "");
1457             cookie += (domain  ? "; domain="+domain : "");
1458             cookie += (secure  ? "; secure" : "");
1459             document.cookie = cookie;
1460         };
1461 
1462         /**
1463          * @param name
1464          * @return void
1465          * @access public
1466          */
1467         this.del = function(name)
1468         {
1469             document.cookie = name + "=; expires=Thu, 01-Jan-70 00:00:01 GMT";
1470         };
1471     }
1472 }
1473 
1474 if (!String.prototype.trim) {
1475     String.prototype.trim = function() {
1476         return this.replace(/^\s*|\s*$/g, "");
1477     };
1478 }
1479 
1480 if (!Array.prototype.removeByIndex) {
1481     Array.prototype.removeByIndex = function(index) {
1482         this.splice(index, 1);
1483     };
1484 }
1485 
1486 if (!String.prototype.format) {
1487     String.prototype.format = function() {
1488         if (!arguments.length) { throw "String.format() failed, no arguments passed, this = "+this; }
1489         var tokens = this.split("?");
1490         if (arguments.length != (tokens.length - 1)) { throw "String.format() failed, tokens != arguments, this = "+this; }
1491         var s = tokens[0];
1492         for (var i = 0; i < arguments.length; ++i) {
1493             s += (arguments[i] + tokens[i + 1]);
1494         }
1495         return s;
1496     };
1497 }

 

posted @ 2013-04-25 22:50  诗意花  阅读(179)  评论(0)    收藏  举报