Learn Object-Oriented Programming In JavaScript by Creating Tetris (3)

Ravi da N.
6 min readMay 4, 2023

In this time, the goal is that you can create a program for moving a mino using the principles of OOP.

Here is a link to articles in this series: Previous Article / Next Article

In the last issue, we created MinoGfx that can draw a mino. We will rework it a bit as follows.

function MinoGfx(color, blkpos8) {
const _ctx = gFieldGfx.context2d;
const _color = color;
const _pxpos8 = [];
for (let idx = 0; idx < 8; idx += 2) {
_pxpos8[idx] = blkpos8[idx] * g.Px_BLOCK;
_pxpos8[idx + 1] = blkpos8[idx + 1] * g.Px_BLOCK;
}

this.draw = () => {
_ctx.fillStyle = _color;
for (let idx = 0; idx < 8; idx += 2) {
_ctx.fillRect(_pxpos8[idx] + 1, _pxpos8[idx + 1] + 1
, g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
}
}
}

const t_mino = new MinoGfx('magenta', [1, 0, 0, 1, 1, 1, 2, 1]);
t_mino.draw();

When creating a mino using MinoGfx, we have made it easier to specify the shape of the mino. In the case of creating T-mino, we can specify its form as (1, 0), (0, 1), (1, 1), (2, 1) in a straightforward manner. See Fig.1.

Fig.1

As shown in Fig. 2, T-mino is shown buried in the wall, so move it before drawing it.

Fig.2

In a such case, we should think about making MinoGfx add a funciton that moves a mino. The program will be as follows. In this article, there are many program fragments to be added, so I attach the entire code at the end of this article.

function MinoGfx(color, blkpos8) {
const _ctx = gFieldGfx.context2d;
const _color = color;
const _pxpos8 = [];

// omitted...

this.move = (dx, dy) => {
for (let idx = 0; idx < 8; idx += 2) {
_pxpos8[idx] += dx * g.Px_BLOCK;
_pxpos8[idx + 1] += dy * g.Px_BLOCK;
}
}
}

Now, let’s try this feature. As shown in Fig.3, T-mino will be drawn at a position four squares to the right.

const t_mino = new MinoGfx('magenta', [1, 0, 0, 1, 1, 1, 2, 1]);
t_mino.move(4, 0);
t_mino.draw();
Fig.3

If you create o_mino using MinoGfx with added the function, you can naturally use the new feature in o_mino as well. Add the program below and run it, you can see Fig.4.

const o_mino = new MinoGfx('yellow', [1, 0, 2, 0, 1, 1, 2, 1]);
o_mino.move(7,0);
o_mino.draw();
Fig.4: O-mino is also moved.

As we have just done, in OOP, we complete the program by adding functions to the object that do the job.

Now that MinoGfx has obtained the new function to move a mino, let’s make it possible to move a mino on the screen by input from your keyboard.

Here is something important to consider. As we discussed in the last issue, in OOP, it is important to first consider what the object is responsible for. Currently we have two functions: gFieldGfx, which draws the game field, and MinoGfx, which draws a mino. It is obvious that those two should not be responsible for accepting keyboard input. So, prepare a new object gGame to coordinate the entire game, which includes accepting key input.

When the key is pressed, the object document is notified of it. If a function is registered with document.onkeydown, it receives information about the key pressed. Considering those things, gGame will be as follows.

const gGame = new function() {
let _curMinoGfx = new MinoGfx('magenta', [1, 0, 0, 1, 1, 1, 2, 1]);
_curMinoGfx.move(4, 0);
_curMinoGfx.draw();

document.onkeydown = (e) => {
switch (e.key)
{
case 'ArrowLeft':
_curMinoGfx.move(-1, 0);
_curMinoGfx.draw();
break;

case 'ArrowRight':
_curMinoGfx.move(1, 0);
_curMinoGfx.draw();
break;

case 'ArrowDown':
_curMinoGfx.move(0, 1);
_curMinoGfx.draw();
break;
}
}
}

To run the program yourself is a shortcut to understanding the OOP. It takes a bit of work, but please drop tetris.html into your browser and give it a try.

If you move T-mino down, you will see a screen like the one below.

That’s what happens when you don’t erase T-mino. Now let’s add the function to erase a mino to MinoGfx.

function MinoGfx(color, blkpos8) {
const _ctx = gFieldGfx.context2d;
const _color = color;
const _pxpos8 = [];

// omitted...

this.erase = () => {
_ctx.fillStyle = 'black';
for (let idx = 0; idx < 8; idx += 2) {
_ctx.fillRect(_pxpos8[idx] + 1, _pxpos8[idx + 1] + 1
, g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
}
}
}

Change gGame to erase and then move on.

const gGame = new function() {
let _curMinoGfx= new MinoGfx('magenta', [1, 0, 0, 1, 1, 1, 2, 1]);
_curMinoGfx.move(4, 0);
_curMinoGfx.draw();

document.onkeydown = (e) => {
switch (e.key)
{
case 'ArrowLeft':
_curMinoGfx.erase();
_curMinoGfx.move(-1, 0);
_curMinoGfx.draw();
break;

case 'ArrowRight':
_curMinoGfx.erase();
_curMinoGfx.move(1, 0);
_curMinoGfx.draw();
break;

case 'ArrowDown':
_curMinoGfx.erase();
_curMinoGfx.move(0, 1);
_curMinoGfx.draw();
break;
}
}
}

Make the above changes and run it again.

You can now move T-mino correctly.

It is easier to understand the concept of OOP when you create a program yourself. There are other important concepts in OOP, and I will try to make them clear to you. Thanks for reading this article.

// tetris.js
'use strict';

const divTitle = document.createElement('div');
divTitle.textContent = "TETRIS";
document.body.appendChild(divTitle);

const g = {
Px_BLOCK: 30,
Px_BLOCK_INNER: 28,

PCS_COL: 10,
PCS_ROW: 20,
PCS_FIELD_COL: 12,
}

const gFieldGfx = new function() {
const pxWidthField = g.Px_BLOCK * g.PCS_FIELD_COL;
const pxHeightField = g.Px_BLOCK * (g.PCS_ROW + 1);

const canvas = document.createElement('canvas');
canvas.width = pxWidthField;
canvas.height = pxHeightField;
document.body.appendChild(canvas);

const _ctx = canvas.getContext('2d');
_ctx.fillStyle = "black";
_ctx.fillRect(0, 0, pxWidthField, pxHeightField);

const yBtmBlk = g.Px_BLOCK * g.PCS_ROW;
const xRightBlk = pxWidthField - g.Px_BLOCK + 1;

_ctx.fillStyle = 'gray';
for (let y = 1; y < yBtmBlk; y += g.Px_BLOCK) {
_ctx.fillRect(1, y, g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
_ctx.fillRect(xRightBlk, y, g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
}

for (let x = 1; x < pxWidthField; x += g.Px_BLOCK) {
_ctx.fillRect(x, yBtmBlk + 1, g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
}

this.context2d = _ctx;
}

function MinoGfx(color, blkpos8) {
const _ctx = gFieldGfx.context2d;
const _color = color;
const _pxpos8 = [];

for (let idx = 0; idx < 8; idx += 2) {
_pxpos8[idx] = blkpos8[idx] * g.Px_BLOCK;
_pxpos8[idx + 1] = blkpos8[idx + 1] * g.Px_BLOCK;
}

this.draw = () => {
_ctx.fillStyle = _color;
for (let idx = 0; idx < 8; idx += 2) {
_ctx.fillRect(_pxpos8[idx] + 1, _pxpos8[idx + 1] + 1
, g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
}
}

this.move = (dx, dy) => {
for (let idx = 0; idx < 8; idx += 2) {
_pxpos8[idx] += dx * g.Px_BLOCK;
_pxpos8[idx + 1] += dy * g.Px_BLOCK;
}
}

this.erase = () => {
_ctx.fillStyle = 'black';
for (let idx = 0; idx < 8; idx += 2) {
_ctx.fillRect(_pxpos8[idx] + 1, _pxpos8[idx + 1] + 1
, g.Px_BLOCK_INNER, g.Px_BLOCK_INNER);
}
}
}

const gGame = new function() {
let _curMinoGfx = new MinoGfx('magenta', [1, 0, 0, 1, 1, 1, 2, 1]);
_curMinoGfx.move(4, 0);
_curMinoGfx.draw();

document.onkeydown = (e) => {
switch (e.key)
{
case 'ArrowLeft':
_curMinoGfx.erase();
_curMinoGfx.move(-1, 0);
_curMinoGfx.draw();
break;

case 'ArrowRight':
_curMinoGfx.erase();
_curMinoGfx.move(1, 0);
_curMinoGfx.draw();
break;

case 'ArrowDown':
_curMinoGfx.erase();
_curMinoGfx.move(0, 1);
_curMinoGfx.draw();
break;
}
}
}

--

--

Ravi da N.

C++, C# developer. I'm happy to help so many people enjoy programming. I'd also like to share a lot of knowledge about the program!