Years ago I've blogged on a challenge which was to implement a simple tetris game. This new reloaded version is a direct one-to-one translation to vanilla Javascript and runs in a browser. Feel free to learn from the code and/or reuse it in any way. Note that the UP key has to be pressed to start the game.
(Re)start: up
Moves: left/right/down/space
Moves: left/right/down/space
The code:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script> var pieceMove = { left: 'left', right: 'right', up: 'up', down: 'down', rotate: 'rotate' }; function board() { this.MARGIN = 5; this.SIZEX = 10; this.SIZEY = 40; this.BLOCKSIZE = 10; this.PIECESTARTX = 8; var i, j; this.squares = new Array( this.MARGIN + this.SIZEX + this.MARGIN ); for ( i=0; i < this.squares.length; i++ ) this.squares[i] = new Array( this.MARGIN + this.SIZEY + this.MARGIN ); // fill the margin for ( i=0; i < this.MARGIN + this.SIZEX + this.MARGIN; i++ ) for ( j=0; j < this.MARGIN; j++ ) this.squares[i][j] = this.squares[i][j + this.SIZEY + this.MARGIN] = true; for ( i=0; i < this.MARGIN; i++ ) for ( j=0; j < this.MARGIN + this.SIZEY + this.MARGIN; j++ ) this.squares[i][j] = this.squares[i + this.SIZEX + this.MARGIN][j] = true; this.currentPiece = piece.getRandomPiece(); this.currentPieceX = this.PIECESTARTX; this.currentPieceY = this.MARGIN; this.score = 0; } board.prototype.obtainContext = function(canvas) { this.context = canvas.getContext('2d'); this.contextWidth = canvas.width; this.contextHeight = canvas.height; } board.prototype.paint = function() { var i, j; this.context.fillStyle = 'white'; this.context.fillRect(0, 0, this.contextWidth, this.contextHeight); // background this.context.fillStyle = 'black'; for ( i=0; i < this.SIZEX; i++ ) for ( j=0; j < this.SIZEY; j++ ) if ( this.squares[this.MARGIN + i][this.MARGIN + j] ) this.context.fillRect( i * this.BLOCKSIZE, j * this.BLOCKSIZE, this.BLOCKSIZE, this.BLOCKSIZE ); // piece if ( this.currentPiece ) { for ( i=0; i < this.currentPiece.SIZE; i++ ) for ( j=0; j < this.currentPiece.SIZE; j++ ) if ( this.currentPiece.blockData[i][j] ) { this.context.fillRect( (this.currentPieceX - this.MARGIN + i) * this.BLOCKSIZE, ( this.currentPieceY - this.MARGIN + j ) * this.BLOCKSIZE, this.BLOCKSIZE, this.BLOCKSIZE); } } // score this.context.fillStyle = 'red'; this.context.fillText( this.score, 5, 10 ); } /* Advance one step of the game */ board.prototype.autoUpdate = function() { this.removeFullLines(); if ( this.currentPiece ) { var _newX = this.currentPieceX; var _newY = this.currentPieceY + 1; if ( this.isLegalPosition( this.currentPiece, _newX, _newY ) ) { this.currentPieceY = _newY; } else { this.absorbCurrentPiece(); this.currentPiece = piece.getRandomPiece(); this.currentPieceX = this.PIECESTARTX; this.currentPieceY = this.MARGIN; if ( !this.isLegalPosition( this.currentPiece, this.currentPieceX, this.currentPieceY ) ) { this.score = 'Game over: ' + this.score; this.currentPiece = null; return false; } } } return true; } /* Remove all full lines */ board.prototype.removeFullLines = function() { var fullLineNumber = -1; do { fullLineNumber = this.getPossibleFullLine(); if ( fullLineNumber >= 0 ) this.removeLine( fullLineNumber ); } while ( fullLineNumber >= 0 ); } /* Check if one of lines is possibly filled */ board.prototype.getPossibleFullLine = function() { for ( var j = 0; j < this.SIZEY; j++ ) { var isFull = true; for ( var i = 0; i < this.SIZEX; i++ ) isFull &= this.squares[this.MARGIN + i][this.MARGIN + j]; if ( isFull ) return j + this.MARGIN; } return -1; } /* Remove selected line */ board.prototype.removeLine = function( lineNumber ) { for ( var j = lineNumber; j > this.MARGIN; j-- ) for ( var i=this.MARGIN; i < this.SIZEX + this.MARGIN; i++ ) this.squares[i][j] = this.squares[i][j - 1]; this.score += 1; } /* Copy piece data to board's block data. Called only when piece becomes "solid", non-movable. */ board.prototype.absorbCurrentPiece = function() { if ( this.currentPiece ) for ( var i=0; i < this.currentPiece.SIZE; i++ ) for ( var j=0; j < this.currentPiece.SIZE; j++ ) if ( this.currentPiece.blockData[i][j] ) this.squares[i + this.currentPieceX][j + this.currentPieceY] = true; } /* Moves current piece into a new position, if possible */ board.prototype.moveCurrentPiece = function(move) { var _newPiece = this.currentPiece; var _newX = this.currentPieceX; var _newY = this.currentPieceY; if ( this.currentPiece ) { if ( move == pieceMove.rotate ) _newPiece = piece.rotate( this.currentPiece ); if ( move == pieceMove.left ) _newX = this.currentPieceX - 1; if ( move == pieceMove.right ) _newX = this.currentPieceX + 1; if ( move == pieceMove.down ) _newY = this.currentPieceY + 1; // move only if legal position if ( this.isLegalPosition( _newPiece, _newX, _newY ) ) { this.currentPiece = _newPiece; this.currentPieceX = _newX; this.currentPieceY = _newY; } } } /* Is the new possible position of a piece "legal" in a sense that the piece doesn't overlap with non-empty cells */ board.prototype.isLegalPosition = function( piece, pieceX, pieceY ) { for ( var i=0; i < piece.SIZE; i++ ) for ( var j=0; j < piece.SIZE; j++ ) if ( piece.blockData[i][j] && this.squares[i + pieceX][j + pieceY] ) return false; return true; } function piece(r0, r1, r2, r3, r4) { this.SIZE = 5; var i; this.blockData = new Array( this.SIZE ); for ( i=0; i < this.SIZE; i++ ) this.blockData[i] = new Array( this.SIZE ); for ( i=0; i<this.SIZE; i++ ) { this.blockData[i][0] = r0[i]; this.blockData[i][1] = r1[i]; this.blockData[i][2] = r2[i]; this.blockData[i][3] = r3[i]; this.blockData[i][4] = r4[i]; } } piece.rotate = function( source ) { if ( source == piece.i_horiz ) return piece.i_vert; if ( source == piece.i_vert ) return piece.i_horiz; if ( source == piece.z_lefthoriz ) return piece.z_leftvert; if ( source == piece.z_leftvert ) return piece.z_lefthoriz; if ( source == piece.z_righthoriz ) return piece.z_rightvert; if ( source == piece.z_rightvert ) return piece.z_righthoriz; if ( source == piece.l_l1 ) return piece.l_l2; if ( source == piece.l_l2 ) return piece.l_l3; if ( source == piece.l_l3 ) return piece.l_l4; if ( source == piece.l_l4 ) return piece.l_l1; if ( source == piece.r_l1 ) return piece.r_l2; if ( source == piece.r_l2 ) return piece.r_l3; if ( source == piece.r_l3 ) return piece.r_l4; if ( source == piece.r_l4 ) return piece.r_l1; return piece.box; } piece.getRandomPiece = function() { var _next = Math.floor( Math.random() * 6 ); switch ( _next ) { case 1 : return piece.z_lefthoriz; case 2 : return piece.z_righthoriz; case 3 : return piece.l_l1; case 4 : return piece.r_l1; case 5 : return piece.i_horiz; default : return piece.box; } } piece.box = new piece( [0, 0, 0, 0, 0 ], [0, 1, 1, 0, 0 ], [0, 1, 1, 0, 0 ], [0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0 ] ); piece.i_horiz = new piece( [0, 0, 1, 0, 0 ], [0, 0, 1, 0, 0 ], [0, 0, 1, 0, 0 ], [0, 0, 1, 0, 0 ], [0, 0, 0, 0, 0 ] ); piece.i_vert = new piece( [0, 0, 0, 0, 0 ], [0, 1, 1, 1, 1 ], [0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0 ] ); piece.z_lefthoriz = new piece( [0, 0, 0, 0, 0 ], [0, 1, 1, 0, 0 ], [0, 0, 1, 1, 0 ], [0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0 ] ); piece.z_leftvert = new piece( [0, 0, 0, 0, 0 ], [0, 0, 1, 0, 0 ], [0, 1, 1, 0, 0 ], [0, 1, 0, 0, 0 ], [0, 0, 0, 0, 0 ] ); piece.z_righthoriz = new piece( [0, 0, 0, 0, 0 ], [0, 0, 1, 1, 0 ], [0, 1, 1, 0, 0 ], [0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0 ] ); piece.z_rightvert = new piece( [0, 0, 0, 0, 0 ], [0, 0, 1, 0, 0 ], [0, 0, 1, 1, 0 ], [0, 0, 0, 1, 0 ], [0, 0, 0, 0, 0 ] ); piece.l_l1 = new piece( [0, 0, 0, 0, 0 ], [0, 1, 0, 0, 0 ], [0, 1, 1, 1, 0 ], [0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0 ] ); piece.l_l2 = new piece( [0, 0, 0, 0, 0 ], [0, 1, 1, 0, 0 ], [0, 1, 0, 0, 0 ], [0, 1, 0, 0, 0 ], [0, 0, 0, 0, 0 ] ); piece.l_l3 = new piece( [0, 0, 0, 0, 0 ], [1, 1, 1, 0, 0 ], [0, 0, 1, 0, 0 ], [0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0 ] ); piece.l_l4 = new piece( [0, 0, 1, 0, 0 ], [0, 0, 1, 0, 0 ], [0, 1, 1, 0, 0 ], [0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0 ] ); piece.r_l1 = new piece( [0, 0, 0, 0, 0 ], [0, 0, 0, 1, 0 ], [0, 1, 1, 1, 0 ], [0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0 ] ); piece.r_l2 = new piece( [0, 0, 0, 0, 0 ], [0, 0, 1, 1, 0 ], [0, 0, 0, 1, 0 ], [0, 0, 0, 1, 0 ], [0, 0, 0, 0, 0 ] ); piece.r_l3 = new piece( [0, 0, 0, 0, 0 ], [0, 0, 1, 1, 1 ], [0, 0, 1, 0, 0 ], [0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0 ] ); piece.r_l4 = new piece( [0, 0, 1, 0, 0 ], [0, 0, 1, 0, 0 ], [0, 0, 1, 1, 0 ], [0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0 ] ); var _board; var _interval; // current frame interval var _speedinterval = 15000; // speed up every 15 sec var _intervalHandle; /* * This advances one frame */ function tick() { _board.paint(); if ( _board.autoUpdate() ) { _intervalHandle = setTimeout( tick, _interval); } } /* * This fires every 15 seconds and decreases the timeout of the other timer */ function speedup() { if ( _interval > 20 ) _interval -= 10; } function init(canvas) { _interval = 200; _board = new board(); _board.obtainContext(canvas); _intervalHandle = setTimeout( tick, _interval ); } (function main(){ window.addEventListener('load', function() { var canvas = document.getElementById('tetris'); canvas.addEventListener('click', function() { if ( _intervalHandle ) clearTimeout( _intervalHandle ); init(canvas); }); setInterval( speedup, _speedinterval ); } ); window.addEventListener( 'keydown', function(e) { //e.preventDefault(); if ( e.keyCode == 37 ) // arrow left _board.moveCurrentPiece( pieceMove.left ); if ( e.keyCode == 39 ) // arrow right _board.moveCurrentPiece( pieceMove.right ); if ( e.keyCode == 40 ) // arrow down _board.moveCurrentPiece( pieceMove.down ); if ( e.keyCode == 32 ) // space _board.moveCurrentPiece( pieceMove.rotate ); _board.paint(); } ); })(); </script> <style> #welcome { border: 1px solid black; font: 10px tahoma; width: 100px; } #tetris { border: 1px solid black; } </style> </head> <body> <div id="welcome"> Moves: left/right/down/space Click: (re)start. </div> <div> <canvas id="tetris" width="100" height="400"> </canvas> </div> </body> </html>
No comments:
Post a Comment