DEV Community

Cover image for Building a connect-four game with JavaScript
Fakorede Damilola
Fakorede Damilola

Posted on

Building a connect-four game with JavaScript

The connect four game can be likened to two players building a wall, each player with a different brick(i.e color). The rule is simple, each person drops a brick one at a time. Like an actually wall, a player must drop the brick on top of another cause a brick cannot stand alone in the air. The game is won when one of the two players can lay up their brick in an horizontal / vertical or diagonal manner made up of four bricks (i.e connect-four).

Here is how you can build your connect four game using HTML, CSS and JavaScript. I will be covering the

basic HTML and CSS

The structure of this project is straightforward. This skeleton(HTML and CSS) will house data from the JS

HTML

<div class="container">
<div class="currentPlayer">The current player is 
<span class="player"></span>
<div class="board"></div>
<div class="restart">
<button class="playAgain">play again</button>
</div>
</div>
Enter fullscreen mode Exit fullscreen mode

From above, we have a span with a class player that will tell us whose turn it is to play, The grid class will house the game board and the restart class with house the button to easily renew the game.

CSS

.board { 
border:1px solid black; 
display:flex; 
flex-wrap:wrap; 
height:276px; 
width:330px; 
background:blue 
} 
.board div { 
height:40px; 
width:40px; 
margin:3px;
border-radius:20px 
}
.square{ 
background:white; 
} 
.taken { 
/*background:red !important */
} 
.player-one { 
background:red;
}
.player-two { 
background:yellow; 
}
.restart{ 
background: red; 
width: 200px; 
height: 200px; 
position: fixed; 
top: 100px; 
left: 100px; 
display: none; 
justify-content: center; 
align-items: center; 
flex-direction: column; 
}
Enter fullscreen mode Exit fullscreen mode

Most of what is going on in the CSS might make little or no sense to you, has we start working with JS, you will begin to understand what is going on.
But from here, we are setting up the board which will house a lot of divs. This are all block elements, but the flex property arranges them horizontally. The flex-wrap moves the div to the next line once it is about to reach the end of the board (remember we gave it a width and height).
The .board div styles each div inside the board. player-one and player-two gives each player a different color of brick. (Note that the commented parts is to help you understand what is going on, and they have no actually effect on the game. Has time goes on, we will comment and uncomment them to show what is actually going on).

variables and data needed in JS

let board=document.querySelector(".board") 
let player=document.querySelector(".player") 
let playAgain=document.querySelector(".playAgain") 
let restart=document.querySelector(".restart") 
let box=0 
let winningArray = [ 
[0, 1, 2, 3], [41, 40, 39, 38],[7, 8, 9, 10], 
[34, 33, 32, 31], [14, 15, 16, 17], [27, 26, 25, 24], 
[21, 22, 23, 24], [20, 19, 18, 17], [28, 29, 30, 31], 
[13, 12, 11, 10], [35, 36, 37, 38], [6, 5, 4, 3], 
[0, 7, 14, 21], [41, 34, 27, 20], [1, 8, 15, 22], 
[40, 33, 26, 19], [2, 9, 16, 23], [39, 32, 25, 18], 
[3, 10, 17, 24], [38, 31, 24, 17], [4, 11, 18, 25], 
[37, 30, 23, 16], [5, 12, 19, 26], [36, 29, 22, 15], 
[6, 13, 20, 27], [35, 28, 21, 14], [0, 8, 16, 24], 
[41, 33, 25, 17], [7, 15, 23, 31], [34, 26, 18, 10], 
[14, 22, 30, 38], [27, 19, 11, 3], [35, 29, 23, 17], 
[6, 12, 18, 24], [28, 22, 16, 10], [13, 19, 25, 31], 
[21, 15, 9, 3], [20, 26, 32, 38], [36, 30, 24, 18], 
[5, 11, 17, 23], [37, 31, 25, 19], [4, 10, 16, 22], 
[2, 10, 18, 26], [39, 31, 23, 15], [1, 9, 17, 25], 
[40, 32, 24, 16], [9, 7, 25, 33], [8, 16, 24, 32], 
[11, 7, 23, 29], [12, 18, 24, 30], [1, 2, 3, 4], 
[5, 4, 3, 2], [8, 9, 10, 11], [12, 11, 10, 9],
[15, 16, 17, 18], [19, 18, 17, 16], [22, 23, 24, 25], 
[26, 25, 24, 23], [29, 30, 31, 32], [33, 32, 31, 30], 
[36, 37, 38, 39], [40, 39, 38, 37], [7, 14, 21, 28], 
[8, 15, 22, 29], [9, 16, 23, 30], [10, 17, 24, 31], 
[11, 18, 25, 32], [12, 19, 26, 33], [13, 20, 27, 34] 
]; 
let currentPlayer=1 
Enter fullscreen mode Exit fullscreen mode

