Saturday, 8 July 2023

Web Tutorial: Tetris in vanilla JavaScript (Part 2/4)

This is the bread and butter of the Tetris game - blocks! There are a few different kinds of blocks in the game, and each of them can be rotated four ways. To represent that, let's create the shapes array in the game object.
<script>
    var game = {
        paused: false,
        stopped: false,
        shapes:
        [
        
        ]

    }
</script>


There are five different shapes, so let's have five elements in this array. Each of these are, in turn, arrays.
shapes:
[
    [
            
    ],
    [
    
    ],
    [
    
    ],
    [
    
    ],
    [
    
    ]

]


Each of these shapes have four rotations. So give them four elements. Again, these are also arrays.
shapes:
[
    [
        [
        
        ],
        [
        
        ],
        [
        
        ],
        [
        
        ]

    ],
    [
        [
        
        ],
        [
        
        ],
        [
        
        ],
        [
        
        ]

    ],
    [
        [
        
        ],
        [
        
        ],
        [
        
        ],
        [
        
        ]

    ],
    [
        [
        
        ],
        [
        
        ],
        [
        
        ],
        [
        
        ]

    ],
    [
        [
        
        ],
        [
        
        ],
        [
        
        ],
        [
        
        ]

    ]
]


Each rotation is a 4 by 4 grid, so let's have 4 elements - each element is an array of 4 elements, all 0s for now.
shapes:
[
    [
        [
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],

        ],
        [
        
        ],
        [
        
        ],
        [
        
        ]
    ],
    [
        [
        
        ],
        [
        
        ],
        [
        
        ],
        [
        
        ]
    ],
    [
        [
        
        ],
        [
        
        ],
        [
        
        ],
        [
        
        ]
    ],
    [
        [
        
        ],
        [
        
        ],
        [
        
        ],
        [
        
        ]
    ],
    [
        [
        
        ],
        [
        
        ],
        [
        
        ],
        [
        
        ]
    ]
]


Now let's say we want to create a square block. We replace the 0s in this "matrix" with 1s where appropriate. The way I have written this code, it's easy to discern the shape.
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 1, 1, 0]


What about the rest? Well, this is easy, because the square block looks the same no matter what way you rotate it! So just repeat for the other array element.
[
    [
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 1, 1, 0],
        [0, 1, 1, 0]
    ],
    [
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 1, 1, 0],
        [0, 1, 1, 0]

    ],
    [
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 1, 1, 0],
        [0, 1, 1, 0]

    ],
    [
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 1, 1, 0],
        [0, 1, 1, 0]

    ]
],


The next one we will do is the long straight block. It has only two different views - either horizontally or vertically.
[
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [1, 1, 1, 1]

],
[
    [0, 1, 0, 0],
    [0, 1, 0, 0],
    [0, 1, 0, 0],
    [0, 1, 0, 0]

],
[
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [1, 1, 1, 1]

],
[
    [0, 1, 0, 0],
    [0, 1, 0, 0],
    [0, 1, 0, 0],
    [0, 1, 0, 0]

]


Here is the completed shapes property, with all the shapes and their possible rotations.
shapes:
[
    [
        [
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 1, 1, 0],
            [0, 1, 1, 0]
        ],
        [
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 1, 1, 0],
            [0, 1, 1, 0]
        ],
        [
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 1, 1, 0],
            [0, 1, 1, 0]
        ],
        [
            [0, 0, 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, 0],
            [1, 1, 1, 1]
        ],
        [
            [0, 1, 0, 0],
            [0, 1, 0, 0],
            [0, 1, 0, 0],
            [0, 1, 0, 0]
        ],
        [
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [1, 1, 1, 1]
        ],
        [
            [0, 1, 0, 0],
            [0, 1, 0, 0],
            [0, 1, 0, 0],
            [0, 1, 0, 0]
        ]
    ],
    [
        [
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [1, 1, 0, 0],
            [0, 1, 1, 0]

        ],
        [
            [0, 0, 0, 0],
            [0, 0, 1, 0],
            [0, 1, 1, 0],
            [0, 1, 0, 0]

        ],
        [
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [1, 1, 0, 0],
            [0, 1, 1, 0]

        ],
        [
            [0, 0, 0, 0],
            [0, 0, 1, 0],
            [0, 1, 1, 0],
            [0, 1, 0, 0]

        ]
    ],
    [
        [
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [1, 1, 1, 0],
            [0, 0, 1, 0]

        ],
        [
            [0, 0, 0, 0],
            [0, 0, 1, 0],
            [0, 0, 1, 0],
            [0, 1, 1, 0]

        ],
        [
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [1, 0, 0, 0],
            [1, 1, 1, 0]

        ],
        [
            [0, 0, 0, 0],
            [0, 1, 1, 0],
            [0, 1, 0, 0],
            [0, 1, 0, 0]

        ]
    ],
    [
        [
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 1, 0, 0],
            [1, 1, 1, 0]

        ],
        [
            [0, 0, 0, 0],
            [0, 1, 0, 0],
            [0, 1, 1, 0],
            [0, 1, 0, 0]

        ],
        [
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [1, 1, 1, 0],
            [0, 1, 0, 0]

        ],
        [
            [0, 0, 0, 0],
            [0, 1, 0, 0],
            [1, 1, 0, 0],
            [0, 1, 0, 0]

        ]
    ]
]


