Search code examples
javascriptp5.jstetris

Implementation of p5.js tetris has a bug that makes pieces fall through the bottom


I'm making 3D tetris in p5.js. But right now I have a bug that makes the pieces fall through the bottom! I'm not sure why that is because i wrote a function, "fallingPieceIsLegal" that should prevent this. Thanks so much for your help!

//Audrey Zheng
//3D Tetris



var cx;
var cy;

// set board dimensions and margin
var rows = 15;
var cols = 10;
var margin = 50;
let emptyColor;

//make board

var state = new Array();
for (var i = 0; i < cols; i ++) {
    state.push(emptyColor);
}


var board = new Array();
for (var i = 0; i< rows; i ++) {
    board.push(state);
}

//seven "standard" pieces (tetrominoes)

var iPiece = [
    [ true,  true,  true,  true]
    ];
  
var jPiece = [
    [ true, false, false ],
    [ true, true,  true]
    ];
  
var lPiece = [
    [ false, false, true],
    [ true,  true,  true]
    ];
  
var oPiece = [
    [ true, true],
    [ true, true]
    ];
  
var sPiece = [
    [ false, true, true],
    [ true,  true, false ]
    ];
  
var tPiece = [
    [ false, true, false ],
    [ true,  true, true]
    ];

var zPiece = [
    [ true,  true, false ],
    [ false, true, true]
    ];

var tetrisPieces = [ iPiece, jPiece, lPiece, oPiece, sPiece, tPiece, zPiece ];
var tetrisPieceColors = [ "green", "pink", "orange", "yellow", "purple", "blue", "red" ];

//the falling piece

var fallingPiece;
var fallingPieceCols;
var fallingPieceCol;
var fallingPieceRow;



function setup() {
    frameRate(10);

    createCanvas(550, 800);
    background(220);




    // osc = new p5.TriOsc();
    // osc.freq(880.0);
    // osc.amp(0.1);
    // osc.start();


    cx = width/2;
    cy = width/2;


    newFallingPiece();
    //fallingPieceIsLegal();
    

}

function draw() {

    fill(255,255,220);
    rect(0,0,width,height);



    emptyColor = color(255,0,0,63);



    drawBoard(rows,cols, 450,675);


    placeFallingPiece();
    rectMode(CORNER);
    drawFallingPiece();

    


    if (moveFallingPiece(-1,0) == false) {
        placeFallingPiece();
        newFallingPiece();

    }
    

}

function newFallingPiece(){


    fallingPiece = random(tetrisPieces);
    fallingPieceCols = fallingPiece[0].length;
    fallingPieceCol = cols/2 - Math.floor(fallingPieceCols/2);
    fallingPieceRow = 0;
    //console.log(fallingPiece);


}

function placeFallingPiece() {
    for (var r = 0; r < fallingPiece.length; r ++) {
        for (var c = 0; c < fallingPiece[0].length; c ++) {
            if (fallingPiece[r][c]) {
                board[r + fallingPieceRow][c + fallingPieceCol] = "magenta";
            }
            //print("hi");
        }
    }
}

function drawFallingPiece() {
    for (var r = 0; r < fallingPiece.length; r ++) {
        for (var c = 0; c < fallingPiece[0].length; c ++) {
            if (fallingPiece[r][c]) {
                var bnds = getCellBounds(r + fallingPieceRow, c + fallingPieceCol, 450, 675);
                fill(255);
                rect(bnds[0],bnds[2],45,45);

                var tetrisCube = new cube(bnds[0],bnds[2], true);
                //systems.push(tetrisCube);
                tetrisCube.display();
                //fill(230,245,255);
                rect(bnds[0], bnds[2], 45,45);

            }
            //print("hi");
        }
    }

}

function fallingPieceIsLegal() {

    for (var r = 0; r < fallingPiece.length; r++) {
        for (var c = 0; c < fallingPieceCols; c++) {
            if (fallingPiece[r][c] == true) {
                
                if ((c + fallingPieceCol > cols - 1) || (c + fallingPieceCol < 0)) {
                    return false;
                }
                if (r + fallingPieceRow > rows -1) {
                    return false;
                }


            }
        }
    }
    return true;
}