From above, we get the board, the player, and the restart button from the HTML5 via querySelector. The box variable will be used to check if the board is filled and the winningArray is just an array that houses the different possible way a player can win the game, currentPlayer changes whose turn it is to play.

board and DOM load in JS

Like my earlier article on memory game, most of the heavy lifting will be done via JS.

document.addEventListener("DOMContentLoaded", loadDOM)
Enter fullscreen mode Exit fullscreen mode

On DOM load, we will call a function loadDOM which will set the game and everything

//load dom function

function loadDOM(){ 
createBoard() 
player.innerHTML=currentPlayer 
let squares =document.querySelectorAll(".board div") 
Array.from(squares).forEach(square=>{ 
square.addEventListener("click",clickBox)
})
playAgain.addEventListener("click",reset) 
}
Enter fullscreen mode Exit fullscreen mode

The first thing here is to call createBoard which will create the gameBoard, after which we will set the player innerHTML to let the user know whose turn it is to play. Finally, we get all the divs inside the board via querySelectorAll and add a click eventListener to watch out for a click.

// createBoard function

function createBoard(){ 
for(let i=0;i<49;i++){ 
let div =document.createElement("div") 
div.setAttribute("data-id",i) 
div.className = "square" 
if (i>=42){ 
div.className="taken" 
} 
board.appendChild(div) 
} 
}
Enter fullscreen mode Exit fullscreen mode

In this game, we are going to need exactly 49 divs. So we loop from 0 to 49 and every time we create a new div, give it a dataset attribute (this will be used later) and add a class of square to each div. This is to give all the divs a generic styling. Moving forward, we put a conditional statement from 42 i.e all divs from no 42 to 49 should have a class taken. Like I earlier stated, a player can only drop a brick on top of another. so this 7 bottom div will act like our ground level. This is similar to a house. A house stands on a ground not on the air. So in this case, the seven brick is serving as our ground. After all this, we simply append all the divs to the board which we got via querySelector.
You can uncomment the CSS class of taken to see the divs we will be using has the ground level.
After setting up the board and all, we are going to add an eventListener for every time we click a div and check if the player is actually dropping a block on top of another.

//clickBoard function

function clickBox(){ 
let squares =document.querySelectorAll(".board div") 
let click =parseInt(this.dataset.id) 
if( squares[click+7].classList.contains("taken") && !squares[click].classList.contains("taken")){ 
if(currentPlayer===1){ 
currentPlayer=2 
player.innerHTML=currentPlayer
this.className="player-one taken" 
checkWon() 
}else if(currentPlayer===2){ 
currentPlayer=1 
player.innerHTML=currentPlayer 
this.className="player-two taken" 
checkWon() 
} 
if(box===42){ 
setTimeout(()=>alert("boxes filled"),300)
setTimeout(()=>restart.style.display="flex",500) 
} 
}else{
alert("You cannot build on an empty space or on a space that has been built on")
} 
}
Enter fullscreen mode Exit fullscreen mode

