Intro to Memory



"Memory" is a fun game that you may have played in "real life", with actual paper tiles. It's called that because the main skill in the game is your memory - how well you can remember the position of tiles. It's also a game that can be re-created on a computer, and is a good example of using computer memory to remember information, like with arrays.

To make the game, we will need to re-create the physical parts of the game using our drawing commands and implement the rules of the game using variables and logic. We will make a single-player version of the game, which will simplify some of the logic. Here's an outline of what we'll implement:

• The game consists of an even number of tiles with images on one side and a generic back. Each image appears on precisely two tiles.
• When the game starts, all tiles are turned face down.
• The player then flips over two cards, selecting them by clicking on them. If the two tiles have the same image, they remain face up.
Otherwise, the tiles flip back over after a small period of time.
• The goal of the game is to get all the tiles flipped face up (i.e., find all the matching image pairs) in the least number of tries. That means that lower number of tries are better scores.

• 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 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?)

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

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);
};

// Create the array of tiles at appropriate positions
var tiles = [];
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));
}
}

// Start by drawing them all face down
for (var i = 0; i < tiles.length; i++) {
tiles[i].drawFaceDown();
}

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 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 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, except that the this.face property is passed to the image function.

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.