If you're seeing this message, it means we're having trouble loading external resources for Khan Academy.

If you're behind a web filter, please make sure that the domains *.kastatic.org and *.kasandbox.org are unblocked.

The first step of playing the "Memory" game is to randomly shuffle all the tiles, and then lay them out in a rectangular grid, face down so that we can't see which image is on the other side of each tile.

Face-down tiles

To start off in programming the game, let's just worry about creating face-down tiles, and figure out how to do the different images later.

The "tile" is an important enough object in the game of "Memory" that we will use object-oriented principles to define a Tile object and then create multiple instances of it. Then we'll be able associate both properties (like location and image) as well as behavior (like turning face down or up) with each of the Tiles.

To start off, we'll define the Tile constructor function. Since we're not dealing with the images yet, we'll just pass x and y arguments to it. We'll also remember the tile width (a constant) in a property on the object.

var Tile = function(x, y) {
    this.x = x;
    this.y = y;
    this.width = 70;
};

Now that we've defined the constructor, we can use that in a loop to create tiles at appropriate x and y positions. In fact, we'll use two for loops - a nested for loop - as that makes it conceptually easy to generate coordinates for a grid.

We start off by initializing an empty tiles array:

var tiles = [];

Our outer loop iterates for as many columns as we want, our inner loop iterates for each of the rows, and each new Tile is initialized with an x and y that corresponds to that row and column.

var NUM_COLS = 5;
var NUM_ROWS = 4;
for (var i = 0; i < NUM_COLS; i++) {
    for (var j = 0; j < NUM_ROWS; j++) {
        tiles.push(new Tile(i * 78 + 10, j * 78 + 40));
    }
}

But, uh, it's hard to know if the tiles will look good, because we don't have any code to draw them yet! In fact, maybe we should have done that first. Sometimes, in programming, it's hard to know what to do first, aye? Let's now add a method to the Tile object that draws a tile face-down on the canvas. We'll draw a rounded rectangle with a cute Khan leaf on top, at the assigned location.

Tile.prototype.drawFaceDown = function() {
    fill(214, 247, 202);
    strokeWeight(2);
    rect(this.x, this.y, this.width, this.width, 10);
    image(getImage("avatars/leaf-green"), this.x, this.y, this.width, this.width);
};

And now we can check how our tiles look. Let's add a new for loop that iterates through all the tiles and calls the drawing method on them:

for (var i = 0; i < tiles.length; i++) {
    tiles[i].drawFaceDown();
}

Here's what our program looks like, with all that code. Try tweaking the different numbers in the nested for loop to see how it changes the grid or changing how they're drawn (different logo, perhaps?)

Face-up tiles

Now that we've got a grid of face-down tiles, let's tackle a bit of a trickier problem: assigning each of them an image, such that there's 2 of every image in the array, randomly distributed throughout. There are likely many ways we could accomplish this, but here's what I'd suggest:

  1. We create an array of the possible images, using the getImage function to pick ones from our library.
  2. We'll only need 10 images for the faces of our 20 tiles, so then we create a new array that holds 2 copies of 10 randomly selected images from that first array.
  3. We randomize the selected images array, so that the pairs of images are no longer next to eachother in an array.
  4. In the nested for loop where we create the tiles, we'll assign an image from that array to each tile.

Those steps may not make sense yet - let's do them each and see what they look like.

Step 1: We create an array of the possible images, using the getImage function to pick ones from our library:

var faces = [
    getImage("avatars/leafers-seed"),
    getImage("avatars/leafers-seedling"),
    getImage("avatars/leafers-sapling"),
    getImage("avatars/leafers-tree"),
    getImage("avatars/leafers-ultimate"),
    getImage("avatars/marcimus"),
    getImage("avatars/mr-pants"),
    getImage("avatars/mr-pink"),
    getImage("avatars/old-spice-man"),
    getImage("avatars/robot_female_1"),
    getImage("avatars/piceratops-tree"),
    getImage("avatars/orange-juice-squid")
];

