Advertisement

Build a Flickr-Based Pairs Game With JavaScript: Gameplay

by

In this two-part tutorial, you'll learn how to use JavaScript and HTML to create a card matching game powered by the Flickr API. Check out the demo, and read on!


Also available in this series:

  1. Build a Flickr-Based Pairs Game With JavaScript: Gameplay
  2. Build a Flickr-Based Pairs Game With JavaScript: Polish

Step 1: Getting a Flickr Account

To be able to use the Flickr API you must be a registered user on flickr.com. From the homepage of Flickr, click the Sign Up link.



Step 2: Obtaining an API Key

Once logged in, you will need to visit the App Garden to get started.

You will want to bookmark this page if you are planning on doing a lot of Flickr development, as it contains a lot of useful information for developers.

Click on the “Create An App” link once you arrive at the App Garden.


Under “Get your API Key”, click the “Request an API Key” link.


Here you will need to choose whether you intend to use the app for commercial or non-commercial purposes. For this game I chose non-commercial.


Next you will be taken to the App Details page. Enter the name of your app and a description of what your app does, accept the agreements, and click the Submit button.


Next, you will be presented with your key and your secret. We will not be using the secret key here as our app does not require authentication. Make a note of your API key, as we will need it for the rest of this tutorial.



Step 3: Diving Into the Flickr API

Flickr has a REST API for developers. We will be using these two methods from the API:

The flickr.photos.search method will allow us to search for photos, while the flickr.photos.getInfo method will allow us to get information for a single photo, such as the username (owner) of the photo, the title of the photo, and the URL to the photo's page on Flickr.

If you visit one of the links above, at the bottom of the page there is a link to the API Explorer where you can enter some data and get an example response.



The image above is for the flickr.photos.search method. Go ahead and click the link now.

There are lots of options, and it may seem overwhelming, but all we are interested in for this tutorial is the “tags” option; I entered “dog” into the tags search box. Choose JSON as the output method, as we will be using JSON in this tutorial.



