<commit_before>// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('views/view') ;
require('views/label') ;
require('mixins/control') ;

// Constants
SC.TOGGLE_BEHAVIOR = 'toggle';
SC.PUSH_BEHAVIOR =   'push';
SC.TOGGLE_ON_BEHAVIOR = "on";
SC.TOGGLE_OFF_BEHAVIOR = "off" ;

/** @class

  A button handles simple link functions.  It can be set to a selected,
  enabled or disabled state.

  @extends SC.View
  @extends SC.Control
  @author Charles Jolley
  @version 1.0

*/
SC.ButtonView = SC.View.extend(SC.Control,
/** @scope SC.ButtonView.prototype */ {

  emptyElement: '',

  // PROPERTIES

  /**
    Used to automatically update the state of the button view for toggle style
    buttons.

    for toggle style buttons, you can set the value and it will be used to
    automatically update the state of the button view.  The value will also
    change as the user selects to deselects.  The button will make its best
    effort to convert this value into a reasonable selection state:

    null, false, 0 -> isSelected = false
    any other single value -> isSelected = true
    array -> if all values are the same state: that state.  otherwise MIXED.

    @type Object

  */
  value: false,

  /**
    Value of a selected toggle button.

    for a toggle button, set this to any object value you want.  The button
    will be selected if the value property equals the targetValue.  If the
    value is an array of multiple items that contains the targetValue, then
    the button will be set to a mixed state.

    default is false

    @type Object
  */
  toggleOnValue: true,

  /**
    Value of an unselected toggle button.

    For a toggle button, set this to any object value you want.  When the
    user toggle's the button off, the value of the button will be set to this
    value.

    default is false

    @type Object

  */
  toggleOffValue: false,

  /**
    optionally set this to the theme you want this button to have.

    This is used to determine the type of button this is.  You generally
    should set a class name on the HTML with the same value to allow CSS
    styling.

    The default SproutCore theme supports "regular", "checkbox", and "radio"
  */
  theme: 'regular',

  /**
    Optionally set the behavioral mode of this button.

    Possible values are:

    {{{
     SC.PUSH_BEHAVIOR: ('push')
       Pressing the button will trigger an action tied to the button. Does
       not change the value of the button.

     SC.TOGGLE_BEHAVIOR: ('toggle')
       Pressing the button will invert the current value of the button. If
       the button has a mixed value, it will be set to true.

     SC.TOGGLE_ON_BEHAVIOR: ('on')
       Pressing the button will set the current state to true no matter the
       previous value.

     SC.TOGGLE_OFF_BEHAVIOR: ('off')
       Pressing the button will set the current state to false no matter the
       previous value.
    }}}

  */
  buttonBehavior: SC.PUSH_BEHAVIOR,

  /**
    If NO the button will be disabled.

    @type Bool
  */
  isEnabled: YES,

  /**
    button's selection state.  Returns YES, NO, or SC.MIXED_STATE
  */
  isSelected: NO,

  /**
    If YES, then this button will be triggered when you hit return.

    This is the same as setting the keyEquivalent to 'return'.  This will also
    apply the "def" classname to the button.
  */
  isDefault: NO,
  isDefaultBindingDefault: SC.Binding.OneWayBool,

  /**
    If YES, then this button will be triggered when you hit escape.

    This is the same as setting the keyEquivalent to 'escape'.
  */
  isCancel: NO,
  isCancelBindingDefault: SC.Binding.OneWayBool,

  /**
    If YES, then the title will be localized.
  */
  localize: NO,

  /**
    The selector path to the element that contains the button title.

    This property is only used if you try to get or set the title property.
  */
  titleSelector: '.label',

  /**
    The button title.

    This property is observable and bindable.

    @field {String}
  */
  title: function(key, value) {

    // set the value of the label text.  Possibly localize and set innerHTML.
    if (value !== undefined) {
      if (this._title != value) {
        var text = this._title = value ;
        var lsel = this.get('titleSelector') ;
        var el = (lsel) ? this.$sel(lsel) : this.rootElement ;

        if (this.get('localize')) text = text.loc() ;
        el.innerHTML = text ;
      }
    }

    // lazily fetch the label text.
    if (!this._title) {
      var el = this.$sel(this.get('titleSelector')) ;
      this._title = (el) ? el.innerHTML : '' ;
    }
    return this._title ;
  }.property(),

  /**
    The name of the action you want triggered when the button is pressed.

    This property is used in conjunction with the target property to execute
    a method when a regular button is pressed.  These properties are not
    relevant when the button is used in toggle mode.

    If you do not set a target, then pressing a button will cause the
    responder chain to search for a view that implements the action you name
    here.  If you set a target, then the button will try to call the method
    on the target itself.

    For legacy support, you can also set the action property to a function.
    Doing so will cause the function itself to be called when the button is
    clicked.  It is generally better to use the target/action approach and
    to implement your code in a controller of some type.

    @type String
  */
  action: null,

  /**
    The target object to invoke the action on when the button is pressed.

    If you set this target, the action will be called on the target object
    directly when the button is clicked.  If you leave this property set to
    null, then the button will search the responder chain for a view that
    implements the action when the button is pressed instead.

    @type Object
  */
  target: null,

  /**
    The key equivalent that should trigger this button on the page.
  */
  keyEquivalent: null,

  /** @private {String} used to store a previously defined key equiv */
  _lastKeyEquivalent: null,

  performKeyEquivalent: function( keystring, evt )
  {
    if (!this.get('isEnabled')) return false;

    var keyEquivalent = this.get('keyEquivalent');
    if (keyEquivalent && (keyEquivalent == keystring))
    {
      // button has defined a keyEquivalent and it matches!
      // if triggering succeeded, true will be returned and the operation will be handeled
      // (i.e performKeyEquivalent will cease crawling the view tree)
      return this.triggerAction(evt);
    }
    return false;
  },

  /**
    fakes a click... evt is optional.

    Temporarily highlights the button to show that it is being triggered.
    Does nothing if the button is disabled.

    @returns {bool} success/failure of the request
  */
  triggerAction: function(evt) {
    if (!this.get('isEnabled')) return false;
    this.setClassName('active', true);
    this.didTriggerAction();
    this._action(evt);
    this.invokeLater('setClassName', 200, 'active', false) ;
    return true;
  },

  didTriggerAction: function() {},

  // ................................................................
  // INTERNAL SUPPORT

  /** @private */
  init: function() {
    arguments.callee.base.call(this) ;

    // setup initial CSS clases
    this._isDefaultOrCancelObserver() ;

    // If we need to localze, handle it...
    var el ;
    var sel = this.get('titleSelector') ;
    if (this.get('localize') && sel && (el = this.$sel(sel))) {
      this._title = (el.innerHTML || '').strip() ;
      el.innerHTML = this._title.loc() ;
    }
  },

  // determines the target selected state
  _selectedStateFromValue: function(value) {
    var targetValue = this.get('toggleOnValue') ;
    var state ;

    if ($type(value) == T_ARRAY) {
      if (value.length == 1) {
        state = (value[0] == targetValue) ;
      } else {
        state = (value.indexOf(targetValue) >= 0) ? SC.MIXED_STATE : false ;
      }
    } else {
      state = (value == targetValue) ;
    }
    return state ;
  },

  /** @private */
  propertyObserver: function(observing,target,key,value) {
    if (target != this) return ;

    // handle changes to the value
    switch(key) {

      // determine the new selection state.
      case 'value':
        value = this.get('value') ;
        if (value == this._value) return ; // process value one time.
        this._value = value ;

        // set the new selected state if it does not match
        var state = this._selectedStateFromValue(value) ;
        this.setIfChanged('isSelected', state) ;
        break ;

      // forward to value if needed.
      case 'isSelected':
        var newState = this.get('isSelected') ;
        var curState = this._selectedStateFromValue(this.get('value')) ;
        if (curState != newState) {
          var valueKey = (newState) ? 'toggleOnValue' : 'toggleOffValue' ;
          this.set('value', this.get(valueKey)) ;
        }
        break ;

      // otherwise do nothing
      default:
        break ;
    }
  },

  _isDefaultOrCancelObserver: function() {
    var isDef = !!this.get('isDefault') ;
    var isCancel = !isDef && this.get('isCancel') ;
    if(this.didChangeFor('defaultCancelChanged','isDefault','isCancel')) {
      this.setClassName('def', isDef) ;
      var key = this.get('keyEquivalent') ;
      if (isDef) {
        //cache the previously defined key equivalent
        this._lastKeyEquivalent = key;
        this.setIfChanged('keyEquivalent', 'return');
      }
      else if (isCancel)
      {
        //cache the previously defined key equivalent
        this._lastKeyEquivalent = key;
        this.setIfChanged('keyEquivalent', 'escape') ;
      }
      else
      {
        this.setIfChanged("keyEquivalent",this._lastKeyEquivalent);
      }
    }

  }.observes('isDefault', 'isCancel'),

  // on mouse down, set active only if enabled.
  /** @private */
  mouseDown: function(evt) {
    this.setClassName('active',this.get('isEnabled')) ;
    return true ;
  },

  // on mouse up, trigger the action only if we are enabled and the mouse
  // was released inside the view.
  /** @private */
  mouseUp: function(evt) {
    this.setClassName('active', false) ;

    var tgt = Event.element(evt) ;
    var inside = false ;
    while(tgt && (tgt != this.rootElement)) tgt = tgt.parentNode;
    if (tgt == this.rootElement) inside = true ;

    if (inside && this.get('isEnabled')) this._action(evt) ;
    return true ;
  },

  // perform action depending on the behavior of the button.
  _action: function(evt) {
    switch(this.get('buttonBehavior')) {

    // When toggling, try to invert like values. i.e. 1 => 0, etc.
    case SC.TOGGLE_BEHAVIOR:
      var sel = this.get('isSelected') ;
      if (sel == true) {
        this.set('value', this.get('toggleOffValue')) ;
      } else {
        this.set('value', this.get('toggleOnValue')) ;
      }
      break ;

    // set value to on.  change 0 => 1.
    case SC.TOGGLE_ON_BEHAVIOR:
      this.set('value', this.get('toggleOnValue')) ;
      break ;

    // set the value to false. change 1 => 0
    case SC.TOGGLE_OFF_BEHAVIOR:
      this.set('value', this.get('toggleOffValue')) ;
      break ;

    // otherwise, just trigger an action if there is one.
    default:
      //if (this.action) this.action(evt);
      var action = this.get('action');
      var target = this.get('target') || null;
      if (action)
      {
        if (this._hasLegacyActionHandler()) {
          // old school...
          this._triggerLegacyActionHandler(evt);
        } else {
          // newer action method + optional target syntax...
          SC.app.sendAction(action, target, this);
        }
      }
    }
  },

  /** @private */
  _hasLegacyActionHandler: function()
  {
    var action = this.get('action');
    if (action && ($type(action) == T_FUNCTION)) return true;
    if (action && ($type(action) == T_STRING) && (action.indexOf('.') != -1)) return true;
    return false;
  },

  /** @private */
  _triggerLegacyActionHandler: function( evt )
  {
    if (!this._hasLegacyActionHandler()) return false;

    var action = this.get('action');
    if ($type(action) == T_FUNCTION) this.action(evt);
    if ($type(action) == T_STRING)
    {
      eval("this.action = function(e) { return "+ action +"(this, e); };");
      this.action(evt);
    }
  }

}) ;
<commit_msg>Merging in fix for <rdar://problem/5917267> 5/27: Keyboard shortcut for Delete doesn't work .  <commit_after>// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('views/view') ;
require('views/label') ;
require('mixins/control') ;

