<commit_before>(function(){
	'use strict';

	var fb      = new Firebase('https://tictactoemax.firebaseio.com/'),
        counter = 0;

        $('#newGame').on('click', newGame
        )

        function newGame () {
        	$('.move').remove();

        }

	fb.child('games/test').once('value',function (snap) {
		var data = snap.val();

	    $('.panel').on('click', function (event) {

		   	var $panel  = $(this);

	        if ($panel.attr("data-move")) {
		      alert("already played")
		    }
		    else if (counter % 2 === 0) {
		      $(this).attr('data-move', 'X');
			  $(this).append('
X
');
		      changeBoardState($panel, data);
		      counter++;
	        }
		    else if (counter % 2 === 1) {
	          $(this).attr('data-move', 'O');
			  $(this).append('
O
');
		      changeBoardState($panel, data);
	   	      counter++;
		    }

		    var piece = 'X';

		    if (piece === 'X') {
              piece === 'O'
		    } else {
              piece === 'X'
		    }

		   	if ($('#0').attr('data-move') === piece && $('#1').attr('data-move') === piece && $('#2').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#3').attr('data-move') === piece && $('#4').attr('data-move') === piece && $('#5').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#6').attr('data-move') === piece && $('#7').attr('data-move') === piece && $('#8').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#0').attr('data-move') === piece && $('#3').attr('data-move') === piece && $('#6').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#1').attr('data-move') === piece && $('#4').attr('data-move') === piece && $('#7').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#2').attr('data-move') === piece && $('#5').attr('data-move') === piece && $('#8').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#0').attr('data-move') === piece && $('#4').attr('data-move') === piece && $('#8').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#2').attr('data-move') === piece && $('#4').attr('data-move') === piece && $('#6').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	}
        });

	}); //end fb.child

  fb.child('games/test').once('value',function (snap) {
    var data = snap.val();

    loadBoardState(data)
  });

  $('#newGame').on('click', function () {
        var fbTestGame    = fb.child('games/test/boardState')
        var newBoardState = {
                            position0: 'undefined',
                            position1: 'undefined',
                            position2: 'undefined',
                            position3: 'undefined',
                            position4: 'undefined',
                            position5: 'undefined',
                            position6: 'undefined',
                            position7: 'undefined',
                            position8: 'undefined'
                            };

        fbTestGame.set(newBoardState);
      });

	function changeBoardState(panel, data) {
      var fbTestGame     = fb.child('games/test/boardState'),
	        $boardPosition = panel.attr('id'),
          $boardMove     = panel.attr('data-move'),
          position       = 'position' + $boardPosition,
	        boardPosition  = {};

          boardPosition[position] = $boardMove;

	      fbTestGame.update(boardPosition);
	} //end changeBoardState

  function loadBoardState(data) {
    var boardState = data.boardState;
    console.log(boardState);

    _.forEach(boardState, function (position, key) {
      var panelId = key[8];

      if (position !== 'undefined') {
        $('p#' + panelId).append('
' + position + '
');
      }

    });
  }

}()); // end IIFE<commit_msg>two player functionality<commit_after>/* globals $:false */

(function(){
	'use strict';

	var fb      = new Firebase('https://tictactoemax.firebaseio.com/'),
      $moves;

  $('#newGame').on('click', function newGame () {
    	$('.move').remove();
    });

	    $('.panel').on('click', function (event, data) {
		   	var $panel  = $(this);

        $moves = $('.move').length || 0;

	        if ($panel.attr("data-move")) {
		      alert("already played")
		    }
		    else if ($moves % 2 === 0) {
		      $(this).attr('data-move', 'X');
			  $(this).append('
X
');
		      changeBoardState($panel, data);

	        }
		    else if ($moves % 2 === 1) {
	          $(this).attr('data-move', 'O');
			  $(this).append('
O
');
		      changeBoardState($panel, data);

		    }

        $moves = $('.move').length;

		    var piece = 'X';

		    if (piece === 'X') {
              piece === 'O'
		    } else {
              piece === 'X'
		    }

		   	if ($('#0').attr('data-move') === piece && $('#1').attr('data-move') === piece && $('#2').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#3').attr('data-move') === piece && $('#4').attr('data-move') === piece && $('#5').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#6').attr('data-move') === piece && $('#7').attr('data-move') === piece && $('#8').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#0').attr('data-move') === piece && $('#3').attr('data-move') === piece && $('#6').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#1').attr('data-move') === piece && $('#4').attr('data-move') === piece && $('#7').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#2').attr('data-move') === piece && $('#5').attr('data-move') === piece && $('#8').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#0').attr('data-move') === piece && $('#4').attr('data-move') === piece && $('#8').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	} else if ($('#2').attr('data-move') === piece && $('#4').attr('data-move') === piece && $('#6').attr('data-move') === piece) {
		   		alert(piece + ' Wins!');
		   		$('.panel').off('click');
		   	}
});

  fb.child('games/test').once('value',function (snap) {
    var data = snap.val();

    loadBoardState(data);
  });

  $('#newGame').on('click', function () {
        var fbTestGame    = fb.child('games/test/boardState')
        var newBoardState = {
                            position0: 'undefined',
                            position1: 'undefined',
                            position2: 'undefined',
                            position3: 'undefined',
                            position4: 'undefined',
                            position5: 'undefined',
                            position6: 'undefined',
                            position7: 'undefined',
                            position8: 'undefined'
                            };

        fbTestGame.set(newBoardState);
      });

	function changeBoardState(panel, data) {
      var fbTestGame     = fb.child('games/test/boardState'),
	        $boardPosition = panel.attr('id'),
          $boardMove     = panel.attr('data-move'),
          position       = 'position' + $boardPosition,
	        boardPosition  = {};

          boardPosition[position] = $boardMove;

	      fbTestGame.update(boardPosition);
	} //end changeBoardState

  function loadBoardState(data) {
    var boardState = data.boardState;
    console.log(boardState);

    _.forEach(boardState, function (position, key) {
      var panelId = key[8];

      if (position !== 'undefined') {
        $('p#' + panelId).append('
' + position + '
');
      }

    });
  }

}()); // end IIFE<|endoftext|>


<commit_before>chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {

	if (request.action == 'isDisabled') {
		if(!document.getElementById('chrome-envmarker')) {
			sendResponse({isDisabled: 'notSet'});
		} else {
			if(document.getElementById('chrome-envmarker')) {
				sendResponse({isDisabled: document.getElementById('chrome-envmarker').style.display == 'none'});
			} else {
				sendResponse({isDisabled: 'notSet'});
			}
		}
	} else if(request.action == 'toggle') {
		var marker = document.getElementById('chrome-envmarker');
		if(marker.style.display == 'none') {
			marker.style.display = 'block';
		} else {
			marker.style.display = 'none';
		}
	}
});

var regexList = [];
var strictList = [];
var lastUpdate = new Date().getTime();

// verify settings and add label if necessary
function _addEnvironmentLabel() {
	chrome.storage.sync.get({current_state: {
		env_settings: [{name: 'EXAMPLE', address: 'geovanneborges.com.br', color: '0000ff'},]
	}}, function(data) {
		var tablink = window.location.href;
		if (!data.current_state.env_settings.lastUpdate || data.current_state.lastUpdate > lastUpdate) {
			_updateMatchers(data.current_state.env_settings);
		}

		for(var i = 0; i<strictList.length; i++) {
			if(tablink.indexOf(strictList[i].address) > -1) {
				_addMarket(strictList[i]);
				return;
			}
		}
		var hostName = window.location.host;
		for(var i = 0; i<regexList.length; i++) {
			if(regexList[i].regex.test(hostName)) {
				_addMarket(regexList[i]);
				return;
			}
		}
	});
}

function _addMarket(item) {
	var envmarker = document.getElementById('chrome-envmarker');
	if(envmarker && envmarker.length != 0) {
		envmarker.parentNode.removeChild(envmarker);
	}
	wrapperDiv = document.createElement('div');
	wrapperDiv.id = 'chrome-envmarker';
	wrapperDiv.setAttribute('style','text-shadow: -1px -1px 0 #555, 1px -1px 0 #555, -1px 1px 0 #555, 1px 1px 0 #555; position: fixed; right: -127px; top: 43px; background-color: #'+item.color.replace('#','')+'; opacity: 0.9; z-index: 2000; height: 55px; width: 396px; line-height: 55px; transform: rotate(44deg); box-shadow: 7px 0px 9px #000; text-align: center; font-size: 37px; color: #fff;');
	wrapperDiv.innerText = item.name;
	// try not duplicating elements
	document.body.appendChild(wrapperDiv);
}

function _updateMatchers(env_settings) {
	env_settings.forEach(function (item) {
		if (!!item && !!item.address && item.address.startsWith('regex:')) {
			item.regex = new RegExp(item.address.substring('regex:'.length, item.address.length));
			regexList.push(item);
		} else {
			strictList.push(item);
		}
	});
	lastUpdate = new Date().getTime();
}

_addEnvironmentLabel();

// detect dinamic URL update
window.onhashchange = function() {
	_addEnvironmentLabel();
}
<commit_msg>Incorrect updating<commit_after>chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
	blat.get();
	if (request.action == 'isDisabled') {
		if(!document.getElementById('chrome-envmarker')) {
			sendResponse({isDisabled: 'notSet'});
		} else {
			if(document.getElementById('chrome-envmarker')) {
				sendResponse({isDisabled: document.getElementById('chrome-envmarker').style.display == 'none'});
			} else {
				sendResponse({isDisabled: 'notSet'});
			}
		}
	} else if(request.action == 'toggle') {
		var marker = document.getElementById('chrome-envmarker');
		if(marker.style.display == 'none') {
			marker.style.display = 'block';
		} else {
			marker.style.display = 'none';
		}
	}
});

var PLUGIN_STATE = {};
PLUGIN_STATE.regexList = [];
PLUGIN_STATE.strictList = [];
PLUGIN_STATE.lastUpdate = null;

// verify settings and add label if necessary
function _addEnvironmentLabel() {
	chrome.storage.sync.get({current_state: {
		last_update: new Date().getTime(),
		env_settings: [{name: 'EXAMPLE', address: 'geovanneborges.com.br', color: '0000ff'}]
	}}, function(data) {
		var tablink = window.location.href;
		if (!PLUGIN_STATE.lastUpdate || data.current_state.last_update > PLUGIN_STATE.lastUpdate) {
			_updateMatchers(data.current_state.env_settings, data.current_state.last_update);
		}

		for(var i = 0; i<PLUGIN_STATE.strictList.length; i++) {
			if(tablink.indexOf(PLUGIN_STATE.strictList[i].address) > -1) {
				_addMarket(PLUGIN_STATE.strictList[i]);
				return;
			}
		}
		var hostName = window.location.host;
		for(var i = 0; i<PLUGIN_STATE.regexList.length; i++) {
			if(PLUGIN_STATE.regexList[i].regex.test(hostName)) {
				_addMarket(PLUGIN_STATE.regexList[i]);
				return;
			}
		}
	});
}

function _addMarket(item) {
	var envmarker = document.getElementById('chrome-envmarker');
	if(envmarker && envmarker.length != 0) {
		envmarker.parentNode.removeChild(envmarker);
	}
	wrapperDiv = document.createElement('div');
	wrapperDiv.id = 'chrome-envmarker';
	wrapperDiv.setAttribute('style','text-shadow: -1px -1px 0 #555, 1px -1px 0 #555, -1px 1px 0 #555, 1px 1px 0 #555; position: fixed; right: -127px; top: 43px; background-color: #'+item.color.replace('#','')+'; opacity: 0.9; z-index: 2000; height: 55px; width: 396px; line-height: 55px; transform: rotate(44deg); box-shadow: 7px 0px 9px #000; text-align: center; font-size: 37px; color: #fff;');
	wrapperDiv.innerText = item.name;
	// try not duplicating elements
	document.body.appendChild(wrapperDiv);
}

function _updateMatchers(env_settings, last_update) {
	PLUGIN_STATE.regexList = [];
	PLUGIN_STATE.strictList = [];
	env_settings.forEach(function (item) {
		if (!!item && !!item.address && item.address.startsWith('regex:')) {
			item.regex = new RegExp(item.address.substring('regex:'.length, item.address.length));
			PLUGIN_STATE.regexList.push(item);
		} else {
			PLUGIN_STATE.strictList.push(item);
		}
	});
	PLUGIN_STATE.lastUpdate = last_update;
}

_addEnvironmentLabel();

// detect dinamic URL update
window.onhashchange = function() {
	_addEnvironmentLabel();
}
<|endoftext|>


<commit_before>(function($, storage){
  'use strict';

  // Panel model
  var panel = panel || {};

  // Select menu
  var $select = $('[data-options]')[0];

  // Current theme
  panel.currentTheme = '';

  // Default theme
  panel.defaultTheme = 'Solarized Dark';

  // Available themes
  panel.themes = [
    {
      'name': '3024',
      'colors': ['#F7F7F7', '#F0F0F0', '#807d7c', '#4a4543', '#a16a94', '#ed0c8c', '#01a252', '#db2d20', '#cdab53']
    },
    {
      'name': 'Bongzilla',
      'colors': ['#1F1F1F', '#262626', '#AEAEAE', '#F8F8F8', '#8DA6CE', '#FF593E', '#FF6400', '#7F90AA', '#78CE91', '#FFCC66']
    },
    {
      'name': 'Clouds',
      'colors': []
    },
    {
      'name': 'Coda',
      'colors': []
    },
    {
      'name': 'CSSedit',
      'colors': []
    },
    {
      'name': 'Monokai',
      'colors': []
    },
    {
      'name': 'Nodejs',
      'colors': []
    },
    {
      'name': 'Solarized Dark',
      'colors': []
    },
    {
      'name': 'Solarized Light',
      'colors': []
    }
  ];

  // Build select menus like ngOptions
  function _buildSelectMenu(menu, object){
    var options, array;

    // Get the data attribute value
    options = menu.dataset.options;

    // Clean string and create array
    options = options.replace(/in\s/g, '').split(' ');

    // Assign array from object by property name
    // using the value from the last item in options
    array = object[options[options.length - 1]];

    for (var j = 0; j < array.length; j++){

      var option = document.createElement('option');

      // Assign option value & text from array
      option.value = array[j].name.replace(/\s+/g, '-').toLowerCase();
      option.text  = array[j].name;

      // Select currentTheme option
      if (object.currentTheme === array[j].name){
        option.selected = 'selected';
      }

      menu.add(option, null);
    }

    return menu;
  }

  // Set & save theme based on select menu change event
  function setTheme(event, obj){
    function save(theme){
      storage.set({ 'devtools-theme': theme.value },
      function(){ panel.currentTheme = theme.text; });
    }
    if (event && event.type === 'change'){
      var el     = event.target || event.srcElement;
      var option = el.options[el.selectedIndex];
      save(option);
    } else if (event === null && obj){
      save(obj);
    }
  }

  // Object observer for current theme
  function observer(changes){
    changes.forEach(function(change){
      if (change.name === 'currentTheme') {
        $('#currentTheme')[0].innerHTML = change.object.currentTheme;
      }
    });
  }

  // Get theme from chrome sync
  function getTheme(array, string){
    if (!array || !string){
      setTheme(null, {
        value: panel.defaultTheme.replace(/\s+/g, '-').toLowerCase(),
        text: panel.defaultTheme
      });
      return panel.defaultTheme;
    }
    for (var i = 0; i < array.length; i++){
      if (array[i].name.replace(/\s+/g, '-').toLowerCase() === string){
        return array[i].name;
      }
    }
  }

  // Initialize theme panel
  function init(){
    // Listen for changes to the select menu
    $select.addEventListener('change', setTheme);

    // Observe changes to panel model
    Object.observe(panel, observer, ['add', 'update']);

    // Get current theme from Chrome sync
    storage.get('devtools-theme', function(object){

      panel.currentTheme = getTheme(panel.themes, object['devtools-theme']);

      // Build select menus
      _buildSelectMenu($select, panel);
    });
  }

  init();
})(
document.querySelectorAll.bind(document),
chrome.storage.sync
);
<commit_msg>Added panel functionality to persist font-sizes using chrome storage<commit_after>(function($, storage){
  'use strict';

  // Panel model
  var panel = panel || {};

  // Select menu
  var $select = $('[data-options]')[0];

  // Number input
  var $number = $('input[type=number]')[0];

  // Current theme
  panel.currentTheme = '';

  // Default theme
  panel.defaultTheme = 'Solarized Dark';

  // Default fontSize
  panel.defaultFontSize = 14;

  // Available themes
  panel.themes = [
    {
      'name': '3024',
      'colors': ['#F7F7F7', '#F0F0F0', '#807d7c', '#4a4543', '#a16a94', '#ed0c8c', '#01a252', '#db2d20', '#cdab53']
    },
    {
      'name': 'Bongzilla',
      'colors': ['#1F1F1F', '#262626', '#AEAEAE', '#F8F8F8', '#8DA6CE', '#FF593E', '#FF6400', '#7F90AA', '#78CE91', '#FFCC66']
    },
    {
      'name': 'Clouds',
      'colors': []
    },
    {
      'name': 'Coda',
      'colors': []
    },
    {
      'name': 'CSSedit',
      'colors': []
    },
    {
      'name': 'Monokai',
      'colors': []
    },
    {
      'name': 'Nodejs',
      'colors': []
    },
    {
      'name': 'Solarized Dark',
      'colors': []
    },
    {
      'name': 'Solarized Light',
      'colors': []
    }
  ];

  // Build select menus like ngOptions
  function _buildSelectMenu(menu, object){
    var options, array;

    // Get the data attribute value
    options = menu.dataset.options;

    // Clean string and create array
    options = options.replace(/in\s/g, '').split(' ');

    // Assign array from object by property name
    // using the value from the last item in options
    array = object[options[options.length - 1]];

    for (var j = 0; j < array.length; j++){

      var option = document.createElement('option');

      // Assign option value & text from array
      option.value = array[j].name.replace(/\s+/g, '-').toLowerCase();
      option.text  = array[j].name;

      // Select currentTheme option
      if (object.currentTheme === array[j].name){
        option.selected = 'selected';
      }

      menu.add(option, null);
    }

    return menu;
  }

  // Set & save theme based on select menu change event
  function setTheme(event, obj){
    function save(theme){
      storage.set({ 'devtools-theme': theme.value },
      function(){ panel.currentTheme = theme.text; });
    }
    if (event && event.type === 'change'){
      var el     = event.target || event.srcElement;
      var option = el.options[el.selectedIndex];
      save(option);
    } else if (event === null && obj){
      save(obj);
    }
  }

  // Set & save font size based on input menu change event
  function setFontSize(event, value){
    function save(fontSize){
      storage.set({ 'devtools-fontSize': fontSize },
      function(){ panel.currentFontSize = fontSize; });
    }
    if (event && event.type === 'change'){
      var el     = event.target || event.srcElement;
      save(el.value);
    } else if (event === null && value){
      save(value);
    }
  }

  // Object observer for current theme
  function observer(changes){
    changes.forEach(function(change){
      if (change.name === 'currentTheme') {
        $('#currentTheme')[0].innerHTML = change.object.currentTheme;
      } else if (change.name === 'currentFontSize') {
        $number.value = change.object.currentFontSize;
      }
    });
  }

  // Get fontSize from chrome sync
  function getFontSize(value){
    if (!value){
      setFontSize(null, panel.defaultFontSize);
      return panel.defaultFontSize;
    } else {
      return value;
    }
  }

  // Get theme from chrome sync
  function getTheme(array, string){
    if (!array || !string){
      setTheme(null, {
        value: panel.defaultTheme.replace(/\s+/g, '-').toLowerCase(),
        text: panel.defaultTheme
      });
      return panel.defaultTheme;
    }
    for (var i = 0; i < array.length; i++){
      if (array[i].name.replace(/\s+/g, '-').toLowerCase() === string){
        return array[i].name;
      }
    }
  }

  // Initialize theme panel
  function init(){
    // Listen for changes to the select menu
    $select.addEventListener('change', setTheme);

    // Listen for changes to the select menu
    $number.addEventListener('change', setFontSize);

    // Observe changes to panel model
    Object.observe(panel, observer, ['add', 'update']);

    // Get current theme from Chrome sync
    storage.get('devtools-theme', function(object){

      panel.currentTheme = getTheme(panel.themes, object['devtools-theme']);

      // Build select menus
      _buildSelectMenu($select, panel);
    });

    // Get current fontSize from Chrome sync
    storage.get('devtools-fontSize', function(object){
      panel.currentFontSize = getFontSize(object['devtools-fontSize']);
    });
  }

  init();
})(
document.querySelectorAll.bind(document),
chrome.storage.sync
);
<|endoftext|>


<commit_before>(function () {
'use strict';

window.Rally = function() {
    this.init();

    this.ui = new RallyUI(this);
};

Rally.prototype = {
    instructions: [ ],
    instruction_map: new Map(),
    instruction_id_map: new Map(),
    last_instr: null,

    init: function() {
        var db = this.db = new Dexie('MyDatabase');

        // Define a schema
        db.version(1).stores({
            instructions: [ 'id++', '&instr', 'raw_mlg', 'cas', 'delay', 'mlg', 'time', 'notes'].join()
        });

        // Open the database
        db.open()
            .catch(function(error){ alert('Uh oh : ' + error); });

        this.last_instr = 0;
        this.calculate();

        this.cachedClockAdj = this.clockAdj();
    },

    cachedClockAdj: 0,

    now: function() {
        return Date.now() + this.cachedClockAdj;
    },

    calculate: function() {
        var rally = this;
        var prev = null;
        this.instruction_map.clear();
        this.instruction_id_map.clear();
        return this.db.instructions.toArray(function (rows) {
            rally.instructions = rows.sort(function (a,b) {
                return a.instr - b.instr;
            }).map(function (row) {
                var instruction = new RallyInstruction(row);
                instruction.calculate(rally, prev);
                rally.instruction_map.set(parseFloat(instruction.instr), instruction);
                rally.instruction_id_map.set(instruction.id, instruction);
                rally.last_instr = instruction.instr.value;
                instruction.prev = prev;
                if (prev) {
                    prev.next = instruction;
                }
                prev = instruction;
                return instruction;
            });
            rally.ui.renderInstructions();
        });
    },

    instruction: function (instr) {
        instr = parseFloat(instr);
        if (this.instruction_map.has(instr)) {
            return this.instruction_map.get(instr);
        } else {
            return null;
        }
    },

    addInstruction: function() {
        var row = {};

        row.instr = Number.parseFloat(arguments[0]);

        if (this.instruction_map.has(row.instr)) {
            throw new Error("Instruction numbers must be unique");
        }

        row.raw_mlg = null;
        row.cas = null;
        row.delay = null;
        row.mlg = null;
        row.time = null;

        switch (arguments.length) {
            case 6:
                row.time = Number.parseInt(arguments[5]);
                /* falls through */
            case 5:
                row.mlg = Number.parseFloat(arguments[4]);
                /* falls through */
            case 4:
                row.delay = Number.parseFloat(arguments[3]);
                /* falls through */
            case 3:
                row.cas = Number.parseInt(arguments[2]);
                /* falls through */
            case 2:
                row.raw_mlg = Number.parseFloat(arguments[1]);
                break;
        }

        var rally = this;
        return this.db.instructions.put(row).then(function () {
            return rally.calculate().then(function() {
                return row.instr;
            });
        });
    },

    addNextInstruction: function() {
        this.last_instr = Math.floor(this.last_instr + 1);
        return this.addInstruction(this.last_instr);
    },

    setValue: function (instruction, col, val) {
        var rally = this;
        var obj = {};
        if (col.name == 'instr') {
            val = rally.parseInstruction(col.format_cb(val));
        } else if (col.name == 'tod') {
            val = rally.parseTime(val);
        } else if (col.format_cb) {
            val = col.format_cb(val);
        }
        obj[col.name] = val;

        this.db.instructions.update(instruction.id, obj).then(function () { rally.calculate(); }).catch(function (err) {
            console.log(instr);
            console.log(obj);
        });
    },

    deleteInstruction: function(instruction) {
        var rally = this;
        if (instruction && this.last_instr == instruction.instr.value) {
            if (instruction.prev) {
                this.last_instr = instruction.prev.instr.value;
            } else {
                this.last_instr = 0;
            }
        }
        rally.db.instructions.where('id')
            .equals(instruction.id)
            .delete()
            .then(function () {
                rally.calculate();
            });
    },

    odomFactor: function(val) {
        if (arguments.length > 0) {
            this.setConfig('odom_factor', val);
        }
        val = this.getConfig('odom_factor');
        if (val === null) {
            val = 1;
        }
        return Number.parseFloat(val);
    },

    casFactor: function(val) {
        if (arguments.length > 0) {
            this.setConfig('cas_factor', val);
        }
        val = this.getConfig('cas_factor');
        if (val === null) {
            val = 1;
        }
        return Number.parseFloat(val);
    },

    rallySpeed: function(val) {
        if (arguments.length > 0) {
            this.setConfig('rally_speed', val);
        }
        val = this.getConfig('rally_speed');
        if (val === null) {
            val = 1;
        }
        return Number.parseInt(val);
    },

    clockAdj: function(val) {
        if (arguments.length > 0) {
            this.setConfig('clock_adj', val);
            this.cachedClockAdj = this.clockAdj();
        }
        val = this.getConfig('clock_adj');
        if (val === null) {
            val = 0;
        }
        return Number.parseInt(val);
    },

    timeSeconds: function(val) {
        if (arguments.length > 0) {
            this.setConfig('time_seconds', Boolean(val));
        }
        val = this.getConfig('time_seconds');
        if (val === null) {
            val = true;
        } else {
            val = (val.toLowerCase() == "true");
        }
        return Boolean(val);
    },

    adjustMilleage: function(val) {
        return Number.parseFloat(val) * this.odomFactor();
    },

    adjustCAS: function(val) {
        return Number.parseFloat(val) * this.casFactor();
    },

    getConfig: function(name) {
        return window.localStorage.getItem(name);
    },

    setConfig: function(name, value) {
        return window.localStorage.setItem(name, value);
    },

    reset: function() {
        this.db.delete();
        this.init();
    },

    parseInstruction: function(v) {
        if (v === null || isNaN(v)) {
            throw new Error("Invalid format for instruction");
        }
        if (this.instruction_map.has(v)) {
            throw new Error("Instruction numbers must be unique");
        }
        return v;
    },

    parseTime: function(v) {
        var result = null;
        if (v !== null) {
            if (!v.match(/^\s*[0-9:.]+(\s*[pamPAM]+)?\s*$/)) {
                throw new Error("Invalid time format");
            }
            if (this.timeSeconds()) {
                result = moment(v, ["h-m-s", "h-m", "H-m A"]);
            } else {
                if (typeof v == 'string' && v.includes('.')) {
                    var m = null;
                    m = v.match(/^\s*(([0-9]+):)?([0-9]+)\.([0-9]+)(\s*[pamPAM]+)?\s*$/);
                    if (m) {
                        result = moment({h: m[2], m: m[3]});
                        result.add(Number.parseFloat('0.'+m[4]) * 60, 'seconds');
                    } else {
                        throw new Error("Invalid time format");
                    }
                } else {
                    result = moment(v, ["h-m-s", "h-m", "H-m A"]);
                }
            }
            if (result.isValid()) {
                result = result.valueOf();
            } else {
                result = null;
            }
        }
        return result;
    },

};

window.RallyInstruction = function(row) {
    this.id = row.id;
    this.columns = [];
    this.columns_calc_order = [];

    var instruction = this;
    this.column_defs_display_order.forEach(function (def) {
        var val = null;
        if (row.hasOwnProperty(def.name)) {
            val = row[def.name];
        }
        var col = new RallyInstructionColumn(val, def);
        instruction.columns.push(col);
        Object.defineProperty(instruction, col.name, {
            get: function() {
                return col;
            },
        });
    });
    this.column_defs_calc_order.forEach(function (name) {
        instruction.columns_calc_order.push(instruction[name]);
    });
};

RallyInstruction.prototype = {
    column_defs_display_order: [],
    column_defs_by_name: {},
    column_defs_calc_order: [],

    parseFixedFloat: function(f) {
        return function(v) {
            var result = Number.parseFloat(v);
            if (isNaN(result)) {
                result = null;
            } else {
                result = result.toFixed(f);
            }
            return result;
        };
    },

    parseFloat: function(v) {
        var result = Number.parseFloat(v);
        if (isNaN(result)) {
            result = null;
        }
        return result;
    },

    parseInt: function(v) {
        var result = Number.parseInt(v);
        if (isNaN(result)) {
            result = null;
        }
        return result;
    },

    calculate: function(rally, prev) {
        var cur = this;
        this.columns_calc_order.forEach(function (c) {
            c.calc(rally, prev, cur);
        });
    },
};

window.RallyInstructionColumn = function(value, props) {
    this.name = props.name;
    this.label = props.label;
    this.default_value = props.default_value;
    this.format_cb = props.format_cb;
    this.calc_if_prev_cb = props.calc_if_prev_cb;
    this.calc_always_cb = props.calc_always_cb;
    this.read_only = (props.read_only ? true : false);

    this.calculated_value = value;
    this.stored_value = value;

    Object.defineProperty(this, 'value', {
        get: function() {
            return (this.stored_value !== null ? this.stored_value : this.calculated_value);
        },
    });
};

RallyInstructionColumn.prototype = {
    calc: function(rally, prev, cur) {
        var val = null;
        if (this.isSet()) {
            val = this.value;
        } else if (this.calc_always_cb) {
            val = this.calc_always_cb.apply(this, arguments);
        } else if (prev && this.calc_if_prev_cb) {
            val = this.calc_if_prev_cb.apply(this, arguments);
        } else {
            val = this.default_value;
        }
        this.calculated_value = val;
    },

    isSet: function() {
        return this.stored_value !== null;
    },

    toString: function() {
        var value = this.value;
        if (this.format_cb) {
            value = this.format_cb(value);
        }
        return value;
    },
};

RallyInstruction.prototype.column_defs_display_order = [
    {
        name: 'instr',
        label: 'NRI',
        description: 'Number Route Instruction',
        format_cb: RallyInstruction.prototype.parseFixedFloat(1),
    }, {
        name: 'raw_mlg',
        label: 'Raw Mlg',
        description: 'Unmodified overall milleage from rally instructions',
        default_value: 0,
        format_cb: RallyInstruction.prototype.parseFloat,
        calc_if_prev_cb: function(rally, prev, cur) {
            if (cur.mlg.isSet() && !cur.raw_mlg.isSet()) {
                return prev.raw_mlg.value + (cur.mlg.value - prev.mlg.value);
            } else {
                return 0;
            }
        },
    }, {
        name: 'raw_d_mlg',
        label: 'Raw ΔMlg',
        description: 'Unmodified delta milleage between NRIs',
        read_only: true,
        default_value: 0,
        format_cb: RallyInstruction.prototype.parseFloat,
        calc_if_prev_cb: function(rally, prev, cur) {
            var result = cur.raw_mlg.value - prev.raw_mlg.value;
            return (result < 0 ? 0 : result);
        },
    }, {
        name: 'mlg',
        label: 'Mlg',
        description: 'Adjusted overall milleage: Defaults to calculated, can be overriden as needed',
        default_value: 0,
        format_cb: RallyInstruction.prototype.parseFloat,
        calc_if_prev_cb: function(rally, prev, cur) {
            if (cur.raw_mlg.value === 0) {
                return 0;
            } else {
                return prev.mlg.value + cur.d_mlg.value;
            }
        },
    }, {
        name: 'd_mlg',
        label: 'ΔMlg',
        description: 'Delta milleage between NRIs',
        read_only: true,
        default_value: 0,
        format_cb: RallyInstruction.prototype.parseFloat,
        calc_always_cb: function(rally, prev, cur) {
            return rally.adjustMilleage(cur.raw_d_mlg.value);
        },
    }, {
        name: 'cas',
        label: 'CAS',
        description: 'Change Average Speed',
        default_value: 0,
        format_cb: RallyInstruction.prototype.parseInt,
        calc_if_prev_cb: function(rally, prev, cur) {
            return prev.cas.value;
        },
    }, {
        name: 'delay',
        label: 'Delay',
        description: 'An additional delay at instruciton',
        default_value: 0,
        format_cb: RallyInstruction.prototype.parseFloat,
    }, {
        name: 'tod',
        label: 'TOD',
        description: 'Time Of Day: Calculated from rally-speed and milleage, can be overriden as needed',
        format_cb: RallyInstruction.prototype.parseInt,
        calc_if_prev_cb: function(rally, prev, cur) {
            var value = prev.tod.value;
            value += (cur.d_time.value + prev.err_time.value) * 1000;
            return value;
        },
    }, {
        name: 'time',
        label: 'Time',
        description: 'Stop-watch time in seconds since start of rally',
        default_value: 0,
        read_only: true,
        format_cb: RallyInstruction.prototype.parseInt,
        calc_if_prev_cb: function(rally, prev, cur) {
            return prev.time.value + cur.d_time.value;
        },
    }, {
        name: 'd_time',
        label: 'ΔTime',
        description: 'Delta time between NRIs',
        default_value: 0,
        read_only: true,
        format_cb: RallyInstruction.prototype.parseFloat,
        calc_if_prev_cb: function(rally, prev, cur) {
            var value = cur.raw_d_mlg.value * 3600;
            value /= prev.cas.value;
            value += cur.delay;
            return value;
        },
    }, {
        name: 'err_time',
        label: 'Err Time',
        description: 'Error time at this NRI',
        default_value: 0,
        read_only: true,
        format_cb: RallyInstruction.prototype.parseFloat,
        calc_if_prev_cb: function(rally, prev, cur) {
            var value = 0;
            if (cur.tod.isSet()) {
                value = prev.tod.value - cur.tod.value;
                value /= 1000;
                value += cur.d_time.value + prev.err_time.value;
            }
            return value;
        },
    }, {
        name: 'notes',
        label: 'Notes',
    }
];

RallyInstruction.prototype.column_defs_display_order.forEach(function (col) {
    RallyInstruction.prototype.column_defs_by_name[col.name] = col;
});

RallyInstruction.prototype.column_defs_calc_order = [
    'instr',
    'raw_d_mlg',
    'd_mlg',
    'mlg',
    'cas',
    'd_time',
    'time',
    'tod',
    'raw_mlg',
    'err_time',
];

}());<commit_msg>Rally: now(): ClockAdj is in seconds, Date.now() is milliseconds<commit_after>(function () {
'use strict';

window.Rally = function() {
    this.init();

    this.ui = new RallyUI(this);
};

Rally.prototype = {
    instructions: [ ],
    instruction_map: new Map(),
    instruction_id_map: new Map(),
    last_instr: null,

    init: function() {
        var db = this.db = new Dexie('MyDatabase');

        // Define a schema
        db.version(1).stores({
            instructions: [ 'id++', '&instr', 'raw_mlg', 'cas', 'delay', 'mlg', 'time', 'notes'].join()
        });

        // Open the database
        db.open()
            .catch(function(error){ alert('Uh oh : ' + error); });

        this.last_instr = 0;
        this.calculate();

        this.cachedClockAdj = this.clockAdj();
    },

    cachedClockAdj: 0,

    now: function() {
        return Date.now() + this.cachedClockAdj * 1000.0;
    },

    calculate: function() {
        var rally = this;
        var prev = null;
        this.instruction_map.clear();
        this.instruction_id_map.clear();
        return this.db.instructions.toArray(function (rows) {
            rally.instructions = rows.sort(function (a,b) {
                return a.instr - b.instr;
            }).map(function (row) {
                var instruction = new RallyInstruction(row);
                instruction.calculate(rally, prev);
                rally.instruction_map.set(parseFloat(instruction.instr), instruction);
                rally.instruction_id_map.set(instruction.id, instruction);
                rally.last_instr = instruction.instr.value;
                instruction.prev = prev;
                if (prev) {
                    prev.next = instruction;
                }
                prev = instruction;
                return instruction;
            });
            rally.ui.renderInstructions();
        });
    },

    instruction: function (instr) {
        instr = parseFloat(instr);
        if (this.instruction_map.has(instr)) {
            return this.instruction_map.get(instr);
        } else {
            return null;
        }
    },

    addInstruction: function() {
        var row = {};

        row.instr = Number.parseFloat(arguments[0]);

        if (this.instruction_map.has(row.instr)) {
            throw new Error("Instruction numbers must be unique");
        }

        row.raw_mlg = null;
        row.cas = null;
        row.delay = null;
        row.mlg = null;
        row.time = null;

        switch (arguments.length) {
            case 6:
                row.time = Number.parseInt(arguments[5]);
                /* falls through */
            case 5:
                row.mlg = Number.parseFloat(arguments[4]);
                /* falls through */
            case 4:
                row.delay = Number.parseFloat(arguments[3]);
                /* falls through */
            case 3:
                row.cas = Number.parseInt(arguments[2]);
                /* falls through */
            case 2:
                row.raw_mlg = Number.parseFloat(arguments[1]);
                break;
        }

        var rally = this;
        return this.db.instructions.put(row).then(function () {
            return rally.calculate().then(function() {
                return row.instr;
            });
        });
    },

    addNextInstruction: function() {
        this.last_instr = Math.floor(this.last_instr + 1);
        return this.addInstruction(this.last_instr);
    },

    setValue: function (instruction, col, val) {
        var rally = this;
        var obj = {};
        if (col.name == 'instr') {
            val = rally.parseInstruction(col.format_cb(val));
        } else if (col.name == 'tod') {
            val = rally.parseTime(val);
        } else if (col.format_cb) {
            val = col.format_cb(val);
        }
        obj[col.name] = val;

        this.db.instructions.update(instruction.id, obj).then(function () { rally.calculate(); }).catch(function (err) {
            console.log(instr);
            console.log(obj);
        });
    },

    deleteInstruction: function(instruction) {
        var rally = this;
        if (instruction && this.last_instr == instruction.instr.value) {
            if (instruction.prev) {
                this.last_instr = instruction.prev.instr.value;
            } else {
                this.last_instr = 0;
            }
        }
        rally.db.instructions.where('id')
            .equals(instruction.id)
            .delete()
            .then(function () {
                rally.calculate();
            });
    },

    odomFactor: function(val) {
        if (arguments.length > 0) {
            this.setConfig('odom_factor', val);
        }
        val = this.getConfig('odom_factor');
        if (val === null) {
            val = 1;
        }
        return Number.parseFloat(val);
    },

    casFactor: function(val) {
        if (arguments.length > 0) {
            this.setConfig('cas_factor', val);
        }
        val = this.getConfig('cas_factor');
        if (val === null) {
            val = 1;
        }
        return Number.parseFloat(val);
    },

    rallySpeed: function(val) {
        if (arguments.length > 0) {
            this.setConfig('rally_speed', val);
        }
        val = this.getConfig('rally_speed');
        if (val === null) {
            val = 1;
        }
        return Number.parseInt(val);
    },

    clockAdj: function(val) {
        if (arguments.length > 0) {
            this.setConfig('clock_adj', val);
            this.cachedClockAdj = this.clockAdj();
        }
        val = this.getConfig('clock_adj');
        if (val === null) {
            val = 0;
        }
        return Number.parseInt(val);
    },

    timeSeconds: function(val) {
        if (arguments.length > 0) {
            this.setConfig('time_seconds', Boolean(val));
        }
        val = this.getConfig('time_seconds');
        if (val === null) {
            val = true;
        } else {
            val = (val.toLowerCase() == "true");
        }
        return Boolean(val);
    },

    adjustMilleage: function(val) {
        return Number.parseFloat(val) * this.odomFactor();
    },

    adjustCAS: function(val) {
        return Number.parseFloat(val) * this.casFactor();
    },

    getConfig: function(name) {
        return window.localStorage.getItem(name);
    },

    setConfig: function(name, value) {
        return window.localStorage.setItem(name, value);
    },

    reset: function() {
        this.db.delete();
        this.init();
    },

    parseInstruction: function(v) {
        if (v === null || isNaN(v)) {
            throw new Error("Invalid format for instruction");
        }
        if (this.instruction_map.has(v)) {
            throw new Error("Instruction numbers must be unique");
        }
        return v;
    },

    parseTime: function(v) {
        var result = null;
        if (v !== null) {
            if (!v.match(/^\s*[0-9:.]+(\s*[pamPAM]+)?\s*$/)) {
                throw new Error("Invalid time format");
            }
            if (this.timeSeconds()) {
                result = moment(v, ["h-m-s", "h-m", "H-m A"]);
            } else {
                if (typeof v == 'string' && v.includes('.')) {
                    var m = null;
                    m = v.match(/^\s*(([0-9]+):)?([0-9]+)\.([0-9]+)(\s*[pamPAM]+)?\s*$/);
                    if (m) {
                        result = moment({h: m[2], m: m[3]});
                        result.add(Number.parseFloat('0.'+m[4]) * 60, 'seconds');
                    } else {
                        throw new Error("Invalid time format");
                    }
                } else {
                    result = moment(v, ["h-m-s", "h-m", "H-m A"]);
                }
            }
            if (result.isValid()) {
                result = result.valueOf();
            } else {
                result = null;
            }
        }
        return result;
    },

};

window.RallyInstruction = function(row) {
    this.id = row.id;
    this.columns = [];
    this.columns_calc_order = [];

    var instruction = this;
    this.column_defs_display_order.forEach(function (def) {
        var val = null;
        if (row.hasOwnProperty(def.name)) {
            val = row[def.name];
        }
        var col = new RallyInstructionColumn(val, def);
        instruction.columns.push(col);
        Object.defineProperty(instruction, col.name, {
            get: function() {
                return col;
            },
        });
    });
    this.column_defs_calc_order.forEach(function (name) {
        instruction.columns_calc_order.push(instruction[name]);
    });
};

RallyInstruction.prototype = {
    column_defs_display_order: [],
    column_defs_by_name: {},
    column_defs_calc_order: [],

    parseFixedFloat: function(f) {
        return function(v) {
            var result = Number.parseFloat(v);
            if (isNaN(result)) {
                result = null;
            } else {
                result = result.toFixed(f);
            }
            return result;
        };
    },

    parseFloat: function(v) {
        var result = Number.parseFloat(v);
        if (isNaN(result)) {
            result = null;
        }
        return result;
    },

    parseInt: function(v) {
        var result = Number.parseInt(v);
        if (isNaN(result)) {
            result = null;
        }
        return result;
    },

    calculate: function(rally, prev) {
        var cur = this;
        this.columns_calc_order.forEach(function (c) {
            c.calc(rally, prev, cur);
        });
    },
};

window.RallyInstructionColumn = function(value, props) {
    this.name = props.name;
    this.label = props.label;
    this.default_value = props.default_value;
    this.format_cb = props.format_cb;
    this.calc_if_prev_cb = props.calc_if_prev_cb;
    this.calc_always_cb = props.calc_always_cb;
    this.read_only = (props.read_only ? true : false);

    this.calculated_value = value;
    this.stored_value = value;

    Object.defineProperty(this, 'value', {
        get: function() {
            return (this.stored_value !== null ? this.stored_value : this.calculated_value);
        },
    });
};

RallyInstructionColumn.prototype = {
    calc: function(rally, prev, cur) {
        var val = null;
        if (this.isSet()) {
            val = this.value;
        } else if (this.calc_always_cb) {
            val = this.calc_always_cb.apply(this, arguments);
        } else if (prev && this.calc_if_prev_cb) {
            val = this.calc_if_prev_cb.apply(this, arguments);
        } else {
            val = this.default_value;
        }
        this.calculated_value = val;
    },

    isSet: function() {
        return this.stored_value !== null;
    },

    toString: function() {
        var value = this.value;
        if (this.format_cb) {
            value = this.format_cb(value);
        }
        return value;
    },
};

RallyInstruction.prototype.column_defs_display_order = [
    {
        name: 'instr',
        label: 'NRI',
        description: 'Number Route Instruction',
        format_cb: RallyInstruction.prototype.parseFixedFloat(1),
    }, {
        name: 'raw_mlg',
        label: 'Raw Mlg',
        description: 'Unmodified overall milleage from rally instructions',
        default_value: 0,
        format_cb: RallyInstruction.prototype.parseFloat,
        calc_if_prev_cb: function(rally, prev, cur) {
            if (cur.mlg.isSet() && !cur.raw_mlg.isSet()) {
                return prev.raw_mlg.value + (cur.mlg.value - prev.mlg.value);
            } else {
                return 0;
            }
        },
    }, {
        name: 'raw_d_mlg',
        label: 'Raw ΔMlg',
        description: 'Unmodified delta milleage between NRIs',
        read_only: true,
        default_value: 0,
        format_cb: RallyInstruction.prototype.parseFloat,
        calc_if_prev_cb: function(rally, prev, cur) {
            var result = cur.raw_mlg.value - prev.raw_mlg.value;
            return (result < 0 ? 0 : result);
        },
    }, {
        name: 'mlg',
        label: 'Mlg',
        description: 'Adjusted overall milleage: Defaults to calculated, can be overriden as needed',
        default_value: 0,
        format_cb: RallyInstruction.prototype.parseFloat,
        calc_if_prev_cb: function(rally, prev, cur) {
            if (cur.raw_mlg.value === 0) {
                return 0;
            } else {
                return prev.mlg.value + cur.d_mlg.value;
            }
        },
    }, {
        name: 'd_mlg',
        label: 'ΔMlg',
        description: 'Delta milleage between NRIs',
        read_only: true,
        default_value: 0,
        format_cb: RallyInstruction.prototype.parseFloat,
        calc_always_cb: function(rally, prev, cur) {
            return rally.adjustMilleage(cur.raw_d_mlg.value);
        },
    }, {
        name: 'cas',
        label: 'CAS',
        description: 'Change Average Speed',
        default_value: 0,
        format_cb: RallyInstruction.prototype.parseInt,
        calc_if_prev_cb: function(rally, prev, cur) {
            return prev.cas.value;
        },
    }, {
        name: 'delay',
        label: 'Delay',
        description: 'An additional delay at instruciton',
        default_value: 0,
        format_cb: RallyInstruction.prototype.parseFloat,
    }, {
        name: 'tod',
        label: 'TOD',
        description: 'Time Of Day: Calculated from rally-speed and milleage, can be overriden as needed',
        format_cb: RallyInstruction.prototype.parseInt,
        calc_if_prev_cb: function(rally, prev, cur) {
            var value = prev.tod.value;
            value += (cur.d_time.value + prev.err_time.value) * 1000;
            return value;
        },
    }, {
        name: 'time',
        label: 'Time',
        description: 'Stop-watch time in seconds since start of rally',
        default_value: 0,
        read_only: true,
        format_cb: RallyInstruction.prototype.parseInt,
        calc_if_prev_cb: function(rally, prev, cur) {
            return prev.time.value + cur.d_time.value;
        },
    }, {
        name: 'd_time',
        label: 'ΔTime',
        description: 'Delta time between NRIs',
        default_value: 0,
        read_only: true,
        format_cb: RallyInstruction.prototype.parseFloat,
        calc_if_prev_cb: function(rally, prev, cur) {
            var value = cur.raw_d_mlg.value * 3600;
            value /= prev.cas.value;
            value += cur.delay;
            return value;
        },
    }, {
        name: 'err_time',
        label: 'Err Time',
        description: 'Error time at this NRI',
        default_value: 0,
        read_only: true,
        format_cb: RallyInstruction.prototype.parseFloat,
        calc_if_prev_cb: function(rally, prev, cur) {
            var value = 0;
            if (cur.tod.isSet()) {
                value = prev.tod.value - cur.tod.value;
                value /= 1000;
                value += cur.d_time.value + prev.err_time.value;
            }
            return value;
        },
    }, {
        name: 'notes',
        label: 'Notes',
    }
];

RallyInstruction.prototype.column_defs_display_order.forEach(function (col) {
    RallyInstruction.prototype.column_defs_by_name[col.name] = col;
});

RallyInstruction.prototype.column_defs_calc_order = [
    'instr',
    'raw_d_mlg',
    'd_mlg',
    'mlg',
    'cas',
    'd_time',
    'time',
    'tod',
    'raw_mlg',
    'err_time',
];

}());<|endoftext|>


<commit_before> var Future = Npm.require('fibers/future');

// Server startup
Meteor.startup(function(){
});

Router.map(function () {
  this.route('emailParse', {
    where: 'server',
    path: '/api/email/parse',

    action: function () {
      // Mail is coming from mandrill inbound POST
      try{
            var mail = {};
            if(this.request.body.mandrill_events){
                mail = JSON.parse(this.request.body.mandrill_events)[0].msg;
            }
            else{
                mail = this.request.body;
            }
            Feeds.update(mail.email.toString().split('@')[0], { $addToSet: {
              comments: {
                owner: null,
                author: mail.from_name +' - '+ mail.from_email,
                content: mail.text,
                postedAt: new Date()
              }
            }});
            this.response.writeHead(200, {'Content-Type': 'text/html'});
            this.response.end('OK');
      }
      catch(err){
          console.log('error:', err, 'request.body', this.request.body);
          this.response.writeHead(500, {'Content-Type': 'text/html'});
          this.response.end('Internal server error');
      }
    }
  });
  this.route('avatar', {
    where: 'server',
    path: '/api/avatar/:id',

    action: function () {
      // Mail is coming from mandrill inbound POST
      try{
          var res = this.response;
            var fut = new Future();
            var user = Meteor.users.findOne({_id:this.params.id});
            if(!user){
                res.writeHead(404);
                res.end();
                fut.return();
            }
            else{
                res.writeHead(301, {'Location': user.profile.profilePic});
                res.end();
                res.on('end', function () {
                    fut.return();
                });
            }

            return fut.wait();
      }
      catch(err){
          console.log('error:', err, 'request.body', this.request.body);
          this.response.writeHead(500, {'Content-Type': 'text/html'});
          this.response.end('Internal server error');
      }
    }
  });
});<commit_msg>avatar api might have broken something<commit_after> var Future = Npm.require('fibers/future');

// Server startup
Meteor.startup(function(){
});

Router.map(function () {
  this.route('emailParse', {
    where: 'server',
    path: '/api/email/parse',

    action: function () {
      // Mail is coming from mandrill inbound POST
      try{
            var mail = {};
            if(this.request.body.mandrill_events){
                mail = JSON.parse(this.request.body.mandrill_events)[0].msg;
            }
            else{
                mail = this.request.body;
            }
            Feeds.update(mail.email.toString().split('@')[0], { $addToSet: {
              comments: {
                owner: null,
                author: mail.from_name +' - '+ mail.from_email,
                content: mail.text,
                postedAt: new Date()
              }
            }});
            this.response.writeHead(200, {'Content-Type': 'text/html'});
            this.response.end('OK');
      }
      catch(err){
          console.log('error:', err, 'request.body', this.request.body);
          this.response.writeHead(500, {'Content-Type': 'text/html'});
          this.response.end('Internal server error');
      }
    }
  });
  this.route('avatar', {
    where: 'server',
    path: '/api/avatar/:id',

    action: function () {
      // Mail is coming from mandrill inbound POST
      try{
          var res = this.response;
            var fut = new Future();
            var user = Meteor.users.findOne({_id:this.params.id});
            if(!user){
                res.writeHead(404);
                res.end();
            }
            else{
                res.writeHead(301, {'Location': user.profile.profilePic});
                res.end();
            }
            res.on('end', function () {
                fut.return();
            });
            return fut.wait();
      }
      catch(err){
          console.log('error:', err, 'request.body', this.request.body);
          this.response.writeHead(500, {'Content-Type': 'text/html'});
          this.response.end('Internal server error');
      }
    }
  });
});<|endoftext|>


<commit_before>/* global jQuery */
import Ember from 'ember';
import config from 'travis/config/environment';
let defaultOptions;

jQuery.support.cors = true;

defaultOptions = {
  accepts: {
    json: 'application/json; version=2'
  }
};

const { service } = Ember.inject;

export default Ember.Service.extend({
  auth: service(),
  features: service(),

  get(url, callback, errorCallback) {
    return this.ajax(url, 'get', {
      success: callback,
      error: errorCallback
    });
  },

  post(url, data, callback) {
    return this.ajax(url, 'post', {
      data,
      success: callback
    });
  },

  postV3(url, data, callback) {
    return this.ajax(url, 'post', {
      data: data,
      success: callback,
      headers: {
        'Travis-API-Version': '3'
      }
    });
  },

  patch(url, data, callback) {
    return this.ajax(url, 'patch', {
      data,
      success: callback
    });
  },

  needsAuth() {
    return true;
  },

  ajax(url, method, options) {
    let accepts, data, delimeter, endpoint, error, key, name, params,
      promise, ref, ref1, ref2, reject, resolve, success, token, value, xhr;
    method = (method || 'GET').toUpperCase();
    endpoint = config.apiEndpoint || '';
    options = options || {};
    token = Ember.get(this, 'auth').token();
    if (token && (this.needsAuth(method, url) || options.forceAuth)) {
      options.headers = options.headers || {};
      if (!options.headers['Authorization']) {
        options.headers['Authorization'] = `token ${token}`;
      }
    }
    options.url = url = `${endpoint}${url}`;
    options.type = method;
    options.dataType = options.dataType || 'json';
    options.context = this;
    if (options.data && method !== 'GET') {
      options.data = JSON.stringify(options.data);
    }
    if (method !== 'GET' && method !== 'HEAD') {
      options.contentType = options.contentType || 'application/json; charset=utf-8';
    }
    success = options.success || ((() => {}));
    options.success = function (data, status, xhr) {
      return success.call(this, data, status, xhr);
    };
    error = options.error || (() => {});
    options.error = (data, status, xhr) => {
      if (Ember.get(this, 'features').get('debugLogging')) {
        //eslint-disable-next-line
        console.log(`[ERROR] API responded with an error (${status}): ${JSON.stringify(data)}`);
      }
      return error.call(this, data, status, xhr);
    };

    options = Ember.$.extend(options, defaultOptions);

    if (options.data && (method === 'GET' || method === 'HEAD')) {
      params = jQuery.param(options.data);
      delimeter = url.indexOf('?') === -1 ? '?' : '&';
      url = url + delimeter + params;
    }
    xhr = new XMLHttpRequest();
    xhr.open(method, url);
    if (options.accepts && (((ref = options.headers) != null ? ref.accept : void 0) == null)) {
      accepts = [];
      ref1 = options.accepts;
      for (key in ref1) {
        value = ref1[key];
        accepts.pushObject(value);
      }
      xhr.setRequestHeader('Accept', accepts.join(', '));
    }
    if (options.headers) {
      ref2 = options.headers;
      for (name in ref2) {
        value = ref2[name];
        xhr.setRequestHeader(name, value);
      }
    }
    if (options.contentType) {
      xhr.setRequestHeader('Content-Type', options.contentType);
    }
    resolve = null;
    reject = null;
    promise = new Ember.RSVP.Promise((_resolve, _reject) => {
      resolve = _resolve;
      return reject = _reject;
    });
    xhr.onreadystatechange = () => {
      let contentType, data;
      if (xhr.readyState === 4) {
        contentType = xhr.getResponseHeader('Content-Type');
        data = (function () {
          if (contentType && contentType.match(/application\/json/)) {
            try {
              return jQuery.parseJSON(xhr.responseText);
            } catch (error1) {
              if (Ember.get(this, 'features').get('debugLogging')) {
                // eslint-disable-next-line
                console.log('error while parsing a response', method, options.url, xhr.responseText);
              }
            }
          } else {
            return xhr.responseText;
          }
        })();
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve(data);
          return options.success.call(options.context, data, xhr.status, xhr);
        } else {
          reject(xhr);
          return options.error.call(options.context, data, xhr.status, xhr);
        }
      }
    };
    data = options.data;
    let contentType = options.contentType;
    let isJSON = Ember.isNone(contentType) || contentType.match(/application\/json/);
    if (typeof options.data === 'object' && isJSON) {
      data = JSON.stringify(data);
    }
    if (data) {
      xhr.send(data);
    } else {
      xhr.send();
    }
    return promise;
  }
});
<commit_msg>Fix closure scope that should avoid raising exception (#930)<commit_after>/* global jQuery */
import Ember from 'ember';
import config from 'travis/config/environment';
let defaultOptions;

jQuery.support.cors = true;

defaultOptions = {
  accepts: {
    json: 'application/json; version=2'
  }
};

const { service } = Ember.inject;

export default Ember.Service.extend({
  auth: service(),
  features: service(),

  get(url, callback, errorCallback) {
    return this.ajax(url, 'get', {
      success: callback,
      error: errorCallback
    });
  },

  post(url, data, callback) {
    return this.ajax(url, 'post', {
      data,
      success: callback
    });
  },

  postV3(url, data, callback) {
    return this.ajax(url, 'post', {
      data: data,
      success: callback,
      headers: {
        'Travis-API-Version': '3'
      }
    });
  },

  patch(url, data, callback) {
    return this.ajax(url, 'patch', {
      data,
      success: callback
    });
  },

  needsAuth() {
    return true;
  },

  ajax(url, method, options) {
    let accepts, data, delimeter, endpoint, error, key, name, params,
      promise, ref, ref1, ref2, reject, resolve, success, token, value, xhr;
    method = (method || 'GET').toUpperCase();
    endpoint = config.apiEndpoint || '';
    options = options || {};
    token = Ember.get(this, 'auth').token();
    if (token && (this.needsAuth(method, url) || options.forceAuth)) {
      options.headers = options.headers || {};
      if (!options.headers['Authorization']) {
        options.headers['Authorization'] = `token ${token}`;
      }
    }
    options.url = url = `${endpoint}${url}`;
    options.type = method;
    options.dataType = options.dataType || 'json';
    options.context = this;
    if (options.data && method !== 'GET') {
      options.data = JSON.stringify(options.data);
    }
    if (method !== 'GET' && method !== 'HEAD') {
      options.contentType = options.contentType || 'application/json; charset=utf-8';
    }
    success = options.success || ((() => {}));
    options.success = function (data, status, xhr) {
      return success.call(this, data, status, xhr);
    };
    error = options.error || (() => {});
    options.error = (data, status, xhr) => {
      if (Ember.get(this, 'features').get('debugLogging')) {
        //eslint-disable-next-line
        console.log(`[ERROR] API responded with an error (${status}): ${JSON.stringify(data)}`);
      }
      return error.call(this, data, status, xhr);
    };

    options = Ember.$.extend(options, defaultOptions);

    if (options.data && (method === 'GET' || method === 'HEAD')) {
      params = jQuery.param(options.data);
      delimeter = url.indexOf('?') === -1 ? '?' : '&';
      url = url + delimeter + params;
    }
    xhr = new XMLHttpRequest();
    xhr.open(method, url);
    if (options.accepts && (((ref = options.headers) != null ? ref.accept : void 0) == null)) {
      accepts = [];
      ref1 = options.accepts;
      for (key in ref1) {
        value = ref1[key];
        accepts.pushObject(value);
      }
      xhr.setRequestHeader('Accept', accepts.join(', '));
    }
    if (options.headers) {
      ref2 = options.headers;
      for (name in ref2) {
        value = ref2[name];
        xhr.setRequestHeader(name, value);
      }
    }
    if (options.contentType) {
      xhr.setRequestHeader('Content-Type', options.contentType);
    }
    resolve = null;
    reject = null;
    promise = new Ember.RSVP.Promise((_resolve, _reject) => {
      resolve = _resolve;
      return reject = _reject;
    });
    xhr.onreadystatechange = () => {
      let contentType, data;
      if (xhr.readyState === 4) {
        contentType = xhr.getResponseHeader('Content-Type');
        data = (() => {
          if (contentType && contentType.match(/application\/json/)) {
            try {
              return jQuery.parseJSON(xhr.responseText);
            } catch (error1) {
              if (Ember.get(this, 'features').get('debugLogging')) {
                // eslint-disable-next-line
                console.log('error while parsing a response', method, options.url, xhr.responseText);
              }
            }
          } else {
            return xhr.responseText;
          }
        })();
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve(data);
          return options.success.call(options.context, data, xhr.status, xhr);
        } else {
          reject(xhr);
          return options.error.call(options.context, data, xhr.status, xhr);
        }
      }
    };
    data = options.data;
    let contentType = options.contentType;
    let isJSON = Ember.isNone(contentType) || contentType.match(/application\/json/);
    if (typeof options.data === 'object' && isJSON) {
      data = JSON.stringify(data);
    }
    if (data) {
      xhr.send(data);
    } else {
      xhr.send();
    }
    return promise;
  }
});
<|endoftext|>


<commit_before>import { Create, ForgotPassword, Login, ResetPassword } from './views/auth-views';
import React, { Component } from 'react';
import AuthedRoute from './AuthedRoute';
import Dashboard from './views/Dashboard';
import Header from './components/Header';
import Player from './components/Player.js';
import PodcastsView from './views/PodcastsView.js';
import RSSFeedsView from './views/RSSFeedsView.js';
import { Router, Switch, Route } from 'react-router-dom';
import UnauthedRoute from './UnauthedRoute';
import config from './config';
import { createHashHistory, createBrowserHistory } from 'history';
import fetch from './util/fetch';
import AdminView from './views/AdminView';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import NotFound from './views/404View';

var userAgent = navigator.userAgent.toLowerCase();
let isElectron = userAgent.indexOf(' electron/') > -1;

let history;
if (isElectron) {
	history = createHashHistory();
} else {
	history = createBrowserHistory();
}

class AppRouter extends Component {
	componentDidMount() {
		if (localStorage['authedUser']) {
			fetch('GET', `/users/${localStorage['authedUser']}`)
				.then(res => {
					this.props.dispatch({
						type: 'UPDATE_USER',
						user: res.data,
					});
				})
				.catch(err => {
					if (err.response.status === 401 || err.response.status === 404) {
						localStorage.clear();
						window.location = '/';
					}
				});
		}
	}
	render() {
		return (
			<Router history={history}>

					<AuthedRoute component={Header} redirect={false} showLoader={false} />
					<Switch>
						<AuthedRoute component={Dashboard} exact path="/" />
						<AuthedRoute component={PodcastsView} exact path="/podcasts" />
						<AuthedRoute
							component={PodcastsView}
							path="/podcasts/:podcastID"
						/>
						<AuthedRoute component={RSSFeedsView} exact path="/rss" />
						<AuthedRoute component={RSSFeedsView} path="/rss/:rssFeedID" />
						<AuthedRoute component={AdminView} path="/admin" />
						<UnauthedRoute component={Login} exact path="/login" />
						<UnauthedRoute component={Create} exact path="/create-account" />
						<UnauthedRoute
							component={ForgotPassword}
							exact
							path="/forgot-password"
						/>
						<UnauthedRoute
							component={ResetPassword}
							exact
							path="/reset-password"
						/>
						<Route component={NotFound} />
					</Switch>
					<AuthedRoute component={Player} redirect={false} showLoader={false} />

			</Router>
		);
	}
}

AppRouter.propTypes = {
	dispatch: PropTypes.func.isRequired,
};

export default connect()(AppRouter);
<commit_msg>Removed config import since we're not using it in the Router file.<commit_after>import { Create, ForgotPassword, Login, ResetPassword } from './views/auth-views';
import React, { Component } from 'react';
import AuthedRoute from './AuthedRoute';
import Dashboard from './views/Dashboard';
import Header from './components/Header';
import Player from './components/Player.js';
import PodcastsView from './views/PodcastsView.js';
import RSSFeedsView from './views/RSSFeedsView.js';
import { Router, Switch, Route } from 'react-router-dom';
import UnauthedRoute from './UnauthedRoute';
import { createHashHistory, createBrowserHistory } from 'history';
import fetch from './util/fetch';
import AdminView from './views/AdminView';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import NotFound from './views/404View';

var userAgent = navigator.userAgent.toLowerCase();
let isElectron = userAgent.indexOf(' electron/') > -1;

let history;
if (isElectron) {
	history = createHashHistory();
} else {
	history = createBrowserHistory();
}

class AppRouter extends Component {
	componentDidMount() {
		if (localStorage['authedUser']) {
			fetch('GET', `/users/${localStorage['authedUser']}`)
				.then(res => {
					this.props.dispatch({
						type: 'UPDATE_USER',
						user: res.data,
					});
				})
				.catch(err => {
					if (err.response.status === 401 || err.response.status === 404) {
						localStorage.clear();
						window.location = '/';
					}
				});
		}
	}
	render() {
		return (
			<Router history={history}>

					<AuthedRoute component={Header} redirect={false} showLoader={false} />
					<Switch>
						<AuthedRoute component={Dashboard} exact path="/" />
						<AuthedRoute component={PodcastsView} exact path="/podcasts" />
						<AuthedRoute
							component={PodcastsView}
							path="/podcasts/:podcastID"
						/>
						<AuthedRoute component={RSSFeedsView} exact path="/rss" />
						<AuthedRoute component={RSSFeedsView} path="/rss/:rssFeedID" />
						<AuthedRoute component={AdminView} path="/admin" />
						<UnauthedRoute component={Login} exact path="/login" />
						<UnauthedRoute component={Create} exact path="/create-account" />
						<UnauthedRoute
							component={ForgotPassword}
							exact
							path="/forgot-password"
						/>
						<UnauthedRoute
							component={ResetPassword}
							exact
							path="/reset-password"
						/>
						<Route component={NotFound} />
					</Switch>
					<AuthedRoute component={Player} redirect={false} showLoader={false} />

			</Router>
		);
	}
}

AppRouter.propTypes = {
	dispatch: PropTypes.func.isRequired,
};

export default connect()(AppRouter);
<|endoftext|>


<commit_before>define(['app'], function (app) {
	'use strict';

	app.controller('TemplateController', ['$scope', function ($scope) {

    	$scope.controller = "TemplateController";

    	_loadCss('/app/libs/font-awesome/css/font-awesome.css');
        _loadCss('//fast.fonts.net/cssapi/ab363dc0-d9f9-4148-a52d-4dca15df47ba.css');

	}]);

	function _loadCss(url) {
	    var link = document.createElement("link");
	    link.type = "text/css";
	    link.rel = "stylesheet";
	    link.href = url;
	    document.getElementsByTagName("head")[0].appendChild(link);
	}
});<commit_msg>Temporarily disable fonts.net<commit_after>define(['app'], function (app) {
	'use strict';

	app.controller('TemplateController', ['$scope', function ($scope) {

    	$scope.controller = "TemplateController";

    	_loadCss('/app/libs/font-awesome/css/font-awesome.css');
        //_loadCss('//fast.fonts.net/cssapi/ab363dc0-d9f9-4148-a52d-4dca15df47ba.css');

	}]);

	function _loadCss(url) {
	    var link = document.createElement("link");
	    link.type = "text/css";
	    link.rel = "stylesheet";
	    link.href = url;
	    document.getElementsByTagName("head")[0].appendChild(link);
	}
});<|endoftext|>


<commit_before>const Bot = require('../Bot');

class SmartBot extends Bot {

  constructor(logger, name, game) {
    super(logger, name, game);

    this._cards = {};
    this._gameStatus = null;
    this._handCardKnown = null;
  }

  static getType() {
    return 'smart';
  }

  ['roundStart']() {
    this._cards = {};
    this._gameStatus = null;
  }

  ['gameStatus'](event) {
    this._gameStatus = event;

    this._gameStatus.players.forEach((player) => {
      this._addPlayerCardBase(player.name);

      this._cards[player.name].tableCards = player.tableCards.slice(0);
    });
  }

  ['cardReveal'](event) {
    this._addPlayerCardBase(event.player);
    this._cards[event.player].handCards.push(event.card);

    this.logger.info(
      `${this.getName()} remembers that ${event.player} has ${event.card}`
    );
  }

  ['cardShow'](event) {
    if (event.player !== this.getName()) return;

    const knownCard = this._cards[this.getName()].handCards[0];
    this.logger.info(
      `${this.getName()} knows that ${event.target}`,
      `knows its hand card ${knownCard}`
    );
    this._handCardKnown = knownCard;
  }

  ['cardPlay'](event) {
    const { player, card } = event;
    this._addPlayerCardBase(player);

    const index = this._cards[player].handCards.indexOf(card);

    if (index > -1) {
      const handCards = this._cards[player].handCards;
      this._cards[player].handCards = handCards.splice(index, 1);

      this.logger.info(
        `${this.getName()} knows that ${player}`,
        `does not have ${card} anymore`
      );
    }
  }

  ['privateStatus'](event) {
    this._cards[this.getName()].handCards = event.cardsInHand.map(c => c.name);
    // Filter irrelevant input
    if (!event.isCurrentPlayer) return;

    const cardToPlay = this._getPreferredCard(event.cardsInHand);
    const cardName = cardToPlay.name;
    const cardTarget = this._getPreferredTarget(cardToPlay);
    const cardChoice = this._getPreferredChoice(cardToPlay, cardTarget);

    this.getGame().playCardAsPlayer(
      this.getName(),
      cardName,
      cardTarget,
      cardChoice
    );
  }

  _addPlayerCardBase(player) {
    if (this._cards[player]) return;

    this._cards[player] = {
      handCards: [],
      tableCards: [],
    };
  }

  _getPreferredCard(cardsInHand) {
    const canPlayCards = cardsInHand.slice(0).filter(c => c.canPlay);

    // If someone knows our card and we can play it, play it away
    if (this._handCardKnown) {
      const canPlayKnownCard = canPlayCards.find(
        c => c.name === this._handCardKnown
      );

      if (canPlayKnownCard) {
        this.logger.info(
          `${this.getName()} knows that someone else knows its hand`,
          'card and chooses to play the known card'
        );

        this._handCardKnown = null;
        return canPlayKnownCard;
      }
    }

    // Otherwise play the smalles value card
    canPlayCards.sort((c1, c2) => (
      this.CARD_VALUES[c1.name] - this.CARD_VALUES[c2.name]
    ));

    return canPlayCards[0];
  }

  _getPreferredTarget(cardToPlay) {
    let validTargets = cardToPlay.validTargetPlayers;

    if (cardToPlay.name === 'Guard') {
      const target = Object.keys(this._cards)
        .filter(playerName => validTargets.indexOf(playerName) !== -1)
        .find((playerName) => {
          const playerCards = this._cards[playerName];
          return playerCards.length > 0 && playerCards[0] !== 'Guard';
        });

      if (target) {
        this.logger.info(
          `${this.getName()} knows the hand card of ${target}`,
          `and targets ${target} with a Guard`
        );
        return target;
      }
    }

    // Prioritize other targets
    if (validTargets.length === 1) {
      validTargets = validTargets.filter(target => target !== this.getName());
    }

    if (cardToPlay.validTargetPlayers.length > 0) {
      return validTargets[
        Math.floor(Math.random() * validTargets.length)
      ];
    }

    return null;
  }

  _getPreferredChoice(cardToPlay, cardTarget) {
    if (cardTarget && cardToPlay.name !== 'Guard') return null;

    // If we know the handcard, return that (if it is not Guard)
    const targetCards = this._cards[cardTarget];
    if (
        targetCards &&
        targetCards.handCards.length > 0 &&
        targetCards.handCards[0] !== 'Guard'
      ) {
      return targetCards.handCards[0];
    }

    // Otherwise count what is left and random
    const remainingCards = this.GUARD_TARGET_CARDS.slice(0);

    let knownCards = this._gameStatus.removedCards;

    Object.values(this._cards).forEach((playerCards) => {
      knownCards = knownCards.concat(
        playerCards.handCards, playerCards.tableCards
      );
    });

    knownCards.forEach((card) => {
      const index = remainingCards.indexOf(card);
      if (index > -1) {
        remainingCards.splice(index, 1);
      }
    });

    this.logger.info(
      `${this.getName()} knows that only certains cards remain hidden`,
      remainingCards
    );

    return remainingCards[Math.floor(Math.random() * remainingCards.length)];
  }
}

module.exports = SmartBot;
<commit_msg>Typo fix<commit_after>const Bot = require('../Bot');

class SmartBot extends Bot {

  constructor(logger, name, game) {
    super(logger, name, game);

    this._cards = {};
    this._gameStatus = null;
    this._handCardKnown = null;
  }

  static getType() {
    return 'smart';
  }

  ['roundStart']() {
    this._cards = {};
    this._gameStatus = null;
  }

  ['gameStatus'](event) {
    this._gameStatus = event;

    this._gameStatus.players.forEach((player) => {
      this._addPlayerCardBase(player.name);

      this._cards[player.name].tableCards = player.tableCards.slice(0);
    });
  }

  ['cardReveal'](event) {
    this._addPlayerCardBase(event.player);
    this._cards[event.player].handCards.push(event.card);

    this.logger.info(
      `${this.getName()} remembers that ${event.player} has ${event.card}`
    );
  }

  ['cardShow'](event) {
    if (event.player !== this.getName()) return;

    const knownCard = this._cards[this.getName()].handCards[0];
    this.logger.info(
      `${this.getName()} knows that ${event.target}`,
      `knows its hand card ${knownCard}`
    );
    this._handCardKnown = knownCard;
  }

  ['cardPlay'](event) {
    const { player, card } = event;
    this._addPlayerCardBase(player);

    const index = this._cards[player].handCards.indexOf(card);

    if (index > -1) {
      const handCards = this._cards[player].handCards;
      this._cards[player].handCards = handCards.splice(index, 1);

      this.logger.info(
        `${this.getName()} knows that ${player}`,
        `does not have ${card} anymore`
      );
    }
  }

  ['privateStatus'](event) {
    this._cards[this.getName()].handCards = event.cardsInHand.map(c => c.name);
    // Filter irrelevant input
    if (!event.isCurrentPlayer) return;

    const cardToPlay = this._getPreferredCard(event.cardsInHand);
    const cardName = cardToPlay.name;
    const cardTarget = this._getPreferredTarget(cardToPlay);
    const cardChoice = this._getPreferredChoice(cardToPlay, cardTarget);

    this.getGame().playCardAsPlayer(
      this.getName(),
      cardName,
      cardTarget,
      cardChoice
    );
  }

  _addPlayerCardBase(player) {
    if (this._cards[player]) return;

    this._cards[player] = {
      handCards: [],
      tableCards: [],
    };
  }

  _getPreferredCard(cardsInHand) {
    const canPlayCards = cardsInHand.slice(0).filter(c => c.canPlay);

    // If someone knows our card and we can play it, play it away
    if (this._handCardKnown) {
      const canPlayKnownCard = canPlayCards.find(
        c => c.name === this._handCardKnown
      );

      if (canPlayKnownCard) {
        this.logger.info(
          `${this.getName()} knows that someone else knows its hand`,
          'card and chooses to play the known card'
        );

        this._handCardKnown = null;
        return canPlayKnownCard;
      }
    }

    // Otherwise play the smallest value card
    canPlayCards.sort((c1, c2) => (
      this.CARD_VALUES[c1.name] - this.CARD_VALUES[c2.name]
    ));

    return canPlayCards[0];
  }

  _getPreferredTarget(cardToPlay) {
    let validTargets = cardToPlay.validTargetPlayers;

    if (cardToPlay.name === 'Guard') {
      const target = Object.keys(this._cards)
        .filter(playerName => validTargets.indexOf(playerName) !== -1)
        .find((playerName) => {
          const playerCards = this._cards[playerName];
          return playerCards.length > 0 && playerCards[0] !== 'Guard';
        });

      if (target) {
        this.logger.info(
          `${this.getName()} knows the hand card of ${target}`,
          `and targets ${target} with a Guard`
        );
        return target;
      }
    }

    // Prioritize other targets
    if (validTargets.length === 1) {
      validTargets = validTargets.filter(target => target !== this.getName());
    }

    if (cardToPlay.validTargetPlayers.length > 0) {
      return validTargets[
        Math.floor(Math.random() * validTargets.length)
      ];
    }

    return null;
  }

  _getPreferredChoice(cardToPlay, cardTarget) {
    if (cardTarget && cardToPlay.name !== 'Guard') return null;

    // If we know the handcard, return that (if it is not Guard)
    const targetCards = this._cards[cardTarget];
    if (
        targetCards &&
        targetCards.handCards.length > 0 &&
        targetCards.handCards[0] !== 'Guard'
      ) {
      return targetCards.handCards[0];
    }

    // Otherwise count what is left and random
    const remainingCards = this.GUARD_TARGET_CARDS.slice(0);

    let knownCards = this._gameStatus.removedCards;

    Object.values(this._cards).forEach((playerCards) => {
      knownCards = knownCards.concat(
        playerCards.handCards, playerCards.tableCards
      );
    });

    knownCards.forEach((card) => {
      const index = remainingCards.indexOf(card);
      if (index > -1) {
        remainingCards.splice(index, 1);
      }
    });

    this.logger.info(
      `${this.getName()} knows that only certains cards remain hidden`,
      remainingCards
    );

    return remainingCards[Math.floor(Math.random() * remainingCards.length)];
  }
}

module.exports = SmartBot;
<|endoftext|>


<commit_before>const
    moment = require('moment'),
    waterfall = require("async/waterfall")
;

/**
 * Another option would be to get all the today offers and compare based on the user's categories
 * @param datastore
 * @param userObject
 * @returns {Promise}
 */
module.exports = function (datastore, userObject) {
    function fromDatastore (obj) {
        let newObj = obj.messageData;
        newObj.id = obj[datastore.KEY].id;
        return newObj;
    }

    return new Promise((resolve, reject) => {
        let
            today = moment().startOf('day').toDate(),
            tomorrow = moment().endOf('day').toDate(),
            posts = []
        ;

        function checkDup (postEntity) {
            let dup = false;
            for (let post of posts) {
                if (post.id === postEntity.id) dup = true;
            }
            return dup;
        }

        function createWaterfallFunction (category) {
            return function (cb) {
                console.log('querying for', category);
                let query = datastore.createQuery("Post")
                    .filter('category', category)
                    //.filter('sentDate','>=', new Date(today))
                ;
                query.run((err, entities, info) => {
                    if (err) {
                        console.error("Error transation",err);
                        // probably no results
                        cb();
                        return;
                    }

                    for (let entity of entities) {
                        console.log('entity from cat', category, entity);
                        let post = fromDatastore(entity);
                        if (entity.sentDate >= today &&
                            entity.sentDate < tomorrow &&
                            !checkDup(post))
                        {
                            posts.push(post);
                        } else {
                            console.log('dup or not in range');
                        }
                    }
                })
            }
        }

        let functions = [];
        for (let category of userObject.interest) {
            if (category !== "NaN" && !isNaN(category))
                functions.push(createWaterfallFunction(category));
        }

        waterfall(functions, (err, result) => {
            if (posts.length > 0) {
                resolve(posts);
            } else {
                if (err) {
                    console.error(err);
                } else
                    console.error("today offers query did not throwed results", userObject.interest);
                reject();
            }
        });
    });
};

/*[
    {
        title: "Carlin's gay",
        subtitle: "Next-generation virtual reality",
        item_url: "https://www.oculus.com/en-us/rift/",
        image_url: "http://messengerdemo.parseapp.com/img/rift.png",
        buttons: [{
            type: "web_url",
            url: "https://goo.gl/maps/oguRzdieeyy",
            title: "Ver ubicación"
        }, {
            type: "postback",
            title: "Compartir anuncio",
            payload: "Payload for first bubble",
        }],
    }, {
        title: "touch",
        subtitle: "Your Hands, Now in VR",
        item_url: "https://www.oculus.com/en-us/touch/",
        image_url: "http://messengerdemo.parseapp.com/img/touch.png",
        buttons: [{
            type: "web_url",
            url: "https://www.facebook.com/sharer/sharer.php?u=https%3A//goo.gl/maps/oguRzdieeyy",
            title: "Share this con FB!"
        }, {
            type: "element_share",
        }]
    }
];*/
<commit_msg>Implementing carousel of today offers<commit_after>const
    moment = require('moment'),
    waterfall = require("async/waterfall")
;

/**
 * Another option would be to get all the today offers and compare based on the user's categories
 * @param datastore
 * @param userObject
 * @returns {Promise}
 */
module.exports = function (datastore, userObject) {
    function fromDatastore (obj) {
        let newObj = obj.messageData;
        newObj.id = obj[datastore.KEY].id;
        return newObj;
    }

    return new Promise((resolve, reject) => {
        let
            today = moment().startOf('day').toDate(),
            tomorrow = moment().endOf('day').toDate(),
            posts = []
        ;

        function checkDup (postEntity) {
            let dup = false;
            for (let post of posts) {
                if (post.id === postEntity.id) dup = true;
            }
            return dup;
        }

        function createWaterfallFunction (category) {
            return function (cb) {
                console.log('querying for', category);
                let query = datastore.createQuery("Post")
                    .filter('category', category)
                    //.filter('sentDate','>=', new Date(today))
                ;
                query.run((err, entities, info) => {
                    if (err) {
                        console.error("Error transation",err);
                        // probably no results
                        cb();
                        return;
                    }

                    console.log('entity from cat', category, entities);
                    for (let entity of entities) {
                        let post = fromDatastore(entity);
                        if (entity.sentDate >= today &&
                            entity.sentDate < tomorrow &&
                            !checkDup(post))
                        {
                            posts.push(post);
                        } else {
                            console.log('dup or not in range');
                        }
                    }
                    cb();
                })
            }
        }

        let functions = [];
        for (let category of userObject.interest) {
            if (category !== "NaN" && !isNaN(category))
                functions.push(createWaterfallFunction(category));
        }

        waterfall(functions, (err, result) => {
            if (posts.length > 0) {
                resolve(posts);
            } else {
                if (err) {
                    console.error(err);
                } else
                    console.error("today offers query did not throwed results", userObject.interest);
                reject();
            }
        });
    });
};

/*[
    {
        title: "Carlin's gay",
        subtitle: "Next-generation virtual reality",
        item_url: "https://www.oculus.com/en-us/rift/",
        image_url: "http://messengerdemo.parseapp.com/img/rift.png",
        buttons: [{
            type: "web_url",
            url: "https://goo.gl/maps/oguRzdieeyy",
            title: "Ver ubicación"
        }, {
            type: "postback",
            title: "Compartir anuncio",
            payload: "Payload for first bubble",
        }],
    }, {
        title: "touch",
        subtitle: "Your Hands, Now in VR",
        item_url: "https://www.oculus.com/en-us/touch/",
        image_url: "http://messengerdemo.parseapp.com/img/touch.png",
        buttons: [{
            type: "web_url",
            url: "https://www.facebook.com/sharer/sharer.php?u=https%3A//goo.gl/maps/oguRzdieeyy",
            title: "Share this con FB!"
        }, {
            type: "element_share",
        }]
    }
];*/
<|endoftext|>