I picked a bunch of avatars, but you could change it to pick whatever your favorite images are. The important thing is to make sure that this array has at least 10 images in it, so that we don't run out of images for our 20 tiles. We can add lots more than 10 images though, to give our game more variety each time its played, because we'll narrow down the list in the next step.

 

Step 2: We'll only need 10 images for the faces of our 20 tiles, so then we create a new array that holds 2 copies of 10 randomly selected images from that first array.

To do that, we create a for loop that iterates 10 times. In each iteration, we randomly pick an index from the faces array, push that twice onto the selected array, and then use the splice method remove it from the faces array, so that we don't select it twice. That last step is very important!

var selected = [];
for (var i = 0; i < 10; i++) {
    // Randomly pick one from the array of faces
    var randomInd = floor(random(faces.length));
    var face = faces[randomInd];
    // Push 2 copies onto array
    selected.push(face);
    selected.push(face);
    // Remove from faces array so we don't re-pick
    faces.splice(randomInd, 1);
}

Step 3: We randomize the selected images array, so that the pairs of images are no longer next to eachother in an array.

You might be wondering, how does one randomly sort an array in JavaScript? There are a few techniques, but I'll show you my favorite.

In JavaScript, every array object has a built-in sort method that will sort the array "lexicographically". That means that it will convert each item to a string, and sort them as if they were words in a dictionary. For example, let's see how it'd sort an array of numbers and letters:

var items = ["A", 1, "C", "H", 10, "D", 2];
items.sort();
1,10,2,A,C,D,H

That sorting order can sometimes be useful, but most of the time, we want to sort our array in some other way. For example, if we had an array of numbers, we may want to sort them numerically. That's why the sort method optionally accepts an argument, a callback function that will get called on every pair of items in an array, and return a value to indicate whether to sort one item higher than the other. A negative number means that the first item should be first, a positive number means that the second item should be first, and a zero will leave the items' order unchanged.

To sort an array numerically from lowest to highest, we can pass a function that returns a-b.

var nums = [1, 5, 10, 2, 4];
nums.sort(function(a, b) {
   return  a-b;
});
// 1,2,4,5,10

Okay, so how can we use this to sort an array randomly? Well, we just need to return a random number from that function, a number that's either negative or positive. Here's how we can do that, on our selected array from above:

selected.sort(function() {
    return 0.5 - random();
});

And now we have an array of 10 pairs of images, randomly sorted!

Step 4: In the nested for loop where we create the tiles, we'll assign an image from that array to each tile.

We have 20 images in our selected array, and we're iterating 20 times to instantiate new tiles at locations in the grid. To assign each image to a tile, we can just use the pop method on the array. That method removes the last element from the array and returns it, and is the easiest way to make sure we assign all the images but don't double assign them.

for (var i = 0; i < NUM_COLS; i++) {
    for (var j = 0; j < NUM_ROWS; j++) {
        tiles.push(new Tile(i * 78 + 10, j * 78 + 40, selected.pop()));
    }
}

So we now theoretically have images assigned to each tile, but we're not displaying them yet! Let's add a method to the Tile object that's responsible for drawing them face up. It'll be similar to drawing face down, but the image command use the this.image property instead.

Tile.prototype.drawFaceUp = function() {
    fill(214, 247, 202);
    strokeWeight(2);
    rect(this.x, this.y, this.width, this.width, 10);
    image(this.face, this.x, this.y, this.width, this.width);
};

To reduce repeated code, we could actually add an additional method that is only responsible for drawing the outline, and just call that method from each of the drawing methods. But we'll leave it like this for now.

Finally, to test it all works, we can change our for loop to call drawFaceUp instead of drawFaceDown:

for (var i = 0; i < tiles.length; i++) {
    tiles[i].drawFaceUp();
}

Here it is, all together. Try restarting it to see how the tiles change each time.