// Constants
SC.TOGGLE_BEHAVIOR = 'toggle';
SC.PUSH_BEHAVIOR =   'push';
SC.TOGGLE_ON_BEHAVIOR = "on";
SC.TOGGLE_OFF_BEHAVIOR = "off" ;

/** @class

  A button handles simple link functions.  It can be set to a selected,
  enabled or disabled state.

  @extends SC.View
  @extends SC.Control
  @author Charles Jolley
  @version 1.0

*/
SC.ButtonView = SC.View.extend(SC.Control,
/** @scope SC.ButtonView.prototype */ {

  emptyElement: '',

  // PROPERTIES

  /**
    Used to automatically update the state of the button view for toggle style
    buttons.

    for toggle style buttons, you can set the value and it will be used to
    automatically update the state of the button view.  The value will also
    change as the user selects to deselects.  The button will make its best
    effort to convert this value into a reasonable selection state:

    null, false, 0 -> isSelected = false
    any other single value -> isSelected = true
    array -> if all values are the same state: that state.  otherwise MIXED.

    @type Object

  */
  value: false,

  /**
    Value of a selected toggle button.

    for a toggle button, set this to any object value you want.  The button
    will be selected if the value property equals the targetValue.  If the
    value is an array of multiple items that contains the targetValue, then
    the button will be set to a mixed state.

    default is false

    @type Object
  */
  toggleOnValue: true,

  /**
    Value of an unselected toggle button.

    For a toggle button, set this to any object value you want.  When the
    user toggle's the button off, the value of the button will be set to this
    value.

    default is false

    @type Object

  */
  toggleOffValue: false,

  /**
    optionally set this to the theme you want this button to have.

    This is used to determine the type of button this is.  You generally
    should set a class name on the HTML with the same value to allow CSS
    styling.

    The default SproutCore theme supports "regular", "checkbox", and "radio"
  */
  theme: 'regular',

  /**
    Optionally set the behavioral mode of this button.

    Possible values are:

    {{{
     SC.PUSH_BEHAVIOR: ('push')
       Pressing the button will trigger an action tied to the button. Does
       not change the value of the button.

     SC.TOGGLE_BEHAVIOR: ('toggle')
       Pressing the button will invert the current value of the button. If
       the button has a mixed value, it will be set to true.

     SC.TOGGLE_ON_BEHAVIOR: ('on')
       Pressing the button will set the current state to true no matter the
       previous value.

     SC.TOGGLE_OFF_BEHAVIOR: ('off')
       Pressing the button will set the current state to false no matter the
       previous value.
    }}}

  */
  buttonBehavior: SC.PUSH_BEHAVIOR,

  /**
    If NO the button will be disabled.

    @type Bool
  */
  isEnabled: YES,

  /**
    button's selection state.  Returns YES, NO, or SC.MIXED_STATE
  */
  isSelected: NO,

  /**
    If YES, then this button will be triggered when you hit return.

    This is the same as setting the keyEquivalent to 'return'.  This will also
    apply the "def" classname to the button.
  */
  isDefault: NO,
  isDefaultBindingDefault: SC.Binding.OneWayBool,

  /**
    If YES, then this button will be triggered when you hit escape.

    This is the same as setting the keyEquivalent to 'escape'.
  */
  isCancel: NO,
  isCancelBindingDefault: SC.Binding.OneWayBool,

  /**
    If YES, then the title will be localized.
  */
  localize: NO,

  /**
    The selector path to the element that contains the button title.

    This property is only used if you try to get or set the title property.
  */
  titleSelector: '.label',

  /**
    The button title.

    This property is observable and bindable.

    @field {String}
  */
  title: function(key, value) {

    // set the value of the label text.  Possibly localize and set innerHTML.
    if (value !== undefined) {
      if (this._title != value) {
        var text = this._title = value ;
        var lsel = this.get('titleSelector') ;
        var el = (lsel) ? this.$sel(lsel) : this.rootElement ;

        if (this.get('localize')) text = text.loc() ;
        el.innerHTML = text ;
      }
    }

    // lazily fetch the label text.
    if (!this._title) {
      var el = this.$sel(this.get('titleSelector')) ;
      this._title = (el) ? el.innerHTML : '' ;
    }
    return this._title ;
  }.property(),

  /**
    The name of the action you want triggered when the button is pressed.

    This property is used in conjunction with the target property to execute
    a method when a regular button is pressed.  These properties are not
    relevant when the button is used in toggle mode.

    If you do not set a target, then pressing a button will cause the
    responder chain to search for a view that implements the action you name
    here.  If you set a target, then the button will try to call the method
    on the target itself.

    For legacy support, you can also set the action property to a function.
    Doing so will cause the function itself to be called when the button is
    clicked.  It is generally better to use the target/action approach and
    to implement your code in a controller of some type.

    @type String
  */
  action: null,

  /**
    The target object to invoke the action on when the button is pressed.

    If you set this target, the action will be called on the target object
    directly when the button is clicked.  If you leave this property set to
    null, then the button will search the responder chain for a view that
    implements the action when the button is pressed instead.

    @type Object
  */
  target: null,

  /**
    The key equivalent that should trigger this button on the page.
  */
  keyEquivalent: null,

  /** @private {String} used to store a previously defined key equiv */
  _defaultKeyEquivalent: null,

  performKeyEquivalent: function( keystring, evt )
  {
    if (!this.get('isEnabled')) return false;
    var keyEquivalent = this.get('keyEquivalent');
    if (keyEquivalent && (keyEquivalent == keystring))
    {
      // button has defined a keyEquivalent and it matches!
      // if triggering succeeded, true will be returned and the operation will be handeled
      // (i.e performKeyEquivalent will cease crawling the view tree)
      return this.triggerAction(evt);
    }
    return false;
  },

  /**
    fakes a click... evt is optional.

    Temporarily highlights the button to show that it is being triggered.
    Does nothing if the button is disabled.

    @returns {bool} success/failure of the request
  */
  triggerAction: function(evt) {
    if (!this.get('isEnabled')) return false;
    this.setClassName('active', true);
    this.didTriggerAction();
    this._action(evt);
    this.invokeLater('setClassName', 200, 'active', false) ;
    return true;
  },

  didTriggerAction: function() {},

  // ................................................................
  // INTERNAL SUPPORT

  /** @private */
  init: function() {
    arguments.callee.base.call(this) ;

    //cache the key equivalent
    if(this.get("keyEquivalent")) this._defaultKeyEquivalent = this.get("keyEquivalent");
    // setup initial CSS clases
    this._isDefaultOrCancelObserver() ;

    // If we need to localze, handle it...
    var el ;
    var sel = this.get('titleSelector') ;
    if (this.get('localize') && sel && (el = this.$sel(sel))) {
      this._title = (el.innerHTML || '').strip() ;
      el.innerHTML = this._title.loc() ;
    }
  },

  // determines the target selected state
  _selectedStateFromValue: function(value) {
    var targetValue = this.get('toggleOnValue') ;
    var state ;

    if ($type(value) == T_ARRAY) {
      if (value.length == 1) {
        state = (value[0] == targetValue) ;
      } else {
        state = (value.indexOf(targetValue) >= 0) ? SC.MIXED_STATE : false ;
      }
    } else {
      state = (value == targetValue) ;
    }
    return state ;
  },

  /** @private */
  propertyObserver: function(observing,target,key,value) {
    if (target != this) return ;

    // handle changes to the value
    switch(key) {

      // determine the new selection state.
      case 'value':
        value = this.get('value') ;
        if (value == this._value) return ; // process value one time.
        this._value = value ;

        // set the new selected state if it does not match
        var state = this._selectedStateFromValue(value) ;
        this.setIfChanged('isSelected', state) ;
        break ;

      // forward to value if needed.
      case 'isSelected':
        var newState = this.get('isSelected') ;
        var curState = this._selectedStateFromValue(this.get('value')) ;
        if (curState != newState) {
          var valueKey = (newState) ? 'toggleOnValue' : 'toggleOffValue' ;
          this.set('value', this.get(valueKey)) ;
        }
        break ;

      // otherwise do nothing
      default:
        break ;
    }
  },

  _isDefaultOrCancelObserver: function() {
    var isDef = !!this.get('isDefault') ;
    var isCancel = !isDef && this.get('isCancel') ;

    if(this.didChangeFor('defaultCancelChanged','isDefault','isCancel')) {
      this.setClassName('def', isDef) ;
      if (isDef) {
        this.setIfChanged('keyEquivalent', 'return');
      }
      else if (isCancel)
      {
        this.setIfChanged('keyEquivalent', 'escape') ;
      }
      else
      {
        //restore the default key equivalent
        this.set("keyEquivalent",this._defaultKeyEquivalent);
      }
    }

  }.observes('isDefault', 'isCancel'),

  // on mouse down, set active only if enabled.
  /** @private */
  mouseDown: function(evt) {
    this.setClassName('active',this.get('isEnabled')) ;
    return true ;
  },

  // on mouse up, trigger the action only if we are enabled and the mouse
  // was released inside the view.
  /** @private */
  mouseUp: function(evt) {
    this.setClassName('active', false) ;

    var tgt = Event.element(evt) ;
    var inside = false ;
    while(tgt && (tgt != this.rootElement)) tgt = tgt.parentNode;
    if (tgt == this.rootElement) inside = true ;

    if (inside && this.get('isEnabled')) this._action(evt) ;
    return true ;
  },

  // perform action depending on the behavior of the button.
  _action: function(evt) {
    switch(this.get('buttonBehavior')) {

    // When toggling, try to invert like values. i.e. 1 => 0, etc.
    case SC.TOGGLE_BEHAVIOR:
      var sel = this.get('isSelected') ;
      if (sel == true) {
        this.set('value', this.get('toggleOffValue')) ;
      } else {
        this.set('value', this.get('toggleOnValue')) ;
      }
      break ;

    // set value to on.  change 0 => 1.
    case SC.TOGGLE_ON_BEHAVIOR:
      this.set('value', this.get('toggleOnValue')) ;
      break ;

    // set the value to false. change 1 => 0
    case SC.TOGGLE_OFF_BEHAVIOR:
      this.set('value', this.get('toggleOffValue')) ;
      break ;

    // otherwise, just trigger an action if there is one.
    default:
      //if (this.action) this.action(evt);
      var action = this.get('action');
      var target = this.get('target') || null;
      if (action)
      {
        if (this._hasLegacyActionHandler()) {
          // old school...
          this._triggerLegacyActionHandler(evt);
        } else {
          // newer action method + optional target syntax...
          SC.app.sendAction(action, target, this);
        }
      }
    }
  },

  /** @private */
  _hasLegacyActionHandler: function()
  {
    var action = this.get('action');
    if (action && ($type(action) == T_FUNCTION)) return true;
    if (action && ($type(action) == T_STRING) && (action.indexOf('.') != -1)) return true;
    return false;
  },

  /** @private */
  _triggerLegacyActionHandler: function( evt )
  {
    if (!this._hasLegacyActionHandler()) return false;

    var action = this.get('action');
    if ($type(action) == T_FUNCTION) this.action(evt);
    if ($type(action) == T_STRING)
    {
      eval("this.action = function(e) { return "+ action +"(this, e); };");
      this.action(evt);
    }
  }

}) ;
<|endoftext|>


