SitePoint Sponsor

User Tag List

Results 1 to 12 of 12
  1. #1
    I solve practical problems. bronze trophy
    Michael Morris's Avatar
    Join Date
    Jan 2008
    Location
    Knoxville TN
    Posts
    2,023
    Mentioned
    62 Post(s)
    Tagged
    0 Thread(s)

    Java Script Challenge: A Game of Memory

    Hello everyone. If you visit the home page frequently or the CSS forum you might have seen some of the CSS challenges I've posted. This week's challenge is broken in half between CSS and Java Script. The members of the CSS forum have been challenged to style and animate the cards of a game of Memory, and the challenge to the forum is to adjudicate the game. Here's the HTML we are working from

    Code html:
    <!DOCTYPE html>
    <html>
    <head>
      <title>Memory</title>
      <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
      <script type="text/javascript">
        $('.card').click(function(){
    	  $(this).toggleClass('down');
    	});
      </script>
    </head>
    <body>
      <div id="Playfield">
        <div class="card down one" data-face="1"></div>
        <div class="card down two" data-face="2"></div>
        <div class="card down three" data-face="3"></div>
        <div class="card down four" data-face="4"></div>
        <div class="card down five" data-face="5"></div>
        <div class="card down six" data-face="6"></div>
        <div class="card down seven" data-face="7"></div>
        <div class="card down eight" data-face="8"></div>
        <div class="card down nine" data-face="9"></div>
        <div class="card down ten" data-face="10"></div>
        <div class="card down eleven" data-face="11"></div>
        <div class="card down twelve" data-face="12"></div>
        <div class="card down one" data-face="1"></div>
        <div class="card down two" data-face="2"></div>
        <div class="card down three" data-face="3"></div>
        <div class="card down four" data-face="4"></div>
        <div class="card down five" data-face="5"></div>
        <div class="card down six" data-face="6"></div>
        <div class="card down seven" data-face="7"></div>
        <div class="card down eight" data-face="8"></div>
        <div class="card down nine" data-face="9"></div>
        <div class="card down ten" data-face="10"></div>
        <div class="card down eleven" data-face="11"></div>
        <div class="card down twelve" data-face="12"></div>
      </div>
    </body>
    </html>

    Note that I've already provided the CSS members enough JS to test their styling - toggling the classes of the cards. Here's the rest of the challenge, and most of its logic can be tied into the click event above

    The cards are in pairs, their last class name reveals the matching. I don't expect 4 year olds to check and read source code, so don't worry about that - it simplifies the coding.

    When a card is clicked, a check needs to be made if any other cards are face up that do not have the CSS class 'matched'. If there is, does the value of the data-face attribute of the two cards match? If so, the two cards gain the css class of 'matched', otherwise turn both face down.

    You also need to test when all cards are matched to give a 'You Win' result.

    Advanced
    Shuffle the position of the cards. You can do this a number of ways - but if you choose the changing of the data-face value make sure you change the corresponding css class because that's how the styling is going to give the card it's particular image.

    Expert
    Make a two player game and keep score.

    At all levels you may add to the HTML to give additional controls, like a reset game or shuffle button, but don't remove anything or you risk breaking the style sheets being written for the challenge.

    Use spoiler tags to hide your answers to the challenge that you post.

    This...
    [code][spoiler]Like this[/spoiler][/code]

    Renders
    Code:
    Like this

    If you want to participate in the CSS side of this challenge you may do so here.

    Note - the data-face attribute is present to make the JS code easier to write - I realize it's redundant to the final CSS class name. I also know it's possible to style on attributes, but browser support for that trick is uneven (not that such has stopped us before). Both are presented to keep the difficulty of the challenge down some.
    Last edited by cpradio; Mar 4, 2013 at 15:35. Reason: Added closing script tag and http: so the script reference works locally.

  2. #2
    Programming Since 1978 silver trophybronze trophy felgall's Avatar
    Join Date
    Sep 2005
    Location
    Sydney, NSW, Australia
    Posts
    16,789
    Mentioned
    25 Post(s)
    Tagged
    1 Thread(s)
    The problem with using HTML like that is that anyone with JavaScript turned off can see the cards but cannot interact with them. Far better id all the cards are added into the HTML via JavaScript after they have been shuffled. That way you haven't got to update the existing HTML when shuffling and you also don't have a broken game visible on the page when JavaScript is off.

    For a single person version of the game you can also allow them to compare their score this attempt to prior attempts by keeping track of how long it takes. There's nothing particularly difficult about creating a 2 or more player version that keeps score - it would just need each player to be identified first so it can display whose turn it is.
    Stephen J Chapman

    javascriptexample.net, Book Reviews, follow me on Twitter
    HTML Help, CSS Help, JavaScript Help, PHP/mySQL Help, blog
    <input name="html5" type="text" required pattern="^$">

  3. #3
    I solve practical problems. bronze trophy
    Michael Morris's Avatar
    Join Date
    Jan 2008
    Location
    Knoxville TN
    Posts
    2,023
    Mentioned
    62 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by felgall View Post
    The problem with using HTML like that is that anyone with JavaScript turned off....
    This is a Java Script exercise for fun. This is not a "make it work in all possible configurations over as wide an array of devices as possible" assignment. That wouldn't be a challenge - that would be a job.

    The HTML is presented primarily to simplify the exercise.

  4. #4
    Programming Since 1978 silver trophybronze trophy felgall's Avatar
    Join Date
    Sep 2005
    Location
    Sydney, NSW, Australia
    Posts
    16,789
    Mentioned
    25 Post(s)
    Tagged
    1 Thread(s)
    Quote Originally Posted by Michael Morris View Post
    The HTML is presented primarily to simplify the exercise.
    It is rather obvious that the basic exercise is aimed at JavaScript beginners as even the "expert" level suggestion should be a relatively trivial task to produce for anyone with an intermediate level knowledge of JavaScript. My comments were directed more at those considering the more "advanced" variants that you suggested. Anyone with a beginner level knowledge of JavaScript tackling the basic script should disregard all my comments as they are not intended for them.

    For shuffling the cards first the task is definitely easier if that is done before adding the HTML into the page - which makes it easier to consider adding that HTML from JavaScript (at least that's what I found with the various variants of this script that I have created).
    Stephen J Chapman

    javascriptexample.net, Book Reviews, follow me on Twitter
    HTML Help, CSS Help, JavaScript Help, PHP/mySQL Help, blog
    <input name="html5" type="text" required pattern="^$">

  5. #5
    Gre aus'm Pott gold trophysilver trophybronze trophy
    Pullo's Avatar
    Join Date
    Jun 2007
    Location
    Germany
    Posts
    5,890
    Mentioned
    211 Post(s)
    Tagged
    12 Thread(s)
    Hi Michael,

    Really nice idea for a challenge!!

    Here's my entry:

    Code:
    <!DOCTYPE html>
    <html>
      <head>
        <title>Memory</title>
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
        <style>
          .card{
            width:65px;
            height:100px;
            float:left;
            margin:5px;
            color:blue;
            padding:5px;
            border:solid 1px blue;
          }
          
          #Playfield{
            width: 600px;
          }
          
          .down{
            background:blue;
          }
          
          .clear{
            clear:both;
          }
          
          p {
            margin:0; 
            padding: 5px 15px 0 5px;
          }
          
          #playerInfo{
            padding:15px 0 35px 0;
          }
    			
          #player1 p, #player2 p{
            float:left;
          }
          
          .active{
            background: yellow;
          }
        </style>
      </head>
      
      <body>
        <div id="Playfield">
          <div class="card down one" data-face="1"></div>
          <div class="card down two" data-face="2"></div>
          <div class="card down three" data-face="3"></div>
          <div class="card down four" data-face="4"></div>
          <div class="card down five" data-face="5"></div>
          <div class="card down six" data-face="6"></div>
          <div class="card down seven" data-face="7"></div>
          <div class="card down eight" data-face="8"></div>
          <div class="card down nine" data-face="9"></div>
          <div class="card down ten" data-face="10"></div>
          <div class="card down eleven" data-face="11"></div>
          <div class="card down twelve" data-face="12"></div>
          <div class="card down one" data-face="1"></div>
          <div class="card down two" data-face="2"></div>
          <div class="card down three" data-face="3"></div>
          <div class="card down four" data-face="4"></div>
          <div class="card down five" data-face="5"></div>
          <div class="card down six" data-face="6"></div>
          <div class="card down seven" data-face="7"></div>
          <div class="card down eight" data-face="8"></div>
          <div class="card down nine" data-face="9"></div>
          <div class="card down ten" data-face="10"></div>
          <div class="card down eleven" data-face="11"></div>
          <div class="card down twelve" data-face="12"></div>
        </div>
        
        <div id="playerInfo" class="clear">
          <div id="player1">
            <p><strong>Player 1:</strong></p>
            <p>Turns Taken: <span class="noTurns">0</span></p>
            <p>Pairs matched: <span class="noPairs">0</span></p>
          </div>
          
          <div id="player2" class="clear">
            <p><strong>Player 2:</strong></p>
            <p>Turns Taken: <span class="noTurns">0</span></p>
            <p>Pairs matched: <span class="noPairs">0</span></p>
          </div>
        </div>
        <p class="clear"><a href="#" id="reset">Click here to reset game</a></p>
        
        <script type="text/javascript">
          function twoCardsFaceUp(){
            return $(".up").length == 2;
          }
          
          function cardsMatch(){
            return $(".up:eq(0)").text() == $(".up:eq(1)").text();
          }
          
          function markCardsAsMatched(){
            $(".up").each(function(){
              $(this).addClass("matched").removeClass("up").off("click");
            });
          }
          
          function updateScore(player){
            var el = $(player).find(".noPairs");
            var p = Number(el.text());
            el.text(p+1);
          }
          
          function allCardsMatched(){
            return ($(".matched").length == 24)
          }
          
          function flipCardsBackOver(){
            setTimeout(function(){
              $(".up").each(function(){
                $(this).addClass("down").removeClass("up");
              });
            }, 1000);
          }
          
          function shuffle(cards){
            // Uses Fisher–Yates shuffle
            // See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
            var i = cards.length, j, tempi, tempj;
            if ( i == 0 ) return false;
            while ( --i ) {
              j = Math.floor( Math.random() * ( i + 1 ) );
              tempi = cards[i];
              tempj = cards[j];
              cards[i] = tempj;
              cards[j] = tempi;
            }
            return cards;
          }
          
          function highlightCurrentPlayer(player){
            $("#playerInfo p").each(function(){
              if ($(this).hasClass("active")){
                $(this).removeClass("active");
              }
            });
            $(player).find("p").first().addClass("active");
          }
          
          function incrementTurns(player){
            var el = $(player).find(".noTurns");
            var t = Number(el.text());
            el.text(t+1);
          }
          
          function updateCurrentPlayer(player){
            currentPlayer = (currentPlayer.match(/1/))? "#player2" : "#player1"
          }
          
          function winner(){
            var playerOnePoints = Number($("#player1").find(".noPairs").text());
            var playerTwoPoints = Number($("#player2").find(".noPairs").text());
            if (playerOnePoints > playerTwoPoints){
              return "Player one won!";
            } else if (playerOnePoints < playerTwoPoints){
              return "Player two won!";
            } else {
              return "An honourable draw!";
            }
          }
          
          // Shuffle cards
          var cards = $(".card");
          cards.remove();
          cards = shuffle(cards);
          cards.appendTo($("#Playfield"));
          
          // Main Loop
          $('.card').on("click", function(){
            if ($(".up").length == 2){
              return false;
            }
      
            $(this).removeClass("down").addClass("up");
            
            if (twoCardsFaceUp()){
              incrementTurns(currentPlayer);
              if (cardsMatch()){
                markCardsAsMatched();
                updateScore(currentPlayer);
                if (allCardsMatched()){
                  alert(winner());
                }
              }else{
                flipCardsBackOver();
                updateCurrentPlayer(currentPlayer);
                setTimeout(function(){highlightCurrentPlayer(currentPlayer)}, 1000);
              }
            }
          });
          
          $("#reset").click(function(){
            location.reload();
          });
          
          $(".card").each(function(){
            var num = $(this).data("face");
            $(this).text(num);
          });
          
          var currentPlayer = "#player1";
          highlightCurrentPlayer(currentPlayer);
        </script>
      </body>
    </html>
    I also made a demo in case anyone wants to check it out.

  6. #6
    SitePoint Member
    Join Date
    May 2011
    Posts
    2
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'm doing both challenges (CSS and JS). I'm covering the advanced and expert things. I used some of the CSS I did for my CSS entry so you can actually see the divs (basically sans transitions), but the Playfield and its stuff is completely unchanged. Here is my JS entry (also uploaded a demo for convenience):

    Code:
    <!DOCTYPE html>
    <html>
    <head>
      <title>Memory</title>
      <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
      <script type="text/javascript">
        $(function () {
            var freeze = false,
                firstShuffle = true,
                curPlayer = 0,
                maxPlayers = 1,
                shuffle,
                cardClick,
                changeGameType,
                showScore,
                setupPlayers,
                players,
                tCards,
                determineWinner;
            showScore = function () {
                var i;
                for (i = 0; i < players.length; i += 1) {
                    players[i].el.innerHTML = '<b>Player ' + (i + 1) + '</b>: matches: ' + players[i].matches + '; turns: ' + players[i].turns;
                    if (curPlayer === i) {
                        $(players[i].el).addClass('active');
                    } else {
                        $(players[i].el).removeClass('active');
                    }
                }
            };
            determineWinner = function () {
                var winner = 0, isDraw, i;
                if (players.length === 1) {
                    alert('You Win!');
                } else {
                    isDraw = true;
                    for (i = 1; i < players.length; i += 1) {
                        if (players[i].matches !== players[winner].matches) { isDraw = false; }
                        if (players[i].matches > players[winner].matches) {
                            winner = i;
                        }
                    }
                    if (isDraw) {
                        alert('Alas, a draw!');
                    } else {
                        alert('Player ' + (winner + 1) + ' wins!');
                    }
                }
            };
            cardClick = function () {
                var t;
                if (!freeze) {
                    if (!$(this).hasClass('matched') && $(this).hasClass('down')) {
                        $(this).removeClass('down');
                        t = $('.card:not(.down, .matched)');
                        if (t.length >= 2) {
                            players[curPlayer].turns += 1;
                            if (t[0].getAttribute('data-face') === t[1].getAttribute('data-face')) {
                                $(t).addClass('matched');
                                players[curPlayer].matches += 1;
                                if ($('.matched').length === $('.card').length) {
                                    showScore();
                                    determineWinner();
                                }
                            } else {
                                freeze = true;
                                setTimeout(function () {
                                    $(t).addClass('down');
                                    freeze = false;
                                    curPlayer += 1;
                                    if (curPlayer >= players.length) { curPlayer = 0; }
                                    showScore();
                                }, 1000);
                            }
                            showScore();
                        }
                    }
                }
            };
            shuffle = function () {
                var allCards, i;
                allCards = $('.card');
                $(allCards).addClass('down').removeClass('matched');
                tCards = [];
                while (allCards.length > 0) {
                    i = parseInt(Math.random()*allCards.length);
                    tCards.push(allCards[i]);
                    allCards.splice(i,1);
                }
                if (firstShuffle) {
                    $('#Playfield').empty();
                    $('#Playfield').append(tCards);
                    $('.card').click(cardClick);
                    firstShuffle = false;
                } else {            
                    freeze = true;
                    setTimeout(function () {
                        $('#Playfield').empty();
                        $('#Playfield').append(tCards);
                        $('.card').click(cardClick);
                        freeze = false;
                    }, 700);
                }
            };
            setupPlayers = function () {
                var i;
                players = [];
                $('#PlayScore').empty();
                for (i = 0; i < maxPlayers; i += 1) {
                    players.push({turns: 0, matches: 0, el: document.createElement('li')});
                    $('#PlayScore').append(players[i].el);
                }
            };
            changeGameType = function () {
                $(this).text('Play ' + maxPlayers + '-player game');
                maxPlayers = maxPlayers === 1 ? 2 : 1;
                setupPlayers();
                showScore();
                shuffle();
            };
            $('#shuffleButton').click(shuffle);
            $('#playerButton').click(changeGameType);
            setupPlayers();
            showScore();
            shuffle();
        });
      </script>
      <style type="text/css">
        #Playfield {
            width: 500px;
            height: 300px;
            margin: 0 auto;
        }
        #PlayControls, #PlayScore {
            width: 500px;
            margin: 0 auto;
        }
        .card {
            background-color: #ffffff;
            border: 1px solid black;
            border-radius: 0px;
            float: left;
            width: 50px;
            height: 50px;
            padding: 5px;
            margin: 5px;
        }
        
        .card.one {
            border-radius: 0px;
        }
        .card.two {
            border-radius: 10px;
        }
        .card.three {
            border-radius: 30px;
        }
        .card.four {
            border-top-left-radius: 60px;
        }
        .card.five {
            border-top-right-radius: 60px;
        }
        .card.six {
            border-bottom-right-radius: 60px;
        }
        .card.seven {
            border-bottom-left-radius: 60px;
        }
        .card.eight {
            border-top-left-radius: 60px;
            border-bottom-right-radius: 60px;
        }
        .card.nine {
            border-top-right-radius: 60px;
            border-bottom-left-radius: 60px;
        }
        .card.ten {
            border-top-left-radius: 30px;
            border-top-right-radius: 30px;
        }
        .card.eleven {
            border-bottom-left-radius: 30px;
            border-bottom-right-radius: 30px;
        }
        .card.twelve {
            border-top-right-radius: 30px;
            border-bottom-left-radius: 30px;
            border-bottom-right-radius: 30px;
        }
        .card.down {
            background-color: #000000;
            border-radius: 0px;
        }
        .card.matched {
            background-color: #999999;
        }
        li.active {
            background-color: #aaaaff;
        }
      </style>
    </head>
    <body>
      <div id="Playfield">
        <div class="card down one" data-face="1"></div>
        <div class="card down two" data-face="2"></div>
        <div class="card down three" data-face="3"></div>
        <div class="card down four" data-face="4"></div>
        <div class="card down five" data-face="5"></div>
        <div class="card down six" data-face="6"></div>
        <div class="card down seven" data-face="7"></div>
        <div class="card down eight" data-face="8"></div>
        <div class="card down nine" data-face="9"></div>
        <div class="card down ten" data-face="10"></div>
        <div class="card down eleven" data-face="11"></div>
        <div class="card down twelve" data-face="12"></div>
        <div class="card down one" data-face="1"></div>
        <div class="card down two" data-face="2"></div>
        <div class="card down three" data-face="3"></div>
        <div class="card down four" data-face="4"></div>
        <div class="card down five" data-face="5"></div>
        <div class="card down six" data-face="6"></div>
        <div class="card down seven" data-face="7"></div>
        <div class="card down eight" data-face="8"></div>
        <div class="card down nine" data-face="9"></div>
        <div class="card down ten" data-face="10"></div>
        <div class="card down eleven" data-face="11"></div>
        <div class="card down twelve" data-face="12"></div>
      </div>
      <div id="PlayControls">
        <button id="shuffleButton">Shuffle/reset</button>
        <button id="playerButton">Play 2-player game</button>
      </div>
      <div id="PlayScore"></div>
    </body>
    </html>
    Here is a demo of both the JS and CSS entries working together, in case if anyone's interested hehe. x] This and my post at the CSS challenge are my first posts. I did a few of the challenges but I never posted them for reasons unknown to myself.

  7. #7
    SitePoint Wizard bronze trophy chris.upjohn's Avatar
    Join Date
    Apr 2010
    Location
    Melbourne, AU
    Posts
    2,189
    Mentioned
    17 Post(s)
    Tagged
    1 Thread(s)
    @Pullo ; I found a minor flaw in your version of the game which is if you press Ctrl + A you can see where all the matching numbers are

  8. #8
    Gre aus'm Pott gold trophysilver trophybronze trophy
    Pullo's Avatar
    Join Date
    Jun 2007
    Location
    Germany
    Posts
    5,890
    Mentioned
    211 Post(s)
    Tagged
    12 Thread(s)
    Aww dude, that's not a flaw, it's a feature!!

    Just kidding
    Thanks for pointing it out.
    I fixed this in my demo.

  9. #9
    SitePoint Wizard bronze trophy chris.upjohn's Avatar
    Join Date
    Apr 2010
    Location
    Melbourne, AU
    Posts
    2,189
    Mentioned
    17 Post(s)
    Tagged
    1 Thread(s)
    Quote Originally Posted by Pullo View Post
    Aww dude, that's not a flaw, it's a feature!!

    Just kidding
    Thanks for pointing it out.
    I fixed this in my demo.
    No problem, I used it to cheat for about 10 minutes since it was a "feature"

  10. #10
    SitePoint Member
    Join Date
    Dec 2012
    Posts
    1
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    @jaythar Could you add local storage to your game, to show us how that works. Could you use it to keep a record of our attempts, so that we can see if we are improving?

  11. #11
    SitePoint Member
    Join Date
    Mar 2013
    Posts
    1
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Question Alternative cheat

    OK bit cheeky but by that logic if you use any developer toolbar/view source you can view the data attributes and also just cheat and match them. Using just HTML and JS how would you suggest you mask the data-ids? Only way I can suggest is by minimising the JS file and hiding performing some kind of hash on each data-id making each card unique?!

    Quote Originally Posted by Pullo View Post
    Aww dude, that's not a flaw, it's a feature!!

    Just kidding
    Thanks for pointing it out.
    I fixed this in my demo.

  12. #12
    Programming Since 1978 silver trophybronze trophy felgall's Avatar
    Join Date
    Sep 2005
    Location
    Sydney, NSW, Australia
    Posts
    16,789
    Mentioned
    25 Post(s)
    Tagged
    1 Thread(s)
    Quote Originally Posted by themagicofit View Post
    Using just HTML and JS how would you suggest you mask the data-ids?
    Perhaps by not using them at all since they are in fact completely unnecessary to being able to get a memory game script to work. If the JavaScript only adds the information about the card selected into the HTML while the card is visible then you can't tell what any of the other cards are unless you actually set a breakpoint in the script itself so as to be able to see what is stored where in the array.
    Stephen J Chapman

    javascriptexample.net, Book Reviews, follow me on Twitter
    HTML Help, CSS Help, JavaScript Help, PHP/mySQL Help, blog
    <input name="html5" type="text" required pattern="^$">


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •