Using arr.every() on [...HTMLCollection] doesn't return true element change

96 views Asked by At

I'm trying to build a Tic Tac Toe game so I tried to collect the playbox areas in an array by first using getElementsByClassName() before using spread operator to convert the collection into an array. But when I try to use the arr.every() method to check if all boxes are played, I still get false even after all the boxes are filled

This is my HTML code

<div class="board">
  <div class="board-sections">
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
   </div>
</div>

First I collected all play boxes.

let boxes = document.getElementsByClassName("play-box");
let numOfBoxes = boxes.length;

Then I converted the collection to array and applied the condition to check if all boxes are not empty

let boxesArr = [...boxes];
let checkBoxes = boxesArr.every(function(box) {
  return box.innerHTML !== "";
});

Then I created a function to check if all boxes are not empty

function checkBoard() {
  if (checkBoxes) {
    console.log("All Filled");
  }
}

Then I created a funtion to add text to each box and check if all boxes are filled by calling the checkBoard() function onclick.

for (var i = 0; i < numOfBoxes; i++) {
  boxes[i].addEventListener("click", addChip);
}

The addChip() function adds a text to each box as it's clicked, then check if all boxes are filled

function addChip() {
  this.innerHTML = "X";
  checkBoard();
}

The boxes were filled as they're clicked, and I expected the checkBoard() function to return true after all boxes are filled, but after all boxes have filled the checkBoard() function still returned false.

Here's what the full code looks like

let boxes = document.getElementsByClassName("play-box");
let numOfBoxes = boxes.length;

let boxesArr = [...boxes];
let checkBoxes = boxesArr.every(function(box) {
  return box.innerHTML !== "";
});

function checkBoard() {
  if (checkBoxes) {
    console.log("All Filled");
  }
}

for (var i = 0; i < numOfBoxes; i++) {
  boxes[i].addEventListener("click", addChip);
}

function addChip() {
  this.innerHTML = "X";
  checkBoard();
}
.board {
  text-align: center;
  max-width: 100%;
  width: 500px;
  margin: auto;
}

.board-sections {
  display: grid;
  grid-template-columns: auto auto auto;
  background: #ccc;
  gap: 5px;
  align-content: center;
}

.play-box {
  height: 100px;
  background: transparent;
  border: 1px solid #000;
  min-width: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
}
<div class="board">
  <div class="board-sections">
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
    <div class="play-box"></div>
  </div>
</div>

2

There are 2 answers

9
Carsten Massmann On BEST ANSWER

It is difficult to answer your question without having the actual code. Below I assembled a possible scenario that actually works. Maybe it is helpful to you?

const pb=[...document.querySelectorAll(".play-box")];
document.querySelectorAll("button").forEach(b=>{
 b.addEventListener("click",ev=>{
  if(b.textContent=="check")
   console.log(pb.every(p=>p.textContent!=""))
  else
   pb.forEach(p=>p.textContent="X");
 })
})
.play-box {display:inline-block; width:40px;height:40px; border:1px solid grey}
<div class="board">
  <div class="board-sections">
<div class="play-box"></div>
<div class="play-box"></div>
<div class="play-box"></div><br>
<div class="play-box"></div>
<div class="play-box"></div>
<div class="play-box"></div><br>
<div class="play-box"></div>
<div class="play-box"></div>
<div class="play-box"></div>
   </div>
</div>
<button>check</button> <button>fill</button>

The point I was trying to make is that the .textContent of the .play-box elements must be checked after they have been filled. OP's original script did the check only once, directly after the page was loaded.

Explanation:
The key part in my snippet above is the expression

pb.every(p=>p.textContent!="")

It returns true if all boxes are filled in some way. In your tic-tac-toe game this will need to be executed after each setting of an "X" or an "O".

Your checkBoard function should look like this:

function checkBoard() {
  if (boxesArr.every(p=>p.textContent!="")) {
    console.log("All Filled");
  }
}
3
Roko C. Buljan On

Your checkBoxes holds a boolean value, not a function:

// This is wrong
let checkBoxes = boxesArr.every(function(box) {
  return box.innerHTML !== "";
});

it will only return the value as the state before the game even started. You cannot reuse it like if (checkBoxes) during the game and expect an updated value.
Instead, return a function that returns a boolean by using it like if (checkBoxes()) {:

const boxes = document.querySelectorAll(".board-box");
const numOfBoxes = boxes.length;

const checkBoxes = () => [...boxes].every((box) => box.textContent.trim() !== "");

function checkBoard() {
  if (checkBoxes()) {
    console.log("All Filled");
  }
}

boxes.forEach((box) => {
  box.addEventListener("click", addChip);
});


function addChip() {
  this.textContent = "X";
  checkBoard();
}
.board {
  text-align: center;
  max-width: 100%;
  width: 500px;
  margin: auto;
}

.board-sections {
  display: grid;
  grid-template-columns: auto auto auto;
  background: #ccc;
  gap: 5px;
  align-content: center;
}

.board-box {
  height: 100px;
  background: transparent;
  border: 1px solid #000;
  min-width: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
}
<div class="board">
  <div class="board-sections">
    <div class="board-box"></div>
    <div class="board-box"></div>
    <div class="board-box"></div>
    <div class="board-box"></div>
    <div class="board-box"></div>
    <div class="board-box"></div>
    <div class="board-box"></div>
    <div class="board-box"></div>
    <div class="board-box"></div>
  </div>
</div>

Also, use textContent to return text instead of innerHTML


Additionally you can simplify the code to:

const boxes = document.querySelectorAll(".board-box");
const tot = boxes.length;
let turn = 0;

const isBoxEmpty = (box) => box.textContent.trim() === "";

const checkBoard = () => {
  if (![...boxes].some(isBoxEmpty)) {
    console.log("All Filled");
  }
};

const addChip = (ev) => {
  const box = ev.currentTarget;
  if (!isBoxEmpty(box)) return; // Not an empty box. Exit function here.
  
  box.textContent = (turn % 2) ? "O" : "X";
  checkBoard();
  // Increment turn
  turn += 1;
}

boxes.forEach((box) => box.addEventListener("click", addChip));
.board {
  font: 14vmin/1 sans-serif;
  margin: auto;
  display: inline-grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 5px;
}

.board-box {
  height: 20vmin;
  aspect-ratio: 1;
  border: 2px solid #000;
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
}
<div class="board">
  <div class="board-box"></div>
  <div class="board-box"></div>
  <div class="board-box"></div>
  <div class="board-box"></div>
  <div class="board-box"></div>
  <div class="board-box"></div>
  <div class="board-box"></div>
  <div class="board-box"></div>
  <div class="board-box"></div>
</div>