If you're seeing this message, it means we're having trouble loading external resources on our website.

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

Main content

Memory game: Flipping tiles

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

Right now, we have a program that draws a grid of tiles and then never draws anything else. Going forward, we want our program to draw different things over time - it'll start off drawing face down tiles, but then it'll display clicked tiles, and if all goes well for the player (fingers crossed!), it'll display a win screen.
So let's move all our drawing code into the ProcessingJS draw function. The computer will keep calling draw() while the program is running, so the tiles will keep getting drawn according to whether they're face up or face down:
draw = function() {
    background(255, 255, 255);
    for (var i = 0; i < tiles.length; i++) {
        tiles[i].draw();
    }
};
Let's get some of those tiles face up now! To flip a tile, the player must click on it. To respond to clicking in ProcessingJS programs, we can define a mouseClicked function, and the computer will execute that code 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 an 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.size, and if the given y is between this.y and this.y + this.size:
Tile.prototype.isUnderMouse = function(x, y) {
    return x >= this.x && x <= this.x + this.size  &&
        y >= this.y && y <= this.y + this.size;
};
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 set the tile's isFaceUp property to true:
mouseClicked = function() {
  for (var i = 0; i < tiles.length; i++) {
    if (tiles[i].isUnderMouse(mouseX, mouseY)) {
      tiles[i].isFaceUp = true;
    }
  }
};
Here's what that looks like. Click a bunch of tiles and see what happens:

Restricting tile flips

