Assignment: Concentration

The game of Concentration (which also goes by many other names) is very old. This version was developed by Ellen Hildreth for CS 110.

Goal

You'll build a web application that will have grid of images, all of them blank (displaying a 150x150 pixel image of white pixels). However, each is clickable to reveal a picture (like turning over a card). You click on them in pairs.

  • for the first click, the picture is revealed. (if the picture is already revealed, you can do nothing).
  • for the second click, the picture is also revealed, but then the pictures are compared, and if they aren't the same, they both get hidden again, after a short delay (1 second) so that the user can see the pictures.

Reference Version

Here is my obfuscated reference solution

You are welcome to use my HTML and CSS as your starting point. You should use the supplied.js file, which provides some helper functions.

You must replace the solution.js file with your own code.

Note: if you click too fast, or if you click where you're not supposed to (such as on a previously matched picture), you can confused the app. Don't do that. You may make the same assumption in your own app: it'll be played properly, so you don't have to protect against improper usage.

Demo

If you want to see a demo of the game being played, you can view this:

Game Play

Your Game of Concentration will be played on a 4x4 game board whose cells initially contain blank images, as shown in this initial game display. Each cell will have a "hidden" image that appears when the user clicks on the blank cell. In each round of the game, the user will select two blank cells by clicking on them, one after the other. As the user clicks on each blank cell, the hidden image should appear. If the two cells contain matching images, the images should remain visible. If the two images do not match, a message should appear on the page in a visible location indicating that there is no match.

Your program should keep track of, and display, the total number of clicks and matches as the game progresses, as shown in this game in progress, which has three pairs of matching images that were found after 16 clicks. When the user succeeds at finding all 8 pairs of matching images, a message should appear on the page, congratulating the user on their success and indicating how many clicks were needed to finish the game. The object of this version of the game is to finish with the fewest number of clicks (beware! once you have a working game, it can become addictive!). The game display should include a button that the user can click to start a new game, as shown in the bottom right corner of the sample game displays.

Making the images disappear

There's one tricky thing that we need to help you with, and that is getting the images to disappear after 1 second. JavaScript has a built-in function called setTimeout that takes two arguments: a function and an integer. The integer says how long to wait (in milliseconds) before executing the function. Copy/paste the following into a JavaScript console to see how it works:

setTimeout( function () { alert("done!"); }, 1000);

So, to make the images disappear after after 1 second, just write the appropriate function and use setTimeout to invoke it after 1000 milliseconds.

(This is yet another callback function.)

Setup

  1. Create a concentration folder for this work
  2. Copy the game.html file from the reference solution. The CSS is included
  3. Copy the folder of images
  4. Create an empty solution.js file

The following commands will get you started:

cd ~/public_html/cs204-assignments/
mkdir concentration
cp ~cs204/pub/assignments/concentration/solution/game.html concentration/
cp ~cs204/pub/assignments/concentration/solution/blank.jpg concentration/
cp -r ~cs204/pub/assignments/concentration/solution/images/ concentration/

Try that page in the browser to make sure everything works.

The Supplied Code

We provide two JavaScript functions, named shuffleImages() and getImage(), to assist with your implementation of the game. The definitions of these functions are stored in an external JavaScript file that can be loaded with the following statement that you should add to the bottom of your page, after the loading of jQuery, and before your own code. Here's the URL:

https://cs.wellesley.edu/~cs204/assignments/concentration/supplied.js

