A Basic Memory Game with jQuery and PHP

So, I got up this morning to find that my 3.5 year old daughter is busy playing on my laptop. She decided she wants to play so she found the CD for her game, opened the DVD drive and loaded her game. She was very busy playing when I got up, and needless to say I was quite surprised. I figured it was time to create some new games for her, so I thought I’d start with a basic Memory game (some may know it as Concentration). You know, the one with lots of face down cards where you need to find pairs of matching cards.

So let’s break down what’s in this game:

  1. Pairs of cards
  2. Raising the level increases the number of cards
  3. In each turn the player flips over 2 cards
  4. Matching cards are removed from the board
  5. The player wins when there are no more cards
  6. To keep things interesting – some effects

We have a lot to cover in this little guide so in order to keep things as short and quick as we can I’m going to skip some of the phases in building this game. If you want to see those phases, they can all be found in the demo.

To understand this guide you need to know some basic javascript & jQuery, and some PHP. There are some excellent guides for these topics all over the web to get you started or answer your questions.

1. Pairs of cards

In order to create the cards that will be used in the game I used an array to hold the available image files. I used 20 different image files in order to create a randomness factor for the images (assuming I will use less than 40 cards). When the board needs to be initialized, we select some random elements from the array of image files according to the number of cards we need on the board, and then we create card objects from each file. Since we know we need two of each card on the board, we “double” the array by merging it with itself. And now, a quick shuffle and the array for the board is done.

// Shuffle the available image files array so  
// we won't pick the same ones every time
shuffle($card_files);
// Get the card objects
$cards = array();
for ( $i = 0; $i < $num_of_cards; ++$i ){
    $cards[$i] = new Card($card_files[$i]);
    $this->css[] = $cards[$i]->get_css_block();
}
// Double the array so that we'll have pairs
$this->cards = array_merge($cards, $cards);
   
// Shuffle the cards to create their order on the board
shuffle($this->cards);

What is a card? Well a card will be displayed in the game, we will need to know how to build its css properties in such a way that we will have an element with the right background image and so on. As you can see below, the Card class is quite simple:

class Card{
    private $css_class = "";
    private $url = "";
       
    function __construct($url) {
        $this->url = $url;
        $this->css_class = $this->extract_name($url);
    }
       
    function get_name(){
        return $this->css_class;
    }
       
    function get_css_block(){
        return "\n.".$this->get_name()."{background:url(".$this->url.") center center no-repeat;}";
    }
       
    function get_html_simple_block(){
        return "\r<div class=\"card {toggle:'".$this->get_name()."'}\"></div>";
    }
       
    function get_html_block(){
        return "\r<div class=\"card {toggle:'".$this->get_name()."'}\">
                \r<div class=\"off\"></div>
                \r<div class=\"on\"></div>
            </div>"
;
    }
    private function extract_name($str){
        $tmp = pathinfo($str);
        return $tmp['filename'];
    }
}

The Card object creates its css class name based on the file name. It handles the its own html markup and css markup, and that’s about it. The Board class that we saw pieces from earlier will handle the building of the board’s html and css using the Card class.

2. Raising the level increases the number of cards

In the code above we used a variable called num_of_cards. This variable’s value is determined by the level of the current game: At low levels we will have relatively few cards, but as the level increases, so does the number of cards on the board.
Let’s look at the code for this:

private var $modes = array(6, 8, 10, 12, 15, 18);
...
$num_of_cards = $this->modes[$level - 1];

This code was also taken from the Board class. Let’s see how it adds up by taking a look at how we initialize the Board class and set the level:

$level = 1;

if (isset($_REQUEST['level']) ) {
    $level = $_REQUEST['level'];
       
    $board = new Board($level, $CARDS);
    $_SESSION['board'] = $board;
} else {
    if (!isset($_SESSION['board'])) {
        $board = new Board($level, $CARDS);
        $_SESSION['board'] = $board;
    } else {
        $board = $_SESSION['board'];
    }
}

If the user sends a request for a certain level, we use it for every game he plays from this moment on. If no request is sent, we use the default first level (1). In addition, we can see that we only set the board once per session, unless the a level request is sent, which is almost true (later on we will see that we also reset the board after the player wins). Saving the board in the session is done with the future in mind: as you will see in this post, the logic of matching the cards is carried out, for the moment, by the client. But if you want the game’s logic to be more secure, it should be carried out in the server, and then the server will need to know how the game board is built. For now, let’s go back to our subject.

Just before we move on to see how the player turns the cards we need to actually see how we build the board:

<style>
    <?php
        print $board->get_css();
    ?>
</style>
...
<div id="game_board" style="width:<?php print $board->get_cols()*75; ?>px;">
<?php
    print $board->get_html();
?>
</div>

3. In each turn the player flips over 2 cards

Now that we have our board, it’s time to add some interactivity and the main gameplay. We will use a special (and excellent) jQuery plugin to handle the flipping for us. You may have noticed before that, when we built the card’s html block, we actually built a wrapper and two elements inside; one for the back side and one for the front. This markup is recognized by the QuickFlip plugin (which you can get here). Now there are two things we need to do. First, we need to register the Click events for the cards. Second, when a card is clicked, we need to trigger the flip and run a check to see if:

  • The two cards match, but there are still cards on the board
  • The two cards don’t match
  • The two cards match, and there are no cards left on the board (i.e., the player has finished the game)

Let’s take a look:

var chk_cards_timeout = null;

$(document).ready(function(){
    $(".card").bind("click", toggleCard);
    $(".card").quickFlip();
});

function toggleCard(event){
    if ( chk_cards_timeout != null ){
        clearTimeout(chk_cards_timeout);
        chk_cards_timeout = null;
        checkCards();
    }
    var $card = $(this);
    if($card.children(".off").is(":visible")){
        $(document).trigger("flipping_cards");
        var num_already_opened = $card.parent("#game_board").find(".card>.on:visible").length;
        var css_class = $card.metadata()["toggle"];
        $card.children(".on").addClass(css_class);
        $card.quickFlipper();
       
        if ( num_already_opened == 1 ){
            chk_cards_timeout = setTimeout(checkCards, 1000);
        }
    }
    $card = null;
};

In order to do its job, QuickFlip will duplicate our elements, so we want our selectors to be more precise. That’s why we’re using the direct child (>) directive in our css selector. We can also see the use of the setTimeout function since in case we have two cards turned over, we want to give the user a second or two to look at them before we turn them face down again. We added the card’s css class to its element according to the metadata which we got when the Board class built the html (metadata is another excellent jquery plugin that can be found here).
If you noticed the line: $(document).trigger(“flipping_cards”); we are also triggering a custom event which we can bind to later with different plugins for the game if we will want to, if you haven’t already done so you can read about custom events in my previous post: jQuery custom events.

4. Matching cards are removed from the board

Now that the player can flip a card to see its image, we need to check if we have a match or not. If we have a match, the cards will be removed from the board; if we don’t, the cards will be turned face down again.

$(document).ready(function(){
    $(document).bind("found_match", matchingCards);
    $(document).bind("no_match", resetOnCards);
});

function checkCards(){
    $on_cards = $("#game_board .card>.on:visible");
    if ( $on_cards.length == 2 ){
        $(document).trigger("player_made_move");
        // Get the first object css class
        var css_class = $on_cards.parent(".card").metadata()["toggle"];
        $matched_cards = $on_cards.filter("."+css_class);
        var event_name = "no_match";
        if ( $matched_cards.length == 2 ){
            event_name = "found_match";
        }
        $(document).trigger(event_name, {css_class: css_class});
        $matched_cards = null;
    }
    clearTimeout(chk_cards_timeout);
    chk_cards_timeout = null;
    $on_cards = null;
};

function resetOnCards(event){
    $cards = $(".on:visible");
    $.each($cards, function(index, card){
        $card = $(card);
        var css_class = $card.parent(".card").metadata()["toggle"];
        $card.trigger("card_closing");
        $card.removeClass(css_class);
        $card.parent(".card").quickFlipper();
        $card = null;
    });
    $cards = null;
};

function matchingCards(event, params){
    $cards = $("."+params.css_class+".on:visible");
    $.each($cards, function(index, card){
        var $card = $(card);
        $card.trigger("card_removed");
        $card.parent(".card").unbind("*").before("<div class='card_ph'></div>").remove();
        $card = null;
    });
   
    $cards_left = $("#game_board>.card");
    if ( $cards_left.length == 0 ){
        $(document).trigger("game_won", {});
        /*
         * quickFlip has a problem when working in IE: when the last
         * bound element is removed, a problem which is caused by the
         * bound resize event on the window causes
         * the end game to get stuck when the game is over...
         */

        $(window).unbind("resize");
    }
    $cards_left = $cards = null;
};

5. The player wins when there are no more cards

In the above code we check if no more cards are left; if that is the case, the player_won event is triggered. When this event fires we need to do several things: We need to notify the server that the player won, and we need to show the player a message letting him know that he won. Let’s see how it goes:

$(document).ready(function(){
    $(document).bind("game_won", gameWon);
});

function gameWon(){
    $.getJSON(document.location.href, {won: 1}, notifiedServerWin);
    var $game_board = $("#game_board");
    var $player_won = $("#player_won");
    $game_board.hide();
    $player_won.show();
    $game_board = $player_won = null;
};

function notifiedServerWin(data){
    $("#start_again").show();
}

We can see that we notify the server with a simple ajax request. The server will then reset the board for us and remember the win.

if ( isset($_REQUEST["won"]) ){
    // if we won we need to reset the game board
    unset($_SESSION['board']);
    $_SESSION['games_won'] = ++$_SESSION['games_won'];
    $response = array("status" => "ok");
    exit(json_encode($response));
}

6. To keep things interesting – some effects

At this point the game is basically working, and it’s time to add some sound to it. Usually I absolutely hate sounds in web pages but since this is a game, it’s allowed :) . So in order to add the sound, we are going to use the “hooks” we left in the code in the shape of custom events. We are going to use a Flash object with some preloaded mp3s for the sound effects. The communication between the JavaScript and the Flash (actionscript) is pretty simple these days, but it’s not in the scope of this guide. However, if you want a guide on this let me know in the comments and I’ll post one. For the purpose of this guide, let’s assume we have a Flash object that can accept commands and output sounds. We will use swfobject to embed the flash object to the page.

<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript">
    var flashvars = false;
    var attributes = {};
    var params = {
      allowscriptaccess : "always",
      wmode : "transparent",
      menu: "false"
    };
    swfobject.embedSWF("sfx.swf", "sfx_movie", "1", "1", "9.0.0",
                "expressInstall.swf", flashvars, params, attributes);
</script>

Now that’s done, it time to handle the binding the events and triggering the sounds. We want the sound to play when the player flips a card over, when we wins the game, and when a match is found.

$(document).ready(function(){
    $(document).bind("flipping_cards.sound", playFlip);
    $(document).bind("game_won.sound", playCheer);
    $(document).bind("found_match", playMatch);
});

function playCheer(){
    var sfxMovie=getFlashMovieObject("sfx_movie");
    try{
        sfxMovie.cheer();
    } catch(e) {};
};

function playFlip(){
    var sfxMovie=getFlashMovieObject("sfx_movie");
    try{
        sfxMovie.flip();
    } catch(e) {};
};

function playMatch(){
    var sfxMovie=getFlashMovieObject("sfx_movie");
    try{
        sfxMovie.match();
    } catch(e) {};
};

That’s it! You can check how everything fits in place and view all the code by looking at the demo.

Zip file for this tutorial

Demo for this tutorial

So until next time, have fun!

Adi Gabai

Tags: , ,

31 Responses to “A Basic Memory Game with jQuery and PHP”

  1. Virtualfog – Gaming & Coding Community…

    [...]Creating a basic memory game with jQuery and php | Webdev Playground[...]…

  2. Thanks for this tutorial Adi. I’m going to try to implement it on a childrens website I’m working on at the moment.

  3. Ales says:

    Hello Adi,
    thank you for the memory game. I would like to use it on my website – but I am thinking of matching two different cards (headword and its translation for example). I create dynamically images from text and saving them to tmp directory. The pairs are named this way – ./images/tmp/file-4-1.png and ./images/tmp/file-4-2.png
    How can I acheive now that these two picture match? Thank you Ales

  4. Benny says:

    Thanks for this great tutorial. I am also looking for a solution of the problem Thorsten and Richard lately wrote about: you can click onto every picture very fast and open it.

    Did you find any solution?

  5. Johan van der Kuijl says:

    Hi Adi,

    If you click the second tile before the first tile is finished rotating, both tiles remain visible :)

  6. masteriii says:

    good one sample card game. Thank you.

  7. Pixsansar says:

    Thanks, it’s simple and very useful for beginners who want to start game programming. Here is my HTML5 and jquery powered game with box2dweb at : http://pixsansar.com/cool-box2dweb-games

  8. EKreuz says:

    Click on another card, while the effect is still going on on the first card, or double click on any card, and see what happens. I had this same situation happen, when I made a tic-tac-toe game, so i had to make tiles .fade()/.fadeTo(), instead of .effect(“explode”… .

  9. Claudio says:

    Please does anyone know how to display the name of the matched images when they match (right before or right after they disappear)?
    Great tutorial. Thank you!

Leave a Reply

(English only please)