function moveFallingPiece(drow, dcol) {
    if ((drow == 0) && (dcol == -1)) { //move left
        fallingPieceCol -= 1;
        if (fallingPieceIsLegal() == false) {
            fallingPieceCol += 1;
            print('hi');
        }
    }

    if ((drow == 0) && (dcol == 1)) { //move right
        fallingPieceCol += 1;
        if (fallingPieceIsLegal() == false) {
            print("yo");
            fallingPieceCol -= 1;
        }
    }

    if ((drow == -1) && (dcol == 0)) { //move down
        fallingPieceRow += 1;  
        if (fallingPieceIsLegal() == false) {
            fallingPieceRow -= 1;
            return false;
        }   
            return true;   
    }
}

function rotate1(L) {
    var result = [];
    var a;
    for (var col = L[0].length -1; col > -1; col--) {
        //print("yeet");
        var result1 = [];
        for (var row = 0; row < L.length; row++) {
            a = L[row][col];
            result1.push(a);
            print(a);

        }
        result.push(result1);
    }
    return result;
}

function rotateFallingPiece() {
    fallingPiece = rotate1(fallingPiece);
    
    fallingPieceCols = fallingPiece[0].length;
    if (fallingPieceIsLegal == false) {
        for (var i = 0; i < 3; i ++) {
            fallingPiece = rotate1(fallingPiece);
            fallingPieceCols = fallingPiece[0].length;
        }
    }
    print(fallingPiece);
}

function getCellBounds(row,col, width,height) {
    var gridWidth = width - 2 * margin;
    var gridHeight = height - 2 * margin;
    var x0 = margin + width * col/ cols;
    var x1 = margin + width * (col + 1)/ cols;
    var y0 = margin + height * row / rows;
    var y1 = margin + height * (row + 1)/ rows;
    return [x0,x1,y0,y1];
}

//console.log(getCellBounds(0,0, 450,600));

function drawBoard(rows, cols, width,height) {
    for (var row = 0; row < rows; row ++) {
        for (var col = 0; col < cols; col++) {
            drawCell(row,col,width,height);
        }
    }
}

function drawCell(row, col, width, height) {
    var bounds = getCellBounds(row,col, width, height);
    x0 = bounds[0];
    x1 = bounds[1];
    y0 = bounds[2];
    y1 = bounds[3];

    rectMode(CORNER);

    var cellCube = new cube(x0 ,y0, false);

    cellCube.display();
    //quad(x0,y0, x0,y0 + 40, x0+40, y0+40, x0 + 40, y0 );

}

function cube(x,y, isSolid) { //the cube
    this.x = x;
    this.y = y;

    this.width = 45;


    this.NW =[this.x, this.y];    
    this.NE = [x+this.width, this.y];

    this.SE = [this.x+this.width, y+this.width];

    this.SW = [this.x, y+this.width];

    this.larger = new square(x,y,this.width, this.width);
    this.smaller = new square(x + (cx -x) * 0.25, y + (cy - y) *0.25, this.width * 0.75, this.width * 0.75);


    this.NWs =[(this.x + (cx - this.x) * 0.20), this.y + (cy - this.y) * 0.20];
     
    this.NEs = [(this.x + (cx - this.x) * 0.20) + (this.width * 0.8), this.y + (cy - this.y) * 0.20];

    this.SEs = [(this.x + (cx - this.x) * 0.20) + (this.width * 0.8), this.y + (cy - this.y) * 0.20 + (this.width * 0.8)];

    this.SWs = [(this.x + (cx - this.x) * 0.20), this.y + (cy - this.y) * 0.20 +(this.width * 0.8)];




   
    this.display = function() {


        //rect(this.x, this.y, this.width, this.width);
        //rect(this.x + (cx - this.x) * 0.20, this.y + (cy - this.y) * 0.20, this.width * 0.8, this.width * 0.8);
        stroke(127);
        //bigger square
        line(this.x,this.y, this.x + this.width, this.y);
        line(this.x,this.y, this.x, this.y + this.width);
        line(this.x + this.width, this.y, this.x + this.width, this.y + this.width);
        line(this.x, this.y+ this.width, this.x + this.width, this.y + this.width);

        //smaller square
        line(this.x + (cx - this.x) * 0.20, this.y + (cy - this.y) * 0.20, (this.x + (cx - this.x) * 0.20 )+ this.width * 0.8, this.y + (cy - this.y) * 0.20);
        line(this.x + (cx - this.x) * 0.20, this.y + (cy - this.y) * 0.20, this.x + (cx - this.x) * 0.20, this.y + (cy - this.y) * 0.20 + this.width * 0.8);
        line(this.x + (cx - this.x) * 0.20 + this.width * 0.8, this.y + (cy - this.y) * 0.20,this.x + (cx - this.x) * 0.20 + this.width * 0.8,this.y + (cy - this.y) * 0.20 + this.width * 0.8);
        line(this.x + (cx - this.x) * 0.20, this.y + (cy - this.y) * 0.20 + this.width * 0.8, (this.x + (cx - this.x) * 0.20 )+ this.width * 0.8, this.y + (cy - this.y) * 0.20 + this.width * 0.8);

        if (isSolid == false) {

            line(this.NW[0], this.NW[1], this.NWs[0], this.NWs[1]);
            line(this.NE[0], this.NE[1], this.NEs[0], this.NEs[1]);
            line(this.SE[0], this.SE[1], this.SEs[0], this.SEs[1]);

            line(this.SW[0], this.SW[1], this.SWs[0], this.SWs[1]);
        }

        if (isSolid) {
            noStroke();
            fill(230);
            quad(this.SW[0], this.SW[1], this.SE[0], this.SE[1], this.SEs[0], this.SEs[1], this.SWs[0], this.SWs[1]); //bottom
            quad(this.NE[0], this.NE[1], this.NEs[0], this.NEs[1], this.SEs[0], this.SEs[1], this.SE[0], this.SE[1]); // right
            quad(this.NW[0], this.NW[1], this.NWs[0], this.NWs[1], this.SWs[0], this.SWs[1], this.SW[0], this.SW[1]); //fill left
            quad(this.NE[0], this.NE[1], this.NEs[0], this.NEs[1], this.NWs[0], this.NWs[1], this.NW[0], this.NW[1]); //fill top
            fill(240);
            quad(this.NE[0], this.NE[1], this.SE[0], this.SE[1], this.SW[0], this.SW[1], this.NW[0], this.NW[1]);
        }



    };

}

