<commit_before>// ==========================================================================
// Project:   MySystem
// Copyright: ©2010 Concord Consortium
// ==========================================================================
/*globals MySystem */

// This is the function that will start your app running.  The default
// implementation will load any fixtures you have created then instantiate
// your controllers and awake the elements on your page.
//
// As you develop your application you will probably want to override this.
// See comments for some pointers on what to do next.
//

sc_require('lib/old_format_json_parser');

// MySystem.studentMode = MySystem.NOVICE_STUDENT;
MySystem.studentMode = MySystem.ADVANCED_STUDENT;

MySystem.setupStore = function (obj, ignoreUndeclaredFields) {
  // clear the ids counters on the AutoId record types
  // typically this won't matter at runtime but it will matter during testing
  delete MySystem.Node._nextIdIndex;
  delete MySystem.Link._nextIdIndex;

  if (typeof ignoreUndeclaredFields === 'undefined') ignoreUndeclaredFields = NO;

  obj.dataSource = SC.CascadeDataSource.create({
    dataSources: ['studentStateDataSource', 'fixturesDataSource'],

    studentStateDataSource: MySystem.MergedHashDataSource.create({
      ignoreUndeclaredFields: ignoreUndeclaredFields,

      handledRecordTypes: [
        MySystem.Link,
        MySystem.Node,
        MySystem.Story,
        MySystem.StorySentence,
        MySystem.RuleFeedback,
        MySystem.GraphicPreview,
        MySystem.RubricScore],

      // write the updated student-state data to the DOM whenever it changes
      // New: Write the updated student-state data to a variable whenever it changes.
      dataStoreDidUpdateDataHash: function () {
        // include a 'version' field in the saved data. The WISE4 glue code will warn users if
        // MySystem.learnerDataVersion === DEVELOPMENT_HEAD; hopefully, this will prevent students from
        // actually using a MySystem version that saves learner data with 'version' field === DEVELOPMENT_HEAD
        this.get('dataHash').version = MySystem.learnerDataVersion;
        var textRep = JSON.stringify(this.get('dataHash'), null, 2);
        SC.$('#my_system_state').text(textRep);
        MySystem.savingController.set("dataIsDirty", YES);
      }
    }),

    fixturesDataSource: SC.FixturesDataSource.create({
      // Patch. CascadeDataSource's commitRecords naively has each data source attempt to commit the same records.
      // (This results in an internal inconsistency error on record creation, because the fixturesDataSource tries to
      // create the same record that the studentStateDataSource has just created, and whose storeKey is therefore in
      // the wrong state -- READY instead of BUSY)
      commitRecords: function () { return YES; }
    })
  });

  // the store passes student state data to the student state data source
  obj.store = SC.Store.create({
    commitRecordsAutomatically: YES,

    setStudentStateDataHash: function (hash) {
      this.dataSource.studentStateDataSource.setDataHash(this, hash);
    },
    getStudentStateDataHash: function() {
      var dataHash = this.dataSource.studentStateDataSource.dataHash();
      return JSON.stringify(dataHash,null,2);
    }
  }).from(obj.dataSource);
};

MySystem.main = function main() {
  MySystem.setupStore(MySystem);

  // load the initial data from the DOM (see core.js)
  MySystem.updateFromDOM();

  MySystem.getPath('mainPage.mainPane').append();

  SC.ExceptionHandler = MySystem.ExceptionHandler;

  //Set the content property on the primary controller
  var nodes = MySystem.store.find(MySystem.Diagrammable);
  MySystem.nodesController.set('content', nodes);

  // Configured activity
  var activity = MySystem.store.find(MySystem.Activity, 'assign1');
  MySystem.activityController.set('content',activity);

  // User story
  var storyQuery = SC.Query.local(MySystem.StorySentence, { orderBy: 'order' });
  var storySentences = MySystem.store.find(storyQuery);
  MySystem.storySentenceController.set('content', storySentences);

  MySystem.canvasView = MySystem.mainPage.mainPane.get('diagramView');    // TODO rename to 'diagramView' project-wide RPK 8-11-11

  MySystem.statechart.initStatechart();
  MySystem.storyController.showInstructions();
  SC.RootResponder.CAPTURE_BACKSPACE_KEY = YES;
};

function main() { MySystem.main(); }

MySystem.clearCanvas = function () {
  var i;
  var nodes = MySystem.store.find(MySystem.Node);
  for (i = 0; i < nodes.get('length'); ++i) {
      nodes.objectAt(i).destroy();
  }
};

// Load canvas data for student
MySystem.loadCanvas = function () {
  MySystem.clearCanvas();
  // For now it will only load from the fixture StudentState
  var state = MySystem.store.find(MySystem.StudentState).objectAt(0);
  var json = JSON.parse(state.get('content'));
  MySystem.parseOldFormatJson(json);
};

MySystem.loadWiseConfig = function(authoredContent,latestResponse) {
  SC.run( function() {
    var activity = MySystem.Activity.fromWiseStepDef(authoredContent);
    MySystem.activityController.set('content',activity);
    MySystem.updateFromDOM();
    var lastFeedback = MySystem.store.find(MySystem.RuleFeedback, MySystem.RuleFeedback.LAST_FEEDBACK_GUID);
    MySystem.activityController.set('lastFeedback',lastFeedback.get('feedback'));
    MySystem.activityController.set('numOfSubmits',lastFeedback.get('numOfSubmits'));
  });
};

// an external save function so that, when we are in an external application which can
// save data (Wise2...), we can save our data externally whenever we want
MySystem.registerExternalSaveFunction = function(func, context) {
  if (!!func) {
    MySystem.savingController.set('saveFunction', function(isSubmit){
      func.call(context, isSubmit);
    });
  } else {
     MySystem.savingController.set('saveFunction', null);
  }

};

// Do any processing or cleanup that ought to be done before an external application
// wants to save data and exit
MySystem.preExternalSave = function() {
  SC.RunLoop.begin();
  MySystem.activityController.getDiagramFeedback({isSubmit: NO});
  MySystem.GraphicPreview.makePreview(MySystem.store);
  SC.RunLoop.end();
};

// Set how frequently the save function is triggered, in ms. If < 0 it will never autosave
MySystem.setAutoSaveFrequency = function(ms) {
  MySystem.savingController.set('autoSaveFrequency', ms);
};

// Called by external app when an external save function returns.
// @param successful {Boolean}      true if the data was successfully saved
MySystem.externalSaveSuccessful = function(successful) {
  MySystem.savingController.saveSuccessful(successful);
};

<commit_msg>extract method 'loadInitialDiagram'<commit_after>// ==========================================================================
// Project:   MySystem
// Copyright: ©2010 Concord Consortium
// ==========================================================================
/*globals MySystem */

// This is the function that will start your app running.  The default
// implementation will load any fixtures you have created then instantiate
// your controllers and awake the elements on your page.
//
// As you develop your application you will probably want to override this.
// See comments for some pointers on what to do next.
//

sc_require('lib/old_format_json_parser');

// MySystem.studentMode = MySystem.NOVICE_STUDENT;
MySystem.studentMode = MySystem.ADVANCED_STUDENT;

MySystem.setupStore = function (obj, ignoreUndeclaredFields) {
  // clear the ids counters on the AutoId record types
  // typically this won't matter at runtime but it will matter during testing
  delete MySystem.Node._nextIdIndex;
  delete MySystem.Link._nextIdIndex;

  if (typeof ignoreUndeclaredFields === 'undefined') ignoreUndeclaredFields = NO;

  obj.dataSource = SC.CascadeDataSource.create({
    dataSources: ['studentStateDataSource', 'fixturesDataSource'],

    studentStateDataSource: MySystem.MergedHashDataSource.create({
      ignoreUndeclaredFields: ignoreUndeclaredFields,

      handledRecordTypes: [
        MySystem.Link,
        MySystem.Node,
        MySystem.Story,
        MySystem.StorySentence,
        MySystem.RuleFeedback,
        MySystem.GraphicPreview,
        MySystem.RubricScore],

      // write the updated student-state data to the DOM whenever it changes
      // New: Write the updated student-state data to a variable whenever it changes.
      dataStoreDidUpdateDataHash: function () {
        // include a 'version' field in the saved data. The WISE4 glue code will warn users if
        // MySystem.learnerDataVersion === DEVELOPMENT_HEAD; hopefully, this will prevent students from
        // actually using a MySystem version that saves learner data with 'version' field === DEVELOPMENT_HEAD
        this.get('dataHash').version = MySystem.learnerDataVersion;
        var textRep = JSON.stringify(this.get('dataHash'), null, 2);
        SC.$('#my_system_state').text(textRep);
        MySystem.savingController.set("dataIsDirty", YES);
      }
    }),

    fixturesDataSource: SC.FixturesDataSource.create({
      // Patch. CascadeDataSource's commitRecords naively has each data source attempt to commit the same records.
      // (This results in an internal inconsistency error on record creation, because the fixturesDataSource tries to
      // create the same record that the studentStateDataSource has just created, and whose storeKey is therefore in
      // the wrong state -- READY instead of BUSY)
      commitRecords: function () { return YES; }
    })
  });

  // the store passes student state data to the student state data source
  obj.store = SC.Store.create({
    commitRecordsAutomatically: YES,

    setStudentStateDataHash: function (hash) {
      this.dataSource.studentStateDataSource.setDataHash(this, hash);
    },
    getStudentStateDataHash: function() {
      var dataHash = this.dataSource.studentStateDataSource.dataHash();
      return JSON.stringify(dataHash,null,2);
    }
  }).from(obj.dataSource);
};

