Playing the game



Okay, so we now have a grid of tiles that we can display face up or face down. But we have no way of actually playing the game. To remind you, here's how the game works:

• 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. If not, they should be flipped face down again after a short delay.

• Click-flipping tiles



We'll start by turning our tiles all face down again:

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

To flip a tile, the player must click on it. To respond to clicking in ProcessingJS programs, we can define a mouseClicked function, and that code will be executed every time the mouse is clicked.

mouseClicked = function() {
// process click somehow
};

When our program sees that the player has clicked somewhere, we want to check if they've clicked on a tile, using mouseX and mouseY. Let's start by adding a isUnderMouse method to Tile that returns true if a given x and y is within a tile's area. With the way we've drawn the tiles, the x and y of the tile correspond to the upper left corner of the tile, so we should return true only if the given x is between this.x and this.x + this.width, and if the given y is between this.y and this.y + this.width:

Tile.prototype.isUnderMouse = function(x, y) {
return x >= this.x && x <= this.x + this.width &&
y >= this.y && y <= this.y + this.width;
};

Now that we have that method, we can use a for loop in mouseClicked to check if each tile is under the mouseX and mouseY. If so, we'll draw them face up:

mouseClicked = function() {
for (var i = 0; i < tiles.length; i++) {
if (tiles[i].isUnderMouse(mouseX, mouseY)) {
tiles[i].drawFaceUp();
}
}
};

Here's what that looks like. Click a bunch of tiles and see what happens:

var Tile = function(x, y, face) {
this.x = x;
this.y = y;

this.face = face;
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);
};

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

Tile.prototype.isUnderMouse = function(x, y) {
return x >= this.x && x <= this.x + this.width &&
y >= this.y && y <= this.y + this.width;
};

// Declare an array of all possible faces
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")
];

// Make an array which has 2 of each, then randomize it

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

// Now we need to randomize the array

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

// Create the tiles

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

background(255, 255, 255);

// Now draw them face up

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

mouseClicked = function() {
// check if mouse was inside a tile
for (var i = 0; i < tiles.length; i++) {
if (tiles[i].isUnderMouse(mouseX, mouseY)) {
tiles[i].drawFaceUp();
}
}
};

Notice something? We implemented one aspect of game play, thatthe player is able to flip over the tiles, but we're missing an important restriction: they shouldn't be able to flip more than two tiles at once.
We will need to keep track of the number of flipped tiles somehow. One simple way would be a global numFlipped variable that we increment each time the player turns a card face up. We could then skip the tile hit check if numFlipped is 2.

var numFlipped = 0;
mouseClicked = function() {
for (var i = 0; i < tiles.length; i++) {
if (tiles[i].isUnderMouse(mouseX, mouseY)) {
if (numFlipped < 2) {
tiles[i].drawFaceUp();
numFlipped++;
}
}
}
};

That's not quite right, however - with that code, what would happen if we clicked on the same tile twice in a row? Think about it - it would flip it "twice", set numFlipped to 2, and prevent future flips. Instead, we only want it to flip the card if it hasn't already been turned face up.
How do we know if the current tile in the for loop is face up or down? We might have called the drawFaceUp method on it at some point before, but we never kept track of that. Ba ba bum, it's time for a boolean! Let's modify the draw methods to set a boolean that tells us the state of a tile.

Tile.prototype.drawFaceDown = function() {

// ...

this.isFaceUp = false;
};

Tile.prototype.drawFaceUp = function() {

// ...

this.isFaceUp = true;
};

Now we can modify the check to make sure the tile is actually face down:

if (tiles[i].isUnderMouse(mouseX, mouseY)) {
if (numFlipped < 2 && !tiles[i].isFaceUp) {
tiles[i].drawFaceUp();
numFlipped++;
}
}

Delay-flipping tiles



Okay, our two-tile-flipping logic is complete. What's next? Let's recap the game rules again:

If the two tiles have the same image, they remain face up. Otherwise, the tiles flip back over after some period of time.

We'll first implement the second part, which automatically flips the tiles back over, because it will be hard to test the first part if we can't easily look for new matches.

We know how to flip tiles back over, using the turnFaceDown method, but how do we do that after some period of time? Every language and environment has a different approach to delaying execution of code, and we need to figure out how to do it in ProcessingJS. We need some way of keeping track of time - whether the delay period has passed - and a way of calling code after the period of time has passed. Here's what I'd suggest:

• We create a global variable called delayStartFC, initially null.
• In the mouseClicked function, right after we've flipped over a second tile, we store the current value of frameCount in delayStartFC. That variable tells us how many frames have passed since the program started running, and is one way of telling time in our programs.
• We define the draw function, as that function will be called continuously as the program runs, so we know it will be called for every value of frameCount.

In that function, we check if the new value of frameCount is significantly higher than the old one, and if so, we flip all of the tiles over and set numFlipped to 0. We also reset delayStartFC to null.
It's actually a nice solution that doesn't require too much code to implement. As a performance optimization, we can use the loop and noLoop functions to make sure that the draw code is only being called when there's a delay happening. Here it all is:

var numFlipped = 0;
var delayStartFC = null;

mouseClicked = function() {
for (var i = 0; i < tiles.length; i++) {
if (tiles[i].isUnderMouse(mouseX, mouseY)) {
if (numFlipped < 2 && !tiles[i].isFaceUp) {
tiles[i].drawFaceUp();
numFlipped++;
if (numFlipped === 2) {
delayStartFC = frameCount;
loop();
}
}
}
}
};

draw = function() {
if (delayStartFC && (frameCount - delayStartFC) > 30) {
for (var i = 0; i < tiles.length; i++) {
tiles[i].drawFaceDown();
}
numFlipped = 0;
delayStartFC = null;
noLoop();
}
};

Try it out below - it's pretty cool how the tiles automatically flip back over, aye?

var Tile = function(x, y, face) {
this.x = x;
this.y = y;
this.face = face;
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);
this.isFaceUp = false;
};

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);
this.isFaceUp = true;
};

Tile.prototype.isUnderMouse = function(x, y) {
return x >= this.x && x <= this.x + this.width &&
y >= this.y && y <= this.y + this.width;
};

// Declare an array of all possible faces

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")
];

// Make an array which has 2 of each, then randomize it

var possibleFaces = faces.slice(0);
var selected = [];
for (var i = 0; i < 10; i++) {
// Randomly pick one from the array of remaining faces
var randomInd = floor(random(possibleFaces.length));
var face = possibleFaces[randomInd];

// Push 2 copies onto array

selected.push(face);
selected.push(face);

// Remove from array


possibleFaces.splice(randomInd, 1);
}

// Now we need to randomize the array

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

// Create the tiles

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

background(255, 255, 255);

// Now draw them face up

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

var numFlipped = 0;
var delayStartFC = null;

mouseClicked = function() {
for (var i = 0; i < tiles.length; i++) {
if (tiles[i].isUnderMouse(mouseX, mouseY)) {
if (numFlipped < 2 && !tiles[i].isFaceUp) {
tiles[i].drawFaceUp();
numFlipped++;
if (numFlipped === 2) {
delayStartFC = frameCount;
loop();
}
}
}
}
};

draw = function() {
if (delayStartFC && (frameCount - delayStartFC) > 30) {
for (var i = 0; i < tiles.length; i++) {
tiles[i].drawFaceDown();
}
numFlipped = 0;
delayStartFC = null;
noLoop();
}
};

But we don't always want the tiles to flip back over - remember, if two tiles match, then they should stay face up. That means that we should check for matching tiles whenever there are 2 flipped over, and before we set up the delay. In pseudo-code, that'd be:

if there are two tiles flipped over:

if the first tile has the same face as the second tile:

keep the tiles face up

We already have a check for whether there are two tiles flipped over (numFlipped === 2), so how do we check if the tiles have the same face? First, we need some way of accessing the two flipped over tiles. We could iterate through our array, find all the tiles with isFaceUp set to true, and then store those into an array. Or, I have a better idea: let's just always store our flipped tiles in an array, for easy access. In fact, we can just replace numFlipped with an array, and then use flippedTiles.length everywhere we previously used numFlipped.

Our modified mouseClick function looks like this:

var flippedTiles = [];
var delayStartFC = null;

mouseClicked = function() {
for (var i = 0; i < tiles.length; i++) {
if (tiles[i].isUnderMouse(mouseX, mouseY)) {
if (flippedTiles.length < 2 && !tiles[i].isFaceUp) {
tiles[i].drawFaceUp();
flippedTiles.push(tiles[i]);
if (flippedTiles.length === 2) {
delayStartFC = frameCount;
loop();
}
}
}
}
};

Now, we need to figure out if the two tiles in the flippedTiles array do indeed have the same face. Well, what is the face property? It's an object - and actually, the face of matching tiles should be exactly the same object, as in, the variable is pointing at the same place in computer memory for both. That's because we only created each image object once (like with getImage("avatars/old-spice-man") and then we pushed the same image object onto the faces array twice:

var face = possibleFaces[randomInd];
selected.push(face);
selected.push(face);

In JavaScript at least, the equality operator will return true if it's used on two variables that point to objects, and both of those variables refer to the same object in memory. That means that our check can be simple - just use the equality operator on the face property of each tile:

if (flippedTiles[0].face === flippedTiles[1].face) {

// ...
}

Now that we know the tiles match, we need to keep them up. Currently, they'd all get turned over after a delay. We could just not set up the animation in this case, but remember, there will be an animation in later turns - so we can't rely on that. Instead, we need a way of knowing "hey, when we turn all of them back over, we shouldn't turn these particular ones over." Sounds like another good use for a boolean property! We could set a isMatch property to true inside that if block, and then only turn tiles over if it's not true:

if (flippedTiles[0].face === flippedTiles[1].face) {
flippedTiles[0].isMatch = true;
flippedTiles[1].isMatch = true;
}
for (var i = 0; i < tiles.length; i++) {
if (!tiles[i].isMatch) {
tiles[i].drawFaceDown();
}
}

Now, when you find two matching tiles below, they should stay up after the delay (and after future turns):

var Tile = function(x, y, face) {
this.x = x;
this.y = y;
this.face = face;
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);
this.isFaceUp = false;
};

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);
this.isFaceUp = true;
};

Tile.prototype.isUnderMouse = function(x, y) {
return x >= this.x && x <= this.x + this.width &&
y >= this.y && y <= this.y + this.width;
};

// Declare an array of all possible faces
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")
];

// Make an array which has 2 of each, then randomize it

var possibleFaces = faces.slice(0);
var selected = [];
for (var i = 0; i < 10; i++) {
// Randomly pick one from the array of remaining faces
var randomInd = floor(random(possibleFaces.length));
var face = possibleFaces[randomInd];

// Push 2 copies onto array

selected.push(face);
selected.push(face);

// Remove from array

possibleFaces.splice(randomInd, 1);
}

// Now we need to randomize the array

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

// Create the tiles

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

background(255, 255, 255);

// Now draw them face up

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

var flippedTiles = [];
var delayStartFC = null;
mouseClicked = function() {
for (var i = 0; i < tiles.length; i++) {
if (tiles[i].isUnderMouse(mouseX, mouseY)) {
if (flippedTiles.length < 2 && !tiles[i].isFaceUp) {
tiles[i].drawFaceUp();
flippedTiles.push(tiles[i]);
if (flippedTiles.length === 2) {
if (flippedTiles[0].face === flippedTiles[1].face) {
flippedTiles[0].isMatch = true;
flippedTiles[1].isMatch = true;
}
delayStartFC = frameCount;
loop();
}
}
}
}
};

draw = function() {
if (delayStartFC && (frameCount - delayStartFC) > 30) {
for (var i = 0; i < tiles.length; i++) {
if (!tiles[i].isMatch) {
tiles[i].drawFaceDown();
}
}
flippedTiles = [];
delayStartFC = null;
noLoop();
}
};

Scoring



We're only missing one thing now: the scoring. Here's a reminder of that part of the game rules:

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.

How do we track the number of tries? Well, a "try" is every time you've flipped over two tiles, which corresponds to our if block that checks flippedTiles.length === 2. We can add a new global variable, numTries, that we increment inside that conditional.

if (flippedTiles.length === 2) {
numTries++;
// ...
}

We can display the score when the game is over- when the player has matched all of the tiles. How do we check that? Well, we have an array of all our tiles, and each has a boolean that tells us if they're matches, so we can just loop through that and check if all of them are true. A neat way to accomplish this in code is to initialize a boolean to true before looping, and keep ANDing it with the boolean on each looped item. As long as it never sees a false (and the array is more than 0 items long!), it will still be true at the end. Here's what that looks like:

var foundAllMatches = true;
for (var i = 0; i < tiles.length; i++) {
foundAllMatches = foundAllMatches && tiles[i].isMatch;
}

When we've found all of them, we can display some congratulatory text to the user with the number of tries, like so:

if (foundAllMatches) {
fill(0, 0, 0);
text("You found them all in " + numTries + " tries", 20, 360);
}

We'll put all that code at the end of our mouseClicked function by the way, so that we run it after we've checked for matches in that turn.

You can try it out below, but it might take you a while to get to the win state (no offense to you, of course, it also takes me a while!). Here's a tip for whenever you're testing out parts of your game that are hard to reach - modify your game temporarily so that it's quicker to get there. For example, in this game, change NUM_ROWS and NUM_COLS to be smaller numbers, and you'll be able to finish much more quickly. Now, try *that* below!