<commit_before>function array_reverse (array, preserve_keys) {
    // http://kevin.vanzonneveld.net
    // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   improved by: Karol Kowalski
    // *     example 1: array_reverse( [ 'php', '4.0', ['green', 'red'] ], true);
    // *     returns 1: { 2: ['green', 'red'], 1: 4, 0: 'php'}
    var arr_len = array.length,
        newkey = 0,
        tmp_arr = {},
        key = '';
    preserve_keys = !! preserve_keys;

    for (key in array) {
        newkey = arr_len - key - 1;
        tmp_arr[preserve_keys ? key : newkey] = array[key];
    }

    return tmp_arr;
}
<commit_msg>When preserve_keys is falsy, function will return Array instead of Object. Fixed bug where passing object and falsy preserve_keys returned wrong results.<commit_after>function array_reverse (array, preserve_keys) {
    // http://kevin.vanzonneveld.net
    // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   improved by: Karol Kowalski
    // *     example 1: array_reverse( [ 'php', '4.0', ['green', 'red'] ], true);
    // *     returns 1: { 2: ['green', 'red'], 1: 4, 0: 'php'}
    var isArray = Object.prototype.toString.call(array) === "[object Array]",
        tmp_arr = preserve_keys ? {} : [],
        key;

    if (isArray && !preserve_keys) {
        return array.slice(0).reverse();
    }

    if (preserve_keys) {
        var keys = [];
        for (key in array) {
            // if (array.hasOwnProperty(key)) {
            keys.push(key);
            // }
        }

        var i = keys.length;
        while (i--) {
            key = keys[i];
            // FIXME: don't rely on browsers keeping keys in insertion order
            // it's implementation specific
            // eg. the result will differ from expected in Google Chrome
            tmp_arr[key] = array[key];
        }
    } else {
        for (key in array) {
            // if (array.hasOwnProperty(key)) {
            tmp_arr.unshift(array[key]);
            // }
        }
    }

    return tmp_arr;
}
<|endoftext|>


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

    var argv, express, corser, mongo, url, binaryBodyParser, app;

    argv = require("optimist")
             .options("port", {
                 default: 1337,
                 describe: "Port"
             })
             .options("connection-string", {
                 default: "mongodb://localhost/ming",
                 describe: "MongoDB Connection String for the default deployment."
             })
             .options("enable-proxying", {
                 default: false,
                 describe: "Allow connections to other MongoDB deployments."
             })
             .argv;
    express = require("express");
    corser = require("corser");
    mongo = require("mongodb");
    url = require("url");

    binaryBodyParser = function (req, res, next) {
        var body;
        body = new Buffer(0);
        req.on("data", function (chunk) {
            var buffer;
            buffer = new Buffer(body.length + chunk.length);
            body.copy(buffer);
            chunk.copy(buffer, body.length);
            body = buffer;
        });
        req.on("end", function () {
            req.body = body;
            next();
        });
    };

    app = express();

    app.configure(function () {

     // Handle CORS.
        app.use(corser.create({
            methods: corser.simpleMethods.concat(["DELETE", "PATCH"]),
            requestHeaders: corser.simpleRequestHeaders.concat(["Authorization", "X-Connection-String"]),
            responseHeaders: corser.simpleResponseHeaders.concat(["Location"])
        }));

     // Terminate CORS preflights.
        app.use(function (req, res, next) {
            if (req.method === "OPTIONS") {
                res.send(204);
            } else {
                next();
            }
        });

     // Prepare MongoDB client.
        app.use(function (req, res, next) {
            var connectionString;
            if (argv["enable-proxying"] === true && req.headers.hasOwnProperty("x-connection-string") === true) {
                connectionString = req.headers["x-connection-string"];
            } else {
                connectionString = argv["connection-string"];
            }
            connectionString = url.parse(connectionString, true);
         // Keep connection pool small.
            connectionString.query.maxPoolSize = 1;
         // Keep a reference to the connection string for later use.
            req.connectionString = connectionString;
            mongo.MongoClient.connect(url.format(connectionString), function (err, db) {
                if (err !== null) {
                    next(err);
                } else {
                    req.db = db;
                    next();
                }
            });
        });

     // Basic auth.
        app.use(function (req, res, next) {
            var basicAuth;
            basicAuth = express.basicAuth(function (username, password, callback) {
                req.db.authenticate(username, password, function (err) {
                    if (err !== null) {
                     // Close database if authentication fails.
                        req.db.close();
                        callback(err, null);
                    } else {
                        callback(null, username);
                    }
                });
            }, "MongoDB");
            basicAuth(req, res, next);
        });

     // Deploy routes.
        app.use(app.router);

     // 404 handler.
        app.use(function (req, res) {
            req.db.close();
            res.send(404, "Not Found");
        });

     // 500 handler.
        app.use(function (err, req, res, next) {
            if (req.hasOwnProperty("db") === true) {
                req.db.close();
            }
            res.send(500, "Internal Server Error");
        });

    });

    app.get("/", function (req, res) {
        req.db.collectionNames({
            namesOnly: true
        }, function (err, collections) {
            var names;
            if (err !== null) {
                next(err);
            } else {
                names = collections.map(function (collection) {
                 // Strip database name.
                    return collection.substring(req.connectionString.pathname.length);
                });
                res.send({
                    collections: names
                });
                req.db.close();
            }
        });
    });

    app.get("/:collection", function (req, res) {
        var collectionParam;
        collectionParam = req.params.collection;
        req.db.collection(collectionParam, function (err, collection) {
            if (err !== null) {
                next(err);
            } else {
                collection.find().count(function (err, count) {
                    if (err !== null) {
                        next(err);
                    } else {
                        res.send({count: count});
                        req.db.close();
                    }
                });
            }
        });
    });

    app.get("/:prefix.files/:file", function (req, res, next) {
        var prefixParam, fileParam;
        prefixParam = req.params.prefix;
        fileParam = req.params.file;
        req.db.collection(prefixParam + ".files", function (err, collection) {
            var id;
            if (err !== null) {
                next(err);
            } else {
                try {
                    id = new mongo.ObjectID(fileParam);
                 // Get metadata first.
                    collection.findOne({
                        _id: id
                    }, function (err, document) {
                        var grid;
                        if (err !== null) {
                            next(err);
                        } else {
                            if (document === null) {
                             // Route to 404 handler.
                                next();
                            } else {
                                if (req.query.hasOwnProperty("binary") === true && req.query.binary === "true") {
                                    grid = new mongo.Grid(req.db, prefixParam);
                                    grid.get(id, function (err, file) {
                                        var contentType;
                                        if (err !== null) {
                                            next(err);
                                        } else {
                                            if (document.contentType) {
                                                contentType = document.contentType;
                                            } else if (document.filename) {
                                             // Guess file type from file extension.
                                                contentType = document.filename.match(/(\.\w+)$/)[1];
                                            } else {
                                                contentType = "application/octet-stream";
                                            }
                                            res.type(contentType);
                                            res.send(file);
                                            req.db.close();
                                        }
                                    });
                                } else {
                                    res.send(document);
                                    req.db.close();
                                }
                            }
                        }
                    });
                } catch (e) {
                 // Route to 404 handler.
                    next();
                }
            }
        });
    });

    app.get("/:collection/:document", function (req, res, next) {
        var collectionParam, documentParam;
        collectionParam = req.params.collection;
        documentParam = req.params.document;
        req.db.collection(collectionParam, function (err, collection) {
            var id;
            if (err !== null) {
                next(err);
            } else {
                try {
                    id = new mongo.ObjectID(documentParam);
                    collection.findOne({
                        _id: id
                    }, function (err, document) {
                        if (err !== null) {
                            next(err);
                        } else {
                            if (document === null) {
                             // Route to 404 handler.
                                next();
                            } else {
                                res.send(document);
                                req.db.close();
                            }
                        }
                    });
                } catch (e) {
                 // Route to 404 handler.
                    next();
                }
            }
        });
    });

    app.get("/:collection/:document/:field", function (req, res, next) {
        var collectionParam, documentParam, fieldParam;
        collectionParam = req.params.collection;
        documentParam = req.params.document;
        fieldParam = req.params.field;
        req.db.collection(collectionParam, function (err, collection) {
            var id, fields;
            if (err !== null) {
                next(err);
            } else {
                try {
                    id = new mongo.ObjectID(documentParam);
                    fields = {};
                    fields[fieldParam] = 1;
                    collection.findOne({
                        _id: id
                    }, {
                        fields: fields
                    }, function (err, document) {
                        if (err !== null) {
                            next(err);
                        } else {
                            if (document === null || document.hasOwnProperty(fieldParam) === false) {
                             // Route to 404 handler.
                                next();
                            } else {
                                res.send(document[fieldParam]);
                                req.db.close();
                            }
                        }
                    });
                } catch (e) {
                 // Route to 404 handler.
                    next();
                }
            }
        });
    });

    app.post("/:collection/query", express.json(), function (req, res) {
        var collectionParam;
        collectionParam = req.params.collection;
        req.db.collection(collectionParam, function (err, collection) {
            var options;
            if (err !== null) {
                next(err);
            } else {
                options = {};
                if (req.query.limit) {
                    options.limit = req.query.limit;
                }
                if (req.query.skip) {
                    options.skip = req.query.skip;
                }
                if (req.query.sort) {
                    options.sort = req.query.sort;
                }
                collection.find(req.body, options).toArray(function (err, documents) {
                    if (err !== null) {
                        next(err);
                    } else {
                        res.send(documents);
                        req.db.close();
                    }
                });
            }
        });
    });

    app.post("/:prefix.files", binaryBodyParser, function (req, res, next) {
        var prefixParam, contentType, grid;
        prefixParam = req.params.prefix;
        contentType = req.headers["content-type"];
        grid = new mongo.Grid(req.db, prefixParam);
        grid.put(req.body, {
            content_type: contentType
        }, function (err, document) {
            if (err !== null) {
                next(err);
            } else {
                res.location(prefixParam + ".files/" + document._id.toHexString());
                res.send(201, "Created");
                req.db.close();
            }
        });
    });

    app.post("/:collection", express.json(), function (req, res) {
        var collectionParam, payload;
        collectionParam = req.params.collection;
        payload = req.body;
        req.db.collection(collectionParam, function (err, collection) {
            if (err !== null) {
                next(err);
            } else {
                collection.insert(payload, function (err, document) {
                    if (err !== null) {
                        next(err);
                    } else {
                        res.location(collectionParam + "/" + document[0]._id.toHexString());
                        res.send(201, "Created");
                        req.db.close();
                    }
                });
            }
        });
    });

    app.patch("/:collection/:document", express.json(), function (req, res, next) {
        var collectionParam, documentParam;
        collectionParam = req.params.collection;
        documentParam = req.params.document;
        req.db.collection(collectionParam, function (err, collection) {
            var id;
            if (err !== null) {
                next(err);
            } else {
                try {
                    id = new mongo.ObjectID(documentParam);
                    collection.update({
                        _id: id
                    }, req.body, function (err) {
                        if (err !== null) {
                            next(err);
                        } else {
                            res.send(204, "No Content");
                            req.db.close();
                        }
                    });
                } catch (e) {
                 // Route to 404 handler.
                    next();
                }
            }
        });
    });

    app.delete("/:prefix.files/:file", function (req, res, next) {
        var prefixParam, fileParam, grid, id;
        prefixParam = req.params.prefix;
        fileParam = req.params.file;
        grid = new mongo.Grid(req.db, prefixParam);
        try {
            id = new mongo.ObjectID(fileParam);
            grid.delete(id, function (err, flag) {
                if (err !== null) {
                    next(err);
                } else {
                 // TODO: Detect if file was actually deleted.
                    res.send(200, "OK");
                    req.db.close();
                }
            });
        } catch (e) {
         // Route to 404 handler.
            next();
        }
    });

    app.delete("/:collection/:document", function (req, res, next) {
        var collectionParam, documentParam;
        collectionParam = req.params.collection;
        documentParam = req.params.document;
        req.db.collection(collectionParam, function (err, collection) {
            var id;
            if (err !== null) {
                next(err);
            } else {
                try {
                    id = new mongo.ObjectID(documentParam);
                    collection.remove({
                        _id: id
                    }, function (err, num) {
                        if (err !== null) {
                            next(err);
                        } else {
                            if (num === 0) {
                             // Route to 404 handler.
                                next();
                            } else {
                                res.send(200, "Deleted");
                                req.db.close();
                            }
                        }
                    });
                } catch (e) {
                 // Route to 404 handler.
                    next();
                }
            }
        });
    });

    app.listen(argv.port);

    console.log("Ming is running on port " + argv.port + ", connected to " + argv["connection-string"]);

}());
<commit_msg>Close connection if user is unauthorized.<commit_after>(function () {
    "use strict";

    var argv, express, corser, mongo, url, basicAuth, binaryBodyParser, app;

    argv = require("optimist")
             .options("port", {
                 default: 1337,
                 describe: "Port"
             })
             .options("connection-string", {
                 default: "mongodb://localhost/ming",
                 describe: "MongoDB Connection String for the default deployment."
             })
             .options("enable-proxying", {
                 default: false,
                 describe: "Allow connections to other MongoDB deployments."
             })
             .argv;
    express = require("express");
    corser = require("corser");
    mongo = require("mongodb");
    url = require("url");

 // Connect's basicAuth is not flexible enough: it doesn't allow you to
 // overwrite unauthorized, needed to close the connection to MongoDB.
    basicAuth = function (req, res, next) {
        var authorization, parts, scheme, credentials, index, user, pass;
        authorization = req.headers.authorization;
        if (authorization === undefined) {
            req.db.close();
            res.setHeader("WWW-Authenticate", "Basic realm=\"Ming\"");
            res.send(401, "Unauthorized");
        } else {
            parts = authorization.split(" ");
            if (parts.length !== 2) {
                next(new Error());
            } else {
                scheme = parts[0];
                credentials = new Buffer(parts[1], "base64").toString();
                index = credentials.indexOf(":");
                if (scheme !== "Basic" || index < 0) {
                    next(utils.error(400));
                } else {
                    user = credentials.slice(0, index);
                    pass = credentials.slice(index + 1);
                    req.db.authenticate(user, pass, function (err) {
                        if (err !== null) {
                            req.db.close();
                            res.setHeader("WWW-Authenticate", "Basic realm=\"Ming\"");
                            res.send(401, "Unauthorized");
                        } else {
                            next();
                        }
                    });
                }
            }
        }
    };

    binaryBodyParser = function (req, res, next) {
        var body;
        body = new Buffer(0);
        req.on("data", function (chunk) {
            var buffer;
            buffer = new Buffer(body.length + chunk.length);
            body.copy(buffer);
            chunk.copy(buffer, body.length);
            body = buffer;
        });
        req.on("end", function () {
            req.body = body;
            next();
        });
    };

    app = express();

    app.configure(function () {

     // Handle CORS.
        app.use(corser.create({
            methods: corser.simpleMethods.concat(["DELETE", "PATCH"]),
            requestHeaders: corser.simpleRequestHeaders.concat(["Authorization", "X-Connection-String"]),
            responseHeaders: corser.simpleResponseHeaders.concat(["Location"])
        }));

     // Terminate CORS preflights.
        app.use(function (req, res, next) {
            if (req.method === "OPTIONS") {
                res.send(204);
            } else {
                next();
            }
        });

     // Prepare MongoDB client.
        app.use(function (req, res, next) {
            var connectionString;
            if (argv["enable-proxying"] === true && req.headers.hasOwnProperty("x-connection-string") === true) {
                connectionString = req.headers["x-connection-string"];
            } else {
                connectionString = argv["connection-string"];
            }
            connectionString = url.parse(connectionString, true);
         // Keep connection pool small.
            connectionString.query.maxPoolSize = 1;
         // Keep a reference to the connection string for later use.
            req.connectionString = connectionString;
            mongo.MongoClient.connect(url.format(connectionString), function (err, db) {
                if (err !== null) {
                    next(err);
                } else {
                    req.db = db;
                    next();
                }
            });
        });

     // Basic auth.
        app.use(basicAuth);

     // Deploy routes.
        app.use(app.router);

     // 404 handler.
        app.use(function (req, res) {
            req.db.close();
            res.send(404, "Not Found");
        });

     // 500 handler.
        app.use(function (err, req, res, next) {
            if (req.hasOwnProperty("db") === true) {
                req.db.close();
            }
            res.send(500, "Internal Server Error");
        });

    });

    app.get("/", function (req, res) {
        req.db.collectionNames({
            namesOnly: true
        }, function (err, collections) {
            var names;
            if (err !== null) {
                next(err);
            } else {
                names = collections.map(function (collection) {
                 // Strip database name.
                    return collection.substring(req.connectionString.pathname.length);
                });
                res.send({
                    collections: names
                });
                req.db.close();
            }
        });
    });

    app.get("/:collection", function (req, res) {
        var collectionParam;
        collectionParam = req.params.collection;
        req.db.collection(collectionParam, function (err, collection) {
            if (err !== null) {
                next(err);
            } else {
                collection.find().count(function (err, count) {
                    if (err !== null) {
                        next(err);
                    } else {
                        res.send({count: count});
                        req.db.close();
                    }
                });
            }
        });
    });

    app.get("/:prefix.files/:file", function (req, res, next) {
        var prefixParam, fileParam;
        prefixParam = req.params.prefix;
        fileParam = req.params.file;
        req.db.collection(prefixParam + ".files", function (err, collection) {
            var id;
            if (err !== null) {
                next(err);
            } else {
                try {
                    id = new mongo.ObjectID(fileParam);
                 // Get metadata first.
                    collection.findOne({
                        _id: id
                    }, function (err, document) {
                        var grid;
                        if (err !== null) {
                            next(err);
                        } else {
                            if (document === null) {
                             // Route to 404 handler.
                                next();
                            } else {
                                if (req.query.hasOwnProperty("binary") === true && req.query.binary === "true") {
                                    grid = new mongo.Grid(req.db, prefixParam);
                                    grid.get(id, function (err, file) {
                                        var contentType;
                                        if (err !== null) {
                                            next(err);
                                        } else {
                                            if (document.contentType) {
                                                contentType = document.contentType;
                                            } else if (document.filename) {
                                             // Guess file type from file extension.
                                                contentType = document.filename.match(/(\.\w+)$/)[1];
                                            } else {
                                                contentType = "application/octet-stream";
                                            }
                                            res.type(contentType);
                                            res.send(file);
                                            req.db.close();
                                        }
                                    });
                                } else {
                                    res.send(document);
                                    req.db.close();
                                }
                            }
                        }
                    });
                } catch (e) {
                 // Route to 404 handler.
                    next();
                }
            }
        });
    });

    app.get("/:collection/:document", function (req, res, next) {
        var collectionParam, documentParam;
        collectionParam = req.params.collection;
        documentParam = req.params.document;
        req.db.collection(collectionParam, function (err, collection) {
            var id;
            if (err !== null) {
                next(err);
            } else {
                try {
                    id = new mongo.ObjectID(documentParam);
                    collection.findOne({
                        _id: id
                    }, function (err, document) {
                        if (err !== null) {
                            next(err);
                        } else {
                            if (document === null) {
                             // Route to 404 handler.
                                next();
                            } else {
                                res.send(document);
                                req.db.close();
                            }
                        }
                    });
                } catch (e) {
                 // Route to 404 handler.
                    next();
                }
            }
        });
    });

    app.get("/:collection/:document/:field", function (req, res, next) {
        var collectionParam, documentParam, fieldParam;
        collectionParam = req.params.collection;
        documentParam = req.params.document;
        fieldParam = req.params.field;
        req.db.collection(collectionParam, function (err, collection) {
            var id, fields;
            if (err !== null) {
                next(err);
            } else {
                try {
                    id = new mongo.ObjectID(documentParam);
                    fields = {};
                    fields[fieldParam] = 1;
                    collection.findOne({
                        _id: id
                    }, {
                        fields: fields
                    }, function (err, document) {
                        if (err !== null) {
                            next(err);
                        } else {
                            if (document === null || document.hasOwnProperty(fieldParam) === false) {
                             // Route to 404 handler.
                                next();
                            } else {
                                res.send(document[fieldParam]);
                                req.db.close();
                            }
                        }
                    });
                } catch (e) {
                 // Route to 404 handler.
                    next();
                }
            }
        });
    });

    app.post("/:collection/query", express.json(), function (req, res) {
        var collectionParam;
        collectionParam = req.params.collection;
        req.db.collection(collectionParam, function (err, collection) {
            var options;
            if (err !== null) {
                next(err);
            } else {
                options = {};
                if (req.query.limit) {
                    options.limit = req.query.limit;
                }
                if (req.query.skip) {
                    options.skip = req.query.skip;
                }
                if (req.query.sort) {
                    options.sort = req.query.sort;
                }
                collection.find(req.body, options).toArray(function (err, documents) {
                    if (err !== null) {
                        next(err);
                    } else {
                        res.send(documents);
                        req.db.close();
                    }
                });
            }
        });
    });

    app.post("/:prefix.files", binaryBodyParser, function (req, res, next) {
        var prefixParam, contentType, grid;
        prefixParam = req.params.prefix;
        contentType = req.headers["content-type"];
        grid = new mongo.Grid(req.db, prefixParam);
        grid.put(req.body, {
            content_type: contentType
        }, function (err, document) {
            if (err !== null) {
                next(err);
            } else {
                res.location(prefixParam + ".files/" + document._id.toHexString());
                res.send(201, "Created");
                req.db.close();
            }
        });
    });

    app.post("/:collection", express.json(), function (req, res) {
        var collectionParam, payload;
        collectionParam = req.params.collection;
        payload = req.body;
        req.db.collection(collectionParam, function (err, collection) {
            if (err !== null) {
                next(err);
            } else {
                collection.insert(payload, function (err, document) {
                    if (err !== null) {
                        next(err);
                    } else {
                        res.location(collectionParam + "/" + document[0]._id.toHexString());
                        res.send(201, "Created");
                        req.db.close();
                    }
                });
            }
        });
    });

    app.patch("/:collection/:document", express.json(), function (req, res, next) {
        var collectionParam, documentParam;
        collectionParam = req.params.collection;
        documentParam = req.params.document;
        req.db.collection(collectionParam, function (err, collection) {
            var id;
            if (err !== null) {
                next(err);
            } else {
                try {
                    id = new mongo.ObjectID(documentParam);
                    collection.update({
                        _id: id
                    }, req.body, function (err) {
                        if (err !== null) {
                            next(err);
                        } else {
                            res.send(204, "No Content");
                            req.db.close();
                        }
                    });
                } catch (e) {
                 // Route to 404 handler.
                    next();
                }
            }
        });
    });

    app.delete("/:prefix.files/:file", function (req, res, next) {
        var prefixParam, fileParam, grid, id;
        prefixParam = req.params.prefix;
        fileParam = req.params.file;
        grid = new mongo.Grid(req.db, prefixParam);
        try {
            id = new mongo.ObjectID(fileParam);
            grid.delete(id, function (err, flag) {
                if (err !== null) {
                    next(err);
                } else {
                 // TODO: Detect if file was actually deleted.
                    res.send(200, "OK");
                    req.db.close();
                }
            });
        } catch (e) {
         // Route to 404 handler.
            next();
        }
    });

    app.delete("/:collection/:document", function (req, res, next) {
        var collectionParam, documentParam;
        collectionParam = req.params.collection;
        documentParam = req.params.document;
        req.db.collection(collectionParam, function (err, collection) {
            var id;
            if (err !== null) {
                next(err);
            } else {
                try {
                    id = new mongo.ObjectID(documentParam);
                    collection.remove({
                        _id: id
                    }, function (err, num) {
                        if (err !== null) {
                            next(err);
                        } else {
                            if (num === 0) {
                             // Route to 404 handler.
                                next();
                            } else {
                                res.send(200, "Deleted");
                                req.db.close();
                            }
                        }
                    });
                } catch (e) {
                 // Route to 404 handler.
                    next();
                }
            }
        });
    });

    app.listen(argv.port);

    console.log("Ming is running on port " + argv.port + ", connected to " + argv["connection-string"]);

}());
<|endoftext|>


