<?xml version="1.0" encoding="UTF-8"?>
<Module>
  <ModulePrefs
    title="Pick-up Sticks"
    height="500px"
    description="Wave implementation of pick-up sticks"
    author="Bryan Bibat"
    author_email="bryan.bibat@gmail.com">
   <Require feature="wave"/>
   <Require feature="dynamic-height"/>
   <Require feature="locked-domain"/>
  </ModulePrefs>
  <Content type="html">
  <![CDATA[
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
  <script src="http://wave.bryanbibat.com/js/jquery-ui-1.7.2.custom.min.js"></script>
  <link rel="stylesheet" href="http://wave.bryanbibat.com/css/cupertino/jquery-ui-1.7.2.custom.css" type="text/css" media="all" />
   
  <script type="text/javascript">
  var NUMBER_OF_STICKS = 60;
  var PENALTY_WEIGHT = 4;

  // UTILITY FUNCTIONS
  
  // workaround for 404 error in images
  function getThumbnailUrl(participant) {
    if (participant.getThumbnailUrl() == "https://wave.google.com/static/images/unknown.gif") {
      return "https://wave.google.com/wave/static/images/unknown.gif"
    } 
    return participant.getThumbnailUrl();
  }
  
  //In case I change my mind about "game_id" somewhere down the line
  function getGameId(state) {
    return state.get("game_id");
  }
 
  function isVisible(stickId) {
    var state = wave.getState();
    var gameId = getGameId(state);
    
    //check if the stick exists and is not picked
    return state.get('stick_' + stickId + '_' + gameId) && 
      !state.get("picked_" + stickId + "_" + gameId); 
  }
  
  function isPicked(stickId) {
    var state = wave.getState();
    var gameId = getGameId(state);
    
    //check only if picked
    return !state.get("picked_" + stickId + "_" + gameId); 
  }
  
  function isLegalMove(clickedId) {
    var state = wave.getState();
    var gameId = getGameId(state);
    
    var pos = state.get(clickedId + "_" + gameId).split('|');
    var class = pos[0];
    var x = parseInt(pos[1]);
    var y = parseInt(pos[2]);
    var x2 = x + (pos[0] == "horizontal" ? 200 : 5);
    var y2 = y + (pos[0] == "horizontal" ? 5: 200);
    
    var start = parseInt(clickedId.split("_")[1]);
    
    for (var stickId = start + 1; stickId < NUMBER_OF_STICKS; ++stickId) {
      
      if (isVisible(stickId)) {
    
        pos = state.get('stick_' + stickId  + "_" + gameId).split('|');
        
        var targetX = parseInt(pos[1]);
        var targetY = parseInt(pos[2]);
        var targetX2 = targetX + (pos[0] == "horizontal" ? 200 : 5);
        var targetY2 = targetY + (pos[0] == "horizontal" ? 5: 200);
  
        // funky formula for checking overlapping rectangles
        if (x < targetX2 && x2 > targetX && y < targetY2 && y2 > targetY) {
          return false;
        }
      }
    }
    
    return true;
  }
  
  // Remove robots. Derived from connect-four gadget
  function getPlayers() {
    var players = new Array();
    var participants = wave.getParticipants();
    for (idx in participants) {
      var pid = participants[idx].getId();
      if(/@.*appspot\.com$/.exec(pid) != null ? true : false)
          continue;
      if(/@a\.gwave\.com$/.exec(pid) != null ? true : false)
          continue;
      players.push(participants[idx]);    
    }
    return players;
  }
  
  function getReadableTimeDiff(last_move, start_time) {
    var seconds = Math.floor( (parseInt(last_move) - parseInt(start_time)) /1000)
    var minutes = Math.floor(seconds / 60)
    seconds = Math.floor(seconds % 60)
    
    var hours = Math.floor(minutes / 60)
    minutes = Math.floor(minutes % 60)
    
    var display = "";
    if (hours) {
      display += hours + " hour" + (hours == 1 ? "" : "s") + " "; 
    }
    if (minutes) {
      display += minutes + " minute" + (minutes == 1 ? "" : "s") + " "; 
    }
    if (seconds) {
      display += seconds + " second" + (seconds == 1 ? "" : "s"); 
    }
    return display;
  }
  
  // CALLBACKS  

  function waveParticipantUpdated() {

    //moved display to updatestate   
    gadgets.window.adjustHeight();
        
  }

  function stateUpdated() {
   
    if (isGameInitialized()) {

      initializeSticks();
      showSticks();
      updatePlayerScores();
       
      if (isGameOver()) {
        showWinners();
      } else {
        hideWinners();
      }
      gadgets.window.adjustHeight();
      
    } 
    
  }

  // STATE UPDATED FUNCTIONS
  
  function isGameInitialized() {
    return wave.getState().get("game_id");
  }  
  
  // - Display functions
  
  function initializeSticks() {
  
    //I removed this from main in order to allow future versions to dynamically
    //define the number of sticks on the board.
    
    for (var stickId = 0; stickId < NUMBER_OF_STICKS; ++stickId) {
    
      if ($("#stick_" + stickId).length > 0) {
        continue;
      }
      
      var html = '<div id="stick_' + stickId + '" >&nbsp;</div>'
      div = $(html);
       
      div.click(function () { 
        var state = wave.getState();
        var gameId = getGameId(state);
        var stickId = $(this).attr("id").split("_")[1];
        
        if (isVisible(stickId)) {
          var delta = {};
          if (isLegalMove($(this).attr("id"))) {  
            delta["picked_" + stickId + "_" + gameId] = wave.getViewer().getId();
            delta["last_move_" + gameId] = wave.getTime();
          } else {
            var mistakes = state.get("mistakes_" + wave.getViewer().getId() + "_" + gameId);
            if (!mistakes) {
              mistakes = 0;
            }
            delta["mistakes_" + wave.getViewer().getId() + "_" + gameId] = parseInt(mistakes) + 1;
          }
          state.submitDelta(delta);
        }
      });
      
      $("#innercontainer").append(div);
    }
  }
  
  function showSticks() {
    var state = wave.getState();
    var gameId = getGameId(state);
    
    for (var stickId = 0; stickId < NUMBER_OF_STICKS; ++stickId) {

      if (isVisible(stickId)) {

        var pos = state.get('stick_' + stickId + '_' + gameId).split('|');
        var class = pos[0];
        var x = parseInt(pos[1]);
        var y = parseInt(pos[2]);
        var color = pos[3];
        
        if (class == "horizontal") {
          if ($('#stick_' + stickId).hasClass("vertical")) {
            $('#stick_' + stickId).removeClass("vertical"); 
          }
          $('#stick_' + stickId).addClass("horizontal");
        } else {
          if ($('#stick_' + stickId).hasClass("horizontal")) {
            $('#stick_' + stickId).removeClass("horizontal"); 
          }        
          $('#stick_' + stickId).addClass("vertical");
        }
        
        $('#stick_' + stickId).css({left: x + 'px', top: y + 'px', position: 'absolute', display: 'table', background: color});
      } else {
        $('#stick_' + stickId).css({display: 'none'});
      }
    }
  } 
  
  function updatePlayerScores() {
    
    var scores = calculateScoreMap();
    
    var state = wave.getState();
    var gameId = getGameId(state);
    var players = getPlayers(); 
    
    for (idx in players) {
      var player = players[idx];
      var playId = player.getId();
      
      var penalty = (state.get("mistakes_" + playId + "_" + gameId) ?  
        parseInt(state.get("mistakes_" + playId + "_" + gameId)) : 0);
      var score = scores[playId] ? scores[playId] : 0;
      var displayScore = score - (penalty * PENALTY_WEIGHT);
      
      
      if (penalty == 0 && score == 0) {
        
        if (document.getElementById(playId) != null) {
          $(document.getElementById(playId)).remove();
        }
         
        continue;
      }
      
      
      if (document.getElementById(playId)) {
      
        $(document.getElementById("score_" + playId)).text(pointsDisplay(displayScore));
        $(document.getElementById("mistakes_" + playId)).text(penaltyDisplay(penalty));
        
      } else {
      
        var playerDiv = $("<div class='player' id='" + player.getId() + "'></div>");
        
        var str = "<img height='30px' width='30px' src='" + getThumbnailUrl(player) + "'>";            
        if (playId == wave.getViewer().getId())
          str += "me";
        else
          str += player.getDisplayName();
        str += ": <span id='score_" + playId +"'>" + pointsDisplay(displayScore) + "</span> " +
          "(<span id='mistakes_" + playId +"'>" + penaltyDisplay(penalty) + "</span>)";
        
        playerDiv.html(str);
        
        $("#participants").append(playerDiv);
        
      } 
      
        
    }
    
  }
  
  function pointsDisplay(displayScore) {
    return "" + displayScore + (displayScore == 1 ? " point" : " points");
  }
  
  function penaltyDisplay(penalty) {
    return "" + penalty + (penalty == 1 ? " mistake" : " mistakes");
  }
    
  function calculateScoreMap() {
    var state = wave.getState();
    var gameId = getGameId(state);
    var scores = {}; 
    
    for (var stickId = 0; stickId < NUMBER_OF_STICKS; ++stickId) {
      pickerId = state.get("picked_" + stickId + "_" + gameId);
      if (pickerId) {
        if (scores[pickerId]) {
          scores[pickerId] += 1; 
        } else {
          scores[pickerId] = 1;
        }
      }
    }
    return scores;
  }
  
  function isGameOver() {
    for (var stickId = 0; stickId < NUMBER_OF_STICKS; ++stickId) {
      if (isPicked(stickId)) {
        return false;
      }
    }
    return true;
  }   
    
  function showWinners() {
    $('#winners').css({display: 'block'});
    
    var state = wave.getState();
    var gameId = getGameId(state);

    var players = getPlayers();
    var max = null;
    var maxId = new Array();
    
    var scores = calculateScoreMap();
    
    //determine winners
    for (idx in players) {
      var player = players[idx];
      var playId = player.getId();
     
      var penalty = (state.get("mistakes_" + playId + "_" + gameId) ?  
        parseInt(state.get("mistakes_" + playId + "_" + gameId)) : 0);
      var score = (scores[playId] ? scores[playId] : 0) - (penalty * PENALTY_WEIGHT);
      
      if (max == null) {
        max = score;
        maxId.push(playId);
      } else {
        if (score > max) {
          maxId = new Array();
          maxId.push(playId);
          max = score;
        } else if (score == max) {
          maxId.push(playId);
        }
      } 
      
    }
    
    $('#winners').html(buildWinnerText(max, maxId));
    
  }  
  
  function buildWinnerText(max, maxId) {
  
    var state = wave.getState();
    var gameId = getGameId(state);
    
    var winners = "";
    if (maxId.length == 1) {
      winners += "<img height='30px' width='30px' src='" + getThumbnailUrl(wave.getParticipantById(maxId[0])) + "'>"; 
      if (maxId[0] == wave.getViewer().getId()) {
        winners += "You won with ";
      } else {           
        winners += wave.getParticipantById(maxId[0]).getDisplayName() + " won with ";
      }
      winners += max + " point" + (max == 1 ? "" : "s");
    } else {
      winners = "It's a tie between ";
      for (var j = 0; j < maxId.length; j++) {
        if (j > 0) {
          winners += " and ";
        }
        winners += "<img height='30px' width='30px' src='" + getThumbnailUrl(wave.getParticipantById(maxId[j])) + "'>"; 
        if (maxId[j] == wave.getViewer().getId()) {
          winners += "you";
        } else {           
          winners += wave.getParticipantById(maxId[j]).getDisplayName();
        }
      }
      
    }
    winners += "! (" + getReadableTimeDiff(state.get("last_move_" + gameId), state.get("time_" + gameId)) + ")";
    return winners;
  }
  
  function hideWinners() {
    $('#winners').css({display: 'none'});
  }  

  // NEW GAME
  
  function newGame() {
    var delta = {};
    
    clearState(delta);
    initializeGame(delta);
    
    return false;
  }

  // - Initializing functions  
  function clearState(delta) {
    var state = wave.getState();
    for (idx in state.getKeys()) {
      delta[state.get(state.getKeys()[idx])] = null;      
    }
  }
  
  function initializeGame(delta){
  
    if (delta == null) {
      delta = {};
    }
    
    //prevent unsynched moves from previous games from affecting new games
    var gameId = Math.floor(100000 * Math.random()).toString();
    delta["game_id"] = gameId;
    
    randomizeSticks(delta, gameId);
    setGameStartTime(delta, gameId);

    wave.getState().submitDelta(delta);
    
  }
  
  function randomizeSticks(delta, gameId) {
    
    for (var stickId = 0; stickId < NUMBER_OF_STICKS; ++stickId) {
    
      var class = (Math.random() < 0.57) ? "horizontal" : "vertical";
      var x = Math.round( (class == "horizontal" ? 200 : 395) * Math.random()); 
      var y = Math.round( (class == "horizontal" ? 295 : 100) * Math.random());
      var color = "rgb(" + getRandom256Color() + ", " + getRandom256Color() + ", " + 
        getRandom256Color() + ")";

      delta['stick_' + stickId + '_' + gameId] = class + '|' + x + '|' + y + '|' + color;
      
    }
  }
    
  function getRandom256Color() {  
    // the "50 +" is there to make the color lighter
    return (50 + Math.floor(206 * Math.random()));
  }
    
  function setGameStartTime(delta, gameId) {
    delta["time" + '_' + gameId] = wave.getTime();
  }
  
  
  // MAIN
 
  function main() {
    if (wave && wave.isInWaveContainer()) {
      wave.setStateCallback(stateUpdated);
      wave.setParticipantCallback(waveParticipantUpdated);

      $('#dialog').dialog({
        autoOpen: false
      });
      $('#dialog_link').click(function(){
        $('#dialog').dialog('open');
        $('#help_link').blur();
        return false;
      });
      $('#reset_link').click(newGame);
      $('#help_penalty').text(PENALTY_WEIGHT);

    }
  };
  
  gadgets.util.registerOnLoadHandler(main);
  </script>

  <style>
  .horizontal {
    border-left: 1px solid #AFAFAF;
    border-top: 1px solid #AFAFAF;
    border-right: 1px solid #101010;
    border-bottom: 1px solid #101010;
    height:5px;
    width:200px;
    display: table;
    cursor: pointer;
    position: absolute;
    font-size:1px;
  }

  .vertical {
    border-left: 1px solid #AFAFAF;
    border-top: 1px solid #AFAFAF;
    border-right: 1px solid #101010;
    border-bottom: 1px solid #101010;
    height:200px;
    width:5px;
    display: table;
    cursor: pointer;
    position: absolute;
    font-size:1px;
  }
  
  body {
    font-family : Trebuchet MS, Helvetica, sans-serif;
    font-size: 12px;
  }
  
  #title {
    font-size: 24px;
  }
  
  #container {
    border: 1px solid rgb(221, 221, 221); 
    padding: 3px;
  }
  
  #innercontainer {
    width:400px; 
    height:300px; 
    border: 0px; 
    position: relative; 
    margin: 5px; 
    border: 1px solid rgb(221, 221, 221);
  }
  
  .player {
    padding:1px;
    margin:1px;
  }
  
  a {
    padding: 0.4em 1em 0.4em 1em;
    position: relative;
    text-decoration: none;
  }
  
  #help_link {
    padding: 0; 
    text-decoration: underline; 
    border: none;
  }
  
  </style>
  
  <div id="container">
    <span id="title">Pick-up Sticks</span> 
    <p>
      <a href="#" id="dialog_link" class="ui-state-default ui-corner-all">Show Rules</a> 
      <a href="#" id="reset_link" class="ui-state-default ui-corner-all">New Game</a> 
    </p>
    <div id="innercontainer">&nbsp;</div>
    <div id="participants"></div>
    <div id="winners" style="display:none"></div>
    <div id="dialog" title="Rules of the Game">
      <p>This is a Google Wave implementation of the game 
        <a id="help_link" href="http://en.wikipedia.org/wiki/Pick-up_sticks">Pick-up sticks</a>.</p>
      <p>Players can remove sticks by clicking on them. Each removed stick is 
        worth one point.</p>
      <p>Players can only remove sticks at the top of the stack. Trying to 
        remove a stick with sticks on top of it will result in a 
        <span id="help_penalty">10</span> point deduction to the player's 
        score.</p>
      <p>The player with the most points wins the game.</p>   
    </div>
  </div>
]]></Content>
</Module>

