<div id="Tetris">
    <ul id="Scrren">
        <li><canvas width="150" height="300" id="Main-scrren">error</canvas></li>
        <li>        
            <p>Next</p>
            <canvas width="50" height="50" id="Sub-scrren">error</canvas>
            <p>Score</p>
            <div id="Score">
            </div>
            <p id="play">Play</p>    
        </li>
    </ul>
   
    <table style="margin-left:70px;">
        <tr>
          <td>←→</td>
          <td>Move</td>
        </tr>
        <tr>
          <td>↓</td>
          <td>Decision</td>
        </tr>
        <tr>
            <td>space or↑</td>
            <td>Rotation</td>
        </tr>
    </table>

    <canvas width="250" height="40" id="Game_over">error</canvas> 
</div> 
* { margin: 0; padding: 0; border: 0; font-size:11pt; font-family:Times New Roman; color:#303030; line-height: 15pt;}
body { background: #fdf; font: 30px sans-serif;}
#Tetris{width: 300px; height: 400px; margin: 50px auto 0 auto; background-color: #DCDCDC; border: 2px solid gray; position: relative;}
#Scrren{list-style-type: none; float: left;}
#Scrren li{float: left;}
#Main-scrren{background-color: black; margin: 20px 0 0 50px;}
#Sub-scrren{background-color: black; margin: 0 0 0 20px;}
#Scrren p{margin: 30px 0 0 25px;}
#Game_over{position: absolute; top:180px; left:60px; display: none;}
#Score{position: relative; width: 80px; height: 20px; top: 0px; left:10px; overflow: hidden; }
#play{background-color: white; width: 40px; height: 20px; border: solid 2px gray; border-radius: 3px; padding-left: 10px; cursor: pointer;}
var Tetris = {
    WIDHT : 15,
    HEIGHT : 30,
    NEXT_WIDHT : 5,
    NEXT_HEIGHT : 5,
    BLOCK_SIZE : 10,
    piece_type : [
                  [
                    [{x:0,y:0} , {x:0,y:1} , {x:1,y:0} , {x:1,y:1}]     // O
                  ],
                  [
                    [{x:0,y:0} , {x:0,y:1} , {x:0,y:2} , {x:0,y:3}],
                    [{x:0,y:1} , {x:1,y:1} , {x:2,y:1} , {x:3,y:1}]     // I

                  ],
                  [
                    [{x:0,y:1} , {x:0,y:2} , {x:1,y:0} , {x:1,y:1}],    // Z
                    [{x:0,y:1} , {x:1,y:1} , {x:1,y:2} , {x:2,y:2}]
                  ],
                  [
                    [{x:0,y:0} , {x:0,y:1} , {x:0,y:2} , {x:1,y:2}],    // L
                    [{x:0,y:1} , {x:0,y:2} , {x:1,y:1} , {x:2,y:1}],
                    [{x:0,y:0} , {x:1,y:0} , {x:1,y:1} , {x:1,y:2}],
                    [{x:0,y:1} , {x:1,y:1} , {x:2,y:0} , {x:2,y:1}]
                  ],
                  [
                    [{x:1,y:0} , {x:0,y:1} , {x:1,y:1} , {x:2,y:1}],    // T
                    [{x:0,y:0} , {x:0,y:1} , {x:1,y:1} , {x:0,y:2}],
                    [{x:0,y:0} , {x:1,y:0} , {x:2,y:0} , {x:1,y:1}],
                    [{x:1,y:0} , {x:0,y:1} , {x:1,y:1} , {x:1,y:2}]
                  ]
                 ],
    next_piece : [
                    [{x:15,y:15} , {x:25,y:15} , {x:15,y:25} , {x:25,y:25}],    // O
                    [{x:20,y:5 } , {x:20,y:15} , {x:20,y:25} , {x:20,y:35}],    // I 
                    [{x:25,y:10} , {x:15,y:20} , {x:25,y:20} , {x:15,y:30}],    // Z
                    [{x:15,y:10} , {x:15,y:20} , {x:15,y:30} , {x:25,y:30}],    // L
                    [{x:20,y:15} , {x:10,y:25} , {x:20,y:25} , {x:30,y:25}]     // T
                 ],
    lock : false,
    stop : false,
    piece_color : ['#0000FF' , '#FF0000' , '#00FF00' , '#FFFF00' , '#808080'],
    Tetris_piece : null,
    Tetris_currnet_canvas : null,
    Tetris_next_canvas : null,
    Tetris_currnet_size : null,
    Tetris_next_size : null,
    ready_sw : false,
    level_time : 800,
    level_cnt : 5,
    key_code     : {
                    UP : 38 , DOWN : 40 , LEFT : 37 , RIGTH : 39 , BACKSPACE : 8 ,
                    ESC : 27 , ENTER : 13 , DELETE : 46 , TAB : 9 , DOUBLE : 50 ,
                    SINGULE : 55 , SPACE : 32
                },
    defineClass : function(constructor,methos,statics){
        if(methos) this.extend(constructor.prototype,methos);
        if(statics) this.extend(constructor,statics);
        return constructor;
    },
     extend : function(o,p){
        for(prop in p) o[prop] = p[prop];
        return o;
    },
    log : function(msg){
        if(!msg) return;
        if (window.console && window.console.log) {
            window.console.log(msg);
        }
    }
}

/*
 * 
 *  Tetris Piece Class
 * 
 */
Tetris.Piece = Tetris.defineClass(
    function(type,color,n){
        this.type = type;
        this.color = color;
        this.no = n;
        this.next = Tetris.next_piece[this.no];
        this.index = 0;
        this.position = {x:0 , y:0};
        
    },
    {
        set_position : function(x,y){
            this.position.x = x;
            this.position.y = y;
            if(this.end_down()) Tetris.Control.game_over = true;
        },
        check_down : function(){
            if(this.end_down()) return true;
            this.position.y++;
            return false;
        },
        end_down : function(){
            var pos = this.get_position() , i;
            var check = false;
            for(i=0;i<pos.length;i++){
                if(pos[i].y+1 >= Tetris.HEIGHT) return true;
                if(Tetris.Tetris_piece[pos[i].y+1][pos[i].x] != -1) return true;
            }
            return false;
        },
        set_piece : function(){
            var pos = this.get_position();
            var no = this.no;
            pos.forEach(function(e,i,a){
                Tetris.Tetris_piece[e.y][e.x] = no;
            });
        },
        down_piece : function(){
            var i;
            for(i=0;i<3;i++){
                if(this.check_down()){
                    return true;
                }
            }
            return false;
        },
        current_draw : function(){
            var pos = this.get_position();
            var color = this.color;
            pos.forEach(
                function(e,i,a){
                    Tetris.Control.current_draw(e.x,e.y,color);
                }
            );
        },
        next_draw : function(){
            var color = this.color;
            this.next.forEach(
                function(e,i,a){
                    Tetris.Control.next_draw(e.x,e.y,color);
                }
            );
        },
        get_position : function(){
            var pos = [] ;
            var position = this.position
            this.type[this.index].forEach(
                function(e,i,a){
                   pos.push({x:e.x+position.x , y:e.y+position.y});
                }
            );
            return pos;
        },
        get_size : function(){
            var size = this.get_rect();
            return {width:size.rigth.x+1,height:size.bottom.y+1};
        },
        get_rect : function(){
            var left = {x:0,y:0} , rigth = {x:0,y:0} , top = {x:0,y:0} , bottom = {x:0,y:0} ;
            this.type[this.index].forEach(
                function(e,i,a){
                   if(e.x < left.x) Tetris.extend(left,e);
                   if(e.x > rigth.x) Tetris.extend(rigth,e);
                   if(e.y > bottom.y) Tetris.extend(bottom,e);
                }     
            );
            return {left:left , rigth:rigth , top:top , bottom:bottom};
        },
        left : function(){
            var pos = this.get_position() , i;
            for(i=0;i<pos.length;i++){
                if(pos[i].x - 1 < 0){
                    return false;
                }
                if(Tetris.Tetris_piece[pos[i].y][pos[i].x - 1] != -1){
                    return false;
                }
            }
            this.position.x--;
            return true;
        },
        rigth : function(){
            var pos = this.get_position() , i;
            for(i=0;i<pos.length;i++){
                if(pos[i].x + 2 > Tetris.WIDHT){
                    return false;
                }
                if(Tetris.Tetris_piece[pos[i].y][pos[i].x + 1] != -1){
                    return false;
                }
            }
            this.position.x++;
            return true;
        },
        rotation : function(){
            var save_index = this.index , i , check_out = false;
            this.index = (this.index + 1) % this.type.length;
            var pos = this.get_position();
            for(i=0;i<pos.length;i++){
                if(pos[i].x < 0){
                    check_out = true;
                    break;
                }
                if(Tetris.Tetris_piece[pos[i].y][pos[i].x] != -1){
                    check_out = true;
                    break;
                }
                if(pos[i].x + 1 > Tetris.WIDHT){
                    check_out = true;
                    break;
                }
                if(Tetris.Tetris_piece[pos[i].y][pos[i].x] != -1){
                    check_out = true;
                    break;
                }
                if(pos[i].y >= Tetris.HEIGHT){
                    check_out = true;
                    break;
                }
                if(Tetris.Tetris_piece[pos[i].y][pos[i].x] != -1){
                    check_out = true;
                    break;
                }
            }
            if(check_out){
                this.index = save_index;
                return false;
            }
            return true;
        }
    }
);

/*
 * 
 *  Tetris Control
 * 
 */

Tetris.Control = {
    setup : function(){
        var tetris = Tetris;
        Tetris.Timer.setup();
        Tetris.KeyBoard.setup();
        Tetris.Tetris_currnet_canvas = $('#Main-scrren')[0].getContext("2d");
        Tetris.Tetris_next_canvas = $('#Sub-scrren')[0].getContext("2d");
        Tetris.Tetris_currnet_size = get_size('Main-scrren');
        Tetris.Tetris_next_size = get_size('Sub-scrren');
        Tetris.KeyBoard.add({object:this , id:'DOWN' , post:this.keydown.bind(this)});
        Tetris.KeyBoard.add({object:this , id:'RIGTH' , post:this.keyrigth.bind(this)});
        Tetris.KeyBoard.add({object:this , id:'LEFT' , post:this.keyleft.bind(this)});
        Tetris.KeyBoard.add({object:this , id:'SPACE' , post:this.keyspace.bind(this)});
        Tetris.KeyBoard.add({object:this , id:'UP' , post:this.keyspace.bind(this)});
        this.lock = true;
        function get_size(p){
            var w = $('#'+p)[0].width;
            var h = $('#'+p)[0].height;
            return {w:w , h:h}
        }
    },
    game_start : function(){
        this.init_screen();
        this.screen_clear();
        this.lock = false;
        this.game_over = false;
        this.now_time = new Date().getTime();
        Tetris.Timer.remove(this);
        Tetris.Timer.add({object:this , post:this.onLoop.bind(this)});
        Tetris.Score.setup();        
        this.level_time = Tetris.level_time;
        this.level_cnt = Tetris.level_cnt;
        var current = this.random() , next = this.random();
        this.current_piece = new Tetris.Piece(Tetris.piece_type[current],Tetris.piece_color[current],current);
        this.next_piece = new Tetris.Piece(Tetris.piece_type[next],Tetris.piece_color[next],next);
        var y = 0;
        var x = Math.floor(Tetris.WIDHT/2 - this.current_piece.get_size().width/2);
        this.current_piece.set_position(x,y);
        this.onLoop();
    },
    init_screen : function(){
        var i, j;
        Tetris.Tetris_piece = [];
        for(i=0;i<Tetris.HEIGHT;i++){            
            Tetris.Tetris_piece[i] = [];
            for(j=0;j<Tetris.WIDHT;j++){
                Tetris.Tetris_piece[i][j] = -1;
            }
        }
    },
    screen_clear : function(){
        Tetris.Tetris_currnet_canvas.clearRect(0,0,Tetris.Tetris_currnet_size.w,Tetris.Tetris_currnet_size.h);
        Tetris.Tetris_next_canvas.clearRect(0,0,Tetris.Tetris_next_size.w,Tetris.Tetris_next_size.h);
    },
    onLoop : function(){
        var now = new Date().getTime();
        if(now - this.now_time < this.level_time) return;
        this.now_time = now;        
        this.draw();
        if(this.lock) return;
        if(this.current_piece.check_down()) this.set_next();
        this.line_check();
    },
    keydown : function(){
        if(this.lock) return;
        this.down_key();
    },
    keyrigth : function(){
        if(this.lock) return;
        if(this.current_piece.rigth()) this.draw();
    },
    keyleft : function(){
        if(this.lock) return;
        if(this.current_piece.left()) this.draw();
    },
    keyspace : function(){
        if(this.lock) return;
        if(this.current_piece.rotation()) this.draw();
    },
    down_key : function(){
        this.down_sw = true;
        this.lock = true;
        var timer_id = {};
        var key_down = {object:timer_id , post:function(){
            if(this.current_piece.down_piece()){
                this.draw();
                this.lock = false;
                Tetris.Timer.remove(timer_id);
                return;
            }
            this.draw();
        }.bind(this)}
        Tetris.Timer.add(key_down);
    },
    set_next : function(){
        this.current_piece.set_piece();
        var next = this.random();
        this.current_piece = this.next_piece;
        this.next_piece = new Tetris.Piece(Tetris.piece_type[next],Tetris.piece_color[next],next);
        var y = 0;
        var x = Math.floor(Tetris.WIDHT/2 - this.next_piece.get_size().width/2);
        this.current_piece.set_position(x,y);
        this.draw();
        if(this.game_over) this.gameOver();
    },
    line_check : function(){
        var i;
        for(i=Tetris.HEIGHT-1;i>0;i--){
            if(this.check_block(i)){
                this.line_light(i,function(){
                    Tetris.Score.add();
                    this.timer_down();
                    this.line_clear(i);
                    this.draw();
                    this.block_down(i);
                }.bind(this));
                return;
            }
        }
    },
    timer_down : function(){
        if(this.level_cnt - 1 < 0){
            this.level_cnt = Tetris.level_cnt;
            if(this.level_time - 100 > 100){
                this.level_time -= 200;
            }
        }else this.level_cnt--;
    },
    line_light : function(i,p){
        var line = i , post = p;
        var timer_id = {};
        var index = 0;
        this.lock = true;
        var ligth_timer = {object:timer_id , post:function(){
            if(index + 1 == Tetris.WIDHT){
                Tetris.Timer.remove(timer_id);
                this.lock = false;
                post();
                return;
            }
            Tetris.Tetris_piece[line][index++] = -1;
            this.draw();
        }.bind(this)};
        Tetris.Timer.add(ligth_timer);
    },
    line_clear : function(line){
        var i;
        for(i=0;i<Tetris.WIDHT;i++){
            Tetris.Tetris_piece[line][i] = -1;
        }
    },
    block_down : function(n){
        var line = n , timer , i , end_block = false;
        var timer_id = {};
        var now_time = new Date().getTime();
        var block_down = {object:timer_id , post:function(){
            var now = new Date().getTime();
            if(now - now_time < 30) return;
            now_time = now;
            var down_line = line - 1;
            if(this.check_off_block(down_line)) end_block = true;
            for(i=0;i<Tetris.WIDHT;i++){
                Tetris.Tetris_piece[line][i] = Tetris.Tetris_piece[down_line][i];
            }
            this.line_clear(down_line);
            if(end_block){
                Tetris.Timer.remove(timer_id);
                this.lock = false;
                this.line_check();
                return;
            }
            line--;
            this.draw();
        }.bind(this)};
        Tetris.Timer.add(block_down);
        this.lock = true;
    },
    check_block : function(line){
        var i;
        for(i=0;i<Tetris.WIDHT;i++){
            if(Tetris.Tetris_piece[line][i] == -1) return false;
        }
        return true;
    },
    check_off_block : function(line){
        var i;
        for(i=0;i<Tetris.WIDHT;i++){
            if(Tetris.Tetris_piece[line][i] != -1) return false;
        }
        return true;
    },
    gameOver : function(){
        this.lock = true;
        $('#Game_over').fadeIn('slow');
        $('#play').fadeIn('slow');
        
    },
    random : function(){
        var no = Math.floor(Math.random()*10);
        return no % Tetris.piece_type.length;
    },
    draw : function(){
        this.screen_clear();
        this.current_piece.current_draw();
        this.next_piece.next_draw();
        var y , x ;
        for(y=0;y<Tetris.HEIGHT;y++){            
            for(x=0;x<Tetris.WIDHT;x++){
                var index = Tetris.Tetris_piece[y][x];
                if(index != -1){
                    var color = Tetris.piece_color[index];
                    this.current_draw(x,y,color);
                }
            }
        }
    },
    current_draw : function(x,y,c){
        var current_x = x * Tetris.BLOCK_SIZE;
        var current_y = y * Tetris.BLOCK_SIZE;
        Tetris.Tetris_currnet_canvas.fillStyle = c;
        Tetris.Tetris_currnet_canvas.fillRect(current_x,current_y,Tetris.BLOCK_SIZE,Tetris.BLOCK_SIZE);
    },
    next_draw : function(x,y,c){
        Tetris.Tetris_next_canvas.fillStyle = c;
        Tetris.Tetris_next_canvas.fillRect(x,y,Tetris.BLOCK_SIZE,Tetris.BLOCK_SIZE);
    }
};

/*
 * 
 *  KeyBoard Task
 * 
 */

Tetris.KeyBoard = {
    setup : function(){        
        window.focus();
        this.qt = [];
        this.down_sw = false;
        $(document).bind("keydown", this.keydown.bind(this));
        $(document).bind("keyup",function(){window.focus(); this.down_sw = false;}.bind(this));
    },
    add : function(p){
        var evet = Tetris.extend({object:null , id:null , post:null},p || {});
        this.qt.push(evet);
    },
    remove : function(p){
        for(var i=0;i<this.qt.length;i++){
            if(this.qt[i].object == p){
                this.qt.splice(i,1);
                break;
            }
        }
    },
    keydown : function(e){
        if(this.down_sw) return;
        window.focus();
        switch (e.keyCode) {
        case Tetris.key_code.UP: this.post('UP'); return false;
        case Tetris.key_code.DOWN: this.post('DOWN'); return false;
        case Tetris.key_code.RIGTH: this.post('RIGTH'); return false;
        case Tetris.key_code.LEFT: this.post('LEFT'); return false;
        case Tetris.key_code.SPACE: this.post('UP'); return false;
        } 
    },
    post : function(i){
        var id = i;
        this.qt.forEach(function(e,i,a){if(e.id == id) e.post();});
    }
};

/*
 * 
 *  Timer Task
 * 
 */
Tetris.Timer = {
    setup : function(){
        this.qt = [];
        this.timer = setInterval(this.onLoop.bind(this),33);
    },
    add : function(p){
        var evet = Tetris.extend({object:null , post:null},p || {});
        this.qt.push(evet);
    },
    remove : function(p){
        for(var i=0;i<this.qt.length;i++){
            if(this.qt[i].object == p){
                this.qt.splice(i,1);
                break;
            }
        }
    },
    onLoop : function(){
        this.qt.forEach(function(e,i,a){e.post();});
    }
};

/*
 * 
 *  Score Task
 * 
 */

Tetris.Score = {
    setup : function(){
        this.qt = [];
        this.score = 0;
        this.current_dom = $('<p>').text('00000').css({
            position: 'absolute',
            top: '-30px',
            width: '60px',
            height: '20px',
            paddingLeft: '10px',
            backgroundColor : 'white'
        }).appendTo('#Score');
        this.lock = false;
    },
    add : function(){
       this.score += 10;
       var data = '00000'+String(this.score);
       var pos = data.length - 5;
       data = data.substring(pos);
       var dom = $('<p>').text(data).css({
            position: 'absolute',
            top: '-15px',
            width: '60px',
            height: '20px',
            paddingLeft: '10px',
            backgroundColor : 'white'
       });
       this.qt.push(dom);
       this.score_up();
    },
    score_up : function(){
        if(this.qt.length != 0 && !this.lock){
            this.lock = true;
            var dom = this.qt.shift();
            $(dom).appendTo('#Score');
            $(dom).animate(
                { top : -30 },
                {
                    duration: 1000,
                    complete: function(){
                        this.lock = false;
                        $(this.current_dom).remove();
                        this.current_dom = dom;
                        if(this.qt.length != 0) this.score_up();
                    }.bind(this)
                 }
            );
        }
    }
}

/*
 * 
 *  Game Over
 * 
 */

Tetris.GameOvaer = function(){
    var Context = $('#Game_over')[0].getContext("2d");
    $('#Game_over').hide();
    Context.font = 'normal bold 23px fantasy';
    Context.fillStyle = 'red';
    Context.textBaseline = 'top';
    Context.shadowColor = "#A0A0A0";
    Context.shadowOffsetX = 5;
    Context.shadowOffsetY = 5;
    Context.shadowBlur = 5;
    Context.fillText( 'Game Over', 0, 0);    
}

/*
 * 
 *  ON Load
 * 
 */
$(function(){
    Tetris.Control.setup();
    Tetris.GameOvaer();
    $('#play').click(
        function(){
            $(this).fadeOut('slow');
            $('#Game_over').fadeOut('slow');
            Tetris.Control.game_start();
        }
    );
});
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js