0

So I'm making a Tetris game and right now I'm trying to clear a row when each square is filled by a tetromino piece. So far I am able to determine if that row is filled up using the every() method on the row. It looks like this

function checkForPoints() {
    for(let r = 0; r < row; r++) {
        if(board[r].every(squareCheck)) {
            alert('yo');
            board[r].every(clearSquare);
        }
    }

    function squareCheck(sq) {
        if(sq !== vacant) return true;
        else return false;
    }

    function clearSquare(sq) {
        sq = vacant;
    }
}

If every square in the row is filled up then the condition is true and it should proceed to make all squares black or vacant in this case. However this doesn't happen. I have no clue why? My every() method works in determining if every square is not vacant, but that same method won't work in changing that array's elements to equal "black" or vacant?

// //create your globals
// const canvas = document.querySelector('#canvas');
// const ctx = canvas.getContext('2d');
// const row = 20;
// const col = 10;
// const sq = 40;
// const vacant = 'black';

// //-----------------------Why can't I initialize the tetrominos??

// //create and draw board
// let board = [];
// for(let r = 0; r < row; r++) {
//  board[r] = [];
//  for(let c = 0; c < col; c++) {
//   board[r][c] = vacant;
//   draw(c, r, board[r][c]);
//  }
// }

// //define a function to draw to the canvas
// function draw(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 an object for the tetrominos 
// function Tetromino(tetromino, color) {
//  this.tetromino = tetromino;
//  this.color = color;
//  this.tetrominoN = 0;
//  this.activeTetromino = this.tetromino[this.tetrominoN];
//  this.x = 0;
//  this.y = 0;
// }

// //create an array for the pieces
// const pieces = [
//  [Z, 'red'],
//  [S, 'limegreen'],
//  [T, 'yellow'],
//  [O, 'blue'],
//  [L, '#b938ff'],
//  [I, 'cyan'],
//  [J, 'orange']
// ]

// //create a new instance of Tetromino
// function randomPiece() {
//  let r = Math.floor(Math.random() * pieces.length);
//  return new Tetromino(pieces[r][0], pieces[r][1]);
// }
// let p = randomPiece();

// //draw the piece
// function drawPiece(piece) {
//  //loop through the tetromino
//  for(let r = 0; r < piece.length; r++) {
//   for(let c = 0; c < piece.length; c++) {
//    //if the tetromino index is zero skip it
//    if(!piece[r][c]) continue;
//    //else draw it
//    else draw(p.x + c, p.y + r, p.color);
//   }
//  }
// }

// //undrawdraw the piece
// function undrawPiece(piece) {
//  //loop through the tetromino
//  for(let r = 0; r < piece.length; r++) {
//   for(let c = 0; c < piece.length; c++) {
//    //if the tetromino index is zero skip it
//    if(!piece[r][c]) continue;
//    //else draw it
//    else draw(p.x + c, p.y + r, vacant);
//   }
//  }
// }

// drawPiece(p.activeTetromino);

// //control the piece 
// document.addEventListener('keydown', (event) => {
//  if(event.keyCode === 37) p.moveLeft();

//  else if (event.keyCode === 38) p.rotate();

//     else if (event.keyCode === 39) p.moveRight();

//  else if (event.keyCode === 40) p.moveDown();

// });

// Tetromino.prototype.moveDown = function() {
//  if(!this.collision(0, 1, this.activeTetromino)) {
//   undrawPiece(this.activeTetromino);
//   this.y++;
//   drawPiece(this.activeTetromino);
//  } else {
//   //lock piece and generate a new one
//   this.lock();
//   p = randomPiece();
//  }
// }

// Tetromino.prototype.moveLeft = function() {
//  if(!this.collision(-1, 0, this.activeTetromino)) {
//   undrawPiece(this.activeTetromino);
//   this.x--;
//   drawPiece(this.activeTetromino);
//  }
// }

// Tetromino.prototype.moveRight = function() {
//  if(!this.collision(1, 0, this.activeTetromino)) {
//   undrawPiece(this.activeTetromino);
//   this.x++;
//   drawPiece(this.activeTetromino);
//  }
// }

