Memory game: Drawing a grid of tiles

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 to associate both properties (like location and image) as well as behavior (like drawing them) 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 = 50;
};
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.
First we need to declare an empty tiles array, to store all those tiles:
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++) {
var tileX = i * 54 + 5;
var tileY = j * 54 + 40;
tiles.push(new Tile(tileX, tileY));
}
}
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.draw = 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);
};
We're so close to being able to 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].draw();
}
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 shuffle the selected images array, so that the pairs of images are no longer next to each other 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 to 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 shuffle the selected images array, so that the pairs of images are no longer next to each other in an array.
You've probably shuffled a deck of cards in your life, but have you ever shuffled an array in JavaScript? The most popular technique for shuffling in any programming language is called the Fisher-Yates Shuffle, and that's what we'll use here.
The Fisher-Yates Shuffle starts by first selecting a random element anywhere in the array, and swapping it with the last element in the array. In the next step, it selects a random element from anywhere in the array besides the last element, and swaps it with the second to last element. It keeps going until it has swapped every element.
You can click through this visualization to see what I mean:
To implement that in JavaScript, let's make a shuffleArray function that takes in an array and shuffles its elements, changing the original array:
var shuffleArray = function(array) {
var counter = array.length;

// While there are elements in the array
while (counter > 0) {
// Pick a random index
var ind = Math.floor(Math.random() * counter);
// Decrease counter by 1
counter--;
// And swap the last element with it
var temp = array[counter];
array[counter] = array[ind];
array[ind] = temp;
}
};
If the algorithm doesn't quite make sense yet after stepping through the visualization and reading the code, you could try it with an actual deck of cards in the real world or watch how Adam Khoury does it in his Youtube video.
After defining that function, we need to actually call it:
shuffleArray(selected);
And now we have an array of 10 pairs of images, randomly shuffled!
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 select a random image for each tile, we can call 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++) {
var tileX = i * 54 + 5;
var tileY = j * 54 + 40;
var tileFace = selected.pop();
var tile = new Tile(tileX, tileY, tileFace);
tiles.push(tile);
}
}
Notice how that code passes tileFace as the third parameter to the Tileconstructor? Our constructor originally only had 2 parameters, x and y, but now we modify it so that we can also remember the image of each tile face, plus whether it's face up:
var Tile = function(x, y, face) { this.x = x; this.y = y; this.width = 70; this.face = face; this.isFaceUp = false; };
So we now theoretically have images assigned to each tile, but we're not displaying them yet! Let's modify the Tile.draw method so that it can draw tiles that are face up:
Tile.prototype.draw = function() {
fill(214, 247, 202);
strokeWeight(2);
rect(this.x, this.y, this.width, this.width, 10);
if (this.isFaceUp) {
image(this.face, this.x, this.y,
this.width, this.width);
} else {
image(getImage("avatars/leaf-green"),
this.x, this.y, this.width, this.width);
}
};
Finally, to test it all works, we can change our for loop to set each tile's isFaceUp property to true before drawing it:
for (var i = 0; i < tiles.length; i++) {
tiles[i].isFaceUp = true;
tiles[i].draw();
}
Here it is, all together. Try restarting it to see how the tiles change each time.