I am coding a Connect4 game in JavaScript and I am having issues trying to run all the win conditions at the same time. I have seen several questions already asked here but I could not find the answer to my question. I have also seen different ways for checking the winning combinations but I wanted to code mine from scracth.
As you can see in the code below, I have already coded the winning conditions for the vertical and horizontal lines. If you run these separately, they work as intended. However, when you run them at the same time, some rows and some columns do not work and never get checked.
For example, if you fill the rows from the bottom and try to make a line at the top last row, the system will not detect your horizontal win as it previously retrieves the following error:
main.js:466 Uncaught TypeError: Cannot read properties of undefined (reading 'classList')
at checkVertical (main.js:466:25)
at fillSlot (main.js:103:3)
at fillFromBottom (main.js:66:11)
at HTMLDivElement.<anonymous> (main.js:557:5)
What I am trying to achieve here is this: when the winning combination is not in the vertical column, jump to do the horizontal check. However, with my current code it gets stuck in the vertical check.
To prevent this, I have tried adding an extra validation in my winning conditions:
let selected1 = 0;
for (let i = 0; i < row1.length; i++) {
if (row1[i].classList.contains("taken")) {
selected1++;
}
}
if (selected1 >= 4) {}
With this I have managed to get less times the error cited above but still, the code is not working properly.
Why is this error appearing and why is blocking my code to properly check for winning combinations?
EDIT: I have found the solution by myself. I think I was going out borders with my loops and that was causing the error message.
//DOM Elements and items
const holes = document.getElementsByClassName("hole");
let currentPlayer = 1;
const col1 = document.getElementsByClassName("col1");
const col2 = document.getElementsByClassName("col2");
const col3 = document.getElementsByClassName("col3");
const col4 = document.getElementsByClassName("col4");
const col5 = document.getElementsByClassName("col5");
const col6 = document.getElementsByClassName("col6");
const col7 = document.getElementsByClassName("col7");
const row1 = document.getElementsByClassName("row1");
const row2 = document.getElementsByClassName("row2");
const row3 = document.getElementsByClassName("row3");
const row4 = document.getElementsByClassName("row4");
const row5 = document.getElementsByClassName("row5");
const row6 = document.getElementsByClassName("row6");
let rows = 6;
let columns = 7;
let verticalWin = false;
let horizontalWin = false;
let diagonalWin = false;
//Fill the gaps from bottom sequence
const fillFromBottom = (hole) => {
let currentColumn = hole.classList.value[8];
switch (currentColumn) {
case "1":
for (let i = col1.length - 1; i >= 0; i--) {
if (!col1[i].classList.contains("taken")) {
fillSlot(col1[i]);
return;
}
}
break;
case "2":
for (let i = col2.length - 1; i >= 0; i--) {
if (!col2[i].classList.contains("taken")) {
fillSlot(col2[i]);
return;
}
}
break;
case "3":
for (let i = col3.length - 1; i >= 0; i--) {
if (!col3[i].classList.contains("taken")) {
fillSlot(col3[i]);
return;
}
}
break;
case "4":
for (let i = col4.length - 1; i >= 0; i--) {
if (!col4[i].classList.contains("taken")) {
fillSlot(col4[i]);
return;
}
}
break;
case "5":
for (let i = col5.length - 1; i >= 0; i--) {
if (!col5[i].classList.contains("taken")) {
fillSlot(col5[i]);
return;
}
}
break;
case "6":
for (let i = col6.length - 1; i >= 0; i--) {
if (!col6[i].classList.contains("taken")) {
fillSlot(col6[i]);
return;
}
}
break;
case "7":
for (let i = col7.length - 1; i >= 0; i--) {
if (!col7[i].classList.contains("taken")) {
fillSlot(col7[i]);
return;
}
}
break;
}
};
const fillSlot = (hole) => {
if (currentPlayer === 1) {
hole.classList.add("player-one");
hole.classList.add("taken");
currentPlayer = 2;
} else if (currentPlayer === 2) {
hole.classList.add("player-two");
hole.classList.add("taken");
currentPlayer = 1;
}
checkVertical(hole);
checkHorizontal(hole);
checkDiagonal();
};
//Winning conditions
/*
const checkVertical = () => {
for (let i = 0; i < holes.length; i++) {
if (
holes[i].classList.contains("player-one") &&
holes[i + 7].classList.contains("player-one") &&
holes[i + 14].classList.contains("player-one") &&
holes[i + 21].classList.contains("player-one")
) {
setWinner();
return;
} else if (
holes[i].classList.contains("player-two") &&
holes[i + 7].classList.contains("player-two") &&
holes[i + 14].classList.contains("player-two") &&
holes[i + 21].classList.contains("player-two")
) {
setWinner();
return;
}
}
};
*/
const checkHorizontal = (hole) => {
let currentRow = hole.classList.value[13];
switch (currentRow) {
case "1":
let selected1 = 0;
for (let i = 0; i < row1.length; i++) {
if (row1[i].classList.contains("taken")) {
selected1++;
}
}
if (selected1 >= 4) {
for (let i = 0; i <= 3; i++) {
if (
row1[i].classList.contains("player-one") &&
row1[i + 1].classList.contains("player-one") &&
row1[i + 2].classList.contains("player-one") &&
row1[i + 3].classList.contains("player-one")
) {
horizontalWin = true;
setWinner();
return;
} else if (
row1[i].classList.contains("player-two") &&
row1[i + 1].classList.contains("player-two") &&
row1[i + 2].classList.contains("player-two") &&
row1[i + 3].classList.contains("player-two")
) {
horizontalWin = true;
setWinner();
return;
}
}
}
break;
case "2":
let selected2 = 0;
for (let i = 0; i < row2.length; i++) {
if (row2[i].classList.contains("taken")) {
selected2++;
}
}
if (selected2 >= 4) {
for (let i = 0; i <= 3; i++) {
if (
row2[i].classList.contains("player-one") &&
row2[i + 1].classList.contains("player-one") &&
row2[i + 2].classList.contains("player-one") &&
row2[i + 3].classList.contains("player-one")
) {
horizontalWin = true;
setWinner();
return;
} else if (
row2[i].classList.contains("player-two") &&
row2[i + 1].classList.contains("player-two") &&
row2[i + 2].classList.contains("player-two") &&
row2[i + 3].classList.contains("player-two")
) {
horizontalWin = true;
setWinner();
return;
}
}
}
break;
case "3":
let selected3 = 0;
for (let i = 0; i < row3.length; i++) {
if (row3[i].classList.contains("taken")) {
selected3++;
}
}
if (selected3 >= 4) {
for (let i = 0; i <= 3; i++) {
if (
row3[i].classList.contains("player-one") &&
row3[i + 1].classList.contains("player-one") &&
row3[i + 2].classList.contains("player-one") &&
row3[i + 3].classList.contains("player-one")
) {
horizontalWin = true;
setWinner();
return;
} else if (
row3[i].classList.contains("player-two") &&
row3[i + 1].classList.contains("player-two") &&
row3[i + 2].classList.contains("player-two") &&
row3[i + 3].classList.contains("player-two")
) {
horizontalWin = true;
setWinner();
return;
}
}
}
break;
case "4":
let selected4 = 0;
for (let i = 0; i < row4.length; i++) {
if (row4[i].classList.contains("taken")) {
selected4++;
}
}
if (selected4 >= 4) {
for (let i = 0; i <= 3; i++) {
if (
row4[i].classList.contains("player-one") &&
row4[i + 1].classList.contains("player-one") &&
row4[i + 2].classList.contains("player-one") &&
row4[i + 3].classList.contains("player-one")
) {
horizontalWin = true;
setWinner();
return;
} else if (
row4[i].classList.contains("player-two") &&
row4[i + 1].classList.contains("player-two") &&
row4[i + 2].classList.contains("player-two") &&
row4[i + 3].classList.contains("player-two")
) {
horizontalWin = true;
setWinner();
return;
}
}
}
break;
case "5":
let selected5 = 0;
for (let i = 0; i < row5.length; i++) {
if (row5[i].classList.contains("taken")) {
selected5++;
}
}
if (selected5 >= 4) {
for (let i = 0; i <= 3; i++) {
if (
row5[i].classList.contains("player-one") &&
row5[i + 1].classList.contains("player-one") &&
row5[i + 2].classList.contains("player-one") &&
row5[i + 3].classList.contains("player-one")
) {
horizontalWin = true;
setWinner();
return;
} else if (
row5[i].classList.contains("player-two") &&
row5[i + 1].classList.contains("player-two") &&
row5[i + 2].classList.contains("player-two") &&
row5[i + 3].classList.contains("player-two")
) {
horizontalWin = true;
setWinner();
return;
}
}
}
break;
case "6":
let selected6 = 0;
for (let i = 0; i < row6.length; i++) {
if (row6[i].classList.contains("taken")) {
selected6++;
}
}
if (selected6 >= 4) {
for (let i = 0; i <= 3; i++) {
if (
row6[i].classList.contains("player-one") &&
row6[i + 1].classList.contains("player-one") &&
row6[i + 2].classList.contains("player-one") &&
row6[i + 3].classList.contains("player-one")
) {
horizontalWin = true;
setWinner();
return;
} else if (
row6[i].classList.contains("player-two") &&
row6[i + 1].classList.contains("player-two") &&
row6[i + 2].classList.contains("player-two") &&
row6[i + 3].classList.contains("player-two")
) {
horizontalWin = true;
setWinner();
return;
}
}
}
break;
}
};
const checkDiagonal = () => {};
const checkVertical = (hole) => {
let currentColumn = hole.classList.value[8];
switch (currentColumn) {
case "1":
let selected1 = 0;
for (let i = 0; i < col1.length; i++) {
if (col1[i].classList.contains("taken")) {
selected1++;
}
}
if (selected1 >= 4) {
for (let i = 0; i <= 2; i++) {
if (
col1[i].classList.contains("player-one") &&
col1[i + 1].classList.contains("player-one") &&
col1[i + 2].classList.contains("player-one") &&
col1[i + 3].classList.contains("player-one")
) {
verticalWin = true;
setWinner();
return;
} else if (
col1[i].classList.contains("player-two") &&
col1[i + 1].classList.contains("player-two") &&
col1[i + 2].classList.contains("player-two") &&
col1[i + 3].classList.contains("player-two")
) {
verticalWin = true;
setWinner();
return;
}
}
}
break;
case "2":
let selected2 = 0;
for (let i = 0; i < col2.length - 1; i++) {
if (col2[i].classList.contains("taken")) {
selected2++;
}
}
if (selected2 >= 4) {
for (let i = 0; i <= 2; i++) {
if (
col2[i].classList.contains("player-one") &&
col2[i + 1].classList.contains("player-one") &&
col2[i + 2].classList.contains("player-one") &&
col2[i + 3].classList.contains("player-one")
) {
verticalWin = true;
setWinner();
return;
} else if (
col2[i].classList.contains("player-two") &&
col2[i + 1].classList.contains("player-two") &&
col2[i + 2].classList.contains("player-two") &&
col2[i + 3].classList.contains("player-two")
) {
verticalWin = true;
setWinner();
return;
}
}
}
break;
case "3":
let selected3 = 0;
for (let i = 0; i < col3.length; i++) {
if (col3[i].classList.contains("taken")) {
selected3++;
}
}
if (selected3 >= 4) {
for (let i = 0; i <= 2; i++) {
if (
col3[i].classList.contains("player-one") &&
col3[i + 1].classList.contains("player-one") &&
col3[i + 2].classList.contains("player-one") &&
col3[i + 3].classList.contains("player-one")
) {
verticalWin = true;
setWinner();
return;
} else if (
col3[i].classList.contains("player-two") &&
col3[i + 1].classList.contains("player-two") &&
col3[i + 2].classList.contains("player-two") &&
col3[i + 3].classList.contains("player-two")
) {
verticalWin = true;
setWinner();
return;
}
}
}
break;
case "4":
let selected4 = 0;
for (let i = 0; i < col4.length; i++) {
if (col4[i].classList.contains("taken")) {
selected4++;
}
}
if (selected4 >= 4) {
for (let i = 0; i <= 2; i++) {
if (
col4[i].classList.contains("player-one") &&
col4[i + 1].classList.contains("player-one") &&
col4[i + 2].classList.contains("player-one") &&
col4[i + 3].classList.contains("player-one")
) {
verticalWin = true;
setWinner();
return;
} else if (
col4[i].classList.contains("player-two") &&
col4[i + 1].classList.contains("player-two") &&
col4[i + 2].classList.contains("player-two") &&
col4[i + 3].classList.contains("player-two")
) {
verticalWin = true;
setWinner();
return;
}
}
}
break;
case "5":
let selected5 = 0;
for (let i = 0; i < col5.length; i++) {
if (col5[i].classList.contains("taken")) {
selected5++;
}
}
if (selected5 >= 4) {
for (let i = 0; i <= 2; i++) {
if (
col5[i].classList.contains("player-one") &&
col5[i + 1].classList.contains("player-one") &&
col5[i + 2].classList.contains("player-one") &&
col5[i + 3].classList.contains("player-one")
) {
verticalWin = true;
setWinner();
return;
} else if (
col5[i].classList.contains("player-two") &&
col5[i + 1].classList.contains("player-two") &&
col5[i + 2].classList.contains("player-two") &&
col5[i + 3].classList.contains("player-two")
) {
verticalWin = true;
setWinner();
return;
}
}
}
break;
case "6":
let selected6 = 0;
for (let i = 0; i < col6.length; i++) {
if (col6[i].classList.contains("taken")) {
selected6++;
}
}
if (selected6 >= 4) {
for (let i = 0; i <= 2; i++) {
if (
col6[i].classList.contains("player-one") &&
col6[i + 1].classList.contains("player-one") &&
col6[i + 2].classList.contains("player-one") &&
col6[i + 3].classList.contains("player-one")
) {
verticalWin = true;
setWinner();
return;
} else if (
col6[i].classList.contains("player-two") &&
col6[i + 1].classList.contains("player-two") &&
col6[i + 2].classList.contains("player-two") &&
col6[i + 3].classList.contains("player-two")
) {
verticalWin = true;
setWinner();
return;
}
}
}
break;
case "7":
let selected7 = 0;
for (let i = 0; i < col7.length; i++) {
if (col7[i].classList.contains("taken")) {
selected7++;
}
}
if (selected7 >= 4) {
for (let i = 0; i <= 2; i++) {
if (
col7[i].classList.contains("player-one") &&
col7[i + 1].classList.contains("player-one") &&
col7[i + 2].classList.contains("player-one") &&
col7[i + 3].classList.contains("player-one")
) {
verticalWin = true;
setWinner();
return;
} else if (
col7[i].classList.contains("player-two") &&
col7[i + 1].classList.contains("player-two") &&
col7[i + 2].classList.contains("player-two") &&
col7[i + 3].classList.contains("player-two")
) {
verticalWin = true;
setWinner();
return;
}
}
}
break;
}
};
const setWinner = () => {
alert("Winner");
if (confirm("Reset") === true) {
resetBoard();
}
};
const resetBoard = () => {
horizontalWin = false;
verticalWin = false;
diagonalWin = false;
for (let hole of holes) {
hole.classList.remove("player-one", "player-two", "taken");
}
};
//Event listeners
for (let hole of holes) {
hole.addEventListener("click", () => {
fillFromBottom(hole);
});
}
body {
height: 100vh;
width: 100vw;
overflow: hidden;
}
.game-grid {
display: grid;
grid-template-columns: repeat(7, 110px);
grid-template-rows: repeat(6, 110px);
border-radius: 20px;
background-color: blue;
width: 770px;
height: 660px;
margin: 0 auto;
place-items: center;
}
.hole {
border: 5px solid black;
background-color: white;
border-radius: 50%;
width: 12vmin;
height: 12vmin;
}
.player-one {
background-color: red;
border-radius: 50%;
}
.player-two {
background-color: yellow;
border-radius: 50%;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connect 4</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<div class="game-grid">
<div class="hole col1 row1"></div>
<div class="hole col2 row1"></div>
<div class="hole col3 row1"></div>
<div class="hole col4 row1"></div>
<div class="hole col5 row1"></div>
<div class="hole col6 row1"></div>
<div class="hole col7 row1"></div>
<div class="hole col1 row2"></div>
<div class="hole col2 row2"></div>
<div class="hole col3 row2"></div>
<div class="hole col4 row2"></div>
<div class="hole col5 row2"></div>
<div class="hole col6 row2"></div>
<div class="hole col7 row2"></div>
<div class="hole col1 row3"></div>
<div class="hole col2 row3"></div>
<div class="hole col3 row3"></div>
<div class="hole col4 row3"></div>
<div class="hole col5 row3"></div>
<div class="hole col6 row3"></div>
<div class="hole col7 row3"></div>
<div class="hole col1 row4"></div>
<div class="hole col2 row4"></div>
<div class="hole col3 row4"></div>
<div class="hole col4 row4"></div>
<div class="hole col5 row4"></div>
<div class="hole col6 row4"></div>
<div class="hole col7 row4"></div>
<div class="hole col1 row5"></div>
<div class="hole col2 row5"></div>
<div class="hole col3 row5"></div>
<div class="hole col4 row5"></div>
<div class="hole col5 row5"></div>
<div class="hole col6 row5"></div>
<div class="hole col7 row5"></div>
<div class="hole col1 row6"></div>
<div class="hole col2 row6"></div>
<div class="hole col3 row6"></div>
<div class="hole col4 row6"></div>
<div class="hole col5 row6"></div>
<div class="hole col6 row6"></div>
<div class="hole col7 row6"></div>
</div>
</body>
<script type="text/javascript" src="main.js"></script>
</html>