<commit_before>'use strict';
let schemePresets = {
  analogous: {
    count: 3,
    fn: 'analogous',
    options: {

    }
  },
  complementary: {
    count: 1,
    fn: 'complement',
    options: {

    }
  },
  splitComplementary: {
    count: 3,
    fn: 'complement',
    options: {

    }
  },
  triadic: {
    count: 3,
    fn: 'equidistant',
    options: {

    }
  },
  squere: {
    count: 4,
    fn: 'equidistant',
    options: {

    }
  },
  rectangle: {
    count: 4,
    fn: 'rotate',
    options: {

    }
  },
};

module.exports = schemePresets;
<commit_msg>update file formatting<commit_after>'use strict';
let schemePresets = {
  analogous: {
    count: 3,
    fn: 'analogous',
    options: {}
  },

  complementary: {
    count: 2,
    fn: 'complement',
    options: {}
  },

  splitComplementary: {
    count: 3,
    fn: 'complement',
    options: {}
  },

  triadic: {
    count: 3,
    fn: 'equidistant'
  },

  squere: {
    count: 4,
    fn: 'equidistant'
  },

  rectangle: {
    count: 4,
    fn: 'rotate',
    options: {}
  },
};

module.exports = schemePresets;
<|endoftext|>


<commit_before>var objectProto = Object.prototype;
var toString = objectProto.toString;
var ownProperty = objectProto.hasOwnProperty;
var isArray = Array.isArray;
var push = Array.prototype.push;