The block shapes have been defined, so we will need to fill in the grid. But before that, there's a few things we need to do. We need to define some new properties.
grid - an empty array by default, which will be used to populate the grid on screen.
colors - an array of five different colors.
currentBlock - an object, but null by default.
nextBlock - an object, but null by default.
var game = {
    grid: [],
    colors: ["red", "purple", "cyan", "yellow", "lime"],
    currentBlock: null,
    nextBlock: null,

    paused: false,
    stopped: false,


In the reset() method, we first give the grid array its shape. We begin by defining it as an empty array. Now, it's going to be  an array of 20 elements, each of which is another array of 10 elements. So for now, use a For loop to iterate from 0 to 19.
reset: function()
{
    this.grid = [];
    for(var i = 0; i < 20; i++)
    {

    }


    this.renderGrid();
},


Then inside the loop, we define temp as an empty array. We then have a nested For loop iterating from 0 to 9, and within it, we push the value null into temp. So we'll have an array of 10 null values!
reset: function()
{
    this.grid = [];
    for(var i = 0; i < 20; i++)
    {
        var temp = [];
        for(var j = 0; j < 10; j++)
        {
            temp.push(null);
        }

    }

    this.renderGrid();
},


Once done, we will push temp into grid. At the end of it, we will have a 20 by 10 elements array.
reset: function()
{
    this.grid = [];
    for(var i = 0; i < 20; i++)
    {
        var temp = [];
        for(var j = 0; j < 10; j++)
        {
            temp.push(null);
        }

        this.grid.push(temp);
    }

    this.renderGrid();
},


We set currentBlock to the object returned by calling getRandomBlock(). We do the same for nextBlock.
reset: function()
{
    this.grid = [];
    for(var i = 0; i < 20; i++)
    {
        var temp = [];
        for(var j = 0; j < 10; j++)
        {
            temp.push(null);
        }

        this.grid.push(temp);
    }

    this.currentBlock = this.getRandomBlock();
    this.nextBlock = this.getRandomBlock();


    this.renderGrid();
},


Time to define getRandomBlock().
renderGrid: function()
{
    var grid = document.getElementById("grid");

    grid.innerHTML = "";

    var pauseScreen = document.createElement("div");
    pauseScreen.id = "pauseScreen";
    grid.appendChild(pauseScreen);

    var stopScreen = document.createElement("div");
    stopScreen.id = "stopScreen";
    grid.appendChild(stopScreen);
},
getRandomBlock: function()
{

},

setPause: function(isAuto)    
{
    if (this.stopped) return;

    this.paused = (this.paused ? false : true);

    if (!isAuto)
    {
        var pauseScreen = document.getElementById("pauseScreen");
        pauseScreen.style.visibility = (this.paused ? "visible" : "hidden");
    }
},


Define shapeIndex and colorIndex. Using a JavaScript random number generator, we make shapeIndex any number from 0 to the number of shapes, minus 1. And colorIndex will be a number from 0 to the number of colors, minus 1.
getRandomBlock: function()
{
    var shapeIndex = Math.floor(Math.random() * this.shapes.length);
    var colorIndex = Math.floor(Math.random() * this.colors.length);

},


Then we return an object containing two properties. The first is shape, and for its value we pass in shapeIndex. The second is color and it will be slightly different; colorIndex will point to an element inside colors. Thus, in renderGrid(), currentBlock and nextBlock will be an object like this.
getRandomBlock: function()
{
    var shapeIndex = Math.floor(Math.random() * this.shapes.length);
    var colorIndex = Math.floor(Math.random() * this.colors.length);

    return {"shape": shapeIndex, "color": this.colors[colorIndex]};
},


The next thing we will do is work with the method populateAspect(). The parameter id is the id of the element we will be populating, So we begin by clearing that element of content.
getRandomBlock: function()
{
    var shapeIndex = Math.floor(Math.random() * this.shapes.length);
    var colorIndex = Math.floor(Math.random() * this.colors.length);

    return {"shape": shapeIndex, "color": this.colors[colorIndex]};
},
populateAspect: function(id, shape, color, className)
{
    var container = document.getElementById(id);
    container.innerHTML = "";                    
},

setPause: function(isAuto)    
{
    if (this.stopped) return;

    this.paused = (this.paused ? false : true);

    if (!isAuto)
    {
        var pauseScreen = document.getElementById("pauseScreen");
        pauseScreen.style.visibility = (this.paused ? "visible" : "hidden");
    }
},


The next parameter is shape, which will be the exact sub-array in shapes that we will be representing. We first iterate through the number of elements in shape.
populateAspect: function(id, shape, color, className)
{
    var container = document.getElementById(id);
    container.innerHTML = "";

    for (var i = 0; i < shape.length; i++)
    {
    
    }  
                 
},


Then we iterate through the number of elements in each element using a nested For.
populateAspect: function(id, shape, color, className)
{
    var container = document.getElementById(id);
    container.innerHTML = "";

    for (var i = 0; i < shape.length; i++)
    {
        for (var j = 0; j < shape[i].length; j++)
        {

        }  
 
    }                    
},


Create a div, square. Set its class to the last parameter, className. If the current element has the value of 1, add the third parameter, color, to the class.
populateAspect: function(id, shape, color, className)
{
    var container = document.getElementById(id);
    container.innerHTML = "";

    for (var i = 0; i < shape.length; i++)
    {
        for (var j = 0; j < shape[i].length; j++)
        {
            var square = document.createElement("div");
            square.className = className + " " + (shape[i][j] == 1 ? color : "");

        }    
    }                    
},


Then we append square to the containing element.
populateAspect: function(id, shape, color, className)
{
    var container = document.getElementById(id);
    container.innerHTML = "";

    for (var i = 0; i < shape.length; i++)
    {
        for (var j = 0; j < shape[i].length; j++)
        {
            var square = document.createElement("div");
            square.className = className + " " + (shape[i][j] == 1 ? color : "");

            container.appendChild(square);
        }    
    }                    
},


We first call this at the tail end of the reset() function. We use this to show the user which block is next. Thus, we pass into populateAspect() these arguments.
Firstly, "gridNext", which is the id of the container we will populate. Secondly, the shape pointed to by the shape property of nextBlock. Since we only want to show the first rotation, we use the first element. Thirdly, the color, which is the color property of nextBlock. Finally, the class, which is square_small.
reset: function()
{
    this.grid = [];
    for(var i = 0; i < 20; i++)
    {
        var temp = [];
        for(var j = 0; j < 10; j++)
        {
            temp.push(null);
        }

        this.grid.push(temp);
    }

    this.currentBlock = this.getRandomBlock();
    this.nextBlock = this.getRandomBlock();

    this.renderGrid();
    this.populateAspect("gridNext", this.shapes[this.nextBlock.shape][0], this.nextBlock.color, "square_small");
},


Before we go any further, we also need to define the square_small CSS class. It's basically a 10 pixel square, floated left.
#grid
{
    width: 200px;
    height: 400px;
    background-color: rgba(0, 0, 0, 0.5);
    margin: 5px auto 5px auto;
    position: relative;
    border: 5px inset rgb(155, 155, 155);
}

.square_small
{
    width: 10px;
    height: 10px;
    float: left;
}


#footer
{
    width: 100%;
    height: 100px;        
    text-align: center;
}


You see that we have a 3 by 3 grid of squares. But there's no block! That's because we have not yet handled colors in the CSS.




We also need to define styles for various colors. For, say, the color yellow, we have the yellow CSS class. We define a pseudoselector to create an inner square.
The border is a solid yellow and the background, a translucent yellow.
.pad button
{
    width: 100%;
    height: 100%;
    background: none;
    color: rgb(255, 255, 255);
    font-size: 1em;
}

.yellow::before
{
    content: "";
    display: block;
    border: 1px solid rgb(255, 255, 0);
    background-color: rgba(255, 255, 0, 0.8);
}


#pauseScreen, #stopScreen
{
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    visibility: hidden;
}


The rest of the colors are as follows. Notice we even define white even though it isn't in the colors array. That will come in useful later.
.buttons button
{
    width: 50px;
    height: 50px;
    border-radius: 50%;
    font-size: 3em;
}

.pad button
{
    width: 100%;
    height: 100%;
    background: none;
    color: rgb(255, 255, 255);
    font-size: 1em;
}

.yellow::before
{
    content: "";
    display: block;
    border: 1px solid rgb(255, 255, 0);
    background-color: rgba(255, 255, 0, 0.8);
}

.cyan::before
{
    content: "";
    display: block;
    border: 1px solid rgb(0, 255, 255);
    background-color: rgba(0, 255, 255, 0.8);
}

.lime::before
{
    content: "";
    display: block;
    border: 1px solid rgb(0, 255, 0);
    background-color: rgba(0, 255, 0, 0.8);
}

.purple::before
{
    content: "";
    display: block;
    border: 1px solid rgb(255, 0, 255);
    background-color: rgba(255, 0, 255, 0.8);
}

.red::before
{
    content: "";
    display: block;
    border: 1px solid rgb(255, 0, 0);
    background-color: rgba(255, 0, 0, 0.8);
}

.white::before
{
    content: "";
    display: block;
    border: 1px solid rgb(255, 255, 255);
    background-color: rgba(255, 255, 255, 0.8);
}


.square_small::before
{
    width: 8px;
    height: 8px;
}            

#pauseScreen, #stopScreen
{
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    visibility: hidden;
}


And then we apply a pseudselector for square_small, to set height and width to 8 pixels.
.white::before
{
    content: "";
    display: block;
    width: 18px;
    height: 18px;
    border: 1px solid rgb(255, 255, 255);
    background-color: rgba(255, 255, 255, 0.8);
}

.square_small::before
{
    width: 8px;
    height: 8px;
}      
     

#pauseScreen, #stopScreen
{
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    visibility: hidden;
}


Reload! You can see what the next block is now.



Looks like you got the gist of what's happening, so we will take care of the moving block now. Add a new property, currentPosition. It is an object with the properties x, y and r, all set to 0 by default. x represents the horizontal position, y the vertical, and r the rotation index.
var game = {
    grid: [],
    colors: ["red", "purple", "cyan", "yellow", "lime"],
    currentBlock: null,
    nextBlock: null,
    currentPosition: { x: 0, y: 0, r: 0},
    paused: false,

    stopped: false,


At the start of the renderGrid() method, set currentPosition to an object. This is the position and rotation the moving block has every time the grid is re-rendered.
renderGrid: function()
{
    this.currentPosition = { x: 0, y: -4, r: 0};

    var grid = document.getElementById("grid");

    grid.innerHTML = "";

    var pauseScreen = document.createElement("div");
    pauseScreen.id = "pauseScreen";
    grid.appendChild(pauseScreen);

    var stopScreen = document.createElement("div");
    stopScreen.id = "stopScreen";
    grid.appendChild(stopScreen);
},


And after clearing grid of all content, we create a div and give it an id of movingBlock.
renderGrid: function()
{
    this.currentPosition = { x: 0, y: -4, r: 0};

    var grid = document.getElementById("grid");

    grid.innerHTML = "";

    var movingBlock = document.createElement("div");
    movingBlock.id = "movingBlock";


    var pauseScreen = document.createElement("div");
    pauseScreen.id = "pauseScreen";
    grid.appendChild(pauseScreen);

    var stopScreen = document.createElement("div");
    stopScreen.id = "stopScreen";
    grid.appendChild(stopScreen);
},


Now append movingBlock to grid, then call positionBlock().
renderGrid: function()
{
    this.currentPosition = { x: 0, y: -4, r: 0};

    var grid = document.getElementById("grid");

    grid.innerHTML = "";

    var movingBlock = document.createElement("div");
    movingBlock.id = "movingBlock";

    grid.appendChild(movingBlock);

    this.positionBlock();


    var pauseScreen = document.createElement("div");
    pauseScreen.id = "pauseScreen";
    grid.appendChild(pauseScreen);

    var stopScreen = document.createElement("div");
    stopScreen.id = "stopScreen";
    grid.appendChild(stopScreen);
},


Let's define positionBlock().
getRandomBlock: function()
{
    var shapeIndex = Math.floor(Math.random() * this.shapes.length);
    var colorIndex = Math.floor(Math.random() * this.colors.length);

    return {"shape": shapeIndex, "color": this.colors[colorIndex]};
},
positionBlock: function()
{

},

setPause: function(isAuto)    
{
    if (this.stopped) return;

    this.paused = (this.paused ? false : true);

    if (!isAuto)
    {
        var pauseScreen = document.getElementById("pauseScreen");
        pauseScreen.style.visibility = (this.paused ? "visible" : "hidden");
    }
},


We declare container and set it to the value of the element movingBlock.
positionBlock: function()
{
    var container = document.getElementById("movingBlock");
},


The set the left and top margins of movingBlock to the x and y properties of the currentPosition object. Since the size of each square is supposed to be 20 pixels (take note!) we will have to multiply each value by 20.
positionBlock: function()
{
    var container = document.getElementById("movingBlock");
    container.style.marginLeft = (this.currentPosition.x * 20) + "px";
    container.style.marginTop = (this.currentPosition.y * 20) + "px";

},


Here is the style for movingBlock. The position property is absolute because it is going to appear anywhere within grid regardless of what other elements are inside. Width and height have been set to 80 pixels because each square is 20 pixels in height and width, and a 4 by 4 square grid will be displayed at any one time. By default, top and left margins are at 0.
.square_small::before
{
    width: 8px;
    height: 8px;
}            

#movingBlock
{
    position: absolute;
    width: 80px;
    height: 80px;
    margin-top: 0px;
    margin-left: 0px;
}


#pauseScreen, #stopScreen
{
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    visibility: hidden;
}


See the red square at the top left, right over the Stage and Score panel? That's movingBlock. It's at the top because the y property of currentPosition was set to -4.




Next, create a new div with the id of movingBlockAspects. Then append it to movingBlock before appending movingBlock to grid.
renderGrid: function()
{
    this.currentPosition = { x: 0, y: -4, r: 0};

    var grid = document.getElementById("grid");

    grid.innerHTML = "";

    var movingBlock = document.createElement("div");
    movingBlock.id = "movingBlock";

    var movingBlockAspects = document.createElement("div");
    movingBlockAspects.id = "movingBlockAspects";

    movingBlock.appendChild(movingBlockAspects);

    grid.appendChild(movingBlock);

    this.positionBlock();

    var pauseScreen = document.createElement("div");
    pauseScreen.id = "pauseScreen";
    grid.appendChild(pauseScreen);

    var stopScreen = document.createElement("div");
    stopScreen.id = "stopScreen";
    grid.appendChild(stopScreen);
},


This is the style for movingBlockAspects. It is 4 times the width of its parent, occupies full height, and the left marginis at 0%.
#movingBlock
{
    position: absolute;
    width: 80px;
    height: 80px;
    margin-top: 0px;
    margin-left: 0px;
}

#movingBlockAspects
{
    width: 400%;
    height: 100%;
    margin-left: 0%;
}


#pauseScreen, #stopScreen
{
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    visibility: hidden;
}


See this now? There's another red div within the first one, and it's stretched out all the way to the right. That's because this new div is, remember, 4 times the size of its parent.




We're going to populate movingBlockAspects with 4 divs. We'll use a For loop for that.
renderGrid: function()
{
    this.currentPosition = { x: 0, y: -4, r: 0};

    var grid = document.getElementById("grid");

    grid.innerHTML = "";

    var movingBlock = document.createElement("div");
    movingBlock.id = "movingBlock";

    var movingBlockAspects = document.createElement("div");
    movingBlockAspects.id = "movingBlockAspects";

    movingBlock.appendChild(movingBlockAspects);
    grid.appendChild(movingBlock);

    for (var i = 0; i <= 3; i++)
    {

    }


    this.positionBlock();

    var pauseScreen = document.createElement("div");
    pauseScreen.id = "pauseScreen";
    grid.appendChild(pauseScreen);

    var stopScreen = document.createElement("div");
    stopScreen.id = "stopScreen";
    grid.appendChild(stopScreen);
},


Inside the loop, create aspect as a new div. Its style is the aspect CSS class. The id will be a string constructed from "aspect" and the value of i. And after all that, append aspect to movingBlockAspects.
renderGrid: function()
{
    this.currentPosition = { x: 0, y: -4, r: 0};

    var grid = document.getElementById("grid");

    grid.innerHTML = "";

    var movingBlock = document.createElement("div");
    movingBlock.id = "movingBlock";

    var movingBlockAspects = document.createElement("div");
    movingBlockAspects.id = "movingBlockAspects";

    movingBlock.appendChild(movingBlockAspects);
    grid.appendChild(movingBlock);

    for (var i = 0; i <= 3; i++)
    {
        var aspect = document.createElement("div");
        aspect.className = "aspect";
        aspect.id = "aspect" + i;
        movingBlockAspects.appendChild(aspect);

    }

    this.positionBlock();

    var pauseScreen = document.createElement("div");
    pauseScreen.id = "pauseScreen";
    grid.appendChild(pauseScreen);

    var stopScreen = document.createElement("div");
    stopScreen.id = "stopScreen";
    grid.appendChild(stopScreen);
},


We are going to style aspect. There will be 4 such divs within movingBlockAspects, so the width will be a quarter of its parent. It will occupy full height and float left so that it should line up in a straight row.
#movingBlockAspects
{
    width: 400%;
    height: 100%;
    margin-left: 0%;
}

.aspect
{
    width: 25%;
    height: 100%;
    float: left;
}


#pauseScreen, #stopScreen
{
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    visibility: hidden;
}


Here, we can see the long div seems to have been divided into 4 boxes! They are all divs styled using the aspect CSS class.




Now we complete this by declaring block and setting it to the element of the shapes array pointed to by the shape property of currentShape. Then we make sure that every div is populated with the block using the populateAspect() method. We pass in the id of the current element. block has 4 elements, one for each rotation, so we use i to get one rotation from block into each element. We then pass in the color of currentBlock and the name of the square CSS class.
renderGrid: function()
{
    this.currentPosition = { x: 0, y: -4, r: 0};

    var grid = document.getElementById("grid");

    grid.innerHTML = "";

    var block = this.shapes[this.currentBlock.shape];

    var movingBlock = document.createElement("div");
    movingBlock.id = "movingBlock";

    var movingBlockAspects = document.createElement("div");
    movingBlockAspects.id = "movingBlockAspects";

    movingBlock.appendChild(movingBlockAspects);
    grid.appendChild(movingBlock);

    for (var i = 0; i <= 3; i++)
    {
        var aspect = document.createElement("div");
        aspect.className = "aspect";
        aspect.id = "aspect" + i;
        movingBlockAspects.appendChild(aspect);
        this.populateAspect(aspect.id, block[i], this.currentBlock.color, "square");
    }

    this.positionBlock();

    var pauseScreen = document.createElement("div");
    pauseScreen.id = "pauseScreen";
    grid.appendChild(pauseScreen);

    var stopScreen = document.createElement("div");
    stopScreen.id = "stopScreen";
    grid.appendChild(stopScreen);
},


Now we define the square CSS class. It's like square_small, only twice the size.
.row
{
    width: 100%;
    height: 20px;
    float: left;
}

.square
{
    width: 20px;
    height: 20px;
    float: left;
}


.square_small
{
    width: 10px;
    height: 10px;
    float: left;
}


And here.
.white::before
{
    content: "";
    display: block;
    border: 1px solid rgb(255, 255, 255);
    background-color: rgba(255, 255, 255, 0.8);
}

.square::before
{
    width: 18px;
    height: 18px;
}    
        

.square_small::before
{
    width: 8px;
    height: 8px;
}    


And now populateAspects() has rendered the current block, in all its four rotations and the current color, inside movingBlockAspects!




One last thing before we conclude this part...

We need to fill grid up with squares. We have already defined the square CSS class. But we also want to pack them into separate rows. So, in the renderGrid() method, right after clearing grid of content, we use a For loop to iterate through the elements of grid.
renderGrid: function()
{
    this.currentPosition = { x: 0, y: -4, r: 0};

    var grid = document.getElementById("grid");

    grid.innerHTML = "";
    for(var i = 0; i < this.grid.length; i++)
    {

    }


    var block = this.shapes[this.currentBlock.shape];


In it, we define row as a new div element and style it using the row CSS class. And then append it to grid.
for(var i = 0; i < this.grid.length; i++)
{
    var row = document.createElement("div");
    row.className = "row";

    grid.appendChild(row);

}


This is the row CSS class. It takes up full width, but only 20 pixels in height, which is the same as the square CSS class. And we float it left so that the other divs styled using row fall to the next line neatly.
#grid
{
    width: 200px;
    height: 400px;
    background-color: rgba(0, 0, 0, 0.5);
    margin: 5px auto 5px auto;
    position: relative;
    border: 5px inset rgb(155, 155, 155);
}

.row
{
    width: 100%;
    height: 20px;
    float: left;
}


.square
{
    width: 20px;
    height: 20px;
    float: left;
}


Now you see the rows within grid.




Right before appending row to grid, use a nested For loop to iterate through the elements of grid's current element.
for(var i = 0; i < this.grid.length; i++)
{
    var row = document.createElement("div");
    row.className = "row";

    for(var j = 0; j < this.grid[i].length; j++)
    {

    }


    grid.appendChild(row);
}


In here, create square as a new div. It's styled using the square CSS class and whatever color that this current sub-element in grid has. And finally, we append square to row.
for(var i = 0; i < this.grid.length; i++)
{
    var row = document.createElement("div");
    row.className = "row";

    for(var j = 0; j < this.grid[i].length; j++)
    {
        var square = document.createElement("div");
        square.className = "square " + this.grid[i][j];
        row.appendChild(square);

    }

    grid.appendChild(row);
}


Now we can see all the squares within the individual rows.




The red outlines so far have given us a good idea where the divs are located. There's no more HTML to be created, either "manually" or via JavaScript.

Next

Directional controls!

Thursday, 6 July 2023

Web Tutorial: Tetris in vanilla JavaScript (Part 1/4)

Early into my foray into JavaScript, I saw an online job ad from a gaming company that asked me just how good my JavaScript was. The line that grabbed me was: Can you write Tetris without using jQuery?

Now, I wasn't sure I wanted to join that company, but it was a challenge I couldn't refuse. It took a week or two, but I got it working. Years later, I revisited the code and was dismayed. Sure, it worked. But it was a mess. The naming conventions were crap. The flow was embarrassingly cryptic. There were so many points that needed to be optimized.

So I redid it. And today I will share it with you.

We will start off with layout, and this is really just some standard HTML. We need a meta tag in the head tag to make sure that this little application works on mobile devices. In the styling, we make sure that all divs have a red outline, for visibility while we work. We will also set the background to black and define the font properties.
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>JS Tetris</title>

        <style>
            div { outline: 1px solid red;}
      
            body
            {
                background-color: rgb(0, 0, 0);
                font-family: sans-serif;
                font-size: 10px;
            }
        </style>

        <script>

        </script>
    </head>

    <body>

    </body>
</html>


In the body, we start with a div with an id of container. The style for this includes a set height and width, the margin property setting it in the middle and a white background. We cap it off with rounded corners using the border-radius property.
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>JS Tetris</title>

        <style>
            div { outline: 1px solid red;}
      
            body
            {
                background-color: rgb(0, 0, 0);
                font-family: sans-serif;
                font-size: 10px;
            }
      
            #container
            {
                width: 300px;
                height: 650px;
                margin: 0 auto 0 auto;
                background-color: rgb(255, 255, 255);
                border-radius: 10px;
            }  
     
            </style>

        <script>

        </script>
    </head>

    <body>
        <div id="container">

        </div>

    </body>
</html>


This white rectangle is where the user interface takes place.
00



Now for more layout. We need three divs, with ids header, grid and footer inside container.
<div id="container">
  <div id="header">

  </div>

  <div id="grid">

  </div>

  <div id="footer">

  </div>

</div>


header takes up full width and only 100 pixels height.
<style>
  div { outline: 1px solid red;}

  body
  {
    background-color: rgb(0, 0, 0);
    font-family: sans-serif;
    font-size: 10px;
  }

  #container
  {
    width: 300px;
    height: 650px;
    margin: 0 auto 0 auto;
    background-color: rgb(255, 255, 255);
    border-radius: 10px;
  }        
 
  #header
  {
    width: 100%;
    height: 100px;
  }

</style>


grid has only 200 pixels width and 400 pixels height, and is set in the middle via the margin property. The background color has been set darker, and border property is set to 5 pixels with an inset, because I think thematically, it looks good.
#header
{
  width: 100%;
  height: 100px;
}

#grid
{
  width: 200px;
  height: 400px;
  background-color: rgba(0, 0, 0, 0.5);
  margin: 5px auto 5px auto;
  position: relative;
  border: 5px inset rgb(155, 155, 155);
}


footer has the same dimensions as header, with the addition of setting text to align center.
#header
{
  width: 100%;
  height: 100px;
}

#grid
{
  width: 200px;
  height: 400px;
  background-color: rgba(0, 0, 0, 0.5);
  margin: 5px auto 5px auto;
  position: relative;
  border: 5px inset rgb(155, 155, 155);
}

#footer
{
  width: 100%;
  height: 100px;    
  text-align: center;
}


Nothing too complicated here at this point.




The topmost section is going to be the where we house the score and stage displays, as well as the next Tetris block. To that end, we begin by adding a paragraph with the word "TETRIS" in it, as a title, then a div with an id of topDisplay.
<div id="header">
    <p>TETRIS</p>
    <div id="topDisplay">

    </div>

</div>


In the styles, we add this specification for any p tags within header. It's just cosmetic, so you can go with my choice here if you want. I basically aligned it center, added a bit of padding at the top, bolded it and made the kerning wide using the letter-spacing property.
#header
{
    width: 100%;
    height: 100px;
}

#header p
{
    padding-top: 10px;
    text-align: center;
    font-weight: bold;
    letter-spacing: 2em;
}


#grid
{
  width: 200px;
  height: 400px;
  background-color: rgba(0, 0, 0, 0.5);
  margin: 5px auto 5px auto;
  position: relative;
  border: 5px inset rgb(155, 155, 155);
}


Now for topDisplay, we want it to have an inset just like grid. The width and height have been made big enough to house whatever it is we're adding in.
#header
{
    width: 100%;
    height: 100px;
}

#header p
{
    padding-top: 10px;
    text-align: center;
    font-weight: bold;
    letter-spacing: 2em;
}

#topDisplay
{
    width: 200px;
    height: 60px;
    background-color: rgba(0, 0, 0, 0.5);
    margin: 5px auto 5px auto;
    position: relative;
    border: 5px inset rgb(155, 155, 155);
}

            
#grid
{
  width: 200px;
  height: 400px;
  background-color: rgba(0, 0, 0, 0.5);
  margin: 5px auto 5px auto;
  position: relative;
  border: 5px inset rgb(155, 155, 155);
}


Looking good.




We need to divide this div into two. To do that, we have two divs, one with id header_left and the other with id header_right. It's a bit clumsy but what the hell, eh?
<div id="header">
    <p>TETRIS</p>
    <div id="topDisplay">
        <div id="header_left">

        </div>

        <div id="header_right">

        </div>
    </div>
</div>


In header_left, we put in text and span tags with ids. The span tags are important; they will act as containers for the values we will provide later. In header_right, we have a div with id gridNext.
<div id="header">
    <p>TETRIS</p>
    <div id="topDisplay">
        <div id="header_left">
            <b>STAGE</b><br />
            <span id="txtStage">0</span><br />
            <b>SCORE</b><br />
            <span id="txtScore">0</span><br />

        </div>

        <div id="header_right">
            <div id="gridNext"></div>
        </div>
    </div>
</div>


header_left takes up half the width of its parent, and we float it left. header_right takes up 35%, and we float it right. In both cases, we give it a padding of 5 pixels.
#topDisplay
{
    width: 200px;
    height: 60px;
    background-color: rgba(0, 0, 0, 0.5);
    margin: 5px auto 5px auto;
    position: relative;
    border: 5px inset rgb(155, 155, 155);
}

#header_left
{
    width: 50%;
    height: 100%;
    padding: 5px;
    float: left;
}

#header_right
{
    width: 35%;
    height: 100%;
    padding: 5px;
    float: right;
}


#grid
{
    width: 200px;
    height: 400px;
    background-color: rgba(0, 0, 0, 0.5);
    margin: 5px auto 5px auto;
    position: relative;
    border: 5px inset rgb(155, 155, 155);
}


And then we style gridNext to be a 40 by 40 pixel square, with 5 pixel padding.
#topDisplay
{
    width: 200px;
    height: 60px;
    background-color: rgba(0, 0, 0, 0.5);
    margin: 5px auto 5px auto;
    position: relative;
    border: 5px inset rgb(155, 155, 155);
}

#header_left
{
    width: 50%;
    height: 100%;
    padding: 5px;
    float: left;
}

#header_right
{
    width: 35%;
    height: 100%;
    padding: 5px;
    float: right;
}

#gridNext
{
    width: 40px;
    height: 40px;
    margin: 0px auto 0 auto;
    padding: 5px;
}


#grid
{
    width: 200px;
    height: 400px;
    background-color: rgba(0, 0, 0, 0.5);
    margin: 5px auto 5px auto;
    position: relative;
    border: 5px inset rgb(155, 155, 155);
}


Let's see what we did here.




We don't need to do anything for grid at the moment, so let's mosey on over to footer. In it, we need three divs, each with a class of buttons. In the first two, we have button tags. The first one has a circular arrow icon (we will use this as the Rotate button) and the second one has what I hope looks like a Start/Stop icon. In reality, I know it looks like a gigantic cartoony boobie, but really I don't care at this point. If you care, change it.
<div id="footer">
    <div class="buttons">
        <br />
        <button>&#8635;</button>
    </div>

    <div class="buttons">
        <br />
        <button>&#9737;</button>
    </div>


    <div class="buttons">

    </div>

</div>


The buttons CSS class has a width of 33% so that each width takes up roughly a third of its parents. Height is at 90 pixels, and we have a slight margin at the top. More importantly, the float property is set to left, so it all lines up.
#footer
{
    width: 100%;
    height: 100px;        
    text-align: center;
}

.buttons
{
    width: 33%;
    height: 90px;
    float: left;
    margin-top: 10px;
}


By default, button tags within the CSS class buttons will be 50 by 50 pixels, circular, and have a large font size.
.buttons
{
    width: 33%;
    height: 90px;
    float: left;
    margin-top: 10px;
}

.buttons button
{
    width: 50px;
    height: 50px;
    border-radius: 50%;
    font-size: 3em;
}


Let's see what we have here!




What we need next is a directional pad. For this, we insert nine divs into the last div, with a space in them, and style them using the pad CSS class.
<div id="footer">
    <div class="buttons">
        <br />
        <button>&#8635;</button>
    </div>

    <div class="buttons">
        <br />
        <button>&#9737;</button>
    </div>

    <div class="buttons">
        <div class="pad">&nbsp;</div>
        <div class="pad">&nbsp;</div>
        <div class="pad">&nbsp;</div>
        <div class="pad">&nbsp;</div>
        <div class="pad">&nbsp;</div>
        <div class="pad">&nbsp;</div>
        <div class="pad">&nbsp;</div>
        <div class="pad">&nbsp;</div>
        <div class="pad">&nbsp;</div>

    </div>
</div>


pad takes up roughly a third of its parent's width, and 30 pixels height. We float it left. Why one-third, you ask? Because we need it in a 3 by 3 grid, and floating left means that the 9 divs will show up that way, breaking to the new line every 3 divs.
.buttons
{
    width: 33%;
    height: 90px;
    float: left;
    margin-top: 10px;
}

.pad
{
    width: 33%;
    height: 30px;
    float: left;
}


.buttons button
{
    width: 50px;
    height: 50px;
    border-radius: 50%;
    font-size: 3em;
}


Yep, and we do indeed have that grid now.




Add the dirPad CSS class to these particular divs.
<div class="buttons">
    <div class="pad">&nbsp;</div>
    <div class="pad dirPad">&nbsp;</div>
    <div class="pad">&nbsp;</div>
    <div class="pad dirPad">&nbsp;</div>
    <div class="pad dirPad">&nbsp;</div>
    <div class="pad dirPad">&nbsp;</div>
    <div class="pad">&nbsp;</div>
    <div class="pad dirPad">&nbsp;</div>
    <div class="pad">&nbsp;</div>
</div>


dirPad basically just gives this div a black background.
.pad
{
    width: 33%;
    height: 30px;
    float: left;
}

.dirPad
{
    background-color: rgb(0, 0, 0);
}


.buttons button
{
    width: 50px;
    height: 50px;
    border-radius: 50%;
    font-size: 3em;
}


And now we have a black cross!



 
Time to make buttons in those black squares. Replace the non-breaking space in these specific divs.
<div class="buttons">
    <div class="pad">&nbsp;</div>
    <div class="pad dirPad"><button>&#9650;</button></div>
    <div class="pad">&nbsp;</div>
    <div class="pad dirPad"><button>&#9664;</button></div>
    <div class="pad dirPad">&nbsp;</div>
    <div class="pad dirPad"><button>&#9654;</button></div>
    <div class="pad">&nbsp;</div>
    <div class="pad dirPad"><button>&#9660;</button></div>
    <div class="pad">&nbsp;</div>
</div>


We style these buttons within the pad CSS class by giving them 100% of the width and height of their parents. The color is defined as white, and we define the font size as 1em to override the previous class.
.dirPad
{
    background-color: rgb(0, 0, 0);
}

.buttons button
{
    width: 50px;
    height: 50px;
    border-radius: 50%;
    font-size: 3em;
}

.pad button
{
    width: 100%;
    height: 100%;
    background: none;
    color: rgb(255, 255, 255);
    font-size: 1em;
}


And here be thy directional buttons!




More stuff

I was planning on making this purely about the user interface layout, but the next bits are fairly small and doable, and I figure, why not? The big stuff is coming later. What we're going to do here are the Pause and Stop screens, which are a combination of user interface and JavaScript.

We begin with an object, game, in the script tag. In it, we have two properties, paused and stopped. Both of these are Boolean values, and set to false by default.
<script>
    var game = {
        paused: false,
        stopped: false    
    }
</script>


In the body tag, we run the reset() method when the page is loaded.
<body onload="game.reset();">


And now let's define this method. We're going to do a lot of stuff in here later, but at the moment, we are just going to call the renderGrid() method.
<script>
    var game = {
        paused: false,
        stopped: false,
        reset: function()
        {
            this.renderGrid();
        }

    }
</script>


Time to define renderGrid(). We begin by defining the variable grid. Set it to the div element grid. Clear it of all content.
<script>
    var game = {
        paused: false,
        stopped: false,
        reset: function()
        {
            this.renderGrid();
        },
        renderGrid: function()
        {
            var grid = document.getElementById("grid");

            grid.innerHTML = "";
        }

    }
</script>


We then define pauseScreen by creating a div, then set the id to pauseScreen. After that, we use the appendChild() method to insert pauseScreen into grid.
renderGrid: function()
{
    var grid = document.getElementById("grid");

    grid.innerHTML = "";

    var pauseScreen = document.createElement("div");
    pauseScreen.id = "pauseScreen";
    grid.appendChild(pauseScreen);

}


Let's define some styles for pauseScreen. The position property is set to absolute because we want it to overlap everything that will be going into grid. And since it will be overlapping everything, width and height should be 100% of grid. The background color is set to a translucent black, and it is hidden by default.
.pad button
{
    width: 100%;
    height: 100%;
    background: none;
    color: rgb(255, 255, 255);
    font-size: 1em;
}
            
#pauseScreen
{
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    visibility: hidden;
}


We then use a pseudoselector to implant a block element within pauseScreen. It will have the word "PAUSED" in there, and the rest of it is cosmetic.
#pauseScreen
{
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    visibility: hidden;
}

#pauseScreen::before
{
    display: block;
    content: "PAUSED";
    text-align: center;
    font-weight: bold;
    font-size: 2em;
    color: rgb(255, 255, 255);
    margin-top: 200px;
}


We will set the Up button and set it to call the setPause() method when clicked, with false as an argument.
<div class="buttons">
    <div class="pad">&nbsp;</div>
    <div class="pad dirPad"  onclick="game.setPause(false)"><button>&#9650;</button></div>
    <div class="pad">&nbsp;</div>
    <div class="pad dirPad"><button>&#9664;</button></div>
    <div class="pad dirPad">&nbsp;</div>
    <div class="pad dirPad"><button>&#9654;</button></div>
    <div class="pad">&nbsp;</div>
    <div class="pad dirPad"><button>&#9660;</button></div>
    <div class="pad">&nbsp;</div>
</div>


Now we define the setPause() method. What it's supposed to do, is toggle the paused property between true and false. The parameter, isAuto, is used to define if it is a user-activated call, meaning, if the method was called by clicking the Up button. That's because there will be cases where the game is paused for animations to take place, in which case isAuto will be true. For now, we will begin by exiting the method if stopped is true.
<script>
    var game = {
        paused: false,
        stopped: false,
        reset: function()
        {
            this.renderGrid();
        }
        renderGrid: function()
        {
            var grid = document.getElementById("grid");

            grid.innerHTML = "";

            var pauseScreen = document.createElement("div");
            pauseScreen.id = "pauseScreen";
            grid.appendChild(pauseScreen);        
        },
        setPause: function(isAuto)    
        {
            if (this.stopped) return;
        }

    }
</script>


Then we set paused to true if it is currently false, and vice versa.
setPause: function(isAuto)    
{
    if (this.stopped) return;

    this.paused = (this.paused ? false : true);
}


After that, we check if isAuto is false. That means this method was called by clicking the Up button. When that is true, we make sure that pauseScreen is visible if paused is true.
setPause: function(isAuto)    
{
    if (this.stopped) return;

    this.paused = (this.paused ? false : true);

    if (!isAuto)
    {
        var pauseScreen = document.getElementById("pauseScreen");
        pauseScreen.style.visibility = (this.paused ? "visible" : "hidden");
    }

}


Now reload. Click the Up button. Does this come on? If you click it again, does it go off? Great!




Back to renderGrid(). We want to set up stopScreen as well.
renderGrid: function()
{
    var grid = document.getElementById("grid");

    grid.innerHTML = "";

    var pauseScreen = document.createElement("div");
    pauseScreen.id = "pauseScreen";
    grid.appendChild(pauseScreen);

    var stopScreen = document.createElement("div");
    stopScreen.id = "stopScreen";
    grid.appendChild(stopScreen);

},


For styling, it will share the specifications with pauseScreen.
#pauseScreen, #stopScreen
{
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    visibility: hidden;
}


The text for stopScreen is different, but that's the only difference.
#pauseScreen, #stopScreen
{
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    visibility: hidden;
}

#pauseScreen::before
{
    display: block;
    content: "PAUSED";
    text-align: center;
    font-weight: bold;
    font-size: 2em;
    color: rgb(255, 255, 255);
    margin-top: 200px;
}

#stopScreen::before
{
    display: block;
    content: "GAME OVER";
    text-align: center;
    font-weight: bold;
    font-size: 2em;
    color: rgb(255, 255, 255);
    margin-top: 200px;
}


In the HTML, set the button to call setStop() when it's clicked.
<div class="buttons">
    <br />
    <button onclick="game.setStop()">&#9737;</button>
</div>


And then define setStop(). As before, we toggle stopped between true and false.
setPause: function(isAuto)    
{
    if (this.stopped) return;

    this.paused = (this.paused ? false : true);

    if (!isAuto)
    {
        var pauseScreen = document.getElementById("pauseScreen");
        pauseScreen.style.visibility = (this.paused ? "visible" : "hidden");
    }
},
setStop: function()    
{
    this.stopped = (this.stopped ? false : true);
}


Since there is no "auto" for this, we don't need to check for it. We just ensure that stopScreen is visible if stopped is true, and vice versa.
setStop: function()    
{
    this.stopped = (this.stopped ? false : true);

    var stopScreen = document.getElementById("stopScreen");
    stopScreen.style.visibility = (this.stopped ? "visible" : "hidden");

}


Now reload and click the button that looks like a giant boob. Does the "GAME OVER" screen come on? If you click it again, does it go off?




The layout and user interface is in place. We will populate the grid next!

Next

Displaying blocks in the grid.

Sunday, 2 July 2023

Confidence is overrated, and here's why, Redux

Roughly four years back, I wrote a blistering piece on how confidence is overrated. I stand by everything I said back then, but there are nuances to it. There are cases where projecting confidence actually helps your case, even if that confidence is not being backed by any substance. Confidence cannot be a substitute for actual substance. In that sense, it absolutely is overrated. I would rather be operated on by a cautious but extremely qualified surgeon who is constantly second-guessing himself (or herself) than some supremely confident upstart.

But in cases where the confidence is more important than actual skill, those are the cases where confidence is the skill.

John Cena

Someone recently told me told me that "John Cena speaks fluent Mandarin". Given that I had never heard this individual speak in anything but English, I was naturally skeptical - how would this guy even know what fluent Mandarin sounds like? Nevertheless, I had to check out this claim, and came across this video clip on YouTube. Somewhere in the middle, John Cena starts (or attempts) to speak Mandarin.


If anyone reading this blog is actually a Mandarin speaker, I apologize for what I just put you through. Yes, that was a long, long way from being "fluent". In fact, it was extremely hard for me to understand Cena's butchered version of Mandarin.

But as I watched on, my ears burning in agony, I noticed one other thing.

Despite his obvious deficiencies in the language, John Cena maintained a big smile as he uttered the gibberish they called "Mandarin". As he regaled the audience with his horrible Mandarin, the massive shit-eating grin on his face never wavered. He did not hesitate, or show any signs of being anything less than confident. And the non-Chinese crowd just ate it up.

This is the lesson I got from this. John Cena knew his Mandarin was crap. But it didn't matter. He wasn't there to teach linguistics, engage in a debate in Mandarin, or read a documentary. He was there to entertain, and by God, that's what he did. For the largely monolingual crowd (this being the USA, that's a safe assumption), it didn't matter either.

The confidence that John Cena projected, was the skill. Not his Mandarin, which was rubbish.

That job interview

I have mentioned before my habit of attending job interviews even though I have no intention of taking the job, just for the practice. I don't do it much these days, but there was a time I was doing it constantly.

At one such interview for the position of a Software Engineer, I went into the interview and did my thing (at this point I could pretty much do it with my eyes closed) and at the end of it, my interviewer asked me why I was so confident. He'd noted that my resume didn't showcase any skills or achievements that would have been special considering my years of work experience. There was nothing in it that warranted that air of confidence I projected. (If nothing else, I'm very honest in my resume and I flatly refuse to inflate anything).

Nerves of steel.

Shucks, I wasn't sure what to tell him. That his company was the tenth I had interviewed at this month alone? That I'd already been rejected by companies far bigger and more prestigious? That what he saw as supreme confidence was simply a case of me being inured to interview anxiety simply through constant exposure? And that since I did not lie on my (admittedly mediocre) resume, I felt completely able to back up everything on it?

Then he surprised me. He asked me if I would be open to a Managerial position instead. He explained that as a Software Engineer, I would probably be totally mediocre and he had many good candidates he was already considering. But what he needed was someone who could project the same confidence I did, to customers. Any technical work would be handed by the Software Engineers, so all he really needed was my confidence, and the actual substance would be provided by the technical team.

No, I didn't wind up working for him. By the time he came back to me, I was already working somewhere else.

But you see, this is another example of confidence actually being the skill rather than the supplement.

Don't get me wrong...

Confidence is still overrated. We cannot afford to think of confidence as a crutch for any deficiencies.

What confidence can do, is amplify what you're already capable of. It's a good complementary attribute. Conversely, lack of confidence can negatively affect your actual qualities.

What makes you confident?
T___T