Advertisement
Guest User

Untitled

a guest
Sep 14th, 2017
329
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. "use strict";
  2.  
  3. // Global configuration
  4. var rows = 20;
  5. var cols = 10;
  6. var cellSize = 30;
  7. var interval = 400;
  8. var backgroundColour = "#f2f2f2";
  9.  
  10. /** "Enum" for representing colours. */
  11. var Colour = {Red: "red", Magenta: "magenta", Gold: "gold", Cyan: "cyan", Blue: "blue", Orange: "orange", Green: "green"};
  12.  
  13. /**
  14.  * Represents a position, i.e. a row-column pair.
  15.  * @constructor
  16.  */
  17. var Position = function (row, col) {
  18.     this.row = row;
  19.     this.col = col;
  20. };
  21.  
  22. Position.prototype.clone = function () {
  23.     return new Position(this.row,this.col);
  24. };
  25.  
  26. Position.prototype.add = function (other) {
  27.     return new Position(this.row + other.row,this.col + other.col);
  28. };
  29.  
  30. Position.prototype.moveLeft = function () {
  31.     return new Position(this.row,this.col - 1);
  32. };
  33.  
  34. Position.prototype.moveRight = function () {
  35.     return new Position(this.row,this.col + 1);
  36. };
  37.  
  38. Position.prototype.moveUp = function () {
  39.     return new Position(this.row-1,this.col);
  40. };
  41.  
  42. Position.prototype.moveDown = function () {
  43.     return new Position(this.row + 1,this.col);
  44. };
  45.  
  46. /**
  47.  * Returns an array of nulls.
  48.  * @param {Number} length - The length of the returned array.
  49.  */
  50. var nullArray = function (length) {
  51.     var output = [];
  52.     while (output.length < length) {
  53.         output.push(null);
  54.     }
  55.  
  56.     return output;
  57. };
  58.  
  59. /**
  60.  * Returns a grid of nulls.
  61.  * @param {Number} rows - The number of rows in the returned grid.
  62.  * @param {Number} cols - The number of cols in the returned grid.
  63.  */
  64. var nullGrid = function (rows, cols) {
  65.     var output = [];
  66.     while (output.length < rows) {
  67.         output.push(nullArray(cols));
  68.     }
  69.  
  70.     return output;
  71. };
  72.  
  73. /** Clones, i.e. performs a deep copy on, a grid. */
  74. var gridClone = function (grid) {
  75.     var copy = [];
  76.     for (var line of grid) copy.push(line.slice());
  77.     return copy;
  78. };
  79.  
  80. /**
  81.  * Returns an array of the Positions in a grid corresponding to the
  82.  * elements that are == true.
  83.  *
  84.  * @example truthyCoords([[0, 1, 0],
  85.  *                        [0, 1, 0],
  86.  *                        [1, 1, 0]])
  87.  *              = [ Position { row: 0, col: 1 },
  88.  *                  Position { row: 1, col: 1 },
  89.  *                  Position { row: 2, col: 0 },
  90.  *                  Position { row: 2, col: 1 } ]
  91.  */
  92. var truthyCoords = function (grid) {
  93.     var row, col, output = [];
  94.     for (row=0; row < grid.length; row++){
  95.         for (col = 0; col<grid[row].length; col++){
  96.             if (grid[row][col]){
  97.                 output.push(new position(row,col));
  98.             }
  99.         }
  100.     }
  101.     return output;
  102. };
  103.  
  104. /**
  105.  * Rotates a grid clockwise.
  106.  *
  107.  * @example rotateCW([[0, 1, 0],
  108.  *                    [0, 1, 0],
  109.  *                    [1, 1, 0]])
  110.  *              = [[ 1, 0, 0 ],
  111.  *                 [ 1, 1, 1 ],
  112.  *                 [ 0, 0, 0 ]]
  113.  */
  114. var rotateCW = function (grid) {
  115.     var output = [];
  116.     for(var i = 0; i < array.length; i++){
  117.         output.push([]);
  118.     };
  119.  
  120.     for(var i = 0; i < array.length; i++){
  121.         for(var j = 0; j < arrayLength; j++){
  122.             output[j].push(array[i][j]);
  123.         };
  124.     };
  125.  
  126.     return output;
  127. };
  128.  
  129. /**
  130.  * Returns the smallest column values out of all the truthy elements
  131.  * in a grid.
  132.  *
  133.  * @example leftMostTruthyCol([[0, 0, 1, 0],
  134.  *                             [0, 0, 1, 0],
  135.  *                             [0, 0, 1, 0],
  136.  *                             [0, 0, 1, 0]]) = 2
  137.  */
  138. var leftMostTruthyCol = function (grid) {
  139.     var i, smallestCol = null;
  140.     var coords = truthyCoords(grid);
  141.     for (i=0; i<coords.length; i++){
  142.         if smallestCol===null || smallestCol > coords[i].col){
  143.             smallestCol = coords[i].col;
  144.         }
  145.     }
  146.     return smallestCol
  147. };
  148.  
  149. /**
  150.  * Returns the larget column values out of all the truthy elements
  151.  * in a grid.
  152.  *
  153.  * @example rightMostTruthyCol([[0, 1, 0],
  154.  *                              [0, 1, 0],
  155.  *                              [1, 1, 0]]) = 1
  156.  */
  157. var rightMostTruthyCol = function (grid) {
  158.     var i, largestCol = null;
  159.     var coords = truthyCoords(grid);
  160.     for (i=coords.length; i>0; i--){
  161.         if largestCol===null || largestCol < coords[i].col){
  162.             largestCol = coords[i].col;
  163.         }
  164.     }
  165.     return largestCol
  166. };
  167.  
  168. /**
  169.  * Returns the larget row values out of all the truthy elements
  170.  * in a grid.
  171.  *
  172.  * @example bottomMostTruthyCol([[0, 1, 1],
  173.  *                               [1, 1, 0],
  174.  *                               [0, 0, 0]] = 1
  175.  */
  176. var bottomMostTruthyRow = function (grid) {
  177.     var i, smallestCol = null;
  178.     var coords = truthyCoords(grid);
  179.     for (i=0; i<coords.length; i++){
  180.         if smallestCol===null || smallestCol > coords[i].col){
  181.             smallestCol = coords[i].col;
  182.         }
  183.     }
  184.     return smallestCol
  185. };
  186.  
  187. /**
  188.  * Returns the smallest row values out of all the truthy elements
  189.  * in a grid.
  190.  *
  191.  * @example topMostTruthyCol([[0, 0, 0],
  192.  *                            [1, 1, 1],
  193.  *                            [0, 1, 0]] = 1
  194.  */
  195. var topMostTruthyRow = function (grid) {
  196.     // TODO
  197. };
  198.  
  199. /**
  200.  * Represents a tetromino.
  201.  * @constructor
  202.  * @param {Grid} shape - A grid of ones and zeroes, where the ones represent
  203.  *                where the blocks of the tetromino are.
  204.  * @param {Colour} colour - The colour of the tetromino.
  205.  */
  206. var Tetromino = function (shape, colour) {
  207.     this.shape = shape;
  208.     this.colour = colour;
  209. };
  210.  
  211. Tetromino.prototype.clone = function () {
  212.     return new Tetromino(gridClone(this.shape), this.colour);
  213. };
  214.  
  215. Tetromino.prototype.rotateCW = function () {
  216.     return new Tetromino(rotateCW(this.shape), this.colour);
  217. };
  218.  
  219. // The tetrominos.
  220. var I = new Tetromino(
  221.     [   [0, 0, 1, 0],
  222.         [0, 0, 1, 0],
  223.         [0, 0, 1, 0],
  224.         [0, 0, 1, 0]],
  225.     Colour.Red);
  226.  
  227. var J = new Tetromino(
  228.     [   [0, 1, 0],
  229.         [0, 1, 0],
  230.         [1, 1, 0]],
  231.     Colour.Magenta);
  232.  
  233. var L = new Tetromino(
  234.     [   [0, 1, 0],
  235.         [0, 1, 0],
  236.         [0, 1, 1]],
  237.     Colour.Gold);
  238.  
  239. var O = new Tetromino(
  240.     [   [1, 1],
  241.         [1, 1]],
  242.     Colour.Cyan);
  243.  
  244. var S = new Tetromino(
  245.     [   [0, 1, 1],
  246.         [1, 1, 0],
  247.         [0, 0, 0]],
  248.     Colour.Blue);
  249.  
  250. var T = new Tetromino(
  251.     [   [0, 0, 0],
  252.         [1, 1, 1],
  253.         [0, 1, 0]],
  254.     Colour.Orange);
  255.  
  256. var Z = new Tetromino(
  257.     [   [1, 1, 0],
  258.         [0, 1, 1],
  259.         [0, 0, 0]],
  260.     Colour.Green);
  261.  
  262. /**
  263.  * Represents the active tetromino on our game.
  264.  *
  265.  * The active tetromino is the one that is currently under the control
  266.  * of the player.
  267.  * @constructor
  268.  * @param {Tetromino} tetromino - The tetromino.
  269.  * @param {Position} topLeft - The position of the top left cell of
  270.  *                             the tetromino.
  271.  */
  272. var ActiveTetromino = function (tetromino, topLeft) {
  273.     this.tetromino = tetromino;
  274.     this.topLeft = topLeft;
  275. };
  276.  
  277. ActiveTetromino.prototype.clone = function () {
  278.     return new ActiveTetromino(this.tetromino.clone(), this.topLeft.clone());
  279. };
  280.  
  281. ActiveTetromino.prototype.moveLeft = function () {
  282.     return new ActiveTetromino(this.tetromino, this.topLeft.moveLeft());
  283. };
  284.  
  285. ActiveTetromino.prototype.moveRight = function () {
  286.     return new ActiveTetromino(this.tetromino, this.topLeft.moveRight());
  287. };
  288.  
  289. ActiveTetromino.prototype.moveUp = function () {
  290.     return new ActiveTetromino(this.tetromino, this.topLeft.moveUp());
  291. };
  292.  
  293. ActiveTetromino.prototype.moveDown = function () {
  294.     return new ActiveTetromino(this.tetromino, this.topLeft.moveDown());
  295. };
  296.  
  297. ActiveTetromino.prototype.rotateCW = function () {
  298.     return new ActiveTetromino(this.tetromino.rotateCW(), this.topLeft);
  299. };
  300.  
  301. /**
  302.  * Returns whether or not the active tetromino is fully visible to the
  303.  * player.
  304.  *
  305.  * Note that the active tetromino starts with a negative top row, so
  306.  * that it is initial totally hidden from the player.
  307.  */
  308. ActiveTetromino.prototype.isFullyVisible = function () {
  309.     // TODO
  310. };
  311.  
  312. /**
  313.  * Represents a model of the entire state of our game.
  314.  * @constructor
  315.  * @param {Grid} grid - Represents the "background" grid of the game.
  316.  *                      This is where the tetrominos go when they
  317.  *                      become inactive. This values in the grid are
  318.  *                      either a Colour or null.
  319.  * @param {ActiveTetromino} active - The current active tetromino.
  320.  *                                   Note that this can be null,
  321.  *                                   meaning that there is currently
  322.  *                                   no active tetromino.
  323.  * @param {Bool} gameOver - Is the game over?
  324.  */
  325. var Model = function (grid, active, gameOver) {
  326.     this.grid = grid;
  327.     this.active = active;
  328.     this.gameOver = gameOver;
  329. };
  330.  
  331. Model.prototype.clone = function () {
  332.     if (this.active === null) {
  333.         return new Model(gridClone(this.grid), null, this.gameOver);
  334.     } else {
  335.         return new Model(gridClone(this.grid), this.active.clone(), this.gameOver);
  336.     }
  337. };
  338.  
  339. /**
  340.  * Returns the difference between two models.
  341.  *
  342.  * This is used in the rendering code so that we don't waste resources
  343.  * updating everything, but only what has changed.
  344.  */
  345. Model.prototype.diff = function (oldModel) {
  346.     var row, col;
  347.     var old = oldModel.clone();
  348.     var current = this.clone();
  349.     old.activeToGrid();
  350.     current.activeToGrid();
  351.     var diff = {
  352.         gameOver: current.gameOver != old.gameOver,
  353.         grid: []
  354.     };
  355.  
  356.     for (row = 0; row < rows; row++) {
  357.         for (col = 0; col < cols; col++) {
  358.             if (current.grid[row][col] !== old.grid[row][col]) {
  359.                 diff.grid.push({
  360.                     position: new Position(row, col),
  361.                     value: current.grid[row][col]
  362.                 });
  363.             }
  364.         }
  365.     }
  366.  
  367.     return diff;
  368. };
  369.  
  370. Model.prototype.moveLeft = function () {
  371.     return new Model(this.grid, this.active.moveLeft(), this.gameOver);
  372. };
  373.  
  374. Model.prototype.moveRight = function () {
  375.     return new Model(this.grid, this.active.moveRight(), this.gameOver);
  376. };
  377.  
  378. Model.prototype.moveUp = function () {
  379.     return new Model(this.grid, this.active.moveUp(), this.gameOver);
  380. };
  381.  
  382. Model.prototype.moveDown = function () {
  383.     return new Model(this.grid, this.active.moveDown(), this.gameOver);
  384. };
  385.  
  386. Model.prototype.rotateCW = function () {
  387.     return new Model(this.grid, this.active.rotateCW(), this.gameOver);
  388. };
  389.  
  390. /**
  391.  * Returns whether the active tetromino collides/overlaps with any
  392.  * non-null values in grid, or whether the active tetromino has cells
  393.  * that appear outside of the grid.
  394.  */
  395. Model.prototype.hasCollisions = function () {
  396.     // TODO
  397. };
  398.  
  399. /**
  400.  * Moves the active tetromino to the "background" grid.
  401.  *
  402.  * If this.active === null then does nothing.
  403.  */
  404. Model.prototype.activeToGrid = function () {
  405.     // TODO
  406. };
  407.  
  408. /**
  409.  * Removes complete rows from the "background" grid.
  410.  *
  411.  * Complete lines are rows which contain no nulls. Rows above the
  412.  * removed rows are moved down.
  413.  */
  414. Model.prototype.clearLines = function () {
  415.     // TODO
  416. };
  417.  
  418. /** An array of all the posible active tetrominos we can generate. */
  419. var tetrominoChoices = (function () {
  420.     var i, j, tetrominos, rotatedTetrominos, tetromino, top, minLeft, maxLeft, left, choices;
  421.     tetrominos = [I, J, L, O, S, T, Z];
  422.  
  423.     // Collect not just the origianal tetrominos, but rotated copies of them.
  424.     rotatedTetrominos = [];
  425.     for (i = 0; i < tetrominos.length; i++) {
  426.         tetromino = tetrominos[i];
  427.         for (j = 0; j < 4; j++) {
  428.             rotatedTetrominos.push(tetromino);
  429.             tetromino = tetromino.rotateCW();
  430.         }
  431.     }
  432.  
  433.     // Collect all the choices by examing where we can place these
  434.     // tetrominos.
  435.     choices = [];
  436.     for (i = 0; i < rotatedTetrominos.length; i++) {
  437.         tetromino = rotatedTetrominos[i];
  438.         top = -(bottomMostTruthyRow(tetromino.shape) + 1);
  439.         minLeft = -leftMostTruthyCol(tetromino.shape);
  440.         maxLeft = cols - (rightMostTruthyCol(tetromino.shape) + 1);
  441.         for (left = minLeft; left <= maxLeft; left++) {
  442.             choices.push(new ActiveTetromino(tetromino, new Position(top, left)));
  443.         }
  444.     }
  445.  
  446.     return choices;
  447. })();
  448.  
  449. /**
  450.  * "Enum" of actions we can perform on the model.
  451.  *
  452.  * Note that the Tick action gets called every `interval` milliseconds.
  453.  */
  454. var Action = {
  455.     Tick: "tick",
  456.     GenTetromino: "genTetromino",
  457.     Down: "down",
  458.     Left: "left",
  459.     Right: "right",
  460.     Rotate: "rotate"
  461. };
  462.  
  463. /** Update the model after performing an Action upon it. */
  464. Model.prototype.update = function (action) {
  465.     var next;
  466.     if (this.gameOver) return;
  467.  
  468.     if (action === Action.Tick) {
  469.         this.update(Action.Down);
  470.         this.update(Action.GenTetromino);
  471.     } else if (action === Action.GenTetromino) {
  472.         if (this.active === null) {
  473.             this.active = tetrominoChoices[Math.floor(Math.random()*tetrominoChoices.length)];
  474.         }
  475.     } else if (action === Action.Down) {
  476.         // TODO If this.active is not null attempt to move it down. If
  477.         // it cannot be moved down, and it is currently not fully
  478.         // visible then the game is over. If it is fully visible, but
  479.         // cannot be moved down, then move the active to the grid, and
  480.         // clear lines.
  481.    
  482.     } else if (action === Action.Left) {
  483.         if (this.active !==null){
  484.             next= this.moveLeft();
  485.             if (!next.hasColisions) {
  486.                 this.active = this.active.moveLeft();
  487.             }
  488.         }
  489.    
  490.     } else if (action === Action.Right) {
  491.             if (this.active !==null){
  492.             next= this.moveRight();
  493.             if (!next.hasColisions) {
  494.                 this.active = this.active.moveRight();
  495.             }
  496.         }
  497.    
  498.     } else if (action === Action.Rotate) {
  499.         if (this.active !==null){
  500.             next= this.rotateCW();
  501.             if (!next.hasColisions) {
  502.                 this.active = this.active.rotateCW();
  503.             }
  504.        // TODO If this.active is not null attempt to rotate it
  505.         // clockwise. If this fails, then try wall kicks, i.e.
  506.         // try to rotate it AND move it one left, also try to rotate it
  507.         // AND move it one right.
  508.     }
  509. };
  510.  
  511. /** The entry-point for our Tetris app. */
  512. // eslint-disable-next-line no-unused-vars
  513. var tetrisMain = function () {
  514.     var model;
  515.  
  516.     // Initialize the model.
  517.     model = new Model(nullGrid(rows, cols), null, false);
  518.     model.update(Action.GenTetromino);
  519.  
  520.     // Function that takes a model and renders it to the screen.
  521.     // Initialises the DOM within a lexical closure. Keeps a copy of
  522.     // the previous model, and uses Model.prototype.diff, so that we
  523.     // only change what needs to be changed in the DOM.
  524.     var render = (function () {
  525.         var row, col;
  526.         var $gameOver, $grid, $cell, $cells, $tetrisapp;
  527.         var oldModel = model.clone();
  528.  
  529.         $gameOver = $("<div></div>", {id: "gameover"});
  530.         $gameOver.html("GAME OVER");
  531.         $gameOver.css("fontFamily", "Arial, sans-serif");
  532.         $gameOver.css("textAlign", "center");
  533.         $gameOver.css("fontSize", "3em");
  534.         $gameOver.css("fontWeight", "bold");
  535.         $gameOver.css("color", "crimson");
  536.         $gameOver.css("width", (cols*cellSize) + "px");
  537.         $gameOver.css("position", "absolute");
  538.         $gameOver.css("left", "0");
  539.         $gameOver.css("top", (0.4*rows*cellSize) + "px");
  540.         $gameOver.css("display", "none");
  541.  
  542.         $grid = $("<div></div>", {id: "grid"});
  543.         $grid.css("width", (cols*cellSize) + "px");
  544.         $grid.css("height", (rows*cellSize) + "px");
  545.         $cells = nullGrid(rows, cols);
  546.         for (row = 0; row < rows; row++) {
  547.             for (col = 0; col < cols; col++) {
  548.                 $cell = $("<div></div>");
  549.                 $cell.css("backgroundColor", backgroundColour);
  550.                 $cell.css("width", cellSize + "px");
  551.                 $cell.css("height", cellSize + "px");
  552.                 $cell.css("float", "left");
  553.                 $grid.append($cell);
  554.                 $cells[row][col] = $cell;
  555.             }
  556.         }
  557.  
  558.         // Add it to #tetrisapp
  559.         $tetrisapp = $("#tetrisapp");
  560.         $tetrisapp.css("position", "relative");
  561.         $tetrisapp.css("margin", "20px auto");
  562.         $tetrisapp.css("width", (cols*cellSize) + "px");
  563.         $tetrisapp.append($grid);
  564.         $tetrisapp.append($gameOver);
  565.  
  566.         return function (model) {
  567.             var i, row, col, colour;
  568.             var currentModel = model.clone();
  569.             var diff = currentModel.diff(oldModel);
  570.             for (i = 0; i < diff.grid.length; i++) {
  571.                 row = diff.grid[i].position.row;
  572.                 col = diff.grid[i].position.col;
  573.                 if (diff.grid[i].value === null) {
  574.                     colour = backgroundColour;
  575.                 } else {
  576.                     colour = diff.grid[i].value;
  577.                 }
  578.  
  579.                 $cells[row][col].css("backgroundColor", colour);
  580.             }
  581.  
  582.             if (diff.gameOver) {
  583.                 $grid.css("opacity", "0.15");
  584.                 $gameOver.css("display", "block");
  585.             }
  586.  
  587.             oldModel = currentModel;
  588.         };
  589.     })();
  590.  
  591.     // This is the actual "runtime" code.
  592.     var onTimer = function () {
  593.         model.update(Action.Tick);
  594.         if (!model.gameOver) {
  595.             setTimeout(onTimer, interval);
  596.         }
  597.         render(model);
  598.     };
  599.  
  600.     setTimeout(onTimer, interval);
  601.  
  602.     $(document).on("keydown", function (e) {
  603.         if (!model.gameOver) {
  604.             if (e.which === 65) { // "a"
  605.                 model.update(Action.Left);
  606.                 render(model);
  607.             } else if (e.which === 83) { // "s"
  608.                 model.update(Action.Down);
  609.                 render(model);
  610.             } else if (e.which === 68) { // "d"
  611.                 model.update(Action.Right);
  612.                 render(model);
  613.             } else if (e.which === 32) { // " "
  614.                 model.update(Action.Rotate);
  615.                 render(model);
  616.             }
  617.         }
  618.     });
  619. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement