Overview of the Project

Tetris 3D is a Tetris clone, but rendered in 3D! You can play the game here

3D Render with three.js

My experience with OpenGL made three.js easy to pick up. This JavaScript library makes it very easy to render cubes, and so this entire project literally just 3D cubes rendered in different positions. This project also required writing multiple algorithms for the Tetris game logic, which I have described below.

Game Logic

The game logic is found in tetris.js. Every cell in the game is stored in the 2D grid[][], which is a 10x20 integer array, where the number represents the color of the cell (0 = empty). Every frame, the cubes are rendered based on grid[][], so 3D rendering logic is clearly separated from the 2D game logic.

The falling shape is stored is rendered and handled separately. The shape is represented by a 2x2, 3x3, or 4x4 array. The shape also has a position, so this 2D array is translated by the position variable. When the shape overlaps the grid, it is locked into place.

The falling tetromino is represented by this dictionary:

// shapes dictionary
const shapes = {
    'I': [
        [0, 0, 0, 0],
        [1, 1, 1, 1],
        [0, 0, 0, 0],
        [0, 0, 0, 0]
    ],
    'L': [
        [0, 0, 0],
        [1, 1, 1],
        [1, 0, 0]
    ],
    'J': [
        [0, 0, 0],
        [1, 1, 1],
        [0, 0, 1]
    ],
    'T': [
        [0, 0, 0],
        [1, 1, 1],
        [0, 1, 0]
    ],
    'S': [
        [0, 0, 0],
        [0, 1, 1],
        [1, 1, 0]
    ],
    'Z': [
        [0, 0, 0],
        [1, 1, 0],
        [0, 1, 1]
    ],
    'O': [
        [1, 1],
        [1, 1]
    ]
};



Rotating the shape means rotating this 2D square array, which can be done with this algorithm:

// rotate shape
const result = Array.from({ length: N }, () => Array(N).fill(0));
for (let i = 0; i < N; i++) {
    for (let j = 0; j < N; j++) {
        result[j][N - 1 - i] = this.shape[i][j];
    }
}



Clearing rows just requires iterating each row and checking all 10 cells are NOT equal to 0 (0 represents empty cell). If this is the case, move the row above down to this current position, and do this for every row.

 
// check for clears
checkForClears() {
    for (let y = 0; y < 20; y++) {
        for (let x = 0; x < 10; x++) {
            if (this.grid[x][y] <= 0)
                break;
 
            // check if the whole row has been iterated
            if (x == 9) {
                console.log("Clearing row: " + y);
                for (let i = 0; i < 10; i++) {
                    this.grid[i][y] = 0;
 
                    //now move all rows above this one down
                    for (let j = y; j > 0; j--) {
                        if (j > 0) {
                            //set this cell to the cell above
                            this.grid[i][j] = this.grid[i][j-1];
                        } else {
                            //because its the top row, just clear it (j-1 = -1)
                            this.grid[i][j] = 0;
                        }
                    }
 
                }
 
                // done clearing row, update text
                this.linesCleared += 1;
                this.speed -= DROP_SPEED_ACCELERATE;
            }
        }
    }
}

Preview

Here is a glimpse of the 3D game in action: GameScreenshot