Notice something? We implemented one aspect of game play, that the 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 only flip a tile over if numFlipped is less than 2 and the tile isn't already face up:
var numFlipped = 0;
mouseClicked = function() {
    for (var i = 0; i < tiles.length; i++) {
        var tile = tiles[i];
        if (tiles.isUnderMouse(mouseX, mouseY)) {
            if (numFlipped < 2 && !tile.isFaceUp) { 
              tile.isFaceUp = true;
              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, by setting isFaceUp to false, 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.
  • In the draw 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++) {
    var tile = tiles[i];
    if (tile.isUnderMouse(mouseX, mouseY)) {
      if (numFlipped < 2 && !tile.isFaceUp) {
        tile.isFaceUp = true;
        numFlipped++;
        if (numFlipped === 2) {
          delayStartFC = frameCount;
        }
        loop();
      } 
    }
  }
};

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

  background(255, 255, 255);
  for (var i = 0; i < tiles.length; i++) {
    tiles[i].draw();
  }
};
Flip some tiles below - it's pretty cool how the tiles automatically flip back over, aye? To help you understand it more, try changing how long the delay waits and how many tiles have to be flipped over before the delay starts.

Checking matches

If you managed to match any tiles above, you were probably sad when they flipped back over, because, hey, you made a match! So now it's time to implement this rule of the game:
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 first tile has same face as 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. How do we find them?
We could iterate through our array each time, find all the tiles with isFaceUp set to true, and then store those into an array.
Here's a shortcut: let's just always store our flipped tiles in an array, for easy access. That way, we don't have to iterate through our whole tiles array every time the player flips a tile.
As a first step, we can replace numFlipped with an array, and then use flippedTiles.length everywhere we previously used numFlipped. Our mouseClicked function looks like this:
var flippedTiles = [];
var delayStartFC = null;

mouseClicked = function() {
  for (var i = 0; i < tiles.length; i++) {
    var tile = tiles[i];
    if (tile.isUnderMouse(mouseX, mouseY)) {
      if (flippedTiles.length < 2 && !tile.isFaceUp) {
        tile.isFaceUp = true;
        flippedTiles.push(tile);
        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 a good use for a boolean property! Let's add an isMatch property to the Tile constructor, and then set isMatch to true only inside that if block:
if (flippedTiles[0].face === flippedTiles[1].face) {
  flippedTiles[0].isMatch = true;
  flippedTiles[1].isMatch = true;
}
Now we can use that property to decide whether to turn the tiles over after the delay.
for (var i = 0; i < tiles.length; i++) {
  var tile = tiles[i];
  if (!tile.isMatch) {
    tile.isFaceUp = false;
  }
}
Play with it below! When you find two matching tiles below, they should stay up after the delay (and after future turns):

Want to join the conversation?

  • orange juice squid orange style avatar for user Curious Georgio
    What does this mean?
    (delayStartFC && (frameCount - delayStartFC) > 30)
    (25 votes)
    Default Khan Academy avatar avatar for user
    • blobby green style avatar for user lightsecond
      The first part before the && checks if
      delayStartFC is true or false.

      If delayStartFC still has the value: null,
      then a second tile hasn't been opened yet,
      and delayStartFC evaluates to false,
      the second part never gets checked.

      That's because of the logical "and" operator: &&,
      both sides of the && operator must be true
      for the whole statement to be true.

      delayStartFC is true if it has a value
      other than: null, 0, false, undefined or NaN.

      In this game that means that delayStartFC has
      been assigned the current frameCount when we
      clicked on the second tile:

      if(numFlipped === 2){
      delayStartFC = frameCount;
      loop();
      }

      The second part checks if the difference
      between the current framecount and
      delayStartFC is greater than 30.

      For example:
      If the frameCount was 20 when we opened
      a second tile, then delayStartFC would
      get the value: 20

      the if statement would look like this:
      if(20 && (frameCount - 20) > 30)

      the first part: if(20 ..., evaluates to true
      (it's not null, 0, false, undefine or NaN)

      and the second part: (frameCount - 20) > 30
      is checked aswell.

      Every time the draw loop runs, the
      frameCount increases by 1.

      The first time through the draw loop,
      frameCount is 20:
      (20 - 20) > 30, 0 > 30, that's false,
      the tiles will stay turned face up.

      The second time through the draw loop,
      frameCount is 21:
      (21 - 20) > 30, 1 > 30, false again.

      It will evaluate to false until the
      draw loop has gone through 31 times
      and the frameCount is 51,
      (51 - 20) > 30, 31 > 30, that's true,

      and the tiles now turn face down,
      numFlipped and delayStartFC gets reset
      and the draw loop stops, waiting for
      another two tiles to be opened.
      (83 votes)
  • leaf red style avatar for user Noble Mushtak
    Why don't we use frameRate(1); and only wait 1 frame to turn the cards down?
    (7 votes)
    Default Khan Academy avatar avatar for user
    • leafers sapling style avatar for user Peter Collingridge
      The problem with doing that is that it's a global change, so effects the entire program, not just the delay for flipping. In the simple case of this game, it will work, but if you later want to add animation, or make a timer show how long the player has been playing, then you'll run into problems. In general it's best to avoid changing the frameRate or making changes that have large repercussions unless you're absolutely sure that's how you want your game to run.

      It's all about writing code to be as flexible as possible so it's easy for you, or someone else, to make modifications later.

      Having said that, this code does use loop and noLoop which make similarly global changes to the draw loop, so they would also have to be changed if you wanted to modify the program to include animations later.
      (22 votes)
  • aqualine seed style avatar for user K B
    How Math.random() is different from random()?
    (1 vote)
    Default Khan Academy avatar avatar for user
    • ohnoes default style avatar for user Brynden
      Math.random() can have no parameters, and only returns a random number between 0 and 1. If you call random() without any parameters, it will generate a random number between 0 and 1, just like Math.random(). If you use it with one parameter, it will generate a random number between 0 and the number you specified. (i.e random(50) will generate a random number between 0 and 50) If you specify two parameters, it will generate a random number between num1 and num2. (i.e random(10, 15) will generate a random number between 10 and 15)
      (23 votes)
  • old spice man blue style avatar for user Flostin(READ BIO)
    What's the difference between null and undefined?
    (7 votes)
    Default Khan Academy avatar avatar for user
    • blobby green style avatar for user C++ coder
      in JavaScript undefined means a variable has been declared but has not yet been assigned a value. null is an assignment value.You can assign the value null to a variable to represent that it has no value . JSYK: undefined and null are two distinct types: undefined is a type(undefined) while null is an object
      (15 votes)
  • piceratops tree style avatar for user BenG
    I noticed that on this "Playing the game" page, what was previously called the 'faces' array (in the previous "Grid of tiles" page) is recreated here as an array called possibleFaces, via the line:
    var possibleFaces = faces.slice(0);

    Besides providing a good prompt to learn a new method that can be applied to arrays (ie. slice() - I thought it was a typo for splice at first! :) - was there any particular reason to recreate the array here, or is it likely that Pamela just had a change of heart regarding what she wanted the array to be called?

    With thanks,
    Ben
    (8 votes)
    Default Khan Academy avatar avatar for user
  • orange juice squid orange style avatar for user Mr. Granger
    When you initiate "var possibleFaces" why did you define it as "faces.slice(0)"?

    In fact, why was a possibleFaces variable needed at all? Was it used as a check to make sure we didn't have more Faces than tiles?

    Also, did I use the terms "initiate" and "define" correctly.
    (5 votes)
    Default Khan Academy avatar avatar for user
  • female robot ada style avatar for user Beatrix Ducz
    Hi! 1st question: The function isUnderMouse is a processing js function or we created it? How can I tell which functions are in the processing js? I cannot tell, both looks the same:
    Tile.prototype.isUnderMouse= function() {...}
    mouseClicked = function() {...};

    2nd question: flippedTiles[0].isMatch = true;
    How can we create a property isMatch of flippedTiles inside a function and use it outside of it? I've did some programming before and I always thought that any new variable has to be typed and initialized before the first use. Same with tiles[i].isMatch. Where is it defined? How do we know the value of tiles[i].isMatch? Also I was tought that any object can only be accessed within it's parent where it was created, otherwise it would just result an error or an empty object. Maybe t is different with properties.
    (4 votes)
    Default Khan Academy avatar avatar for user
    • old spice man green style avatar for user Bob Lyon
      If a function or method is not in your program, then it must come from the environment - Javascript, Processing.js or the web page. isMouseUnder is declared on line 24 while mouseClciked has no visible declaration.

      Unlike many languages, Javascript do not require that all properties of an object be statically declared when the object is declared. The program may assign a new property to any object at any time (or delete a property from an object at any time). For example
      var v = { x: 150, y: 212 };
      v.z = 347;
      v.isMatch = false;
      (8 votes)
  • duskpin ultimate style avatar for user Rachel Ritchey
    In "Project: Memory++" I'm attempting to have the tiles change color when I move my mouse over them, but I'm stuck. I see that there's a mouseOver function, but I'm not sure how to implement it. Please help :'(
    (7 votes)
    Default Khan Academy avatar avatar for user
  • blobby green style avatar for user Mohammed Elsir
    What do loop and noLoop functions do here? Pamela said she used them as a performance optimization. Could someone explain more?
    (5 votes)
    Default Khan Academy avatar avatar for user
    • leaf blue style avatar for user Hunter R. Shaw
      Well, from a distance, it's unnecessary to call the draw function when it's just waiting for a user input. It's akin to playing a video with a minutes of a black screen at the end vs one that goes directly to the recommended list. One increases the networking costs for no reason but does the same thing as the other. Also, please take down your duplicate question.
      (4 votes)
  • aqualine ultimate style avatar for user Sky Wang
    For the challenge: Tic-Tac-Toe, what do you type under the line "if (!this.empty())"? The tip says to exit the function.
    (3 votes)
    Default Khan Academy avatar avatar for user