var keys = Object.keys || function(obj) {
  var k = [];
  for (var attr in obj) {
    if (obj.hasOwnProperty(attr)) {
      k.push(obj[attr]);
    }
  }
  return k;
};

function indexOf(arr, value) {
  var i = -1;
  var l = arr ? arr.length : 0;
  while (++i < l) {
    if (arr[i] === value) return i;
  }
  return -1;
}

function groupBy(arr, prop) {
  var fn;
  if (isFunction(prop))
    fn = prop;
  else
    fn = (v) => v[prop];

  return reduce(arr, (result, val) => {
    var key = fn(val);
    (isArray(result[key])
      ? result[key]
      : result[key] = []).push(val);
    return result;
  }, {});
}

function sortedIndex(arr, value, fn) {
  var low = 0;
  var high = arr ? arr.length : low;

  value = fn(value);

  while (low < high) {
    var mid = (low + high) >>> 1;
    if (fn(arr[mid]) < value) {
      low = mid + 1;
    } else {
      high = mid;
    }
  }
  return low;
}

function find(arr, fn) {
  var i = -1;
  var l = arr ? arr.length : 0;
  while (++i < l) {
    if (fn(arr[i], i)) return arr[i];
  }
}

function findLast(arr, fn) {
  var i = arr ? arr.length : 0;
  while (--i >= 0) {
    if (fn(arr[i], i)) return arr[i];
  }
}

function baseFlatten(arr, fromIndex) {
  var i = (fromIndex || 0) - 1;
  var l = arr ? arr.length : 0;
  var n = [];
  while (++i < l) {
    var value = arr[i];
    if (value && typeof value == "object" && typeof value.length == "number") {
      var valIndex = -1,
          valLength = value.length,
          resIndex = n.length;

      n.length += valLength;
      while (++valIndex < valLength) {
        n[resIndex++] = value[valIndex];
      }
    } else {
      n.push(value);
    }
  }
  return n;
}

function flatten(arr, fn) {
  return baseFlatten(fn ? map(arr, fn) : arr, 0);
}

function isDate(value) {
  return !!value && typeof value == "object" && toString.call(value) == "[object Date]" || false;
}

function isFunction(value) {
  return !!value && typeof value == "function" || false;
}

function isNumber(value) {
  return typeof value == "number";
}

function isObject(value) {
  return !!value && typeof value == "object" || false;
}

function isString(value) {
  return typeof value == "string";
}

function isPromise(value) {
  return !!value && typeof value.then == "function";
}

function isObservable(value) {
  return !!value && typeof value.subscribe == "function";
}

function identity(x) {
  return x;
}

function noop() {
  return;
}

function last(arr) {
  return arr[arr.length - 1];
}

var uniqueId = (function() {
  var __id = 0;
  return (prefix) => {
    if (!prefix) prefix = "";
    return (""+prefix) + (__id++);
  };
})();

function contains(arr, value) {
  return indexOf(arr, value) > -1;
}

function extend(dst) {
  var args = arguments;
  for (var i = 1; i < args.length; i++) {
    var src = args[i];
    if (!isObject(src))
      continue;

    var ks = keys(src);
    for (var j = 0, l = ks.length; j < l; j++) {
      dst[ks[j]] = src[ks[j]];
    }
  }
  return dst;
}

function defaults(obj, def) {
  for (var attr in def) {
    if (typeof obj[attr] == "undefined") {
      obj[attr] = def[attr];
    }
  }
  return obj;
}

function cloneObject(obj) {
  return extend({}, obj);
}

function cloneArray(arr) {
  var l = arr ? arr.length : 0;
  var i = -1;
  var n = Array(l);
  while (++i < l) n[i] = arr[i];
  return n;
}

function map(arr, fn) {
  var l = arr ? arr.length : 0;
  var i = -1;
  var n = Array(l);
  while (++i < l) n[i] = fn(arr[i], i);
  return n;
}

function reduce(arr, fn, init) {
  var l = arr ? arr.length : 0;
  var i = -1;
  var n = init;
  while(++i < l) n = fn(n, arr[i], i);
  return n;
}

function each(arr, fn) {
  var l = arr ? arr.length : 0;
  var i = -1;
  while (++i < l) fn(arr[i], i);
}

function values(object) {
  var props = keys(object);
  var i = -1;
  var l = props.length;
  var n = Array(l);
  while (++i < l)  n[i] = object[props[i]];
  return n;
}

function filter(arr, fn) {
  var l = arr ? arr.length : 0;
  var i = -1;
  var n = [];
  while (++i < l) {
    if (fn(arr[i], i)) n.push(arr[i]);
  }
  return n;
}

function compact(arr) {
  return filter(arr, i => i != null);
}

function memoize(fn, resolver) {
  var memoized = function() {
    var cache = memoized.cache;
    var key = resolver ? resolver.apply(this, arguments) : arguments[0];
    return ownProperty.call(cache, key)
      ? (cache[key])
      : (cache[key] = fn.apply(this, arguments));
  };
  memoized.cache = {};
  return memoized;
}

function pick(object, vals) {
  return reduce(vals, (result, key) => {
    if (key in object) result[key] = object[key];
    return result;
  }, {});
}

function pluck(arr, key) {
  return map(arr, o => o[key]);
}

function tryCatch(fn) {
  try {
    return fn();
  }
  catch(e) {
    return e;
  }
}

function simpleMerge(source, dist) {
  for (var attr in source) {
    if (!dist.hasOwnProperty(attr)) continue;
    var src = source[attr];
    var dst = dist[attr];
    if (isString(src) || isNumber(src) || isDate(src)) {
      source[attr] = dst;
    }
    else if (isArray(src)) {
      src.length = 0;
      push.apply(src, dst);
    }
    else {
      source[attr] = simpleMerge(src, dst);
    }
  }
  return source;
}

function chunk(arr, size) {
  var r = [];
  var c = 0;
  var i = -1;
  var l = arr ? arr.length : 0;
  while(++i<l) {
    if (!r[c]) { r[c] = [arr[i]]; }
    else {
      if (r[c].length === size) { r[++c] = [arr[i]]; }
      else { r[c].push(arr[i]); }
    }
  }
  return r;
}

module.exports = {
  chunk,
  compact,
  contains,
  cloneArray,
  cloneObject,
  defaults,
  each,
  extend,
  values,
  filter,
  find,
  findLast,
  flatten,
  groupBy,
  identity,
  indexOf,
  isArray,
  isDate,
  isFunction,
  isNumber,
  isObject,
  isString,
  isPromise,
  isObservable,
  keys,
  last,
  map,
  memoize,
  noop,
  pick,
  pluck,
  reduce,
  simpleMerge,
  sortedIndex,
  tryCatch,
  uniqueId,
};
<commit_msg>added between<commit_after>var objectProto = Object.prototype;
var toString = objectProto.toString;
var ownProperty = objectProto.hasOwnProperty;
var isArray = Array.isArray;
var push = Array.prototype.push;

var keys = Object.keys || function(obj) {
  var k = [];
  for (var attr in obj) {
    if (obj.hasOwnProperty(attr)) {
      k.push(obj[attr]);
    }
  }
  return k;
};

function indexOf(arr, value) {
  var i = -1;
  var l = arr ? arr.length : 0;
  while (++i < l) {
    if (arr[i] === value) return i;
  }
  return -1;
}

function groupBy(arr, prop) {
  var fn;
  if (isFunction(prop))
    fn = prop;
  else
    fn = (v) => v[prop];

  return reduce(arr, (result, val) => {
    var key = fn(val);
    (isArray(result[key])
      ? result[key]
      : result[key] = []).push(val);
    return result;
  }, {});
}

function sortedIndex(arr, value, fn) {
  var low = 0;
  var high = arr ? arr.length : low;

  value = fn(value);

  while (low < high) {
    var mid = (low + high) >>> 1;
    if (fn(arr[mid]) < value) {
      low = mid + 1;
    } else {
      high = mid;
    }
  }
  return low;
}

function find(arr, fn) {
  var i = -1;
  var l = arr ? arr.length : 0;
  while (++i < l) {
    if (fn(arr[i], i)) return arr[i];
  }
}

function between(arr, f, v) {
  var i = -1;
  var l = arr ? arr.length : 0;
  while (++i < l) {
    if (arr[i][f] <= v &&
        arr[i+1]       &&
        v < arr[i+1][f])
      return arr[i];
  }
}

function findLast(arr, fn) {
  var i = arr ? arr.length : 0;
  while (--i >= 0) {
    if (fn(arr[i], i)) return arr[i];
  }
}

function baseFlatten(arr, fromIndex) {
  var i = (fromIndex || 0) - 1;
  var l = arr ? arr.length : 0;
  var n = [];
  while (++i < l) {
    var value = arr[i];
    if (value && typeof value == "object" && typeof value.length == "number") {
      var valIndex = -1,
          valLength = value.length,
          resIndex = n.length;

      n.length += valLength;
      while (++valIndex < valLength) {
        n[resIndex++] = value[valIndex];
      }
    } else {
      n.push(value);
    }
  }
  return n;
}

function flatten(arr, fn) {
  return baseFlatten(fn ? map(arr, fn) : arr, 0);
}

function isDate(value) {
  return !!value && typeof value == "object" && toString.call(value) == "[object Date]" || false;
}