(You're welcome to look at that code; there are no secrets.

The shuffleImages() function generates a random configuration of the 8 pairs of images for the game board. This function has no inputs or outputs and can be invoked as follows:

shuffleImages();

Note that the shuffleImages() function initializes the board, so you must invoke that before the getImage() function will work.

The getImage() function will be used to get the filename for the hidden image associated with each cell on the game board. The getImage() function has a single input that is the id for a particular img element on the game board (e.g. "cell1", "cell2", etc.). This function returns a string that specifies a relative URL for the hidden image for the cell associated with the input id. For example:

> getImage('cell4')
< "images/im1.jpg"

The value is the URL for the hidden image associated with the img element whose id is "cell4" (top right corner). The URL, images/im1.jpg, is the image for the cardinal. Try it out a little using the JS Console, so that you are comfortable with it. You can try it in the reference solution as well.

Note that the relative URL assumes that the images are in a subfolder called images, so don't use a different name for your folder of images. The cp -r command, above, will give you a folder with that name. If you want to use different pictures, you're welcome to do so, but put them in that folder and rename them.

While shuffling is important to have a real game, it makes things harder to test. If you add ?grading to the end of the URL, the shuffleImages() function will set up the board but not shuffle it, so the 3rd row is the same as the first row and the fourth row is the same as the second row, which makes testing a lot easier!

Building the Concentration Game Incrementally

When faced with a large programming task, it is always best to develop the implementation incrementally. We recommend that you work "bottom" up, building pieces that you can test to make sure they work.

Stage 1: Reveal and conceal images on the game board

After completing this first stage, you'll be able to click on each blank cell on the game board and the following actions will take place: (1) the hidden image will appear in the cell, and 1 second later, the image will disappear. To complete this stage, add JavaScript code to do the following:

(1) invoke the shuffleImages() function to create a random configuration of hidden images. This should happen when the page loads, so it only happens once.

(2) define a function named showImage() that displays the hidden image for a blank cell that the user clicked. This function should have one parameter that is the id for the img element associated with the cell that the user clicked. The showImage() function should do the following


  • change the SRC attribute for the image with the given ID to be the url returned by getImage()

You can and should test the showImage() function using the JS console. For example, the following will reveal the image associated with cell 4, which is the upper right cell:

showImage('cell4')

(3) define a function named hideImage() that changes a cell back to the blank image. This function should also have one parameter that is the id for the <img> element associated with the cell that the user clicked. The hideImage() function should change the SRC attribute for the image with the given ID back to the blank.jpg image.

(4) You can and should also test this function using the JS console. The following will conceal the image associated with the upper right cell. (You can try this with the reference solution.)

hideImage('cell4')

(5) define a function named processClick() that processes the user's clicks. This function will take one argument, the ID of the image that was clicked on. This API allows you to easily test it from the JS console and makes the event handler as lean as possible. (The processClick() function is not the event handler; it can't be, because it takes a different kind of argument.) For this first stage, the processClick() function should just do the following steps:

  1. invoke the showImage() function with the ID as an argument, in order to to reveal the hidden image,
  2. it should use setTimeout() to make have the image be hidden again 1 second later.
  3. the function in the timeout will invoke hideImage() with the ID as an argument, to conceal the image again.

Testing Your Code

You can test the processClick function in the JS console (as you can all of these) just by calling them. E.g. to simulate clicking on the upper left cell, do:

processClick('cell4');

You can try pairs of clicks in the reference solution as well.

Stage 2 Handler Attachment

(Revised for Fall 2021)

Finally, you must invoke processClick() when one of the img elements is clicked. This can be done by attaching a function (that invokes processClick with the appropriate argument) to every game image as a click handler.

You should do this the same way the event handlers were added to each thumbnail in Ottergram, using the jQuery each method. This would be a good time to go back and review Ottergram, looking carefully at how the event handlers were attached to the images. The basic outline is:

  1. gather a jQuery set of the clickable images
  2. use the .each method on that array to
  3. add an event handler to each one

Note that you will have to make sure each event handler knows the ID of the image that it's attached to, since that's the argument to processClick. There are several ways to do this.

  • You can read the id attribute off the IMG element, using the one-argument version of the jQuery attr method, or
  • You can construct the id from the index that is supplied by the .each method as the first argument of the callback.

Ideally, you should make sure this code works before proceeding to the next stage, but it's possible that you will get stuck, because attaching the event handlers is a little tricky. So, if you do get stuck, you can instead proceed to the next stage, improving the click handling and testing your click handling in the JS console like this:

processClick('cell4');
processClick('cell5');

(This simulates clicking on those two cells).