MySystem.main = function main() {
  MySystem.setupStore(MySystem);

  // load the initial data from the DOM (see core.js)
  MySystem.updateFromDOM();

  MySystem.getPath('mainPage.mainPane').append();

  SC.ExceptionHandler = MySystem.ExceptionHandler;

  //Set the content property on the primary controller
  var nodes = MySystem.store.find(MySystem.Diagrammable);
  MySystem.nodesController.set('content', nodes);

  // Configured activity
  var activity = MySystem.store.find(MySystem.Activity, 'assign1');
  MySystem.activityController.set('content',activity);

  // User story
  var storyQuery = SC.Query.local(MySystem.StorySentence, { orderBy: 'order' });
  var storySentences = MySystem.store.find(storyQuery);
  MySystem.storySentenceController.set('content', storySentences);

  MySystem.canvasView = MySystem.mainPage.mainPane.get('diagramView');    // TODO rename to 'diagramView' project-wide RPK 8-11-11

  MySystem.statechart.initStatechart();
  MySystem.storyController.showInstructions();
  SC.RootResponder.CAPTURE_BACKSPACE_KEY = YES;
};

function main() { MySystem.main(); }

MySystem.clearCanvas = function () {
  var i;
  var nodes = MySystem.store.find(MySystem.Node);
  for (i = 0; i < nodes.get('length'); ++i) {
      nodes.objectAt(i).destroy();
  }
  MySystem.loadInitialDiagram();
};

MySystem.loadInitialDiagram = function() {
  MySystem.loadLearnerJSON(MySystem.activityController.get('initialDiagramJson')||'');
};

// Load canvas data for student
MySystem.loadCanvas = function () {
  MySystem.clearCanvas();
  // For now it will only load from the fixture StudentState
  var state = MySystem.store.find(MySystem.StudentState).objectAt(0);
  var json = JSON.parse(state.get('content'));
  MySystem.parseOldFormatJson(json);
};

MySystem.loadWiseConfig = function(authoredContent,latestResponse) {
  SC.run( function() {
    var activity = MySystem.Activity.fromWiseStepDef(authoredContent);
    MySystem.activityController.set('content',activity);
    MySystem.loadInitialDiagram();
    MySystem.updateFromDOM();
    var lastFeedback = MySystem.store.find(MySystem.RuleFeedback, MySystem.RuleFeedback.LAST_FEEDBACK_GUID);
    MySystem.activityController.set('lastFeedback',lastFeedback.get('feedback'));
    MySystem.activityController.set('numOfSubmits',lastFeedback.get('numOfSubmits'));
  });
};

// an external save function so that, when we are in an external application which can
// save data (Wise2...), we can save our data externally whenever we want
MySystem.registerExternalSaveFunction = function(func, context) {
  if (!!func) {
    MySystem.savingController.set('saveFunction', function(isSubmit){
      func.call(context, isSubmit);
    });
  } else {
     MySystem.savingController.set('saveFunction', null);
  }

};

// Do any processing or cleanup that ought to be done before an external application
// wants to save data and exit
MySystem.preExternalSave = function() {
  SC.RunLoop.begin();
  MySystem.activityController.getDiagramFeedback({isSubmit: NO});
  MySystem.GraphicPreview.makePreview(MySystem.store);
  SC.RunLoop.end();
};

// Set how frequently the save function is triggered, in ms. If < 0 it will never autosave
MySystem.setAutoSaveFrequency = function(ms) {
  MySystem.savingController.set('autoSaveFrequency', ms);
};

// Called by external app when an external save function returns.
// @param successful {Boolean}      true if the data was successfully saved
MySystem.externalSaveSuccessful = function(successful) {
  MySystem.savingController.saveSuccessful(successful);
};

<|endoftext|>


<commit_before>/**
 * Copyright 2013 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @providesModule ReactInstanceHandles
 * @typechecks static-only
 */

"use strict";

var ReactID = require('ReactID');

var invariant = require('invariant');

var SEPARATOR = '.';
var SEPARATOR_LENGTH = SEPARATOR.length;

/**
 * Maximum depth of traversals before we consider the possibility of a bad ID.
 */
var MAX_TREE_DEPTH = 100;

/**
 * Size of the reactRoot ID space. We generate random numbers for React root
 * IDs and if there's a collision the events and DOM update system will
 * get confused. If we assume 100 React components per page, and a user
 * loads 1 page per minute 24/7 for 50 years, with a mount point space of
 * 9,999,999 the likelihood of never having a collision is 99.997%.
 */
var GLOBAL_MOUNT_POINT_MAX = 9999999;

/**
 * Creates a DOM ID prefix to use when mounting React components.
 *
 * @param {number} index A unique integer
 * @return {string} React root ID.
 * @internal
 */
function getReactRootIDString(index) {
  return SEPARATOR + 'r[' + index.toString(36) + ']';
}

/**
 * Checks if a character in the supplied ID is a separator or the end.
 *
 * @param {string} id A React DOM ID.
 * @param {number} index Index of the character to check.
 * @return {boolean} True if the character is a separator or end of the ID.
 * @private
 */
function isBoundary(id, index) {
  return id.charAt(index) === SEPARATOR || index === id.length;
}

/**
 * Checks if the supplied string is a valid React DOM ID.
 *
 * @param {string} id A React DOM ID, maybe.
 * @return {boolean} True if the string is a valid React DOM ID.
 * @private
 */
function isValidID(id) {
  return id === '' || (
    id.charAt(0) === SEPARATOR && id.charAt(id.length - 1) !== SEPARATOR
  );
}

/**
 * Checks if the first ID is an ancestor of or equal to the second ID.
 *
 * @param {string} ancestorID
 * @param {string} descendantID
 * @return {boolean} True if `ancestorID` is an ancestor of `descendantID`.
 * @internal
 */
function isAncestorIDOf(ancestorID, descendantID) {
  return (
    descendantID.indexOf(ancestorID) === 0 &&
    isBoundary(descendantID, ancestorID.length)
  );
}

/**
 * Gets the parent ID of the supplied React DOM ID, `id`.
 *
 * @param {string} id ID of a component.
 * @return {string} ID of the parent, or an empty string.
 * @private
 */
function getParentID(id) {
  return id ? id.substr(0, id.lastIndexOf(SEPARATOR)) : '';
}

/**
 * Gets the next DOM ID on the tree path from the supplied `ancestorID` to the
 * supplied `destinationID`. If they are equal, the ID is returned.
 *
 * @param {string} ancestorID ID of an ancestor node of `destinationID`.
 * @param {string} destinationID ID of the destination node.
 * @return {string} Next ID on the path from `ancestorID` to `destinationID`.
 * @private
 */
function getNextDescendantID(ancestorID, destinationID) {
  invariant(
    isValidID(ancestorID) && isValidID(destinationID),
    'getNextDescendantID(%s, %s): Received an invalid React DOM ID.',
    ancestorID,
    destinationID
  );
  invariant(
    isAncestorIDOf(ancestorID, destinationID),
    'getNextDescendantID(...): React has made an invalid assumption about ' +
    'the DOM hierarchy. Expected `%s` to be an ancestor of `%s`.',
    ancestorID,
    destinationID
  );
  if (ancestorID === destinationID) {
    return ancestorID;
  }
  // Skip over the ancestor and the immediate separator. Traverse until we hit
  // another separator or we reach the end of `destinationID`.
  var start = ancestorID.length + SEPARATOR_LENGTH;
  for (var i = start; i < destinationID.length; i++) {
    if (isBoundary(destinationID, i)) {
      break;
    }
  }
  return destinationID.substr(0, i);
}

/**
 * Gets the nearest common ancestor ID of two IDs.
 *
 * Using this ID scheme, the nearest common ancestor ID is the longest common
 * prefix of the two IDs that immediately preceded a "marker" in both strings.
 *
 * @param {string} oneID
 * @param {string} twoID
 * @return {string} Nearest common ancestor ID, or the empty string if none.
 * @private
 */
function getFirstCommonAncestorID(oneID, twoID) {
  var minLength = Math.min(oneID.length, twoID.length);
  if (minLength === 0) {
    return '';
  }
  var lastCommonMarkerIndex = 0;
  // Use `<=` to traverse until the "EOL" of the shorter string.
  for (var i = 0; i <= minLength; i++) {
    if (isBoundary(oneID, i) && isBoundary(twoID, i)) {
      lastCommonMarkerIndex = i;
    } else if (oneID.charAt(i) !== twoID.charAt(i)) {
      break;
    }
  }
  var longestCommonID = oneID.substr(0, lastCommonMarkerIndex);
  invariant(
    isValidID(longestCommonID),
    'getFirstCommonAncestorID(%s, %s): Expected a valid React DOM ID: %s',
    oneID,
    twoID,
    longestCommonID
  );
  return longestCommonID;
}

/**
 * Traverses the parent path between two IDs (either up or down). The IDs must
 * not be the same, and there must exist a parent path between them.
 *
 * @param {?string} start ID at which to start traversal.
 * @param {?string} stop ID at which to end traversal.
 * @param {function} cb Callback to invoke each ID with.
 * @param {?boolean} skipFirst Whether or not to skip the first node.
 * @param {?boolean} skipLast Whether or not to skip the last node.
 * @private
 */