// Tetromino.prototype.rotate = function() {
//  let nextPattern = this.tetromino[(this.tetrominoN + 1) % 4];
//  if(!this.collision(0, 0, nextPattern)) {
//   if(this.tetromino.length > 1) {
//    undrawPiece(this.activeTetromino);
//    this.tetrominoN = (this.tetrominoN + 1) % 4; // take paranthesis off
//    this.activeTetromino = this.tetromino[this.tetrominoN];
//    drawPiece(this.activeTetromino);
//   }
//  }
// }

// //create a function to check for collisions
// Tetromino.prototype.collision = function(x, y, piece) {
//  for(let r = 0; r < piece.length; r++) {
//   for(let c = 0; c < piece.length; c++) {
//    //skip index if it is 0
//    if(!piece[r][c]) continue;
//    //create vars for the future piece position
//    let newX = this.x + c + x;
//    let newY = this.y + r + y;
//    //see if new position collides with border
//    if(newX < 0 || newX >= col || newY >= row) return true;
//    //see if there's a locked piece on the board
//    if(board[newY][newX] !== vacant) return true;
//   }
//  }
//  return false;
// }

// Tetromino.prototype.lock = function() {
//  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 piece reaches the top its gameover
//    if(this.y + r < 0) {
//     gameover = true;
//     alert('Game Over!');
//    }
//    //lock the piece by updating the board
//    board[this.y + r][this.x + c] = this.color;
//   }
//  }
// }

// let dropStart = Date.now();
// //drop the piece every 1s
// function drop() {
//  let now = Date.now();
//  let delta = now - dropStart;
//  //if delta is greater than 1s drop the piece
//  if(delta > 800) {
//   p.moveDown();
//   dropStart = Date.now();
//  }
//  requestAnimationFrame(drop);
// }

// drop();

//declare globals
const col = 10;
const row = 20;
const sq = 40;
const vacant = 'black';
const cvs = document.querySelector('#canvas');
const ctx = cvs.getContext('2d');
let gameOver = false;

//create and draw 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(c, r, board[r][c]);
 }
}

//create a blueprint function to draw to the board
function draw(x, y, color) {
 //set the drawing specifications
 ctx.fillStyle = color;
 ctx.fillRect(x * sq, y * sq, sq, sq);
 ctx.strokeStyle = 'white';
 ctx.strokeRect(x * sq, y * sq, sq, sq);
}

//create a blueprint object for the tetrominos
function Piece(tetromino, color) {
 //create the properties
 this.tetromino = tetromino;
 this.color = color;
 this.tetrominoN = 0;
 this.activeTetromino = this.tetromino[this.tetrominoN];
 this.x = 0;
 this.y = -1;
 if (this.tetromino === pieces[5][0]) this.y = -2;
}

//create an array to hold all of the tetrominos
const pieces = [
 [Z, 'red'],
 [S, 'limegreen'],
 [T, 'yellow'],
 [O, 'blue'],
 [L, '#b938ff'],
 [I, 'cyan'],
 [J, 'orange']
]

function randomPiece() {
 let r = Math.floor(Math.random()*pieces.length);
 return new Piece(pieces[r][0], pieces[r][1]);
}

//grab a piece
let p = randomPiece();

//draw a piece to the board
// drawPiece(p.activeTetromino, p.color);

//create a blueprint function to draw tetrominos to the board
function drawPiece(piece, color) {
 for(let r = 0; r < piece.length; r++) {
  for(let c = 0; c < piece.length; c++) {
   if (!piece[r][c]) continue;
   draw(c + p.x, r + p.y, color);
  }
 }
}


//control the piece
document.addEventListener('keydown', (e) => {
 //check user's input
 if(e.keyCode === 37) p.moveLeft();
 else if(e.keyCode === 38) p.rotate();
 else if(e.keyCode === 39) p.moveRight();
 else if (e.keyCode === 40) p.moveDown();
});

Piece.prototype.moveDown = function() {
 if(!this.collision(0, 1, this.activeTetromino)) {
  drawPiece(this.activeTetromino, vacant);
  this.y++;
  drawPiece(this.activeTetromino, this.color);  
 } else {
  this.lockPiece(this.activeTetromino);
  checkForPoints();
  p = randomPiece();
 }
}