Here is how the clickBoard function works, we first get all the squares (i.e divs created. We could not do that globally at the top because they have not been created then) via querySelectorAll And then get the id of the div clicked with dataset (which is used to hold data in html5 and can then be accessed in js. This actually returns a string, so to turn it into a number you wrap it with parseInt). After which we check if the element below it i.e the seventh square after it (since it is a six by seven grid, the block below it will be seven steps ahead) already has a class taken (i.e it has been clicked) and if also the square you are clicking on does not already have the className of taken. If the former is true (i.e you are dropping a brick on top of another) and the latter is false (i.e you are dropping a brick at an empty spot), then we add one to the box variable and simply check who is dropping the brick player-one or player-two and add a className of taken and player-one (if it was player one that dropped the brick). This will give that div a color(via css) which will fill that space. But if one or any of the two conditions is not what it should be, we simply alert the player that they are doing something they should not. After which we simply change the value of currentPlayer and put it on the screen via innerHTML. Finally after each player clicks on a div and we add the className and change the value of whose turn it is to play, we then check if the game has been won with the checkWon function.

//the checkWon function

function checkWon(){
let squares =document.querySelectorAll(".board div")
for (let y=0;y<winningArray.length;y++){
let square =winningArray[y]
if(square.every(q=>squares[q].classList.contains("player-one"))){
  setTimeout(() =>alert("player one(red) wins "), 200)
  setTimeout(() =>restart.style.display="flex", 500)
  }else if(square.every(q=>squares[q].classList.contains("player-two"))){
  setTimeout(() =>alert("player two(yellow) wins"), 200)
  setTimeout(() =>restart.style.display="flex", 500)
}
}
}
Enter fullscreen mode Exit fullscreen mode

The checkWon function like the clickBoard function first gets the squares (div) and then loops over the winningArray. Each value of the array is stored in a variable square and then a conditional statement is called. Note that each value in the winningArray is basically another array with a set of numbers (from 0-42, similar to the value we gave to each div via dataset i.e above). So everytime we get a new array from winningArray, we call an higher order function called every on that array, (this loops through every element in that array and cross check it with the condition stated). So basically, it loops through each element in the array i.e gets the value which is a number, and then it passes that number into squares (remember squares is basically all the divs which we got via querySelctorAll. So to get a div, we access it like an array with the number we got from this current looping) and then we simply check if that particular div has a className of player-two or player-one, meaning there is already a brick there. the every higher order function like I said earlier loops through each element of the array you specified, but it adds a special effect, if the condition you specified in the every parenthesis is true for every element, then it returns true, but even if just one value in that array does not agree with the condition it returns false. E.g if every div selected already has a class of player-two it returns true. So moving forward, the first if statement in the checkWon loop function simply check if any set of the divs in the game board matches any of the pattern defined in the winningArray and if all that set of div contains a class of player-one. If it does, alert the user that player one has one and you can then display the popup div to give players the access to play again. Else you should check if the set of divs has a class of player-two then alert the user that player two has won the game and display the popup div .If no set of div matches any of the defined pattern, we move ahead to check if all the board has been filled (remember we kept adding one to the box variable every time a player clicks on a spot). Since the number of divs on the gameBoard is 42, we check if variable box is equal to 42. If it is, we alert the user that it is gameOver and display the restart button to allow players restart the game. If there is still space on the board, i.e box is not equal to 42, the game will continue and every time a player clicks on a div it checks again.

restart game function

From above, an eventListener has been added for every time the restart button is clicked, this calls the reset game function. The reset game function is a simply function that clears the game board ,prepares for another game(via the loadDOM function which we defined above) and hides the popup afterward. This is the reset function.

function reset(){
board.innerHTML="" 
loadDOM() 
restart.style.display="none" 
}
Enter fullscreen mode Exit fullscreen mode

This is my codepen link to the game.
You could also use make this game player VS AI, this uses the minmax algorithm. I think and there are some pretty great articles on it, you can check it out. But I really don't understand it yet :( .

This is how you create a connect four game. If you have any question or better idea let me know in the comments below.

Thank you, Adios

Discussion (0)