Still, it'll be nice when you can test your code by clicking, so it's worth getting the handlers to work.

Finally, a common mistake in attaching handlers is addressed here in functions as arguments. If you have trouble, I recommend refreshing your memory on that.

Another common mistake is to have a click handler to add the click handlers or, sometimes, to have the click handler add itself. Adding click handlers is not a recursive process. Just as Ottergram and in all our assignments so far, your code to add event handlers will run when the page loads.

When you're successful with this state, you've connected clicks to behavior; from now on, we'll be improving the behavior.

Stage 3: Distinguish the first and second clicks

So far, the processClick function treats every click the same. We need to change it to treat first clicks differently from second clicks.

After completing this second stage, you'll be able to click on pairs of blank cells on the game board and have both hidden images revealed. After a few seconds, both images will be hidden again.

Create a global variable to store whether the user is clicking for the first time or the second time. I suggest it have values of 1 or 2. Initialize it to 1.

Create a second global variable to store the ID of the first cell that was clicked on.

Modify the processClick function so that if it's a first click, the image is not hidden, but the cell's ID is stored in the global variable. If it's a second click, both cells are hidden again after a suitable delay.

The processClick function also needs to manage the variable about whether it's a first click or a second click. That's probably the trickiest part of this stage. Here's the idea:

  • If it's a first click, show the image and set the click number to 2.
  • If it's a second click, hide the images and set the click number back to 1.
  • You can tell which it is by looking at the click number global variable.

Test to make sure this code works before going on. You should be able to:

  1. click on a first image, having it stay up indefinitely until
  2. you click on a second image, then both disappear after a short delay.
  3. And repeat.

Stage 4: Check for Matches

Again, we'll modify the processClick function. Make sure you don't break the previous behavior. It might be smart to save a copy of your solution to stage 3, in case you get confused during stage 4 and want to start over.

In the code to process a second click, modify the code to compare the URLs that are returned by getImage for the first click (you saved its ID in a global variable) and for the second click (the argument).

If the URLs are the same, then the images match.

  • If so, put a message on the screen to inform the user, and don't hide the two images.
  • If they don't match, hide the two images as usual.

Test your code for this stage to be sure it works correctly before proceeding to the next stage!

Stage 5: Keep track of the status of the game

At this point, the game pretty much works, but we need to count stuff to keep track of how the player is proceeding. We'll count tries and matches.

Create two additional global variables to store the following information:

  • the number of tries the user has made so far, initialized to 0
  • the number of matches the user has found so far, initialized to 0

Modify the processClick() function to record and display the status of the game. In handling second clicks, increment the number of tries and, if there's a match, increment the number of matches.

Update the text on the page to show these numbers.

If the number of matches is 8, the game is over; that's a different message. Congratulate the user.

Test your code for this stage to be sure it works correctly before proceeding to the next stage!

Stage 6: Allow the user to start a new game

After this final stage is complete, the user will be able to start a new game without having to reload the page, by clicking the button. This final stage can be completed as follows:

  • Define a function named startNewGame() that starts a new game for the user. This function should have no parameters and should perform the following actions:
  • invoke shuffleImages() to generate a new random arrangement of images for the new game
  • reset the global variables for the start of a new game
  • reset the text on the webpage to indicate 0 clicks and 0 matches
  • change all the cells of the board to the blank image (you can use your hideImage() function here, multiple times, or think of an even better way using jQuery).
  • Attach the startNewGame() function to be the click handler for the button on your page.

If you invoke this function when the page loads, you'll be all set for the first game, and you won't have to invoke shuffleImages() by hand.

Test this. When it works, you're done!

Note You can assume that the user plays the game correctly! For example, you can assume that the user only clicks on blank cells, and that they only click on two cells for each round of the game.

How to turn this in

We'll grade the work in your cs204-assignments/concentration folder. Just submit the Gradescope item.

Tutors/Graders

You can use the password to view the Concentration

Time and Work

The following link has been updated for Fall 2023.

Finally, when you have completed the assignment, make sure you fill out the Time and Work Fall 2023 That report is required.