Nim : artificial intelligence

Your first HTML page

In this step, you'll learn:
  • What an HTML page looks like
  • How to open a very simple HTML page in your browser

I'm assuming that you have never created an HTML page before. HTML means "HyperText Markup Language". This is what a very simple HTML page looks like:

<!DOCTYPE html>
<html>
  <head>
   <title>This appears in the tab / title bar</title>
  </head>
  <body>
    <p>This appears inside the browser window</p>
  </body>
</html>

Here's how this HTML page looks in different browsers.

screenshot of simple HTML page in different browsers
Figure 1: How HTML <title> and <body> HTML elements appear in a browser

You can discover what all these <tags> mean in the Understanding HTML Tags section at the end of page 6: Your game goes here. But in fact, your browser will automatically create most of these tags if they are missing, so you can start with something even simpler, for now.

Do It Yourself

In the programming community, it is a tradition that your first program in any new language should display the words "hello world". To honour that tradition, you can create your own Hello World page and open it in your browser.

If you haven't already got a text editor, download and install a trial copy of Sublime Text. Open your text editor and create a new document. You can type hello world (or any other message you like), then save your file as index.html.

Find the file that you have just created on your desktop, and double-click on it. It should open in your favourite browser.

A Hello World HTML page displayed in a browser
Figure 2. A Hello World HTML page displayed in a Google Chrome

Hello... Desktop

Actually, the world can't see your message yet. This file that you saved on your local hard drive is only visible in your browser. Your next step will be to put an HTML file onto this server, so that anyone in the world can see it. To be precise, it will take you a total of 15 steps, including the ones that you have just taken. There will be clear instructions, explanations and screenshots to make sure that you find your way.

If you are eager to continue building your game, click on the Next arrow below, or on item 3: Getting Git in the menu on the left. Your Hello message should be online for the world to see when you have reach the end of page 5. If you need help or want to know more, click on optional sections below.

On almost every page, you will find a button that allows you to Download The Source Files for that page. You might like to download these files into a separate project folder, and use them as a preview of what you will have achieved by the end of the section.

If you get really stuck at any point, you can download my original files and use them to replace your own files, so that you can continue from a stable code base.

If you are using Sublime Text, you can use a shortcut to open your HTML file in a browser
Sublime Text's right-click shortcut to open an HTML file in your browser
Figure 3. Right-click on your document and choose Open In Browser
Why call your file index.html? In fact, you could call your html file anything you like. The fact that the first line is <!DOCTYPE html> tells your browser all it needs to know.

When you visit a site, if your browser doesn't ask for any particular file, the server on the site will send you index.html by default, if a file with that name exists. Unless you have a very good reason for using a different name, it is good practice to use index.html

Your HTML page is not appearing correctly in the browser.

  • If you are not using Sublime Text, perhaps your text editor is saving your files in a rich text format, such as .doc or .rtf. Make sure that you are saving in a plain text format, using .html as the extension.
  • There may be many files called index.html on your computer. Make sure that you are opening the right one. If you are using Sublime Text, you can use the right-click > Show in Browser shortcut, to be sure that you are opening the same document that you are working on.

If this doesn't solve your problem, please tell us what happened and we'll do our best to find a solution for you.

Getting started with Git

In this step, you'll learn:
  • How to use the Terminal
  • How to install or update Git, if you need to

GitHub is a meeting place for the open source community, where ideas are shared, where everyone can benefit from the energy and initiatives of others. A budding programmer like you has everything to gain from joining a community like this.

The Git application itself tracks the progress of your project. It creates a space for you make experiments, to make mistakes, to explore new possibilities without getting lost.

git --version

It's quite possible that git is already installed on your computer. To find out, open a Terminal window and type:

git --version

If all goes well, you should see something like the screenshot below.

Command Prompt showing git -- Terminal showing git --
Figure 4. Running git --version on different operating systems

If your version of git is 1.7.9.5 or earlier, or if you see a message like git is not a recognized command, then you can find more details about what to do in the Installing and Updating Git section below.

If you simply want to keep moving forward, then click on the Next arrow below, or on item 4: Your GitHub repo in the menu on the left. If you want to learn more about Terminal commands, then you can continue reading the Understanding Terminal Commands section.

Installing Git on Windows

The easiest way to install Git on the Windows is to download an installer. When you click on the link, the download should start automatically.

You can launch the installer application and simply click on the Next > button, until you reach the screen Adjusting Your PATH Environment. (See image below). Here, it is best to select the Use Git From The Windows Command Prompt radio button. The Windows Command Prompt uses the Windows-standard / character in path names, which means that you can copy and paste path names from the Explorer windows, or drag files onto the Command Prompt window to paste their paths automatically into the window. Perhaps later, you will prefer to use the Git Bash application: selecting the second radio button, as shown in the image below, will allow you to choose the option you like best.

Figure 5. Choose - Use Git From The Windows Command Prompt

Installing Git on Mac OS X

If git --version tell you command not found: git, then you can download an installer for Git and run the installer

If you are using Mac OS 10.9 (Mavericks) or later, you can download an installer from the git-scm.com site.

If your version of Mac OS X is older (but no older than OS 10.6 Snow Leopard), then you can use the latest Snow Leopard installer that you can find atsourceforge.net

The sourceforge site contains commercial advertising, and may include links to third-party sites that masquerade as download buttons. It is safe to click on the links with the format git-2.x.x-intel-universal...dmg.

When you have downloaded the DMG file that is appropriate for your version of Mac OS X:

  • Double-click on the downloaded file
  • Right-click on the yellow PKG icon and select Open from the contextual menu. (If you simply double-click, Mac OS X may tell you that it cannot be opened.)
  • Click Open in the dialog window
  • Enter the name and password of an admin user, to allow the installation
  • Click through the Continue buttons in the installation dialog
  • Click on Close when the installation is finished
Installing Git on Mac OS X
Figure 6. Installing Git on Mac OS X

Updating Git: Mac

If you have installed other coding environments, such as XCode, Git may already be installed on your Mac. However, it's possible that this previously installed version is now considered obsolete, and that you will need to upgrade it. If git --version tells you that you have version 1.7.9.5 or earlier, you might run into errors if you relied on it to follow the instructions in section 5.

Your other coding environment may rely on this older version for housekeeping duties, so it is best not to change it. The safest option is to install an up-to-date version of Git, and to use the newer version for all your work.

To do this, follow the Install Git on Mac instructions above.

To tell your Mac that you want to use the newer version, you need to execute some commands in the Terminal. Here are commands that I use on Mac OS 10.8.5 after running the Git installer for version 2.3.5:

git --version
git version 1.7.4.4
which git
/usr/bin/git

The original version is still active. It as located in a hidden folder. The Git installer places the new version in a different hidden file. The next command tells Mac OS X where to find the newer version. The following commands confirm that the newer version is now active.

PATH=/usr/local/git/bin:$PATH
which git
/usr/local/git/bin/git
git --version
git version 2.3.5

Installing Git on a Unix-like system

If this doesn't solve your problem, please tell us what happened and we'll do our best to find a solution for you.

Command Line Tools

You probably have plenty of experience using a mouse in a point-and-click graphic user interface (GUI). You are used to double-clicking on files to open them, and dragging them from one folder to another, to move them. You are used to opening an application when you want to send an email, edit a photo or listen to music. There is always visual feedback on the screen to tell you, a human, what is happening. Even if it's only an endless progress bar.

And yet, at any given time, there are many applications running on your computer which do not appear anywhere on the screen. These are the command-line tools (CLT).

When you go to a restaurant, you see the waiters who come to your table, with their little notebooks, but you do not normally see the cooks. Without the cooks, however, the restaurant would fail.

When you type a command into a Terminal window, you can imagine that you are talking directly to the cooks that produce all the goodness. The Terminal window is the equivalent of a waiter's notebook.

The operating system itself takes care of some commands, like cd . Other commands need a special chef CLT application to take care of them, so you need to refer to the name of the application first. For example, git --version tells the application git (which has no window that you can see on the screen) to send back information about which version it is.

Git commands

To see all the commands that git can understand, you can type git --help.

For information on all these commands, you can refer to the official documentation. However, this information can be overwhelming. Each time a git command is first used in this tutorial, you will find more information about how to use it in the optional sections at the foot of this page.

Particularities of the Terminal application

You are probably used to editing text in a word processor. In a word processor, you can:
  • Select a chunk of text with your cursor
  • Replace that chunk of text by simply typing over it
  • Remove the selected text with keyboard shortcut for Cut
  • Easily undo and redo your actions

In a word processor, you can also place the text insertion point anywhere you want and type to insert new text.

Terminal applications do not work that way.

In a Terminal application, you cannot cut or replace text, and you must use the arrow keys (rather than the mouse) to place the text insertion point. To delete text, you must place the text insertion point at the end of the text to delete and then use the backspace key to delete the characters one by one.

This may seem awkward to you at first, but it allows you to keep your hands over the keyboard at all times; you never need to reach out sideways for the mouse.

If, regrettably, you enter a command that does something that you did not want to do, your only solution is to enter a new command that explicitly undoes that precise action. It's good to check your command carefully before you press the Enter key.

Copy and Paste in the Windows Command Prompt

The standard keyboard shortcuts like Ctrl-C and Ctrl-Z were introduced by Apple in 1984 and were adopted in Windows applications only much later. The Windows Command Prompt uses an older keyboard shortcut system. In the Windows Command Prompt, you will need to use Alt-Spacebar to show a contextual menu, and then press the letter that appears underlined in the menu item that you want to use.

For example, to select text, you can press Alt-Spacebar then E (for Edit), then M (for Mark). You can now use the arrow keys to move the selection around and extend it. Pressing Enter will copy the highlighted text to your clipboard. Use Alt-Spacebar Edit, Paste to paste your text.

Using Git in other ways

You can use the Git application to work with many different sites that provide version control hosting. Some examples are: Bitbucket, GitLab and Launchpad. Other sites that help you to create applications in the cloud, such as Heroku and Cloud 9 offer integration with GitHub.

You can find a list of hosted version control services on slant.co

Your GitHub repository

In this step, you'll learn:
  • How to create a free GitHub account
  • How to create a Git repository on the GitHub server