function traverseParentPath(start, stop, cb, arg, skipFirst, skipLast) {
  start = start || '';
  stop = stop || '';
  invariant(
    start !== stop,
    'traverseParentPath(...): Cannot traverse from and to the same ID, `%s`.',
    start
  );
  var traverseUp = isAncestorIDOf(stop, start);
  invariant(
    traverseUp || isAncestorIDOf(start, stop),
    'traverseParentPath(%s, %s, ...): Cannot traverse from two IDs that do ' +
    'not have a parent path.',
    start,
    stop
  );
  // Traverse from `start` to `stop` one depth at a time.
  var depth = 0;
  var traverse = traverseUp ? getParentID : getNextDescendantID;
  for (var id = start; /* until break */; id = traverse(id, stop)) {
    if ((!skipFirst || id !== start) && (!skipLast || id !== stop)) {
      cb(id, traverseUp, arg);
    }
    if (id === stop) {
      // Only break //after// visiting `stop`.
      break;
    }
    invariant(
      depth++ < MAX_TREE_DEPTH,
      'traverseParentPath(%s, %s, ...): Detected an infinite loop while ' +
      'traversing the React DOM ID tree. This may be due to malformed IDs: %s',
      start, stop
    );
  }
}

/**
 * Manages the IDs assigned to DOM representations of React components. This
 * uses a specific scheme in order to traverse the DOM efficiently (e.g. in
 * order to simulate events).
 *
 * @internal
 */
var ReactInstanceHandles = {

  separator: SEPARATOR,

  createReactRootID: function() {
    return getReactRootIDString(
      Math.ceil(Math.random() * GLOBAL_MOUNT_POINT_MAX)
    );
  },

  /**
   * True if the supplied `node` is rendered by React.
   *
   * @param {*} node DOM Element to check.
   * @return {boolean} True if the DOM Element appears to be rendered by React.
   * @internal
   */
  isRenderedByReact: function(node) {
    if (node.nodeType !== 1) {
      // Not a DOMElement, therefore not a React component
      return false;
    }
    var id = ReactID.getID(node);
    return id ? id.charAt(0) === SEPARATOR : false;
  },

  /**
   * Traverses up the ancestors of the supplied node to find a node that is a
   * DOM representation of a React component.
   *
   * @param {*} node
   * @return {?DOMEventTarget}
   * @internal
   */
  getFirstReactDOM: function(node) {
    var current = node;
    while (current && current.parentNode !== current) {
      if (ReactInstanceHandles.isRenderedByReact(current)) {
        return current;
      }
      current = current.parentNode;
    }
    return null;
  },

  /**
   * Finds a node with the supplied `id` inside of the supplied `ancestorNode`.
   * Exploits the ID naming scheme to perform the search quickly.
   *
   * @param {DOMEventTarget} ancestorNode Search from this root.
   * @pararm {string} id ID of the DOM representation of the component.
   * @return {DOMEventTarget} DOM node with the supplied `id`.
   * @internal
   */
  findComponentRoot: function(ancestorNode, id) {
    var firstChildren = [ancestorNode.firstChild];
    var childIndex = 0;

    while (childIndex < firstChildren.length) {
      var child = firstChildren[childIndex++];
      while (child) {
        var childID = ReactID.getID(child);
        if (childID) {
          if (id === childID) {
            return child;
          } else if (isAncestorIDOf(childID, id)) {
            // If we find a child whose ID is an ancestor of the given ID,
            // then we can be sure that we only want to search the subtree
            // rooted at this child, so we can throw out the rest of the
            // search state.
            firstChildren.length = childIndex = 0;
            firstChildren.push(child.firstChild);
            break;
          }
        } else {
          // If this child had no ID, then there's a chance that it was
          // injected automatically by the browser, as when a `<table>`
          // element sprouts an extra `<tbody>` child as a side effect of
          // `.innerHTML` parsing. Optimistically continue down this
          // branch, but not before examining the other siblings.
          firstChildren.push(child.firstChild);
        }
        child = child.nextSibling;
      }
    }

    global.console && console.error && console.error(
      'Error while invoking `findComponentRoot` with the following ' +
      'ancestor node:',
      ancestorNode
    );
    invariant(
      false,
      'findComponentRoot(..., %s): Unable to find element. This probably ' +
      'means the DOM was unexpectedly mutated (e.g. by the browser).',
      id,
      ReactID.getID(ancestorNode)
    );
  },

  /**
   * Gets the DOM ID of the React component that is the root of the tree that
   * contains the React component with the supplied DOM ID.
   *
   * @param {string} id DOM ID of a React component.
   * @return {?string} DOM ID of the React component that is the root.
   * @internal
   */
  getReactRootIDFromNodeID: function(id) {
    var regexResult = /\.r\[[^\]]+\]/.exec(id);
    return regexResult && regexResult[0];
  },

  /**
   * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
   * should would receive a `mouseEnter` or `mouseLeave` event.
   *
   * NOTE: Does not invoke the callback on the nearest common ancestor because
   * nothing "entered" or "left" that element.
   *
   * @param {string} leaveID ID being left.
   * @param {string} enterID ID being entered.
   * @param {function} cb Callback to invoke on each entered/left ID.
   * @param {*} upArg Argument to invoke the callback with on left IDs.
   * @param {*} downArg Argument to invoke the callback with on entered IDs.
   * @internal
   */
  traverseEnterLeave: function(leaveID, enterID, cb, upArg, downArg) {
    var ancestorID = getFirstCommonAncestorID(leaveID, enterID);
    if (ancestorID !== leaveID) {
      traverseParentPath(leaveID, ancestorID, cb, upArg, false, true);
    }
    if (ancestorID !== enterID) {
      traverseParentPath(ancestorID, enterID, cb, downArg, true, false);
    }
  },

  /**
   * Simulates the traversal of a two-phase, capture/bubble event dispatch.
   *
   * NOTE: This traversal happens on IDs without touching the DOM.
   *
   * @param {string} targetID ID of the target node.
   * @param {function} cb Callback to invoke.
   * @param {*} arg Argument to invoke the callback with.
   * @internal
   */
  traverseTwoPhase: function(targetID, cb, arg) {
    if (targetID) {
      traverseParentPath('', targetID, cb, arg, true, false);
      traverseParentPath(targetID, '', cb, arg, false, true);
    }
  },

  /**
   * Exposed for unit testing.
   * @private
   */
  _getFirstCommonAncestorID: getFirstCommonAncestorID,

  /**
   * Exposed for unit testing.
   * @private
   */
  _getNextDescendantID: getNextDescendantID

};

module.exports = ReactInstanceHandles;
<commit_msg>Don't ignore children of non-ancestor ID nodes in findComponentRoot.<commit_after>/**
 * Copyright 2013 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @providesModule ReactInstanceHandles
 * @typechecks static-only
 */

"use strict";

var ReactID = require('ReactID');

var invariant = require('invariant');

var SEPARATOR = '.';
var SEPARATOR_LENGTH = SEPARATOR.length;

/**
 * Maximum depth of traversals before we consider the possibility of a bad ID.
 */
var MAX_TREE_DEPTH = 100;

/**
 * Size of the reactRoot ID space. We generate random numbers for React root
 * IDs and if there's a collision the events and DOM update system will
 * get confused. If we assume 100 React components per page, and a user
 * loads 1 page per minute 24/7 for 50 years, with a mount point space of
 * 9,999,999 the likelihood of never having a collision is 99.997%.
 */
var GLOBAL_MOUNT_POINT_MAX = 9999999;

/**
 * Creates a DOM ID prefix to use when mounting React components.
 *
 * @param {number} index A unique integer
 * @return {string} React root ID.
 * @internal
 */
function getReactRootIDString(index) {
  return SEPARATOR + 'r[' + index.toString(36) + ']';
}

/**
 * Checks if a character in the supplied ID is a separator or the end.
 *
 * @param {string} id A React DOM ID.
 * @param {number} index Index of the character to check.
 * @return {boolean} True if the character is a separator or end of the ID.
 * @private
 */
function isBoundary(id, index) {
  return id.charAt(index) === SEPARATOR || index === id.length;
}

/**
 * Checks if the supplied string is a valid React DOM ID.
 *
 * @param {string} id A React DOM ID, maybe.
 * @return {boolean} True if the string is a valid React DOM ID.
 * @private
 */
function isValidID(id) {
  return id === '' || (
    id.charAt(0) === SEPARATOR && id.charAt(id.length - 1) !== SEPARATOR
  );
}

/**
 * Checks if the first ID is an ancestor of or equal to the second ID.
 *
 * @param {string} ancestorID
 * @param {string} descendantID
 * @return {boolean} True if `ancestorID` is an ancestor of `descendantID`.
 * @internal
 */
function isAncestorIDOf(ancestorID, descendantID) {
  return (
    descendantID.indexOf(ancestorID) === 0 &&
    isBoundary(descendantID, ancestorID.length)
  );
}

/**
 * Gets the parent ID of the supplied React DOM ID, `id`.
 *
 * @param {string} id ID of a component.
 * @return {string} ID of the parent, or an empty string.
 * @private
 */
function getParentID(id) {
  return id ? id.substr(0, id.lastIndexOf(SEPARATOR)) : '';
}

/**
 * Gets the next DOM ID on the tree path from the supplied `ancestorID` to the
 * supplied `destinationID`. If they are equal, the ID is returned.
 *
 * @param {string} ancestorID ID of an ancestor node of `destinationID`.
 * @param {string} destinationID ID of the destination node.
 * @return {string} Next ID on the path from `ancestorID` to `destinationID`.
 * @private
 */
function getNextDescendantID(ancestorID, destinationID) {
  invariant(
    isValidID(ancestorID) && isValidID(destinationID),
    'getNextDescendantID(%s, %s): Received an invalid React DOM ID.',
    ancestorID,
    destinationID
  );
  invariant(
    isAncestorIDOf(ancestorID, destinationID),
    'getNextDescendantID(...): React has made an invalid assumption about ' +
    'the DOM hierarchy. Expected `%s` to be an ancestor of `%s`.',
    ancestorID,
    destinationID
  );
  if (ancestorID === destinationID) {
    return ancestorID;
  }
  // Skip over the ancestor and the immediate separator. Traverse until we hit
  // another separator or we reach the end of `destinationID`.
  var start = ancestorID.length + SEPARATOR_LENGTH;
  for (var i = start; i < destinationID.length; i++) {
    if (isBoundary(destinationID, i)) {
      break;
    }
  }
  return destinationID.substr(0, i);
}

/**
 * Gets the nearest common ancestor ID of two IDs.
 *
 * Using this ID scheme, the nearest common ancestor ID is the longest common
 * prefix of the two IDs that immediately preceded a "marker" in both strings.
 *
 * @param {string} oneID
 * @param {string} twoID
 * @return {string} Nearest common ancestor ID, or the empty string if none.
 * @private
 */
function getFirstCommonAncestorID(oneID, twoID) {
  var minLength = Math.min(oneID.length, twoID.length);
  if (minLength === 0) {
    return '';
  }
  var lastCommonMarkerIndex = 0;
  // Use `<=` to traverse until the "EOL" of the shorter string.
  for (var i = 0; i <= minLength; i++) {
    if (isBoundary(oneID, i) && isBoundary(twoID, i)) {
      lastCommonMarkerIndex = i;
    } else if (oneID.charAt(i) !== twoID.charAt(i)) {
      break;
    }
  }
  var longestCommonID = oneID.substr(0, lastCommonMarkerIndex);
  invariant(
    isValidID(longestCommonID),
    'getFirstCommonAncestorID(%s, %s): Expected a valid React DOM ID: %s',
    oneID,
    twoID,
    longestCommonID
  );
  return longestCommonID;
}

/**
 * Traverses the parent path between two IDs (either up or down). The IDs must
 * not be the same, and there must exist a parent path between them.
 *
 * @param {?string} start ID at which to start traversal.
 * @param {?string} stop ID at which to end traversal.
 * @param {function} cb Callback to invoke each ID with.
 * @param {?boolean} skipFirst Whether or not to skip the first node.
 * @param {?boolean} skipLast Whether or not to skip the last node.
 * @private
 */
function traverseParentPath(start, stop, cb, arg, skipFirst, skipLast) {
  start = start || '';
  stop = stop || '';
  invariant(
    start !== stop,
    'traverseParentPath(...): Cannot traverse from and to the same ID, `%s`.',
    start
  );
  var traverseUp = isAncestorIDOf(stop, start);
  invariant(
    traverseUp || isAncestorIDOf(start, stop),
    'traverseParentPath(%s, %s, ...): Cannot traverse from two IDs that do ' +
    'not have a parent path.',
    start,
    stop
  );
  // Traverse from `start` to `stop` one depth at a time.
  var depth = 0;
  var traverse = traverseUp ? getParentID : getNextDescendantID;
  for (var id = start; /* until break */; id = traverse(id, stop)) {
    if ((!skipFirst || id !== start) && (!skipLast || id !== stop)) {
      cb(id, traverseUp, arg);
    }
    if (id === stop) {
      // Only break //after// visiting `stop`.
      break;
    }
    invariant(
      depth++ < MAX_TREE_DEPTH,
      'traverseParentPath(%s, %s, ...): Detected an infinite loop while ' +
      'traversing the React DOM ID tree. This may be due to malformed IDs: %s',
      start, stop
    );
  }
}

/**
 * Manages the IDs assigned to DOM representations of React components. This
 * uses a specific scheme in order to traverse the DOM efficiently (e.g. in
 * order to simulate events).
 *
 * @internal
 */
var ReactInstanceHandles = {

  separator: SEPARATOR,

  createReactRootID: function() {
    return getReactRootIDString(
      Math.ceil(Math.random() * GLOBAL_MOUNT_POINT_MAX)
    );
  },

  /**
   * True if the supplied `node` is rendered by React.
   *
   * @param {*} node DOM Element to check.
   * @return {boolean} True if the DOM Element appears to be rendered by React.
   * @internal
   */
  isRenderedByReact: function(node) {
    if (node.nodeType !== 1) {
      // Not a DOMElement, therefore not a React component
      return false;
    }
    var id = ReactID.getID(node);
    return id ? id.charAt(0) === SEPARATOR : false;
  },

  /**
   * Traverses up the ancestors of the supplied node to find a node that is a
   * DOM representation of a React component.
   *
   * @param {*} node
   * @return {?DOMEventTarget}
   * @internal
   */
  getFirstReactDOM: function(node) {
    var current = node;
    while (current && current.parentNode !== current) {
      if (ReactInstanceHandles.isRenderedByReact(current)) {
        return current;
      }
      current = current.parentNode;
    }
    return null;
  },

  /**
   * Finds a node with the supplied `id` inside of the supplied `ancestorNode`.
   * Exploits the ID naming scheme to perform the search quickly.
   *
   * @param {DOMEventTarget} ancestorNode Search from this root.
   * @pararm {string} id ID of the DOM representation of the component.
   * @return {DOMEventTarget} DOM node with the supplied `id`.
   * @internal
   */
  findComponentRoot: function(ancestorNode, id) {
    var firstChildren = [ancestorNode.firstChild];
    var childIndex = 0;

    while (childIndex < firstChildren.length) {
      var child = firstChildren[childIndex++];
      while (child) {
        var childID = ReactID.getID(child);
        if (childID) {
          if (id === childID) {
            return child;
          } else if (isAncestorIDOf(childID, id)) {
            // If we find a child whose ID is an ancestor of the given ID,
            // then we can be sure that we only want to search the subtree
            // rooted at this child, so we can throw out the rest of the
            // search state.
            firstChildren.length = childIndex = 0;
            firstChildren.push(child.firstChild);
            break;
          } else {
            // TODO This should not be necessary if the ID hierarchy is
            // correct, but is occasionally necessary if the DOM has been
            // modified in unexpected ways.
            firstChildren.push(child.firstChild);
          }
        } else {
          // If this child had no ID, then there's a chance that it was
          // injected automatically by the browser, as when a `<table>`
          // element sprouts an extra `<tbody>` child as a side effect of
          // `.innerHTML` parsing. Optimistically continue down this
          // branch, but not before examining the other siblings.
          firstChildren.push(child.firstChild);
        }
        child = child.nextSibling;
      }
    }

    global.console && console.error && console.error(
      'Error while invoking `findComponentRoot` with the following ' +
      'ancestor node:',
      ancestorNode
    );
    invariant(
      false,
      'findComponentRoot(..., %s): Unable to find element. This probably ' +
      'means the DOM was unexpectedly mutated (e.g. by the browser).',
      id,
      ReactID.getID(ancestorNode)
    );
  },

  /**
   * Gets the DOM ID of the React component that is the root of the tree that
   * contains the React component with the supplied DOM ID.
   *
   * @param {string} id DOM ID of a React component.
   * @return {?string} DOM ID of the React component that is the root.
   * @internal
   */
  getReactRootIDFromNodeID: function(id) {
    var regexResult = /\.r\[[^\]]+\]/.exec(id);
    return regexResult && regexResult[0];
  },

  /**
   * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
   * should would receive a `mouseEnter` or `mouseLeave` event.
   *
   * NOTE: Does not invoke the callback on the nearest common ancestor because
   * nothing "entered" or "left" that element.
   *
   * @param {string} leaveID ID being left.
   * @param {string} enterID ID being entered.
   * @param {function} cb Callback to invoke on each entered/left ID.
   * @param {*} upArg Argument to invoke the callback with on left IDs.
   * @param {*} downArg Argument to invoke the callback with on entered IDs.
   * @internal
   */
  traverseEnterLeave: function(leaveID, enterID, cb, upArg, downArg) {
    var ancestorID = getFirstCommonAncestorID(leaveID, enterID);
    if (ancestorID !== leaveID) {
      traverseParentPath(leaveID, ancestorID, cb, upArg, false, true);
    }
    if (ancestorID !== enterID) {
      traverseParentPath(ancestorID, enterID, cb, downArg, true, false);
    }
  },

  /**
   * Simulates the traversal of a two-phase, capture/bubble event dispatch.
   *
   * NOTE: This traversal happens on IDs without touching the DOM.
   *
   * @param {string} targetID ID of the target node.
   * @param {function} cb Callback to invoke.
   * @param {*} arg Argument to invoke the callback with.
   * @internal
   */
  traverseTwoPhase: function(targetID, cb, arg) {
    if (targetID) {
      traverseParentPath('', targetID, cb, arg, true, false);
      traverseParentPath(targetID, '', cb, arg, false, true);
    }
  },

  /**
   * Exposed for unit testing.
   * @private
   */
  _getFirstCommonAncestorID: getFirstCommonAncestorID,

  /**
   * Exposed for unit testing.
   * @private
   */
  _getNextDescendantID: getNextDescendantID

};

module.exports = ReactInstanceHandles;
<|endoftext|>


<commit_before>/*
Mood Tracker v1.2
https://github.com/alni/MoodTracker/
----------------------
The MIT License (MIT)

Copyright (c) 2015 Alexander Nilsen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--------------------
src/pebble-js-app.js
*/

var moodMinSetting = 'mood_min';
var moodMaxSetting = 'mood_max';
var moodStepSetting = 'mood_step';
var reminderDaysSetting = 'reminder_days';
var reminderHoursSetting = 'reminder_hours';

Pebble.addEventListener('showConfiguration', function(e) {
  // Show config page
  Pebble.openURL('https://alni.github.io/MoodTracker/pebble/config.html');
});

Pebble.addEventListener('webviewclosed', function(e) {
  // Decode and parse config data as JSON
  var config_data = JSON.parse(decodeURIComponent(e.response));
  console.log('Config window returned: ' + JSON.stringify(config_data));
  // Prepare AppMessage payload
  var dict = {
    "KEY_MOOD_MIN": config_data[moodMinSetting],
    "KEY_MOOD_MAX": config_data[moodMaxSetting],
    "KEY_MOOD_STEP": config_data[moodStepSetting],
    "KEY_REMINDER_DAYS": config_data[reminderDaysSetting]
  };

  // Store Reminder settings in localStorage on the mobile device
  localStorage[reminderDaysSetting] = config_data[reminderDaysSetting] + "";
  localStorage[reminderHoursSetting] = config_data[reminderHoursSetting].join(",");

  // Send settings to Pebble watchapp
  Pebble.sendAppMessage(dict, function() {
    console.log('Sent config data to Pebble');
  }, function() {
    console.log('Failed to send config data!');
  });
});

/**
 * @namespace
 */
var MoodTracker = (function() {
  var formatNumber = function (num) {
    return num < 10 ? "0" + num : "" + num;
  };
  /**
   * Generate Pin ID with a format based on the date
   * @param {Date} time - the time object to use
   * @returns {string} returns the ID with the format:
   *     "moodtracker-YYYY-MM-DD-HH"
   */
  var generatePinId = function(time) {
    var hours = formatNumber(time.getHours()),
        dom = formatNumber(time.getDate()),
        month = formatNumber(time.getMonth()),
        year = time.getFullYear();

    return ["moodtracker", year, month, dom, hours].join("-");
  };
  /**
   * Creates a Timeline Pin from a given time and duration
   * @param {Date} time - the time for the pin
   * @param {integer} duration - the duration of the pin in seconds
   * @returns {object} a Timeline Pin object
   */
  var createPin = function(time, duration) {
    return {
      "id": generatePinId(time),
      "time": time.toISOString(),
      "duration": duration,
      "createNotification": {
        "layout": {
          "type": "genericNotification",
          "title": "New Item",
          "tinyIcon": "system://images/NOTIFICATION_FLAG",
          "body": "A new Mood Tracker pin has been added to your timeline."
        }
      },
      "layout": {
        "title": "Mood Tracker",
        "type": "genericPin",
        "tinyIcon": "system://images/NOTIFICATION_REMINDER",
        "body": "Remember to log your mood.",
        "backgroundColor": "#55AAAA"
      },
      "reminders": [],
      "actions": [
        {
          "title": "Log your mood",
          "type": "openWatchApp",
          "launchCode": 15
        },
        {
          "title": "Review moods",
          "type": "openWatchApp",
          "launchCode": 22
        }
      ]
    };
  };

  /**
   * Creates a Timeline Pin reminder object
   * @param {Date} currentTime - the time to use as a base for the reminder
   * @param {integer} hour - the hour to set the reminder for
   * @param {integer} [minute] - the minute to set the reminder for
   * @returns {Object} a Timeline Pin reminder object
   */
  var createReminder = function(currentTime, hour, minute) {
    var time = new Date(+currentTime);
    time.setHours(hour);
    time.setMinutes(minute || 0);
    return {
      "time": time.toISOString(),
      "layout": {
        "type": "genericReminder",
        "tinyIcon": "system://images/NOTIFICATION_REMINDER",
        "title": "Reminder to log your mood"
      }
    };
  };

  /**
   * Constructs a new MoodTracker Pin
   * @class Pin
   * @memberof MoodTracker
   * @param {Date} time - the time to use for the pin
   * @param {integer} duration - the duration of the pin in seconds
   * @param {integer[]} reminders - the list of reminder hours for the pin
   */
  var MoodTrackerPin = function(time, duration, reminders) {
    this.time = time;
    this.duration = duration;
    this.id = generatePinId(time);
    this.reminders = reminders || [10, 14, 18, 22];
    this.debug = false;
  };

  /**
   * Creates a Timeline Pin
   * @returns {Object} a Timeline Pin object
   */
  MoodTrackerPin.prototype.getPin = function() {
    var time = this.time,
        reminders = this.reminders,
        l = reminders.length,
        i = 0,
        pin = createPin(time, this.duration);
    delete pin.createNotification;
    for (i = 0; i < l; i++) {
      pin.reminders.push(createReminder(time, reminders[i]));
    }
    return pin;
  };
  /**
   * Inserts the MoodTracker pin to the timeline
   * @fires MoodTracker.Pin#onInserted
   * @returns {MoodTracker.Pin} itself (allows chaining)
   */
  MoodTrackerPin.prototype.insertPin = function() {
    if (!this.onInserted) {
      this.onInserted = function() {};
    }
    var pin = this.getPin();
    if (this.debug) {
      console.log('Inserting pin in the future: ' + JSON.stringify(pin));
    }
    insertUserPin(pin, this.onInserted);
    return this;
  };

  return {
    /**
     * @lends MoodTracker.Pin
     */
    Pin : (function() {
      return MoodTrackerPin;
    })(),
    /**
     * Creates a Timeline Pin
     * @memberof MoodTracker
     * @param {Date} time - the time to use for the pin
     * @param {integer} duration - the duration of the pin in seconds
     * @returns {object} a Timeline Pin object
     */
    createPin : function(time, duration) {
      var pin = createPin(time, duration),
          reminderTime = new Date(),
          reminderHour = reminderTime.getHours();
      //var reminderHour = time.getHours();
      //pin.reminders.push(createReminder(reminderHour));
      pin.reminders.push(createReminder(time, 10));
      pin.reminders.push(createReminder(time, 14));
      pin.reminders.push(createReminder(time, 18));
      pin.reminders.push(createReminder(time, 22));
      if(Pebble.getActiveWatchInfo) {
        // Available for use!
        var watch = Pebble.getActiveWatchInfo();
        console.log(watch.model);
        if (watch.model.indexOf("qemu") >= 0) {
          /*for (var i = reminderHour; i < reminderHour+1; i++) {
            for (var j = 0; j < 59; j+=5) {
              pin.reminders.push(createReminder(time, i, j));
            }
          }*/
        }
      } else {
        // Not available, handle gracefully

      }

      return pin;
    }
  };
})();

var NUM_DAYS = 7; // Number of days to create pins for
var HOURS = [10, 14, 18, 22]; // Hours to create pins for

Pebble.addEventListener('ready', function() {
  console.log('PebbleKit JS ready!');

  // Load Reminder settings from localStorage on the mobile device
  // Or use default values of nothing is stored.
  NUM_DAYS = localStorage[reminderDaysSetting] || NUM_DAYS;
  HOURS = localStorage[reminderHoursSetting].split(",").map(Number) || HOURS;

  var time = new Date(),
      duration = 3600, // 1 hour
      times = [],
      hours = HOURS, //[10, 14, 18, 22],
      _time = null,
      i = 0,
      j = 0,
      length = 0,
      completed = 0,
      results = null,
      pin = null;
  time.setHours(10);
  time.setMinutes(0);
  for (i = 0; i < NUM_DAYS; i++) {
    for (j = 0; j < hours.length; j++) {
      _time = new Date(+time);
      _time.setDate(_time.getDate() + i);
      _time.setHours(hours[j]);
      console.log(_time.toString());
      times.push(_time);
    }
  }
  length = times.length;
  results = new Array(length);
  pin = new MoodTracker.Pin(times[0], duration, [times[0].getHours()]);
  pin.onInserted = function(responseText) {
    // Insert another Timeline Pin after the previous as been inserted

    // Add the response from the inserted pin to results
    results[completed] = responseText;
    if (++completed === length) {
      // After all Pins has been inserted log the combined results
      console.log('Result: ' + results.join(' ; '));
    } else {
      // If there are still Pins that need to be inserted, use the same
      // MoodTracker Pin object, but set the Pin time and remnders of the
      // next Pin in line
      pin.time = times[completed];
      pin.reminders = [times[completed].getHours()];
      // Re-set the MoodTracker Pin object to the MoodTracker Pin object
      // returned when inserting the Pin to the Timeline
      pin = pin.insertPin();
      // Because we are using the same MoodTracker Pin object, this same
      // "onInserted" event callback will be called when this Pin has been
      // inserted to the Timeline
    }
  };
  //onsole.log('Inserting pin in the future: ' + JSON.stringify(pin));

  // Insert for today
  pin = pin.insertPin();
});

/******************************* timeline lib *********************************/

// The timeline public URL root
var API_URL_ROOT = 'https://timeline-api.getpebble.com/';

/**
 * Send a request to the Pebble public web timeline API.
 * @param pin The JSON pin to insert. Must contain 'id' field.
 * @param type The type of request, either PUT or DELETE.
 * @param callback The callback to receive the responseText after the request has completed.
 */
function timelineRequest(pin, type, callback) {
  // User or shared?
  var url = API_URL_ROOT + 'v1/user/pins/' + pin.id;

  // Create XHR
  var xhr = new XMLHttpRequest();
  xhr.onload = function () {
    console.log('timeline: response received: ' + this.responseText);
    callback(this.responseText);
  };
  xhr.open(type, url);

  // Get token
  Pebble.getTimelineToken(function(token) {
    // Add headers
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.setRequestHeader('X-User-Token', '' + token);

    // Send
    xhr.send(JSON.stringify(pin));
    console.log('timeline: request sent.');
  }, function(error) { console.log('timeline: error getting timeline token: ' + error); });
}