function isFunction(value) {
  return !!value && typeof value == "function" || false;
}

function isNumber(value) {
  return typeof value == "number";
}

function isObject(value) {
  return !!value && typeof value == "object" || false;
}

function isString(value) {
  return typeof value == "string";
}

function isPromise(value) {
  return !!value && typeof value.then == "function";
}

function isObservable(value) {
  return !!value && typeof value.subscribe == "function";
}

function identity(x) {
  return x;
}

function noop() {
  return;
}

function last(arr) {
  return arr[arr.length - 1];
}

var uniqueId = (function() {
  var __id = 0;
  return (prefix) => {
    if (!prefix) prefix = "";
    return (""+prefix) + (__id++);
  };
})();

function contains(arr, value) {
  return indexOf(arr, value) > -1;
}

function extend(dst) {
  var args = arguments;
  for (var i = 1; i < args.length; i++) {
    var src = args[i];
    if (!isObject(src))
      continue;

    var ks = keys(src);
    for (var j = 0, l = ks.length; j < l; j++) {
      dst[ks[j]] = src[ks[j]];
    }
  }
  return dst;
}

function defaults(obj, def) {
  for (var attr in def) {
    if (typeof obj[attr] == "undefined") {
      obj[attr] = def[attr];
    }
  }
  return obj;
}

function cloneObject(obj) {
  return extend({}, obj);
}

function cloneArray(arr) {
  var l = arr ? arr.length : 0;
  var i = -1;
  var n = Array(l);
  while (++i < l) n[i] = arr[i];
  return n;
}

function map(arr, fn) {
  var l = arr ? arr.length : 0;
  var i = -1;
  var n = Array(l);
  while (++i < l) n[i] = fn(arr[i], i);
  return n;
}

function reduce(arr, fn, init) {
  var l = arr ? arr.length : 0;
  var i = -1;
  var n = init;
  while(++i < l) n = fn(n, arr[i], i);
  return n;
}

function each(arr, fn) {
  var l = arr ? arr.length : 0;
  var i = -1;
  while (++i < l) fn(arr[i], i);
}

function values(object) {
  var props = keys(object);
  var i = -1;
  var l = props.length;
  var n = Array(l);
  while (++i < l)  n[i] = object[props[i]];
  return n;
}

function filter(arr, fn) {
  var l = arr ? arr.length : 0;
  var i = -1;
  var n = [];
  while (++i < l) {
    if (fn(arr[i], i)) n.push(arr[i]);
  }
  return n;
}

function compact(arr) {
  return filter(arr, i => i != null);
}

function memoize(fn, resolver) {
  var memoized = function() {
    var cache = memoized.cache;
    var key = resolver ? resolver.apply(this, arguments) : arguments[0];
    return ownProperty.call(cache, key)
      ? (cache[key])
      : (cache[key] = fn.apply(this, arguments));
  };
  memoized.cache = {};
  return memoized;
}

function pick(object, vals) {
  return reduce(vals, (result, key) => {
    if (key in object) result[key] = object[key];
    return result;
  }, {});
}

function pluck(arr, key) {
  return map(arr, o => o[key]);
}

function tryCatch(fn) {
  try {
    return fn();
  }
  catch(e) {
    return e;
  }
}

function simpleMerge(source, dist) {
  for (var attr in source) {
    if (!dist.hasOwnProperty(attr)) continue;
    var src = source[attr];
    var dst = dist[attr];
    if (isString(src) || isNumber(src) || isDate(src)) {
      source[attr] = dst;
    }
    else if (isArray(src)) {
      src.length = 0;
      push.apply(src, dst);
    }
    else {
      source[attr] = simpleMerge(src, dst);
    }
  }
  return source;
}

function chunk(arr, size) {
  var r = [];
  var c = 0;
  var i = -1;
  var l = arr ? arr.length : 0;
  while(++i<l) {
    if (!r[c]) { r[c] = [arr[i]]; }
    else {
      if (r[c].length === size) { r[++c] = [arr[i]]; }
      else { r[c].push(arr[i]); }
    }
  }
  return r;
}

module.exports = {
  chunk,
  compact,
  contains,
  cloneArray,
  cloneObject,
  defaults,
  each,
  extend,
  values,
  filter,
  find,
  between,
  findLast,
  flatten,
  groupBy,
  identity,
  indexOf,
  isArray,
  isDate,
  isFunction,
  isNumber,
  isObject,
  isString,
  isPromise,
  isObservable,
  keys,
  last,
  map,
  memoize,
  noop,
  pick,
  pluck,
  reduce,
  simpleMerge,
  sortedIndex,
  tryCatch,
  uniqueId,
};
<|endoftext|>


<commit_before>(function (require, exports) {

var Q = require("qq");
var BOOT = require("fs-boot");
var FS = require("./q-fs");
var COMMON = require("./common");

var Fs = exports.Fs = function (files) {
    var fs = Object.create(BOOT);
    var root = {};
    var now = new Date();

    function init() {
        // construct a file tree
        Object.keys(files).forEach(function (path) {
            var content = files[path];
            find(root, path).set(content);
        });
    }

    function find(at, path) {
        path = fs.absolute(path);
        if (path === "" || path === FS.ROOT) {
            return Node(function get() {
                return root;
            }, function set(content) {
                root = content;
            });
        }
        var parts = FS.split(path);
        var empty = parts.shift();
        if (empty !== "")
            throw new Error("assertion: first component of root should be empty");
        var i, ii;
        var manifest = function () {};
        for (i = 0, ii = parts.length - 1; i < ii; i++) {
            var part = parts[i];
            if (part === ".") {
                continue;
            } if (typeof at[part] !== "object") {
                manifest = (function (on, part, manifest) {
                    var created = {};
                    at = created;
                    return function () {
                        on[part] = created;
                        manifest();
                    };
                })(at, part, manifest);
            } else {
                at = at[part];
            }
        }
        var leaf = parts[i];
        return Node(function get() {
            return at[leaf];
        }, function set(content) {
            manifest();
            at[leaf] = content;
        });
    }

    fs.list = function (path) {
        path = String(path);
        return Q.when(fs.stat(path), function (stat) {
            if (!stat.isDirectory())
                throw new Error("Can't list non-directory " + path);
            var node = find(root, path).get();
            return Object.keys(node);
        });
    };

    fs.open = function (path, flags, charset, options) {
        if (typeof flags == "object") {
            options = flags;
            flags = options.flags;
            charset = options.charset;
        } else {
            options = options || {};
        }
        var node = find(root, path);
        // TODO create an actual open file object, rather
        // than this rather primitive duck
        flags = flags || "r";
        var binary = flags.indexOf("b") >= 0;
        charset = charset || "utf-8";
        var stat = fs.stat(path);
        var isFile = Q.post(stat, 'isFile');
        return Q.when(isFile, function (isFile) {
            if (!isFile) {
                throw new Error("Can't open non-file " + path);
            }
            return {
                "read": function () {
                    var content = node.get();
                    if (!binary)
                        content = content.toString(charset);
                    return content;
                }
            };
        });
    };

    fs.stat = function (path) {
        var stat = find(root, path);
        if (stat.get() === undefined)
            return Q.reject("No such file: " + path);
        return stat;
    };

    fs.getNode = function (path) {
        path = path || "";
        return find(root, path).get();
    };

    fs.canonical = function (path) {
        return fs.normal(path);
    };

    var Node = function (get, set) {
        var self = Object.create(Node.prototype);
        self.get = get;
        self.set = set;
        return self;
    };

    Node.prototype = Object.create(GenericNode.prototype);

    Node.prototype.constructor = Node;

    Node.prototype.lastModified = function () {
        return now;
    };

    COMMON.update(fs, function () {
        return fs.ROOT;
    });

    init();

    return fs;
};

var GenericNode = function () {};

GenericNode.prototype.exists = function () {
    var node = this.get();
    return typeof node !== "undefined";
};

GenericNode.prototype.isFile = function () {
    var node = this.get();
    return typeof node !== "undefined" && (
        typeof node !== "object" ||
        node.constructor !== Object
    );
};

GenericNode.prototype.isDirectory = function () {
    var node = this.get();
    return (
        typeof node === "object" &&
        node.constructor === Object
    )
};

exports.mock = function (fs, root) {
    return Q.when(fs.listTree(root), function (list) {
        var tree = {};
        var done;
        list.forEach(function (path) {
            var actual = fs.join(root, path);
            var relative = fs.relativeFromDirectory(root, actual);
            var partial = Q.when(fs.stat(actual), function (stat) {
                if (stat.isFile()) {
                    tree[relative] = fs.read(path, "rb");
                }
            });
            done = Q.when(done, function () {
                return partial;
            });
        });
        return Q.when(done, function () {
            return Q.when(Q.shallow(tree), Fs);
        });
    });
};

}).apply(null, typeof exports !== "undefined" ?
    [require, exports] :
    [
        function (id) {
            id = id.toUpperCase()
                .replace(".", "Q_FS")
                .replace("/", "$")
                .replace("-", "_");
            return window[id];
        },
        Q_FS$MOCK
    ]
)
<commit_msg>Please let this be the last QQ reference<commit_after>(function (require, exports) {

var Q = require("q");
var BOOT = require("fs-boot");
var FS = require("./q-fs");
var COMMON = require("./common");

var Fs = exports.Fs = function (files) {
    var fs = Object.create(BOOT);
    var root = {};
    var now = new Date();

    function init() {
        // construct a file tree
        Object.keys(files).forEach(function (path) {
            var content = files[path];
            find(root, path).set(content);
        });
    }

    function find(at, path) {
        path = fs.absolute(path);
        if (path === "" || path === FS.ROOT) {
            return Node(function get() {
                return root;
            }, function set(content) {
                root = content;
            });
        }
        var parts = FS.split(path);
        var empty = parts.shift();
        if (empty !== "")
            throw new Error("assertion: first component of root should be empty");
        var i, ii;
        var manifest = function () {};
        for (i = 0, ii = parts.length - 1; i < ii; i++) {
            var part = parts[i];
            if (part === ".") {
                continue;
            } if (typeof at[part] !== "object") {
                manifest = (function (on, part, manifest) {
                    var created = {};
                    at = created;
                    return function () {
                        on[part] = created;
                        manifest();
                    };
                })(at, part, manifest);
            } else {
                at = at[part];
            }
        }
        var leaf = parts[i];
        return Node(function get() {
            return at[leaf];
        }, function set(content) {
            manifest();
            at[leaf] = content;
        });
    }

    fs.list = function (path) {
        path = String(path);
        return Q.when(fs.stat(path), function (stat) {
            if (!stat.isDirectory())
                throw new Error("Can't list non-directory " + path);
            var node = find(root, path).get();
            return Object.keys(node);
        });
    };

    fs.open = function (path, flags, charset, options) {
        if (typeof flags == "object") {
            options = flags;
            flags = options.flags;
            charset = options.charset;
        } else {
            options = options || {};
        }
        var node = find(root, path);
        // TODO create an actual open file object, rather
        // than this rather primitive duck
        flags = flags || "r";
        var binary = flags.indexOf("b") >= 0;
        charset = charset || "utf-8";
        var stat = fs.stat(path);
        var isFile = Q.post(stat, 'isFile');
        return Q.when(isFile, function (isFile) {
            if (!isFile) {
                throw new Error("Can't open non-file " + path);
            }
            return {
                "read": function () {
                    var content = node.get();
                    if (!binary)
                        content = content.toString(charset);
                    return content;
                }
            };
        });
    };

    fs.stat = function (path) {
        var stat = find(root, path);
        if (stat.get() === undefined)
            return Q.reject("No such file: " + path);
        return stat;
    };

    fs.getNode = function (path) {
        path = path || "";
        return find(root, path).get();
    };

    fs.canonical = function (path) {
        return fs.normal(path);
    };

    var Node = function (get, set) {
        var self = Object.create(Node.prototype);
        self.get = get;
        self.set = set;
        return self;
    };

    Node.prototype = Object.create(GenericNode.prototype);

    Node.prototype.constructor = Node;

    Node.prototype.lastModified = function () {
        return now;
    };

    COMMON.update(fs, function () {
        return fs.ROOT;
    });

    init();

    return fs;
};

var GenericNode = function () {};

GenericNode.prototype.exists = function () {
    var node = this.get();
    return typeof node !== "undefined";
};

GenericNode.prototype.isFile = function () {
    var node = this.get();
    return typeof node !== "undefined" && (
        typeof node !== "object" ||
        node.constructor !== Object
    );
};

GenericNode.prototype.isDirectory = function () {
    var node = this.get();
    return (
        typeof node === "object" &&
        node.constructor === Object
    )
};

exports.mock = function (fs, root) {
    return Q.when(fs.listTree(root), function (list) {
        var tree = {};
        return Q.all(list.map(function (path) {
            var actual = fs.join(root, path);
            var relative = fs.relativeFromDirectory(root, actual);
            return Q.when(fs.stat(actual), function (stat) {
                if (stat.isFile()) {
                    return Q.when(fs.read(path, "rb"), function (content) {
                        tree[relative] = content;
                    });
                }
            });
        })).then(function () {
            return Fs(tree);
        });
    });
};

}).apply(null, typeof exports !== "undefined" ?
    [require, exports] :
    [
        function (id) {
            id = id.toUpperCase()
                .replace(".", "Q_FS")
                .replace("/", "$")
                .replace("-", "_");
            return window[id];
        },
        Q_FS$MOCK
    ]
)
<|endoftext|>


