var utils = require('./utils.js'); var consts = require('./consts.js'); var shapes = require('./shapes.js'); var views = require('./views.js'); var canvas = require('./canvas.js'); var inputs = require('./input.js'); /** Init game matrix */ var initMatrix = function(rowCount, columnCount) { var result = []; for (var i = 0; i < rowCount; i++) { var row = []; result.push(row); for (var j = 0; j < columnCount; j++) { row.push(0); } } return result; }; /** Clear game matrix */ var clearMatrix = function(matrix) { for (var i = 0; i < matrix.length; i++) { for (var j = 0; j < matrix[i].length; j++) { matrix[i][j] = 0; } } }; /** Check all full rows in game matrix return rows number array. eg: [18,19]; */ var checkFullRows = function(matrix) { var rowNumbers = []; for (var i = 0; i < matrix.length; i++) { var row = matrix[i]; var full = true; for (var j = 0; j < row.length; j++) { full = full && row[j] !== 0; } if (full) { rowNumbers.push(i); } } return rowNumbers; }; /** Remove one row from game matrix. copy each previous row data to next row which row number less than row; */ var removeOneRow = function(matrix, row) { var colCount = matrix[0].length; for (var i = row; i >= 0; i--) { for (var j = 0; j < colCount; j++) { if (i > 0) { matrix[i][j] = matrix[i - 1][j]; } else { matrix[i][j] = 0; } } } }; /** Remove rows from game matrix by row numbers. */ var removeRows = function(matrix, rows) { for (var i in rows) { removeOneRow(matrix, rows[i]); } }; /** Check game data to determin wether the game is over */ var checkGameOver = function(matrix) { var firstRow = matrix[0]; for (var i = 0; i < firstRow.length; i++) { if (firstRow[i] !== 0) { return true; }; } return false; }; /** Calculate the extra rewards add to the score */ var calcRewards = function(rows) { if (rows && rows.length > 1) { return Math.pow(2, rows.length - 1) * 100; } return 0; }; /** Calculate game score */ var calcScore = function(rows) { if (rows && rows.length) { return rows.length * 100; } return 0; }; /** Calculate time interval by level, the higher the level,the faster shape moves */ var calcIntervalByLevel = function(level) { return consts.DEFAULT_INTERVAL - (level - 1) * 60; }; // Default max scene size var defaults = { maxHeight: 700, maxWidth: 600 }; /** Tetris main object definition */ function Tetris(id) { this.id = id; this.init(); } Tetris.prototype = { init: function(options) { var cfg = this.config = utils.extend(options, defaults); this.interval = consts.DEFAULT_INTERVAL; views.init(this.id, cfg.maxWidth, cfg.maxHeight); canvas.init(views.scene, views.preview, views.hold); inputs.init(); this.matrix = initMatrix(consts.ROW_COUNT, consts.COLUMN_COUNT); this.reset(); this._initEvents(); this._fireShape(); }, //Reset game reset: function() { this.running = false; this.isGameOver = false; this.level = 1; this.score = 0; this.lines = 0; this.startTime = new Date().getTime(); this.currentTime = this.startTime; this.prevTime = this.startTime; this.levelTime = this.startTime; this.shapeQueue = []; this.holdQueue = []; this.canPullFromHoldQueue = false; clearMatrix(this.matrix); views.setLevel(this.level); views.setScore(this.score); views.setGameOver(this.isGameOver); this._draw(); }, //Start game start: function() { this.running = true; window.requestAnimationFrame(utils.proxy(this._refresh, this)); }, //Pause game pause: function() { this.running = false; this.currentTime = new Date().getTime(); this.prevTime = this.currentTime; }, pushHoldStack: function() { if(this.holdQueue.length <= 4) { this.holdQueue.push(this.shape); this.shape = this.shapeQueue.shift(); this.canPullFromHoldQueue = false; this.shape.resetOrigin(); //canvas.drawHoldShape(this.holdQueue); this._draw(); // update? } }, popHoldStack: function() { if(this.holdQueue.length >= 1 && this.canPullFromHoldQueue) { this.canPullFromHoldQueue = false; this.shapeQueue.unshift(this.shape); this.shape = this.holdQueue.pop(); this.shape.resetOrigin(); //canvas.drawHoldShape(this.holdQueue); this._draw(); } }, //Game over gamveOver: function() { }, // All key event handlers _keydownHandler: function(e) { var matrix = this.matrix; /* if (!e) { var e = window.event; } if (this.isGameOver || !this.shape) { return; } switch (e.keyCode) { case 37: { //this.shape.goLeft(matrix); this._draw(); } break; case 39: { //this.shape.goRight(matrix); this._draw(); } break; case 90: { this.shape.rotate(matrix); this._draw(); } break; case 88: { this.shape.rotateClockwise(matrix); this._draw(); } break; case 40: { this.shape.goDown(matrix); this._draw(); } break; case 32: { this.shape.goBottom(matrix); this._update(); } break; } */ }, // Restart game _restartHandler: function() { this.reset(); this.start(); }, // Bind game events _initEvents: function() { window.addEventListener('keydown', utils.proxy(this._keydownHandler, this), false); views.btnRestart.addEventListener('click', utils.proxy(this._restartHandler, this), false); }, // Fire a new random shape _fireShape: function() { this.shape = this.shapeQueue.shift() || shapes.randomShape(); while( this.shapeQueue.length < 4 ) { this.preparedShape = shapes.randomShape(); this.shapeQueue.push(this.preparedShape); } //canvas.drawPreviewShape(this.shapeQueue); this._draw(); }, // Draw game data _draw: function() { canvas.drawScene(); canvas.drawShape(this.shape); canvas.drawHoldShape(this.holdQueue); canvas.drawPreviewShape(this.shapeQueue); if(this.shape != undefined) { let clone = Object.assign(Object.create(Object.getPrototypeOf(this.shape)), this.shape); var bottomY = clone.bottomAt(this.matrix); clone.color = "#ffffff"; canvas.drawGhostShape(clone, bottomY); } canvas.drawMatrix(this.matrix); }, // Refresh game canvas _refresh: function() { if (!this.running) { return; } this.currentTime = new Date().getTime(); var deltaTime = this.currentTime - this.prevTime; /* if(deltaTime >= 10) { inputs.incFrame(); } */ if(deltaTime >= 1) { // 600hz inputs.incDeciframes(); } if(deltaTime >= 1) { inputs.updateGamepad(); //inputs.processButtons(); inputs.processGamepadInput(); } // drain gamepad queue if(deltaTime > 5) { while((inputs.gamepadQueue != undefined && inputs.gamepadQueue.length >= 1)){ var curkey = inputs.gamepadQueue.shift(); if(curkey == "DPad-Left") { this.shape.goLeft(this.matrix); this._draw(); } if(curkey == "DPad-Right") { this.shape.goRight(this.matrix); this._draw(); } if(curkey == "A") { this.shape.rotate(this.matrix); this._draw(); } if(curkey == "B") { this.shape.rotateClockwise(this.matrix);; this._draw(); } if(curkey == "DPad-Down") { this.shape.goDown(this.matrix); this._draw(); } if(curkey == "RB") { this.shape.goBottom(this.matrix); this._update(); } if(curkey == "LB") { this.pushHoldStack(); this._update(); } if(curkey == "DPad-Up") { this.popHoldStack(); this._update(); } } inputs.gamepadQueue = []; } //inputs.gamepadButtonClear(); // Do keyboard if(deltaTime > 1) // 120hz { inputs.processKeys(); } if (deltaTime > 5) { // 60hz inputs.processKeyShift(); // Keyboard inputs while((inputs.inputqueue != undefined && inputs.inputqueue.length >= 1)){ var curkey = inputs.inputqueue.shift(); if(curkey == 37) { this.shape.goLeft(this.matrix); this._draw(); } if(curkey == 39){ this.shape.goRight(this.matrix); this._draw(); } if(curkey == 40) { this.shape.goDown(this.matrix); this._draw(); } if(curkey == 90) { this.shape.rotate(this.matrix); this._draw(); } if(curkey == 88){ this.shape.rotateClockwise(this.matrix);; this._draw(); } if(curkey == 32) { this.shape.goBottom(this.matrix); this._update(); } if(curkey == 16) { this.pullHoldQueue(); //this._update(); } if(curkey == 16) { this.pushHoldQueue(); //this._update(); } } inputs.inputqueue = []; } if(deltaTime >= 10) inputs.saveKeyboardKeys(); if(deltaTime >= 1) inputs.saveButtons(); if (deltaTime > this.interval) { this._update(); this.prevTime = this.currentTime; this._checkLevel(); } if (!this.isGameOver) { window.requestAnimationFrame(utils.proxy(this._refresh, this)); } }, // Update game data _update: function() { if (this.shape.canDown(this.matrix)) { this.shape.goDown(this.matrix); } else { this.canPullFromHoldQueue = true; this.shape.copyTo(this.matrix); this._check(); this._fireShape(); new Audio('./dist/Blop.ogg').play(); } this._draw(); this.isGameOver = checkGameOver(this.matrix); views.setGameOver(this.isGameOver); if (this.isGameOver) { views.setFinalScore(this.score); } }, // Check and update game data _check: function() { var rows = checkFullRows(this.matrix); if (rows.length) { if(rows.length >= 4) new Audio('./dist/Tetris.ogg').play(); removeRows(this.matrix, rows); var score = calcScore(rows); var reward = calcRewards(rows); this.score += score + reward; this.lines += rows.length; views.setScore(this.score); views.setReward(reward); views.setLines(this.lines); } }, // Check and update game level _checkLevel: function() { var currentTime = new Date().getTime(); if (currentTime - this.levelTime > consts.LEVEL_INTERVAL) { this.level += 1; this.interval = calcIntervalByLevel(this.level); views.setLevel(this.level); this.levelTime = currentTime; } } } window.Tetris = Tetris;