Memory Game In JavaScript

This a memory game is created with HTML, CSS and Javascript.

How To Play

The game board consists of randomly placed 8 different pairs of face down cards.

  • Click on a card to reveal the card.
  • Click on another card to reveal it as well.
  • If two cards match,they will remain revealed otherwise they will be face down again.
  • Once all the cards are matched, game ends and pop up window appears with information of how many moves you took to complete the game and your rating aka your bragging rights.

HTML

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Matching Game</title>
        <meta name="description" content="">
        <link rel="stylesheet prefetch" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css">
        <link rel="stylesheet prefetch" href="https://fonts.googleapis.com/css?family=Coda">
        <link href="https://afeld.github.io/emoji-css/emoji.css" rel="stylesheet">
        <link rel="stylesheet" href="css/app.css">
    </head>
    <body>
    
        <div class="container">
            <header>
                <h1>Matching Game</h1>
            </header>
    
            <section class="score-panel">
            	<ul class="stars">
            	</ul>
    
                <span class="moves"></span> Moves&nbsp;&nbsp;
                <span class="timer">time</span>
    
                <div class="restart">
            		<i class="fa fa-repeat"></i>
            	</div>
            </section>
    
            <ul class="deck">
                <li class="card">
                    <i class="fa fa-diamond"></i>
                </li>
                <li class="card">
                    <i class="fa fa-paper-plane-o"></i>
                </li>
                <li class="card">
                    <i class="fa fa-anchor"></i>
                </li>
                <li class="card">
                    <i class="fa fa-bolt"></i>
                </li>
                <li class="card">
                    <i class="fa fa-cube"></i>
                </li>
                <li class="card">
                    <i class="fa fa-anchor"></i>
                </li>
                <li class="card">
                    <i class="fa fa-leaf"></i>
                </li>
                <li class="card">
                    <i class="fa fa-bicycle"></i>
                </li>
                <li class="card">
                    <i class="fa fa-diamond"></i>
                </li>
                <li class="card">
                    <i class="fa fa-bomb"></i>
                </li>
                <li class="card">
                    <i class="fa fa-leaf"></i>
                </li>
                <li class="card">
                    <i class="fa fa-bomb"></i>
                </li>
                <li class="card">
                    <i class="fa fa-bolt"></i>
                </li>
                <li class="card">
                    <i class="fa fa-bicycle"></i>
                </li>
                <li class="card">
                    <i class="fa fa-paper-plane-o"></i>
                </li>
                <li class="card">
                    <i class="fa fa-cube"></i>
                </li>
            </ul>
        </div>
    
        <!-- Complete Modal -->
        <div id="completeModal" class="modal">  
            <!-- Modal content -->
            <div class="modal-content">
                <div class="modal-text">
                    <span>You did it!!!</span>
                    <i class="em em-trophy"></i>
                    <i class="em em-clap"></i>
                    <i class="em em-clap"></i>
                    <i class="em em-clap"></i>
    
                    <p>You completed the game in <span id="totalMove"></span> move(s) 
                        in <span id="totalTime"></span> 
                    </p>
                    <p><span class="star_rating"></span> </p>
                    <p><span class="start_again">Start Again</span></p>
                </div> 
            </div>
        </div>
    
        <script src="js/app.js"></script>
    </body>
    </html>

    CSS

    @import url('https://fonts.googleapis.com/css?family=Indie+Flower');
    
    html {
        box-sizing: border-box;
    }
    
    *,
    *::before,
    *::after {
        box-sizing: inherit;
    }
    
    html,
    body {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
    }
    
    body {
        background: #ffffff url('../img/geometry2.png'); /* Background pattern from Subtle Patterns */
        font-family: 'Coda', cursive;
    }
    
    .container {
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
    }
    
    h1 {
        font-family: 'Open Sans', sans-serif;
        font-weight: 300;
    }
    
    /*
     * Styles for the deck of cards
     */
    
    .deck {
        width: 660px;
        min-height: 680px;
        background: linear-gradient(160deg, #02ccba 0%, #aa7ecd 100%);
        padding: 32px;
        border-radius: 10px;
        box-shadow: 12px 15px 20px 0 rgba(46, 61, 73, 0.5);
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
        align-items: center;
        margin: 0 0 3em;
    }
    
    .deck .card {
        height: 125px;
        width: 125px;
        background: #2e3d49;
        font-size: 0;
        color: #ffffff;
        border-radius: 8px;
        cursor: pointer;
        display: flex;
        justify-content: center;
        align-items: center;
        box-shadow: 5px 2px 20px 0 rgba(46, 61, 73, 0.5);
    }
    
    .deck .card.open {
        transform: rotateY(0);
        background: #02b3e4;
        cursor: default;
    }
    
    .deck .card.show {
        font-size: 33px;
    }
    
    .deck .card.match {
        animation: pop 0.5s;
        cursor: default;
        background: #02ccba;
        font-size: 33px;
    }
    
    .deck .card.disabled {
    	pointer-events: none;
    }
    
    .deck .card.unmatched {
    	animation: shake 0.65s; 
    	background: rgb(226,4,59);
    }
    
    /*
     * Styles for the Score Panel
     */
    
    .score-panel {
        text-align: left;
        width: 345px;
        margin-bottom: 10px;
    }
    
    .score-panel .stars {
        margin: 0;
        padding: 0;
        display: inline-block;
        margin: 0 5px 0 0;
    }
    
    .score-panel .stars li {
        list-style: none;
        display: inline-block;
    }
    
    .score-panel .restart {
        float: right;
        cursor: pointer;
    }
    /** End of Score Panel **/
    
    /*
    * Styles for Modal
    */
    
    /** Modal from https://www.w3schools.com/howto/howto_css_modals.asp **/
    .modal {
        display: none; /* Hidden by default */
        position: fixed; /* Stay in place */
        z-index: 1; /* Sit on top */
        padding-top: 100px; /* Location of the box */
        left: 0;
        top: 0;
        width: 100%; /* Full width */
        height: 100%; /* Full height */
        overflow: auto; /* Enable scroll if needed */
        background-color: rgb(0,0,0); /* Fallback color */
        background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
    }
    
    .modal-content {
        background-color: #fefefe;
        margin: auto;
        padding: 20px;
        border: 1px solid #888;
        width: 60%;
    }
    
    .modal-text {
        width: 80%;
        margin-left: 100px;
        font-size: 2em;
        font-family: 'Indie Flower', cursive,sans-serif;
    }
    
    .start_again {
        cursor: pointer;
        border: 1px solid rgb(187, 186, 186);
        text-align: center;
        padding: 10px;
        box-shadow: 2px 3px 0 #000;
        margin-left: 35%;
        border-radius: 5px;
        font-size: 0.8em;
    }
    
    .fa-star,
    .star_style {
        color:  #feda68;
    }
    
    .fa-star{
        font-size: 22px;
    }
    /** End of Modal **/
    
    /* animation for shake unmatched card */
    @keyframes shake {
        0% { transform: translate(0px, 0px);} 
        15% { transform: translate(9px, 0px);}
        30% { transform: translate(-9px, 0px);} 
        45% { transform: translate(9px, 0px);}
        60% { transform: translate(-9px, 0px);}
        75% { transform: translate(9px, 0px);}
        90% { transform: translate(-9px, 0px);}
        100% { transform: translate(0px, 0px);}
    }
    
    @keyframes pop {
        0% {  transform: scale(0.5); }
        100% {  transform: scale(1); }
    }

    Javascript

    //convert node list to array
    let card_array = [].slice.call(document.querySelectorAll(".card"));
    let deck = document.querySelector('.deck');
    
    //For modal
    let modal = document.querySelector('#completeModal');
    let start_again = document.querySelector('.start_again');
    let star_rating_modal = document.querySelector('.star_rating');
    let stars = '';
    
    //For counting moves
    let move = document.querySelector(".moves");
    let move_counter = 0;
    
    //For display time
    let timer = document.querySelector('.timer');
    let time_interval;
    
    //For restarting the game
    let restart = document.querySelector(".restart");
    
    let list_open_cards = [];
    let match_counter = 0;
    
    let star_rating_board = document.querySelector('.stars');
    // Shuffle function from http://stackoverflow.com/a/2450976
    function shuffle(array) {
        var currentIndex = array.length, temporaryValue, randomIndex;
    
        while (currentIndex !== 0) {
            randomIndex = Math.floor(Math.random() * currentIndex);
            currentIndex -= 1;
            temporaryValue = array[currentIndex];
            array[currentIndex] = array[randomIndex];
            array[randomIndex] = temporaryValue;
        }
    
        return array;
    }
    
    window.onload = init();
    
    //function for starting game
    function init() {
        var shuffle_cards = shuffle(card_array);
    
        for (var i = 0; i < shuffle_cards.length; i++) {
            var li = shuffle_cards[i]
            var icon = shuffle_cards[i].querySelector('i');
            li.id = icon.classList[1];
            deck.appendChild(li);
            shuffle_cards[i].classList.remove("show", "open", "match", "disabled");
        }
    
        star_rating_board.innerHTML = '';
        for(var j = 0; j < 3; j++){
           var li = document.createElement("li");
           var i = document.createElement("i");
           i.setAttribute("class", "fa fa-star");
           li.appendChild(i);
           star_rating_board.appendChild(li);
        }
    
        list_open_cards = [];
        move_counter = 0;
        match_counter = 0;
        move.innerHTML = move_counter;
        sec = 0;
        timer.innerHTML = 'time';
        clearInterval(time_interval);
    }
    
    //function for checking if card matches or not
    function openCard() {  
        if(list_open_cards.length < 2 ){ 
            displayCard(this);
            moveCounter();
            if(list_open_cards.length === 0){
                list_open_cards.push(this);
             }else if(list_open_cards.length === 1){
                list_open_cards.push(this);
                
                if(list_open_cards[0].id === list_open_cards[1].id){
                   match_counter+=2;
                    matched();
                    if(match_counter === card_array.length){
                        clearInterval(time_interval);
                        complete();
                    }
                }else{
                  unmatched();
                }
            }
        }
    }
    
    //function for displaying clicked card
    function displayCard(card){
        card.classList.toggle('show');
        card.classList.toggle('open');
        card.classList.add('disabled');
    }
    
    //function for adding classes to matched card
    function matched(){
        list_open_cards[0].classList.add("match", "disabled");
        list_open_cards[1].classList.add("match", "disabled");
        list_open_cards = [];
    }
    
    //function for adding and removing classes to cards that do not match
    function unmatched(){
        list_open_cards[0].classList.add("unmatched");
        list_open_cards[1].classList.add("unmatched");
    
        setTimeout(function(){
            list_open_cards[0].classList.remove("show", "open", "unmatched","disabled");
            list_open_cards[1].classList.remove("show", "open", "unmatched","disabled");
            list_open_cards = [];
        },1100);
    }
    
    //function for counting moves
    function moveCounter(){
        move_counter++;
        if(move_counter === 1){
            displayTimer();
        }
        move.innerHTML = move_counter;
        if(move_counter > 16 && move_counter <= 24){
            stars = '<span class="star_style"><i class="fa fa-star"></i></span>'+'<span class="star_style"><i class="fa fa-star"></i></span>';
            star_rating_board.innerHTML = stars;
        }
        if(move_counter > 24){
            stars = '<span class="star_style"><i class="fa fa-star"></i></span>';
            star_rating_board.innerHTML = stars;
        }
    }
    
    //function for timer - how many seconds
    function displayTimer(){
        time_interval = setInterval(function(){
           sec++;
           timer.innerHTML = sec+"secs";
           
       },1000);
       
    } 
    
    //function for displaying modal when game is completed
    function complete(){
        document.getElementById("totalMove").innerHTML = move_counter;
        document.getElementById("totalTime").innerHTML = timer.innerHTML;
        star_rating_modal.innerHTML = "Rating "+ stars;
        star_rating_board.innerHTML = "Rating "+ stars;
        modal.style.display = "block";
        match_counter = 0;
        clearInterval(time_interval);
    }
    
    //function for closing modal
    function close(){
        modal.style.display = "none";
        init();
    }
    
    
    start_again.addEventListener('click', close);
    
    restart.addEventListener('click', init);
    
    for (var i = 0; i < card_array.length; i++) {
        card_array[i].addEventListener('click', openCard);
    }