Piece.prototype.moveLeft = function() {
 if(!this.collision(-1, 0, this.activeTetromino)) {
  drawPiece(this.activeTetromino, vacant);
  this.x--;
  drawPiece(this.activeTetromino, this.color);
 }
}

Piece.prototype.moveRight = function() {
 if(!this.collision(1, 0, this.activeTetromino)) {
  drawPiece(this.activeTetromino, vacant);
  this.x++;
  drawPiece(this.activeTetromino, this.color);
 }
}

Piece.prototype.rotate = function() {
 let nextPattern = this.tetromino[(this.tetrominoN + 1) % 4];

 let kick = 0;
 if (this.collision(0, 0, nextPattern)) {
  if(this.x < col/2) {
   if(this.x === -2) {
    kick = 2;
   } else {
    kick = 1; //kick from right
   }
  } 
  if(this.x > col/2) {
   if(this.tetromino === pieces[5][0]) {
    kick = -2;
   } else {
    kick = -1; //kick from left
   }
  } 
 }

 if(!this.collision(kick, 0, nextPattern)) {
  drawPiece(this.activeTetromino, vacant);
  this.x += kick;
  this.tetrominoN = (this.tetrominoN + 1) % 4;
  this.activeTetromino = this.tetromino[this.tetrominoN];
  drawPiece(this.activeTetromino, this.color);
 } 
}

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(board[newY][newX] !== vacant) return true;
  }
 }
 return false;
}

Piece.prototype.lockPiece = function(piece) {
 for (let r = 0; r < piece.length; r++) {
  for(let c = 0; c < piece.length; c++) {
   if(!piece[r][c]) continue;
   if(this.y + r === 1) alert('yo');
   if(this.y + r <= 0) {
    alert('Game Over');
    gameOver = true;
    break;
   }

   board[this.y + r][this.x + c] = this.color;
  }
 }
}

function checkForPoints() {
 for(let r = 0; r < row; r++) {
  if(board[r].every(squareCheck)) {
   alert('yo');
   board[r].every(clearSquare);
  }
 }

 function squareCheck(sq) {
  if(sq !== vacant) return true;
  else return false;
 }

 function clearSquare(sq) {
  sq = vacant;
 }
}

//start a time to set as a refrence for the dropstart
let dropStart = Date.now();
//create a blueprint function to drop the piece
function drop() {
 //grab the current time
 let now = Date.now();
 //create a var to hold the difference of the current time
 let delta = now - dropStart; //------Why can't these be switched------
 if(delta > 800) {
  dropStart = Date.now();
  p.moveDown();
  //------put request animation here------
 }

 if (!gameOver) requestAnimationFrame(drop);
}

drop();
<!-- <!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Tetris</title>
 <link href="https://fonts.googleapis.com/css?family=Orbitron&display=swap" rel="stylesheet">
</head>
<style>
 body {
  background-color: #595959;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  overflow-y: hidden;
 }
 canvas {
  outline: 1px solid white;
 }
 .canvas-wrap {
  padding-left: 50px;
  padding-top: 50px;
  position: relative;
 }
 .num-top, .num-bottom {
  position: absolute;
  top: -1px;
  left: 0;
 }

 .num-top {
  width: 100%;
  height: 50px;
 }

 .num-bottom {
  height: 100%;
  width: 50px;
 }
 .nb {
  font-family: 'Orbitron';
  color: white;
 }
 .num-wrap-t {
  display: flex;
  justify-content: space-around;
  margin-left: 50px;
  width: 400px;
 }
 .num-wrap-b {
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  margin-top: 50px;
  height: 800px;
 }
 .num-wrap-b .nb {
  text-align: right;
  margin-right: 3px;
 }

 .num-wrap-t .nb {
  position: relative;
  top: 31px;
 }