<commit_before>var Class = require(__basedir + "/utilities/class");
var BaseGame = require("./baseGame");
var extend = require("extend");
var serializer = require("../serializer");
var log = require("../log");

/**
 * @class TurnBasedGame - a base game that is turn based, with helper functions that should be common between turn based games. defined in Creer data and implimented here so we don't have to re-code it all the time.
 */
var TurnBasedGame = Class(BaseGame, {
    init: function(data /* ... */) {
        // clients can request a different amount of time added after each time, in sec
        if(data.turnTimeAdded) {
            var num = Number(data.turnTimeAdded);
            if(!isNaN(num) && num > 0) {
                this._playerAdditionalTimePerTurn = num * 1e9; // convert sec to ns
            }
        }

        return BaseGame.init.apply(this, arguments);
    },

    _playerAdditionalTimePerTurn: 1e9, // 1 sec in ns

    /**
     * begins the turn based game to the first player
     */
    begin: function() {
        BaseGame.begin.apply(this, arguments);

        this.currentPlayer = this.players[0];

        this.beforeTurn(); // different from nextPlayer, this is called because their turn has not yet started
    },

    /**
     * after the game has started we order the first player to runTurn
     */
    _started: function() {
        BaseGame._started.apply(this, arguments);

        this.order(this.currentPlayer, "runTurn");
    },

    /**
     * Called when an AI has finished their turn, if they returned true from that then they want to end their turn.
     *
     * @param {Player} player - the player for the ai that finished a runTurn()
     * @param {boolean} data - true when they finished their turn, false otherwise (which re-runs their turn)
     */
    aiFinishedRunTurn: function(player, data) {
        if(data === true) { // ais that return true from runTurn() mean they ran their turn, and are done with it
            this.nextTurn();
        }

        this.order(this.currentPlayer, "runTurn"); // tell the current player to run their turn, because the passed in player may no longer be the current player as we ran game logic above
    },

    /**
     * Transitions to the next turn, increasing turn and setting the currentPlayer to the next one.
     */
    nextTurn: function() {
        if(this.currentTurn + 1 === this.maxTurns) {
            this._maxTurnsReached();
            return;
        }

        this.currentTurn++;
        this.currentPlayer = this.players.nextWrapAround(this.currentPlayer);
        this.currentPlayer.timeRemaining += this._playerAdditionalTimePerTurn;

        this.beforeTurn();
    },

    /**
     * Called before a players turn, including the first turn.
     */
    beforeTurn: function() {
        if(this.currentTurn + 1 === this.maxTurns) {
            this._maxTurnsReached();
            return;
        }

        this.currentTurn++;
        this.currentPlayer = this.players.nextWrapAround(this.currentPlayer);
        this.currentPlayer.timeRemaining += this._playerAdditionalTimePerTurn;
    },

    /**
     * Intended to be inherited and then max turn victory conditions checked to find the winner/looser.
     */
    _maxTurnsReached: function() {
        this.isOver(true);
    },
});

module.exports = TurnBasedGame;
<commit_msg>Fix turn based games skipping player 2's turn<commit_after>var Class = require(__basedir + "/utilities/class");
var BaseGame = require("./baseGame");
var extend = require("extend");
var serializer = require("../serializer");
var log = require("../log");

/**
 * @class TurnBasedGame - a base game that is turn based, with helper functions that should be common between turn based games. defined in Creer data and implimented here so we don't have to re-code it all the time.
 */
var TurnBasedGame = Class(BaseGame, {
    init: function(data /* ... */) {
        // clients can request a different amount of time added after each time, in sec
        if(data.turnTimeAdded) {
            var num = Number(data.turnTimeAdded);
            if(!isNaN(num) && num > 0) {
                this._playerAdditionalTimePerTurn = num * 1e9; // convert sec to ns
            }
        }

        return BaseGame.init.apply(this, arguments);
    },

    _playerAdditionalTimePerTurn: 1e9, // 1 sec in ns

    /**
     * begins the turn based game to the first player
     */
    begin: function() {
        BaseGame.begin.apply(this, arguments);

        this.currentPlayer = this.players[0];

        this.beforeTurn(); // different from nextPlayer, this is called because their turn has not yet started
    },

    /**
     * after the game has started we order the first player to runTurn
     */
    _started: function() {
        BaseGame._started.apply(this, arguments);

        this.order(this.currentPlayer, "runTurn");
    },

    /**
     * Called when an AI has finished their turn, if they returned true from that then they want to end their turn.
     *
     * @param {Player} player - the player for the ai that finished a runTurn()
     * @param {boolean} data - true when they finished their turn, false otherwise (which re-runs their turn)
     */
    aiFinishedRunTurn: function(player, data) {
        if(data === true) { // ais that return true from runTurn() mean they ran their turn, and are done with it
            this.nextTurn();
        }

        this.order(this.currentPlayer, "runTurn"); // tell the current player to run their turn, because the passed in player may no longer be the current player as we ran game logic above
    },

    /**
     * Transitions to the next turn, increasing turn and setting the currentPlayer to the next one.
     */
    nextTurn: function() {
        if(this.currentTurn + 1 === this.maxTurns) {
            this._maxTurnsReached();
            return;
        }

        this.currentTurn++;
        this.currentPlayer = this.players.nextWrapAround(this.currentPlayer);
        this.currentPlayer.timeRemaining += this._playerAdditionalTimePerTurn;

        this.beforeTurn();
    },

    /**
     * Called before a players turn, including the first turn.
     * You don't have to call this base method if you don't want to, it does nothing without overriding it
     */
    beforeTurn: function() {
        // intended to be overwritten by games that need this
    },

    /**
     * Intended to be inherited and then max turn victory conditions checked to find the winner/looser.
     */
    _maxTurnsReached: function() {
        this.isOver(true);
    },
});

module.exports = TurnBasedGame;
<|endoftext|>