<div id='js-event-counter' class='event-counter'>0</div>

<div id='js-score' class='score'>0</div>

<div id='js-level' class='level'>0</div>

<div id='js-tetris' class='grid' data-component='grid'></div>

<div id='js-next-piece' class='preview-cells'></div>
@import "compass/css3";

*,
*:after,
*:before
{
    -moz-box-sizing: border-box; 
    -webkit-box-sizing: border-box; 
    box-sizing: border-box;
    background-repeat: no-repeat;
    position: relative;
}

$cell-size:20px;
$grid-width:($cell-size * 10) + 2px;
$grid-height:($cell-size * 20) + 2px;
$preview-size:($cell-size * 5) + 2px;

/* needs to correspond to piece.js */
$colours:
    ('white',#f0f0f0),
    ('blue',#3498db),
    ('yellow',#f1c40f),
    ('green',#2ecc71),
    ('red',#e74c3c),
    ('grey',#95a5a6),
    ('black',#34495e),
    ('purple',#9b59b6),
    ('white',#ecf0f1),
    ('orange',#e67e22),
    ('turquoise',#1abc9c);

@each $colour, $hex in $colours
{
    .#{$colour}
    {
        background-color:$hex; 
        border:1px solid darken($hex,20);
        box-shadow:inset 0 0 1px 1px lighten($hex,20);
    }
}

body { background-color:#222; }

.event-counter
{
    width:$grid-width;
    height:20px;
    margin:20px auto;
    font-size:10px;
    text-align:center;
    color:#f0f0f0;
    opacity:0;
}

.score,
.level
{
    position:fixed;
    left:463px;
    z-index:1;
    width:$grid-width;
    height:40px;
    margin:10px auto;
    font-size:20px;
    text-align:center;
    font-family: Consolas, monaco, monospace;
    color:#f0f0f0;
}

.score
{
    top:150px;
    font-weight:bold;
    &:before
    {
        content:'score: ';
    }
}

.level
{
    top:175px;
    &:before
    {
        content:'level: ';
    }
}

.grid
{
    width:$grid-width;
    height:$grid-height;
    position:fixed;
    top:50px;
    left:300px;
    border:1px solid darken(#f0f0f0,20);
    box-shadow:0 0 200px 0 #000;
}


.preview-cells
{
    width:$preview-size;
    height:$preview-size;
    position:fixed;
    top:50px;
    left:520px;
    border:1px solid darken(#f0f0f0,20);
}

.cell
{
    width:$cell-size;
    height:$cell-size;
    display:inline-block;
    float:left;
}
View Compiled
Config = {
    size:{
        'width':10,
        'height':20
    },
    levels:{
        0:{
            'interval':750,
            'threshold':25
        },
        1:{
            'interval':725,
            'threshold':50
        },
        2:{
            'interval':700,
            'threshold':75
        },
        3:{
            'interval':675,
            'threshold':100
        },
        4:{
            'interval':650,
            'threshold':125
        },
        5:{
            'interval':625,
            'threshold':150
        },
        6:{
            'interval':600,
            'threshold':175
        },
        7:{
            'interval':580,
            'threshold':200
        },
        8:{
            'interval':560,
            'threshold':225
        },
        9:{
            'interval':540,
            'threshold':250
        },
        10:{
            'interval':520,
            'threshold':275
        },
        11:{
            'interval':500,
            'threshold':300
        },
        12:{
            'interval':480,
            'threshold':325
        },
        13:{
            'interval':460,
            'threshold':350
        },
        14:{
            'interval':440,
            'threshold':375
        },
        15:{
            'interval':420,
            'threshold':400
        },
        16:{
            'interval':400,
            'threshold':425
        },
        17:{
            'interval':380,
            'threshold':450
        },
        18:{
            'interval':360,
            'threshold':475
        },
        19:{
            'interval':340,
            'threshold':500
        },
        20:{
            'interval':320,
            'threshold':525
        },
        21:{
            'interval':300,
            'threshold':550
        },
        22:{
            'interval':285,
            'threshold':575
        },
        23:{
            'interval':270,
            'threshold':600
        },
        24:{
            'interval':255,
            'threshold':625
        },
        25:{
            'interval':240,
            'threshold':650
        },
        26:{
            'interval':225,
            'threshold':675
        },
        27:{
            'interval':210,
            'threshold':700
        },
        28:{
            'interval':195,
            'threshold':725
        },
        29:{
            'interval':180,
            'threshold':750
        },
        30:{
            'interval':165,
            'threshold':775
        },
        31:{
            'interval':155,
            'threshold':825
        },
        32:{
            'interval':145,
            'threshold':880
        },
        33:{
            'interval':140,
            'threshold':950
        },
        34:{
            'interval':135,
            'threshold':1050
        },
        35:{
            'interval':130,
            'threshold':1150
        },
        36:{
            'interval':125,
            'threshold':1250
        },
        37:{
            'interval':120,
            'threshold':1350
        },
        38:{
            'interval':115,
            'threshold':1500
        },
        39:{
            'interval':110,
            'threshold':2000
        },
        40:{
            'interval':100,
            'threshold':5000
        }
    }
}



;(function() {

    'use strict';

    function Grid(gridDiv) {

        this.gridDiv=document.getElementById(gridDiv);

        this.inititialiseCells();

        this.previewDiv=document.getElementById('js-next-piece');

        this.initialisePreviewCells();

        this.timer=new window.Timer(this);

        this.keyListener=new window.KeyListener(this);

        this.startGame();

    }

    Grid.prototype = {

        // initial grid size
        size:Config.size,

        // the div this grid appears in
        gridDiv:{},

        // an array of cells, based on size
        cells:[],

        // current level
        level:0,

        // level definitions, such as interval, score thresh-hold etc
        levels:Config.levels,

        // the currently generated pieces
        pieces:[],    

        // preview panel
        previewCells:[],
        previewDiv:{},

        // the running score total
        score:0,  

        /**
         *  inititialiseCells() - builds an array of cell objects
         */
        inititialiseCells:function()
        {

            for (var y=0;y<this.size.height;y++)
            {    
                this.cells[y]=[];

                for (var x=0;x<this.size.width;x++)
                {
                    this.cells[y][x]=new window.Cell(x,y);

                    this.cells[y][x].buildCellHtml()
                    this.cells[y][x].appendCell(this.gridDiv);
                }
            }

        },  

        /**
         *  initialisePreviewCells() - sets up the piece preview cells
         */
        initialisePreviewCells: function()
        {

            for (var y=0; y<5; y++)
            {

                this.previewCells[y]=[];

                for (var x=0; x<5; x++)
                {

                    this.previewCells[y][x]=new window.Cell(x,y);

                    this.previewCells[y][x].buildCellHtml()
                    this.previewCells[y][x].appendCell(this.previewDiv);

                }

            }

        },

        /**
         *  startGame() - triggers a game, including piece generation and timing
         */
        startGame:function()
        {

            this.addPieceToGame();

            this.addPieceToGame();

            this.pieces[0].displayPreviewPiece(this.previewCells);

            this.pieces[1].displayPiece(this.cells);

            this.timer.startTimer();

        },      

        /**
         *  addPieceToGame() - generates a new piece object for play
         */
        addPieceToGame:function()
        {

            this.pieces.unshift(new Piece());

        },

        /**
         *  outputScore() - put the score on the screen
         */
        outputScore: function()
        {

            document.getElementById("js-score").innerHTML=this.score;

        },

        /**
         *  outputLevel() - put the level on the screen
         */
        outputLevel: function()
        {

            document.getElementById("js-level").innerHTML=this.level;

        },

        /**
         *  findCompletedRows() - looks for completed rows now this piece is stopped
         */
        findCompletedRows: function(piecePosition)
        {

            // get the rows this piece occupies
            var rowsToCheck={};
            for (var index in piecePosition)
            {

                rowsToCheck[piecePosition[index].y]=1;

            }

            // iterate over those rows checking for a full set of states
            var removeRows={};
            for (var row in rowsToCheck)
            {

                var cellCount=0;

                for (var x=0; x<this.size.width; x++)
                {

                    cellCount+=this.cells[row][x].state;

                }

                if (cellCount === this.size.width)
                {

                    removeRows[row]=1;

                }

            }

            // if completed then trigger completed row function
            var score=Object.keys(removeRows).length * Object.keys(removeRows).length;
            this.score+=score;
            this.outputScore();
            for (var removeRow in removeRows)
            {

                // set all cells in the row to white and state=0
                for (var x=0; x<this.size.width; x++)
                {

                    this.cells[removeRow][x].unmarkCell();

                    // look up this column setting colour and state 
                    for (var y=removeRow; y>=0; y--)
                    {

                        if ((y-1)>=0)
                        {

                            if (1==this.cells[y-1][x].state)
                            {

                                this.cells[y][x].markCell(this.cells[y-1][x].colour);

                            }
                            else
                            {

                                this.cells[y][x].unmarkCell();

                            }

                        }


                    }

                }

            }

        }

    };

    window.Grid = Grid;

}());



;(function() {

    'use strict';

    function Piece() {

        this.currentPosition={};

        this.selectShape();

        this.selectColour();

        this.allowedMoves={
            'left':true,
            'right':true,
            'down':true,
            'rotate':true
        };

        this.stopped=false;

        this.devOutput();

    }

    Piece.prototype = {

        // stores what moves can be made from the current piece position
        allowedMoves:{
            'left':true,
            'right':true,
            'down':true,
            'rotate':true
        },

        // stores the colour of this piece
        colour:'',

        // the set of colours this piece may take, needs to correspond to _colours.scss
        colours:{
            0:'blue',
            1:'green',
            2:'yellow',
            3:'red',
            4:'grey',
            5:'black',
            6:'purple',
            7:'orange',
            8:'turquoise'
        },

        // the current set of co-ordinates for this piece, four x/ys
        currentPosition:{},

        // the current orientation of this piece
        currentOrientation:0,

        // as above two but for next
        nextPosition:{},
        nextOrientation:0,

        // the shape of this piece
        shape:{},

        // shapes including their initial positions on a ten wide grid
        // positions can be centred for other grid widths by incrementing
        // or decrementing all x co-ordinates
        shapes:{
            // I 0123456789  0123456789   
            // 0 .....#....  ..........  
            // 1 .....#....  ...####...  
            // 2 .....#....  ..........  
            // 3 .....#....  ..........  
            0:
            {               
                0:{0:{x:5,y:0},1:{x:5,y:1},2:{x:5,y:2},3:{x:5,y:3}},
                1:{0:{x:3,y:1},1:{x:4,y:1},2:{x:5,y:1},3:{x:6,y:1}} 
            },
            // J 0123456789  0123456789  0123456789  0123456789     
            // 0 .....#....  ....#.....  .....##...  ..........    
            // 1 .....#....  ....###...  .....#....  ....###...    
            // 2 ....##....  ..........  .....#....  ......#...    
            // 3 ..........  ..........  ..........  ..........    
            1:
            {               
                0:{0:{x:5,y:0},1:{x:5,y:1},2:{x:4,y:2},3:{x:5,y:2}},               
                1:{0:{x:4,y:0},1:{x:4,y:1},2:{x:5,y:1},3:{x:6,y:1}}, 
                2:{0:{x:5,y:0},1:{x:6,y:0},2:{x:5,y:1},3:{x:5,y:2}},                 
                3:{0:{x:4,y:1},1:{x:5,y:1},2:{x:6,y:1},3:{x:6,y:2}}
            },
            // L 0123456789  0123456789  0123456789  0123456789 
            // 0 .....#....  ..........  ....##....  ......#...
            // 1 .....#....  ....###...  .....#....  ....###...
            // 2 .....##...  ....#.....  .....#....  ..........
            // 3 ..........  ..........  ..........  ..........
            2:
            {
                0:{0:{x:5,y:0},1:{x:5,y:1},2:{x:5,y:2},3:{x:6,y:2}},                
                1:{0:{x:4,y:1},1:{x:5,y:1},2:{x:6,y:1},3:{x:4,y:2}},                
                2:{0:{x:4,y:0},1:{x:5,y:0},2:{x:5,y:1},3:{x:5,y:2}},                
                3:{0:{x:6,y:0},1:{x:4,y:1},2:{x:5,y:1},3:{x:6,y:1}}
            },
            // O 0123456789
            // 0 ....##....
            // 1 ....##....
            // 2 ..........
            // 3 ..........
            3:
            {
                0:{0:{x:4,y:0},1:{x:5,y:0},2:{x:4,y:1},3:{x:5,y:1}}
            },
            // S 0123456789  0123456789   
            // 0 .....##...  ....#.....  
            // 1 ....##....  ....##....  
            // 2 ..........  .....#....  
            // 3 ..........  ..........
            4:
            {
                0:{0:{x:5,y:0},1:{x:6,y:0},2:{x:4,y:1},3:{x:5,y:1}},                
                1:{0:{x:4,y:0},1:{x:4,y:1},2:{x:5,y:1},3:{x:5,y:2}}
            },
            // T 0123456789  0123456789  0123456789  0123456789 
            // 0 ..........  .....#....  .....#....  .....#....
            // 1 ....###...  ....##....  ....###...  .....##...
            // 2 .....#....  .....#....  ..........  .....#....
            // 3 ..........  ..........  ..........  ..........
            5:
            {
                0:{0:{x:4,y:1},1:{x:5,y:1},2:{x:6,y:1},3:{x:5,y:2}},                
                1:{0:{x:5,y:0},1:{x:4,y:1},2:{x:5,y:1},3:{x:5,y:2}},
                2:{0:{x:5,y:0},1:{x:4,y:1},2:{x:5,y:1},3:{x:6,y:1}},                
                3:{0:{x:5,y:0},1:{x:5,y:1},2:{x:6,y:1},3:{x:5,y:2}}
            },
            // Z 0123456789  0123456789   
            // 0 ....##....  .....#....  
            // 1 .....##...  ....##....  
            // 2 ..........  ....#.....  
            // 3 ..........  ..........
            6:
            {
                0:{0:{x:4,y:0},1:{x:5,y:0},2:{x:5,y:1},3:{x:6,y:1}},                
                1:{0:{x:5,y:0},1:{x:4,y:1},2:{x:5,y:1},3:{x:4,y:2}}
            }
        },

        // is this piece stopped, ie rached as far down as it will go and so a new piece is required
        stopped:false,

        /**
         *  countElements() - count the elements in an object
         *
         *  @param count - integer count of object properties
         */
        countElements: function(obj)
        {

            return Object.keys(obj).length;

        },

        /**
         *  selectShape() - selects a shape and orientation
         */
        selectShape:function()
        {

            // select one of seven pieces at random
            this.shape=this.shapes[this.selectRandom(this.shapes)];

            // select a current orientation from that shape
            this.currentOrientation=this.selectRandom(this.shape);

            // clone that orientation into the current position property so
            // shapes array is not edited on piece move
            this.cloneOrientation();

        },

        /**
         *  selectColour() - selects a colour for this piece
         */
        selectColour:function()
        {

            this.colour=this.colours[this.selectRandom(this.colours)];

        },

        /**
         *  selectRandom() - randomly selects a ref num from a count of object properties
         *
         *  @param count - integer count of object properties
         */
        selectRandom: function (obj) 
        {

            return Math.floor(Math.random() * this.countElements(obj));

        },

        /**
         *  cloneOrientation() - copy the orientation into the object from the shapes
         */
        cloneOrientation: function()
        {
            this.currentPosition={
                0:{
                    x:this.shape[this.currentOrientation][0].x,
                    y:this.shape[this.currentOrientation][0].y
                },
                1:{
                    x:this.shape[this.currentOrientation][1].x,
                    y:this.shape[this.currentOrientation][1].y
                },
                2:{
                    x:this.shape[this.currentOrientation][2].x,
                    y:this.shape[this.currentOrientation][2].y
                },
                3:{
                    x:this.shape[this.currentOrientation][3].x,
                    y:this.shape[this.currentOrientation][3].y
                }
            }
        },

        /**
         *  getNextOrientation() - looks at the next orientation for this piece
         */
        getNextOrientation: function()
        {

            var count=this.countElements(this.shape);

            if (this.currentOrientation === (count - 1))
            {

                var orientation=this.shape[0];

                this.nextOrientation=0;

                return orientation;

            }
            else
            {

                var orientation=this.shape[this.currentOrientation + 1];

                this.nextOrientation=this.currentOrientation + 1;

                return orientation;

            }

        },

        /**
         *  displayPiece() - set the current piece to be visible on the screen in its current
         *      position
         *  @param cells - the array of cells
         */
        displayPiece:function(cells)
        {

            for (var index in this.currentPosition)
            {

                var coordinates=this.currentPosition[index];

                cells[coordinates.y][coordinates.x].markCell(this.colour);

            }

        },

        /**
         *  displayPreviewPiece() - place a piece in the preview window
         */
        displayPreviewPiece: function(previewCells)
        {

            for (var y=0; y<5; y++)
            {

                for (var x=0; x<5; x++)
                {

                    previewCells[y][x].unmarkCell();

                }

            }

            for (var index in this.currentPosition)
            {

                var coordinates=this.currentPosition[index];

                previewCells[coordinates.y+1][coordinates.x-3].markCell(this.colour);

            }

        },

        /**
         *  movePiece() - moves a piece one cell in the given direction
         *  @param cells - array of cells for redrawing
         *  @param direction - left, right or down
         *  @param interval - timing interval for pausing set stopped action
         */
        movePiece:function(cells,direction,interval)
        {

            if (false === this.checkMove('down') &&
                'down' === direction)
            {
                // delay set stopped so that piece can be moved either side
                var pauseInterval=interval-1; // remove one millisecond so as not to interfere with next interval

                var t=this;

                setTimeout(function() { t.setStopped(); },pauseInterval);

            }
            else
            {

                if (true === this.checkMove(direction))
                {

                    for (var index in this.currentPosition)
                    {

                        var coordinates=this.currentPosition[index];

                        cells[coordinates.y][coordinates.x].unmarkCell();

                        this.setNewCoordinates(coordinates,direction);

                    }

                    this.setAllowedMoves(cells);

                    this.displayPiece(cells);

                }

            }

        },

        /**
         *  setNewCoordinates() - sets the new coordinates based on direction
         *  @param currentCoordinates - the cell coordinates to work on
         *  @param direction - direction the piece should move
         */
        setNewCoordinates:function (currentCoordinates,direction)
        {
            
            switch(direction)
            {
                case 'left':
                    currentCoordinates.x--;
                    break;
                case 'right':
                    currentCoordinates.x++;
                    break;
                case 'down':
                    currentCoordinates.y++;
                    break;
            }     
            
        },

        /**
         *  rotate() - rotate the current piece clockwise
         */
        rotate:function(cells)
        {

            if (true === this.checkMove('rotate'))
            {
                // get the next orientation
                this.nextPosition=this.getNextOrientation();

                // compute cell differences, i.e. the relationship between current and next
                // use the original orientation
                var compareOrientation=this.shape[this.currentOrientation];

                // get x and y offset from the original orientation position
                var xoffset=this.currentPosition[0].x - compareOrientation[0].x;
                var yoffset=this.currentPosition[0].y - compareOrientation[0].y;

                for (var index in this.currentPosition)
                {

                    var coordinates=this.currentPosition[index];

                    cells[coordinates.y][coordinates.x].unmarkCell();

                    this.currentPosition[index].x=(this.nextPosition[index].x + xoffset);
                    this.currentPosition[index].y=(this.nextPosition[index].y + yoffset);

                }

                this.setAllowedMoves(cells);

                this.displayPiece(cells);

                this.currentOrientation=this.nextOrientation;

            }

        },

        /**
         *  setStopped() - set this piece as stopped, this is checked by the interval timer
         *      and used to generate the new pieces
         */
        setStopped: function ()
        {

            this.stopped=true;

        },

        /**
         *  resetAllowedMoves() - sets all allowed moves to true - required so that a piece
         *      move made before into an unmovable position doesn't block a legal move now
         */
        resetAllowedMoves: function()
        {

            this.allowedMoves['left']=true;
            this.allowedMoves['right']=true;
            this.allowedMoves['down']=true;
            this.allowedMoves['rotate']=true;

        },

        /**
         *  setAllowedMoves() - takes the current position and checks for the allowed moves
         *  @param cells - the array of cells
         */
        setAllowedMoves: function(cells)
        {

            this.resetAllowedMoves();

            for (var index in this.currentPosition)
            {

                var coordinates=this.currentPosition[index];

                if (0 === coordinates.x ||
                    1 === cells[coordinates.y][(coordinates.x - 1)].state)
                {

                    this.allowedMoves['left']=false;

                }

                if ((Config.size.width - 1) === coordinates.x ||
                    1 === cells[coordinates.y][(coordinates.x + 1)].state)
                {

                    this.allowedMoves['right']=false;

                }

                if ((Config.size.height - 1) === coordinates.y ||
                    1 === cells[(coordinates.y + 1)][coordinates.x].state)
                {

                    this.allowedMoves['down']=false;

                }

            }

            // rotate
            // get the next orientation
            var nextPositionTest=this.getNextOrientation();

            // compute cell differences, i.e. the relationship between current and next
            // use the original orientation
            var compareOrientation=this.shape[this.currentOrientation];

            // get x and y offset from the original orientation position
            var xoffset=this.currentPosition[0].x - compareOrientation[0].x;
            var yoffset=this.currentPosition[0].y - compareOrientation[0].y;

            for (var index in this.currentPosition)
            {

                var coordinates=this.currentPosition[index];

                var nextPosX=nextPositionTest[index].x + xoffset;
                var nextPosY=nextPositionTest[index].y + yoffset;

                if (nextPosX<0 ||
                    nextPosX>=Config.size.width)
                {

                    this.allowedMoves['rotate']=false;

                    break;

                }

                if (nextPosY<0 ||
                    nextPosY>=Config.size.height)
                {

                    this.allowedMoves['rotate']=false;

                    break;

                }

                if (1 === cells[nextPosY][nextPosX].state)
                {

                    this.allowedMoves['rotate']=false;

                    break;
                    
                }

            }
 
        },

        /**
         *  checkMove() - looks at allowed moves to see if the move event is viable
         */
        checkMove:function(move)
        {

            return this.allowedMoves[move];

        },

        /**
         *  devOutput() - adds this pieces data to the piece array dev output
         */
        devOutput: function()
        {

            var devPieceOut=document.createElement("div");

            var coordinates="<span class='dev-coordinates'>";

            var c=1;

            for (var coords in this.currentPosition)
            {

                coordinates+=c+"y:"+this.currentPosition[coords].y+" x:"+this.currentPosition[coords].x+"; ";

                c++;
            }

            coordinates+="</span><br/><br/>";

            devPieceOut.innerHTML=coordinates;

            var allowedMoves="<span class='dev-allowed'>";

            for (var allowed in this.allowedMoves)
            {

                allowedMoves+=this.allowedMoves[allowed]+"; ";

                c++;
            }

            allowedMoves+="</span><br/><br/>";

            devPieceOut.innerHTML=JSON.stringify(this,null,4)+"<br/><br/>";

            //devPieceOut.innerHTML=JSON.stringify(this.shapes,null,4)+"<br/><br/>";

            //document.getElementById("js-piece-data").appendChild(devPieceOut);

        }

    };

    window.Piece = Piece;

}());



;(function() {

    'use strict';

    function Cell(x,y) {

        this.x=x;
        this.y=y;

    }

    Cell.prototype = {

        // the div for this cell, with it's classes for colour
        cellHtml:'',

        // dev output to show cell state
        devState:false,

        // cells colour
        colour:'white',

        // the piece to which this cell currently belongs
        piece:{},

        // is this cell filled or not, filled cells restrict moves
        state:0,

        // co-ordinates
        x:0,
        y:0,

        /**
         * appendCell() - adds a cell to the grid div
         * @param gridDiv - the div which contains the playing grid
         */
        appendCell: function(gridDiv)
        {

            gridDiv.appendChild(this.cellHtml);

        },

        /**
         * buildCellHtml() - builds some html for this cell
         */
        buildCellHtml: function()
        {

            this.cellHtml=document.createElement("div");

            this.addDevOutput();

            this.setHtmlClass();

        },

        /**
         * markCell() - marks a cell as filled and coloured
         *
         * @param colour - colour to mark the cell with
         */
        markCell: function(colour)
        {

            this.setColour(colour);

            this.setState(1);

        },

        /**
         * unmarkCell() - unmarks a cell, back to white and empty
         */
        unmarkCell: function()
        {

            this.setColour('white');

            this.setState(0);

        },

        /**
         * setColour() - sets the colour for this cell, html and 
         */
        setColour: function(colour)
        {

            this.colour=colour;

            this.setHtmlClass();

        },

        /**
         * setState() - sets a cells state
         *
         * @param state - Boolean part of a piece or not
         */
        setState: function(state)
        {

            this.state=state;

            if (true === this.devState)
            {

                document.getElementById('js-state-dev-'+this.x+'-'+this.y).innerHTML=state;

            }

        },

        /**
         * setHtmlClass() - sets the class for this cell
         */
        setHtmlClass: function()
        {

            this.cellHtml.setAttribute("class","cell "+this.colour);

        },

        /** 
         * addDevOutput() - adds x, y and state values to cells for dev work
         */
        addDevOutput: function()
        {

            if (true === this.devState)
            {

                this.cellHtml.innerHTML="<span id='js-x-dev' class='cell-dev x-dev'>"+this.x+"</span><span id='js-y-dev y-dev' class='cell-dev'>"+this.y+"</span><span id='js-state-dev-"+this.x+"-"+this.y+"' class='cell-dev state-dev'>"+this.state+"</span>";

            }

        }



    };

    window.Cell = Cell;

}());



;(function() {

    'use strict';

    var t;

    function KeyListener(grid,timer) {

        t=this;

        t.grid=grid;

        t.timer=grid.timer;

        t.inititialiseKeys();

    }

    KeyListener.prototype = {  

        // the grid the events happen to
        grid:{},     

        // the timer triggering intervals
        timer:{}, 

        /**
         * inititialiseKeys() - adds an event listener for keypresses
         */
        inititialiseKeys:function()
        {

            window.addEventListener('keydown',t.keyListener);

        },

        /**
         * keyListener() - responds to the key presses for game play
         */
        keyListener:function(keyEvent)
        {
            
            var cells=t.grid.cells;

            var inPlayPiece=t.grid.pieces[1];

            switch (keyEvent.keyCode)
            {

                case 37:                  
                    keyEvent.preventDefault();
                    inPlayPiece.movePiece(cells,'left',t.timer);
                    break;
                case 39:
                    keyEvent.preventDefault();
                    inPlayPiece.movePiece(cells,'right',t.timer);
                    break;
                case 40:
                    keyEvent.preventDefault();
                    inPlayPiece.movePiece(cells,'down',t.timer);
                    break;

                case 32:
                    keyEvent.preventDefault();
                    inPlayPiece.rotate(cells);
                    break;

                case 49: // 1 key for pausing
                    keyEvent.preventDefault();
                    if (true === t.timer.running)
                    {
                        t.timer.pauseTimer();
                    }
                    else
                    {
                        t.timer.startTimer();
                    }
                    break;


            }

        }

    };

    window.KeyListener = KeyListener;

}());;(function() {

    'use strict';

    var t;

    function Timer(grid) {

        t=this;

        t.grid=grid;

        t.currentInterval=t.grid.levels[0]['interval'];

        t.eventCounterSpan=document.getElementById('js-event-counter');

    }

    Timer.prototype = {

        // the current number of milliseconds between timer event
        currentInterval:0,

        // a counter for the number of events since game started
        eventCount:0,

        // a dom element for outputting event count (dev)
        eventCounterSpan:{},

        // stores the interval id for pausing (useful for dev)
        intervalId:null,

        // state, if running === true then the timer is running, else it's paused
        running:true,

        /**
         * startTimer() - sets the timer running in response to a game start
         */
        startTimer:function()
        {

            t.intervalId = setInterval(t.intervalTrigger,t.currentInterval);

            t.running=true;

        },

        /**
         * pauseTimer() - pause the timer
         */
        pauseTimer:function()
        {

            window.clearInterval(t.intervalId);

            t.running=false;

        },

        /**
         * intervalTrigger() - function called each time an interval elapsed
         */
        intervalTrigger:function()
        {

            var pieces=t.grid.pieces;

            t.eventCount++;

            t.eventCounterSpan.innerHTML=t.eventCount;

            pieces[1].movePiece(t.grid.cells,'down',t.currentInterval);

            if (true === pieces[1].stopped)
            {

                pieces[1].displayPiece(t.grid.cells);

                t.grid.findCompletedRows(pieces[1].currentPosition);

                pieces.unshift(new Piece());

                pieces[0].displayPreviewPiece(t.grid.previewCells);

                // stop game if new piece won't fit
                var gameOver=false;
                for (var index in pieces[1].currentPosition)
                {

                    var coordinates=pieces[1].currentPosition[index];

                    if (1 === t.grid.cells[coordinates.y][coordinates.x].state)
                    {

                        gameOver=true;

                        break;

                    }

                }

                if (true === gameOver)
                {
                    t.pauseTimer();                 
                }

            }

            // do levels
            var levelData=t.grid.levels[t.grid.level];
            if (t.eventCount>=levelData.threshold)
            {

                t.grid.level++;

                t.grid.outputLevel();

                t.pauseTimer();

                t.currentInterval=levelData.interval;

                t.startTimer()

            }

        }

    };

    window.Timer = Timer;

}());





window.tetris=new window.Grid('js-tetris');

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.