User:Kruhly/jqCountingCardGame.js

From WikiEducator
Jump to: navigation, search

Note: After saving, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Clear the cache in Tools → Preferences
//Created by Robert J. Kruhlak
//License: CC-BY
//Inspiration: Elated.com
//Modification of Drag and Drop and Card Style from Elated.com
//See http://www.elated.com/res/File/articles/development/javascript/jquery/
// drag-and-drop-with-jquery-your-essential-guide/card-game.html
 
;(function($){
 
  var symbols=['♣', '♥', '♠', '♦', '◊', '∇', 'Δ',  '∞'];
  var myNumbers = [ 1, 2, 3, 4, 5, 6,7,8,9,10,11,12,13,14,15, 16,17,18,19,20];
 
  $.fn.countingCardGame = function(options) {
 
      var defaults = {
          numbers: myNumbers, // Numbers used for matching and counting
          __correctCards: 0, // Initialize to zero, increment when cards are placed correctly
          __incorrectCards: 0,
          cardColor: 'purple',
          cardPileLabel: 'Take a card',
          slotsTableColor: 'blue',
          lowCard: 1,
          highCard: 1,
          minCard: 1,
          maxCard: myNumbers.length,
          minColumns: 1,
          maxColumns: 7,
          cardColumns: 5,
          symbolIndex: 1,
          symbols: symbols,
          shufflePictures: true,
          shufflePicturesLabel: 'Shuffle Pictures',
          shuffleNumbers: true,
          shuffleNumbersLabel: 'Shuffle Numbers',
          successMessage: 'You did it!',
          randIds: 1,
          animate: true,
          };
      if (options){
        checkIntOptions(defaults, options);
        options.minColumns = checkMinMax(options.minColumns,options.maxColumns);
        options.lowCard =  checkMinMax(options.lowCard,options.highCard);
      }
      var options = $.extend(defaults, options);
 
      // support mutltiple elements
      if (this.length > 1){
       this.each(function() {
        $(this).countingCardGame(options)
       });
       return this;
      }
      var ids = {
          debug: 0,
          obj:  this.attr('id'),
          license: 'CC-BY',
          functionPanel: 'functionPanel',
          slot: 'slot',
          slotsTable: 'slotsTable',
          cardTable: 'cardTable',
          cardTableRow: 'cardTableRow',
          cell: 'cell',
          card: 'card',
          cardFace: 'cardFace',
          cardPile:   'cardPile',
          panelList:  'panelList',
          shufflePictures: 'shufflePicts',
          shuffleNumbers:  'shuffleNums',
          shuffleFaces: 'shuffleFaces',
          resetButton: 'resetButton',
          rangeSlider: 'slider-range',
          lowCard:   'low',
          highCard:  'high',
          columnsSlider: 'columnsSlider',
          columnsSliderLabel: 'cardTableColumns',
          symbolsSlider: 'symbolsSlider',
          symbolsSliderLabel: 'symbol',
          successMessage: 'successMessage',
          };
      //Prepend a random string to ids so more than one game
      // can be put on a page. Needs to be done before
      // other objects are populated with ids.
 
      if (options.randIds){ ids=prependRandomString(ids);}
 
      var labels = {
 
          cardPile: options.cardPileLabel,
          shufflePictures: options.shufflePicturesLabel,
          shuffleNumbers: options.shuffleNumbersLabel,
          lowCard: 'Low Card',
          highCard: 'High Card',
          resetButton: 'Reset',
          shuffleFaces:   'Suffle Faces',
          columnsSlider: 'Columns: ',
          symbolsSlider: 'Symbol: ',
          };
 
      var symbolsSlider = {
          min: 0,
          max: (symbols.length-1),
          value: options.symbolIndex,
          id: ids.symbolsSlider,
          labelId: ids.symbolsSliderLabel,
          label: labels.symbolsSlider,
          symbols: symbols,
          };
 
      var columnsSlider = {
          min: options.minColumns,
          max: options.maxColumns,
          value: options.cardColumns,
          id: ids.columnsSlider,
          labelId: ids.columnsSliderLabel,
          label: labels.columnsSlider,
          };
      var sliders = {'columns': columnsSlider, 'symbols': symbolsSlider};
 
      this.getOptions = function() {
            return options;
          };
 
      this.printOptions = function(){
            alert(dump(options));
           };
 
      this.getIds = function() {
            return ids;
          };
      this.getLabels = function() {
            return labels;
          };
 
      this.getSliders = function() {
            return sliders;
          };
 
 
      //Private functions
      //UI
      // Card faces are added to cardSlots
      function addCardFace(id, num, symbol, opts, ids){
 
        var visArray=[];
 
        for (var i in opts.numbers){
          visArray.push(0);
        }
        // set visible symbols
        for (i=0; i<num; i++){
          visArray[i]=1;
        }
        // Randomize the placement of the visible symbols
        visArray=fisherYates(visArray);
 
        // need to send number as data for droppable code
        $('<table><tbody></tbody></table>').data( 'number', num )
                                           .attr( 'id', ids.cardFace + id )
                                           .appendTo('#' + ids.slot+ id);
 
        // A card face can hold 20 symbols five rows by 4 columns
        // is the max that will fit in a slot
        var maxFaceRows = 5;      // May be able to generalize this
        var maxFaceColumns = 4;
 
        for (j=0; j<maxFaceRows; j++){
          $('<tr></tr>').attr('id', ids.cardFace+ id +'r' + j)
                        .appendTo('#' + ids.cardFace + id);
 
          for (i = j*maxFaceColumns; i < ((j+1)*maxFaceColumns); i++){
             $('<td><div>'+symbol+'</div></td>').attr('id', ids.cardFace + id + 'c' + i)
                                                .appendTo('#' + ids.cardFace + id +'r' + j);
             // <div> above is necessary because drag and drop don't play well with tables.
             $('#' + ids.cardFace +id+'c'+i).addClass("hidden");
             if (symbol == "&hearts;" || symbol == "&diams;" || symbol == "&loz;" || symbol == "&Delta;" ){
               $('#' + ids.cardFace + id + 'c' + i).addClass("red");
             }
             if (visArray[i]){
              $('#' + ids. cardFace + id + 'c' + i).toggleClass("hidden");
             }
          }
        }
      };
 
      function addCardSymbol(ind,symbols){
 
        if (parseInt(ind)>=0 && parseInt(ind)< symbols.length ){
           return symbols[ind];
        }else{
           return symbols[0];
        }
      };
 
      function addCardSlots( picts, ids, opts ){
 
        var myMin = $('#' + ids.lowCard).val();
        var myMax = $('#'  + ids.highCard).val();
        picts = picts.slice((myMin-1),myMax);
        var myColumns = $( '#' + ids.columnsSlider ).slider( 'value' );
        var myRows = Math.ceil(picts.length/myColumns);
 
 
        if ($('#' + ids.shufflePictures).is(':checked')){
          picts=fisherYates(picts);
        }
 
        resizeUI(myRows,myColumns,ids);
        removeHTML(ids.cardTable);
        createCardSlots(picts, myRows, myColumns, ids, opts);
 
        if (ids.debug){ alert($('#' + ids.obj).css('height'))}
      };
 
      function resizeUI(myRows, myColumns, ids){
        //Used when card columns, number of cards change.
        var sTableWidth=98*myColumns;
        var sTableHeight = 142*myRows;
        var contentWidth = 315+sTableWidth;
        var contentHeight = 30+ sTableHeight;
 
 
        $('#' + ids.slotsTable).css('width', sTableWidth + 'px');
        $('#' + ids.obj).css({'width': contentWidth + 'px', 'height': contentHeight + 'px'});
        $('#' + ids.obj).css({'min-width': contentWidth + 'px'});
 
      };
      function createCardSlots( picts, myRows, myColumns, ids, opts){
 
        var pictIndex=0;
        var correctCards = opts.__correctCards;
        for ( ii = 0; ii < myRows; ii++) {
 
          $('<tr></tr>').attr('id', ids.cardTableRow + ii).appendTo('#' + ids.cardTable);
 
          for ( jj=(ii*(myRows-1)); jj < ((ii*(myRows-1)) + myColumns); jj++ ) {
            if (pictIndex < picts.length) {
              $('<td></td>').attr('id', ids.cell + pictIndex).appendTo('#' + ids.cardTableRow + ii);
              $('<div class="slot"></div>').data( 'number', picts[pictIndex])
                                           .data( 'ids', ids )
                                           .data( 'opts', opts)
                                           .attr( 'id', ids.slot + picts[pictIndex] )
                                           .appendTo( '#' + ids.cell + pictIndex )
                                           .droppable( {
                                             accept: '#' + ids.cardPile + ' div',
                                             hoverClass: 'hovered',
                                             drop: handleCardDrop
                                           } );
              var myPict = picts[pictIndex];
              addCardFace(myPict, myPict, addCardSymbol($( '#' + ids.symbolsSlider ).slider( 'value' ), opts.symbols),
              opts,ids);
            }
            pictIndex=pictIndex+1;
         }
       }
 
      };
 
      function addCardPile(numbers, ids, opts){
 
        removeHTML(ids.cardPile);
        numbers=numbers.slice(($('#' + ids.lowCard).val()-1), $('#' + ids.highCard).val());
        numbers=numbers.reverse(); // reverse so 1 starts when unshuffled
 
        if ($('#' + ids.shuffleNumbers).is(':checked')){
          numbers=fisherYates(numbers);
        }
       return createCardPile(numbers, ids, opts);
 
      };
      function animateTopCard(myShift,myDuration){
          $('.animate').animate({'marginLeft': myShift, 'marginTop': -myShift},myDuration, 'swing')
               .animate({'marginLeft':'10px', 'marginTop': '10px'}, myDuration, 'swing', function() {
                animateTopCard(myShift,myDuration);});
      };
 
      function stopAnimation(){
 
          $('.animate').stop(true,true)
                       .animate({'marginLeft':'10', 'marginTop' : '10'},1000)
                       .stop(true,true).removeClass('animate');
 
 
      };
 
      function addLicense(id){
        $('#' + id).html('<a rel="license" href="http://creativecommons.org/licenses/by/3.0/"><img alt="Creative Commons Attribution 3.0 Unported License" style="border-width:0" src="http://upload.wikimedia.org/wikipedia/commons/1/16/CC-BY_icon.svg"></a>').css({'padding': '10px'})
      };
 
      function createCardPile( numbers, ids, opts ){
        var cPId = '#' + ids.cardPile;
        var cardId = ids.card;
        var topCardId = '#'+cardId+numbers[numbers.length-1];
        if (ids.debug){alert($('#' + ids.obj).attr('id'));}
 
        for ( var i in numbers) {
          $('<div>' + numbers[i] + '</div>').data( 'number', numbers[i] )
            .attr( 'id', cardId + numbers[i] ,'z-index',i,'position','absolute')
            .css( {'background': opts.cardColor})
            .appendTo( cPId).draggable( {
              containment: $('#' + ids.obj),
              stack: cPId + ' div',
              cursor: 'move',
              revert: true
          } );
        }
        $(topCardId).addClass('animate').hover(function(){stopAnimation();});
        return '#'+cardId+numbers[numbers.length-1];
      };
 
      function createFunctionPanel(ids, opts, labels){ // will be the right panel in UI
        var cPId=ids.cardPile;
        return $('<div></div>').addClass( 'functionPanel')
                               .attr('id', ids.functionPanel)
                               .append(myLabel(cPId, labels.cardPile))
                               .append(myDiv(cPId).addClass('cardPile'))
                               .append(createPanelList(ids,opts,labels));
      };
 
      function createPanelList(ids,opts,labels){
 
       return myList(ids.panelList)
            .append(addCheckboxToList(ids.shufflePictures, labels.shufflePictures,opts.shufflePictures))
            .append(addCheckboxToList(ids.shuffleNumbers, labels.shuffleNumbers,opts.shuffleNumbers))
            .append(addButtonToList(ids.resetButton,labels.resetButton,'resetButton'))
            .append(addInputTextboxToList(ids.lowCard, labels.lowCard))
            .append(addInputTextboxToList(ids.highCard, labels.highCard))
            .append(addDiv(ids.rangeSlider))
            .append(addP(ids.symbolsSliderLabel))
            .append(addDiv(ids.symbolsSlider))
            .append(addP(ids.columnsSliderLabel))
            .append(addDiv(ids.columnsSlider))
            .append(addDiv(ids.license));
      };
 
      function createSuccessMessage(ids, opts){
 
          $('<div></div>').appendTo($('#' + ids.obj))
                          .attr('id', ids.successMessage)
                          .css('position', 'absolute')
                          .addClass('successMessage')
                          .append($('<h2>'+opts.successMessage +'</h2>'))
                          .append($('<div></div>').attr('id','stats'))
                          .append( $('<button></button>')
                            .html('Play Again')
                            .click({'ids': ids, 'opts': opts}, handleClick)
                          );
 
          hideSuccessMessage(ids);
      };
 
 
      function hideSuccessMessage(ids){ // Hide the success message
          var $sMId = $('#' + ids.successMessage);
          var $objId = $('#' + ids.obj);
          $sMId.hide();
          $sMId.css( {
          'top': $objId.position().top,
          'left': $objId.position().left,
          'width': '0',
          'height': '0',
          } );
      };
 
      // Event Handlers
 
      function handleCardDrop( event, ui ) {
        var slotNumber = $(this).data( 'number' );
        var cardNumber = ui.draggable.data( 'number' );
        var ids= $(this).data( 'ids');
        var opts = $(this).data('opts');
        // currently sent as data
        // because handleCardDrop can't take any other parameters
 
 
        var $sMId =$('#' + ids.successMessage);
 
        // If the card was dropped to the correct slot,
        // change the card colour, position it directly
        // on top of the slot, and prevent it being dragged
        // again
        if ( slotNumber == cardNumber ) {
          ui.draggable.addClass( 'correct' );
          ui.draggable.draggable( 'disable' );
          $(this).droppable( 'disable' );
          ui.draggable.position( { of: $(this), my: 'left top', at: 'left top' } );
          ui.draggable.draggable( 'option', 'revert', false );
          opts.__correctCards++;
 
        } else {
          opts.__incorrectCards++;
        }
 
        // If all the cards have been placed correctly then display a message
        // and reset the cards for another go
 
        if ( opts.__correctCards ==  ($('#' + ids.highCard).val()-$('#' + ids.lowCard).val()+1)) {
          $('#stats').html('Correct: ' + opts.__correctCards + ' Incorrect: ' + opts.__incorrectCards);
          $sMId.show();
 
          var $objId = $('#'+ ids.obj);
 
          $sMId.animate( {
            left: $objId.position().left + $objId.width()/4,
            top: $objId.position().top + $objId.height()/4,
            width: '300px',
            height: '130px',
            opacity: 1
          } );
        }
 
      };
 
      function setHandleClick(id, ids, opts){
        // ids, and opts needed as parameters for handleClick
        // so that the game can be updated properly.
        $('#' + id).click({'ids': ids, 'opts': opts}, handleClick);
      };
 
      function updateSlider() {
          var ids = $(this).data('ids');
          var opts = $(this).data('opts');
          if (ids.debug) {alert( 'Check a value of ids array ' +ids.cardPile);}
          if (ids.debug) {alert('Check a value of opts array ' + opts.numbers);}
          resetGame(opts,ids);
      };
 
      function handleClick(event){
        ids=event.data.ids;
        opts = event.data.opts;
        if (ids.debug){ alert('HandleClick event.data.ids.cardPile=' + ids.cardPile);}
        if (ids.debug) {alert('HandleClick event.data.opts.numbers' + opts.numbers);}
        resetGame(opts,ids);
      };
 
      function resetGame(opts,ids){ // Reset the game
          stopAnimation();
          hideSuccessMessage(ids,opts);
          opts.__correctCards = 0;
          opts.__incorrectCards = 0;
          var card1Id=addCardPile(opts.numbers,ids, opts);
          addCardSlots(opts.numbers,ids, opts);
 
          if (opts.animate) {
            jQuery.fx.interval = 50;
            $('.animate').delay(1000).animate({'marginLeft':'10px'},50);
            animateTopCard(-40,2000);
          }
      };
 
      //Sliders
 
      function createSlider( o, ids, opts){
 
          var $id = $('#'+ o.id).data('ids', ids)
                                .data('opts', opts);
          var $labelId = $('#'+ o.labelId);
 
 
          $id.slider({
              min: o.min,
              max: o.max,
              value: o.value,
              slide: function(event, ui) {
                  $labelId.html(o.label + ui.value);
              },
              stop: updateSlider,
          });
          //Render initial value
          $labelId.html( o.label + $id.slider( 'value' ) ); // In this instance it is a <p>
          $id.find('a').css({'background':'transparent','border':'none','text-align':'center','text-decoration':'none'}).html('&#9660;');
 
      };
 
      function createSymbolSlider( o, ids, opts){
 
          var $id = $('#'+ o.id).data('ids', ids)
                                .data('opts', opts);
          var $labelId = $('#'+ o.labelId);
 
 
          $id.slider({
              min: o.min,
              max: o.max,
              value: o.value,
              slide: function(event, ui) {
                  $labelId.html(o.label + addCardSymbol(ui.value, opts.symbols));
              },
              stop: updateSlider,
          });
          //Render initial value
          $labelId.html( o.label + addCardSymbol( $id.slider( 'value' ), opts.symbols )); // In this instance it is<p>
          $id.find('a').css({'background':'transparent','border':'none','text-align':'center','text-decoration':'none'}).html('&#9650;');
 
      };
 
      function createRangeSlider(o,ids, opts){
 
        var $sliderId = $('#' + ids.rangeSlider ).data('ids', ids)
                                                 .data('opts', opts);
        var $lowId = $( '#' + ids.lowCard );
        var $highId = $( '#' + ids.highCard );
 
        $sliderId.slider({
  			  range: true,
  			  min: o.minCard,
  			  values: [ o.lowCard, o.highCard ],
  			  max: o.maxCard,
          slide: function( event, ui ) {
  			     $lowId.val( ui.values[ 0 ] );
             $highId.val( ui.values[ 1 ] );
  			     },
          stop: updateSlider
  		  });
        // Render initial values
  		  $lowId.val( $sliderId.slider( "values", 0 )).attr('disabled',true);
        $highId.val( $sliderId.slider( "values", 1 )).attr('disabled',true);
        $sliderId.find($('a')[0]).css({'background':'transparent no-repeat','border':'none','text-align':'left','text-decoration':'none'}).html('&#9654;');
        $sliderId.find($('a')[1]).css({'background':'transparent','border':'none','text-align':'right','text-decoration':'none'}).html('&#9664;');
 
      };
 
      //Utilities
      function dump(obj) {
        var out = '';
        for (var key in obj) {
            out += key + ": " + obj[key] + "\n";
        }
        return out;
      };
      function checkMinMax(low,high){
        if (low > high){
          return high;
        }
      };
 
      function checkIntOptions(defaults, options){
        for (var key in defaults){
          switch(key){
            case 'minCard':
            case 'maxCard':
            case 'lowCard':
            case 'highCard':
            case '__correctCards':
            case '__incorrectCards':
            case 'minColumns':
            case 'maxColumns':
            case 'cardColumns':
            case 'symbolIndex':
            case 'randIds':
              if (key in options) {
                if (!isInt(options[key])){
                  options[key]=defaults[key];
                  console.log(key + ' is not an integer. Reverting to default.');
                  alert(key + 'is not an integer. Reverting to default.');
                } else {
                  options[key]=Math.abs(options[key]);
                }
              }
              break;
            default:
 
              if (key in options){
               console.log(key + ' is ' + options[key]);
              }
          }
        }
      };
 
      function isInt(val){
         //regex options /^[0-9]+$/.test(val);
        if((parseFloat(val) == parseInt(val)) && !isNaN(val)){
          return true;
        } else {
          return false;
        }
      };
 
 
      function prependRandomString(ids){
 
        var randomString = 'R'+Math.floor(10000*Math.random()).toString();
 
          for (var key in ids){
            if (key != 'debug' && key !='obj' && key != 'license'){
              ids[key] = randomString + ids[key];
            }
          }
        return ids;
      };
 
      function removeHTML(id){
        $('#' + id).html( '' ).removeData();
      };
      function myTable(id){
        return $('<table></table>').attr('id', id);
      };
 
 
 
      function myLabel(id,text){
          return $('<label for=' + id + '></label>').html(text);
      };
 
      function myDiv(id){
          return $('<div></div>').attr( 'id', id);
      };
 
      function myP(id){
          return $('<p></p>').attr( 'id', id);
      };
 
      function myList(id){
        return $('<ul></ul>').attr( 'id', id)
                             .css({'list-style-image':'none','list-style-type':'none','padding': '10'});
      };
 
      function addDiv (id){
        return $('<li></li>').append(myDiv(id));
      };
 
      function addP(id){
        return $('<li></li>').append(myDiv(id));
      };
 
      function addCheckboxToList(id,text,checked){
 
        if (checked){
          var $in=$('<input type="checkbox"  checked="checked" />').attr('id',id);
        } else {
          var $in=$('<input type="checkbox"  />').attr('id',id);
        }
          return $('<li></li>').append(
                          $in
                       ).append(
                          $('<label for='+id+'>'+text+'</label>')
                       );
      };
 
      function addButtonToList(id, text, myClass){
          return $('<li></li>').append(
                                  $('<button></button>').attr('id', id).addClass(myClass).html(text)
                              );
      };
 
      function addInputTextboxToList( id, text){
          return $('<li></li>').append(
                                  $('<label for=id>' + text + '</label>')
                               ).append(
       	                        $('<input type="text" id=' + id + '  maxlength="4" size="4"/>')
                               );
      };
 
      function fisherYates(myArray){
        //Randomizes the order of the array.
        var i = myArray.length;
        while(i--){
          var j=Math.floor(Math.random()*(i+1));
          var tempi=myArray[i];
          myArray[i]=myArray[j];
          myArray[j]=tempi;
        }
        return myArray;
      };
 
      //Initialization
      function initialize(obj){
          var opts=obj.getOptions();
          var ids = obj.getIds();
          var labels = obj.getLabels();
          var sliders= obj.getSliders();
 
          obj.append(myDiv(ids.slotsTable)
             .append(myTable(ids.cardTable)));
 
          $('#'+ids.slotsTable).addClass('slotsTable')
                               .css({'background': opts.slotsTableColor});
          $('#'+ids.cardTable).addClass('cardTable')
                              .css({'background': opts.slotsTableColor});
 
          obj.append(createFunctionPanel(ids, opts, labels));
          createSlider( sliders.columns, ids, opts);
          createSymbolSlider( sliders.symbols,ids, opts);
          createRangeSlider(opts,ids, opts);
          createSuccessMessage(ids, opts);
          addLicense(ids.license);
          setHandleClick(ids.resetButton, ids, opts);
          setHandleClick(ids.shufflePictures, ids, opts);
          setHandleClick(ids.shuffleNumbers, ids, opts);
 
          // Create the pile of shuffled cards
          var card1Id=addCardPile(opts.numbers, ids, opts);
 
          // Add cards to Table
 
          addCardSlots(opts.numbers, ids, opts);
 
          // flesh out the game area
          // in the event that the game is attached
          // to a zero height div.
          var cssObj = {'min-height' : '520px',
                    'min-width' : '800px',
                    'text-align': 'center',
                    '-moz-user-select': 'none',
                    '-webkit-user-select': 'none',
                    'user-select' : 'none',
                    'margin' : '0 auto 0 20px',
                    };
 
          obj.css(cssObj);
          if (opts.animate) {
            jQuery.fx.interval = 50;
            $('.animate').delay(1000);
            animateTopCard(-40,2000);
          };
 
        return obj;
      };
 
    return initialize(this);
  }
})(jQuery);