A simple JavaScript Game
In this tutorial we are going to create an interface to play Connect 4. Plain JavaScript and some basic CSS will be enough to make a nice and functional interface.
References for this tutorial are:
- Lessons CSS , JavaScript , Le DOM .
- The Mozilla Developer Network:
- W3Schools tutorials: https://www.w3schools.com/.
- This course codepens: https://codepen.io/collection/AaMOJZ/.
We refer to the book Eloquent JavaScript for more details.
The Q&A platform StackOverflow has good answers, but apply some critical thinking before using them. Many other tutorials you can find on the internet may refer to outdated versions of JavaScript, so be careful before copy-pasting.
Like you did for last tutorial, create a new project in Glitch.
The game board in pure CSS
For simplicity, let’s start with a 3×2 board.
-
Create a valid HTML document containing a
<div>, itself containing a table (<table>) of 3 lines by 2 columns, every cell being empty. Give a unique identifier to the<div>. -
Attach a style sheet to the HTML document. Give fixed
widthandheightto the table cells. Using theborderproperty, give blue borders to the cells.At this point you have square grid. Using the
border-radiusproperty, make the table cells round, like this:Last touch: fill the blanks between the cells. For that, use wisely the
background-colorproperty on some elements of the table to obtain this: -
It’s time to play. Add classes
player1andplayer2to some table cells. Fill the cells with a red or yellow token, according to whether they have one class or the other (usebackground-coloragain).
Unobtrusive JavaScript
We are ready to write the real interactive application. We could
create a 6×7 board right in the HTML sources, like in the previous
section, but we are too lazy to type 42 times <td></td> by hand.
Instead we will dynamically generate the board via JavaScript.
We are going to follow the popular mantra of Unobtrusive JavaScript, which says that an HTML page should be usable even without JavaScript, all dynamic elements being added only after JavaScript has been loaded.
In our specific instance, since our page only contains an interactive game, we will simply show an error message as long as JavaScript has not loaded.
-
Remove the
<table>from the HTML document. Replace the<div>by a simple<div id="...">Activez JavaScript pour jouer.</div>(replace
...with the identifier you chose previously). -
Attach a JavaScript file to the HTML page. To avoid DOM loading problems, use the
deferattribute on the<script>tag like this:<head> <script src="..." defer></script> </head> <body> <div id="...">Activez JavaScript pour jouer.</div> </body>Perform a debug printing using
console.login your JavaScript file, test that your script has been loaded using the browser console (Shift+Ctrl+Kin Firefox,F12in Chrome). -
In our JavaScript application, we will separate the abstract internal representation of the game from its DOM rendering. We start with the abstract representation: define a global variable
boardand initialize it to a 6×7 array of integers, containing all zeros. Useconsole.logto check that the array is properly initialized.Note: contrary to C or Java, arrays (or, rather, lists) in JavaScript do not have a fixed dimension, and multi-dimensional arrays are simulated by list of lists (of lists of lists…).
You can initialize an empty array of length
nusingArray(n). You can use the.fill()method to put the same value in all cells of an already initialized array.You can take some inspiration from the example below.
-
Write a function
set(row, column, player)that takes as input a row, a column, and an integer representing player 1 or 2, and changes the corresponding entry inboardto 1 or 2.Test it in the browser console.
-
We now move to the generation of the DOM representation. Write a function
render()that:- Finds the DOM element corresponding to the
<div>usingdocument.querySelector. - Empties the element (you can use
.innerHTML = ''to empty a DOM element). - Creates a
<table>DOM node usingdocument.createElement, and adds it to the<div>using the.appendChildmethod. - Using a double
forloop (oneforloop nested in another), create<tr>’s and<td>’s inside the<table>element, so as to obtain a 6×7 table.
You can take some inspiration from the example below.
Test your function by calling it on page load, and verifying that the game board is properly drawn on screen.
- Finds the DOM element corresponding to the
-
Modify
render()so that the<td>in position (i,j) has classplayer1ifboard[i][j]is equal to 1, and classplayer2ifboard[i][j]is equal to 2.You can set an element’s class with
my_element.className = 'some_class_name';Test your functions in the browser console by successively calling
setandrendermany times. -
Define a global variable
turn, indicating whose turn it is to play. Initialize it to the value 1 (player 1 starts). -
Write a function
play(column)that- takes as input a column number,
- looks at
boardto find the first free row in the column (if any), starting from the bottom, - if no row is free in the column, it returns
false, - if a row is free in the column, it changes the corresponding
value in
boardto the value ofturn, changes the value ofturn, and returns the row number.
Test your function in the console by repeatedly calling
play()andrender().
Handling events
It is now time to react to mouse clicks. We want to call the play
function any time the user clicks on a column, regardless of the exact
line that has been clicked.
We could define a event handler for each <td>, but this would not be
elegant nor efficient: 42 event handlers take lots of resources,
especially on a mobile phone… Instead, we are going to define a
unique handler for the whole board, and we will use the Event object
to know which <td> exactly has been clicked. It is import here to
understand the difference between the properties .target and
.currentTarget seen in the lesson on
DOM. Also have a look at the example
below:
But how shall we know which column corresponds to the clicked <td>?
Among many solutions (we could, for example, navigate te DOM to count
how many <td>’s are on the left of our clicked <td>), the HTML5
data attributes API is the most elegant one.
-
Using
.addEventListener(), define a handler for theclickevent on the whole board (e.g., on the<table>element). For the moment, just executeconsole.log(event.target)in it (
eventbeing the lone parameter to the handler). Test you handler by clicking on the board and reading in the console. -
Modify
render()so that each<td>cell contains adata-columnattribute equal to the column containing the<td>. Thesedata-*attributes have a special API in Javascript: every DOM node has adatasetfield, which gives read/write access to thedata-*attributes. For example, ifmy_cellrepresent a<td>element, the following code// example using dataset my_row.appendChild(my_cell); ma_cell.dataset.column = 0;will insert a node
<td data-column="0"></td>in the document. Warning, all
datasetproperties are automatically converted to strings by JavaScript.Modify the click event handler so that it prints in the console the value of the
data-columnattribute. Test in the console. -
Modify the click event handler to respond to clicks by playing a token in the corresponding column (calling
playandrendersuccessively), or by not doing anything if the click happens to be outside of a cell (you can test the existence ofdataset.columnto know if the click happened on a<td>or somewhere else).
Final touches (optional)
You can tackle the following points in any order you like. Follow your inspiration!
-
Write a function to test if a move is winning (4 same color tokens aligned vertically, horizontally ou diagonally). Note that it is enough to test 4 directions around the last clicked cell, and to go at most 3 cells away in each direction. The following example may serve as an inspiration (or maybe not).
-
Modify your application to test at each move whether it is a winning one, or whether the board is completely full (a null match). When the game is over, the interface shows the winner. Clicking again starts a new match.
-
Using the CSS
animationproperty, make the 4 winning tokens flash. You can have a look at this guide on CSS animations (also in French). -
Add some style touches using the
box-shadowproperty. -
Wrap your code in a JavaScript class, and remove all global variables, so as to allow having more than one board per page.
-
Test your application on a smartphone, and adapt it to small screens. If you do not have a smartphone, you can test smartphone emulation in Chrome or Firefox (
Shfit+Ctrl+M).You can read this guide on mobile development, in particular the part on using the
viewportmeta tag (in French).