/**
 * Insert a pin into the timeline for this user.
 * @param pin The JSON pin to insert.
 * @param callback The callback to receive the responseText after the request has completed.
 */
function insertUserPin(pin, callback) {
  timelineRequest(pin, 'PUT', callback);
}

/**
 * Delete a pin from the timeline for this user.
 * @param pin The JSON pin to delete.
 * @param callback The callback to receive the responseText after the request has completed.
 */
function deleteUserPin(pin, callback) {
  timelineRequest(pin, 'DELETE', callback);
}

/***************************** end timeline lib *******************************/

/* EXAMPLE JSON PIN

{
  "id": "moodtracker-453923",
  "time": "2015-07-22T00:00:00Z",
  "duration": 1440,
  "createNotification": {
    "layout": {
      "type": "genericNotification",
      "title": "New Item",
      "tinyIcon": "system://images/NOTIFICATION_FLAG",
      "body": "A new appointment has been added to your calendar at 4pm."
    }
  },
  "layout": {
    "title": "Mood Tracker",
    "type": "genericPin",
    "tinyIcon": "system://images/TIMELINE_CALENDAR",
    "body": "Remember to log your mood."
  },
  "reminders": [
    {
      "time": "2015-07-22T08:00:00Z",
      "layout": {
        "type": "genericReminder",
        "tinyIcon": "system://images/TIMELINE_CALENDAR",
        "title": "Reminder to log your mood"
      }
    },
    {
      "time": "2015-07-22T12:00:00Z",
      "layout": {
        "type": "genericReminder",
        "tinyIcon": "system://images/TIMELINE_CALENDAR",
        "title": "Reminder to log your mood"
      }
    },
    {
      "time": "2015-07-22T16:00:00Z",
      "layout": {
        "type": "genericReminder",
        "tinyIcon": "system://images/TIMELINE_CALENDAR",
        "title": "Reminder to log your mood"
      }
    },
    {
      "time": "2015-07-22T20:00:00Z",
      "layout": {
        "type": "genericReminder",
        "tinyIcon": "system://images/TIMELINE_CALENDAR",
        "title": "Reminder to log your mood"
      }
    }
  ],
  "actions": [
    {
      "title": "Log your mood",
      "type": "openWatchApp",
      "launchCode": 15
    },
    {
      "title": "Review moods",
      "type": "openWatchApp",
      "launchCode": 22
    }
  ]
}

*/<commit_msg>Fix potential faulty JavaScript code <commit_after>/*
Mood Tracker v1.2
https://github.com/alni/MoodTracker/
----------------------
The MIT License (MIT)

Copyright (c) 2015 Alexander Nilsen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--------------------
src/pebble-js-app.js
*/

var moodMinSetting = 'mood_min';
var moodMaxSetting = 'mood_max';
var moodStepSetting = 'mood_step';
var reminderDaysSetting = 'reminder_days';
var reminderHoursSetting = 'reminder_hours';

Pebble.addEventListener('showConfiguration', function(e) {
  // Show config page
  Pebble.openURL('https://alni.github.io/MoodTracker/pebble/config.html');
});

Pebble.addEventListener('webviewclosed', function(e) {
  // Decode and parse config data as JSON
  var config_data = JSON.parse(decodeURIComponent(e.response));
  console.log('Config window returned: ' + JSON.stringify(config_data));
  // Prepare AppMessage payload
  var dict = {
    "KEY_MOOD_MIN": config_data[moodMinSetting],
    "KEY_MOOD_MAX": config_data[moodMaxSetting],
    "KEY_MOOD_STEP": config_data[moodStepSetting],
    "KEY_REMINDER_DAYS": config_data[reminderDaysSetting]
  };

  // Store Reminder settings in localStorage on the mobile device
  localStorage[reminderDaysSetting] = config_data[reminderDaysSetting] + "";
  localStorage[reminderHoursSetting] = config_data[reminderHoursSetting].join(",");

  // Send settings to Pebble watchapp
  Pebble.sendAppMessage(dict, function() {
    console.log('Sent config data to Pebble');
  }, function() {
    console.log('Failed to send config data!');
  });
});

/**
 * @namespace
 */
var MoodTracker = (function() {
  var formatNumber = function (num) {
    return num < 10 ? "0" + num : "" + num;
  };
  /**
   * Generate Pin ID with a format based on the date
   * @param {Date} time - the time object to use
   * @returns {string} returns the ID with the format:
   *     "moodtracker-YYYY-MM-DD-HH"
   */
  var generatePinId = function(time) {
    var hours = formatNumber(time.getHours()),
        dom = formatNumber(time.getDate()),
        month = formatNumber(time.getMonth()),
        year = time.getFullYear();

    return ["moodtracker", year, month, dom, hours].join("-");
  };
  /**
   * Creates a Timeline Pin from a given time and duration
   * @param {Date} time - the time for the pin
   * @param {integer} duration - the duration of the pin in seconds
   * @returns {object} a Timeline Pin object
   */
  var createPin = function(time, duration) {
    return {
      "id": generatePinId(time),
      "time": time.toISOString(),
      "duration": duration,
      "createNotification": {
        "layout": {
          "type": "genericNotification",
          "title": "New Item",
          "tinyIcon": "system://images/NOTIFICATION_FLAG",
          "body": "A new Mood Tracker pin has been added to your timeline."
        }
      },
      "layout": {
        "title": "Mood Tracker",
        "type": "genericPin",
        "tinyIcon": "system://images/NOTIFICATION_REMINDER",
        "body": "Remember to log your mood.",
        "backgroundColor": "#55AAAA"
      },
      "reminders": [],
      "actions": [
        {
          "title": "Log your mood",
          "type": "openWatchApp",
          "launchCode": 15
        },
        {
          "title": "Review moods",
          "type": "openWatchApp",
          "launchCode": 22
        }
      ]
    };
  };

  /**
   * Creates a Timeline Pin reminder object
   * @param {Date} currentTime - the time to use as a base for the reminder
   * @param {integer} hour - the hour to set the reminder for
   * @param {integer} [minute] - the minute to set the reminder for
   * @returns {Object} a Timeline Pin reminder object
   */
  var createReminder = function(currentTime, hour, minute) {
    var time = new Date(+currentTime);
    time.setHours(hour);
    time.setMinutes(minute || 0);
    return {
      "time": time.toISOString(),
      "layout": {
        "type": "genericReminder",
        "tinyIcon": "system://images/NOTIFICATION_REMINDER",
        "title": "Reminder to log your mood"
      }
    };
  };

  /**
   * Constructs a new MoodTracker Pin
   * @class Pin
   * @memberof MoodTracker
   * @param {Date} time - the time to use for the pin
   * @param {integer} duration - the duration of the pin in seconds
   * @param {integer[]} reminders - the list of reminder hours for the pin
   */
  var MoodTrackerPin = function(time, duration, reminders) {
    this.time = time;
    this.duration = duration;
    this.id = generatePinId(time);
    this.reminders = reminders || [10, 14, 18, 22];
    this.debug = false;
  };

  /**
   * Creates a Timeline Pin
   * @returns {Object} a Timeline Pin object
   */
  MoodTrackerPin.prototype.getPin = function() {
    var time = this.time,
        reminders = this.reminders,
        l = reminders.length,
        i = 0,
        pin = createPin(time, this.duration);
    delete pin.createNotification;
    for (i = 0; i < l; i++) {
      pin.reminders.push(createReminder(time, reminders[i]));
    }
    return pin;
  };
  /**
   * Inserts the MoodTracker pin to the timeline
   * @fires MoodTracker.Pin#onInserted
   * @returns {MoodTracker.Pin} itself (allows chaining)
   */
  MoodTrackerPin.prototype.insertPin = function() {
    if (!this.onInserted) {
      this.onInserted = function() {};
    }
    var pin = this.getPin();
    if (this.debug) {
      console.log('Inserting pin in the future: ' + JSON.stringify(pin));
    }
    insertUserPin(pin, this.onInserted);
    return this;
  };

  return {
    /**
     * @lends MoodTracker.Pin
     */
    Pin : (function() {
      return MoodTrackerPin;
    })(),
    /**
     * Creates a Timeline Pin
     * @memberof MoodTracker
     * @param {Date} time - the time to use for the pin
     * @param {integer} duration - the duration of the pin in seconds
     * @returns {object} a Timeline Pin object
     */
    createPin : function(time, duration) {
      var pin = createPin(time, duration),
          reminderTime = new Date(),
          reminderHour = reminderTime.getHours();
      //var reminderHour = time.getHours();
      //pin.reminders.push(createReminder(reminderHour));
      pin.reminders.push(createReminder(time, 10));
      pin.reminders.push(createReminder(time, 14));
      pin.reminders.push(createReminder(time, 18));
      pin.reminders.push(createReminder(time, 22));
      if(Pebble.getActiveWatchInfo) {
        // Available for use!
        var watch = Pebble.getActiveWatchInfo();
        console.log(watch.model);
        if (watch.model.indexOf("qemu") >= 0) {
          /*for (var i = reminderHour; i < reminderHour+1; i++) {
            for (var j = 0; j < 59; j+=5) {
              pin.reminders.push(createReminder(time, i, j));
            }
          }*/
        }
      } else {
        // Not available, handle gracefully

      }

      return pin;
    }
  };
})();

var NUM_DAYS = 7; // Number of days to create pins for
var HOURS = [10, 14, 18, 22]; // Hours to create pins for