function square(x,y,w,h) {
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;

    this.getCorners = function() {
        var NW =[x-w/2,y-h/2];
        //print(NW);
        var NE = [x+w/2, y-h/2];
        var SE = [x+w/2, y-h/2];
        var SW = [x-w/2, y+h/2];

        return [NW,NE,SE,SW];
    };
}


function keyPressed() {
    if (keyCode == LEFT_ARROW) {
        moveFallingPiece(0, -1);
    }

    if (keyCode == RIGHT_ARROW) {
        moveFallingPiece(0,1);
    }

    if (keyCode == DOWN_ARROW) {
        moveFallingPiece(-1,0);
    }

    if (keyCode == UP_ARROW) {
        rotateFallingPiece();
    }
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>p5.js vers 0.7.1, Edit index.html to Change This Title</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.1/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.1/addons/p5.dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.1/addons/p5.sound.js"></script>
    <script src="sketch.js" type="text/javascript"></script>
  </head>
  <body>
  </body>
</html>

The placeFallingPiece function is quite similar to drawFallingPiece, only rather than draw the cells, it loads the corresponding cells of the fallingPiece onto the board with the fallingPieceColor. In this way, the piece is placed on the board.


Solution

  • Wow, that looks like a lot of work ! You might want to either simplify or start fresh to solve your problem. (You can always make it more complex later, but if it doesn't work as you need to ,it's best to make easy to tweak/change first).

    Unfortunately I won't be able to provide a snippet for every single problem, but I can point a few things that might help you fix your bug:

    1. You already has a condition that checks if the falling piece is at the bottom and it works: if (moveFallingPiece(-1,0) == false) {
    2. Currently you only have a single piece and when it reaches the bottom you "recycle" it as a new piece (changing it's Tetris piece type and resetting the row to 0): you might want to create an array to store the pieces that have fallen. This way you can choose stop updating the row (stop them from falling), but keep rendering them on screen and also solve the logic of which parts of which piece connect as a continuos last row
    3. The code is very tighly coupled. You might want to re-organise your objects so it's easier to handle multiple pieces, each pieces with parts.

    (e.g. create a class and instances (objects) , solve any collisions (be it with the bottom of the screen or other pieces) and add multiple objects to the array as collisions are detected to form full rows (gain points) or stack up)

    I would suggest doing a 2D version with p5.js first, then simply change the p5.js renderer to WEBGL and swap your rect() calls for box() calls (taking into account they're drawn from centre and using push()/pop() calls to isolate coordinate spaces for each box). (A bit further down the line you might want to look at the MVC pattern and have the rendering/view separate from the data and control logic, allowing you to change from a 2D to 3D Tetris if you want to)