Finally, press the “Call Method” button, and an example of the response will be returned. Here's a portion of the sort of data you can expect to receive - the last few lines have been cut out:

 
{ "photos": { "page": 1, "pages": "49825", "perpage": 100, "total": "4982408",  
    "photo": [ 
      { "id": "7338255166", "owner": "45596890@N00", "secret": "5f145a92b8", "server": "7083", "farm": 8, "title": "Cute Rhea", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338254718", "owner": "45596890@N00", "secret": "9e1da794a3", "server": "7223", "farm": 8, "title": "Rhea chewing", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338258850", "owner": "8672236@N04", "secret": "d0a5c4124c", "server": "8027", "farm": 9, "title": "", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338271122", "owner": "49270434@N08", "secret": "30876cfdf6", "server": "7236", "farm": 8, "title": "286", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338235972", "owner": "8640124@N02", "secret": "a583d6aa48", "server": "7085", "farm": 8, "title": "Y:4 Day 123: Wind sweeps", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7330075254", "owner": "32081016@N07", "secret": "04cb99e8a7", "server": "7239", "farm": 8, "title": "Mad dogs and Englishmen.....", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338229968", "owner": "67178219@N06", "secret": "92b4be222a", "server": "7095", "farm": 8, "title": "Le petit chien dans la prairie", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338185610", "owner": "7315798@N04", "secret": "9a91bd1280", "server": "7224", "farm": 8, "title": "snuggle", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338111264", "owner": "80044484@N06", "secret": "f795c559e3", "server": "7214", "farm": 8, "title": "DSC_0408", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338135744", "owner": "80023312@N07", "secret": "d37c015be6", "server": "7234", "farm": 8, "title": "0007_Ridgeback", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338136008", "owner": "80023312@N07", "secret": "259e50ebaa", "server": "7219", "farm": 8, "title": "0003_Pug", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338133290", "owner": "80044484@N06", "secret": "a2e954aab0", "server": "7098", "farm": 8, "title": "_DSC0032", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338142010", "owner": "80023312@N07", "secret": "34809c804f", "server": "7071", "farm": 8, "title": "0009_WireFox", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338014824", "owner": "36700168@N04", "secret": "2625ab12a4", "server": "8164", "farm": 9, "title": "Nosferatus` dog", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338092614", "owner": "80023312@N07", "secret": "ee7210e0f1", "server": "8010", "farm": 9, "title": "0002_GreatDane", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338120200", "owner": "80023312@N07", "secret": "38b5c49f4f", "server": "7245", "farm": 8, "title": "0006_Cavalier", "ispublic": 1, "isfriend": 0, "isfamily": 0 }, 
      { "id": "7338159814", "owner": "7315798@N04", "secret": "327e97cb13", "server": "7104", "farm": 8, "title": "puppy love", "ispublic": 1, "isfriend": 0, "isfamily": 0 },

We will be using the data from the response to construct our URLS to the images. The URLS take this form:

http://farm{farm-id}.static.flickr.com/{server-id}/{id}_{secret}_[mstzb].jpg

To make a usable URL we just replace what is inside the {} with the data we get from above. For example, the first item from the photo array inside the above JSON is:

 
{  
    "id": "7338255166",  
    "owner": "45596890@N00",  
    "secret": "5f145a92b8",  
    "server": "7083",  
    "farm": 8,  
    "title": "Cute Rhea",  
    "ispublic": 1,  
    "isfriend": 0,  
    "isfamily": 0  
}

...so the URL we need to construct is this:

http://farm8.static.flickr.com/7083/7338255166_5f145a92b8_q.jpg

The "q" denotes the size of the image: 150x150px.

That link leads to this photo. More info about the URLs can be found in the Flickr Documentation for Photo Source URLS.


Step 4: Setting up the Project

In the source download there is a ZIP file named "StartHere.zip" that contains the files all laid out for you, ready to add some code. You can browse through these files to get and idea of the project's structure. I have also included a "README.txt" that explains the structure in more detail.

We are using the following JavaScript libraries in this project. You can download them separately if you wish.


Step 5: document.ready()

Inside the "js" folder you will find a JavaScript file name "flickrgame.js". Add the following code to this file.

 
$(document).ready(function() { 
	alert("Ready"); 
});

If you test the "index.html" page now, you should see an alert with the word "Ready".

This code uses jQuery's .ready() method, which gets fired when the DOM has completely loaded. It is important to call this method when coding your apps, because you need to ensure the DOM has been loaded before you try to manipulate it. For example, if you tried to add a click event handler to a button before the DOM was loaded and ready, it would have no effect.


Step 6: flickrGame Object Literal

We will be using an object called flickrGame to keep all of our variables neat and tidy and out of the global namespace.

Enter the following code within the $(document).ready() you added in the step above, replacing the alert.

 
$(document).ready(function() { 
	var flickrGame = { 
       
    } 
});

Step 7: Add Your API Key

Within the flickrGame object literal add the following code. Replace the "YOUR API KEY" with the API Key you received when you signed up.

 
$(document).ready(function() { 
	var flickrGame = { 
		APIKEY: "YOUR API KEY" 
    } 
});

Step 8: doSearch()

The doSearch() method will call the Flickr API. Enter the following beneath the flickrGame object literal definition (but still inside the $document.ready() function):

 
function doSearch() { 
	var searchURL = "http://api.flickr.com/services/rest/?method=flickr.photos.search"; 
	searchURL += "&api_key=" + flickrGame.APIKEY; 
	searchURL += "&tags=dog" 
	searchURL += "&per_page=36" 
	searchURL += "&license=5,7"; 
	searchURL += "&format=json"; 
	searchURL += "&jsoncallback=?"; 
	$.getJSON(searchURL, setImages); 
}

Here we build up our query step by step. We point the searchURL variable to the flickr.photos.search method of the Flickr API.

As it is a REST API, to call it we just need to construct a URL with the parameters required. So, here we are using the flickr.photos.search method, and requesting images tagged with "dog". We've set the format to "json" and set "jsoncallback=?", which makes the callback function execute as soon as the data is retrived.

By setting the license parameter tp 5,7 we are asking Flickr to only give us images with an Attribution-ShareAlike licence or with no known copyright license.

We then use jQuery's $.getJSON() method, passing in our searchURL and a reference to the callback function setImages() which we will code next.


Step 9: setImages()

In this step we will code the setImages() function and view the results we get back from Flickr. Enter the following code beneath the doSearch() function you added in the step above.

 
function setImages(data) { 
	var tempString = ""; 
	$.each(data.photos.photo, function (i, item) { 
		var imageURL = 'http://farm' + item.farm + '.static.flickr.com/' + item.server + '/' + item.id + '_' + item.secret + '_' + 'q.jpg'; 
		tempString += imageURL+"<br/>"; 
	}); 
	$("#urls").html(tempString); 
}

Here we initialise a variable tempString that will hold the URLs we get from Flickr. Within each object, the parameter data holds the JSON that was returned from the call to the Flickr API, as laid out in Step 3 - so we can see that the individual photo objects are to be found in data.photos.photo.

We can use jQuery's $.each() method to loop through each JSON photo object that was returned. Inside the loop we construct the imageURL, again as laid out in Step 3, then append it to tempString, and finally output tempString to the div named "urls" by directly modifying the HTML of the div.

The point here is to see what URLs we are getting from our call to the Flickr API. If you are following along with the source code, I added a div with id="urls" so we could view this in the web page. (I will be removing it in the next step.)

Lastly, to actually view it we need to call our doSearch() function. Add this right below the setImages() function.

 
doSearch();

You can test it here. It may take a second or two before you see this data, depending on your connection speed. Below are the first few URLs returned, in case you are not following along with the source code.

 
http://farm9.static.flickr.com/8025/7183874333_9b8b43dfe1_q.jpg 
http://farm9.static.flickr.com/8003/7368215016_3c42485ee9_q.jpg 
http://farm6.static.flickr.com/5467/7179453807_a4c871311f_q.jpg 
http://farm9.static.flickr.com/8002/7364322976_4f02a954ea_q.jpg 
http://farm9.static.flickr.com/8026/7177956063_6d92435602_q.jpg 
http://farm8.static.flickr.com/7221/7177959671_025cc2381b_q.jpg

Step 10: Storing the Images

We will be storing all the URLs we get back from Flickr in an array, rather than jammed together as a single string. Add the following to the flickrGame object.

 
var flickrGame = { 
		APIKEY: "76656089429ab3a6b97d7c899ece839d", 
		imageArray: [] 
    }

(Make sure you add the comma after the APIKEY: "YOUR API KEY".)

Now, add the following within the setImages() function. I removed the code from the previous step, as it was just for demonstrative purposes.

 
 $.each(data.photos.photo, function (i, item) { 
	var imageURL = 'http://farm' + item.farm + '.static.flickr.com/' + item.server + '/' + item.id + '_' + item.secret + '_' + 'q.jpg'; 
	flickrGame.imageArray.push({ 
		imageUrl: imageURL, 
		photoid: item.id 
	}); 
});

Here we push the imageURL and item.id onto the imageArray using the keys imageUrl and photoid respectively. We need the imageUrl to construct our images, and the photoid will be used to identify the particular photo. We also need to show an attribution for the photos we use, and by having their id we can link the user to the photo on Flickr.

The need for all of this data will become clearer as we move through the tutorial, so don't worry too much about it for now.


Step 11: infoLoaded()

The infoLoaded() function will be used for loading the information of the photos. After all of the photo information has been loaded, infoLoaded() will call a function named preloadImages() which will preload the images.

Once all of the images have have been preloaded we call drawImages(), which places the images on screen, and the game begins.

Add the following beneath the setImages() function.

 
function infoLoaded(data) { 
 
}

The data parameter will hold the JSON of each particular photo. We will come back to this in a bit; for now we will just use this function to call preloadImages() directly. Go ahead and add the call now:

 
function infoLoaded(data) { 
	preloadImages(); 
}

Step 12: preloadImages()

The preloadImages() function will be used to create and preload the images for the start of the game, and for each level. We preload them so there is no delay between when we show the images and when the are downloading.

We will come back to the preloading code later. For now, we will just create the first 12 images for the start of the game. We need a new array to hold these 12 images, so add the following to your flickrGame object:

 
var flickrGame = { 
	APIKEY: "76656089429ab3a6b97d7c899ece839d", 
	imageArray: [], 
	tempImages:[] 
}

Make sure you add a comma after imageArray: [].

The tempImages array is used to hold 12 images at a time. Our game uses 12 images per level, and the imageArray holds up to 36 images (depending on how many we set in our search to Flickr). So we will grab 12 images from the imageArray and store them in tempImages.

Add the following code beneath the infoLoaded() function you added in the step above.

 
function preloadImages(){ 
	flickrGame.tempImages = flickrGame.imageArray.splice(0, 12); 
	for (var i = 0; i < flickrGame.tempImages.length; i++) { 
		for (var j = 0; j < 2; j++) { 
			var tempImage = new Image(); 
			tempImage.src = "cardFront.png"; 
			tempImage.imageSource = flickrGame.tempImages[i].imageUrl; 
			flickrGame.theImages.push(tempImage); 
		} 
	} 
}

Here we use the javascript method splice() to pull 12 images out of the imageArray, and place them into the tempImages array. We then use a nested for loop to create 24 images.

For each of these 24 images, we create a new Image, set its src to "cardFront.png" (an image you'll see before the card is flipped over), create a new property named imageSource that contains the URL of the actual image from Flickr to be displayed, and then push it into the array theImages.

We need to add theImages to our flickrGame object, so do that now.

 
var flickrGame = { 
	APIKEY: "76656089429ab3a6b97d7c899ece839d", 
	imageArray: [], 
	tempImages:[], 
	theImages: [] 
}

Make sure you add the comma after tempImages[].

Lets go ahead and call the drawImages() function as well; once we've written it, this will render the images on the screen. Add the following after the nested for loop in the preloadImages() function.

 
function preloadImages(){ 
	flickrGame.tempImages = flickrGame.imageArray.splice(0, 12); 
	for (var i = 0; i < flickrGame.tempImages.length; i++) { 
		for (var j = 0; j < 2; j++) { 
			var tempImage = new Image(); 
			tempImage.src = "cardFront.png"; 
			tempImage.imageSource = flickrGame.tempImages[i].imageUrl; 
			flickrGame.theImages.push(tempImage); 
		} 
	} 
	drawImages(); 
}

Step 13: Shuffling and Rendering

The drawImages() function actually puts the images on screen. We first shuffle the images in the theImages array using a function called randSort and then append them to the #gamediv section of the HTML.

Enter the following code below the flickrGame object definition:

 
function randOrd() { 
	return (Math.round(Math.random()) - 0.5); 
}

This function is used to shuffle the images. We will call JavaScript's sort() method on an array and pass in randOrd as the parameter. This works in basically the same way as outlined in "The Sorting Approach" in this article.

Add the following beneath the preloadImages() function you added in the step above:

 
 function drawImages() { 
	flickrGame.theImages.sort(randOrd); 
	for (var i = 0; i < flickrGame.theImages.length; i++) { 
		$(flickrGame.theImages[i]).attr("class", "card").appendTo("#gamediv"); 
	} 
}

We call the sort method on theImages, passing in our randOrd function, then loop through theImages, appending each image to the #gamediv.

We use the jQuery method .attr to add a class of "card" to each image. Why? Because when we end a level, we remove the cards from the #gamediv; this gives us a way to target just the cards and not all images.

Here are links to jQuery's attr() and appendTo() methods so you can learn more about them.


Step 14: Testing our Progress

We should be ready to get some cards on the screen at this point. After the .each() call in setImages call the infoLoaded() function and test the page.

 
function setImages(data) { 
	$.each(data.photos.photo, function (i, item) { 
		var imageURL = 'http://farm' + item.farm + '.static.flickr.com/' + item.server + '/' + item.id + '_' + item.secret + '_' + 'q.jpg'; 
		flickrGame.imageArray.push({ 
				imageUrl: imageURL, 
				photoid: item.id 
			}); 
	}); 
	infoLoaded(); 
}

If all goes well you should see 24 images with the Flickr Logo on screen. It may take a moment before they show, because we are calling the Flickr API and loading the images as well.

We will make a small change to the code so we can see the actual images for now. Modify the preloadImages() function to show the actual image instead of the "cardFront.png":

 
function preloadImages(){ 
	flickrGame.tempImages = flickrGame.imageArray.splice(0, 12); 
	for (var i = 0; i < flickrGame.tempImages.length; i++) { 
		for (var j = 0; j < 2; j++) { 
			var tempImage = new Image(); 
			tempImage.src = flickrGame.tempImages[i].imageUrl;  //"cardFront.png"; 
			tempImage.imageSource = flickrGame.tempImages[i].imageUrl; 
			flickrGame.theImages.push(tempImage); 
		    
		} 
	} 
	drawImages(); 
}

Test the game now.


Step 15: Adding Click Listeners

The addListeners() function will add a click listener to each of the images that calls a function doFlip(), which in turn will reveal the image beneath (if the logo is currently displayed).

Add the following beneath the drawImages() function you added in the step above:

 
 function addListeners() { 
	for (var i = 0; i < flickrGame.theImages.length; i++) { 
		$(flickrGame.theImages[i]).on("click", function (e) { 
			doFlip(e); 
		}); 
	} 
}

We add the listeners in the drawImages() function:

 
function drawImages() { 
	flickrGame.theImages.sort(randOrd); 
	for (var i = 0; i < flickrGame.theImages.length; i++) { 
		$(flickrGame.theImages[i]).attr("class", "card").appendTo("#gamediv"); 
	} 
	addListeners(); 
}

Step 16: Removing Click Listeners

We need a way to remove the click listeners from the images as well. Add the following beneath the addListeners() function you added in the step above.

 
function removeListeners() { 
	for (var i = 0; i < flickrGame.theImages.length; i++) { 
		$(flickrGame.theImages[i]).off("click"); 
	} 
}

Step 17: doFlip()

The doFlip() function is called when the user clicks on one of the images. It displays the big images on the left and right of the main gamplay area, and then calls a function checkForMatch() which checks whether the images match.

Add the following beneath the removeListeners() function you added in the step above.

 
function doFlip(e) { 
	var theCard = e.target; 
	$(theCard).attr("src", theCard.imageSource); 
	if ($('#image1').css('backgroundImage') == "none") { 
		$('#image1').css('backgroundImage', 'url(' + theCard.imageSource + ')'); 
	} else { 
		$('#image2').css('backgroundImage', 'url(' + theCard.imageSource + ')'); 
	} 
	if (flickrGame.chosenCards.indexOf(theCard) == -1) { 
		flickrGame.chosenCards.push(theCard); 
		$(theCard).off("click"); 
	} 
 
	if (flickrGame.chosenCards.length == 2) { 
		removeListeners(); 
		checkForMatch(); 
 
	} 
}

We first get a reference to which card was clicked and set its src attribute to the URL of the actual image (rather than the Flickr logo).

We then check whether the CSS background of the big image on the left (#image1) is equal to "none", and if it is we set its background image to the same image as the card we clicked on. If it is not equal to "none" then that means a card has been clicked, so we set the background image of the big image on the right.

We check that the chosenCards array (which we will add in a moment) does not already contain this card, for safe housekeeping, and then push it onto the array. We also remove the click event from the card so they cannot click it again.

If the chosenCards length is equal to 2 it means the user has chosen two cards, so we call removeListeners() to remove the click event from the rest of the cards and call the checkForMatch() function (which we will code in the next step) to see if the selected cards match.

We need to add the chosenCards array to our flickrGame object, so do that now:

 
var flickrGame = { 
	APIKEY: "76656089429ab3a6b97d7c899ece839d", 
	imageArray: [], 
	tempImages:[], 
	theImages: [], 
	chosenCards: [] 
}

Make sure to add a comma after theImages[].


Step 18: checkForMatch()

The checkForMatch() function checks whether the two clicked cards match. If they do then we "hide" them; if they don't then we add the listeners back to the remaining cards and let the player have another go.

Add the following beneath the doFlip() function you added in the step above:

 
 function checkForMatch() { 
	if (flickrGame.chosenCards.length == 2) { 
		if ($("#image1").css('background-image') == $("#image2").css('background-image')) { 
			setTimeout(hideCards, 1000); 
		} else { 
			setTimeout(resetImages, 1000); 
		} 
	} 
 
}

Here, we check whether the chosenCards length is equal to 2. If it is, we check whether the two big images are the same by using their background-image. (We could have used the smaller images instead; as an exercise, see if you can do this.)

If the images do match, we use setTimeout() to call the hideCards() function after one second; otherwise we call resetImages() after one second. By delaying these function calls we add a very brief pause to the game.

We'll code the functions to hide the cards and reset the images next.


Step 19: hideCards()

If the user matched two cards, we hide them both and add the click listeners back to the rest of the cards.

Add the following code beneath the checkForMatch() function you created in the step above:

 
function hideCards() { 
	$(flickrGame.chosenCards[0]).animate({ 
		'opacity': '0' 
	}); 
	$(flickrGame.chosenCards[1]).animate({ 
		'opacity': '0' 
	}); 
	flickrGame.theImages.splice(flickrGame.theImages.indexOf(flickrGame.chosenCards[0]), 1); 
	flickrGame.theImages.splice(flickrGame.theImages.indexOf(flickrGame.chosenCards[1]), 1); 
	$("#image1").css('background-image', 'none'); 
	$("#image2").css('background-image', 'none'); 
	addListeners(); 
	flickrGame.chosenCards = new Array(); 
 
}

We use jQuery's animate() method to tween both cards' opacity to 0, making them appear to fade out. We also remove the images from the theImages array, reset the big images' backgrounds to "none", add the listeners back to the other images, and reset our chosenCards array.

Try it out here.


Step 20: resetImages()

This function is run if the two images selected do no match. It resets the small images' src attributes back to "cardFront.png", sets the big images' background to "none", adds the listeners back to the cards, and resets the chosenCards array.

Add the following beneath the hideCards function you added in the step above.

 
 function resetImages() { 
	$(flickrGame.chosenCards[0]).attr("src", "cardFront.png"); 
	$(flickrGame.chosenCards[1]).attr("src", "cardFront.png"); 
	$("#image1").css('background-image', 'none'); 
	$("#image2").css('background-image', 'none'); 
	addListeners(); 
	flickrGame.chosenCards = new Array(); 
}

You can actually play the game now - except that the images are showing. To change that back, alter the preloadImages() function like so:

 
function preloadImages(){ 
	flickrGame.tempImages = flickrGame.imageArray.splice(0, 12); 
	for (var i = 0; i < flickrGame.tempImages.length; i++) { 
		for (var j = 0; j < 2; j++) { 
			var tempImage = new Image(); 
			tempImage.src = "cardFront.png"; 
			tempImage.imageSource = flickrGame.tempImages[i].imageUrl; 
			flickrGame.theImages.push(tempImage); 
		    
		} 
	} 
	drawImages(); 
}

(It is good to change this back and forth for easy testing.)

Here's the result!


End of Part 1

That's the end of the first part of this tutorial. So far, you've made a functional HTML5 game; in the next part, you'll add polish, learning how to:

  • Let the player choose their own tag, rather than playing with pictures of dogs all the time.
  • Obtain and display attribution information for all of the photos used.
  • Add multiple rounds and a scoring system to the game.
  • Preload the images and display a progress bar for this.

See you then!

Advertisement