Pebble.addEventListener('ready', function() {
  console.log('PebbleKit JS ready!');

  // Load Reminder settings from localStorage on the mobile device
  // Or use default values of nothing is stored.

  NUM_DAYS = localStorage[reminderDaysSetting] || NUM_DAYS;
  HOURS = localStorage[reminderHoursSetting] ?
    localStorage[reminderHoursSetting].split(",").map(Number)
    : HOURS;

  var time = new Date(),
      duration = 3600, // 1 hour
      times = [],
      hours = HOURS, //[10, 14, 18, 22],
      _time = null,
      i = 0,
      j = 0,
      length = 0,
      completed = 0,
      results = null,
      pin = null;
  time.setHours(10);
  time.setMinutes(0);
  for (i = 0; i < NUM_DAYS; i++) {
    for (j = 0; j < hours.length; j++) {
      _time = new Date(+time);
      _time.setDate(_time.getDate() + i);
      _time.setHours(hours[j]);
      console.log(_time.toString());
      times.push(_time);
    }
  }
  length = times.length;
  results = new Array(length);
  pin = new MoodTracker.Pin(times[0], duration, [times[0].getHours()]);
  pin.onInserted = function(responseText) {
    // Insert another Timeline Pin after the previous as been inserted

    // Add the response from the inserted pin to results
    results[completed] = responseText;
    if (++completed === length) {
      // After all Pins has been inserted log the combined results
      console.log('Result: ' + results.join(' ; '));
    } else {
      // If there are still Pins that need to be inserted, use the same
      // MoodTracker Pin object, but set the Pin time and remnders of the
      // next Pin in line
      pin.time = times[completed];
      pin.reminders = [times[completed].getHours()];
      // Re-set the MoodTracker Pin object to the MoodTracker Pin object
      // returned when inserting the Pin to the Timeline
      pin = pin.insertPin();
      // Because we are using the same MoodTracker Pin object, this same
      // "onInserted" event callback will be called when this Pin has been
      // inserted to the Timeline
    }
  };
  //onsole.log('Inserting pin in the future: ' + JSON.stringify(pin));

  // Insert for today
  pin = pin.insertPin();
});

/******************************* timeline lib *********************************/

// The timeline public URL root
var API_URL_ROOT = 'https://timeline-api.getpebble.com/';

/**
 * Send a request to the Pebble public web timeline API.
 * @param pin The JSON pin to insert. Must contain 'id' field.
 * @param type The type of request, either PUT or DELETE.
 * @param callback The callback to receive the responseText after the request has completed.
 */
function timelineRequest(pin, type, callback) {
  // User or shared?
  var url = API_URL_ROOT + 'v1/user/pins/' + pin.id;

  // Create XHR
  var xhr = new XMLHttpRequest();
  xhr.onload = function () {
    console.log('timeline: response received: ' + this.responseText);
    callback(this.responseText);
  };
  xhr.open(type, url);

  // Get token
  Pebble.getTimelineToken(function(token) {
    // Add headers
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.setRequestHeader('X-User-Token', '' + token);

    // Send
    xhr.send(JSON.stringify(pin));
    console.log('timeline: request sent.');
  }, function(error) { console.log('timeline: error getting timeline token: ' + error); });
}

/**
 * Insert a pin into the timeline for this user.
 * @param pin The JSON pin to insert.
 * @param callback The callback to receive the responseText after the request has completed.
 */
function insertUserPin(pin, callback) {
  timelineRequest(pin, 'PUT', callback);
}

/**
 * Delete a pin from the timeline for this user.
 * @param pin The JSON pin to delete.
 * @param callback The callback to receive the responseText after the request has completed.
 */
function deleteUserPin(pin, callback) {
  timelineRequest(pin, 'DELETE', callback);
}

/***************************** end timeline lib *******************************/

/* EXAMPLE JSON PIN

{
  "id": "moodtracker-453923",
  "time": "2015-07-22T00:00:00Z",
  "duration": 1440,
  "createNotification": {
    "layout": {
      "type": "genericNotification",
      "title": "New Item",
      "tinyIcon": "system://images/NOTIFICATION_FLAG",
      "body": "A new appointment has been added to your calendar at 4pm."
    }
  },
  "layout": {
    "title": "Mood Tracker",
    "type": "genericPin",
    "tinyIcon": "system://images/TIMELINE_CALENDAR",
    "body": "Remember to log your mood."
  },
  "reminders": [
    {
      "time": "2015-07-22T08:00:00Z",
      "layout": {
        "type": "genericReminder",
        "tinyIcon": "system://images/TIMELINE_CALENDAR",
        "title": "Reminder to log your mood"
      }
    },
    {
      "time": "2015-07-22T12:00:00Z",
      "layout": {
        "type": "genericReminder",
        "tinyIcon": "system://images/TIMELINE_CALENDAR",
        "title": "Reminder to log your mood"
      }
    },
    {
      "time": "2015-07-22T16:00:00Z",
      "layout": {
        "type": "genericReminder",
        "tinyIcon": "system://images/TIMELINE_CALENDAR",
        "title": "Reminder to log your mood"
      }
    },
    {
      "time": "2015-07-22T20:00:00Z",
      "layout": {
        "type": "genericReminder",
        "tinyIcon": "system://images/TIMELINE_CALENDAR",
        "title": "Reminder to log your mood"
      }
    }
  ],
  "actions": [
    {
      "title": "Log your mood",
      "type": "openWatchApp",
      "launchCode": 15
    },
    {
      "title": "Review moods",
      "type": "openWatchApp",
      "launchCode": 22
    }
  ]
}

*/<|endoftext|>