</style>
<body>
 <div class="canvas-wrap">
  <div class="num-top">
   <div class="num-wrap-t">
    <div class="nb">0</div>
    <div class="nb">1</div>
    <div class="nb">2</div>
    <div class="nb">3</div>
    <div class="nb">4</div>
    <div class="nb">5</div>
    <div class="nb">6</div>
    <div class="nb">7</div>
    <div class="nb">8</div>
    <div class="nb">9</div>
   </div>
  </div>
  <canvas id="canvas" width="400" height="800"></canvas>
  <div class="num-bottom">
   <div class="num-wrap-b">
    <div class="nb">0</div>
    <div class="nb">1</div>
    <div class="nb">2</div>
    <div class="nb">3</div>
    <div class="nb">4</div>
    <div class="nb">5</div>
    <div class="nb">6</div>
    <div class="nb">7</div>
    <div class="nb">8</div>
    <div class="nb">9</div>
    <div class="nb">10</div>
    <div class="nb">11</div>
    <div class="nb">12</div>
    <div class="nb">13</div>
    <div class="nb">14</div>
    <div class="nb">15</div>
    <div class="nb">16</div>
    <div class="nb">17</div>
    <div class="nb">18</div>
    <div class="nb">19</div>
   </div>
  </div>
    </div>  
 <script src="tetrominos.js"></script>
 <script src="tetris.js"></script>
</body>
</html> -->
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Tetris</title>
 <link href="https://fonts.googleapis.com/css?family=Orbitron&display=swap" rel="stylesheet">
</head>
<style>
 body, html {
  padding: 0;
  margin: 0;
 }

 body {
  background-color: #595959;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
 }

 canvas {
  outline: 1px solid white;
 }
 .canvas-wrap {
  padding-left: 50px;
  padding-top: 50px;
  position: relative;
 }
 .num-top, .num-bottom {
  position: absolute;
  top: -1px;
  left: 0;
 }

 .num-top {
  width: 100%;
  height: 50px;
 }

 .num-bottom {
  height: 100%;
  width: 50px;
 }
 .nb {
  font-family: 'Orbitron';
  color: white;
 }
 .num-wrap-t {
  display: flex;
  justify-content: space-around;
  margin-left: 50px;
  width: 400px;
 }
 .num-wrap-b {
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  margin-top: 50px;
  height: 800px;
 }
 .num-wrap-b .nb {
  text-align: right;
  margin-right: 3px;
 }

 .num-wrap-t .nb {
  position: relative;
  top: 31px;
 }
</style>
<body>
 <div class="canvas-wrap">
  <div class="num-top">
   <div class="num-wrap-t">
    <div class="nb">0</div>
    <div class="nb">1</div>
    <div class="nb">2</div>
    <div class="nb">3</div>
    <div class="nb">4</div>
    <div class="nb">5</div>
    <div class="nb">6</div>
    <div class="nb">7</div>
    <div class="nb">8</div>
    <div class="nb">9</div>
   </div>
  </div>
  <canvas id="canvas" width="400" height="800"></canvas>
  <div class="num-bottom">
   <div class="num-wrap-b">
    <div class="nb">0</div>
    <div class="nb">1</div>
    <div class="nb">2</div>
    <div class="nb">3</div>
    <div class="nb">4</div>
    <div class="nb">5</div>
    <div class="nb">6</div>
    <div class="nb">7</div>
    <div class="nb">8</div>
    <div class="nb">9</div>
    <div class="nb">10</div>
    <div class="nb">11</div>
    <div class="nb">12</div>
    <div class="nb">13</div>
    <div class="nb">14</div>
    <div class="nb">15</div>
    <div class="nb">16</div>
    <div class="nb">17</div>
    <div class="nb">18</div>
    <div class="nb">19</div>
   </div>
  </div>
 </div>
 <script src="tetrominos.js"></script>  
 <script src="tetris.js"></script>
</body>
</html>
Alfred
  • 245
  • 1
  • 2
  • 13
  • 3
    Please consider reading the documentation for [`Array.every()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every); it is used for validating that all items in an array satisfy a condition. If the first item doesn't `return true`, then it will exit immediately. Manipulating every item in an array should be done with [`Array.map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) instead, which won't manipulate the array on which it's called, but will return a new array with the desired modifications. – Tyler Roper Aug 21 '19 at 15:00
  • 2
    `function clearSquare(sq) { sq = vacant; }` is a function that redefines its first argument and returns `undefined`, causing `every` to stop early. Reassigning a variable or an argument doesn’t touch your array. This wouldn’t even work with `forEach`. – Sebastian Simon Aug 21 '19 at 15:05

