Why are my shapes disappearing when I soft-drop in my Tetris implementation?

I’m working on implementing Tetris in JavaScript. I’m new to JavaScript, so the way I’m coding may not be the most practical or efficient seeing as I’m trying to minimize the amount of help I’m getting.

That being said, when I try to move down my shape (soft-drop) before the shape appears in the grid, it’ll disappear halfway down the grid whether I let go or not. If the shape is already visible fully, it has no problem soft dropping. Only if I’m holding the down key it has issues.

Below is what I think it related to the issue. I cut out a lot of extra information, but I can’t tell if any function is messing with another. I’d appreciate any help and guidance! 🙂

I tried adjusting the conditions of when my pieces should lock, like after passing a certain y but that didn’t help. I added an invisible area above the grid where I tried spawning my pieces but that didn’t really work. I’m expecting that even if I preemptively soft-drop, the piece will just fly down the grid without disappearing. regardless of these changes, i still have disappearing shapes. I know this is a lot, but I appreciate any help!!

const ROW = 22;
const VISIBLE_ROW = 20;
const COL = COLUMN = 10;
const SQ = squareSize = 30;

// draw a square
function drawSquare(x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x * SQ, y * SQ, SQ, SQ);

  ctx.strokeStyle = 'WHITE';
  ctx.strokeRect(x * SQ, y * SQ, SQ, SQ);
}

// create the board
let board = [];
for (let r = 0; r < ROW; r++) {
  board[r] = [];
  for (let c = 0; c < COL; c++) {
    board[r][c] = VACANT;
  }
}

// draw the board
function drawBoard() {
  for (let r = 2; r < ROW; r++) {
    for (let c = 0; c < COL; c++) {
      drawSquare(c, r - 2, board[r][c]);
    }
  }
}

drawBoard();

// TETROMINO SHAPE EXAMPLE (of how I made them)
const Z = [
  [
    [1, 1, 0],
    [0, 1, 1],
    [0, 0, 0]
  ],

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


// piece constructor
function Piece(tetromino, color) {
  this.tetromino = tetromino;
  this.color = color;

  this.tetrominoN = 0; // start from the first pattern
  this.activeTetromino = this.tetromino[this.tetrominoN];

  // control the pieces
  this.x = 3;
  this.y = 1;
}

// draw piece to the board
Piece.prototype.draw = function() {
  for (let r = 0; r < this.activeTetromino.length; r++) {
    for (let c = 0; c < this.activeTetromino.length; c++) {
      // draw only occupied squares
      if (this.activeTetromino[r][c]) {
        let drawY = this.y + r;
        if (drawY >= 2) { // draw only visible rows
          drawSquare(this.x + c, drawY - 2, this.color);
        }
      }
    }
  }
}

// undraw piece from the board
Piece.prototype.unDraw = function() {
  for (let r = 0; r < this.activeTetromino.length; r++) {
    for (let c = 0; c < this.activeTetromino.length; c++) {
      // undraw only occupied squares
      if (this.activeTetromino[r][c]) {
        let drawY = this.y + r;
        if (drawY >= 2) { // draw only visible rows
          drawSquare(this.x + c, drawY - 2, VACANT);
        }
      }
    }
  }
}

// gravity dropping piece
Piece.prototype.gravityDrop = function() {
  if (!this.collision(0, 1, this.activeTetromino)) {
    this.unDraw();
    this.y++;
    this.draw();
  } else {
    this.handleLockDelay();
  }
}

// handle lock delay
Piece.prototype.handleLockDelay = function() {
  if (lockDelayTimer) {
    clearTimeout(lockDelayTimer);
  }

  lockDelayTimer = setTimeout(() => {
    // lock current piece and generate new piece
    this.lock();
    if (!gameOver) {
      p = randomPiece();
    }
  }, lockDelay);

  // increment move count and reset lock delay if under max moves
  if (moveCount < maxMoves) {
    moveCount++;
  } else {
    this.lock();
    if (!gameOver) {
      p = randomPiece();
    }
  }
}

// move piece down
Piece.prototype.softDrop = function() {
  if (this.y >= 1 && !this.collision(0, 1, this.activeTetromino)) {
    this.unDraw();
    this.y++;
    this.draw();
    score += 1; // add 1 point per row for soft drop
    updateScoreDisplay();
  } else {
    this.handleLockDelay();
  }
}

// collision detection
Piece.prototype.collision = function(x, y, piece) {
  for (let r = 0; r < piece.length; r++) {
    for (let c = 0; c < piece.length; c++) {
      if (!piece[r][c]) {
        continue;
      }
      let newX = this.x + c + x;
      let newY = this.y + r + y;
      if (newX < 0 || newX >= COL || newY >= ROW) {
        return true;
      }
      if (newY < 0) {
        return false;
      }
      if (board[newY][newX] != VACANT) {
        return true;
      }
    }
  }
  return false;
}

// lock piece and generate a new one
Piece.prototype.lock = function() {
  let lines = 0;
  for (let r = 0; r < this.activeTetromino.length; r++) {
    for (let c = 0; c < this.activeTetromino.length; c++) {
      if (!this.activeTetromino[r][c]) {
        continue;
      }
      if (this.y + r < 2) {
        // game over
        alert('Game Over');
        gameOver = true;
        break;
      }
      board[this.y + r][this.x + c] = this.color;
    }
  }
  // remove full rows & update score
  for (let r = 0; r < ROW; r++) {
    let isRowFull = true;
    for (let c = 0; c < COL; c++) {
      isRowFull = isRowFull && (board[r][c] != VACANT);
    }
    if (isRowFull) {
      for (let y = r; y > 1; y--) {
        for (let c = 0; c < COL; c++) {
          board[y][c] = board[y - 1][c];
        }
      }
      for (let c = 0; c < COL; c++) {
        board[0][c] = VACANT;
      }
      lines++;
    }
  }
  if (lines > 0) {
    // update score based on number of lines cleared
    const action = lines === 1 ? 'single' : lines === 2 ? 'double' : lines === 3 ? 'triple' : 'tetris';
    score += calculateScore(action, level);

    linesCleared += lines;
    if (linesCleared >= linesPerLevel) {
      level++;
      linesCleared -= linesPerLevel;
    }
    updateScoreDisplay();
  }
  drawBoard();
}

// Drop the piece every 1sec
let dropStart = Date.now();
let gameOver = false;

function drop() {
  let now = Date.now();
  let delta = now - dropStart;
  if (delta > getGravitySpeed(level)) {
    p.gravityDrop();
    dropStart = Date.now();
  }
  if (!gameOver) {
    requestAnimationFrame(drop);
  }
}

drop();
<span id="score"></span><br />
<span id="level"></span><br />
<span id="lines"></span><br />

<canvas id="gameCanvas" width="800" height="800"></canvas>