Now you're ready to open a GitHub acccount. This wont't take long (although finding a username that you like and that isn't already taken can be time-consuming). I've used the name "blackslate" in my examples.

Go to the Join GitHub page, and fill in the form.

At the time of writing, Git requires you to use only the characters A-Z, a-z and - (hyphens) for your username and for the names of your projects. Names with other punctuation, special characters, accents or non-Roman letters, like !android_ or Andréa or Андрей, are not permitted.

The first page asks for a username and an email address that are not currently registered on GitHub. It will also ask you to enter a password. At the beginning, at least, you will be typing this username and password often.

Figure 7. Create a personal account on GitHub

On the second page, you can accept the Free plan, which is already chosen as the default. Simply press the Finish Sign Up button.

Figure 8. Choose a free plan

This is the very first time you are visiting GitHub as a registered user, so you see a special screen, suggesting that you take the official tour of what Git and GitHub can do for you. When you return to this page in the future, the top section will be different.

Figure 9. You're ready to create a repository

You might like to take GitHub's own tour now, to learn about the basic Git commands, or you might like to bookmark the Hello World tutorial or the Try Git tutorial so that you can come back to these after the world has seen you say "Hello!"

On the right of the page, you can see a big green New Repository button. Click on that and fill in the Repository Name field. A new page will appear. This first time, to keep everything simple, leave everything else exactly the way it is, and just click on the Create Repository button. (You might like to fill in the optional Description field, though.)

Figure 10. Give your project a name

You'll find yourself on a page full of choices and command line code. No worries. When you find yourself here again later, you will have a much clearer understanding of what all this means.

Figure 11. Copy the URL for your new repository

The most important thing to do now is to copy the URL for your new GitHub repository. You can do this by clicking on the Copy To Clipboard button, as shown in Figure 11 above. You'll need to paste this URL into a Terminal window in the next step.

Your immediate aim is to get your Hello World page online, so I propose to take an unconventional shortcut. If you want to know what the conventional path is, then you can read the Branches and Pages section below. If you want to move along quickly, just click on the Next arrow below, or on item 5: Creating a branch in the menu on the left.

In this tutorial, you will be following a path that has already been prepared for you, taking predictable steps to recreate something that already exists. This is reassuring, but it is not how you will be working in the future.

When you start working on your first real project, you will be creating something that is different from anything anyone else has ever made before. There will be no clear path; there will be many possible paths that you can follow. You will not know in advance which ones will bring you to a dead end, and which ones will lead you to where you plan to go. You may not even be entirely sure where your journey will lead you. The original creators of iTunes were probably focused on playing music; they did not imagine that their project would develop into a shopping mall.

The purpose of Git is to allow you to explore many possible paths at the same time. You can test two different techniques for achieving the same result, and then decide which is better. Different developers can move off in different directions, each creating something new, and then the whole team can discuss which approach is likely to be the best way forward. Perhaps later, you will discover something that makes you realize that one of the abandoned paths is in fact the right one to take.

Git allows you to keep track of all the paths that anyone in the team has followed. It allows you bring all the best discoveries from the secondary paths, and to weave them all together into a single strand.

Git uses a mixed bag of words to describe this process. It uses words like branch, fork, clone, checkout, merge, push and pull. You can find a full list of all the keywords used by Git here.

Branches

As a rule, you start a project with a single branch called master. The master branch will contain only code that has been tested extensively, to ensure that it is stable. Each time you want to make a change (which might not be so stable), you create a new branch, and you work on that. Let's imagine that you call the new branch develop. When you first create the develop branch, it is identical to the master branch. When you have tested your new code and have decided that the develop branch is stable, you can merge the changes back into the master branch, like one arm of a river flowing back into the main channel. After each new merge, the master branch and the develop branch might again be identical. You can continue working in the develop branch, and make additional branches from that. Your additional branches may be for proposed bug fixes, for new features and for experiments.

Branches
Figure 12. The multiple paths or branches in a project repository

You can give your branches any name you like (so long as they only use the 53 permitted characters). Normally, you would use a name that describes the change you are about to make.

Pages

While you are working on a project, it is often helpful to maintain a web site to let people know what you are doing, how they can help, and how your project can help them. GitHub has used a neat technique that allows you to integrate your web site into your GitHub repository itself.

If you create a special branch called gh-pages (short for GitHub pages), and you place a file named index.html in the root folder of the branch, then GitHub will automatically treat the gh-pages branch as a web site.

You can find more information about this at GitHub Pages.

To get your first project online as quickly as possible, you can:

  • Create a repository (as you have just done) and leave it empty. If it is empty, it has no master branch.
  • Create a gh-pages branch (as you are about to do) and use it for all your development.
  • Create secondary branches, as your work progresses, and merge them back into the gh-pages branch when you are ready.

Telling the world about your project is the best way to find people who are willing to help you. In future projects, you will know how to create a strong web presence, right from the beginning. In future projects, you will know to use the master branch for your main development.

Creating a branch

In this step, you'll learn how to put your HTML page online. In particular:
  • How to set the Terminal window to talk to a given directory
  • How to copy your Git repository from the GitHub server to your computer
  • How to tell Git what changes you have made
  • How to synchronize the repository on the remote GitHub server with the repository on your computer
Any sufficiently advanced technology is indistinguishable from magic. Arthur C. Clarke

You've just created an empty repository, like a dry river bed. And now I'm going to show you how to make a special branch from this repository. This is much easier to do when the repository is still empty.

Many thanks to Rachel Berry of GitHub for pointing out this technique to me.

The next few instructions may seem a little like magic to you for now, but you will soon make sense of them. In the Understanding Cloning section below, you can read up about the git commands that you will be using.

One of the commands you will be using is commit. This requires you to include a note to describe what you have changed. I am guessing that you are not familiar with command line text editors, so I strongly recommend that you configure Git to use Sublime Text as its default text editor. You can find instructions for this in the Tips and Tricks section below. You will be thankful you did this when you reach section 7: Reverting changes.

Here's what you need to do:

  • On your desktop, create an empty folder in the place where you would like to save your new project. I have called my folder nim. It might be easier for you if you do the same.
  • Open a Terminal window. You're going to type 9 commands in all. First you are going to create a command that gives the Terminal control of your new nim folder. You'll create the command in three separate stages. The complete command might look something like this:
    cd /Users/james/nim
    1. Type cd  (that's "cd " followed by a space). This means change directory.
    2. Drag the icon of your nim folder onto the Terminal window. This will automatically insert the path to your folder after the cd  that you have just typed.
    3. Press the Enter key to execute the command. The Terminal now has power over what happens in your nim folder.
Figure 13. Using the cd command in a Terminal window
  • Still in the Terminal window:
    1. Type: git clone  (with a space after "clone")
    2. Paste in the URL that you copied at the end of the last step
    3. Add a space and  gh-pages. The complete command might look something like this, except that you will use your own username instead of "blackslate":

      git clone https://github.com/blackslate/nim.git gh-pages

      Don't simply copy and paste this line: make sure that you have correctly entered your own username, and that you have used the right name for your repository, before you press the Enter key.

    4. Press the Enter key.

On Windows, the standard Ctrl-V keyboard shortcut may not work in a Command Prompt window. You may need to right-click, then select Paste from the contextual menu.

Inside your nim folder you should now see a folder named gh-pages. The clone command should have copied into it all the contents of your new repository on the GitHub server (in other words, nothing, since your new repository is still empty). The output in the Terminal should look something like this:

Figure 14. Using git clone to create a local repository.

The git clone command also created an invisible folder called .git inside the new gh-pages folder, as you can see in Figure 15 below. This contains all the data that the Git application needs to keep track of the changes that you make to your project.

Figure 15. The contents of the invisible .git folder

To learn how to make "invisible" files visible on your computer, you can read the Tips and Tricks section below.

Now for some more command line magic. (Do you remember what the cd  command does?)

  • In the Terminal window, type the following 3 commands:
cd gh-pages
git checkout --orphan gh-pages
git status

To understand what the checkout --orphan command does, see the Understanding Git Commands section below. The git status command is one that you will use often. It lists all the files that you have changed, before you commit the changes to Git's memory system.

Here's how your Terminal window might look now:

Figure 16. Checking out a new orphan branch of your project

Just a few more actions, and you'll see your Hello World web page online.

  • On your computer desktop, find the index.html file that you made in step 2, and drag or copy your index.html file into your new gh-pages folder.
  • In the Terminal window, run the git status command again. You'll see that Git is aware of the change that you have made.
Figure 17. The status of your project is updated each time you make a change.
  • Now you're going to tell Git that you are confident about the changes that you have made. In the Terminal window, type the following 3 commands:
git add -A
git commit -m "Initial commit to the gh-pages branch"
git push origin gh-pages
Figure 18. Pushing your changes to the remote GitHub repository

Git will ask you for the username and password that you registered when you created your Git account. When you type your password in the Terminal window, no characters will appear. Press Enter when you're done.

That's it! Now you can visit your new web site. In your browser, type in the URL for your GitHub site. For me, it will be:

http://blackslate.github.io/nim/

For you, if you called your repository "nim", then all you have to do is replace my "blackslate" username with your own username.

http://<yourUserName>.github.io/nim/

Figure 19. A Hello that the world can see
Here are the git commands that you have just used:
git clone
The clone command copies everything from a given repository to the directory that your Terminal window is currently controlling. (In this case, there was in fact nothing to copy). Your command looked something like this:
git clone https://github.com/blackslate/nim.git gh-pages
The directory name gh-pages is optional. If you had not included it, the command would have created a directory called nim, using the same name as the remote repository. The clone command also creates a new branch in your local repository, but this branch doesn't have a name yet.
git checkout
Before you used the git checkout command, you told your Terminal window to focus its attention on your new gh-pages directory, by executing the command cd gh-pages.Your command looked like this:
git checkout --orphan gh-pages
The checkout command said "Prepare all the files in this folder as the files that I am currently working on." The --orphan option said "The files in this branch are to be treated completely separately from any files in any other branch". In other words, if you had a master branch for your main development, the branch that you are now checking out would be totally unrelated to it. The gh-pages branch name at the end of the command tells Git what you want to call this new branch. If you look at the page for your repository on GitHub, you'll find that it now has a gh-pages branch.
A new branch in your GitHub repository
Figure 20. Using checkout ... branch-name creates a new branch in your project.
git status
The git status lists all the changes that you have made. There are 4 separate places where your work is tracked:
  • In the folder where you are currently making changes (your workspace
  • In an index of all the files whose changes you are tracking
  • In your local repository, on the computer you are working on now
  • In the remote repository, stored on the GitHub servers
Git Data Transport Commands
Figure 21. How Git tracks the changes you make

Credit: Oliver Steele

The status command tells you the differences between the local repository and the index (on the one hand) and your workspace (on the other). The first time you used it, there were no differences, because you had made no changes. The second time, you had just added your index.html file, and you had not yet told Git about it, so it was untracked at that time.

git add
The plain git add command tells Git that you want to track changes to the files listed by git status. You can choose to add only specific files, if you wish. Files that have been added to Git are said to be staged. The -A option says "Add all files that have changed, and remove all files that have been deleted". If you do not use -A then deleted files will be ignored. In this case, there are no deleted files to remove from Git's staging area.
git commit
The commit command moves changed items from the staging area to the local repository. You can move changed items directly to the repository without passing through the staging area by using git commit -a. In this case, you don't need to use the git add command.
The -m option allows you to include a message to explain what it is that you are committing. In my example, the message was "Initial commit to the gh-pages branch". If you don't include -m then Git will open a text editor for you to enter the message. By default, this will be a command-line text editor, in the Terminal window itself. See the Tips and Tricks section below to learn how to set up Sublime Text as Git's preferred text editor... or simply use the -m option every time.
git push
Up until this point, all the changes have been made on your local computer. The git push command tells Git to send all the changed files to the remote repository on the GitHub server. When you did this, the index.html will have appeared in your GitHub page in your browser (you may need to refresh the page first.
index.html becomes visible on GitHub
Figure 22. git push makes your files visible in the remote repository

By default, your local repository is called origin. You have just created and checked out a remote branch called gh-pages.Using plain git push will assume that you mean git push origin gh-pages, but it is best to be explicit each time, just to be sure that Git is not making different assumptions from you.

Using Sublime Text to create commit and revert messages

Windows:

If you used the default installation settings when you installed Sublime Text, you can use the following command in the Command Prompt window:

  • Sublime Text 3
    git config --global core.editor "'c:/program files/sublime text 3/subl.exe' -w"
  • Sublime Text 2
    git config --global core.editor "'c:/program files/sublime text 2/sublime_text.exe' -w"

When you have done this, each time you execute a commit command without using the -m option, a new document will open in Sublime Text with the title COMMIT_EDITMSG. You can edit this message, save it and close it, and then Git will continue with the commit process. If you change your mind about your commit, you can close the message without saving it, or delete all its contents before you save and close it. If Git sees an empty message, it will cancel the commit process.

Credit: jrotello

Macintosh:

Showing invisible files

To show invisible files on Windows:
  • Open any window in Windows Explorer
  • Open the Display menu
  • Select options
  • In the dialog window that opens, choose the Display tab
  • Click on the Display tab
  • Scroll down a little until you can see XXXX
  • Select the radio button that says "Show hidden files, folders and drives"
  • While you're here, you might also like to deselect the checkbox that says "Hide extensions for known file types." If you do this, your index.html file will appear as index.html. If you don't it will appear as index ... and so will any file called index.php, index.doc or index.jpg. Showing all file extensions makes it clear exactly which file you are looking at.
To show hidden files on Macintosh:
  • Step 1

Git tells you that it can't execute your commands

  • When you created your repository, did you click on any buttons, other than Create Repository?
  • If you added a ReadMe file, a .gitignore file or a licence file to your repository, through the GitHub "new repository" page, then the remote repository on the GitHub site will already contain files. You won't be able to push any new files to the remote repository until you have downloaded them to your local repository. The simplest solution (at this early stage) is to delete your repository and start again, as described below.

You get a fatal error message in your Terminal

  • This can happen if you mistype a command, but it can also happen if your version of Git is older that 1.7.9.5. To test this, type git --version in your terminal. If the output is less than 1.7.9.5, then you will need to update Git before you can continue. See the Installing and Updating Git section at the foot of the Getting Git page for more details.

If this doesn't solve your problem, please tell us what happened and we'll do our best to find a solution for you.

Your game goes here

In this step, you'll learn:
  • How to track changes with Git
  • How to update your GitHub web site
Download the source files Test here

Do you remember the very first HTML page that you saw on page 2?

<!DOCTYPE html>
<html>
  <head>
   <title>This appears in the tab / title bar></title>
  </head>
  <body>
    <p>This appears inside the browser window</p>
  </body>
</html>

You can use this as a template for the web page that will be the home for your game. You can copy this text and paste it into your index.html file in the nim/gh-pages folder that you created in the last section. You can make a couple of changes to it, to make it more relevant:

<!DOCTYPE html>
<html>
  <head>
    <title>Nim</title>
  </head>
  <body>
    <p>The game will go here</p>
  </body>
</html>

Save the file, and open it in your browser. If you don't see what you expect to see, check the Troubleshooting section below. If all is well, you can use Git to push your well-formatted HTML page to the GitHub server.

In your Terminal window:

  • Ensure that the Terminal is focused on the folder that contains your index.html file. If you're not sure, type cd  and then the path to the folder. Remember that you can drag the folder's icon onto the Terminal window, to paste its path automatically.
  • As a sanity check, type git status. The terminal window should show that index.html has been modified.
    git status after modifying your index.html file
    Figure 23. git status after modifying your index.html file
  • Type git add -A and press Enter
  • Type git commit -m "'Hello World' becomes 'The game will go here'"
  • Type git push origin gh-pages and then enter your username and password
    Pushing your first modification to the GitHub server
    Figure 24. Pushing your first modification to the GitHub server

Now you can visit your project site on the GitHub web site. Instead of "Hello World", you should see your new page.

the updated page on the GitHub site
Figure 25. The updated page on the GitHub web site

You can recognize HTML code because of the tags that it uses. Tags are instructions that are enclosed in < and > pointed angle brackets, in this style: <style>.

Closing Tags

Most HTML tags come in matching pairs, with an opening and a closing tag. Closing tags contain a forward slash / character, followed by the first word of the matching opening tag. The expression to describe a pair of matching tags, plus all the data between them, is HTML element. In the example above, you can see the following pairs of tags, creating five different HTML elements.

<html>...</html>

This tells the browser to treat all the information between these tags as HTML code. There should always be an opening <html> tag at the beginning of an HTML document, and a closing </html> tag at the end.

There should only be one html element in an HTML file.

<head>...</head>

The head element contains instructions to the browser that are not shown inside the browser window. In sections 11 and 12, you will add instructions to tell the browser where to find additional files to download. The head can also contain information for search engines that summarize the contents of the page.

<title>...</title>

As you can see from the screen shot in figure 1, the text inside the title element appears in the title bar of the browser window, or as a label in the tab for the current page.

Search engines like Google, Yandex and Baidu use the text in the title element to understand the main purpose of the page.

<body>...</body>

The body element contains everything that will appear inside the browser window. There should only be one body element.

<p>...</p>
This indicates a paragraph of text.

Self-closing Tags

Some tags, such as <!DOCTYPE html> are self-closing. A self-closing tag stands on its own, with no matching tag to close it later. Self-closing tags are typically used for elements that cannot have text inside then. For example

<!DOCTYPE html>
The very first line of an HTML file tells the browser what kind of file it is. Browsers can read many kinds of files: XML files, which contain arrays of data, SVG files which contain the formulae needed to draw smooth Scalable Vector Graphics, image files such as JPG and PNG, and many more.
<img>
The <img> tag tells the browser where to find a file that displays an image. You will be working with the <img> tag in section 8: Adding an image.
<link>
The <link> tag tells the browser where to find a file that gives rules on the appearance and layout of the web page. You will be using the <link> tag in section 11: Styling with CSS.
<input>
The <input> tag lets you create elements such as buttons and editable fields that allow the visitor to interact with your page. You will be adding a checkbox <input> to your page in section 21: Showing the rules.
<meta>
The <meta> tag can be used to tell the browser and bots that visit your site what your page is about, what language it's in, how it should be categorized in search engines. You will be adding a <meta> in section 26: Invisible data.

By the time you gave finished this tutorial, you will also have used all the following tags (in order of appearance):

You can find a full list of the tags used to create HTML elements here.

As you saw in your Hello World test, if a file has an .html extension, most modern browsers will treat everything inside it as HTML, even if there is no <html> element, if there is more than one <html> element, or if there is text outside the html element. Modern browsers may also display content correctly even if the body is missing, or if there is more than one body element. Nonetheless, it is good practice to use these elements correctly. Other developers will notice if your code is badly written.

The browser will not display any text between an opening angle bracket < character and a closing angle bracket >. If you use a tag that the browser does not recognize, such as <unofficial_tag>, the browser will simply ignore it. Older browsers may ignore tags that were officially introduced after they were released.

When you open the file in your browser, you still see "Hello World".

  • Perhaps you need to save your index.html file so that your browser can see the new version.
  • Perhaps you opened a different index.html file than the one you saved. If you are using Sublime Text, you can right-click on your edited page and choose Open in Browser.

In Sublime Text, when you right-click on your edited page and choose Open in Browser, nothing happens, or it opens in the wrong browser.

  • Set your default browser to the browser that you want Sublime Text to open
  • On your desktop, select your index.html file and make sure that the default application that will be used to open .html files is your default browser.
    Set the default application for .html files on Windows
    Figure 26. Windows - set the default application for .html files
    Set the default application for .html files on Macintosh
    Figure 27. Macintosh - set the default application for .html files

If this doesn't solve your problem, please tell us what happened and we'll do our best to find a solution for you.

Reverting changes

In this step, you'll learn:
  • How to see what changes you have made, using Git
  • How to check what the project looked like at a previous stage
  • How to revert changes you have made
  • How to rewrite your history of changes

In you index.html file, the words "Hello World!" have gone. The change that you have just made moves your project forward. But let's imagine for a moment that it creates a problem, and that you want to get back to your "Hello World" version of the file. How can you do that?

Git provides 3 commands: checkout, revert and reset, of increasing power. You've already used checkout command to create the gh-pages branch in your repository. You can use a different flavour of it now to look at your project the way it was in the past, without disturbing its current state.

git log

In the Terminal window type the following command:

git log
the git log command
Figure 28. The git log command gives a unique name for each commit

You will see the history of the commits that you have made, starting with the most recent. Each commit has a unique 32-character name. You don't need to memorize this.

git checkout

To see what your project looked like at the moment of a particular commit you can type the command git checkout xxxxxxx, where xxxxxxx represents the first 7 characters in the commit name. In my case, to return to my Hello World state, I can type:

git checkout e6b50f7
Using git checkout to view an earlier stage in the project
Figure 29. Using git checkout to view an earlier stage in the project

Git restores my entire project (in this case, just the index.html file) to the state it was in after my first commit. If I open index.html in my text editor, and refresh it in my browser, I can see that I am back to Hello World.

This change is only temporary. I can get back to where I was before by checking out the most recent commit:

git checkout b6d433d
checking out the most recent <code>commit
Figure 30. Checking out the most recent commit

You can try this for yourself. It's safe to do it now, right at the beginning of your project. If the worst comes to the worst, you can always delete everything you've done, and you need only 15 steps to get back to where you are now.

git revert

The git revert xxxxxxx command is more powerful. It is not an "undo" command, but its effect is to undo all the changes made by the chosen commit. In fact, it creates a new commit that resets the state of your project to the way it was earlier. This action adds a new element to the history of your project. If you change your mind, you can then revert the previous revert. You can revert back to any point in the past, and all the intervening changes will still be safely stored, for you to revert to if you need to.

Before testing the revert command, I strongly recommend that you configure Git to use Sublime Text as its preferred text editor. You can find instructions for this in the Tips and Tricks section at the bottom of page 5: Creating a branch. If you don't do this, you might need to use the Troubleshooting section below to find out how to manage the command line text editor that Git uses by default.

If you have set up Git to use Sublime Text as the default text editor, you might like to test this command now.

  • Check the git log that you made earlier for the name of the most recent commit
  • Run the command git revert + the first 7 characters in the name. For me, this would be:
    git revert b6d433d. The name that you use will be different.
  • Git will now open a text editor for you to create a message explaining why you are reverting to a previous version. If you have configured Git to use Sublime Text as its preferred text editor, you should see something like Figure 30 below.
    you must provide a commit message when you use the revert command
    Figure 30. You must provide a commit message when you use the revert command
  • Type something like Testing the revert command, save the document and close it.
  • In the Terminal window, run the git log command again. You will see that there is now a new commit.

If you open your index.html file now, either in your text editor or in your browser, you will see that everything is back to its Hello World state. If you continued working now, and made a new commit, your "game will go here" version would be ignored. But it would not be forgotten. You would be able to revert to it at any time, if you wanted.

git reset

Actually, you want to restore your "game will go here" version, and continue working from that point. You can now do one of two things.

  1. You could revert your most recent commit action (which was just created by the revert command). If you do this, you will add another commit to your history. If there were other developers working on the project, this would be the best thing to do, so that a permanent trace of all your actions is recorded
  2. You can execute the dangerous reset command.

The reset command can be dangerous, because it can destroy data without checking first. If you have made changes to your files, or created new files, these changes and these files will be erased permanently, without warning.

In this case, all you will be deleting is the most recent commit action, the one you just created by using revert, so you will be back exactly where you want to be.

In the Terminal window, type git reset --hard  and then the first characters of the commit that you made after the "game will go here" edit. In my case, this command looks like this:

git reset --hard b6d433d

Your command will have a different name at the end.

If you now run git log, you will see that you have changed your history: there are now only 2 commits recorded. The third one has gone forever.

using reset to change your history
Fig XX. The reset command erases history.
For a more detailed treatment of checkout, revert and reset, see Undoing Changes on the Atlassian site, or Reset Demystified by Scott Chacon and Ben Straub, authors of Pro Git.

When you type a git command, you get an error like fatal: Not a git repository (or any of the parent directories): .git

  • Make sure that you have told your Terminal window which directory your project is in. You can use the cd  command, followed by the path to your project directory.
  • Remember that you can drag the icon of your project directory from the desktop onto your Terminal window, so that its path is added automatically.

When you execute the revert command, you find that the Terminal window is acting in an unexpected way.

  • Perhaps you have not configured Git to use an external text editor, and it has opened vim, a command line text editor. The easiest solution might be to press Esc then q to quite the editor, and cancel the command. You can then follow the instructions in the Tips and Tricks section at the foot of page 5: Creating a branch, to use Sublime Text to create commit and revert messages.
  • Remember that you can drag the icon of your project directory from the desktop onto your Terminal window, so that its path is added automatically.

If this doesn't solve your problem, please tell us what happened and we'll do our best to find a solution for you.

Creating issues

In this step, you'll learn:
  • How to plan your project
  • How to create an issue to track progress developing features and fixing bugs

You've been working at this for some time now, but your game is still... let's say a little disappointing. No images, no interaction, no artificial intelligence. You might like to take a look at the game you are getting ready to write, and make a note of what needs to be done to get from where you are now to where you want to be. Here are some of the things that you might note:

  1. Add images of matches
  2. Arrange the images in rows
  3. Show the player that the images are interactive
  4. Hide each image when you click on it
  5. Add a button so that the current player can say "Your turn"
  6. Show which player is currently playing
  7. Add buttons to restart the game
  8. Show the rules when the game first loads
  9. Create a computer player that is smart enough to win
  10. ...

In fact, there are plenty other things that you will need to do. You can never be sure that you have thought of everything before you start. You are likely to discover new issues as you go along. That's normal.

Issues

The GitHub site provides you with a system for tracking what still needs to be done. Their system is called "Issues". At the moment, all your issues are requests for features that you want to develop. Later, you might also want to track bugs. In either case, you want to have a clear description of what to do, and a way of noting progress, and of noting when it's all done.

To create an issue

  • Visit your GitHub repository at https://github.com/your-username/nim
  • At the top right of the page (you may have to scroll the window to see it), there is a  +  button, with a tooltip that says "Create new...". Click on that and choose "New issue"
  • The page https://github.com/your-username/nim/issues/new will open
  • Give your new issue a title, such as "Add images of matches"
  • Add a comment, if you think that the title alone is not enough
  • Click on the Submit New Issue button
  • You'll find yourself on a new page, where the issue is described as Open. As you make progress, you can add new comments here. When you have treated the issue to your satisfaction, you will be able to click on the Close Issue button.
  • Repeat this process for each of the notes that you have made. (You can use the New Issue button instead of selecting from the  +  menu button.)
Creating a new issue on GitHub
Figure 31. Creating a new issue on GitHub

You now have a checklist of features that you want to add to your game. It will be very gratifying to close each issue, and to see how much progress you have made.

Adding an image

In this step, you'll learn:
  • How to save an image from the Internet
  • How to add an image to your HTML page with an img element
  • The role of the src and alt attributes for img elements
  • The advantages of working on an open source project, using material from Creative Commons
Download the source files Test here

Here's an image of a match, called match.png :

a match
Figure 32. The match.png image that you can use for your game

This image is available to use with a Creative Commons licence. You can find the original high-resolution source image here. You can read more about Creative Commons in the optional sections below.

Save the image

To use this image for your game, you first need to save it to your nim folder. It is good practice to keep different kinds of files separate. I recommend that you create a folder called img alongside your index.html file, and that you save the match image in this folder. (You'll see in a moment why I suggest that you name the folder img.)

To save the image:

  • Right-click on the image in your browser
  • In the contextual menu that opens, choose "Save Image As..." (The wording may be slightly different in your browser).
save the match.png image to the nim/img/ folder
Figure 33. Save the match.png image to the nim/img/ folder

Show the image in your page

To show the image in your HTML page, you can replace the line...

  <p>The game will go here</p>

... as shown below, then save your file.

<!DOCTYPE html>
<html>
  <head>
    <title>Nim</title>
  </head>
  <body>
    <img src="img/match.png" alt="match" />
  </body>
</html>

Refresh your index.html page in your browser, or reopen it, and check if the image appears. If not, you can check the Troubleshooting section below. If all has gone well, you can upload your changes to the GitHub server.

Displaying the match image in your browser
Figure 34. Displaying the match image in your browser

Upload the changes to the GitHub server

In the Terminal window:

  • Ensure that the Terminal is focused on the folder that contains your index.html file. If you're not sure, type cd  and then the path to the folder.
  • Run the command git status to check that Git knows that your index.html file has changed, that you have added a folder called img and that you have added a file called match.png to the img/ folder.
  • Run the command git add -A to tell Git to record your changes in the index (staging area).
  • Run the command git commit -m "Add img/match.png; modified index.html to show this image". Alternatively you can run just git commit type your commit message in the text editor that opens, then save the document and close it so that Git can continue.
  • Run the command git push origin gh-pages and enter your username and password details when asked. This will upload your new version of your project to the GitHub server.

You (and everyone else in the world) should now be able to see your match image on your online web page.

 Updating your local Git repository and pushing the changes to the GitHub server
Figure 35. Updating your local Git repository and pushing the changes to the GitHub server
You have now successfully treated your first issue: Add images of matches.

You can visit your Issues page on GitHub, click on the link to this particular issue, and triumphantly click on the Close Issue button. Isn't that satisfying?

If you want to know more about the img tag and its src, alt and other attributes, please read the Understanding Images section below. When you're ready to continue, click on the Next arrow below, or on item 10: Rows of images in the menu on the left.

The <img /> tag

An img HTML tag is self closing. There is no closing </img> tag. You will often see it with a / character just before the closing > angle bracket. This is not essential, but it helps remind you that no closing tag is needed.

No image will appear unless the tag contains a src (source) attribute, inside the angle brackets. An img tag should normally also have an alt (alternative) attribute (see below). Other attributes such as width and height are also common.

src

The src (source) attribute tells the browser where to find the image. This can be an either:

  • an absolute path that uniquely identifies the file on the whole of the Internet, such as http://lexogram.github.io/openbook/nim/complete/img/match.png
  • a relative path indicating the how to find the image if you start in the folder that contains the HTML page itself. If the HTML page is in a folder called nim/ and an image called match.png is in a folder called nim/img/, then the relative path will be img/match.png.

alt

The alt attribute is important in two ways:

  • It provides an on-screen text label, describing the image, if the image itself cannot be found
  • It provides screen readers (for people who have difficulty seeing) with a description that can be read aloud by a synthesized voice.

For a mini tutorial on the img tag, see the w3schools site

Fetching images

When you visit a web site, your browser asks the remote server for an HTML file, such as the index.html page that you are working with now.. This file contains nothing but text. The HTML tags are text, the attributes of the tags are text, and the content inside the elements are text. There are no images.

The browser reads the HTML page from top to bottom. If it encounters an img tag with a src attribute, it will send a new request to the server for the image file. The server may reply: "404: I can't find the file you asked for", or it may send the image file back to your browser. Your browser can then show the image.

Image width and height

A big image may take a long time to arrive. Until it does arrive, the browser will not know how big it is going to be, so it will not be able to create the appropriate amount of space for it.

It is good practice to include width and height attributes inside each img tag, so that the browser can prepare a place in the page for all images. If you don't do this, you may see the other content of the page shifting as new images arrive.

Ideally, the img tag for your match should look like this:

  <img src="img/match.png" alt="match" width="12" height="125" />

In this tutorial, for simplicity, I have left this out. The only image you are using is only 3 KB, and it should arrive fast enough for you to see no shift in the contents of the page.

By default, everything that you find on the Internet is subject to copyright. In very simple terms, this means that it is illegal to use most images (and any other works) that you find online in your own projects.

Members of the open source community encourages others to re-use, re-mix and modify images and other works that they have created. There are a number of legal licences, collectively known as Creative Commons, that you can apply to your work to say to others: "Please, feel free to build on what I have started".

This tutorial is covered by a Creative Commons licence. You can use the material you find on this site in any way you like, so long as you allow others to use your version of the material in any way that they like.

Visit the Creative Commons site for full details.

Using your own images

I've used matches for the playing pieces, but the game can be played with any set of 16 items that you have available: coins, pebbles, chocolate chips... You can replace my match with your own image if you want, but to keep your HTML code identical to mine, you might like to call your image match.jpg (at least until you have completed writing the game).

Showing all file extensions on your desktop

Your image does not appear in your index.html page in the browser. A "broken image" icon appears instead.

  • HTML links are Case Sensitive. This means that "link" is not the same as "LINK" or "Link". Check if you have used any UPPERCASE letters when you named the match image file or the folder it is in. The names of the folder and the file should use the same case as the src path that you have created. If your path is src="img/match.png" then the folder should be called "img" and the image itself should be called "match.png".
  • Perhaps you have not stored the image inside a folder called img, which is where you HTML page is looking for it.
  • Perhaps you are using a different image, and you have called it "match.jpg" or "match.gif". In that case, you make sure that the extension you use in your HTML file is the same as the extension for the file itself. You might like to follow the steps to show all file extensions, in the Tips and Tricks section above.

Your image appears correctly when you open your index.html page locally, but it appears broken when you view the page on the GitHub server.

  • See the first explanation above. The file system on your local computer may be case-insensitive, so your browser may be able to find it locally. Thin GitHub server uses a different file system, which is case sensitive, so the case of every letter must be identical.

Your index.html page still shows "The game goes here".

  • Perhaps you haven't saved the changes to your file.
  • Perhaps you are opening a different index.html file in your browser.
  • If this is happening on your GitHub site, perhaps you did not follow all the steps required to push your changes to the GitHub gh-pages repository.

If this doesn't solve your problem, please tell us what happened and we'll do our best to find a solution for you.

Creating Four Rows of Images

In this step, you'll learn:
  • How to add multiple images to your page
  • How to use the div element to arrange them in rows
Download the source files Test here

Showing 2 matches in your web page is as easy as showing one. You can simply copy and paste the line that you have just changed:

<!DOCTYPE html>
<html>
  <head>
    <title>Nim</title>
  </head>
  <body>
    <img src="img/match.png" alt="match" />
    <img src="img/match.png" alt="match" />
  </body>
</html>

If you do this another 14 times, you will have a total of 16 matches, all in a line across the browser window.

16 img tags shows 16 matches in the browser
Fig XX. 16 img tags shows 16 matches in the browser

By default, your browser will attempt to place all img elements side by side. Since your match image is very thin, it has no difficulty doing this. However, you want to display the matches in 4 separate rows, because that's the way the game works.

You can use a different HTML tag - <div> - to divide the img elements into separate blocks. By default, your browser will place all div elements one below the other. Unlike img elements, div elements need both an opening <div> and a closing </div> tag. Here's how you can arrange your HTML code to produce four rows of matches, with the right number of matches in each row:

<!DOCTYPE html>
<html>
  <head>
    <title>Nim</title>
  </head>
  <body>
    <div>
      <img src="img/match.png" alt="match 1 row 1" />
      <img src="img/match.png" alt="match 2 row 1" />
      <img src="img/match.png" alt="match 3 row 1" />
      <img src="img/match.png" alt="match 4 row 1" />
      <img src="img/match.png" alt="match 5 row 1" />
      <img src="img/match.png" alt="match 6 row 1" />
      <img src="img/match.png" alt="match 7 row 1" />
    </div>
    <div>
      <img src="img/match.png" alt="match 1 row 2" />
      <img src="img/match.png" alt="match 2 row 2" />
      <img src="img/match.png" alt="match 3 row 2" />
      <img src="img/match.png" alt="match 4 row 2" />
      <img src="img/match.png" alt="match 5 row 2" />
    </div>
    <div>
      <img src="img/match.png" alt="match 1 row 3" />
      <img src="img/match.png" alt="match 2 row 3" />
      <img src="img/match.png" alt="match 3 row 3" />
    </div>
    <div>
      <img src="img/match.png" alt="match 1 row 4" />
    </div>
  </body>
</html>

You'll notice that I've edited the alt texts for each match, to indicate which position and row it lies in. This means that someone who is using a screen reader to play your game will be able to visualize the layout from the alt text alone.

16 matches arranged in 4 rows
Figure 36. Using div elements to arrange the 16 match images in 4 rows
You might like to use the following commands in your Terminal to push these changes to the GitHub server:
git status
git add -A
git commit -m "16 matches arranged in 4 rows, using <div>"
git push origin gh-pages

Now, you should be able to see your 16 matches on your GitHub site.

You can now close your second issue: Arrange the images in rows.

You can visit your Issues page on GitHub, click on the link to this issue, and click on the Close Issue button.

But now you can see that there is a new issue to deal with: the match images are not laid out neatly. Click on the New Issue button and enter a title for this: "Lay out match images in a neat V arrangement".

It will not take long to have a whole series of closed issues behind you, and to feel a sense of how much you have achieved.

Your new HTML code asks the browser to show the same image 16 times. As the browser is reading the page from top to bottom, it will reach the first request for match.png and it will ask the server to send the image. Then, on the next line, it will see a second request. This time, it will realize that it has already asked the server for an image, and it won't ask again.

In fact, since you already asked for the image in your previous version of this page, it is likely that your browser will say to itself: "Hey, I've already got this image. I don't have to ask the browser for it." Instead, it will find the image in its cache, and show it immediately.

Ouch. Try :

  • Restart your computer.

If this doesn't solve your problem, please tell us what happened and we'll do our best to find a solution for you.

Using Cascading Style Sheets

In this step, you'll learn about:
  • Cascading Style Sheets
  • How to set the alignment of text
Download the source files Test here

You now have the content that you need to play the game of Nim, but the matches are not yet laid out neatly in a symmetrical V shape. The easiest way to get the images to appear in a V shape is to treat them like text (yes, text) and use center alignment for them. But first, you need to create a separate file to contain the layout instructions.

In your text editor, create a new, empty file called style.css, and save it an a folder called css in same folder as your index.html file. Your folder structure should now look like this:

Your nim folder with index.html and folders for img/match.png and css/style.css
Figure 37. "Your nim folder with index.html and folders for img/match.png and css/style.css

Linking the CSS file to your HTML file

You now need to do two things:

  • Tell the index.html file where to find the style.css file
  • Tell the style.css file how to treat the HTML elements

At the beginning of your index.html file, inside the <head> tag, add the line that appears in bold below:

<!DOCTYPE html>
<html>
  <head>
    <title>Nim</title>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
...

This tells the browser: "Ask the server for the file style.css, which is in the folder named css/, which is in the same folder as this index.html file that I'm reading now."

Applying a CSS rule

In the style.css file, add the following text:

body {
  text-align: center;
}

This tells the browser: "For the body element and all its children, make text appear centred on the page."

Centring the matches with text-align: center
Figure 38. Centring the matches with body { text-align: center; }
You might like to use the following commands in your Terminal to push these changes to the GitHub server:
git status
git add -A
git commit -m "Add css/style.css to centre the matches"
git push origin gh-pages

Now, you should be able to see the matches centred in your GitHub page.

What HTML does

HTML defines the content and the structure of your web page. In particular, it defines:

  • The text and the images that the visitor will see
  • How the content is divided into blocks
  • The role of each block.

Here are some common roles for blocks of content, and the tags that indicate each role:

  • <h1>Header</h1>
  • <h2>Sub header</h2>
  • <p>Paragraph</p>
  • <ul>Unordered list (like this list)</ul>
  • <li>Item in a list (like this bullet item)</li>
  • <div>Generic division</div>
  • <button>Button</button>

Parents, children and siblings

HTML elements can be arranged in a hierarchy. The metaphor of a family is used to describe the hierarchy. A parent element can have one or more child elements, which may have children of their own. Each element, except the <html> element itself, will have exactly one parent. Elements that have the same parent are called siblings. Child elements can inherit certain properties from their parents.

For example, the img elements in the snippet below are children of the div element. The "match 1 row 3" sibling element is the first-child, and the "match 3 row 3" element is the last-child.

    <div>
      <img src="img/match.png" alt="match 1 row 3" />
      <img src="img/match.png" alt="match 2 row 3" />
      <img src="img/match.png" alt="match 3 row 3" />
    </div>

In the current version of CSS (CSS3) child elements cannot affect their parents, and younger siblings (which appear lower down the HTML page) cannot affect their older siblings.

classes and other attributes

HTML elements may have attributes, which are defined inside the opening tag. One important attribute that you can use with any element is class. An HTML element can have more than one class, and many elements can share the same class. You can use classes to tell the browser what CSS rules to apply to each element.

Other common attributes include:

  • background-color
  • color (the colour of the text)
  • font-size
  • width
  • height

CSS rules

CSS rules are defined like this:

selector {
attribute: value;
attribute: value;
}
White space is not important in CSS rules, and the final semi-colon is not essential. The rule above could also be defined as:
selector {attribute:value; attribute:value}

A selector can be defined in many ways. Here are some that you will be seeing soon:

  • Official tag names, such as body
  • Class names, such as .matches
  • A combination of tags and classes, such as div.matches img. The order of the selector items, and the gaps (or lack of gaps) between them defines a hierarchy of HTML elements. The browser reads the list from right to left. This example will select all img elements which have a div with the class matches as a parent.
  • Pseudo-elements, such as :hover, which apply to an element only in certain circumstances. For instance, :hover is only applied if the mouse cursor is hovering over the element.

The selector defines which element(s) the rule will apply to. There can be more than one rule that selects a given element. As you will see, there are precise rules about the order in which the rules are applied: precise selectors have priority over more general selectors, and rules that are defined later take priority of rules that were defined earlier.

An attribute is the official name of something that you can change about an HTML element. Different attributes have a different range of values. You can find details of each type of element, its attributes, and their possible values on the w3schools site..

HTML, CSS and JavaScript are written in USAmerican English. This means that you will find words like center and color. Even if you prefer a different spelling of English, you will simply have to use the official spelling. If you don't, the browser will not understand you.

Your matches remain bunched together on the left of the page:

  • In your index.html file, check that the relative path to your style.css is correct
  • In your style.css file, check that you have all the right symbols: curly brackets {} around the rule to apply, a colon : after the attribute name, and a semi-colon ; after the value.

If this doesn't solve your problem, please tell us what happened and we'll do our best to find a solution for you.

CSS selectors and classes

In this step, you'll learn about the class attribute for HTML elements:

In particular, you will learn how to:

  • Select an element by its tag name
  • Giving elements a class attribute to distinguish them
  • Select an element by its class
Download the source files Test here

As it happens, you won't want to centre all the text in your game. For example, later, you will want the text for the rules to be aligned to the left. Using a CSS selector, you can choose to apply text-align: center only to the <div> elements that show the matches.

In your style.css file, rename the selector from body to div, as shown below:

div {
  text-align: center;
}

If you save your file and refresh it in the browser, you should see no change. To check that something has changed, you can add a header to your index.html page:

<!DOCTYPE html>
<html>
  <head>
    <title>Nim</title>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    <h1>Nim</h1>
    <h2>The game that will beat you</h2>
    <div>
      <img src="img/match.png" alt="match 1 row 1" />
...

As you can see in the screenshot below, only the matches inside the div elements are centred. The text is aligned to the left.

Applying the text-align rule to all div elements
Figure 39. Applying the text-align rule to all div elements

Classes

But later, you will be creating more div elements, and they will not contain matches. How can you select just some elements and not others? The answer is to use a class.

For the first div element, the one containing 7 matches, add a class attribute called matches, as shown below:

<!DOCTYPE html>
<html>
  <head>
    <title>Nim</title>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    <h1>Nim</h1>
    <h2>The game that will beat you</h2>
    <div class="matches">
      <img src="img/match.png" alt="match 1 row 1" />
...

In your style.css file, make the rule more specific:

div.matches {
  text-align: center;
}

The rule now applies only to the div that has a class containing the word "matches". The other divs are not affected.

Using div.matches as a selector limits the selection
Figure 40. Using div.matches as a selector limits the selection

To ensure that all the matches appear centred, you can add class="matches" to all 4 divs, save your index.html page, and refresh your browser to check.

Simplifying the selector

In this particular case, the only HTML elements which will have a matches class will be these 4 divs. The browser reads CSS rules from right to left, so .matches is sufficient as a selector.

.matches {
  text-align: center;
}
A dot . before a selector item indicates a class name. A selector item without a dot designates a tag name.

You will also see selector items that start with a hash # character. These are ids. The browser will only recognize one element with a given id on a given page. You can use id="unique-id-name" to identify unique elements, such as a checkbox or radio button. You will be doing this on page 29: Showing the rules.

Remember to use the Terminal to push these changes to the GitHub server. Here's a shorter version, without the git status command. The -a option for commit will automatically apply the add command for all changed files. Since you only have one repository, and one branch checked out, Git will automatically know that you are referring to the origin local repository and the gh-pages branch, so you can omit this information, too.
git commit -am "Add .matches class to designate divs to be centered"
git push

What does your game look like on the GitHub server now?

Great! It's time to close your third issue on your Issues page on GitHub.

Reacting to the mouse

Your next objective is to make your game react to movements of the mouse, to encourage the player to interact with the matches. Specifically, you can:
  • Change the cursor when it moves over a match
  • Create a highlight around the match under the cursor
Download the source files Test here

Here's some CSS that will do this (and some extra things too):

body {
  font-size: 10px;
  background-color: black;
  color: rgb(255,255,255);
}

.matches {
  text-align: center;
}
.matches img{
  width: 1em;
  height: 10em;
  padding: 1em;
  border-radius: 1.5em;
  cursor: pointer;
}
.matches img:hover{
  background: #321;
}

And this is what happens when you roll the mouse over a match:

Changing the background-color on hover
Figure 41. Changing the background-color of an image on hover

If you copy and paste the CSS rules above into your style.css file, save it and refresh your index.html file in your browser, you should see the effect for yourself.

CSS units

In this step, you'll learn how to:
  • Set the font size of an element
  • Inspect the CSS of the elements in your web page
  • Use px and em units
  • Set the size of an element
Download the source files Test here

One important question is: "How big should the game be?" And one good answer is: "I don't know yet. Let's deal with the proportions first and work out the size later." So that's the way I propose that you should work.

For purely practical reasons, since this is a tutorial, it will be good to make the game tall and thin. You'll discover the JavaScript development environment shortly, and you will want to give plenty of space to it, and still keep your game visible. This is easy to do if the game fills only a narrow vertical slice of your screen.

If you edited your style.css file on the previous page, you might like to commit your changes to your Git repository before you continue.
git add -A
git commit -m "Preview of rollover effect"

Now you are going to make some experiments. This would be a good time to create a branch of your project, and make the changes to that branch, leaving your latest commit intact. If your experiments go wrong, you can come back to the point where you are now, and continue from there. Using the -b option with git checkout creates a new branch in your repository with the name that you give as the next word.

git checkout -b rollover
Switched to a new branch 'rollover'

Note that if you push changes to the remote repository on GitHub now, you will not see your changes on your GitHub web site, because your site is linked to the gh-pages branch. The rollover branch is intended only for testing locally.

Now you have a new branch: for now it is identical to your gh-pages branch, complete with the changes that you have just committed. However, you want to take a step back to the previous state, and then work forward from there. You can do this with the git revert <commit-name> command. First you need to know the name of your last commit. You can find this using git log. If you use the --oneline option. Here's the command plus the output that I get when I use it:

git log --oneline
8439f0a Preview of rollover effect
ad5e390 Add .matches class to designate the divs to be centered
656e9b6 Add css/style.css to centre the matches
7c1cdc9 16 matches arranged in 4 rows, using <div>
4d68023 Add img/match.png; modified index.html to show this image
b6d433d 'Hello World' becomes 'The game will go here'
e6b50f7 Initial commit to the gh-pages branch

In my case, the name is 8439f0a. For you it will be different.

git revert 8439f0a # <= use the name of your own last commit here

You will need to enter a commit message, in the text editor, then Git will set your workspace back the way it was before your last commit. (See the Troubleshooting section below if you have any problems.)

Check out a new branch and take one step backwards
Figure 42. Checking out a new branch and using it to take one step backwards

You can now use the commands git checkout gh-pages and git checkout rollover to switch between your two branches. If you refresh your browser after each checkout command, you will see the different states. The text in your style.css file should update too, inside your text editor.

Using checkout to toggle between branches
Figure 43. Using git checkout <branch-name> to toggle between branches

Isn't that cool?

px, em and %: three units of measurement

There are 7 different units of measurement in CSS. The three that are most often used are px, em and %. (The other four units are percentages of the size of the browser window. You can achieve neat effects with them, but you won't use them in this tutorial. See the Learn More about CSS units below to discover how they work.)

px stands for "pixel" (or picture element). I'm guessing that you know that a pixel is the smallest dot that can be shown on your computer monitor. I imagine that you are familiar with setting the size of a font, or checking the dimensions of an image or a computer screen using pixels.When you use px to measure an element in CSS, it will always be the fixed size that you set.

You can use it now to make the text in your index.html page smaller. In your style.css file, add the following rule:

body {
  font-size: 10px;
}

.matches {
  text-align: center;
}

Save your changes and refresh your index.html page in your browser. You should see that the characters have become smaller. But they are not as small as 10 pixels. You can ask your browser to explain why:

  • Right-click on the word "Nim"
  • Choose Inspect Element from the contextual menu that opens

If you don't see an Inspect Element in the contextual menu, see the Troubleshooting section below.

Using the elements inspector
Figure 44. Using the Elements Inspector

In the Styles pane on the right of the Elements Inspector, you should see that the user agent stylesheet has a rule for h1 elements that gives them a font-size of 2em.

The user agent stylesheet

If you provide no CSS information about your web page, the browser will use default values. Different browsers have different default values. In Google Chrome, the default value for font-size is 16px. User agent is a technical word for "browser".

But what's an em?

em: the font ratio unit

In the Elements Inspector, if you click on the Computed tab of the right-hand pane, and scroll down if necessary, you will see that the computed font-size is 20px. The font-size for the body is 10px, the font-size for the h1 element is 2em, and its computed font-size is 20px = 2 x 10px. If you were to guess that em is a number to multiply font-sizes by, you'd be right.

In the world of print, an em is the width of the letter M in a given font face. It is used as a unit of spacing. In the world of the web, an em is a similar unit of spacing, but it refers to the height of the font, from the top of the tallest character to the bottom of the lowest character.

The font-size attribute is inherited. This means that when you (or the user agent) sets it for the body element, all the other elements in your page will inherit the same value, even your images. The match.png image is exactly 10 times as tall as it is wide. If you set its width to 1em and its height to 10em then it will appear 10px wide and 100px tall. If you now change the font-size of the body element to 16px, it will become 16px wide and 160px tall. In other words: you can use the font-size to scale your images.

Try it. In your style.css file, make the following changes:

body {
  font-size: 10px;
}

.matches {
  text-align: center;
}
.matches img{
  width: 1em;
  height: 10em;
}

Save your style.css file and refresh your index.html file in the browser. You should see the matches become smaller.

Note that the selector .matches img means "all img elements which have a parent with the class match. The space between .matches and img means "anywhere in the hierarchy": the element with the match class does not have to be the immediate parent of the img element.

In this case, the selector .matches > img would also work. The > means that the two elements must be in a direct parent-child relationship.

Using the Elements Inspector to experiment with CSS

Instead of changing the value of font-size in your style.css file, saving it, and refreshing your browser, you can use a much faster technique for experimenting with CSS, using the browser's Elements Inspector. Right-click on a match and select Inspect Element from the contextual menu that opens. By default, the Styles pane on the right should be open, and you should see a list of the rules that apply to the element that you clicked on.

The third rule in the list as the one that you applied to the body element, to define the font size. If you click on the value (10px) the value should pop out of the pane and the text should be selected. You can now type any value you like (even invalid ones). Alternatively, you can use the Up and Down arrow keys to alter the value by 1 unit. You can hold down the Shift key to alter the value by 10 each time, or the Alt key to alter the value by 0.1.

You can edit CSS in the Elements Inspector
Figure 45. You can edit CSS in the Elements Inspector
You can edit the CSS of any page quite radically using this technique. You can add new attribute/value pairs, and you can disable existing settings. When you are satisfied with the results, you can copy all or part of a rule and paste it into your CSS file, for later use.

Note that when you refresh the page, all these temporary changes to the CSS will be lost.

A % of the parent

The % unit sets the size of an element as a percentage of the size of its parent. You'll be using this on page XX: Whatever, when you will be arranging the game controls. If you want to know more now, you can read the Learn More About Units section below.

You might like to use the following commands in your Terminal to save these changes to your local Git repository. If you are working on the rollover branch, you don't have to push the changes to the GitHub server unless you want to access them from a different computer.
git status
git add -A
git commit -m "Set body font-size in px and match dimensions in em"

Now that you know how to set dimensions using CSS, you are ready to create some space around the matches. Click on the Next arrow below, or on item 15: The CSS box model in the menu on the left, or continue reading the optional sections below.

There are 7 different ways of measuring size in CSS. In this game you are going to use the first 2 of them. Here is the complete list:

px
px stands for "pixel". As with all CSS units, there must be no space between the digits and the units. For example, the browser will ignore a rule that is written like this:
body { font-size: 10 px }
em
One em is identical to the font-size of the current element. It can therefore have different values in different elements. For example, if the default font-size for the body is 10px and an <h1> header has a font-size of 2em, the header font will have a size of 2 x 10px = 20px. Padding of 2em around the header text will be 40px thick: 2 x 2 x 10px.
The value of an em unit depends on the font size of the base element
Figure 46. The value of an em unit depends on the font size of the base element
%
A percentage width or height is calculated with respect to the width or height of the parent element. If the parent is 100px by 100px and the element has a width of 200% and a height of 50%, it will be 200px by 50px.

There is one exception: padding and margin are always calculated with respect to the width of the parent. This means that you can create an element with a fixed aspect ratio by setting its width to 100% of its parent width, its height to 0 and its padding-top to a percentage of its parent width:

Expressing dimensions as a percentage of the parent's dimensions
Figure 47. Expressing dimensions as a percentage of the parent's dimensions
vw
One vw unit is 1% of the viewport width: 100 vw units fill the width of the window.
vh
One vh unit is 1% of the viewport height: 100 vw units fill the height of the window.
vmin
One vmin unit is the smaller of the vw and the vh units
vmax
One vmax unit is the larger of the vw and the vh units

Elements with sizes measured in px will have a fixed size. Elements with sizes measured in vw, vh, vmin or vmax will change in size when the user resizes the window. Elements with sizes measured in % will have a size relative to their parent: if the size of the parent is measured with respect to the window (as a %, or as a v...), then the size of the element will change with the size of the window. Elements with sizes measured in em will all change in size together if you change the font-size of their base elements.

The em, %, vw, vh, vmin and vmax units are for your convenience. In practice, the browser converts these to pixel dimensions before applying them to the elements in your page. If you look at the values shown in the Computed pane of the Elements Inspector, or at the callouts in the main browser window, you will see that the units that are displayed are always px.
The browser always converts to px units before applying dimensions
Figure 48. The browser always converts to px units before applying dimensions

When you first open the Development Tools in Google Chrome, the main browser window will be split in two, vertically. You can create a horizontal split by clicking on the button in the top right corner of the window, or open the Developer Tools in a separate window by clicking and holding on the button, and choosing the second option in the menu that opens.

Choosing the position of the Development Tools panel
Figure 49. Choosing the position of the Development Tools panel

When you run the revert command, the Terminal starts behaving strangely.

  • Press :q to quit the command line text editor and cancel your revert action, then configure Git to use Sublime Text as its default text editor, then try again.

When you right-click on an element in the browser, the contextual menu that opens does not contain an Inspect Element item.

  • It's likely that you are using Apple Safari as your browser. You will need to activate the development tools before the Inspect Element item will appear.
  • Click on the Safari menu
  • Choose Preferences
  • In the dialogue window that opens, select the Advanced tab
  • At the bottom of the window select the Show Develop Menu in Menu Bar checkbox
  • When you right-click now, the Inspect Element item should appear in the contextual menu
Enabling developer menu on Safari
Figure 50. Enabling the Developer menu on Apple Safari

If this doesn't solve your problem, please tell us what happened and we'll do our best to find a solution for you.

The CSS box model

In this step, you'll learn how to:
  • Create space and lines around your elements
  • Create elements with rounded corners
Download the source files Test here

In the Elements Inspector, you may have already noticed a diagram that looks like this:

The box model diagram
Figure 51.

You may also have noticed that as you move your cursor over the HTML representation of the page elements in the Elements Inspector, coloured highlighting appears in the main browser window, along with a callout of the element type and its dimensions.

Box model highlighting
Figure 52. Using the Elements Inspector to highlight the dimensions of an element in the browser page

This is a representation of the box model. Every visible HTML element is drawn in a rectangular box, with layers like a rectangular onion. The innermost layer is the HTML content itself, such as a piece of text or an image. Around that are three additional layers, which are often invisible:

padding
Padding extends the clickable area of an element. If the element has a background-color this color will extend to the edge of the padding. You can have different amounts of padding one each of the four sides of the element, if you want.
border
The border also extends the clickable area of an element, by drawing a line around the padding area. The line can be a solid line, or dashed or dotted, a groove or a ridge, inset or outset, or hidden. It can be a different width, style and color on each of the 4 sides, if that is what you choose. You can also give the border (and thus the padding area) rounded corners, and each corner can have a different radius.
margin
The margin of an element defines the minimum amount of blank between the outside of its border/padding and the next element.

Here's a demonstration. Right click on one of the shapes in the sandbox below and select Inspect Element, then edit the values for the different attributes in the Styles pane.

red
green
blue
Playing with the box model attributes
Figure 53. Playing with the box model attributes

Edges and shortcuts

If you look at the CSS for the red element, you will see that it includes the following rules:

  padding-top: 10px;
  padding-right: 20px;
  padding-bottom: 40px;
  padding-left: 50px;

However, for the green element, the padding is defined in one line:

  padding: 6em 7em 4em 3em;

The rule for green uses a shortcut, with 4 values given at once. The values are defined clockwise, starting at the top (at 12 o'clock). The one-line rule for green is the equivalent of:

  padding-top: 6em;
  padding-right: 7em;
  padding-bottom: 4em;
  padding-left: 3em;

When you use the one-line shortcut, you do not need to define all the edges. Here are 3 different one-line shortcuts, and their equivalent in the verbose format:

  padding: 6em 7em 4em;
  /* This is the same as: */
  padding-top: 6em;
  padding-right: 7em;
  padding-bottom: 4em;
  padding-left: 7em;
  padding: 6em 7em;
  /* This is the same as: */
  padding-top: 6em;
  padding-right: 7em;
  padding-bottom: 6em;
  padding-left: 7em;
  padding: 6em;
  /* This is the same as: */
  padding-top: 6em;
  padding-right: 6em;
  padding-bottom: 6em;
  padding-left: 6em;

In these examples, I've used the same unit for every edge, but you can use different units for each edge, if that makes sense in your project. For example, this is perfectly valid:

margin: 0 1px 2em 3%

When you set a value to 0, there is no need to include any unit. The value will be zero regardless of the unit you use.

If you use % as the unit for padding or margin then this always refers to the width of the parent, never the height, even when you use it with the -top or -bottom attributes.

You cannot use % as the unit for border-width.

You can also use auto for the two horizontal values of margin, in order to centre an element horizontally within its parent.
selector {
  margin: 0 auto;
}

The various border attributes

If you look at the CSS rule for the blue element, you will see that it includes this definition for the border:

  border-style: solid;
  border-width: 15px 5px;
  border-color: lightblue;
  border-radius: 16px;

However the border rule for the green is a one-line shortcut:

  border: dotted 10px palegreen;

This is equivalent to:

  border-style: dotted;
  border-width: 10px;
  border-color: palegreen;

Because all the attributes have different ranges (units, line style names, colors), you can write them in any order you want. This would give exactly the same result:

  border: 10px dotted palegreen;

You can leave out the -color and -style attributes. Your browser will provide default values (black and 3.333px, in Google Chrome). You must give a valid value for -style. If you don't the browser will ignore your rule.

CSS applies rules in the order in which they appear. For the green element, the colour palegreen is applied to all the edges, thanks to the first rule:border: dotted 10px palegreen;. The next two lines override this choice of border-color for the top and the bottom:
  border-top-color: darkgreen;
  border-bottom-color: rgba(192, 255, 192, 0.5);

You can see different ways of defining color on page 16: CSS colors.

border-radius

The border-radius attribute is not included in the one-line shortcut. If you want to give rounded corners to your element, you must provide a rule for border-radius separately.

As with the padding, border-width and margin attributes, you can provide a different value for each corner, in a single line. This rule for the blue element...

  border-radius: 16px 0 2em;

... is equivalent to:

  border-top-left-radius: 16px;
  border-top-right-radius: 0px;
  border-bottom-right-radius: 2em;
  border-bottom-left-radius: 0px;

If you make the border-radius bigger than the dimensions of the element, it will automatically be reduced to the largest meaningful value. The green element initially has a border-radius of 100em, or 2000px, but the radius that is actually applied is only 110px.

Explore what happens when you change the font-size attribute of the parent element. You'll see how the values with em units change size while the values with px

Using what you have learnt

When you have done enough experimenting to feel comfortable with the different layers of the box model, you can apply your new understanding to your own project. In your style.css file, make the following change:

body {
  font-size: 10px;
}

.matches {
  text-align: center;
}
.matches img{
  width: 1em;
  height: 10em;
  padding: 1em;
  background-color: gray;
  border-radius: 1.5em;
}

Save your file and refresh the page in your browser window. You should see something similar to this:

Creating padding with a background-color
Figure 54. Creating padding with a background-color around the match images

The matches have been pushed apart by the padding that you have created around them.

Now you can see that there is a gap between each match. This is a space character. By default, HTML displays any series of whitespace characters as a single space  . Whitespace characters include:
  • spaces
  • tabulation characters (tabs)
  • carriage returns (used to separate paragraphs on Macintosh computers)
  • line feed characters (used to separate paragraphs on Unix-like computers)
  • the combination of a carriage return and a line feed character (used to separate paragraphs on Windows computers)

If you wanted to get rid of the spaces between the matches, you could edit your HTML so that there is no whitespace between the end of one <img> tag and the beginning of the next:

...
  <body>
    <div>
      <img src="img/match.png" alt="match 1 row 1"
    /><img src="img/match.png" alt="match 2 row 1"
    /><img src="img/match.png" alt="match 3 row 1"
    /><img src="img/match.png" alt="match 4 row 1"
    /><img src="img/match.png" alt="match 5 row 1"
    /><img src="img/match.png" alt="match 6 row 1"
    /><img src="img/match.png" alt="match 7 row 1" />
    </div>
...

You'll notice that the browser doesn't care what whitespace characters you use inside a tag, or how many you use. If you like to write each attribute on a different line, arranged neatly in columns, the browser will be just as happy:

    <img src="img/match.png"
         alt="match 1 row 1"
    />
You might like to update your local repository after making your latests changes:
git commit -am "Add padding, border-radius and background-color"

Here are some external links where you can learn more about the CSS box model:

CSS colors

In this step, you'll lear
  • Define a color in 7 different ways
  • Experiment with JavaScript
  • Set the background colour of an element
  • Set the text color for an element and its children
Download the source files Test here

When you were experimenting with the padding, border and margin settings on page 15: The CSS box model, you may have noticed the colour swatches. You may even have clicked on one of them and seen the colorpicker open. This allows you to change the color that you apply to an element. If you Shift-click on a colour swatch, you will see that the same color can be defined in different ways.

A colour swatch in Google Chrome
Figure 55. A colour swatch in Google Chrome

Color formats

Color names

The w3c organization that creates the standards that browsers (are supposed to) follow, defines 256 color names for 256 distinct colours. These include both familiar and obvious names like white, black and red, through compound names like palegreen and lightgreen (which represent the same colour) to more obscure names like teal and burlywood, which might not evoke any particular colour to you, especially if English is not your first language.

You can use your own preferred English spellings: both grey and gray refer to the same colo(u)r.

Named colours
Figure 56. Defining colours by their official names
rgb: Red, Green, Blue

Most humans have three types of light detector cells in their eyes. These react most strongly to the primary colours that we call red, green and blue.

To create colours that look realistic to us humans, a computer screen generate dots that emit varying levels of red, green or blue light.

Computers count in 0s and 1s. With one bit or binary digit, you can create 2 values: 0 or 1. As a result, computers work fastest with numbers that are powers of 2, like 2, 4, 8, 16, ... 256. 256 is a nice number because it is can be represented by 8 bits, and 8 is a power of 2. With 8 bits, you can represent numbers from 0 (00000000) to 255 (11111111 or 1x128 + 1x64 + 1x32 + 1x16 + 1x16 + 1x8 + 1x4 + 1x2 + 1x1).

In good light, the human eye can distinguish millions of distinct colours. Computer screens can generate millions of distinct colours by varying the amount of red, green and blue light over 256 different values: from 0 to 255. A color that is made of 255 units of red plus 127 units of green and 0 units of blue will appear orange. Using the same amount of all three primary colours will give various shades of grey, from "black" (0 of all three colours) to "white" (255 for all three colours). In fact, you do not get pure black: you get the dark grey colour of your unlit screen. You do not get pure white either: you get a pale grey that doesn't hurt your eyes too much.

When you want to tell a computer which colour to display, you can use the format rgb(red-value, green-value, blue-value). For example: this colour of orange can be expressed as rgb(255, 127, 0).

RGB colours are represented as a mix of red, green and blue
Figure 57. RGB colors are represented as a mix of red, green and blue
rgba: Red, Green, Blue and Alpha

Colours defined in the rgb() format are solid, opaque colours. To make a colour appear transparent, so that it is affected by other colours behind it, you can add a 4th variable: alpha opacity. For CSS, this is expressed as a number between 0.0 and 1.0, where 0.0 means "no opacity at all" (or "completely transparent"), and 1.0 means "totally opaque".

Here are two elements that overlap. The one on the left has a rule background-color: rgb(255,0,0) (opaque red). The one on the right background-color: rgba(0,255,0,0.5) (semi-transparent blue). Where they overlap, the color becomes rgb(127, 0, 127): half of each original color.

Using alpha to measure opacity
Figure 58. RGBA colors use an alpha channel to measure opacity
Hexadecimal

The computer doesn't really understand colours and decimal numbers. It prefers binary, but binary numbers are long to write. Here's the number 42 in binary: 00101010. A compromise is to use a hexadecimal system, where you count in powers of 16. The number 42 can be written as 2x16 + 10x1, or 2A, where A stands for 10.

In a number system that counts in 16s, you need a separate symbol for every value from 0 - 15. Our decimal system has numbers from 0 - 9; for the numbers 11 - 15, the letters A - F are used.

To make it clear that the string #FF7F00 is a number, a # hash sign is placed at the beginning. Here's how you can understand this number:


FF7F00
Fx16 + Fx1 red, 7x16 + Fx1 green, 0x16 + 0x1 blue
15x16 + 15x1 = 255 red, 7x16 + 15x1 = 127 green, 0x16 + 0x1 = 0 blue
rgb(255, 127, 0)
You've seen this colour before. It's orange.

From now on, any time you see a string of 6 characters chosen from the set 0123456789ABCDEF, you'll know that this can be used to represent a colour, in the way that computers like best.

Colours can be expressed as a hexadecimal number
Figure 59. Colours can be expressed as a hexadecimal number
Short hexadecimal: multiples of 17

In the early days of the Internet, connections were slow, and every bit was a burden. That's why you see element names like p and img, and units like px, instead of whole words.

It's possible to express 4,096 distinct colours using only 4 characters instead of 7. For CSS, the number #F70 is a short form for #FF7700. This is not exactly the same orange as #FF7F00, but it is so close you are unlikely to notice the difference.

To save space, you can use just three hex digits to express 4096 different colours
Figure 60. To save space, you can use just three hex digits to express 4096 different colours
hsl: Hue, Saturation, Luminosity

Humans don't think of colours as numbers. It's difficult to compare several colour numbers and know whether they will make a matching set.

Humans (at least English-speaking ones) tend to think of three aspects of colour. The technical terms for these are hue, saturation and luminosity, but you might prefer to think of colour, clarity and darkness.

Hue refers to the basic colours of the rainbow, cycling through red, orange, yellow, green, cyan, blue, magenta and back to red again.

Saturation means how much white has been added. The more white you add to a colour, the more "washed out" it looks. Saturated colours grab your attention. Unsaturated colours are restful pastel shades.

Luminosity refers to how much light you can see. As you add more black to a colour, so its luminosity decreases.

You can think of colour space as being cylindrical. Imagine a or disc with all the bright, saturated colours around the edge. There are 360° in a circle. You can think of redas being at 0°, green at 120° and blue at 240°. The colour ,magenta, halfway between blue and red will be at 300°.

You can imagine that as you move towards the centre of the disc, at the point where all the colours add together to become white light. And you can imagine many discs piled on top of each other, with top one being full of light and the bottom one being in total darkness. Every point on the disc at the bottom will be black.

A colour wheel with saturated colours on the circumference and white in the middle.
Figure 61. A colour wheel with saturated colours on the circumference and white in the middle.

The hsl format describes colours in terms of:

  • Hue: a rotation around the circle, from 0° up to (but not including) 360°
  • Saturation: a value of clarity from 0% (a shade of grey) to 100% (a pure colour)
  • Luminosity: a value of brightness from 0% (totally black) to 100% (bright)

The Google Chrome colorpicker shows the colour space as a series of knife cuts through a cylindrical cake: white at the top left, at the point where the knife will start cutting from the centre of the cake; saturated colours at the top right, at the outside edge of the cake; and black at the bottom. The vertical strip on the right represents a ribbon around the circumference of the cake, showing the colours at the top circular edge.

The colorpicker displays colours in terms of hue, saturation and luminosty
Figure 62. The colorpicker displays colours in terms of hue, saturation and luminosty
hsla: Hue, Saturation, Luminosity and Alpha
As with the RGB format, you can add an alpha value between 0.0 and 1.0 to set the opacity of a colour defined with hue, saturation and luminosity.
Use the alpha slider to set the opacity of the colour.
Figure 63. Use the alpha slider to set the opacity of the colour.

Luminosity changes diagonally, from the bottom right to the top left. At the top and the right edge of the colour slice, saturation is considered to be 100%. At the top right hand corner, saturation is at 50%: there is neither extra darkness nor extra light.

You might find the Hue, Saturation Luminosity method of describing colours the easiest to visualize.
  • To create a page with co-ordinated colours, you can pick one value for hue, and vary the values for saturation and luminosity.
  • To create a page with bright, vibrant colours, you can set saturation to 100 and luminosity to 50, and vary the hue.
  • To create a page in pastel colours, choose a fairly low value for saturation (such as 25) and a fairly high value for luminosity (such as 75), and vary the hue.

You can use the colorpicker in the Elements Inspector to pick a colour, and then copy its definition and paste it into your CSS file.

Opacity

You can use the alpha value in the rgba and hsla colour formats to change the opacity of an area of colour. You can also use the opacity attribute to change the opacity of an entire element, such as an image.

To give you a taste of the JavaScript that you will be writing soon, try this in the Developer Tools window:

  • Select the Console tab at the bottom of the Developer Tools window and open it up to give yourself room.
  • You'll see a > prompt symbol at the top of the Console pane. Click just to the right of it and enter the following JavaScript code:
    document.getElementsByTagName("img")

    In JavaScript, it's important to get the case of every letter right, so you might prefer to copy and paste the code above.

  • Press the Enter key.
  • You should see an array of all the img elements in your index.html page.
  • The > prompt symbol will now appear beneath the output.
  • Press the Up arrow, to make the last command that you typed reappar after the prompt.
  • Add the following text at the end of the command:
    document.getElementsByTagName("img")[0].style.opacity = 0.25
  • Press the Enter key
  • Look at the main browser window for your index.html page. The first match, and the padding around it, should have become semi-transparent.
Using the Console to run JavaScript commands
Figure 64. Using the Console to run JavaScript commands

This is just a preview of what JavaScript can do. You will find explanations for this on page 19: JavaScript.

Setting the background-color for your page

Now that you know how to define colors, perhaps you'd like to change the background colour of your page. You can choose whatever colour and use whichever format you prefer to define it. For my version of the game, I have used the simplest way to say "black":

Make the following change to your style.css file, save your changes, then refresh your browser.

body {
  font-size: 10px;
  background-color: black;
}

.matches {
  text-align: center;
}
.matches img{
  width: 1em;
  height: 10em;
  padding: 1em;
  background-color: gray;
  border-radius: 1.5em;
}
Setting the background color
Figure 65. Setting the background-color of the page

Text color

Oops. Black text on a black background is just a little bit too Ninja to be readable. You can set the color of all the text in your web page by making the change shown below. You can, of course, use whatever colour you want

body {
  font-size: 10px;
  background-color: black;
  color: rgb(255,255,255);
}

.matches {
  text-align: center;
}
.matches img{
  width: 1em;
  height: 10em;
  padding: 1em;
  background-color: gray;
  border-radius: 1.5em;
}
Setting the color for all text
Figure 66. Setting the color for all text on the page

There are 2 things to notice here.

  1. All other -color attributes begin with the name of the part whose colour you want to change: background-color, border-color, border-top-color and so on. For text, there is no text-color attribute. The attribute name is just plain color.
  2. You are not changing the color of the text in the h1 and h2 elements directly. You are changing the color of the body, which is the ultimate parent of every other element on the page. The h1 and h2 elements (and all other elements) inherit the value of color, unless you set it directly for a specific element and its children.
It's time to commit your latest changes to your local repository:
git commit -am "Set background-color and color for the entire page"

When you type JavaScript code in the Console you get an error message, or nothing happens

  • JavaScript is case-Sensitive. This means that the words "case", "Case" and "CASE" (for example) have different meanings. You must be careful to use the write case for every letter that you type.

If this doesn't solve your problem, please tell us what happened and we'll do our best to find a solution for you.

CSS interactions

In this step, you'll learn how to:
  • Change the appearance of the cursor
  • Change the value of an attribute when the mouse is over an element
Download the source files Test here

When you move your cursor over a link on a web page, the cursor changes to a pointing finger, and often the link changes colour. You can make this happen when the cursor moves over a match image, too. Changing the cursor image is easy. You can do it with one line of CSS:

body {
  font-size: 10px;
  background-color: black;
  color: rgb(255,255,255);
}

.matches {
  text-align: center;
}
.matches img{
  width: 1em;
  height: 10em;
  padding: 1em;
  border-radius: 1.5em;
  background-color: gray;
  cursor: pointer;
}
The cursor image changes to a pointer over the match images
Figure 67. The cursor image changes to a pointer over the match images

Changing the background-color is as easy as creating a new rule, and moving one line of code. I've also taken the opportunity to choose a new value for the background-color. You can, of course, choose whichever colour you prefer. Remember to delete the line background-color: gray; from the existing .matches img { } rule.

body {
  font-size: 10px;
  background-color: black;
  color: rgb(255,255,255);
}

.matches {
  text-align: center;
}
.matches img{
  width: 1em;
  height: 10em;
  padding: 1em;
  border-radius: 1.5em;
  background-color: gray;
  cursor: pointer;
}
.matches img:hover{
  background-color: #321;
}
Using the :hover pseudo element as a selector
Figure 68. Using the :hover pseudo element as a selector

The new rule may be short, but the concept is powerful. I recommend that you read the explanation on pseudo elements in the Learn More About... section below.

Remember to commit your changes to your local Git repository. If you need a reminder of the commands to use in the Terminal, hover your mouse over the space below:
git status
git add -A
git commit -m "Add cursor: pointer and an img:hover rule"

(This gimmick uses the technique that you have just learnt.)

The cursor attribute

You can find more about the cursor attribute, the values it can take, and which browser support which values on these sites

  • w3Schools
  • Mozilla

The :hover selector

The :hover selector is known as a pseudo element. The rule that follows it will only be activated if the mouse is over an element that matches the rest of the selector. Here, you have a CSS rule that creates an interaction with the mouse, without using an programming language.

You'll see the use of a semi-colon : as part of a selector two more times in this tutorial:
  • With the :not( ) logical operator, to stop changing the background-color for a match that has been removed
  • With the :checked status for a checkbox, when you are ready to show and hide the rules.

For more information, see:

  • w3Schools
  • Mozilla

:pseudo elements

::pseudo classes

The background-color around all the matches doesn't go away.

  • Perhaps you didn't remove the line background-color: gray; from the .matches img{ } rule.
  • Perhaps your browser has decided to keep using an outdated version of your style.css stylesheet. You can follow this link to learn how to clear the cache for your browser.

If this doesn't solve your problem, please tell us what happened and we'll do our best to find a solution for you.

Merging two branches with Git

In this step, you'll practise using the following Git commands:
  • git diff other-branch
  • git merge other-branch

On page 13: Reacting to the mouse, you tested a CSS file before you had had the chance to understand how it worked. Since then, you have:

  • Created a separate rollover branch in your repository
  • Reverted to an earlier version of the project
  • Added new CSS rules that make your project behave the same way that the preview version did.

How close is your current version to the one you tested earlier? To find out, you can run the git diff command in the Terminal.

You might like to start by ensuring that your Terminal is pointing at the nim folder where your project files are stored. Your command might look something like this...
cd C:\Users\james\nim\gh-pages

... but your own path will be different from the red section above, and it will use forward slashes / if you are not working on Windows.

git diff

In the Terminal window, run the following command:

git diff gh-pages

This will show you all the differences between your current branch (rollever) and the gh-pages branch. If there are no differences, congratulations! You have followed my instructions to the letter! (Or you have skipped most of the last 5 pages, or you haven't been inspired to choose colours of your own).

The chances are, though, that you might see something like this:

git diff rollover gh-pages
diff --git a/css/style.css b/css/style.css
index 6881fbd..b64f124 100644
-- a/css/style.css
++ b/css/style.css
@@ -8,12 +8,12 @@ body {
   text-align: center;
 }
 .matches img{
+  padding: 1em;
   width: 1em;
   height: 10em;
-  padding: 1em;
   cursor: pointer;
 }
 .matches img:hover{
-  background-color: #321;
+  background-color: hls(60, 90, 30);
 }
\ No newline at end of file

The output above says:

  • Two lines were removed, two lines were added
  • The line padding: 1em; was moved from one place to another
  • The line background-color: #321; was changed to background-color hls(60, 90, 30);

Look at your output carefully. If everything you see is meaningful, and what you would expect, you can proceed to merge the two files.

Merging your changes into your main branch

First, you want to get back onto your main gh-pages branch, then you want to merge the gh-pages branch with your rollover branch. Here's what you can do:

git checkout gh-pages
Switched to branch 'gh-pages'

git merge rollover
Updating 8439f0a..32f3995
Fast-forward
 css/style.css | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

git diff rollover

In my case, the last command produced no output. This is because the rollover branch and the gh-pages branch are now identical.

Checking your history

You can also check that the history in the gh-pages branch now includes all the changes you made in the rollover branch, using the git log --oneline command. Here's my output, with the commits made in the rollover branch shown in bold:

git log --oneline
32f3995 Add cursor: pointer and an img:hover rule
4f37955 Set background-color and color for the entire page
e2b3e69 Add padding, border-radius and background-color
7217eb8 Set body font-size in px and match dimensions in em
3e9c2b9 Revert "Preview of rollover effect"
8439f0a Preview of rollover effect
ad5e390 Add .matches class to designate the divs to be centered
656e9b6 Add css/style.css to centre the matches
7c1cdc9 16 matches arranged in 4 rows, using <div>
4d68023 Add img/match.png; modified index.html to show this image
b6d433d 'Hello World' becomes 'The game will go here'
e6b50f7 Initial commit to the gh-pages branch

Reload your local index.html file in your browser. Roll your mouse over the images. Check that it's all still working.

Deleting a branch

Now that your gh-pages branch is up-to-date, you no longer need the rollover branch. You can delete it. In your Terminal window, type the following command:

git branch
* gh-pages
  rollover

In the output, the asterisk shows which branch is currently checked out. You can also use the -v (verbose) option to show the name and message from the most recent commit on each branch, to check that both branches are at the same point:

git branch -v
* gh-pages 32f3995 Add cursor: pointer and an img:hover rule
  rollover 32f3995 Add cursor: pointer and an img:hover rule

The name of the commit will be different for you.

To delete the rollover branch, you can use the -d (delete) option, followed by the branch name:

git branch -d rollover
Deleted branch rollover (was 32f3995).

If your run the simple branch command again, you should see that your repository tree has been trimmed:

git branch
* gh-pages

Making the most of branches

You have now seen the whole life cycle of a branch: how to create a new branch, how to use it to make changes without changing the anything in the stable main branch, how to merge it back into the main branch, and how to delete it when you no longer need it.

You can feel comfortable now about using branches. You can create a new branch whenever you start a new episode in your development process. You can create a new branch whenever you want to try out a new idea.

You have now successfully treated another issue from your list: Show the player that the images are interactive. You can close this on your Issues page on GitHub.

Adding JavaScript to your game

In this step, you'll learn how to:
  • Add JavaScript to your HTML page
  • Show an alert when the page is loaded
Download the source files Test here

JavaScript adds magic to your game... or at least the illusion of magic. Each of the ideas that you are about to learn is simple enough in itself. The power of what you are learning comes from the way you can combine these new ideas.

Each time you learn something new, you can create a new branch in your repository and use it to experiment with the new idea. You can imagine a simple activity of your own, and test how far you can take it.

As your skills and knowledge increase, you can return to branches that you worked on earlier, and move them forward again. Perhaps some of your branches can become the starting point for a whole new project.

To keep your project tidy, you should store all your JavaScript in a separate folder. In your text editor, create a new document, and save at as nim.js in a folder called js, alongside your index.html file. Your project folder should now look like this:

The img, css and js folders in the same folder as your index.html file
Figure 69. The img, css and js folders in the same folder as your index.html file

Hello World in JavaScript

In your nim.js file, add the following code:

alert("Hello World")

This is just to test that JavaScript is working for you.

Now you need to tell your index.html file that your new JavaScript file exists. At the end of your index.html file, make the following change:

...
    <div class="matches">
      <img src="img/match.png" alt="match 1 row 4" />
    </div>

    <script src="js/nim.js"></script>
  </body>
</html>

Save both files and refresh the index.html page in your browser. If all goes well, you should see something like this:

A Hello World JavaScript alert
Figure 70. A Hello World JavaScript alert

If you don't see this alert dialog, then check the Troubleshooting section below.

Why the script element is placed at the bottom of the body

Browsers read an HTML page from top to bottom. Normally, you want the browser to know about all the content (text and images) before you ask JavaScript to do something with this content. The simplest way to do this is to put your JavaScript declarations at the end of the page, just before the closing </body> tag.

Why the script element is empty

You can use a <script></script> element in two different ways:

  • To place JavaScript code inside your HTML page itself, like this:
    <script>alert ("I'm code in the HTML page")</script>
  • To link to an external file which contains code, like this:
    <script src="http://example.com/test.js" ></script>

In this second case, any code you put inside the tag will be ignored, so you might as well leave the tag empty.

Fetching the JavaScript file

When the browser reads the <script> tag, it doesn't know anything about the code in the external file yet. The JavaScript file is stored on a remote server, and the browser needs to send a request to the server to retrieve the file. When the contents of the file have arrived across the Internet, then the browser can start executing your JavaScript code.

Semi-colons? Really?

The official way to indicate the end of a command in JavaScript is to use a semi-colon, like this:

alert ("Hello World!");

In CSS, semi-colons are essential. If you don't include a semi-colon between rules, your CSS will break.

Fortunately, all modern JavaScript engines have an automatic semi-colon insertion system, which works, and which means that you can forget to put semi-colons at the end of your commands... except in certain very specific places.

Semi-colons in JavaScript generate powerful emotions in some developers. For some, they are a sign of purity; for others, a sign of wastefulness.

In this tutorial, I have deliberately chosen not to use semi-colons except where they are absolutely necessary. And when they are absolutely necessary, I put them at the beginning of a line, or in the middle of a line. I have done this for two reasons:

  • So that you can discover where semi-colons are absolutely necessary (and that's important)
  • So that you can see at a glance which code is JavaScript and which code is CSS (and that's just convenient)

When you are working on a project with others, the fact that you use or don't use semi-colons can be one way to distinguish your code immediately from the code of others, with all the shame and glory that that can bring.

A script-only HTML page

Since your browser will automatically add any missing <html> and </body> tags to your page, you can create a very simple HTML page just to test some JavaScript code. You might like to copy the HTML below and paste it into a new text document, save the document as test.html, then open it in your browser.

<script>
alert ("JavaScript says Hello!")
</script>

Be sure to give the file a .html extension, to trick the browser into treating it as a valid HTML file. If you use a different extension, you will simply see the text displayed in the browser window.

No Hello World alert appears when you refresh your index.html page.

  • Can you open your nim.js file directly in your browser? You should see the text that is in the file, but no dialog will appear. If this works, then you can be sure that your nim.js file exists.
  • From the browser address bar, copy the address of your nim.js file. It should be something like: file:///path/to/nim/gh-pages/js/nim.js
  • Copy the address for your index.html file from your browser's address bar. It should be something like: file:///path/to/nim/gh-pages/index.html
  • Compare these two addresses, and delete from the first address everything on the left that is identical in both cases. In the example I give, this would leave you with js/nim.js
  • Make sure that this is what you give as the value for the src attribute of the script element.

If this doesn't solve your problem, please tell us what happened and we'll do our best to find a solution for you.

Hiding an image

In this step, you'll learn to:
  • Create a CSS rule that makes a class of elements transparent
  • Use your browser Console to change the class of an element
Download the source files Test here

If you have read page 14: CSS units you already know at least two ways to make a match img element disappear. Think about it for a minute or two, and then roll your mouse over the space below to see a possible answer.

  1. Set the width of the element to 0
  2. Set the height of the element to 0

The second answer is the better, because it won't move the other matches. If you have been following closely, perhaps you will have thought of two more answers, one of which might be even better:

  1. Set the font-size of the element to 0, because its height and width are defined in em units, and they will both become 0
  2. Set the opacity of the element to 0, as you may have seen on page 16: CSS colors.

Perhaps you thought of right-clicking on the hidden answers, and looking in the Elements Inspector, to see how I had hidden them. If you did, congratulations! You have a smart and inquisitive mind. You explore how others achieve the effects that you want to achieve.

If you look closely at the display in the Elements Inspector, can you see what I did to set the opacity of the answers to 0.

Using a class and a CSS rule to set the opacity of an element
Figure 71. Using a class a CSS rule to set the opacity of an element

Oops! Did I just give away the answer? OK, so here are three new questions:

  • Can you edit your index.html file so that the last match img elements has a class called "removed"?
  • Can you edit your style.css file, so that it now contains a rule that gives all elements of the "removed" class an opacity of 0?
  • What selector will you need to use for this CSS rule?

Here's a clue for the HTML file:

    <div>
      <img src="img/match.png" alt="match 1 row 4" class="removed" />
    </div>

Here's a clue for the CSS file:

img.removed {
opacity: 0;
}

And here's a screenshot to show the changes that I made to my version. (I haven't made my match totally transparent, so that you can still see where it used to be).

Hard-coded changes to hide the last match
Figure 72. Hard-coded changes to hide the last match

Make sure that you make the following change to your style.css file before you continue. (Perhaps your colours will be different. That's fine.)

body {
  font-size: 10px;
  background-color: black;
  color: rgb(255,255,255);
}

.matches {
  text-align: center;
}
.matches img{
  width: 1em;
  height: 10em;
  padding: 1em;
  border-radius: 1.5em;
  cursor: pointer;
}
.matches img:hover{
  background-color: #321;
}
.matches img.removed {
opacity: 0.1;
}

Adding a class to an element

Your goal now is to make a match disappear when the user clicks on it. To do this, you will need to:

  • Discover which match element the user clicked on
  • Add the "removed" class to that element

I'll show you how to deal with this second issue first, and how to treat the first issue on the next page..

  • Right-click on the second match in your browser window and select Inspect Element in the contextual menu.
  • Make sure that the Console pane at the bottom of the window is visible. (Click on the >  button at the top right of the window, if you don't see a Console pane.)
  • Click just to the right of the > prompt character and type (or paste) this command:
    document.getElementsByTagName("img")[1].classList.add("removed")
  • Press Enter
  • Check that the second match has disappeared, and check the rules that have been applied to it, as shown in the screenshot below
Adding a class name to an element
Figure 73. Using classList.add() to add a class name to an element

Detecting a click on an HTML element

In this step, you'll learn how to:
  • Use the JavaScript debugger
  • Use JavaScript to detect a mouse click
  • Discover which element the player clicked
  • Use JavaScript to add a class to the chosen element
Download the source files Test here

For now, the alert command in your nim.js file runs automatically as soon as your page is loaded into the browser. If you make the following change, it will wait until you click somewhere on the page:

document.body.onclick = function (event) {
  alert("Hello World")
}

Save your nim.js file and refresh your page in the browser, then click anywhere on the page. The alert should not appear until you click.

Using the JavaScript debugger

The event argument can give you plenty of details about where and how the player clicked.

  • Right-click on the browser window, or use Ctrl-Shift-I / Cmd-Shift-I to open the Inspector
  • Select the Sources tab. In the column on the right, open the js folder and select nim.js. You should now see your JavaScript code in the central pane. The lines of code will be numbered.
  • In the grey bar on the right of the central pane, click on line number 2. A blue arrow should now appear. This indicates a breakpoint. When your JavaScript code is running, it will stop at this point and let you see what it has done and what it is thinking. From this point on, you will be able to ask your browser to execute just one line of code at a time.
The JavaScript debugger opens at a breakpoint
Figure 74. The JavaScript debugger opens at a breakpoint
  • In the Scope panel on the right, you can see the list of variables that JavaScript is currently using. One of these is called event. This is the event argument that you used in the first line of your code.
  • Click on the small disclosure triangle next to event, as shown in the Figure 74 above. You should now see a long list of the attributes of the event object.
  • Find the target attribute. If you clicked on a match, the Scope Inspector should indicate that the target is a img element. Click on the disclosure triangle next to it. You should now see even more details.
Inspecting the event object
Figure 75. Inspecting the event object
  • Now you should see that the img element has an attribute called classList, and that this is a DOMTokenList object with a length of 0. In plain language, this means that you have given no class attribute to this particular HTML element.
Discovering the classList attribute of an HTML object
Figure 76. Discovering the classList attribute of an HTML object

So what? What can you do with this classList object? (Clue: the answer is on the previous page.)

  • In the Console panel at the bottom of the Developer Tools window, you can type this line of code after the > prompt symbol:
    event.target.classList.add("removed")
  • Press the Enter key
  • What happened right there, in the main browser window? The match that you clicked on disappeared.
Using the JavaScript console during debugging
Figure 77. Using the JavaScript console during debugging
  • Click on one of the Resume script execution buttons... and the Hello World alert should now appear.
Click on a Resume button to let JavaScript run
Figure 78. Click on a Resume button to let JavaScript run

Editing your JavaScript file

In fact, you don't want to see an alert, but you do want to see the match that you clicked disappear. You can change your JavaScript so that it looks like ... Actually, why don't you make the change yourself, and test it, and then check if it looks like the code that I have hidden below?

document.body.onclick = function (event) {
  event.target.classList.add("removed")
}

(Remember to save your nim.js file and to refresh your browser.)

If you don't want the debugger to stop at your breakpoint every time, see the Tips and Tricks section below.

If you click on the Elements tab you will see that your new code has added a class="removed" attribute to the specific image that you clicked on. Click on another match. It will disappear too.

You're now ready to close issue n° 4: Hide each image when you click on it... but there are several new issues:
  1. What happened to the last match? Why is it always invisible now?
  2. How can you make all the matches reappear again, to start a new game?
  3. How do you prevent a player from removing matches from more than one row?
  4. How do you check that the current player has taken the last match, and has therefore lost the game?

You can visit your Issues page on GitHub to close one issue and open 4 more... or perhaps you can solve the first one immediately

Your project has now reached a new stable state. You can make a new commit and push your changes to your GitHub web site:
git status
git add -A
git commit -m "Add CSS 'removed' rule; JavaScript to detect a click"
git push origin gh-pages

If you don't want the debugger to open each time your JavaScript code runs, you can do one of four things:

  • Deactivate all breakpoints, by clicking on the toggle button at the top right of the window
    Deactivate all the breakpoints that you have set for this page
    Figure 79. Deactivate all the breakpoints that you have set for this page
  • Disable just this one breakpoint, by deselecting its checkbox in the Breakpoints section of the right-hand pane
    Disabling breakpoints one at a time
    Figure 80. Disabling breakpoints one at a time
  • Remove the breakpoint completely, by clicking on the grey vertical bar at line 2 in the central pane
    Removing a breakpoint permanently
    Figure 81. Removing a breakpoint permanently
  • Close the Developer Tools window. Your breakpoints will all still be exactly the way you left them, even after you relaunch your browser.

There is no Console pane in the Developer Tools panel

If this doesn't solve your problem, please tell us what happened and we'll do our best to find a solution for you.

Resetting the Game

In this step, you'll learn how to:
  • Remove a class from an element
  • Name a function
  • Create a variable
  • Select all HTML elements with a given class
  • Work with an array of elements
  • Repeat the same action on each element
Download the source files Test here

To add the class "removed" to an element, you used a command like element.classList.add("removed"). I'm sure you can make a good guess at what command you need to remove a class. Here's how you can test your guess:

  • In the browser's Development Tools panel, disable the breakpoint temporarily
  • In your main browser window, click on a match to hide it
  • Re-enable you breakpoint
  • Click on the match that you have just hidden
  • The debugger will open at the line that says event.target.classList.add("removed"), and the match will still be hidden
  • At the prompt in the Console pane, type the command that you think will show the match again
  • Press Enter

Here's how that looks for me, just before I press Enter.

removing a class from an element from the console
Figure 82. Removing a class from an element from the console

Oops, did I make the command a little difficult to read? Is this better?

event.target.classList.remove("removed")

Did your match reappear?

If you want to see what happens when you execute this command when the debugger is not halted at this breakpoint, see the Breaking Things On Purpose section below.

Creating an array of removed elements

When the game is over, all the matches will have had the removed class attached to them. You will want to make all these matches reappear. First, you need to make an array of all the removed matches.

You've already used the .matches class in your style.css file, to select all the match images. Perhaps you could use it again now, to get JavaScript to select all the matches. Type this after the Console prompt:

document.getElementsByClassName("matches")

You should see an array of elements printed out into the Console. However, this is not exactly what you need. You need an array of the img elements inside the match <div>s. For that, you need a different command:

document.querySelectorAll(".matches img")

Try it: it works exactly the same way as a CSS selector, from inside JavaScript.

querySelectorAll() acts exactly like a CSS selector from within JavaScript
Figure 83. querySelectorAll() acts exactly like a CSS selector from within JavaScript

Applying the same code to each element in an array

One of the things you will do very often in JavaScript is to iterate through a sequence of items. There are several ways you can do this in JavaScript. Here is some code to add to your nim.js file, that uses the most basic technique:

document.body.onclick = function (event) {
  event.target.classList.add("removed")
}

function reset() {
  var matches = document.querySelectorAll(".matches img.removed");

  for (var ii=0; ii<matches.length; ii++) {
    var match = matches[ii];
    match.classList.remove("removed");
  }
}

This code creates an array of img elements which have the "removed" class, and then uses a standard for loop to remove the "removed" class from each element in this array.

See the Learn More About Loops section below if you want more information about how this works.

The ++ increment operator adds 1 to the variable it follows. For example, if ii is equal to 3, the expression ii++ makes it become 4.

Using the Console to reset the game

To test that this code works, you can use the Console. Click on one or more matches to make them disappear, and then type the command reset() after the prompt symbol, then press return:

Resetting the game from the Console
Figure 84. Resetting the game from the Console

There are several JavaScript commands that you can use to create an array of elements, so that you can treat them all together:

getElementsByTagName()
parentElement.getElementsByTagName() creates an array of all the elements that are the children of a given parent element, that have a given tag name, such as "img", "div" "h1". You can find more details on the Mozilla Developers site
getElementsByClassName()
parentElement.getElementsByClassName() creates an array of all the elements that are the children of a given parent element, to which you have given a specific class name, such as "matches". You can find more details on the Mozilla Developers site
querySelectorAll()
parentElement.querySelectorAll() creates an array of all the elements that are the children of a given parent element, which match a CSS selector, such as ".matches img". For this command, you must include the . dot in front of the class name, exactly as you would for CSS. You can find more details on the Mozilla Developers site

There are similar methods that return a single element, or the null object if no elements match:

querySelector()
This is like element.querySelectorAll(), except that it returns just the first element that matches the selector.You can find more details on the Mozilla Developers site
getElementById()
An id is similar to a class except that JavaScript will only recognize the first element on a given page with a given id. You can find more details on the Mozilla Developers site

For more information on how the for statetment creates a loop, you can follow this links:

JavaScript errors

I'm going to get you to do something wrong, on purpose.

  • If the JavaScript debugger is still stopped at the breakpoint, click on the Resume Script Execution button. Your match should disappear again, as the line of code in your nim.js file is executed.
  • Click on the Console, to make sure that it is listening to the keyboard
  • Press the Up arrow on your keyboard, to show the last command that you typed again
    event.target.classList.remove("removed")
  • Press Enter, to execute this line of code.

Here's the error message that the Console gives me:

An error caused by using a variable out of scope
Figure 85. An error caused by using a variable out of scope

When you click on a match and then pause your code at a breakpoint in the debugger, the event argument has a value. The JavaScript engine generates this value from all the information that is relevant to your click action.

When you execute the same code outside the context of a click action, the JavaScript engine has no reason to create an event object, so the variable event is undefined.

When everything works perfectly, you do not need to understand how it works. To understand something, it helps to break it apart and put it back together, seeing how all the pieces fit.

You do not become a good programmer by writing perfect code. You become a good programmer by exploring all the imperfections in your thinking. Each time your code breaks, you can seize a new opportunity to learn.

Creating a Reset Button

In this step, you'll learn:
  • How to create a button using HTML
  • Execute code when the user clicks on a button
Download the source files Test here

The Console is a good place to test your code, but you want people to be able to play your game without even knowing that the Console (or your code) exists. Fortunately, you can create a button and make it execute the reset() command with a single line of HTML

In your text editor, open your index.html file and edit it as follows:

<!DOCTYPE html>
<html>
  <head>
    <title>Nim</title>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    <h1>Nim</h1>
    <h2>The game that will beat you</h2>
    <div class="matches">
      <img src="img/match.png" alt="match 1 row 1" />
      <img src="img/match.png" alt="match 2 row 1" />
      <img src="img/match.png" alt="match 3 row 1" />
      <img src="img/match.png" alt="match 4 row 1" />
      <img src="img/match.png" alt="match 5 row 1" />
      <img src="img/match.png" alt="match 6 row 1" />
      <img src="img/match.png" alt="match 7 row 1" />
    </div>
    <div class="matches">
      <img src="img/match.png" alt="match 1 row 2" />
      <img src="img/match.png" alt="match 2 row 2" />
      <img src="img/match.png" alt="match 3 row 2" />
      <img src="img/match.png" alt="match 4 row 2" />
      <img src="img/match.png" alt="match 5 row 2" />
    </div>
    <div class="matches">
      <img src="img/match.png" alt="match 1 row 3" />
      <img src="img/match.png" alt="match 2 row 3" />
      <img src="img/match.png" alt="match 3 row 3" />
    </div>
    <div class="matches">
      <img src="img/match.png" alt="match 1 row 4" />
    </div>
    <button type="button" onclick="reset()">Reset</button>
    <script src="js/nim.js"></script>
  </body>
</html>

Save your file and refresh your browser. You should see a new button named Reset. Click on one or more of the matches, and then on the Reset button. The matches that you clicked on should now reappear.

You can discover how this new line works by reading the optional sections below. You'll be adding four more buttons like this in the following pages, and using CSS to lay them out neatly.
In the Terminal window, you can push your changes to the GitHub repository. If necessary, first use the cd /path/to/your/local/repository command to point to the right folder.
git status
git add -A
git commit -m "Add Reset button"
git push origin gh-pages

You can now close one of the issues that you have created on GitHub: Make all the matches reappear again, to start a new game. You can visit your Issues page on the GitHub site, click on this issue, and then click on the Close Issue button. You're doing well.

Another of your open issues is Add buttons to restart the game. The button that you have just created deals with one essential part of this issue: it resets the layout of the matches. In the completed game, you will have three buttons:

  • One to start a two-player game
  • One to allow you to play first against the computer
  • One to make the computer play first against you

Each of these buttons will call this generic reset() method, and then do something specific. You might like to update this entry to indicate the progress that you have made.

When you're ready to continue, click on the Next arrow below, or on item 24: The game elements in the menu on the left.

You can discover more about HTML buttons on the following sites:

You can discover more about the onclick event attribute at the following sites:

You may notice that the Mozilla page gives several reasons why you should avoid using onclick. On page 26: Whose turn is it? you will see an alternative method.

The Game Elements

In this step, you'll practice:
  • Creating <div> elements to separate your page into distinct sections
  • Creating button elements
  • Adding descriptive classes to elements, so that they can be identified later in JavaScript
Download the source files Test here

Now that you know how to create a button, you can create all the buttons you will need to control the game:

  • A button for each player, to say "I've finished my turn"
  • A button to reset the matches in their original positions, so that you can play a game against another human player
  • A button that will reset the game so that you can start a game against the computer
  • A button to reset the game and make the computer start to play against you

In this step, you can focus on adding the buttons (with HTML). In the next step, you can focus on placing them neatly on the screen (with CSS). Finally, you can add start adding the JavaScript that will give these buttons their purpose.

There are several changes that you need to make to your index.html file. To make it simpler to understand them, I'll explain each change separately, and then provide you with the complete code for the HTML file. I recommend that you make one change at a time, and check the result in your browser. If something goes wrong, you can replace all your HTML with the final version that I give at the end.

When you are about to make a series of changes that might go wrong, it's good to create a new branch with Git. In the terminal window, use the cd  command to navigate to the folder that holds your local Git repository, then use the following commands to create a new branch. You can call the new branch "gameplay":
git checkout -b gameplay
Switched to a new branch 'gameplay'

When you have a two-player game working correctly, you can merge this branch back into the gh-pages branch, and push the working game to the GitHub server. In the meantime, all your changes will be local.

Separating the game from the Start buttons

To make the structure of the page clear, it's good to create separate sections for the elements that control the game itself and the buttons that restart the game. To do this, you can create one <div> element that surrounds the matches elements, a second <div> element into which you can place all elements to say whose turn it is, and a third <div> element into which you can place all the buttons for restarting the game. You can give each of these new <div> elements a specific class, so that it is easy to distinguish them.

In the code listing below, the ... ellipsis dots indicate HTML code that you already have, but which has been omitted here for clarity.

<!DOCTYPE html>
<html>
  <head>
    <title>Nim</title>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    <h1>Nim</h1>
    <h2>The game that will beat you</h2>
    <div class="game">
      <div class="matches">
        ...
      </div>
    </div>

    <div class="turns">
      <!-- elements to say whose turn it is will go here -->
    </div>

    <div class="start">
      <button type="button" onclick="reset()">Reset</button>
    </div>

    <script src="js/nim.js"></script>    
  </body>
</html>

These changes will have little or no effect on the appearance of your page. If you see no changes when you save your index.html file and reload it in your browser, that is quite normal.

The Start buttons

Inside the new "start" <div> elements, you can replace your existing Reset button with HTML code for the three different buttons that will restart the game in different ways. Once again, you can give a specific class to each button to identify it.

Note that none of these new buttons has an onclick="reset()" attribute, so for now these buttons will do nothing when you click on them. You'll still be able to make a match vanish when you click on it, but you will need to reload the whole page to make it reappear again.

At this step, you are just concerned with placing the buttons on your web page. I'll explain how to restore this functionality at step 35: Two-player game, but you might like to fix this before then. You'll find a new method for doing this in step 26: Whose turn is it?

    ...
    <div class="start">
      <button type="button" onclick="reset()">Reset</button>
      <button type="button" class="playerStarts">
        Start new game against computer
      </button>
      <button type="button" class="computerStarts">
        Computer to start new game
      </button>
      <button type="button" class="twoPlayers">
        Start new 2-player game
      </button>
    </div>
    
    <script src="js/nim.js"></script>  
  </body>
</html>

Save your HTML file and reload it in browser. You should see 3 buttons that appear after the matches.

Three buttons for restarting the game
Figure 86. The three buttons for restarting the game are currently inactive

Showing whose turn it is

You also need a way to show the player(s) whose turn it is, and to allow the current player to indicate that s/he has finished removing matches.

You can give the two players the classes "player1" and "player2", and the names "You" and "Computer". If the game is to be between two human players, then you can use JavaScript later to change these names to "Player 1" and "Player 2".

Note that only one player will be active at a time. You can add an "active" class to the first player. In the next step, you can use CSS to give the player's name a distinctive colour, and to hide the "Done" button for the other player.

   ...

    <div class="turns">
      <div class="player1 active">
        <button type="button">Done</button>
        <p>You</p>
      </div>
      <div class="player2">
        <button type="button">Done</button>
        <p>Computer</p>
      </div>
    </div>

    ...
The elements that show whose turn it is
Figure 87. The elements that show whose turn it is

The complete HTML

For reference, here's how your index.html file should look now:

<!DOCTYPE html>
<html>
  <head>
    <title>Nim</title>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    <h1>Nim</h1>
    <h2>The game that will beat you</h2>
    <div class="game">
      <div class="matches">
        <img src="img/match.png" alt="match 1 row 1" />
        <img src="img/match.png" alt="match 2 row 1" />
        <img src="img/match.png" alt="match 3 row 1" />
        <img src="img/match.png" alt="match 4 row 1" />
        <img src="img/match.png" alt="match 5 row 1" />
        <img src="img/match.png" alt="match 6 row 1" />
        <img src="img/match.png" alt="match 7 row 1" />
      </div>
      <div class="matches">
        <img src="img/match.png" alt="match 1 row 2" />
        <img src="img/match.png" alt="match 2 row 2" />
        <img src="img/match.png" alt="match 3 row 2" />
        <img src="img/match.png" alt="match 4 row 2" />
        <img src="img/match.png" alt="match 5 row 2" />
      </div>
      <div class="matches">
        <img src="img/match.png" alt="match 1 row 3" />
        <img src="img/match.png" alt="match 2 row 3" />
        <img src="img/match.png" alt="match 3 row 3" />
      </div>
      <div class="matches">
        <img src="img/match.png" alt="match 1 row 4" />
      </div>
    </div>

    <div class="turns">
      <div class="player1 active">
        <button type="button" disabled="">Done</button>
        <p>You</p>
      </div>
      <div class="player2">
        <button type="button" disabled="">Done</button>
        <p>Computer</p>
      </div>
    </div>

    <div class="start">
      <button type="button" class="playerStarts">
        Start new game against computer
      </button>
      <button type="button" class="computerStarts">
        Computer to start new game
      </button>
      <button type="button" class="twoPlayers">
        Start new 2-player game
      </button>
    </div>
    
    <script src="js/nim.js"></script>  
  </body>
</html>

The Game Layout

In this step, you'll learn how to:
  • Set the width of an element relative to the width of its parent
  • Limit the width of the body element
  • Place two elements side by side
  • Add padding or a margin above and below an element
  • Hide an element if does not belong to a particular class
Download the source files Test here

By default, each button is just wide enough to show the text it contains. It would look neater if all the buttons were the same width.The easiest way to do this is to set each button to be the same width as its parent element. You can add this rule to your style.css file:

button {
  width: 100%;
}

However, you have not yet set the width of any element in your page, so all elements will inherit the width of the browser window. This may mean that the buttons are unnaturally wide. You can change this by setting the width of the <body> element. If you make the following changes to your style.css file, then game will appear tall and thin and centred in the browser window

body {
  font-size: 9px;
  background-color: black;
  color: rgb(255,255,255);
  width: 25em;
  margin: 0 auto;
}

.matches {
  text-align: center;
}
.matches img{
  width: 1em;
  height: 10em;
  padding: 1em;
  border-radius: 1.5em;
  cursor: pointer;
}
.matches img:hover{
  background-color: #321;
}

img.removed {
opacity: 0;
}

button {
  width: 100%;
}
Setting the width of all the buttons
Figure 88. Setting the width of all the buttons

Placing elements side by side

By default, all <div> elements are stacked vertically. It would look better to place the "player1" and "player2" <div>s, inside the "turns" <div>, side by side. This is because the browser's default stylesheet (user agent stylesheet) sets the display style attribute of all <div> elements to block:

The default display setting for divs is block
Figure 89. The default display setting for <div> elements is block

To display the "player" div elements side by side, you can set the value of their display attribute to inline-block, and their width attribute to 49% of the parent "turns" div. (Actually, 50% would make more sense but you'll need to make an adjustment to the HTML before this will work.) Add this to your style.css file, save your file then refresh your browser.

.turns div {
  width: 49%;
  display: inline-block;
}
Placing two divs side by side
Figure 90. Using { display: inline-block } to place two <div> elements side by side

In 15: The CSS box model, you've already seen the reason why a width of 50% will not work: it's a question of whitespace in the HTML file. You can find more information about this in the Tips and Tricks section below.

Applying a rule when a class is missing

To show whose turn it is you can do two things:

  • Emphasize the text that shows the current player
  • Show the "Done" button only for the current player and remove it for the other player.

Here are a couple of rules that do this:

.active {
  color: #fc9;
  font-weight: bold;
}
.turns :not(.active) button {
  display: none;
}

Note the :not(.classname) selector. The complete selector .turns :not(.active) button means (reading right to left) "All buttons that are children of an element which does not have the class "active", and where this element is itself a child of an element with the class "turns".

Revised CSS

Here's a new version of the style.css. It includes some additional changes (in red) that should already be familiar to you.

body {
  font-size: 9px;
  background-color: black;
  color: rgb(255,255,255);
  width: 25em;
  margin: 0 auto;
}

.matches {
  text-align: center;
}
.matches img{
  width: 1em;
  height: 10em;
  padding: 1em;
  border-radius: 1.5em;
  cursor: pointer;
}
.matches img:hover{
  background-color: #321;
}

img.removed {
  opacity: 0;
}

button {
  width: 100%;
}

div.turns {
  padding: 2em 0;
}
.turns div {
  width: 50%;
  display: inline-block;
  font-size: 2em;
  text-align: center;
}
.turns p {
  margin: 0.25em 0;
}
.active {
  color: #fc9;
  font-weight: bold;
}
.turns :not(.active) button {
  display: none;
}

Here's how your new buttons and text elements should look after you make these changes, save your file and refresh your browser.

Buttons and text elements laid out using CSS
Figure 91. How your buttons and text elements look after you have applied your CSS rules
You can commit your latest changes to your gameplay branch:
git status
git add -A
git commit -m "Add Player and Start buttons; Create layout"

Whitespace and 50% width

If you want the "player" <div> elements to appear side by side, both with a width of 50%, then you will need to make sure that there is no whitespace between them in your index.html file. If you followed my earlier instructions, here's what your HTML currently looks like:

   ...
      <div class="player1 active">
        <button type="button" disabled="">Done</button>
        <p>You</p>
      </div>
      <div class="player2">
        <button type="button" disabled="">Done</button>
        <p>Computer</p>
      </div>
      ...

Between the closing </div> tag and the following opening <div> tag there is a new line character. This is displayed in your browser as a single space, like this: </div> </div>

To eliminate this whitespace, you need to run the two lines together, with no space at all between them:

   ...
      <div class="player1 active">
        <button type="button" disabled="">Done</button>
        <p>You</p>
      </div><div class="player2">
        <button type="button" disabled="">Done</button>
        <p>Computer</p>
      </div>
      ...

Whose Turn Is It?

In this step, you'll learn how to:
  • Add a nextTurn() method to change which player is active
  • Add an initializeButtons() method to tell the "Done" buttons to call the nextTurn() method, when they are clicked.
  • Ensure that the initializeButtons() is executed when your web page first loads
  • Modify the existing reset() method so that it activates player 1, when the game begins.
Download the source files Test here

As you did earlier, you can first add a new JavaScript function to your nim.js file, then test it using the Console. When it works the way you expect it to, you can add another JavaScript function to make it work from the web page itself, without resorting to the Console.

Moving the active class from one player to the other

Add this to your nim.js file, save the changes and refresh your browser:

document.body.onclick = function (event) {
  event.target.classList.add("removed")
}

function reset() {
  var matches = document.querySelectorAll(".matches img.removed");

  for (var ii=0; ii<matches.length; ii++) {
    var match = matches[ii];
    match.classList.remove("removed");
  }
}

function nextTurn() {
    var active = document.querySelector(".active");
    var next = document.querySelector(".turns div:not(.active)");

    active.classList.remove("active");
    next.classList.add("active");
}

Now you can open the Console and type nextTurn() after the > prompt, and press return. Press the up arrow to make the command reappear, and press return again. Each time you do this, the highlight should shift to the name of the other player, and the "Done" button above the player that is now active should be the only one visible.

Executing the nextTurn() function from the Console.
Figure 92. Executing the nextTurn() function from the Console.

Here's what this code does:

  • It creates a variable called active to refer to the first (and only) element that currently has the class "active"
  • It creates a variable called next to refer to the first (and only) <div> that is a child of an element with the "turns" class, that does not currently hav the class "active". Note that the selector needs to be specific about what type of element this non "active" element is, and where it is to be found, because all the elements except one have no "active" class.
  • It removes the "active" class from the current active element
  • It adds the "active" class to the element identified as next.

The <div> elements identified as active and next alternate each time the function is called.

Triggering nextTurn() when you click Done

In step 23: The Reset button, you used HTML to tell the Reset button to call a particular JavaScript function, like this:

  <button type="button" onclick="reset()">Reset</button>

It is good to keep HTML and JavaScript separate. Here's how you can use JavaScript to add an onclick attribute to the two Done buttons:

document.body.onclick = function (event) {
  event.target.classList.add("removed")
}

function reset() {
  var matches = document.querySelectorAll(".matches img.removed");

  for (var ii=0; ii<matches.length; ii++) {
    var match = matches[ii];
    match.classList.remove("removed");
  }
}

function nextTurn() {
    var active = document.querySelector(".turns div.active");
    var next = document.querySelector(".turns div:not(.active)");

    active.classList.remove("active");
    next.classList.add("active");
}

function initializeButtons() {
  var buttons = document.querySelectorAll(".turns button");
  var button

  for (var ii=0; ii<buttons.length; ii++) {
    button = buttons[ii];
    button.onclick = nextTurn;
  }
}

initializeButtons()

The function initializeButtons() creates an array from the two buttons that are children of the "turns" <div>., and then uses a for (...) {...} repeat loop to set the onclick attribute for each button to nextTurn.

Note that nextTurn refers to the function as a whole, while nextTurn() is a command to execute all the code inside the function.

The function initializeButtons does nothing on its own: there has to be a command initializeButtons() that will make it execute.

Save the changes to your nim.js file, then refresh your browser. Now, when you click on the visible Done button, it will disappear to be replaced by the other, and the highlight will pass to the other player's name, because the "active" class is moving from one player <div> to the other.

Time to commit these changes to the gameplay branch on your local repository:
git status
git add -A
git commit -m "Add nextTurn() and initializeButtons()"

In the last couple of steps, you have:

  • Added HTML for the two Done buttons and the names of the players
  • Added CSS rules to display them appropriately.

In this step, you added JavaScript to make these buttons switch players. You have now completed one of the issues that you opened on your GitHub site: Add a button so that the current player can say "Your turn".

Now you can visit your Issues page on GitHub, click on the links to this particular issue, and close it. Another task completed.

Currently, the three buttons at the bottom of the page don't do anything. The last one should do two things:

  • Call reset()
  • Ensure that only the first player <div> element has the "active" class.

If you want to test your understanding of all you have learnt so far, you might like to edit the nim.js file to make this happen.

Before you start making your own experimental changes, I suggest that you:

  • Commit all the changes that you have made so far and push them to the remote server
  • Create a new branch, as you saw in step 5: Creating a branch
  • Use the new branch for your experimental work.

This way, if you get stuck, it is easy to use git revert as shown in step 7: Reverting changes, and to carry on with the tutorial from where you left off.

If you are successful in getting it to work, you can feel a glow of pride, and use git merge, as shown in step 18: Merging 2 Git branches to merge your experimental branch back into the currentgameplay branch, and continue from there.

In any case, I will provide you with all the details you need to succeed in this when you get to step 30: Two player game, so you have nothing to lose.

Enforcing the Rules

In this step, you'll learn about:
  • Creating variables that are shared between functions
  • Creating a separate function for each action
Download the source files Test here

Currently, each time you click on one of the Done buttons, your code checks which of the "player" <div>s currently has the "active" class, and which does not. Up until now, this information was just needed for the nextTurn function.

Your goal is this step is to enable the Done button for the active player only after the active player has clicked on a match. This means that you will be creating a new function which also needs to know which player <div> has the "active" class. A good way of treating this is to define theactive variable outside the nextTurn function, so that it will be accessible to other functions, too.

Making the active variable global

At the top of your nim.js file, add the following line:

var active

document.body.onclick = function (event) {
...

Now modify your initializeTurns function as shown below:

function initializeTurns() {
  var turnButtons = document.querySelectorAll(".turns button")
  var button

  for (var ii=0; ii<turnButtons.length; ii++) {
    button = turnButtons[ii];
    button.onclick = nextTurn;

    if (ii === 0) {
      active = button.parentElement
      active.classList.add("active");
    }
  }
}

When this function runs, the value of active will be set to the <div> with the class of "player1" (the parent of the first button found inside the "turns" <div>.

For details on the parentElement property, you can read the Learn More About HTML Properties section below.

For details on the === comparison, you can read the Learn More About Comparisons section below.

The line var active creates a global variable. This means that any function anywhere can get and set its value. This might seem convenient, but it can make your code behave in unexpected ways. You should use global variables as seldom as possible. In the next step, you will see how to limit the scope of the variable active.

Enabling the active player button when a match is removed

Here's the function that currently hides a match when you click on it:

document.body.onclick = function (event) {
  event.targetclassList.add("removed")
}

You could simply add the following line to this function...

active.classList.add("enabled")

... but it's better to do this more neatly, by dividing it into two parts, like this:

var active

document.body.onclick = hideMatch

function hideMatch(event) {
  var match = event.target;
  match.classList.add("removed")

  active.classList.add("enabled")
}

You'll soon see the advantage of having two distinct steps. This code, although it works, is flawed in two ways:

  • You can click on any object (not just a match) and the hideMatch function will be called
  • The player can currently hide matches from more than one row.

If you separate the function that decides which elements trigger the "hide match" feature from the function that actually performs the "hide match" code, then you can fix each of these issues separately, in a clean way.

Simplifying the nextTurn function

Now that the active variable is declared and set outside the nextTurn function, you can simplify this function. First you set the next variable to be refer to the "player" <div> that is not currently active, then you remove the "active" class from the active <div>, and then you change the value of the active variable so that it now refers to the next <div>. Finally, you add the "active" class to this other <div>.

function nextTurn() {
  var active = document.querySelector(".active");
  var next = document.querySelector(".turns div:not(.active)");
  active.classList.remove("active");
  active.classList.remove("enabled");

  active = next
  active.classList.add("active");
}

Save your nim.js file, refresh the browser and test what happens if you click on the Done button. Everything is exactly as it was before. You need to make one more change to force the current player to click on a match before the Done button will become active. Add the lines below to your nextTurn function, save and refresh and try again:

function nextTurn() {
  if (!active.classList.contains("enabled")) {
    return
  }

  var active = document.querySelector(".active");
  var next = document.querySelector(".turns div:not(.active)");
  active.classList.remove("active");
  active.classList.remove("enabled");

  active = next
  active.classList.add("active");
}

The ! operator means "not", so the expression !active.classList.contains("enabled") means "the list of classes for the active element does not contain the word 'enabled'". An expression that JavaScript can evaluate as true or false is called a Boolean expression, after the mathematician George Boole. To discover more about the logical operators such as !, you can read the optional Learn More About Logic section below.

Fixing a bug

Actually, it doesn't quite work does it? If you click twice on the Done button, the players will swap. In fact, if you click on any element (a match, one of the restart buttons, the word "Computer", the background, ...) and then on the Done button, the players will swap.

So there is a further check that you have to make: you need to check if the user is actually clicking on a match. Since the only <img> elements on your page are matches, you can use a shortcut and simply check if the element the user clicked on is an <img> element. Here's a change for you to make:

function hideMatch(event) {
  var match = event.target;
  if (match.nodeName !== "IMG") {
    return
  }
  
  match.classList.add("removed");

  active.classList.add("enabled")
}

For details on the nodeName property, you can read the Learn More About HTML Properties section below.

One bug can hide another

With a little thought, you may be able to find another way to swap players without removing any matches. You've made sure that the player clicks on a match. For the first player, all the matches are visible. But could the second player click on a match <img> element, without hiding a match?

Think about it for a moment. If you're stuck (or if you are sure that you have the right answer and you prefer copy-and-pasting rather than typing), roll the cursor over the space below to see a clue.

...

function hideMatch(event) {
  var match = event.target
  if (match.nodeName !== "IMG") {
    return
  } else if (match.className === "removed") {
    return
  }
  
  match.classList.add("removed")

  active.classList.add("enabled")
}

...

Make the suggested change to your nim.js file, save it, then refresh your browser. Check that the Done button will no longer appear until at least one match has been removed.

Using CSS to show when the Done button not enabled

There is a disabled attribute that you could apply to the Done button, but different browsers would change the appearance of the button in different ways. As you have seen, you can achieve the same effect using the check...

  if (!active.classList.contains("enabled")) {
    return
  }

... to prevent any further code from being executed. This also has the advantage that you can use the absence of the "enabled" class to set the appearance of the disabled button.

Add this rule at the end of your style.css file:

...

.turns :not(.active) button {
  display: none;
}
.active:not(.enabled) button {
  opacity: 0.2;
}

Note how the selector will choose any button which is a descendant of an element with an "active" class, so long as that element does not also have a "enabled" class. If you prefer, you can make the opacity 0 to hide the button completely.

Complete listing of the nim.js file

Here's what your nim.js file should look like now:

var active

document.body.onclick = hideMatch

function hideMatch(event) {
  var match = event.target;
  if (match.nodeName !== "IMG") {
    return
  } else if (match.className === "removed") {
    return
  }
  
  match.classList.add("removed");

  active.classList.add("enabled")
}

function reset() {
  var matches = document.querySelectorAll(".matches img.removed");

  for (var ii=0; ii<matches.length; ii++) {
    var match = matches[ii];
    match.classList.remove("removed");
  }
}

function nextTurn() {
  if (!active.classList.contains("enabled")) {
    return
  }

  var next = document.querySelector(".turns div:not(.active)");
  active.classList.remove("active");
  active.classList.remove("enabled");

  active = next
  active.classList.add("active");
}

function initializeTurns() {
  var turnButtons = document.querySelectorAll(".turns button")
  var button

  for (var ii=0; ii<turnButtons.length; ii++) {
    button = turnButtons[ii];
    button.onclick = nextTurn;

    if (ii === 0) {
      active = button.parentElement
      active.classList.add("active");
    }
  }
}

initializeTurns()
Time to commit these changes to your local repository:
git status
git add -A
git commit -m "Force player to take a match before clicking Done"

parentElement

You can read more about the parentElement property on the following sites:

nodeName

The element.nodeName property returns the UPPERCASE string that can be used to create an element of this type. So for, you have created the following elements:

  • <html> nodeName: HTML
  • <head> nodeName: HEAD
  • <title> nodeName: TITLE
  • <link> nodeName: LINK
  • <body> nodeName: BODY
  • <h1> nodeName: H1
  • <h2> nodeName: H2
  • <div> nodeName: DIV
  • <img> nodeName: IMG
  • <body> nodeName: BODY
  • <button> nodeName: BUTTON
  • <p> nodeName: P

You can read more about the nodeName property on the following sites:

You can read more about using else with if statements on these sites:

You can learn more about the logical operators (or, and, not, exclusive or) on these sites:

If you have any problems with this step, please tell us what happened and we'll do our best to find a solution for you.

Variables, functions, closures and scope

In this step, you'll learn about:
  • Global functions and variables
  • Scope
  • Immediately-invoked function expressions (IIFEs)
Download the source files Test here

Suppose something isn't working in your JavaScript code, and you decide to debug it. Suppose you want to look at the value of the active variable, just after it has been created. You've already seen how to create a debug breakpoint and to use the Scope inspector to look at the values of your variables. You can do that again now.

  • Open the Developer Tools panel at the Sources Tab
  • Select the Source pane in the column on the left
  • Open the js folder and select nim.js
  • In the code pane on the right, click on the line number opposite the line active.classList.add("active"), to create a breakpoint
  • Reload the page, so that the debugger stops at your breakpoint

Now if you look in the Scope pane, you should see the values of the following Local variables:

  • button
  • ii
  • this
  • turnButtons

But there is no sign of active. Why not?

window.active

The variables listed above are all local. That means that they were created inside the function that is currently being executed: initializeTurns. When the initializeTurns function completes what it has to do, the JavaScript engine will forget the values of these variables.

You have defined the variable active outside any function. When you do this, the JavaScript engine considers the variable to be a property of the global window object. To test this, type window.active in the Console. You should see the value of the variable displayed.

The active variable is a global, stored as a property of the window object
Figure 93. The active variable is a global, stored as a property of the window object

So many global variables

If you click on the disclosure triangle beside the word "Global" in the Scope pane, you will see the complete list of all the globals that the browser uses, in alphabetical order. Global variables whose names start with a capital letter will appear before those that start with a lowercase letter, so you'll need to scroll a long way before you find active.

Scrolling through all the globals
Figure 94. Scrolling through all the globals

The properties of window

Most globals are created by the browser, for its own housekeeping. Only a few are created as properties of the window object. All the globals that you create are properties of the window object. You can see all these variables by typing...

keys(window)

... after the > prompt in the Console:

Window properties
Figure 95. window properties

You might recognize some of these:

  • active
  • hideMatch
  • reset
  • nextTurn
  • initializeTurns

In addition to the active variable, these are the functions that you have created in your script.

Functions within functions

To take all these variables and functions out of the global window space, you can wrap your entire code in a function, like this:

;(function() {
  // All your existing code goes here
})()

You can see the full listing of your code wrapped in a function like this at the end of this page.

To test the effect of this, add these lines, one at the beginning and the other at the end of your script, save your nim.js file and refresh your browser. The breakpoint will still be on the same line number, but the code itself will have shifted. The easiest solution is to click on the Step Over button to execute the line at the breakpoint, so that active now has a value.

Using an immediately-invoked function expression to create a closure
Figure 96. Using an immediately-invoked function expression to create a closure

You should now see a new entry in the Scope pane if the Developer Tools: Closure. If you expand the entry for Closure, you should see the active variable. It is no longer listed with the globals, because it is now defined within the unnamed function that you have just created.

You can think of scope as being like the rooms in a house. The things in the room are private to the room: you can only use them when you are in that room. The room also has access to the electrical system and the WiFi that is private to the house. The house has access to public utilities, like the road system, and to global phenomena, like the weather.

Creating a function creates a closed space. You can create a function within a function: the inner functions have access to the information that is available to the outer function. Outer functions cannot see what is going on inside an inner function.

Closure and namespace

In the reset function, there is a variable called ii which is used for iterating through match <img> elements. In the initializeTurns function, there is another variable, also called ii, which is used to iterate through the "turn" <button> elements. Because these two variables are defined inside different functions, they refer to different things, just like the word "table" refers to a different piece of furniture when you are in your kitchen or a dining room or an office.

Functions and parentheses

In English, one word can have many different meanings. The distinction is clear from the context. For example can use the word "set" in the context of the sun, a tennis game, a table, concrete, and so on, and in each case, the meaning of the word "set" is quite distinct.

TIn JavaScript, round parentheses ( ) can have three different meanings, depending on the context.

1) Function declaration

You have already seen how to declare a function. In your code, you write:

function nameOfYourFunction(argumentName) {
  // The commands to execute go here
}

The name of the function is in fact optional. A function that has no name is called an anonymous function.

In the context of a function declaration, the parentheses after the name of the function mean "the words inside these parentheses are the names of variables that are given to this function when it is asked to execute its code". These argument variables are in the scope of the function.

You can refer to the function by using its name, without any parentheses. To test this:

  • Place a breakpoint on the line initializeTurns()
  • Refresh your page. The debugger will open inside the scope of the outer anonymous function.
  • After the > prompt in the Console, type initializeTurns. Do not include any parentheses.
  • The console will show you that the variable name initializeTurns refers to the function initializeTurns()
The function name, without parentheses, refers to the function itself
Figure 97. The function name, without parentheses, refers to the function itself

2) Executing a function

In the context where you want to execute the code inside a function, you write the function name followed by opening and closing parentheses. In this case, the parentheses have a different meaning. Here they mean "take the function with this name and execute the commands inside it." If you include any variabele names inside the parentheses, the value of these variables will be passed to the function as arguments to the function.

3) Evaluating items

A third way that parentheses can be used is to group items together, so that the expression inside the parentheses is evaluated. For example (5 - 4) * 3 means 1 * 3, while 5 - (4 * 3) means 5 - 12. The expression inside the parentheses is evaluated before any other operations are applied.

Immediately-invoked function expressions

The new function that you have created uses parentheses in all three ways:

  • It contains a pair of parentheses after the word function, as part of the function definition
  • It is wrapped in a pair of parentheses, to tell the JavaScript engine to evaluate it
  • It is followed by a pair of parenthesis, to tell the JavaScript engine to execute the evaluated function.

In other words, when the JavaScript engine encounters code in the form...

(function optionalFunctionName() {
  // code to execute
})()

... it immediately executes the code inside the function. This code structure is called an immediately-invoked function expression (IFFE, pronounced "iffy"). This is equivalent to:

function optionalFunctionName() {
  // code to execute
}

optionalFunctionName()

That semi-colon

You'll have noticed that I use a semi-colon as the very first character. If you read the other articles, you'll see that the writers put a semi-colon at the end... and also at the end of every line.

This is one of the few places where the lack of a semi-colon may cause the JavaScript engine to become confused. Perhaps you already know the joke: Time flies like an arrow, and fruit flies like a banana. But the JavaScript engine has no sense of humour.

If you put two immediately-invoked function expressions one after the other, without a semi-colon to separate them, the JavaScript engine will choose the wrong meaning for the parentheses around the second function expression. Instead of understanding it as "evaluate the expression inside these parentheses" (meaning 3), it will understand it as "execute the previously evaluated expression" (meaning 2), and will stop working.

You can test this, to see what will happen. Change the current initializeTurns function to an immediately-invoked function expression, and then add another IIFE right after it, with no semi-colon between them:

  ...

  ;(function initializeTurns() {
    var turnButtons = document.querySelectorAll(".turns button")
    var button

    for (var ii=0; ii<turnButtons.length; ii++) {
      button = turnButtons[ii]
      button.onclick = nextTurn

      if (ii === 0) {
        active = button.parentElement
        active.classList.add("active")
      }
    }
  })()

  (function () {
    alert ("Semi-colon required")
  })()

Now save your changes and refresh the browser. You should see an error reported in the Console, and no alert will appear.

One case where a missing semicolon will cause an error
Figure 98. One case where a missing semicolon will cause an error"

If you now add the semi-colon between the two IFFEs, save your changes and refresh your browser, no error will occur and the alert will show.

I put the semi-colon at the beginning of the IFFE, to tell other developers "I only use semi-colons where they are absolutely necessary, and one is necessary here." That's my coding style, and you are free to put semi-colons at the end of every line if you want to.

The complete code listing

The alert IFFE that you just added is not necessary. It was just used for demonstration purposes. You can delete it. Your code should now look like this:

new
;(function() {
  var active

  document.body.onclick = hideMatch

  function hideMatch(event) {
    var match = event.target
    if (match.nodeName !== "IMG") {
      return
    }
    
    match.classList.add("removed")

    active.classList.add("enabled")
  }

  function reset() {
    var matches = document.querySelectorAll(".matches img.removed")

    for (var ii=0; ii<matches.length; ii++) {
      var match = matches[ii]
      match.classList.remove("removed")
    }
  }

  function nextTurn() {
    if (!active.classList.contains("enabled")) {
      return
    }

    var next = document.querySelector(".turns div:not(.active)")
    active.classList.remove("active")
    active.classList.remove("enabled")

    active = next
    active.classList.add("active")
  }

  ;(function initializeTurns() {
    var turnButtons = document.querySelectorAll(".turns button")
    var button

    for (var ii=0; ii<turnButtons.length; ii++) {
      button = turnButtons[ii]
      button.onclick = nextTurn

      if (ii === 0) {
        active = button.parentElement
        active.classList.add("active")
      }
    }
  })()
})())
You can commit these changes to your local repository now:
git commit -am "Wrap in immediately-invoked function expression"

"Immediately-invoked function expressions" and "self-executing anonymous functions" are two ways of saying the same thing. Here are some places where you can find further explanations of this concept:

If you have any problems with this step, please tell us what happened and we'll do our best to find a solution for you.

One Row at a Time

In this step, you'll learn to:
  • Ask the right questions on the Internet to discover what JavaScript keywords to use
  • Count the number of children of an element
  • Check which row the current player first clicked on
Download the source files Test here

Right now, there is nothing to prevent a player from removing matches from more than one row. This is against the rules, and must be stopped. This means that you are going to have to find a way to check which row a given match belongs to.

One easy way to distinguish between the rows is by the number of <img> elements the row contains.

At the moment, you are attaching an onclick handler to the <body> element. This means that every click anywhere on the page will trigger a click mouse event. In the hideMatch function, you are checking for event.target, which should be an <img> element. If it is an <img> element, then its parent element will be one of the <div> elements with the "matches" class. All you need to do is:

  • Find out which element is the parent of the clicked <img> element. This will be one of the "match" <div> elements.
  • Find out what position this element is in as a child of its parent. This will give the number of the row the match is in

JavaScript starts counting at 0, meaning "there are 0 items before this one". As a result the result of the calculation will be 0, 1, 2 or 3.

You have already seen how to find the parent of a given element, because you have used it in the initializeTurns function. But if you have forgotten, it is very easy to find out using your favourite search engine. You just have to ask the right question. Try these search words:

parent of html element

What is the first hit that you get? It is probably either a link to parentElement or parentNode. Either property is good.

What about finding which position a given element has ? How about these search terms:

javascript get index of element in parent

This time, you might find that the top hits are on a site called StackOverflow.com.

StackOverflow is a site where developers (using any programming language) can ask questions about code. You might like to sign up, so that you can ask questions of your own. I recommend that you take the official tour of the site before you start asking questions, so that you understand how to write questions that will receive useful answers.

Here's one answer that is a good starting point for the solution I propose below. The best answers usually have the highest number of votes, but sometimes it is better to choose an answer that is easier to understand, so that you can adapt it more easily to your particular case.

Here are some changes that you can make to your nim.js file, to prevent matches being removed from more than one row:

;(function() {
  var active
  var rowCount = -1
  var rows = document.querySelectorAll(".matches")
  ...

  function hideMatch(event) {
    var match = event.target
    
    if (match.nodeName !== "IMG") {
      return
    } else if (match.className === "removed") {
      return
    } else if (rowsDontMatch(match)) {
      return
    }
    
    match.classList.add("removed")
    active.classList.add("enabled")
  }

  function rowsDontMatch(match) {
    var currentRowIndex = getCurrentRowIndex(match)

    if (rowIndex < 0) {
      rowIndex = currentRowIndex
    } else if (rowIndex !== currentRowIndex) {
      return true
    }

    return false
  }

  function getCurrentRowIndex(match) {   
    var parentElement = match.parentElement
    var row

    for (var index=0; index<rows.length; index++) {
      row = rows[index]
      if (row === parentElement) {
        return index
      }
    }
  }

  ...

  function nextTurn() {
    if (!active.classList.contains("enabled")) {
      return
    }

    var next = document.querySelector(".turns div:not(.active)")
    active.classList.remove("active")
    active.classList.remove("enabled")

    active = next
    active.classList.add("active")

    rowCount = -1
  }

Identifying the row from which the player took the first match

At the top of your script, you declare a two new variables:

  • rowIndex with a default value of -1
  • rows, which is set to an array of the four rows with the class "matches"

In the nextTurn function, you set the value of rowIndex to -1 again. This resets the value before each player starts to remove matches.

When the player clicks on a visible match, the new function rowsDontMatch is called, and the <img> element on which the player clicked is passed as an argument. The first line of the rowsDontMatch function...

var currentRowIndex = getCurrentRowIndex(match)

... calls the getCurrentRowIndex function, sending the match <img> element as an argument to this function.

Getting the index of the row that was clicked

getCurrentRowIndex starts by setting discovering which element that is the parent of the match img that the player clicked on. It then runs a repeat loop. This uses a variable called index, whose value starts at 0. The repeat loop considers each of the rows of matches in turn, checking to see if the current row is the same as the row that contains the match that was clicked on. If it is, then the repeat loop stops what it's doing and returns the value of to the rowsDontMatch function. The variable currentRowIndex now has a value from 0 (for the top row) to 3 (for the bottom row).

Comparing row numbers

If this is the first match that the player has clicked during this turn, then rowIndex will be -1 so (rowIndex < 0) will be true. In this case the line

  rowIndex = currentRowIndex

... will be executed, and then the rowsDontMatch function will return the value false.

If the player had already clicked another match, then rowIndex will already have a value that is not negative (0, 1, 2 or 3). In this case expression...

(rowIndex !== currentRowIndex)

... will be evaluated. If the player clicked earlier on a match in a different row, the numbers in rowIndex and currentRowIndex will be different; this expression will evaluate as true and the rowsDontMatch function will return the value true to the hideMatch function.

If the click was on the same row (and therefore the match can legally be removed), none of the code inside the if ( ) { } else if ( ) { } statement will be executed, and the next line to run will be return false.

If rowsDontMatch returns true, then hideMatch will not execute any more code. No matches will disappear.

Time to commit these changes to your local repository:
git commit -am "Limit player to taking from only one row"

Now you can visit your Issues page on GitHub, and close this issue.

If you have any problems with this step, please tell us what happened and we'll do our best to find a solution for you.

Who Won?

In this step, you'll learn how to:
  • Read the text of a given HTML element
  • Count the number of HTML elements with a given class
  • Indicate that the game is over
Download the source files Test here

You can now play Nim as a two-player game, removing matches until there are no matches left. When a player removes the last match, the game should say something like "You win" or "Player 1 wins". This means that your code must have some way of:

  • Knowing the name of the current player ("You", "Computer", "Player X")
  • Deciding whether to use "win" or "wins", depending on the name of the winning player.

When the last match has been removed, then all the <img> elements will have the class "removed". There are 16 matches in all. If there are 16 images with the class "removed" then the game is over.

Selecting all the "removed" match images

In the reset method, you have already usedvar matches = document.querySelectorAll(".matches img.removed") to create an array of all the elements with the "removed" class. You have also used matches.length to count the number of elements in this array. It should be easy for you to create a checkForGameOver method that uses these two techniques, and checks whether the result is 16, in which case all the matches have been removed, and the game is over.

Perhaps you'd like to try that for yourself.

Getting the name of the winner

The loser is the player who plays the last move. To announce who won, you will need to read the name of the current player, just before the current player switches. You'll need to do this in the nextTurn function, but you'll need to check for the losing move in the hideMatch function. This means that the variable that holds the name of the winner needs to be declared outside both these functions, inside the outermost closure:

;(function() {
  var active
  var winner

  ...

To discover how to get the text itself, you can run an Internet search such as...

get text of html element

This should lead you to articles such as:

Here's how you can set the value of winner each time a player finishes a turn:

 ...

  function nextTurn() {
    if (!active.classList.contains("enabled")) {
      return
    }

    winner = active.querySelector("p").textContent

    var next = document.querySelector(".turns div:not(.active)")
    active.classList.remove("active")
    active.classList.remove("enabled")

    active = next
    active.classList.add("active")

    rowCount = 0 
  }

  ...

In other words: look inside the active player div for an element with a <p> tag, and set winner to the text of the this element. Then swap players, so that the other player becomes "active".

Detecting that the game is over

Here's how you can add a call to a checkForGameOver function to the hideMatch function.

  ...

  function hideMatch(event) {
    var match = event.target
    
    if (match.nodeName !== "IMG") {
      return
    } else if (match.className === "removed") {
      return
    } else if (rowsDontMatch(match)) {
      return
    }
    
    match.classList.add("removed")
    active.classList.add("enabled")

    checkForGameOver()
  }

  function checkForGameOver() {
    var selector = ".matches img.removed"
    var removed = document.querySelectorAll(selector).length

    if (removed === 16) { // HARD-CODED NUMBER OF MATCHES
      showWinner()
    }
  }

  function showWinner() {
    var message

    if (winner === "You") {
      message  = winner+" win!"
    } else {
      message = winner+" wins!"
    }

    alert (message)
  }

  function rowsDontMatch(match) {
  ...

Note that checking that the game is over and announcing the winner are done in two separate functions, to make it easy to change each feature separately.

Displaying an alert when the game is over
Figure 99. Displaying an alert when the game is over
Well done! That's another issue resolved: Check that the current player has taken the last match. You can visit your Issues page on GitHub, and close this issue.

Do you need a reminder about how to commit your changes locally?

git commit -am "Check when player takes the last match"

In the next step you will see how to display the winner in the page itself, instead of using an alert. When you're ready, click on the Next arrow below, or on item 31: Using an overlay in the menu on the left.

Positioning an Overlay

In this step, you'll learn:
  • How to create a semi-transparent overlay
  • How to change the text of an HTML element
  • How to use the id attribute
Download the source files Test here

It's easy to create an alert to show who has won, but it does not look very professional. It would be better to make the winning announcement in the same style as the page itself. To do this, you'// need to make changes to all three documents in your game: index.html, style.css and nim.js.

Creating an overlay in HTML

When the game is over, all the matches will be gone, and the "player" names and Done buttons will no longer be needed. However, you will want to be able to click on any of the three start buttons. It would make sense to place the winner announcement in the centre of the area currently defined by <div class="game"> and <div class="turns">.

One way to do this is to place both these <div>elements inside a wrapper <div>, and then create a third child of the wrapper <div> which will take up 100% of the width and 100% of the height of its parent.

In your index.html file, add a new <div> with a class of "playzone", as shown below:

<!DOCTYPE html>
<html>
  <head>
    <title>Nim</title>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    <h1>Nim</h1>
    <h2>The game that will beat you</h2>
    <div class="playzone">
      <div class="game">
        <div class="matches">
          <img src="img/match.png" alt="match 1 row 1" />
          ...
        </div>
      </div>

      <div class="turns">
        <div class="player1">
          <button type="button">Done</button>
          <p>You</p>
        </div>
        <div class="player2">
          <button type="button">Done</button>
          <p>Computer</p>
        </div>
      </div>
    </div>

    ...

Now, inside the new <div>, you can create a nest of two new elements: a <div> that will serve to cover the whole area with a semi-opaque mask, and a <p> element to show the identity of the winner:

<!DOCTYPE html>
<html>
  <head>
    <title>Nim</title>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    <h1>Nim</h1>
    <h2>The game that will beat you</h2>
    <div class="playzone">
      <div class="game">
        ..
      </div>

      <div class="turns">
        ...
      </div>

      <div id="winner" class="mask">
        <p class="dialog active">The Computer wins!</p>
      </div></div>
    </div>

    ...

The "mask" div has an id attribute.For more information on the id attribute, see the Learn More About ids section below.

Using CSS to position the winner announcement

If you save your changes and refresh your browser, you will see that the text "The Computer wins!" in small characters beneath the names of the players. To make it appear over the top of other elements, you need to use the position CSS attribute.

In your style.css file, add the following two rules, then save your files and refresh your browser.

.playzone {
  position: relative;
}
.mask {
  position: absolute;
  height: 100%;
  width: 100%;
  top: 0;
  background:rgba(100,0,0,0.8);
}

You should now see a dark red overlay with an opacity of 80% above the matches and the players' names. (I've used red so that it is easy to see. By the end of the next step, it will be changed to black, to blend in with the background). The text "The Computer wins!" should appear at the top left of this area.

Using position: absolute; to place elements outside the normal flow
Figure 100. Using { position: absolute; } to place elements outside the normal flow

The default value for the position property is static. Elements with a static position flow after each other down the page. If you change the value of an element's position to absolute, you tell the browser to remove the element from the normal flow. It will now appear on top of other elements, and with its top left at the absolute position that you give relative to the closest parent that does not have a static position.

By setting the position of the "playzone" parent to "relative", you tell the browser that the "mask" overlay needs to be positioned absolutely with respect to the "playzone" element. If you delete or comment out the CSS rule for .playzone, then the "mask" element will be positioned relative to the <body>, and will thus cover the three start buttons as well, making it impossible to click them. (Try it and see.)

Setting the height and the width of the "mask" <div> makes it exactly the same dimensions as its parent "playzone" <div>. Setting the top attribute to 0 places the "mask" <div> at exactly the same vertical position as its parent.

The Learn More About Position section below will give you insight into how the position property works.

CSS to centre the winner text

The easiest way to position the winner text in a good vertical position is to use absolute positioning and em units for the top attribute. Te centre it horizontally, you can set the width of the element to 100% of the width of its parent, and use center for the text-align attribute.

Add these two rules to your style.css file, save it and refresh your browser.

.dialog{
  position: absolute;
  top: 8.2em; /* Hand-picked hard-coded value */
  width: 100%;
  text-align: center;
}
#winner p{
  font-size: 2.5em;
}

On my system, the value of 8.2em places the text nicely between the middle two rows of matches.

Using em units to fix the vertical position
Figure 101. Using em units to fix the vertical position

Here are some links with more details about how the position property works:

In CSS, you can apply a class to many different elements, and you can apply many classes to one element. Sometimes you need to identify a unique element. It can be complicated to create a selector that will do this if you only use classes.

The solution is to set an id attribute for your unique element. If you give the same id to several elements, the browser will only recognize the first element with that id. In JavaScript, you can use document.getElementById("idString") to refer to the first element in the page that has the given id.

In your CSS document, a class is identified by a preceding dot. An id is identified by a preceding hash symbol:

.class {
  /* CSS rules */
}

#id {
  /* CSS rules */
}

You can read more about the id selector on other sites:

Using CSS Transforms

In this step, you'll learn:
  • How to use the transform property to change an element's layout with respect to itself
Download the source files Test here

The technique that you used in the last step for placing an element vertically assumes that you know the height of parent element. Here's a technique that you can use when you don't know the height:

  • Move the top of the text element half-way down the parent element
  • Shift the text element upwards half its own height

The first of these is easy: you can set the value of top to 50%: 50% of the height of the parent element.

To shift the element by a percentage of its own height, you need to use an attribute that has been added more recently to CSS. The vendor of different browser have not yet agreed on how it should work exactly. As a result, you need to use not one CSS property but several, one for each vendor. Your browser will ignore the properties that it does not understand.

Unit-free CSS for centring an element vertically.

Make the following change to your CSS rule for the "dialog" class:

.dialog{
position: absolute;
width: 100%;
top: 8.2em;
top: 50%;
-webkit-transform: translate(0, -50%);
-moz-transform: translate(0, -50%);
-ms-transform: translate(0, -50%);
-o-transform: translate(0, -50%);
transform: translate(0, -50%);
text-align: center;
}
The transform property will eventually become standard. It should be placed last. The other properties all have a vendor prefix. If a given browser supports both its own vendor-prefixed version of the property and the standard property itself, then the value given later will be applied.

In Google Chrome, for example, if you disable the transform setting in the "dialog" rule, by unchecking its checkbox in the Styles pane (as shown in figure 102 below), the webkit-transform property will be activated instead. The effect should be the same.

Using the transform property to move an element relative to itself
Figure 102. Using the transform property to move an element relative to itself

Here, the transform property is used to translate the winner text element upwards by 50% of its own height. The transform property can be used for many other manipulations, such as rotation, scale, skew, 3D perspective. For more information, see the Learn More About Transform section below.

Note that the position of the "winner" text is different now. In the previous step, you used a hard-coded custom value; with this technique, you are simply placing the text exactly in the vertical centre. Now that you know this new technique, you can choose which you like better.

Matching the mask colour to the background

I used red as the background colour for the "#winner" element, so that you could easily see its extent. To blend the overlay into the page as a whole, it's better to use the same background colour as for the <body>:

.mask {
  position:absolute;
  height: 100%;
  width: 100%;
  top: 0;
  background:rgba(0,0,0,0.8);
}

You might want to read the Learn More About Transforms section below, to understand how this CSS feature works. When you're ready, click on the Next arrow below, or on item 33: Developer cheats in the menu on the left.

You can discover more about the transform property on other sites:

Creating Developer Cheats

In this step, you'll learn:
  • How to call a function inside a closure from outside
Download the source files Test here

Cheating for reasons of efficiency

In the next step, you will be seeing how to show your new winner announcement when the game is over. You will probably want to test that the announcement appears the way you expect it to, several times.

In step 30: Who won?, you needed to remove all the matches one by one, and swap players from time to time before the winner alert would appear. This is time-consuming. It would be good to be able to invoke the winner alert at any time.

At step 22: Resetting the Game, you typed the command reset() in the Console to reset the game. What do you think will happen if you type showWinner("Winner!") after the > prompt in the Console?

Calling a function from outside its scope causes an error
Figure 103. Calling a function from outside its scope causes an error

The answer is: lots of angry red text. Why?

In step 28: Variables and scope, you created an immediately-invoked function expression to wrap all your code, so that it would keep all your variables neatly in their own closures. The downside of this is that you can't now call any of the functions inside this closure. Fortunately there is a simple slution, that you already know: declare a variable outside the closure, and set the value of this variable inside the closure. Like this:

var functionVariable

;(function() {
  var active

  ...

  functionVariable = function showWinner(winner) {
    alert (winner)
  }

  ...
  

The value of a variable can be a function. You can execute your function-variable, by placing opening and closing parentheses after it (enclosing some optional arguments), like this:

functionVariable("Winner")

When you do this, you execute the showWinner function that is stored inside your IIFE closure.

I've used functionVariable here, to show that the variable name and the function have their own separate existences. For simplicity and consistency, I suggest that you use the same name for both.

var showWinner

;(function() {
  var active

  ...

  showWinner = function showWinner(winner) {
    alert (winner)
  }

  ...
Using a top-level variable to call a function inside a closure
Figure 104. Using a top-level variable to call a function inside a closure

The variable showWinner that you have just created is a global, and its good to avoid using globals. This particular global is only used to save you time during development. As soon as the game is working the way you want it to, you can delete your global declarations, and it will continue to work inside the safety of the closure.

A reset cheat

Here's another developer cheat that will prove useful to you:

var showWinner
var reset

;(function() {
  var active
  var winner
 
  ...

  reset = function reset() {
    var matches = document.querySelectorAll(".matches img.removed")

    for (var ii=0; ii<matches.length; ii++) {
      var match = matches[ii]
      match.classList.remove("removed")
    }
  }

  ...

This will let you execute the reset() command in the Console after the > prompt, at any time.

Now that you can summon up the winner announcement whenever you want to see it, you are ready to integrate and test your new system. Click on the Next arrow below, or on item 34: Showing the winner in the menu on the left.

Showing the winner

In this step, you'll learn how to:
  • Hide and show an element and its children
  • Set the text of an HTML element
Download the source files Test here

The winner announcement currently shows all the time. You already know how to make it invisible by setting the opacity of its elements to 0, but a totally transparent element will still catch mouse events, so you won't be able to click on the matches or the buttons. You need to hide the #winner element completely. How can you do that? Here's are some search terms you could use to find the answer:

hide html element

display: none;

You have already seen the display property. In step 25: The game layout, you used { display: inline-block; } to place the two "player" <div>s side by side. You can use it with the value none to remove an element from the page. Here are a couple of changes that you could make to your index.html and style.css files:

HTML

    ...
      </div>

      <div id="winner" class="mask hidden">
        <p class="dialog active">The computer wins!</p>
      </div>
    </div>
    ...

CSS

.hidden{
  display: none;
}

Make these changes, save your files and refresh your browser. The game should appear with all the matches fully visible. Clicking on a match should make it disappear, just as before.

Showing the winner element when the game is over

Now you can replace the alert (winner) command in the showWinner function with a command that makes the #winner element reappear. In your nim.js file, make the following changes:

var showWinner
var reset

;(function() {
  var active
  var winner
  var rowCount = -1
  var rows = document.querySelectorAll(".matches")
  var winnerDiv = document.querySelector("#winner")

  ...

  showWinner = function showWinner(winner) {
    winnerDiv.classList.remove("hidden")
  }

  ...

Save your changes, refresh your browser, and type the command showWinner() into the console. The #winner anouncement should appear.

Hiding the winner when the game is reset

Hiding the #winner anouncement is just as easy. You can add a single line to the reset function:

  ...

  reset = function reset() {
    var matches = document.querySelectorAll(".matches img.removed")

    for (var ii=0; ii<matches.length; ii++) {
      var match = matches[ii]
      match.classList.remove("removed")
    }

    winnerDiv.classList.add("hidden")
  }
  
  ...

If you type reset() in the Console, you should see the winner overlay disappear.

Setting the text of an HTML element

The showWinner function receives the name of the winner as an argument. In step 30: Who won?, you saw how to get the text of an HTML element using textContent. New you can use the same property to set the text in the #winner anouncement. Make the following change, save your changes, and refresh your browser.

  ...

  showWinner = function showWinner(winner) {
    var p = winnerDiv.querySelector("p")
    p.textContent = winner
    winnerDiv.classList.remove("hidden")
  }

  ...

If you type showWinner("Player 1 wins!) in after the > prompt in the Console, you should see the customized winner announcement appear as an overlay over your game.

Showing an overlay with a custom text
Figure 105. Showing an overlay with a custom text
Remember to commit these changes to your local repository.
git commit -am "Use overlay to show winner"

If you have any problems with this step, please tell us what happened and we'll do our best to find a solution for you.

Completing the two-player game

In this step, you'll learn how to:
  • Get the Start buttons to start a new game
  • Set the names of the players
  • Tidy up your JavaScript code
Download the source files Test here

When you complete this step, you will have a fully-working two-player game. It will be good to clean up the code, so that all is ready for you to add a computer player and its artificial intelligence. I'll show you the changes one at a time, and at the end I'll give you the complete code listing, with the functions rearranged in a logical order.

Initialization

When the web page first loads, the game should tell all the interactive elements (matches and buttons) what they should do when you click on them. At the moment, the initializeTurns function invokes itself. In the finished game, there will be more than turns to initialize. To make this clear, you can create an initialize function, and get it to call other functions that deal with each situation.

In your nim.js file, make the following changes:

var showWinner
var reset

;(function() {
  var active
  var winner
  var rowCount = -1
  var rows = document.querySelectorAll(".matches")
  var winnerDiv = document.querySelector("#winner")

  // Code that runs when the page is first loaded
  ;(function initialize () {
    document.body.onclick = hideMatch
    document.querySelector(".start").onclick = startNewGame
    initializeTurns()

    startNewGame()
  })()

  // Code to start a new game
  function startNewGame() {
    reset()
  }

The new initialize function is self-invoking, so the existing initializeTurns function can become an ordinary function again. It no longer needs to be an IFFE. You can remove the parentheses that made it auto-evaluate and auto-execute:

  ;( function initializeTurns() {
    var turnButtons = document.querySelectorAll(".turns button")
    var button

    for (var ii=0; ii<turnButtons.length; ii++) {
      button = turnButtons[ii]
      button.onclick = nextTurn

      if (ii === 0) {
        active = button.parentElement
        active.classList.add("active")
      }
    }
  })()

Save your changes and refresh your browser.

In the Console, you will probably see an error.
Error: reset is not a function
Figure 106. Error: reset is not a function

Why does this happen?

The short answer is: The JavaScript engine has not yet read to the line that says reset = function reset() { ... }, so the variable reset does not yet have a value. The simplest solution is to remove reset = , to create a simple function definition again:

reset = function reset() {
  var matches = document.querySelectorAll(".matches img.removed")

  for (var ii=0; ii<matches.length; ii++) {
    var match = matches[ii]
    match.classList.remove("removed")
  }

  winnerDiv.classList.add("hidden")
}

You're not losing any functionality: clicking one of the Start buttons will now do the same as manually typing reset() into the Console.

Now that you are no longer giving a value to the reset variable, you can remove the declaration at the top of your script:

var showWinner
var reset

;(function() {
...

The long answer is important. Understanding why this error occurred will help you to avoid writing buggy code in the future. I recommend that you read Learn More About Hoisting below.

After you have dealt with the reset issue, save your changes and refresh your browser again, to check that the error has gone.

Starting a new game

I've got you to create a new startNewGame function which does nothing but call another function. What's the point of that? Choose a row of matches and click on some of them to remove them, and then click on any of the Start buttons: the matches should reappear.

This is because a click anywhere in the "start" class <div> will call the startGame function, which will in turn call reset, which removes the "removed" class from all the hidden matches. This should happen regardless of which of the Start buttons is clicked.

In step 37: Computer player, you will be setting the game up differently depending on which Start button is clicked. For now, you can get all Start buttons to start a two-player game, with players called "Player 1" and "Player 2".

...

  // Code to start a new game
  function startNewGame(event) {
    player1 = "Player 1"
    player2 = "Player 2"
    setPlayers(player1, player2)

    reset()
  }

  function setPlayers(player1, player2) {
    var players = document.querySelectorAll(".turns div")
    var player, nameField

    for (var ii=0; ii<players.length; ii++) {
      player = players[ii]
      nameField = player.querySelector("p")

      if (ii === 0) {       
        nameField.textContent = player1

      } else {
        nameField.textContent = player2
      }
    }
  }

...

The startNewGame function now decides what each player is going to be called, and the new setPlayers function displays these names in the HTML page.

It would make sense to move the initialization of the active variable, and the classes attached to it, from the initializeTurns function to the setPlayers function:

...
  function setPlayers(player1, player2) {
    var players = document.querySelectorAll(".turns div")
    var player, nameField

    for (var ii=0; ii<players.length; ii++) {
      player = players[ii]
      nameField = player.querySelector("p")

      if (ii === 0) {       
        active = player
        active.classList.add("active")
        active.classList.remove("enabled")
        nameField.textContent = player1

      } else {
        player.classList.remove("active")
        nameField.textContent = player2
      }
    }
  }

  ...

  function initializeTurns() {
    var turnButtons = document.querySelectorAll(".turns button")
    var button

    for (var ii=0; ii<turnButtons.length; ii++) {
      button = turnButtons[ii]
      button.onclick = nextTurn
       
      if (ii === 0) {
        active = button.parentElement
        active.classList.add("active")
      }
    }
  }

  ...

Now, the first three lines of the initialize function just set up the various onclick event listeners, once and for all, and the setPlayers function deals with the players names and which player will play first.

Resetting rowCount

The two-player game is now complete... or is it? You can save your changes, refresh the browser, play all the way through to the end and see who the winner is, and you can click on any of the Start buttons to restart the two-player game... And that's when the bug bites.

If Player 1 in the second game does not click on a match in last row that was clicked in the last game, nothing happens. Why? The answer is hidden below.

Answer: the variable rowCount is still set to the number of matches in the last row that was clicked.

You need to add one line of code to the reset function. See if you can find the answer for yourself before checking what's missing below:

  ...

  function reset() {
    var matches = document.querySelectorAll(".matches img.removed")

    for (var ii=0; ii<matches.length; ii++) {
      var match = matches[ii]
      match.classList.remove("removed")
    }

    winnerDiv.classList.add("hidden")
    
    rowCount = 0
  }

  ...

The complete code for a two-player game

var showWinner

;(function() {
  var active
  var winner
  var rowCount = -1
  var rows = document.querySelectorAll(".matches")
  var winnerDiv = document.querySelector("#winner")

  // Code that runs when the page is first loaded
  ;(function initialize () {
    document.body.onclick = hideMatch
    document.querySelector(".start").onclick = startNewGame
    initializeTurns()

    startNewGame({target: {className: "twoPlayers"}})
  })()

  function initializeTurns() {
    var turnButtons = document.querySelectorAll(".turns button")
    var button

    for (var ii=0; ii<turnButtons.length; ii++) {
      button = turnButtons[ii]
      button.onclick = nextTurn
    }
  }

  // Code to start a new game
  function startNewGame(event) { 
    player1 = "Player 1"
    player2 = "Player 2"
    setPlayerNames()
  
    reset()
  }

  function setPlayerNames() {
    var players = document.querySelectorAll(".turns div")
    var match, player, nameField

    for (var ii=0; ii<players.length; ii++) {
      player = players[ii]
      nameField = player.querySelector("p")

      if (ii === 0) {       
        active = player
        active.classList.add("active")
        active.classList.remove("enabled")
        nameField.textContent = player1

      } else {
        player.classList.remove("active")
        nameField.textContent = player2
      }
    }
  }

  function reset() {
    var matches = document.querySelectorAll(".matches img.removed")

    for (var ii=0; ii<matches.length; ii++) {
      var match = matches[ii]
      match.classList.remove("removed")
    }

    winnerDiv.classList.add("hidden")

    rowCount = 0
  }

  // Code that runs each time a match is removed
  function hideMatch(event) {
    var match = event.target
    
    if (match.nodeName !== "IMG") {
      return
    } else if (match.className === "removed") {
      return
    } else if (rowsDontMatch(match)) {
      return
    }
    
    match.classList.add("removed")
    active.classList.add("enabled")

    checkForWinner()
  }

  function rowsDontMatch(match) {
    var matchCount = match.parentElement.childElementCount
    if (rowCount) {
      if (rowCount !== matchCount) {
        return true
      }
    } else {
      rowCount = matchCount
    }

    return false
  }

  function checkForWinner() {
    var selector = ".matches img.removed"
    var removed = document.querySelectorAll(selector).length

    if (removed === 16) {
      if (winner === "You") {
        showWinner(winner+" win!")
      } else {
        showWinner(winner+" wins!")
      }
    }
  }

  showWinner = function showWinner(winner) {
    var p = winnerDiv.querySelector("p")
    p.textContent = winner
    winnerDiv.classList.remove("hidden")
  }

  // Code that runs when the Done button is clicked
  function nextTurn() {
    if (!active.classList.contains("enabled")) {
      return
    }

    winner = active.querySelector("p").textContent

    var next = document.querySelector(".turns div:not(.active)")
    active.classList.remove("active")
    active.classList.remove("enabled")

    active = next
    active.classList.add("active")

    rowCount = 0 
  }
})()

Housekeeping

In this step, you'll:
  • Merge your gameplay branch back into the main gh-pages branch
  • Push your working game to the public GitHub.io web site

It's a real achievement to have reached this point. You started with perhaps little to no knowledge of HTML, CSS, JavaScript and Git version control, and now you have a game that you can play with another human. (True, you could play the game with real matches, but you wouldn't have learned so much.

Now, you'll be eager to put your game online, where others can see it. To do that, you first need to merge the local gameplay branch with the main gh-pages branch. Then you need to push the merged version to the GitHub server.

Working in the Terminal

Here are the commands that you will need to use in the Terminal window. They should all be familiar to you.

Remember that you may need to use the cd  command to change directory to the folder where your game files are stored.

1. Commit your changes to the gameplay branch. This will make sure that Git knows exactly how much you have done, and stores it in the hidden git folder.

git commit -am "Phase complete: all features for two-player game"

2. Checkout the gh-pages branch. This action will replace the working two-player game with the version you had at step 23: The Reset button.. You might like to refresh your browser to see how different your game was back then.

git checkout gh-pages

3. Merge in all your changes since then.

git merge gameplay

4. Commit the merge operation.

git commit -am "Merged with gameplay"

5. Push the merged version to your remote repository on GitHub:

git push origin gh-pages

Now you can visit your site on GitHub.io, and check that everything is working there, too.

If you have any problems with this step, please tell us what happened and we'll do our best to find a solution for you.

Creating a Computer Player

In this step, you'll:
  • Reflect on the steps needed to create a computer player
  • Show the appropriate player names when a new game starts
  • Record what matches can still be taken
Download the source files Test here

You've created a game where a human player has to click on each match to make it disappear. Now you're going to create a computer player that can choose one or more matches automatically.

In the first instance, you can create a player that takes matches at random. You'll be able to win against this player, if you understand what moves make. As you are playing, you'll have the chance to work out what the winning strategy is.

The next objective will be to work out how to program the computer player so that it applies this strategy. You'll see that there may be times when a random choice is the only move that the computer player can make, so your first version of the player will still be needed.

Here are the different issues that you are going to have to deal with:

  • The computer must "know" when to play and when to let you play
  • The computer must have a way of knowing which matches are left to take
  • The computer must have a way to choose which match(es) to take...
  • ... two ways, in fact: one random, one calculated
  • The computer can act almost instantaneously: there should be a way to make it act slowly enough for a human to see what move it is making

You can visit your Issues page on GitHub, and create a new issue for each of these. Perhaps you would like to take a little time to think about each one in greater depth, to see how many separate issues you should create. For example, knowing which matches are left means: knowing which rows still have matches, and knowing which specific matches in each row are still visible. Should this be one issue or two?

Telling the computer when to play

There are three Start buttons. One tells the computer it's never going to play. Another tells it that it should play first, then wait for you to respond. The third tells it to wait for you to play first, and then take its turn.

One way to model these three options is to use two variables, each with a value of true or false. You can call the first variable useAI,where AI stands for "Artificial Intelligence". If this is true, then the computer gets to play. You can call the second variable computerToPlay. At the end of each turn, your code can check if useAI is true, and if it is, swap the value of computerToPlay. If both useAI and computerToPlay are true, then it's the computer's turn.

You can set the values of these variables, depending on which Start button the player clicks on. This means that the place to set the values is in the startNewGame function. More than one function will need access to these variables, so you should declare them at the top of your IFFE, outside any of the inner functions.

Make the following changes to your nim.js file, save it and refresh your browser.

var showWinner

;(function() {
  var active
  var winner
  var useAI
  var computerToPlay
  
  ...

  // Code to start a new game
  function startNewGame(event) {
    var buttonName = event.target.className

    if (buttonName === "twoPlayers") {
      useAI = false
      player1 = "Player 1"
      player2 = "Player 2"

    } else {
      useAI = true
      if (buttonName === "computerStarts") {
        computerToPlay = true
        player1 = "Computer"
        player2 = "You"

      } else {
        computerToPlay = false
        player1 = "You"
        player2 = "Computer"
      }
    }

    setPlayerNames()
    reset()
  }

Click on the different Start buttons. Do you see how the names of the players change?

The names of the players change depending on which Start button is clicked.
Figure 107. The names of the players change depending on which Start button is clicked.

If you have any problems with this step, please tell us what happened and we'll do our best to find a solution for you.

Tracking the remaining matches

In this step, you'll learn how to:
  • Create a nested array
  • Change the values in a nested array
Download the source files Test here

The computer needs to keep track of what matches are available to be taken. You can see the matches on the screen, but the computer has no eyes and thinks in a different way. One good way to store information about the matches is in a nested array: a digital structure that resembles the layout of the matches.

What is an array?

A simple array is a list of items. An array is defined as:

  • An opening square bracket: [
  • Zero or more values , separated by commas: "apples", "oranges"
  • Optional white space between the values:   or line breaks
  • A closing square bracket: ]

The values in an array can be any data type recognized by JavaScript. For example, they can be strings of characters (like "apple" in the example above), numbers (like in the examples you will see below), or even other arrays. An array of arrays is called a nested array.

Here's an array showing how many matches appear in each row:

var matchesInRows = [7 ,5 ,3 ,1]

Here's an array showing which rows contain at least one match

var rowsWithMatches = [0, 1, 2, 3]

Note that JavaScript starts counting at 0. You'll get more practice with this in a moment.

Initializing the match arrays

The computer needs to start tracking which rows have matches, and where they are, right from the start of the game. You can change the startNewGame function, and add a new function, to do just this:

var showWinner

;(function() {
  var active
  var winner
  var useAI
  var computerToPlay
  var rowsWithMatches
  var matchesInRows
  
  ...

  // Code to start a new game
  function startNewGame(event) {
    var buttonName = event.target.className

    if (buttonName === "twoPlayers") {
      useAI = false
      player1 = "Player 1"
      player2 = "Player 2"

    } else {
      useAI = true
      if (buttonName === "computerStarts") {
        computerToPlay = true
        player1 = "Computer"
        player2 = "You"

      } else {
        computerToPlay = false
        player1 = "You"
        player2 = "Computer"
      }

      setUpAI()
    }

    setPlayerNames()
    reset()
  }

  function setUpAI() {
    rowsWithMatches = [0, 1, 2, 3]
    matchesInRows = [7, 5, 3, 1]
  }

  ...

Getting values from an array

Save your changes then reload your index.html page in your browser. Nothing will have changed on the screen or in the way the game works, but now you will have three new variables to play with.

Set a breakpoint on the line that calls setPlayerNames(), then click on the Computer To Start New Game button. The debugger should open at the breakpoint, just after setUpAI has been called.

In the Scopes pane, open the Closure disclosure triangle, and then the matchesInRows disclosure triangle, to see the values in the array.

Getting and setting the values in an array
Figure 108. Getting and setting the values in an array

At the > prompt in the Console, type:

matchesInRows[0]
7

The Console tells you 7; this is the first value in the array. Note that the JavaScript starts counting at 0, not at 1. The first value in an array is the 0th value. Or if you prefer: there are 0 values that appear before it. See what values you get when you try matchesInRows[1], matchesInRows[2], matchesInRows[3], matchesInRows[4] and matchesInRows[-1]./p>

Do these results make sense to you? The value undefined that you get for matchesInRows[4] or matchesInRows[-1] simply means: "There is no value at the position 4 or -1 . You've gone beyond the end or the start of the array."

The variable rowsWithMatches tells you which rows currently have matches visible. It starts at 0, meaning the first row, which starts out with 7 matches.

Setting values in an array

To change a value, you can simply use the = operator:

matchesInRows[0] = 4

Reopen the Closure disclosure triangle, and check the values in the matchesInRows array. Try setting the value at other index positions. What happens if you try matchesInRows[10] = 4? Yes, it works. It adds a new value at the given index position.

Making Random Moves

In this step, you'll learn how to:
  • Get the computer to take a turn
  • Create a legal move for the computer player
  • Hide the matches that the computer player has taken
  • Check if the computer lost the game
  • Keep track of which matches the computer player can take on its next move
Download the source files Test here

Your startNewGame function can now tell whether the computer should play first or not. If the computer is to play first, then it must be able to choose its move. Make the following change to the startNewGame function:

...

  // Code to start a new game
  function startNewGame(event) {
    var buttonName = event.target.className

    if (buttonName === "twoPlayers") {
      useAI = false
      player1 = "Player 1"
      player2 = "Player 2"

    } else {
      useAI = true
      if (buttonName === "computerStarts") {
        computerToPlay = true
        player1 = "Computer"
        player2 = "You"

      } else {
        computerToPlay = false
        player1 = "You"
        player2 = "Computer"
      }

      setUpAI()
    }

    setPlayerNames()
    reset()

    if (useAI && computerToPlay) {
      computerChooseMove()
    }
  }
 
 ...

The operator && is a logical AND. The expression (useAI && computerToPlay) is true if both useAI and computerToPlay are true.

You can find more information about logical operators in the Learn More About Logic section of the step 27: Enforcing rules.

Now add a similar chunk of code to the nextTurn function, plus three new stub functions at the end of your script:

...

  // Code that runs when the Done button is clicked
  function nextTurn() {
    if (!active.classList.contains("enabled")) {
      return
    }

    winner = active.querySelector("p").textContent

    var next = document.querySelector(".turns div:not(.active)")
    active.classList.remove("active")
    active.classList.remove("enabled")

    active = next
    active.classList.add("active")

    if (useAI) {
      computerToPlay = !computerToPlay;
      if (computerToPlay) {
        computerChooseMove();
      }
    }

    rowIndex = -1
  }

  // Code for the computer player
  function computerChooseMove() {
    if (false) {
      findWinningMove()
    } else {
      chooseRandomMove()
    }
  }

  function findWinningMove() {
    // More code will go here
  }

  function chooseRandomMove() {
    // More code will go here
  }
})()

These three new functions do nothing useful yet. For now, they simply stop the game from continuing any further when it's the computer's turn to play. In particular, the first line of the computerChooseMove function...

    if (false) { ... }

... ensures that the chooseRandomMove() function will be called every time. It is easier to create a legal random move than to create a winning move, so you can deal with random moves first. When you have written the more complex findWinningMove function, then you will be able to replace false with a more meaningful expression.

Random

There are two random choices that the computer player has to make:

  • Which row to take matches from
  • How many matches to take from the chosen row

You already have a variable called rowIndex that holds a number between 0 and 3, depending on which row the player chooses to take matches from. You can add a new variable at the top of your script to hold the number of matches that either the human player or the computer player decides to take. You'll need to set this to 0 before a new game starts, and increase it by 1 each time a human player takes a match:

;(function() {
  var active
  var winner
  var useAI
  var computerToPlay
  var rowsWithMatches
  var matchesInRows
  var taken
  ...

  function reset() {
    var matches = document.querySelectorAll(".matches img.removed")

    for (var ii=0; ii<matches.length; ii++) {
      var match = matches[ii]
      match.classList.remove("removed")
    }

    winnerDiv.classList.add("hidden")

    rowIndex = -1
    taken = 0
  }

  // Code that runs each time a match is removed
  function hideMatch(event) {
    var match = event.target
    
    if (match.nodeName !== "IMG") {
      return
    } else if (match.className === "removed") {
      return
    } else if (rowsDontMatch(match)) {
      return
    }
    
    match.classList.add("removed")
    active.classList.add("enabled")

    taken++

    checkForWinner()
  }

  ...

Creating a random value

JavaScript has a built-in Math object, which allows you to do many mathematical operations. You will be using two of its methods here:

  • Math.random() creates a seemingly random number from 0.0 up to (but not including) 1.
  • Math.floor(x) takes a floating point number and returns the next smallest integer.
  • Math.ceil(x) (for ceiling) does the opposite. It takes a floating point number and returns the next biggest integer.

To choose a row at random, you can:

  • Create a random number between 0.0 and 1 (e.g. 0.618)
  • Multiply this number by the number of rows that still contain at least one match (e.g. 0.618 * 3 = 1.854
  • Get the floor of this new number (e.g. 1)

This is how the variable index is calculated in the chooseRandomMove function below.

This number that you have generated is not the row number itself: it is the index position of the row number in the rowsWithMatches array. Suppose all the matches in the second row had been taken. rowsWithMatches would look like this: [0, 2, 3], because the row with index number 1 would no longer have any matches. Choosing the value at index 1 in this list will give you the number 2, representing the row that initally has 3 matches. This is how the value of rowIndex is calculated below.

 ...

  function chooseRandomMove() {
    var index, max
    index = Math.floor(Math.random() * rowsWithMatches.length)
    rowIndex = rowsWithMatches[index]
    max = matchesInRows[rowIndex]
    taken = Math.ceil(Math.random() * max); // 1 - max
  }

  ...

Deciding how many matches to take from this row

When you know which rowIndex the computer player will choose, you can calculate how many matches there are still in that row. This information is stored in the matchesInRows array. The current value of matchesInRows[rowIndex] represents the maximum number of matches the computer player can take.

You now want to create a number from 1 to this maximum number, as the number of matches the computer player should remove. The value of the variable taken is set to this number.

The chooseRandomMove function thus sets both rowIndex and taken to usable values. Now you need to create a function that actually removes this number of matches from the chosen row.

Removing matches

Modify the computerChooseMove function and add the hideMatches function, as shown below:


  // Code for the computer player
  function computerChooseMove() {
    var gameOver

    if (false) {
      findWinningMove()
    } else {
      chooseRandomMove()
    }

    hideMatches()
  }

  function chooseRandomMove() {
    var index, max
    index = Math.floor(Math.random() * rowsWithMatches.length)
    rowIndex = rowsWithMatches[index]
    max = matchesInRows[rowIndex]
    taken = Math.ceil(Math.random() * max); // 1 - max
  }

  function hideMatches() {
    var matches = rows[rowIndex].children; // [img, img, ...]
    var removed = 0
    var match; // img

    for (var ii=0; ii<matches.length; ii++) {
      match = matches[ii]
      if (!match.classList.contains("removed")) {
        match.classList.add("removed")
        removed ++

        if (removed === taken) {
          return
        }
      }
    }
  }
})()

The hideMatches function selects the <div> element that represents the row of matches that has been chosen, and iterates through the match <img> elements, looking for <img>s that have are still visible. To do this, it checks each <img> to see if it has a "removed" class or not.

Each time it finds a new visible <img>, it adds the "removed" class to it, and increases the value of the removed variable. When removed has the same value as taken then it stops looking for any more.

Checking for game over

If the human player takes the last match, then the "winner" overlay will appear, preventing the player from clicking on the Done button. As a result, the losing human player cannot trigger the nextTurn function.

The computer player does not have to click any buttons so the presence of the "winner" overlay has no effect on it. When the computer player's turn is over, it can call nextTurn immediately to give the human player a turn. However, if the computer player takes the last match, then the game is over. The "winner" overlay should appear, but the computer player should not call nextTurn.

To take this into acount, you can change the checkForWinner function so that it returns true if the game is over and false if not. When a human player clicks on a match and checkForWinner is called, the value returned by the function can be ignored. When the computer player calls the function, then it can use the returned value to decide whether or not to call nextTurn and let the human play.

  ...

  function checkForWinner() {
    var selector = ".matches img.removed"
    var removed = document.querySelectorAll(selector).length

    if (removed === 16) {
      if (winner === "You") {
        showWinner(winner+" win!")
      } else {
        showWinner(winner+" wins!")
      }
      return true
    }

    return false
  }

  ...

  // Code for the computer player
  function computerChooseMove() {
    var gameOver

    if (false) {
      findWinningMove()
    } else {
      chooseRandomMove()
    }

    hideMatches(rowIndex, taken)
    gameOver = checkForWinner()

    if (!gameOver) {
      active.classList.add("enabled")
      nextTurn()
    }
  }

Note that the computer player's Done button needs to be enabled, otherwise the conditional expression at the beginning of nextTurn will prevent the rest of the function from executing. Right now, you won't see the computer player's Done button appear at all, because it makes its move and gives the turn back to the player before screen updates. Later, you will be adding a timer so that the computer player's actions are not instantaneous, and the Done button will become visible during that time.

Updating the matchesInRows and rowsWithMatches arrays

If the computer player is playing then the matchesInRows and rowsWithMatches arrays need to be updated after every move, so that the computer knows how many matches are visible in each row. Add a couple of lines to the nextTurn function, and create a new takeFromRow function:

  ...

  // Code that runs when the Done button is clicked
  function nextTurn() {
    if (!(active.classList.contains("enabled")) {
      return
    }

    winner = active.querySelector("p").textContent

    var next = document.querySelector(".turns div:not(.active)")
    active.classList.remove("active")
    active.classList.remove("enabled")

    active = next
    active.classList.add("active")   

    if (useAI) {
      takeFromRow(rowIndex, taken)
      taken = 0

      computerToPlay = !computerToPlay
      if (computerToPlay) {
        computerChooseMove()
      }
    }

    rowIndex = -1
  }

  function takeFromRow(row, taken) {
    var remaining = matchesInRows[rowIndex] - taken
    var index;

    matchesInRows[rowIndex] = remaining
    if (!remaining) {
      index = rowsWithMatches.indexOf(rowIndex)
      rowsWithMatches.splice(index, 1)
    }
  }

  ...

The takeFromRow function first calculates how many matches are left in the row from which matches were taken and updates the appropriate entry in matchesInRows accordingly. If there is at least one match remaining, then remaining will be a positive integer, which JavaScript treats as truthy.

To be more precise, when evaluating a Boolean expression, JavaScript considers the number 0 to be the same as false and any other number to be equivalent to true. For more details see the optional Learn More About Truthy and Falsy values below.

If all the matches have been taken from the chosen row, then the computer player can no longer take any matches from that row. The entry for that row is removed from the rowsWithMatches array, using the splice command. For details on the splice command, see Learn More About splice below.

For example, if all the matches are removed from the second row (whose index is 1), then matchesInRows may look like this [7, 0, 3, 1] and rowsWithMatches will lose its second value so that it looks like this: [0, 2, 3]

Save your changes and refresh your index.html page in the browser. You can now choose Start New Game Against Computer or Computer To Start New Game, and the computer will play against you.

The computer will make a legal move each time, but the chances are that it will not be a good move. This means that you can win.

If you play the game many times, you will begin to see patterns emerging. When there are only a few matches left, you will see that certain combinations of matches give a position from which you can be sure to win. For other combinations, you can only win if the computer player makes a mistake.

You might like to use lateral thinking. You can play the game using real matches (or coins, or pebbles, or chocolate chips), and start at the end of the game and work backwards. What are the combinations where the other player is sure to lose? What other combinations guarantee that whatever the current player plays, you can set up a known combination where the other player is sure to lose? Such a combination is also a losing combination.

In order to win, should you start the game, or allow the other player to start?

There are 20 winning combinations. Before you move on to the next step, see if you can identify them all. Note the winning combinations down, they will be useful.

When you program the artificial intelligence of the computer player, you can use these 20 combinations as a way of teaching the computer the strategy. As a human, holding 20 combinations in your head can be hard work. There is a simpler technique for a human to decide at a glance if any combination gives a winning position. If you can find this technique for yourself, you can be truly proud.

If you want a clue, roll your mouse over the empty space below.

bitwise XOR parity check

Pairs of groups of 4, 2 and 1

Win at Nim

Make sure that you understand the strategy before you continue. Copying code that you don't understand is one aspect of cargo cult programming.

Every value that can be expressed in JavaScript can be evaluated as either equivalent to true (truthy) or equivalent to false (falsy). These values are always considered to be equivalent to false:

  • false
  • 0 (zero)
  • "" (empty string)
  • null
  • undefined
  • NaN (Not a Number)

All other values are considered to be equivalent to true. For more details, see: Truthy and Falsy: When All is Not Equal in JavaScript

Winning Combinations

In this step, you'll learn:
  • Which combinations of matches represent losing positions
  • How to create a single combination from many permutations
  • How to check whether the current position is a losing combination
Download the source files Test here

If you've come to this page without being able to beat the game with the random computer player more than half the time, then please continue to elaborate a winning strategy before you continue.

As you should have noticed, the following are losing combinations of matches, starting with the most obvious:

  • 1
  • 1, 1, 1
  • 1, 2, 3
  • 1, 4, 5
  • 2, 2
  • 3, 3
  • 4, 4
  • 5, 5
  • 1, 1, 2, 2
  • 1, 1, 3, 3
  • 1, 1, 4, 4
  • 1, 1, 5, 5
  • 2, 4, 6
  • 3, 5, 6
  • 2, 5, 7
  • 3, 4, 7
  • 1, 2, 5, 6
  • 1, 3, 4, 6
  • 1, 2, 4, 7
  • 1, 3, 5, 7

If it is your turn to play, and the matches are in any of the combinations above, and you opponent knows about these combinations, then you will lose. Alternatively, if the matches are in any other combination, then you can take just the right number of matches from just the right row to create one of these losing combinations, so that you can force your opponent to lose.

Permutations

Most of these combinations can only occur in one way. However, for those shown in italics there are are several possible permutations. The final match that the losing player must take may be in any of the four rows, for instance. The combination 1, 1, 1 could appear as any of the following perumtations:

  • 1, 1, 1, 0
  • 1, 1, 0, 1
  • 1, 0, 1, 1
  • 0, 1, 1, 1

The computer player needs to solve two problems:

  • How to recognize a losing combination, regardless of the permutation.
  • How to create a losing combination from any combination that could lead to a win

Sorting permutations

If you arrange the number of matches in order of the number of matches in each row (rather than by the row numbers), all permutations of the same numbers will reduce to the same combination.

In the Console pane, try this:

[3,1,4,5,9,6,2].sort()
[1, 2, 3, 4, 5, 6, 9]

You can find more information on the array.sort() method here.

Comparing arrays

JavaScripts comparison operators are not designed for comparing arrays. If you take two different arrays with identical contents, and you compare them using the == equals operator, you will get the result false...

[1, 2, 3, 4, 5, 6, 9] == [1, 2, 3, 4, 5, 6, 9]
false

... because the two arrays are stored in different places in the computer memory. The purist's way of comparing two arrays would be to:

  • Check if the arrays are the same length
  • If so, check that each item in each array has the same value

A much simpler way is to convert the array to strings and compare the strings. When JavaScript compares strings it performs the to operations above automatically. To join all the values of an array into a string, you can use the join() method. Try this in the Console pane:

[1, 2, 3, 4, 5, 6, 9].join("") === "1234569"
true

Notice that this technique allows you to use the clean strict equals === operator rather than its "evil twin" ==.

Checking for a losing position

Here are three changes that you can make to your nim.js file.

;(function() {
  var active
  var winner
  var useAI
  var computerToPlay
  var rowsWithMatches
  var matchesInRows
  var taken
  var rowIndex = -1
  var rows = document.querySelectorAll(".matches")
  var winnerDiv = document.querySelector("#winner")
  var losers = ["1357", "1247", "1256", "1346",  "1155", "1144",
  "1133", "1122", "0257", "0347", "0356", "0246", "0145", "0123",
  "0111", "0055", "0044", "0033", "0022", "0001"]

  ...

  function computerChooseMove() {
    var string = convertToString(matchesInRows);

    if (losers.indexOf(string) < 0) {
      findWinningMove()
    } else {
      chooseRandomMove()
    }

    hideMatches()
    gameOver = checkForWinner()

    if (!gameOver) {
      active.classList.add("enabled")
      nextTurn()
    }
  }

  function convertToString(array) {
    var string;

    array = array.slice(0) // use clone of array
    array.sort() // [7, 5, 0, 1] => [0, 1, 5, 7]
    string = array.join(""); // "0157"

    return string;
  }

  ...

When the script first loads, you create a loser array of the combinations that place the current player in a losing position. When it's the computer's turn to play, you take the current values in the matchesInRows variable and convert them to a four-digit string. Then you use the array.indexOf() method to check if the current combination is a losing one.

If the current combination is a losing one, then the computer will be forced to make a random choice (and to "hope" that you will fail to take advantage of its weakness). If the current combination is not in the array of losing combinations, then the computer can make a move that creates a losing combination that you will have to deal with. After the computer has moved itself into a winning position, its victory is secure.

Note the use of ...
array = array.slice(0)

... to create a clone of the matchesInRows array before sorting the cloned array. If the matchesInRows array were itself sorted, the entries in the array might no longer correspond to the actual rows of matches, and the logic used in the next section would not work.

Right now, the game is broken. If you let the computer player get into a winnable position, it will just stop playing, because your findWinningMove function is still an empty stub. You can put that right in the next step.

If you have any problems with this step, please tell us what happened and we'll do our best to find a solution for you.

Making Intelligent Moves

In this step, you'll learn:
  • How to count the matches in each row
  • Identify the special cases where the winning move leaves only one match, or three rows with only one match each
  • Treat these two cases
  • Determine how many matches to take from which row in all other cases
Download the source files Test here

The findWinningMove function is currently a stub: it does nothing. If you study the list of losing combinations at the top of the previous section, you may notice something interesting. Apart from the first two combinations (1 and 1, 1, 1, all the other combinations share a common characteristic: they can be divided into pairs of 1, 2 and 4 matches. In other words, the winning technique requires you to count using the binary system, in base 2.

For example, 1, 2, 3 can be divided into 1, 2, 1+2, with one pair of 1s and one pair of 2s. Similarly, 1, 3, 4, 6 can be divided into 1, 1+2, 4, 2+4, with one pair each of 1s, 2s and 4s. You may like to check through all the other combinations to see how they can be divided up this way.

The findWinningMove function will need to notice when 1 or 1, 1, 1 is the winning move, and all the other cases. This section will deal with both situations.

Identifying a winning move with rows containing only one match

You can look at the winning move positions 1 or 1, 1, 1 and think back one step. The only way to arrive at one of these two positions is if there is no more than one row which contains more than one match. In the case of 1, 1 and 1, 1, 1, 1, there may be no rows with multiple matches. All the other cases have at least two rows with multiple matches.

The code in the listing below checks each row of matches in turn and evaluates three variables:

  • rowsWithOneMatch
  • rowsWithMultipleMatches
  • rowToTakeFrom

If there is only one row with multiple matches, then that will be the rowToTakeFrom. If there are two rows with multiple matches rowToTakeFrom will be ignored.

...

  function findWinningMove() {   
    var rowsWithOneMatch = 0;
    var rowsWithMultipleMatches = 0;
    var rowToTakeFrom;

    (function checkMatchCounts() {
      var matchesInRow;
      for (var ii=0; ii<matchesInRows.length; ii++) {
        matchesInRow = matchesInRows[ii];

        switch (matchesInRow) {
          case 0:
          break;

          case 1:
            rowsWithOneMatch++;
          break;

          default:
            rowsWithMultipleMatches++;
            rowToTakeFrom = ii;
        }
      }
    })();

    // more code goes here
  }

...

Notice the use of a nested IIFE, so that you can use code folding to minimize the checkMatchCounts function, and keep your code neater.

Finding the winning move with only one match per row

If there are less than two rows with multiple matches, then you want to reduce remove all or all but one of the matches in the longest row, to create and odd number of rows with one match.

If all rows already have only one match then you can take a match from any row. Since this row only has one match, then you must take all of the matches in that row. If one row has multiple matches, then you must take all the matches in that row if there is an even number of rows left; if there is an odd number of rows left, then you must leave one match in this longest row.


  function findWinningMove() {   
    var rowsWithOneMatch = 0;
    var rowsWithMultipleMatches = 0;
    var rowToTakeFrom;

    (function checkMatchCounts() {
      var matchesInRow;
      for (var ii=0; ii<matchesInRows.length; ii++) {
        matchesInRow = matchesInRows[ii];

        switch (matchesInRow) {
          case 0:
          break;

          case 1:
            rowsWithOneMatch++;
          break;

          default:
            rowsWithMultipleMatches++;
            rowToTakeFrom = ii;
        }
      }
    })();

    if (rowsWithMultipleMatches < 2) {
      createOddNumberOfRowsWithOneMatch();
    } else {
      // TODO
    }

    function createOddNumberOfRowsWithOneMatch() {
      var adjust = 0;

      if (!rowsWithMultipleMatches)) {
        // No row has more than one match. Take the first match.
        rowIndex = rowsWithMatches[0];
        adjust = 1;
      } else {
        rowIndex = rowToTakeFrom;
      }

      if ((rowsWithOneMatch + adjust) % 2) {
        // Delete all matches in the row with multiple matches
        taken = matchesInRows[rowIndex];
      } else {
        // Leave one match in the row with multiple matches
        taken = matchesInRows[rowIndex] - 1;
      }
    }

    // more code goes here
  }

...

Dealing with multiple rows with multiple matches

If you followed the hidden links at the end of 39: Random moves, you should have some understanding now of the ^ (XOR / exclusive or) operator. In a nutshell, this compares the values of each bit of one binary number with the corresponding bit of another binary number, and outputs a third binary number which has 0s where the bits are the same and 1s where the bits are different. For example:

decimal 5 ^ 3 = binary 101 ^ 011 = binary 110 = decimal 6

The winning strategy in Nim is to leave your partner with a combination where the matches can be arranged in pairs of 4s, 2s and 1s. In other words, if you XOR together the numbers of matches in each row in a losing position, any pairs of 4 matches should cancel, any pairs of 2 matches should cancel and any pairs of 1 match should cancel, leave a result of 0.

When you have a winning position, the trick is to find which row you need to remove matches from, and how many matches your need to remove, in order to convert it into a losing position for your opponent.

Here is a function that does works through the number of matches in each row, and sets rowIndex and taken in a way that produces a losing combination:

...

  function computerChooseMove() {
    var string = convertToString(matchesInRows);

    if (winners.indexOf(string) < 0) {
      findWinningMove();
   } else {
      chooseRandomMove();
    }

    hideMatches();
    gameOver = checkForWinner();

    if (!gameOver) {
      active.classList.add("enabled");
      nextTurn();
    }
  }

  ...

  function findWinningMove() {   
    var rowsWithOneMatch = 0;
    var rowsWithMultipleMatches = 0;
    var rowToTakeFrom;

    (function checkMatchCounts() {
      // code omitted for clarity
    })();

    if (rowsWithMultipleMatches < 2) {
      createOddNumberOfRowsWithOneMatch();
    } else {
      reduceXORtoZero();
    }

    
    function createOddNumberOfRowsWithOneMatch() {
      // code omitted for clarity
    }

    function reduceXORtoZero() {
      var excess = 0;
      var matchCount, leave;

      for (var ii=0; ii<matchesInRows.length; ii++) {
        excess = excess ^ matchesInRows[ii];
      }

      for (var ii=0; ii<matchesInRows.length; ii++) {
        matchCount = matchesInRows[ii];
        leave = excess ^ matchCount;
        if (leave > matchCount) {
          continue;
        }

        rowIndex = ii;
        taken = matchCount - leave;
        break;
      }
    }
  }

...

The first for loop determines the excess number of matches. The second for loop checks each row of matches to see if it there are enough matches in that row to remove the excess number of matches from it.

The continue instruction jumps back to the beginning of the loop without running the remaining instructions in the loop. The break instruction jumps out of the loop completely.

When both rowIndex and taken have been set, the next instruction is to hideMatches, which you saw in 39: Random moves. If the game is not yet over, the nextTurn function will ask the human player to play again.

Taking Care of the Final Details

In this step, you'll learn:
  • How to
Download the source files Test here

Showing the Rules

In this step, you'll learn:
  • How to
Download the source files

Invisible data

In this step, you'll learn:
  • How to
Download the source files

Overview

This tutorial shows you how to create a simple strategy game, where the computer will beat you every time, unless you know how to avoid the trap.

You will be learning to write HTML, CSS and JavaScript. You will also learn how to use GitHub to save and display your work, and how to write code that tests your code. You don't need any previous knowledge of programming, but an inquisitive mind is a bonus.

You can see the completed game here. You might like to try to beat the computer before you learn to write the code that will beat all other players.

HTML

HTML would be the food on your plate. HTML provides the content and the structure of the information in your web page.

A web page that just contains HTML can be informative, and can contain links to other pages.

CSS

CSS would be the way the table is laid and the way the food is presented on your plate. CSS takes care of the colour, shape and position of the HTML elements. It's what gives the page its style.

A web page that just contains CSS is like a deserted restaurant: neat and decorative surroundings, all prepared for something to happen.

JavaScript

JavaScript would be the animation and conversation. JavaScript is what makes the web page come to life, with interactions, animations, cause and effect. JavaScript can connect you to other internauts in other places, making the web a social place to be.

A web page that just contains JavaScript can create all the HTML and CSS that it needs.

Git

Git would be the event organizer, discreetly ensuring that everything is going to plan.

A web page does not need Git to manage it. When you first start creating web pages they will be simple enough for you to keep all your ideas tidy in your head as you are working. But as your experience and your initiatives grow, you will be happy to have Git to keep track of everything for you. Working with Git right from the beginning, on your first small projects, will give you confidence that you are ready to tackle larger projects.

Everyone is a beginner at something, and has expertise in other areas. This tutorial was written with the understanding that some pages might teach you nothing new, and others might present you with a challenge.

In this tutorial, each page starts with the simplest instructions that you need to get to the next page. If these instructions work for you and you understand why they work, then there is no more to be said.

At the end of this starter section, you'll see a green box something like this:

If you're ready to go on, click here or on the Next arrow below, to go to the next page. If you want to know more, you can read the optional sections below.

If you can complete the entire tutorial without reading any of the optional sections below the green box, congratulations! We'd like to hear from you, so that we can feel good, too.

On the other hand, if you read all the optional material carefully, and you are still unable to get the instructions to work for you, then please let us know. We'll do all we can to make the instructions and explanations better.

It would be helpful if you can tell us:

  • What operating system you are using (eg. Windows 8.1, Mac 10.8.5 Mountain Lion, Ubuntu Utopic Unicorn...)
  • What browser you are testing your site with (Google Chrome, Mozilla Firefox, Internet Explorer 9...)
  • What text editor you are using to write your code. (If you are using Sublime Text, we may be able to be more helpful)
  • What version of Git you have running.
  • Where we can find your GitHub repository, so that we can see what you've done so far.
  • What's not working out for you...

If you have any difficulty anywhere in this tutorial, please do create a new issue for us, and we'll do our best to deal with it for you. Keep in touch. Your questions and suggestions can help us make this tutorial better.