Im making Tetris with JS (Repost)

Here is my code, it says that it can’t read X in the function DeleteTetromino() section, and if there are any other errors that you can fix just add that. :slight_smile:

let canvas;
let ctx;
let gBArrayHeight = 22;
let gBArrayWidth = 12;
let startX = 5;
let startY = 0;
let score = 0;
let level = 1;
let winOrLose = "Playing";
let coordinateArray = [...Array(gBArrayHeight)].map(e => Array(gBArrayWidth).fill(0));
let curTetromino = [[1, 0], [0, 1], [1, 1], [2, 1]];
let floor = 22;
let roof = 1;


let tetrominos = [];
let tetrominoColors = ['purple', 'cyan', 'blue', 'yellow', 'orange', 'green', 'red'];
let curTetrominoColor;

let gameBoardArray = [...Array(22)].map(e => Array(12).fill(0));
let stoppedShapeArray = [...Array(22)].map(e => Array(12).fill(0));

let DIRECTION = {
  IDLE: 0,
  DOWN: 1,
  LEFT: 2,
  RIGHT: 3
};
let direction;

class Coordinates {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

document.addEventListener('DOMContentLoaded', SetupCanvas);

function CreateCoordArray() {
  let i = 0, j = 0;
  for (let y = 8; y <= 506; y += 23) {
    for (let x = 8; x <= 264; x += 23) {
      coordinateArray[i][j] = new Coordinates(x, y);
      i++;
    }
    j++
    i = 0;
  }
}

function SetupCanvas() {
  canvas = document.getElementById('my-canvas');
  ctx = canvas.getContext('2d');
  canvas.width = 288;
  canvas.height = 518;

  ctx.scale(1, 1);

  ctx.fillStyle = 'black';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  ctx.strokeStyle = 'black';
  ctx.strokeRect(0, 0, 288, 518);


  document.addEventListener('keydown', HandleKeyPress);
  CreateTetrominos();
  CreateTetromino();

  CreateCoordArray();
  DrawTetromino();
}

function DrawTetromino() {
  for (let i = 0; i < curTetromino.length; i++) {
    let x = curTetromino[i][0] + startX;
    let y = curTetromino[i][1] + startY;
    gameBoardArray[x][y] = 1;
    let coorX = coordinateArray[x][y].x;
    let coorY = coordinateArray[x][y].y;
    ctx.fillStyle = curTetrominoColor;
    ctx.fillRect(coorX, coorY, 21, 21);
  }
}

function HandleKeyPress(key) {
  if (winOrLose != "Game Over") {
    if (key.keyCode === 65) {
      direction = DIRECTION.LEFT;
      if (!HittingTheWall() && !CheckForHorizontalCollision()) {
        DeleteTetromino();
        startX--;
        DrawTetromino();
      }
    } else if (key.keyCode === 68) {
      direction = DIRECTION.RIGHT;
      if (!HittingTheWall() && !CheckForHorizontalCollision()) {
        DeleteTetromino();
        startX++;
        DrawTetromino();
      }
    } else if (key.keyCode === 83) {
      MoveTetrominoDown();
    } else if (key.keyCode === 69) {
      RotateTetromino();
    }
  }
}

function MoveTetrominoDown() {
  direction = DIRECTION.DOWN;
  if (!CheckForVerticalCollision()) {
    DeleteTetromino();
    startY++;
    DrawTetromino();
  }
}

if (score >= 5) {
  level++;
}

var speed = 700;
if (level++) {
  window.setInterval(speed++, 100)
}

window.setInterval(function() {
  if (winOrLose != "Game Over") {
    MoveTetrominoDown();
  }
}, speed)

function DeleteTetromino() {
  for (let i = 0; i < curTetromino.length; i++) {
    let x = curTetromino[i][0] + startX;
    let y = curTetromino[i][1] + startY;
    gameBoardArray[x][y] = 1;
    let coorX = coordinateArray[x][y].x;
    let coorY = coordinateArray[x][y].y;
    ctx.fillStyle = 'black';
    ctx.fillRect(coorX, coorY, 21, 21);
  }
}

function CreateTetrominos() {
  // T
  tetrominos.push([[1, 0], [0, 1], [1, 1], [2, 1]]);
  // I
  tetrominos.push([[0, 0], [1, 0], [2, 0], [3, 0]]);
  // J
  tetrominos.push([[0, 0], [0, 1], [1, 1], [2, 1]]);
  // Square
  tetrominos.push([[0, 0], [1, 0], [0, 1], [1, 1]]);
  // L
  tetrominos.push([[2, 0], [0, 1], [1, 1], [2, 1]]);
  // S
  tetrominos.push([[1, 0], [2, 0], [0, 1], [1, 1]]);
  // z
  tetrominos.push([[0, 0], [1, 0], [1, 1], [2, 1]]);
}

function CreateTetromino() {
  let randomTetromino = Math.floor(Math.random() * tetrominos.length);
  curTetromino = tetrominos[randomTetromino];
  curTetrominoColor = tetrominoColors[randomTetromino];
}

function HittingTheWall() {
  for (let i = 0; i < curTetromino.length; i++) {
    let newX = curTetromino[i][0] + startX;
    if (newX <= 0 && direction === DIRECTION.LEFT) {
      return true;
    } else if (newX >= 11 && direction === DIRECTION.RIGHT) {
      return true;
    }
  }
  return false;
}


function CheckForVerticalCollision() {
  let tetrominoCopy = curTetromino;
  let collision = false;
  for (let i = 0; i < tetrominoCopy.length; i++) {
    let square = tetrominoCopy[i];
    let x = square[0] + startX;
    let y = square[1] + startY;
    if (direction === DIRECTION.DOWN) {
      y++;
    }
    if (gameBoardArray[x][y + 1] === 1) {
      if (typeof stoppedShapeArray[x][y + 1] === 'string') {
        DeleteTetromino();
        startY++;
        DrawTetromino();
        collision = true;
        break;
      }
      if (y == floor) {
        collision = true;
        break;
      }
    }
    if (collision) {
      if (startY == roof) {
        winOrLose = "Game Over";
      } else {
        for (let i = 0; i < tetrominoCopy.length; i++) {
          let square = tetrominoCopy[i];
          let x = square[0] + startX;
          let y = square[1] + startY;
          stoppedShapeArray[x][y] = curTetrominoColor;
        }
        CheckForCompletedRows();
        CreateTetromino();
        direction = DIRECTION.IDLE;
        startX = 4;
        startY = 0;
        DrawTetromino();
      }
    }
  }
}

function CheckForHorizontalCollision() {
  let tetrominoCopy = curTetromino;
  let collision = false;
  for (let i = 0; i < tetrominoCopy.length; i++) {
    let square = tetrominoCopy[i];
    let x = square[0] + startX;
    let y = square[1] + startY;

    if (direction === DIRECTION.LEFT) {
      x--;
    } else if (direction === DIRECTION.RIGHT) {
      x++;
    }
    var stoppedShapeVal = stoppedShapeArray[x][y];
    if (typeof stoppedShapeVal === 'string') {
      collision = true;
      break;
    }
  }
  return collision;
}

function CheckForCompletedRows() {
  let rowsToDelete = 0;
  let startOfDeletion = 0;
  for (let y = 0; y < gBArrayHeight; y++) {
    let completed = true;
    for (let x = 0; x < gBArrayWidth; x++) {
      let square = stoppedShapeArray[x][y];
      if (square === 0 || (typeof square === 'undefined')) {
        completed = false;
        break;
      }
    }
    if (completed) {
      if (startOfDeletion === 0) startOfDeletion = y;
      rowsToDelete++;
      for (let i = 0; i < gBArrayWidth; i++) {
        stoppedShapeArray[i][y] = 0;
        gameBoardArray[i][y] = 0;
        let coorX = coordinateArray[i][y].x;
        let coorY = coordinateArray[i][y].y;
        ctx.fillStyle = 'black';
        ctx.fillRect(coorX, coorY, 21, 21);
        level++;
      }
    }
  }
  if (rowsToDelete > 0) {
    score += 10;
    MoveAllRowsDown(rowsToDelete, startOfDeletion);
  }
}

function MoveAllRowsDown(rowsToDelete, startOfDeletion) {
  for (var i = startOfDeletion - 1; i >= 0; i--) {
    for (var x = 0; gBArrayWidth; x++) {
      var y2 = i + rowsToDelete;
      var square = stoppedShapeArray[x][i];
      var nextSquare = stoppedShapeArray[x][y2];
      if (typeof square === 'string') {
        nextSquare = square;
        gameBoardArray[x][y2] = 1;
        stoppedShapeArray[x][y2] = square;
        let coorX = coordinateArray[x][y2].x;
        let coorY = coordinateArray[x][y2].y;
        ctx.fillStyle = nextSquare;
        ctx.fillRect(coorX, coorY, 21, 21);

        square = 0;
        gameBoardArray[x][i] = 0;
        stoppedShapeArray[x][i] = 0;
        coorX = coordinateArray[x][i].x;
        coorY = coordinateArray[x][i].y;
        ctx.fillStyle = 'black';
        ctx.fillRect(coorX, coorY, 21, 21);
      }
    }
  }
}

function RotateTetromino() {
  let newRotation = new Array();
  let tetrominoCopy = curTetromino;
  let curTetrominoBU;
  for (let i = 0; i < tetrominoCopy.length; i++) {
    curTetrominoBU = [...curTetromino];
    let x = tetrominoCopy[i][0];
    let y = tetrominoCopy[i][1];
    let newX = (GetLastSquareX() - y);
    let newY = x;
    newRotation.push([newX, newY]);
  }
  DeleteTetromino();
  try {
    curTetromino = newRotation;
    DrawTetromino();
  }
  catch (e) {
    if (e instanceof TypeError) {
      curTetromino = curTetrominoBU;
      DeleteTetromino();
      DrawTetromino();
    }
  }
}

function GetLastSquareX() {
  let lastX = 0;
  for (let i = 0; i < curTetromino.length; i++) {
    let square = curTetromino[i];
    if (square[0] > lastX) {
      lastX = square[0];
    }
  }
  return lastX;
} ```

@IsaacSands is this a school assignment? Which line is the error occurring at?

No this is just a home project, the error is around line 139. The problem is in this section:

function DeleteTetromino() {
  for (let i = 0; i < curTetromino.length; i++) {
    let x = curTetromino[i][0] + startX;
    let y = curTetromino[i][1] + startY;
    gameBoardArray[x][y] = 1;
    let coorX = coordinateArray[x][y].x;
    let coorY = coordinateArray[x][y].y;
    ctx.fillStyle = 'black';
    ctx.fillRect(coorX, coorY, 21, 21);
  }
}

@IsaacSands does this code fix your problem?

let ctx;
let gBArrayHeight = 22;
let gBArrayWidth = 12;
let startX = 5;
let startY = 0;
let score = 0;
let level = 1;
let winOrLose = "Playing";
let coordinateArray = [...Array(gBArrayHeight)].map(e => Array(gBArrayWidth).fill(0));
let curTetromino = [[1, 0], [0, 1], [1, 1], [2, 1]];
let floor = 22;
let roof = 1;


let tetrominos = [];
let tetrominoColors = ['purple', 'cyan', 'blue', 'yellow', 'orange', 'green', 'red'];
let curTetrominoColor;

let gameBoardArray = [...Array(22)].map(e => Array(12).fill(0));
let stoppedShapeArray = [...Array(22)].map(e => Array(12).fill(0));

let DIRECTION = {
  IDLE: 0,
  DOWN: 1,
  LEFT: 2,
  RIGHT: 3
};
let direction;

class Coordinates {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

document.addEventListener('DOMContentLoaded', SetupCanvas);

function CreateCoordArray() {
  let i = 0, j = 0;
  for (let y = 8; y <= 506; y += 23) {
    for (let x = 8; x <= 264; x += 23) {
      coordinateArray[i][j] = new Coordinates(x, y);
      i++;
    }
    j++
    i = 0;
  }
}

function SetupCanvas() {
  canvas = document.getElementById('my-canvas');
  ctx = canvas.getContext('2d');
  canvas.width = 288;
  canvas.height = 518;

  ctx.scale(1, 1);

  ctx.fillStyle = 'black';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  ctx.strokeStyle = 'black';
  ctx.strokeRect(0, 0, 288, 518);


  document.addEventListener('keydown', HandleKeyPress);
  CreateTetrominos();
  CreateTetromino();

  CreateCoordArray();
  DrawTetromino();
}

function DrawTetromino() {
  for (let i = 0; i < curTetromino.length; i++) {
    let x = curTetromino[i][0] + startX;
    let y = curTetromino[i][1] + startY;
    gameBoardArray[x][y] = 1;
    let coorX = coordinateArray[x][y].x;
    let coorY = coordinateArray[x][y].y;
    ctx.fillStyle = curTetrominoColor;
    ctx.fillRect(coorX, coorY, 21, 21);
  }
}

function HandleKeyPress(event) {
  if (winOrLose != "Game Over") {
    if (event.keyCode === 65) {
      direction = DIRECTION.LEFT;
      if (!HittingTheWall() && !CheckForHorizontalCollision()) {
        DeleteTetromino();
        startX--;
        DrawTetromino();
      }
    } else if (event.keyCode === 68) {
      direction = DIRECTION.RIGHT;
      if (!HittingTheWall() && !CheckForHorizontalCollision()) {
        DeleteTetromino();
        startX++;
        DrawTetromino();
      }
    } else if (event.keyCode === 83) {
      MoveTetrominoDown();
    } else if (event.keyCode === 69) {
      RotateTetromino();
    }
  }
}

function MoveTetrominoDown() {
  direction = DIRECTION.DOWN;
  if (!CheckForVerticalCollision()) {
    DeleteTetromino();
    startY++;
    DrawTetromino();
  }
}

if (score >= 5) {
  level++;
}

let speed = 700;
if (level++) {
  setInterval(function () {
    if (winOrLose != "Game Over") {
      MoveTetrominoDown();
    }
  }, speed);
}

setInterval(function() {
  if (winOrLose != "Game Over") {
    MoveTetrominoDown();
  }
}, speed);

function DeleteTetromino() {
  for (let i = 0; i < curTetromino.length; i++) {
    let x = curTetromino[i][0] + startX;
    let y = curTetromino[i][1] + startY;
    gameBoardArray[x][y] = 1;
    let coorX = coordinateArray[x][y].x;
    let coorY = coordinateArray[x][y].y;
    ctx.fillStyle = 'black';
    ctx.fillRect(coorX, coorY, 21, 21);
  }
}

function CreateTetrominos() {
  // T
  tetrominos.push([[1, 0], [0, 1], [1, 1], [2, 1]]);
  // I
  tetrominos.push([[0, 0], [1, 0], [2, 0], [3, 0]]);
  // J
  tetrominos.push([[0, 0], [0, 1], [1, 1], [2, 1]]);
  // Square
  tetrominos.push([[0, 0], [1, 0], [0, 1], [1, 1]]);
  // L
  tetrominos.push([[2, 0], [0, 1], [1, 1], [2, 1]]);
  // S
  tetrominos.push([[1, 0], [2, 0], [0, 1], [1, 1]]);
  // z
  tetrominos.push([[0, 0], [1, 0], [1, 1], [2, 1]]);
}

function CreateTetromino() {
  let randomTetromino = Math.floor(Math.random() * tetrominos.length);
  curTetromino = tetrominos[randomTetromino];
  curTetrominoColor = tetrominoColors[randomTetromino];
}

function HittingTheWall() {
  for (let i = 0; i < curTetromino.length; i++) {
    let newX = curTetromino[i][0] + startX;
    if (newX <= 0 && direction === DIRECTION.LEFT) {
      return true;
    } else if (newX >= 11 && direction === DIRECTION.RIGHT) {
      return true;
    }
  }
  return false;
}

function CheckForVerticalCollision() {
  let tetrominoCopy = [...curTetromino];
  let collision = false;
  for (let i = 0; i < tetrominoCopy.length; i++) {
    let square = tetrominoCopy[i];
    let x = square[0] + startX;
    let y = square[1] + startY;
    if (direction === DIRECTION.DOWN) {
      y++;
    }
    if (gameBoardArray[x][y + 1] === 1) {
      if (typeof stoppedShapeArray[x][y + 1] === 'string') {
        DeleteTetromino();
        startY++;
        DrawTetromino();
        collision = true;
        break;
      }
      if (y == floor) {
        collision = true;
        break;
      }
    }
  }
  if (collision) {
    if (startY == roof) {
      winOrLose = "Game Over";
    } else {
      for (let i = 0; i < tetrominoCopy.length; i++) {
        let square = tetrominoCopy[i];
        let x = square[0] + startX;
        let y = square[1] + startY;
        stoppedShapeArray[x][y] = curTetrominoColor;
      }
      CheckForCompletedRows();
      CreateTetromino();
      direction = DIRECTION.IDLE;
      startX = 4;
      startY = 0;
      DrawTetromino();
    }
  }
}

function CheckForHorizontalCollision() {
  let tetrominoCopy = [...curTetromino];
  let collision = false;
  for (let i = 0; i < tetrominoCopy.length; i++) {
    let square = tetrominoCopy[i];
    let x = square[0] + startX;
    let y = square[1] + startY;

    if (direction === DIRECTION.LEFT) {
      x--;
    } else if (direction === DIRECTION.RIGHT) {
      x++;
    }
    var stoppedShapeVal = stoppedShapeArray[x][y];
    if (typeof stoppedShapeVal === 'string') {
      collision = true;
      break;
    }
  }
  return collision;
}

function CheckForCompletedRows() {
  let rowsToDelete = 0;
  let startOfDeletion = 0;
  for (let y = 0; y < gBArrayHeight; y++) {
    let completed = true;
    for (let x = 0; x < gBArrayWidth; x++) {
      let square = stoppedShapeArray[x][y];
      if (square === 0 || (typeof square === 'undefined')) {
        completed = false;
        break;
      }
    }
    if (completed) {
      if (startOfDeletion === 0) startOfDeletion = y;
      rowsToDelete++;
      for (let i = 0; i < gBArrayWidth; i++) {
        stoppedShapeArray[i][y] = 0;
        gameBoardArray[i][y] = 0;
        let coorX = coordinateArray[i][y].x;
        let coorY = coordinateArray[i][y].y;
        ctx.fillStyle = 'black';
        ctx.fillRect(coorX, coorY, 21, 21);
      }
    }
  }
  if (rowsToDelete > 0) {
    score += 10;
    MoveAllRowsDown(rowsToDelete, startOfDeletion);
  }
}

function MoveAllRowsDown(rowsToDelete, startOfDeletion) {
  for (var i = startOfDeletion - 1; i >= 0; i--) {
    for (var x = 0; x < gBArrayWidth; x++) {
      var y2 = i + rowsToDelete;
      var square = stoppedShapeArray[x][i];
      var nextSquare = stoppedShapeArray[x][y2];
      if (typeof square === 'string') {
        nextSquare = square;
        gameBoardArray[x][y2] = 1;
        stoppedShapeArray[x][y2] = square;
        let coorX = coordinateArray[x][y2].x;
        let coorY = coordinateArray[x][y2].y;
        ctx.fillStyle = nextSquare;
        ctx.fillRect(coorX, coorY, 21, 21);

        square = 0;
        gameBoardArray[x][i] = 0;
        stoppedShapeArray[x][i] = 0;
        coorX = coordinateArray[x][i].x;
        coorY = coordinateArray[x][i].y;
        ctx.fillStyle = 'black';
        ctx.fillRect(coorX, coorY, 21, 21);
      }
    }
  }
}

function RotateTetromino() {
  let newRotation = new Array();
  let tetrominoCopy = [...curTetromino];
  let curTetrominoBU;
  for (let i = 0; i < tetrominoCopy.length; i++) {
    curTetrominoBU = [...curTetromino];
    let x = tetrominoCopy[i][0];
    let y = tetrominoCopy[i][1];
    let newX = (GetLastSquareX() - y);
    let newY = x;
    newRotation.push([newX, newY]);
  }
  DeleteTetromino();
  try {
    curTetromino = newRotation;
    DrawTetromino();
  }
  catch (e) {
    if (e instanceof TypeError) {
      curTetromino = curTetrominoBU;
      DeleteTetromino();
      DrawTetromino();
    }
  }
}

function GetLastSquareX() {
  let lastX = 0;
  for (let i = 0; i < curTetromino.length; i++) {
    let square = curTetromino[i];
    if (square[0] > lastX) {
      lastX = square[0];
    }
  }
  return lastX;
}

I found several errors that could/made the game not work such as:
• The HandleKeyPress function’s parameter name was incorrect. It should be event instead of key.
• The condition if (score >= 5) was not wrapped in a block, resulting in an unwanted result. It should be wrapped in curly braces ({}).
• The level++ expression was incorrectly used in the condition for the setInterval function. It should be level > 0 or something similar.
• The setInterval function was called incorrectly. The correct way is setInterval(callback, delay).
• The speed variable in the setInterval function was incremented wrong. Instead of speed++, it should be speed += 100 to up the speed by 100 milliseconds.
• The CheckForVerticalCollision function was missing a bracket at the end (]).
• The MoveAllRowsDown function was missing the condition for the inner loop (x < gBArrayWidth).

No, it doesn’t seem to be loading now, however it’s rather difficult to troubleshoot as my school blocks the inspect tool and I can’t see what’s going wrong at the moment, I will get back to you when possible, Thanks for now.

Try calling this script at the top of your code to give you a console:

(function () {
var script = document.createElement('script');
script.src="//cdn.jsdelivr.net/npm/eruda";
document.body.appendChild(script);
script.onload = function () { eruda.init() }
})();
1 Like

The error you mentioned is likely due to a typo in your code. In the MoveTetrominoDown() function, you have an if condition and some subsequent code that should be inside a separate block or function.

// ...

window.setInterval(function() {
  if (winOrLose != "Game Over") {
    MoveTetrominoDown();
  }
}, speed);

function DeleteTetromino() {
  // ...
}

// ...

You can look up bookmarklets for Inspect element clones. It might work

1 Like

Generally, that won’t work anymore either. Schools have a way (at least on chrome) to block bookmarklets as well now.

1 Like

I am aware that they can block URLs, but my school is clueless so they’re not blocked for us. Schools usually don’t go searching what a bookmarklet is all of a sudden. OP might have a clueless school aswell

Hi. Keep it friendly please. The IT team in a school might be unaware but usually they buy filtering / firewall solutions from third parties who would make the decisions for them.

2 Likes

My school has two filtering software, and bookmarklets are still unblocked.

I did not mean for it to be mean towards the schools.

1 Like

That’s not what I meant. The post could be read as argumentative. Thanks for replying so quickly though! :smile:

4 Likes

No sadly not, it still has the same problem with the “Cannot read properties of undefined (reading ‘x’)”

try:

<!DOCTYPE html>
<html>
<head>
    <title>Tetris Game</title>
    <style>
        canvas {
            border: 1px solid black;
        }
    </style>
</head>
<body>
    <canvas id="my-canvas"></canvas>
    <script>

    let canvas;
    let ctx;
    let gBArrayHeight = 22;
    let gBArrayWidth = 12;
    let startX = 5;
    let startY = 0;
    let score = 0;
    let level = 1;
    let winOrLose = "Playing";
    let coordinateArray = [...Array(gBArrayHeight)].map(e => Array(gBArrayWidth).fill(0));
    let curTetromino = [[1, 0], [0, 1], [1, 1], [2, 1]];

    let tetrominos = [];
    let tetrominoColors = ['purple', 'cyan', 'blue', 'yellow', 'orange', 'green', 'red'];
    let curTetrominoColor;

    let gameBoardArray = [...Array(22)].map(e => Array(12).fill(0));
    let stoppedShapeArray = [...Array(22)].map(e => Array(12).fill(0));

    let DIRECTION = {
      IDLE: 0,
      DOWN: 1,
      LEFT: 2,
      RIGHT: 3
    };
    let direction;

    class Coordinates {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    }
    
    document.addEventListener('DOMContentLoaded', SetupCanvas);

    function CreateCoordArray() {
      let xR = 0, yR = 9;
      let newBlock = true;
      startGameArray = gBArrayHeight - startY + 1;
      for (let i = 0; i < gBArrayHeight; i++) {
        for (let j = 0; j < gBArrayWidth; j++) {
          coordinateArray[i][j] = new Coordinates(xR, yR);
          i++;
        }
        yR -= 23;
        i = 0;
      }
    }

    function SetupCanvas() {
      canvas = document.getElementById('my-canvas');
      ctx = canvas.getContext('2d');
      canvas.width = 438;
      canvas.height = 768;

      ctx.scale(23, 23);

      ctx.fillStyle = 'black';
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      ctx.strokeStyle = 'black';
      ctx.strokeRect(0, 0, canvas.width - 23, canvas.height - 23);
      ctx.strokeStyle = 'black';

      document.addEventListener('keydown', HandleKeyPress);
      CreateTetrominos();
      CreateTetromino();
      CreateCoordArray();
      DrawTetromino();
    }

    function DrawTetromino() {
      for (let i = 0; i < curTetromino.length; i++) {
        let x = curTetromino[i][0] + startX;
        let y = curTetromino[i][1] + startY;
        console.log(y);
        gameBoardArray[x][y] = 1;
        let coorX = coordinateArray[x][y].x;
        let coorY = coordinateArray[x][y].y;
        ctx.fillStyle = curTetrominoColor;
        ctx.fillRect(coorX, coorY, 21, 21);
      }
    }

    function HandleKeyPress(key) {
      if (winOrLose != "Game Over") {
        if (key.keyCode === 65) {
          direction = DIRECTION.LEFT;
          if (!HittingTheWall() && !CheckForHorizontalCollision()) {
            DeleteTetromino();
            startX--;
            DrawTetromino();
          }
        } else if (key.keyCode === 68) {
          direction = DIRECTION.RIGHT;
          if (!HittingTheWall() && !CheckForHorizontalCollision()) {
            DeleteTetromino();
            startX++;
            DrawTetromino();
          }
        } else if (key.keyCode === 83) {
          MoveTetrominoDown();
        } else if (key.keyCode === 69) {
          RotateTetromino();
        }
      }
    }

    function MoveTetrominoDown() {
      direction = DIRECTION.DOWN;
      if (!CheckForVerticalCollision()) {
        DeleteTetromino();
        startY++;
        DrawTetromino();
      }
    }

    var speed = 900 - (level * 200);
    
    if (!(level == 0 || winOrLose == 'Game Over')){
      setTimeout(() => {
        MoveTetrominoDown();
      }, speed)
    } else {
      speed = 900;
    }

    function DeleteTetromino() {
      for (let i = 0; i < curTetromino.length; i++) {
        let x = curTetromino[i][0] + startX;
        let y = curTetromino[i][1] + startY;
        gameBoardArray[x][y] = 0;
        let coorX = coordinateArray[x][y].x;
        let coorY = coordinateArray[x][y].y;
        ctx.fillStyle = 'black';
        ctx.fillRect(coorX, coorY, 21, 21);
      }
    }

    function CreateTetrominos() {
      // T
      tetrominos.push([[1, 0], [0, 1], [1, 1], [2, 1]]);
      // I
      tetrominos.push([[0, 0], [1, 0], [2, 0], [3, 0]]);
      // J
      tetrominos.push([[0, 0], [0, 1], [1, 1], [2, 1]]);
      // Square
      tetrominos.push([[0, 0], [1, 0], [0, 1], [1, 1]]);
      // L
      tetrominos.push([[2, 0], [0, 1], [1, 1], [2, 1]]);
      // S
      tetrominos.push([[1, 0], [2, 0], [0, 1], [1, 1]]);
      // Z
      tetrominos.push([[0, 0], [1, 0], [1, 1], [2, 1]]);
    }

    function CreateTetromino() {
      let randomTetromino = Math.floor(Math.random() * tetrominos.length);
      curTetromino = tetrominos[randomTetromino];
      curTetrominoColor = tetrominoColors[randomTetromino];
    }

    function HittingTheWall() {
      for (let i = 0; i < curTetromino.length; i++) {
        let newX = curTetromino[i][0] + startX;
        if (newX <= 0 && direction === DIRECTION.LEFT) {
          return true;
        } else if (newX >= 11 && direction === DIRECTION.RIGHT) {
          return true;
        }
      }
      return false;
    }

    function CheckForVerticalCollision() {
      let tetrominoCopy = curTetromino;
      let collision = false;
      for (let i = 0; i < tetrominoCopy.length; i++) {
        let square = tetrominoCopy[i];
        let x = square[0] + startX;
        let y = square[1] + startY;
        if (direction === DIRECTION.DOWN) {
          y++;
        }
        if (typeof stoppedShapeArray[x][y+1] === 'string') {
          DeleteTetromino();
          startY++;
          DrawTetromino();
          collision = true;
          break;
        }
        if (y >= 20) {
          collision = true;
          break;
        }
      }
      if(collision){
        if(y == 1){
           winOrLose = "Game Over";
           ctx.fillStyle = 'white';
           ctx.font = '21px Arial';
           ctx.fillText("Press 'R' to restart", 5, canvas.height / 2);
        } else{
          for(let i = 0; i < tetrominoCopy.length; i++){
            let square = tetrominoCopy[i];
            let x = square[0] + startX;
            let y = square[1] + startY;
            stoppedShapeArray[x][y] = curTetrominoColor;
          }
          CheckForCompletedRows();
          direction = DIRECTION.IDLE;
          startX = 5;
          startY = 1;
          CreateTetromino();
        }
      }
    }

    function CheckForHorizontalCollision() {
      let tetrominoCopy = curTetromino;
      let collision = false;
      for (let i = 0; i < tetrominoCopy.length; i++) {
        let square = tetrominoCopy[i];
        let x = square[0] + startX;
        let y = square[1] + startY;

        if (direction === DIRECTION.LEFT) {
          x--;
        } else if (direction === DIRECTION.RIGHT) {
          x++;
        }
        var stoppedShapeVal = stoppedShapeArray[x][y];
        if (typeof stoppedShapeVal === 'string') {
          collision = true;
          break;
        }
      }
      return collision;
    }

    function CheckForCompletedRows(){
      let rowsToDelete = 0;
      let startOfDeletion = 0;
      
      // Check every row to see if it has been completed
      for (let y = 0; y < gBArrayHeight ; y++) {
        let completed = true;
        for(let x = 0; x < gBArrayWidth; x++){
          let square = stoppedShapeArray[x][y];
          if(square === 0 || (typeof square === 'undefined')){
            completed = false;
          }
        }
        if (completed){
          if(startOfDeletion === 0) startOfDeletion = y;
          rowsToDelete++;
        
          // Found a completed row
          for(let i = 0; i < gBArrayWidth; i++){
            stoppedShapeArray[i][y] = 0;
            gameBoardArray[i][y] = 0;
            // Draw the gameboard again
            let coorX = coordinateArray[i][y].x;
            let coorY = coordinateArray[i][y].y;
            ctx.fillStyle = 'white';
            ctx.fillRect(coorX, coorY, 21, 21);
          }
        }
      }
      if(rowsToDelete > 0){
        score += 10;
        console.log(score);
        MoveAllRowsDown(rowsToDelete, startOfDeletion);
      }
    }

    function MoveAllRowsDown(rowsToDelete, startOfDel){
      for (var i = startOfDel-1; i >= 0; i--) {
        for (var x = 0; x < gBArrayWidth; x++) {
          var y2 = i + rowsToDelete;
          var squareVal = stoppedShapeArray[x][i];
          var nextSquareVal = stoppedShapeArray[x][y2];
          if (typeof squareVal === 'string') {
            nextSquareVal = squareVal;
            gameBoardArray[x][y2] = 1;
            stoppedShapeArray[x][y2] = squareVal;
            
            // Draw the square at the new coordinates
            let coorX = coordinateArray[x][y2].x;
            let coorY = coordinateArray[x][y2].y;
            ctx.fillStyle = nextSquareVal;
            ctx.fillRect(coorX, coorY, 21, 21);
            
            // Delete old square
            squareVal = 0;
            gameBoardArray[x][i] = 0;
            stoppedShapeArray[x][i] = 0;
            coorX = coordinateArray[x][i].x;
            coorY = coordinateArray[x][i].y;
            ctx.fillStyle = 'black';
            ctx.fillRect(coorX, coorY, 21, 21);
          }
        }
      }
    }

    function RotateTetromino() {
      let newRotation = new Array();
      let tetrominoCopy = curTetromino;
      let curTetrominoBU;
      for(let i = 0; i < tetrominoCopy.length; i++){
        curTetrominoBU = [...curTetromino];
        let x = tetrominoCopy[i][0];
        let y = tetrominoCopy[i][1];
        let newX = (GetLastSquareX() - y);
        let newY = x;
        newRotation.push([newX, newY]);
      }
      DeleteTetromino();
      try{
        curTetromino = newRotation;
        DrawTetromino();
      }
      catch(e){
        if(e instanceof TypeError){
          curTetromino = curTetrominoBU;
          DeleteTetromino();
          DrawTetromino();
        }
      }
    }

    function GetLastSquareX() {
      let lastX = 0;
      for(let i = 0; i < curTetromino.length; i++){
        let square = curTetromino[i];
        if(square[0] > lastX) {
          lastX = square[0];
        }
      }
      return lastX;
    }
    </script>
</body>
</html>