2 Answers2

4

JavaScript has call by sharing so if you do sq = that will only affect the local sq variable, not the value in the array. You have to directly assign the new value to the array, e.g. board[r][i] = vacant; ...

Additionally .every is the wrong tool then, use .forEach or for(let i = 0; i < board[r].length; i++) , and then reassign the values.

Or could just create a new array and reassign it:

  board[r] = new Array(board[r].length).fill(vacant);

that could also be done with .map.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • Nice use of fill – Sam L Aug 21 '19 at 15:07
  • Genuine curiosity, not a critique: Any reason for `new Array().fill()` over `board[r].map(i => vacant)`? **EDIT:** Just saw your edit (as if on cue!). – Tyler Roper Aug 21 '19 at 15:11
  • 1
    @tylerRoper I imagine that it could be faster as the lambda doesnt have to be invoked with each original value of `board[r]`. – Jonas Wilms Aug 21 '19 at 15:12
  • @tylerRoper a perf test will probably proove me wrong though, and if we take GCing into account, then reassigning is probably better. – Jonas Wilms Aug 21 '19 at 15:14
-2

From documentation:

The every() method tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value.

The passed function is a TEST function, I imagine that behind the scenes it creates a copy of your array element and activates the function on it.

Gibor
  • 1,695
  • 6
  • 20
  • 1
    @JonasWilms From reading the [docs at MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) it does. Would be interesting to know why it doesn't? – phuzi Aug 21 '19 at 15:04
  • 2
    @phuzi The part about deep copies is wrong: there is no deep copy created. – Sebastian Simon Aug 21 '19 at 15:09
  • @SebastianSimon if it was shallow copy, and the array had objects, it would change the array elements no? – Gibor Aug 21 '19 at 15:10
  • @JonasWilms Fair enough, thought you were commenting on the quote from MDN. – phuzi Aug 21 '19 at 15:11
  • @Gibor it passes a reference to the array element. Re-assigning the reference only updates the reference not the element in the array. – phuzi Aug 21 '19 at 15:12
  • @Gibor There is just no copy created. `(sq) => sq = vacant` doesn’t act on any array element at all. This works like `let sq = board[r][i]; sq = vacant;`. It does not create any copy; it does not update the array element; it only updates the variable. – Sebastian Simon Aug 21 '19 at 15:13
  • @phuzi let me understand- if I have this: `let obj = { id: 1 };`, and I do this: `let obj2 = obj; obj2.id =2;` ill get 2 in obj.id as well... how is that different? – Gibor Aug 21 '19 at 15:18
  • Cause objects are *copied by reference*. `obj` and `obj2` both contain a reference to the same object. If you mutate the object you can see the change through both references. – Jonas Wilms Aug 21 '19 at 15:21
  • so, ill go back to my original claim: if my array contains **objects**, not values, and I use array.every- it would mutate the original objects as well if it does not deep copy them.... unless im missing something @JonasWilms – Gibor Aug 21 '19 at 15:22
  • If you would mutate the objects then yes, not if you reassign a local variable. I highly recommend reading about pointers. – Jonas Wilms Aug 21 '19 at 15:25
  • @Gibor _“If I have this: `let obj = { id: 1 };`, and I do this: `let obj2 = obj; obj2.id =2;` I’ll get `2` in `obj.id` as well… how is that different?”_ — that is _very_ different. You’re doing `let obj = {}; let obj2 = something;`; the OP is doing `let obj = {}; obj = something;`. You’re creating two variables; the OP is reassigning one variable. – Sebastian Simon Aug 21 '19 at 15:29
  • 2
    @Gibor The equivalent scenario to your above `obj` and `obj2` would be doing `let obj = { id: 1 }; let obj2 = obj; obj2 = { id: 500 }`. You briefly set `obj2` as a reference to `obj`, but then changed it to something else entirely. In OP's code, `sq` is `obj2`. – Tyler Roper Aug 21 '19 at 15:29