<commit_before>/* Copyright (c) 2010, Sage Software, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

define('argos/PersistentStorage', [
    'dojo/_base/declare',
    'dojo/_base/lang',
    'dojo/_base/json',
    './convert',
    './utility'
], function(
    declare,
    lang,
    json,
    convert,
    utility
) {
    var sosCache = {};

    return declare('argos.PersistentStorage', null, {

        name: false,
        singleObjectStore: false,
        allowCacheUse: true,
        serializeValues: true,

        constructor: function(options){
            lang.mixin(this, options);
        },
        formatQualifiedKey: function(name, key) {
            if (key && key.indexOf(name) !== 0)
                return name + '.' + key;
            return key;
        },
        serializeValue: function(value) {
            return typeof value === 'object'
                ? json.toJson(value)
                : value && value.toString
                    ? value.toString()
                    : value;
        },
        deserializeValue: function(value) {
            if (value && value.indexOf('{') === 0 && value.lastIndexOf('}') === (value.length - 1))
                return json.fromJson(value);
            if (value && value.indexOf('[') === 0 && value.lastIndexOf(']') === (value.length - 1))
                return json.fromJson(value);
            if (convert.isDateString(value))
                return convert.toDateFromString(value);
            if (/^(true|false)$/.test(value))
                return value === 'true';
            var numeric = parseFloat(value);
            if (!isNaN(numeric))
                return numeric;

            return value;
        },
        getItem: function(key, options) {
            options = options || {};
            try
            {
                if (window.localStorage)
                {
                    if (this.singleObjectStore)
                    {
                        var encoded,
                            store;

                        if (this.allowCacheUse && sosCache[this.name])
                        {
                            store = sosCache[this.name];
                        }
                        else
                        {
                            encoded = window.localStorage.getItem(this.name);
                            store = json.fromJson(encoded);

                            if (this.allowCacheUse) sosCache[this.name] = store;
                        }

                        var value = utility.getValue(store, key);

                        if (options.success)
                            options.success.call(options.scope || this, value);

                        return value;
                    }
                    else
                    {
                        var fqKey = this.formatQualifiedKey(this.name, key),
                            serialized = window.localStorage.getItem(fqKey),
                            value = this.serializeValues && options.serialize !== false
                                ? this.deserializeValue(serialized)
                                : serialized;

                        if (options.success)
                            options.success.call(options.scope || this, value);

                        return value;
                    }
                }
                else
                {
                    if (options.failure)
                        options.failure.call(options.scope || this, false);
                }
            }
            catch (e)
            {
                if (options && options.failure)
                    options.failure.call(options.scope || this, e);
            }
        },
        setItem: function(key, value, options) {
            options = options || {};
            try
            {
                if (window.localStorage)
                {
                    if (this.singleObjectStore)
                    {
                        var encoded,
                            store;

                        if (this.allowCacheUse && sosCache[this.name])
                        {
                            store = sosCache[this.name];
                        }
                        else
                        {
                            encoded = window.localStorage.getItem(this.name);
                            store = (encoded && json.fromJson(encoded)) || {};

                            if (this.allowCacheUse) sosCache[this.name] = store;
                        }

                        utility.setValue(store, key, value);

                        encoded = json.toJson(store);

                        window.localStorage.setItem(this.name, encoded);

                        if (options.success)
                            options.success.call(options.scope || this);

                        return true;
                    }
                    else
                    {
                        var fqKey = this.formatQualifiedKey(this.name, key),
                            serialized = this.serializeValues && options.serialize !== false
                                ? this.serializeValue(value)
                                : value;

                        window.localStorage.setItem(fqKey, serialized);

                        if (options.success)
                            options.success.call(options.scope || this);

                        return true;
                    }
                }
                else
                {
                    if (options.failure)
                        options.failure.call(options.scope || this, false);

                    return false;
                }
            }
            catch (e)
            {
                if (options && options.failure)
                    options.failure.call(options.scope || this, e);

                return false;
            }
        },
        clearItem: function(key, options) {
        }
    });
});<commit_msg>Removed PersistentStorage as it is unused<commit_after><|endoftext|>


<commit_before>var _ = require('lodash');
var utils = require('../lib/utils');

// module.exports = {
//   randomMove,
//   getDirection,
//   isVisible,
//   canKill,
//   safeRandomMove,
//   fastGetDirection,
//   turn,
//   getDistance
// };

utils.canKillSafe = (currentPlayerState = {}, enemiesStates = []) => {
    return enemiesStates.some((enemyObject) => {
        return (enemyObject.isAlive && enemyObject.ammo > 0 && utils.isVisible(currentPlayerState.position, enemyObject.position, currentPlayerState.direction));
    });
};

var gridSize, topLeft, topRight, bottomLeft, bottomRight, lastIndex, corners;
var init = function(gameEnvironment) {
    gridSize = gameEnvironment.gridSize;
    lastIndex = gridSize - 1;
    topLeft = [0, 0];
    topRight = [0, lastIndex];
    bottomLeft = [lastIndex, 0];
    bottomRight = [lastIndex, lastIndex];
    corners = [topLeft, topRight, bottomLeft, bottomRight];
};

var eql = function(pos1, pos2) {
    return pos1[0] == pos2[0] && pos1[1] == pos2[1];
}

module.exports = function() {
    var goToClosestAmmo = function(playerState, enemiesStates, gameEnvironment) {
        var closestAmmo = _.sortBy(gameEnvironment.ammoPosition, function(ammoPosition) {
            return utils.getDistance(playerState.position, ammoPosition);
        })[0];

        var direction = utils.getDirection(playerState.position, closestAmmo);
        if (playerState.direction != direction) {
            return direction;
        }

        return 'move';
    };

    var getNextPosition = function(playerState) {
        switch (playerState.direction) {
            case 'north':
                return [playerState[0], playerState[1] + 1];
            case 'west':
                return [playerState[0] - 1, playerState[1]];
            case 'south':
                return [playerState[0], playerState[1] - 1];
            case 'east':
                return [playerState[0] + 1, playerState[1]];
        }
    };

    var getWillBeKilled = function(playerState, enemiesStates) {
        var nextPosition = getNextPosition(playerState);

        return _.some(enemiesStates, function(enemyState) {
            return utils.canKillSafe(enemyState, [{
                direction: playerState.direction,
                position: nextPosition
            }]);
        });
    };

    var getClosestCorner = function(playerState, gameEnvironment) {
        var gridSize = gameEnvironment.gridSize;
        var corners = [
            [0, 0],
            [0, gridSize - 1],
            [gridSize - 1, gridSize - 1],
            [gridSize - 1, 0]
        ];

        var minDis = null;
        var closestCorner = null;

        for (var i in corners) {
            var corner = corners[i];
            var distance = utils.getDistance(playerState.position, corner);
            if (distance < minDis || minDis == null) {
                minDis = distance;
                closestCorner = corner;
            }
        }

        var direction = utils.getDirection(playerState.position, closestCorner);
        if (playerState.direction == direction) {
            return 'move';
        }

        return direction;
    };

    var atCorner = function(playerState, gameEnvironment) {
        var gridSize = gameEnvironment.gridSize;
        var corners = [
            [0, 0],
            [0, gridSize - 1],
            [gridSize - 1, gridSize - 1],
            [gridSize - 1, 0]
        ];

        return _.some(corners, function(corner) {
            return playerState.position[0] == corner[0] && playerState.position[1] == corner[1];
        });
    };

    var comeAtMeBro = function(playerState, gameEnvironment) {
        if (eql(playerState.position, topLeft)) {
            return playerState.direction == 'south' ? 'east' : 'south';
        } else if (eql(playerState.position, topRight)) {
            return playerState.direction == 'south' ? 'west' : 'south';
        } else if (eql(playerState.position, bottomLeft)) {
            return playerState.direction == 'north' ? 'east' : 'north';
        } else if (eql(playerState.position, bottomRight)) {
            return playerState.direction == 'north' ? 'west' : 'north';
        }

        throw "not at corner";
    };

    return {
        info: {
            name: 'will3',
            style: 1 // one of the 6 styles (0 to 5)
        },

        ai: function(playerState, enemiesStates, gameEnvironment) {
            init(gameEnvironment);

            if (utils.canKill(playerState, enemiesStates)) {
                return 'shoot';
            }

            var canMove = !getWillBeKilled(playerState, enemiesStates);

            if (!canMove) {
                return utils.randomMove();
                while (true) {
                    var move = utils.randomMove();
                }
            }

            var move = utils.randomMove();
            if (playerState.ammo == 0 && gameEnvironment.ammoPosition.length > 0) {
                return goToClosestAmmo(playerState, enemiesStates, gameEnvironment);
            }

            if (!atCorner(playerState, gameEnvironment)) {
                return getClosestCorner(playerState, gameEnvironment);
            }

            return comeAtMeBro(playerState, gameEnvironment);
        }
    }
}();<commit_msg>will3<commit_after>var _ = require('lodash');
var utils = require('../lib/utils');

// module.exports = {
//   randomMove,
//   getDirection,
//   isVisible,
//   canKill,
//   safeRandomMove,
//   fastGetDirection,
//   turn,
//   getDistance
// };

utils.canKillSafe = (currentPlayerState = {}, enemiesStates = []) => {
    return enemiesStates.some((enemyObject) => {
        return (enemyObject.isAlive && enemyObject.ammo > 0 && utils.isVisible(currentPlayerState.position, enemyObject.position, currentPlayerState.direction));
    });
};

var gridSize, topLeft, topRight, bottomLeft, bottomRight, lastIndex, corners;
var init = function(gameEnvironment) {
    gridSize = gameEnvironment.gridSize;
    lastIndex = gridSize - 1;
    topLeft = [0, 0];
    topRight = [0, lastIndex];
    bottomLeft = [lastIndex, 0];
    bottomRight = [lastIndex, lastIndex];
    corners = [topLeft, topRight, bottomLeft, bottomRight];
};

var eql = function(pos1, pos2) {
    return pos1[0] == pos2[0] && pos1[1] == pos2[1];
}

module.exports = function() {
    var goToClosestAmmo = function(playerState, enemiesStates, gameEnvironment) {
        var closestAmmo = _.sortBy(gameEnvironment.ammoPosition, function(ammoPosition) {
            return utils.getDistance(playerState.position, ammoPosition);
        })[0];

        var direction = utils.getDirection(playerState.position, closestAmmo);
        if (playerState.direction != direction) {
            return direction;
        }

        return 'move';
    };

    var getNextPosition = function(playerState) {
        switch (playerState.direction) {
            case 'north':
                return [playerState[0], playerState[1] + 1];
            case 'west':
                return [playerState[0] - 1, playerState[1]];
            case 'south':
                return [playerState[0], playerState[1] - 1];
            case 'east':
                return [playerState[0] + 1, playerState[1]];
        }
    };

    var getWillBeKilled = function(playerState, enemiesStates) {
        var nextPosition = getNextPosition(playerState);

        return _.some(enemiesStates, function(enemyState) {
            return utils.canKillSafe(enemyState, [{
                direction: playerState.direction,
                position: nextPosition
            }]);
        });
    };

    var getClosestCorner = function(playerState, gameEnvironment) {
        var gridSize = gameEnvironment.gridSize;
        var corners = [
            [0, 0],
            [0, gridSize - 1],
            [gridSize - 1, gridSize - 1],
            [gridSize - 1, 0]
        ];

        var minDis = null;
        var closestCorner = null;

        for (var i in corners) {
            var corner = corners[i];
            var distance = utils.getDistance(playerState.position, corner);
            if (distance < minDis || minDis == null) {
                minDis = distance;
                closestCorner = corner;
            }
        }

        var direction = utils.getDirection(playerState.position, closestCorner);
        if (playerState.direction == direction) {
            return 'move';
        }

        return direction;
    };

    var atCorner = function(playerState, gameEnvironment) {
        var gridSize = gameEnvironment.gridSize;
        var corners = [
            [0, 0],
            [0, gridSize - 1],
            [gridSize - 1, gridSize - 1],
            [gridSize - 1, 0]
        ];

        return _.some(corners, function(corner) {
            return playerState.position[0] == corner[0] && playerState.position[1] == corner[1];
        });
    };

    var comeAtMeBro = function(playerState, gameEnvironment) {
        if (eql(playerState.position, topLeft)) {
            return playerState.direction == 'south' ? 'east' : 'south';
        } else if (eql(playerState.position, topRight)) {
            return playerState.direction == 'south' ? 'west' : 'south';
        } else if (eql(playerState.position, bottomLeft)) {
            return playerState.direction == 'north' ? 'east' : 'north';
        } else if (eql(playerState.position, bottomRight)) {
            return playerState.direction == 'north' ? 'west' : 'north';
        }

        throw "not at corner";
    };

    return {
        info: {
            name: 'will3',
            style: 1 // one of the 6 styles (0 to 5)
        },

        ai: function(playerState, enemiesStates, gameEnvironment) {
            init(gameEnvironment);

            if (utils.canKill(playerState, enemiesStates) && playerState.ammo > 0) {
                return 'shoot';
            }

            var canMove = !getWillBeKilled(playerState, enemiesStates);

            if (!canMove) {
                return utils.randomMove();
            }

            var move = utils.randomMove();
            if (playerState.ammo == 0 && gameEnvironment.ammoPosition.length > 0) {
                return goToClosestAmmo(playerState, enemiesStates, gameEnvironment);
            }

            if (!atCorner(playerState, gameEnvironment)) {
                return getClosestCorner(playerState, gameEnvironment);
            }

            return comeAtMeBro(playerState, gameEnvironment);
        }
    }
}();<|endoftext|>