"fire-base tetris demo"
Bootstrap 3.3.0 Snippet by rayrc

<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css"> <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/js/bootstrap.min.js"></script> <script src="//code.jquery.com/jquery-1.11.1.min.js"></script> <!------ Include the above in your HEAD tag ----------> <html> <head><base href="https://www.firebase.com/tutorial/#session/xuu8u3i0qru" /> <script src="https://cdn.firebase.com/js/client/2.0.4/firebase.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script> <link rel="stylesheet" type="text/css" href="/resources/tutorial/css/example.css"> </head> <body class="tetris-body example-base"> <div> <canvas id="canvas0" width="250" height="500"></canvas> <canvas id="canvas1" width="250" height="500"></canvas> </div> <input type="button" id="restartButton" value="Restart Game" class="hide" /> <div id="gameInProgress">Game is in progress. You will automatically join if either player leaves.</div> <script> var canvas = $("#canvas0").get(0); if (!canvas || !canvas.getContext || !canvas.getContext('2d')) alert("You must use a browser that supports HTML5 Canvas to run this demo."); function start() { var tetrisRef = new Firebase('https://xuu8u3i0qru.firebaseio-demo.com/'); var tetrisController = new Tetris.Controller(tetrisRef); } var Tetris = { }; /** * Various constants related to board size / drawing. */ Tetris.BOARD_WIDTH = 10; // (in "blocks", not pixels) Tetris.BOARD_HEIGHT = 20; Tetris.BLOCK_SIZE_PIXELS = 25; Tetris.BOARD_HEIGHT_PIXELS = Tetris.BOARD_HEIGHT * Tetris.BLOCK_SIZE_PIXELS; Tetris.BOARD_WIDTH_PIXELS = Tetris.BOARD_WIDTH * Tetris.BLOCK_SIZE_PIXELS; Tetris.BLOCK_BORDER_COLOR = "#484848"; Tetris.BLOCK_COLORS = { 'X': 'black', 'b': 'cyan', 'B': 'blue', 'O': 'orange', 'Y': 'yellow', 'G': 'green', 'P': '#9370D8', 'R': 'red' }; Tetris.GRAVITY_DELAY = 300; // 300ms Tetris.EMPTY_LINE = " "; Tetris.FILLED_LINE = "XXXXXXXXXX"; Tetris.COMPLETE_LINE_PATTERN = /[^ ]{10}/; // Pieces. (Indexed by piece rotation (0-3), row (0-3), piece number (0-6)) Tetris.PIECES = []; for (var i = 0; i < 4; i++) { Tetris.PIECES[i] = []; } Tetris.PIECES[0][0] = [ " ", " ", " ", " ", " ", " ", " " ]; Tetris.PIECES[0][1] = [ " ", "B ", " O ", " YY ", " GG ", " P ", "RR " ]; Tetris.PIECES[0][2] = [ "bbbb", "BBB ", "OOO ", " YY ", "GG ", "PPP ", " RR " ]; Tetris.PIECES[0][3] = [ " ", " ", " ", " ", " ", " ", " " ]; Tetris.PIECES[1][0] = [ " b ", " ", " ", " ", " ", " ", " R " ]; Tetris.PIECES[1][1] = [ " b ", " B ", "OO ", " YY ", " G ", " P ", " RR " ]; Tetris.PIECES[1][2] = [ " b ", " B ", " O ", " YY ", " GG ", " PP ", " R " ]; Tetris.PIECES[1][3] = [ " b ", "BB ", " O ", " ", " G ", " P ", " " ]; Tetris.PIECES[2][0] = [ " ", " ", " ", " ", " ", " ", " " ]; Tetris.PIECES[2][1] = [ " ", " ", " ", " YY ", " GG ", " ", "RR " ]; Tetris.PIECES[2][2] = [ "bbbb", "BBB ", "OOO ", " YY ", "GG ", "PPP ", " RR " ]; Tetris.PIECES[2][3] = [ " ", " B ", "O ", " ", " ", " P ", " " ]; Tetris.PIECES[3][0] = [ " b ", " ", " ", " ", " ", " ", " R " ]; Tetris.PIECES[3][1] = [ " b ", " BB ", " O ", " YY ", " G ", " P ", " RR " ]; Tetris.PIECES[3][2] = [ " b ", " B ", " O ", " YY ", " GG ", "PP ", " R " ]; Tetris.PIECES[3][3] = [ " b ", " B ", " OO ", " ", " G ", " P ", " " ]; /** * Stores the state of a tetris board and handles drawing it. */ Tetris.Board = function (canvas, playerRef) { this.context = canvas.getContext('2d'); this.playerRef = playerRef; this.snapshot = null; this.isMyBoard = false; // Listen for changes to our board. var self = this; playerRef.on('value', function(snapshot) { self.snapshot = snapshot; self.draw(); }); }; /** * Draws the contents of the board as well as the current piece. */ Tetris.Board.prototype.draw = function () { // Clear canvas. this.context.clearRect(0, 0, Tetris.BOARD_WIDTH_PIXELS, Tetris.BOARD_HEIGHT_PIXELS); // Iterate over columns / rows in board data and draw each non-empty block. for (var x = 0; x < Tetris.BOARD_WIDTH; x++) { for (var y = 0; y < Tetris.BOARD_HEIGHT; y++) { var colorValue = this.getBlockVal(x, y); if (colorValue != ' ') { // Calculate block position and draw a correctly-colored square. var left = x * Tetris.BLOCK_SIZE_PIXELS; var top = y * Tetris.BLOCK_SIZE_PIXELS; this.context.fillStyle = Tetris.BLOCK_COLORS[colorValue]; this.context.fillRect(left, top, Tetris.BLOCK_SIZE_PIXELS, Tetris.BLOCK_SIZE_PIXELS); this.context.lineWidth = 1; this.context.strokeStyle = Tetris.BLOCK_BORDER_COLOR; this.context.strokeRect(left, top, Tetris.BLOCK_SIZE_PIXELS, Tetris.BLOCK_SIZE_PIXELS); } } } // If there's a falling piece, draw it. if (this.snapshot !== null && this.snapshot.hasChild('piece')) { var piece = Tetris.Piece.fromSnapshot(this.snapshot.child('piece')); this.drawPiece(piece); } // If this isn't my board, dim it out with a 25% opacity black rectangle. if (!this.isMyBoard) { this.context.fillStyle = "rgba(0, 0, 0, 0.25)"; this.context.fillRect(0, 0, Tetris.BOARD_WIDTH_PIXELS, Tetris.BOARD_HEIGHT_PIXELS); } }; /** * Draw the currently falling piece. */ Tetris.Board.prototype.drawPiece = function (piece) { var self = this; this.forEachBlockOfPiece(piece, function (x, y, colorValue) { var left = x * Tetris.BLOCK_SIZE_PIXELS; var top = y * Tetris.BLOCK_SIZE_PIXELS; self.context.fillStyle = Tetris.BLOCK_COLORS[colorValue]; self.context.fillRect(left, top, Tetris.BLOCK_SIZE_PIXELS, Tetris.BLOCK_SIZE_PIXELS); self.context.lineWidth = 1; self.context.strokeStyle = Tetris.BLOCK_BORDER_COLOR; self.context.strokeRect(left, top, Tetris.BLOCK_SIZE_PIXELS, Tetris.BLOCK_SIZE_PIXELS); }); }; /** * Clear the board contents. */ Tetris.Board.prototype.clear = function () { for (var row = 0; row < Tetris.BOARD_HEIGHT; row++) { this.setRow(row, Tetris.EMPTY_LINE); } }; /** * Given a Tetris.Piece, returns true if it has collided with the board (i.e. its current position * and rotation causes it to overlap blocks already on the board). */ Tetris.Board.prototype.checkForPieceCollision = function (piece) { var collision = false; var self = this; this.forEachBlockOfPiece(piece, function (x, y, colorValue) { // NOTE: we explicitly allow y < 0 since pieces can be partially visible. if (x < 0 || x >= Tetris.BOARD_WIDTH || y >= Tetris.BOARD_HEIGHT) { collision = true; } else if (y >= 0 && self.getBlockVal(x, y) != ' ') { collision = true; // collision with board contents. } }, /*includeInvalid=*/ true); return collision; }; /** * Given a Tetris.Piece that has landed, add it to the board contents. */ Tetris.Board.prototype.addLandedPiece = function (piece) { var self = this; // We go out of our way to set an entire row at a time just so the rows show up as // child_added in the graphical debugger, rather than child_changed. var rowY = -1, rowContents = null; this.forEachBlockOfPiece(piece, function (x, y, val) { if (y != rowY) { if (rowY !== -1) self.setRow(rowY, rowContents); rowContents = self.getRow(y); rowY = y; } rowContents = rowContents.substring(0, x).concat(val) .concat(rowContents.substring(x + 1, Tetris.BOARD_WIDTH)); }); if (rowY !== -1) self.setRow(rowY, rowContents); }; /** * Check for any completed lines (no gaps) and remove them, then return the number * of removed lines. */ Tetris.Board.prototype.removeCompletedRows = function () { // Start at the bottom of the board, working up, removing completed lines. var copyFrom = Tetris.BOARD_HEIGHT - 1; var copyTo = copyFrom; var completedRows = 0; while (copyFrom >= 0) { var fromContents = this.getRow(copyFrom); // See if the line is complete (if so, we'll skip it) if (fromContents.match(Tetris.COMPLETE_LINE_PATTERN)) { copyFrom--; completedRows++; } else { // Copy the row down (to fill the gap from any removed rows) and continue on. this.setRow(copyTo, fromContents); copyFrom--; copyTo--; } } return completedRows; }; /** * Generate the specified number of junk rows at the bottom of the board. Return true if the added * rows overflowed the board (in which case the player loses). */ Tetris.Board.prototype.addJunkRows = function (numRows) { var overflow = false; // First, check if any blocks are going to overflow off the top of the screen. var topRowContents = this.getRow(numRows - 1); overflow = topRowContents.match(/[^ ]/); // Shift rows up to make room for the new rows. for (var i = 0; i < Tetris.BOARD_HEIGHT - numRows; i++) { var moveLineContents = this.getRow(i + numRows); this.setRow(i, moveLineContents); } // Fill the bottom with junk rows that are full except for a single random gap. var gap = Math.floor(Math.random() * Tetris.FILLED_LINE.length); var junkRow = Tetris.FILLED_LINE.substring(0, gap) + ' ' + Tetris.FILLED_LINE.substring(gap + 1); for (i = Tetris.BOARD_HEIGHT - numRows; i < Tetris.BOARD_HEIGHT; i++) { this.setRow(i, junkRow); } return overflow; }; /** * Helper to enumerate the blocks that make up a particular piece. Calls fn() for each block, * passing the x and y position of the block and the color value. If includeInvalid is true, it * includes blocks that would fall outside the bounds of the board. */ Tetris.Board.prototype.forEachBlockOfPiece = function (piece, fn, includeInvalid) { for (var blockY = 0; blockY < 4; blockY++) { for (var blockX = 0; blockX < 4; blockX++) { var colorValue = Tetris.PIECES[piece.rotation][blockY][piece.pieceNum].charAt(blockX); if (colorValue != ' ') { var x = piece.x + blockX, y = piece.y + blockY; if (includeInvalid || (x >= 0 && x < Tetris.BOARD_WIDTH && y >= 0 && y < Tetris.BOARD_HEIGHT)) { fn(x, y, colorValue); } } } } }; Tetris.Board.prototype.getRow = function (y) { var row = (y < 10) ? ('0' + y) : ('' + y); // Pad row so they sort nicely in debugger. :-) var rowContents = this.snapshot === null ? null : this.snapshot.child('board/' + row).val(); return rowContents || Tetris.EMPTY_LINE; }; Tetris.Board.prototype.getBlockVal = function (x, y) { return this.getRow(y).charAt(x); }; Tetris.Board.prototype.setRow = function (y, rowContents) { var row = (y < 10) ? ('0' + y) : ('' + y); // Pad row so they sort nicely in debugger. :-) if (rowContents === Tetris.EMPTY_LINE) rowContents = null; // delete empty lines so we get remove / added events in debugger. :-) this.playerRef.child('board').child(row).set(rowContents); }; Tetris.Board.prototype.setBlockVal = function (x, y, val) { var rowContents = this.getRow(y); rowContents = rowContents.substring(0, x) + val + rowContents.substring(x+1); this.setRow(y, rowContents); }; /** * Immutable object representing a falling piece along with its rotation and board position. * Has helpers for generating mutated Tetris.Piece objects (e.g. rotated or dropped). */ Tetris.Piece = function (pieceNum, x, y, rotation) { if (arguments.length > 0) { this.pieceNum = pieceNum; this.x = x; this.y = y; this.rotation = rotation; } else { // Initialize new random piece. this.pieceNum = Math.floor(Math.random() * 7); this.x = 4; // "center" it. this.y = -2; // this will make the bottom line of the piece visible. this.rotation = 0; } }; /** * Create a piece from a Firebase snapshot representing a piece. */ Tetris.Piece.fromSnapshot = function (snapshot) { var piece = snapshot.val(); return new Tetris.Piece(piece.pieceNum, piece.x, piece.y, piece.rotation); }; /** * Writes the current piece data into Firebase. */ Tetris.Piece.prototype.writeToFirebase = function (pieceRef) { pieceRef.set({pieceNum: this.pieceNum, x: this.x, y: this.y, rotation: this.rotation}); }; Tetris.Piece.prototype.drop = function () { return new Tetris.Piece(this.pieceNum, this.x, this.y + 1, this.rotation); }; Tetris.Piece.prototype.rotate = function () { return new Tetris.Piece(this.pieceNum, this.x, this.y, (this.rotation + 1) % 4); }; Tetris.Piece.prototype.moveLeft = function () { return new Tetris.Piece(this.pieceNum, this.x - 1, this.y, this.rotation); }; Tetris.Piece.prototype.moveRight = function () { return new Tetris.Piece(this.pieceNum, this.x + 1, this.y, this.rotation); }; /** * Manages joining the game, responding to keypresses, making the piece drop, etc. */ Tetris.PlayingState = { Watching: 0, Joining: 1, Playing: 2 }; Tetris.Controller = function (tetrisRef) { this.tetrisRef = tetrisRef; this.createBoards(); this.playingState = Tetris.PlayingState.Watching; this.waitToJoin(); }; Tetris.Controller.prototype.createBoards = function () { this.boards = []; for(var i = 0; i <= 1; i++) { var playerRef = this.tetrisRef.child('player' + i); var canvas = $('#canvas' + i).get(0); this.boards.push(new Tetris.Board(canvas, playerRef)); } }; Tetris.Controller.prototype.waitToJoin = function() { var self = this; // Listen on 'online' location for player0 and player1. this.tetrisRef.child('player0/online').on('value', function(onlineSnap) { if (onlineSnap.val() === null && self.playingState === Tetris.PlayingState.Watching) { self.tryToJoin(0); } }); this.tetrisRef.child('player1/online').on('value', function(onlineSnap) { if (onlineSnap.val() === null && self.playingState === Tetris.PlayingState.Watching) { self.tryToJoin(1); } }); }; /** * Try to join the game as the specified playerNum. */ Tetris.Controller.prototype.tryToJoin = function(playerNum) { // Set ourselves as joining to make sure we don't try to join as both players. :-) this.playingState = Tetris.PlayingState.Joining; // Use a transaction to make sure we don't conflict with other people trying to join. var self = this; this.tetrisRef.child('player' + playerNum + '/online').transaction(function(onlineVal) { if (onlineVal === null) { return true; // Try to set online to true. } else { return; // Somebody must have beat us. Abort the transaction. } }, function(error, committed) { if (committed) { // We got in! self.playingState = Tetris.PlayingState.Playing; self.startPlaying(playerNum); } else { self.playingState = Tetris.PlayingState.Watching; } }); }; /** * Once we've joined, enable controlling our player. */ Tetris.Controller.prototype.startPlaying = function (playerNum) { this.myPlayerRef = this.tetrisRef.child('player' + playerNum); this.opponentPlayerRef = this.tetrisRef.child('player' + (1 - playerNum)); this.myBoard = this.boards[playerNum]; this.myBoard.isMyBoard = true; this.myBoard.draw(); // Clear our 'online' status when we disconnect so somebody else can join. this.myPlayerRef.child('online').onDisconnect().remove(); // Detect when other player pushes rows to our board. this.watchForExtraRows(); // Detect when game is restarted by other player. this.watchForRestart(); $('#gameInProgress').hide(); var self = this; $('#restartButton').show(); $("#restartButton").click(function () { self.restartGame(); }); this.initializePiece(); this.enableKeyboard(); this.resetGravity(); }; Tetris.Controller.prototype.initializePiece = function() { this.fallingPiece = null; var pieceRef = this.myPlayerRef.child('piece'); var self = this; // Watch for changes to the current piece (and initialize it if it's null). pieceRef.on('value', function(snapshot) { if (snapshot.val() === null) { var newPiece = new Tetris.Piece(); newPiece.writeToFirebase(pieceRef); } else { self.fallingPiece = Tetris.Piece.fromSnapshot(snapshot); } }); }; /** * Sets up handlers for all keyboard commands. */ Tetris.Controller.prototype.enableKeyboard = function () { var self = this; $(document).on('keydown', function (evt) { if (self.fallingPiece === null) return; // piece isn't initialized yet. var keyCode = evt.which; var key = { space:32, left:37, up:38, right:39, down:40 }; var newPiece = null; switch (keyCode) { case key.left: newPiece = self.fallingPiece.moveLeft(); break; case key.up: newPiece = self.fallingPiece.rotate(); break; case key.right: newPiece = self.fallingPiece.moveRight(); break; case key.down: newPiece = self.fallingPiece.drop(); break; case key.space: // Drop as far as we can. var droppedPiece = self.fallingPiece; do { newPiece = droppedPiece; droppedPiece = droppedPiece.drop(); } while (!self.myBoard.checkForPieceCollision(droppedPiece)); break; } if (newPiece !== null) { // If the new piece position / rotation is valid, update self.fallingPiece and firebase. if (!self.myBoard.checkForPieceCollision(newPiece)) { // If the keypress moved the piece down, reset gravity. if (self.fallingPiece.y != newPiece.y) { self.resetGravity(); } newPiece.writeToFirebase(self.myPlayerRef.child('piece')); } return false; // handled } return true; }); }; /** * Sets a timer to make the piece repeatedly drop after GRAVITY_DELAY ms. */ Tetris.Controller.prototype.resetGravity = function () { // If there's a timer already active, clear it first. if (this.gravityIntervalId !== null) { clearInterval(this.gravityIntervalId); } var self = this; this.gravityIntervalId = setInterval(function() { self.doGravity(); }, Tetris.GRAVITY_DELAY); }; Tetris.Controller.prototype.doGravity = function () { if (this.fallingPiece === null) return; // piece isn't initialized yet. var newPiece = this.fallingPiece.drop(); // If we've hit the bottom, add the (pre-drop) piece to the board and create a new piece. if (this.myBoard.checkForPieceCollision(newPiece)) { this.myBoard.addLandedPiece(this.fallingPiece); // Check for completed lines and if appropriate, push extra rows to our opponent. var completedRows = this.myBoard.removeCompletedRows(); var rowsToPush = (completedRows === 4) ? 4 : completedRows - 1; if (rowsToPush > 0) this.opponentPlayerRef.child('extrarows').push(rowsToPush); // Create new piece (it'll be initialized to a random piece at the top of the screen). newPiece = new Tetris.Piece(); // Is the board full? if (this.myBoard.checkForPieceCollision(newPiece)) this.gameOver(); } newPiece.writeToFirebase(this.myPlayerRef.child('piece')); }; /** * Detect when our opponent pushes extra rows to us. */ Tetris.Controller.prototype.watchForExtraRows = function () { var self = this; var extraRowsRef = this.myPlayerRef.child('extrarows'); extraRowsRef.on('child_added', function(snapshot) { var rows = snapshot.val(); extraRowsRef.child(snapshot.key()).remove(); var overflow = self.myBoard.addJunkRows(rows); if (overflow) self.gameOver(); // Also move piece up to avoid collisions. if (self.fallingPiece) { self.fallingPiece.y -= rows; self.fallingPiece.writeToFirebase(self.myPlayerRef.child('piece')); } }); }; /** * Detect when our opponent restarts the game. */ Tetris.Controller.prototype.watchForRestart = function () { var self = this; var restartRef = this.myPlayerRef.child('restart'); restartRef.on('value', function(snap) { if (snap.val() === 1) { restartRef.set(0); self.resetMyBoardAndPiece(); } }); }; Tetris.Controller.prototype.gameOver = function () { this.restartGame(); }; Tetris.Controller.prototype.restartGame = function () { this.opponentPlayerRef.child('restart').set(1); this.resetMyBoardAndPiece(); }; Tetris.Controller.prototype.resetMyBoardAndPiece = function () { this.myBoard.clear(); var newPiece = new Tetris.Piece(); newPiece.writeToFirebase(this.myPlayerRef.child('piece')); }; start(); </script> </body> </html>

Related: See More


Questions / Comments: