<commit_before>/**
 * @license
 * Copyright 2015 Google 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.
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'DetailView',
  extends: 'foam.u2.View',

  requires: [
    'foam.core.Property',
    'foam.u2.DetailPropertyView'
  ],

  exports: [
    'controllerMode'
  ],

  properties: [
    {
      name: 'data',
      postSet: function(_, data) {
        if ( data && data.cls_ !== this.of ) this.of = data.cls_;
      }
    },
    {
//      type: 'Model',
      name: 'of'
    },
    {
      class: 'Boolean',
      name: 'showActions'
    },
    {
      name: 'properties',
      // TODO: Make an FObjectArray when it validates properly
      preSet: function(_, ps) {
        this.assert(ps, 'Properties required.');
        for ( var i = 0 ; i < ps.length ; i++ ) {
          this.assert(
              foam.core.Propert.isInstance(ps),
              "Non-Property in 'properties' list:",
              ps);
        }
        return ps;
      },
      expression: function(of) {
        if ( ! of ) return [];
        return of.getAxiomsByClass(foam.core.Property).
            filter(function(p) { return ! p.hidden; });
      }
    },
    {
      name: 'config'
      // Map of property-name: {map of property overrides} for configuring properties
      // values include 'label', 'units', and 'view'
    },
    {
      name: 'actions',
      expression: function(of) {
        return of.getAxiomsByClass(foam.core.Action);
      }
    },
    {
      name: 'controllerMode',
      attribute: true
    },
    {
      name: 'title',
      attribute: true,
      expression: function(of) { return of.label; },
      // documentation: function() {/*
      //
The display title for the $$DOC{ref:'foam.ui.View'}.
      //

      //*/}
    },
    [ 'nodeName', 'div' ]
  ],

  templates: [
    function CSS() {/*
      ^ {
        background: #fdfdfd;
        border: solid 1px #dddddd;
        box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
        display: inline-block;
        margin: 5px;
        padding: 3px;
      }
      ^ table {
        padding-bottom: 2px;
      }
      ^title {
        color: #333;
        float: left;
        font-size: 14px;
        font-weight: bold;
        margin-bottom: 8px;
        padding: 2px;
      }
      ^toolbar {
        margin-left: 5px;
      }
      ^ input {
        border: solid 1px #aacfe4;
        font-size: 10px;
        margin: 2px 0 0px 2px;
        padding: 4px 2px;
      }
      ^ textarea {
        border: solid 1px #aacfe4;
        float: left;
        font-size: 10px;
        margin: 2px 0 0px 2px;
        overflow: auto;
        padding: 4px 2px;
        width: 98%;
      }
      ^ select {
        border: solid 1px #aacfe4;
        font-size: 10px;
        margin: 2px 0 0px 2px;
        padding: 4px 2px;
      }
    */}
  ],

  methods: [
    function initE() {
      var self = this;
      this.add(this.slot(function(of, properties) {
        if ( ! of ) return '';

        var title = self.title && self.E('tr').
          start('td').cssClass(self.myCls('title')).attrs({colspan: 2}).
            add(this.title$).
          end();

        return self.actionBorder(
          self.
            E('table').
            cssClass(self.myCls()).
            add(title).
            add(properties.map(function(p) {
              var config = self.config && self.config[p.name];

              if ( config ) {
                p = p.clone();
                for ( var key in config ) {
                  p[key] = config[key];
                }
              }

              return self.DetailPropertyView.create({prop: p});
            })));
      }));
    },

    function actionBorder(e) {
      if ( ! this.showActions || ! this.actions.length ) return e;

      return this.E().add(e).
        start('div').cssClass(this.myCls('toolbar')).add(this.actions).end();
    },

    function elementForFeature(fName) {
      var f = this.cls_.getFeature(fName) || this.X.data.model_.getFeature(fName);
      return f ? f.toE(this.Y) : this.E('Unknown feature: ' + fName).style({color: 'red'});
    }
  ]
});
<commit_msg>Another fix for last fix.<commit_after>/**
 * @license
 * Copyright 2015 Google 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.
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'DetailView',
  extends: 'foam.u2.View',

  requires: [
    'foam.core.Property',
    'foam.u2.DetailPropertyView'
  ],

  exports: [
    'controllerMode'
  ],

  properties: [
    {
      name: 'data',
      postSet: function(_, data) {
        if ( data && data.cls_ !== this.of ) this.of = data.cls_;
      }
    },
    {
//      type: 'Model',
      name: 'of'
    },
    {
      class: 'Boolean',
      name: 'showActions'
    },
    {
      name: 'properties',
      // TODO: Make an FObjectArray when it validates properly
      preSet: function(_, ps) {
        this.assert(ps, 'Properties required.');
        for ( var i = 0 ; i < ps.length ; i++ ) {
          this.assert(
              foam.core.Property.isInstance(ps[i]),
              "Non-Property in 'properties' list:",
              ps);
        }
        return ps;
      },
      expression: function(of) {
        if ( ! of ) return [];
        return of.getAxiomsByClass(foam.core.Property).
            filter(function(p) { return ! p.hidden; });
      }
    },
    {
      name: 'config'
      // Map of property-name: {map of property overrides} for configuring properties
      // values include 'label', 'units', and 'view'
    },
    {
      name: 'actions',
      expression: function(of) {
        return of.getAxiomsByClass(foam.core.Action);
      }
    },
    {
      name: 'controllerMode',
      attribute: true
    },
    {
      name: 'title',
      attribute: true,
      expression: function(of) { return of.label; },
      // documentation: function() {/*
      //
The display title for the $$DOC{ref:'foam.ui.View'}.
      //

      //*/}
    },
    [ 'nodeName', 'div' ]
  ],

  templates: [
    function CSS() {/*
      ^ {
        background: #fdfdfd;
        border: solid 1px #dddddd;
        box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
        display: inline-block;
        margin: 5px;
        padding: 3px;
      }
      ^ table {
        padding-bottom: 2px;
      }
      ^title {
        color: #333;
        float: left;
        font-size: 14px;
        font-weight: bold;
        margin-bottom: 8px;
        padding: 2px;
      }
      ^toolbar {
        margin-left: 5px;
      }
      ^ input {
        border: solid 1px #aacfe4;
        font-size: 10px;
        margin: 2px 0 0px 2px;
        padding: 4px 2px;
      }
      ^ textarea {
        border: solid 1px #aacfe4;
        float: left;
        font-size: 10px;
        margin: 2px 0 0px 2px;
        overflow: auto;
        padding: 4px 2px;
        width: 98%;
      }
      ^ select {
        border: solid 1px #aacfe4;
        font-size: 10px;
        margin: 2px 0 0px 2px;
        padding: 4px 2px;
      }
    */}
  ],

  methods: [
    function initE() {
      var self = this;
      this.add(this.slot(function(of, properties) {
        if ( ! of ) return '';

        var title = self.title && self.E('tr').
          start('td').cssClass(self.myCls('title')).attrs({colspan: 2}).
            add(this.title$).
          end();

        return self.actionBorder(
          self.
            E('table').
            cssClass(self.myCls()).
            add(title).
            add(properties.map(function(p) {
              var config = self.config && self.config[p.name];

              if ( config ) {
                p = p.clone();
                for ( var key in config ) {
                  p[key] = config[key];
                }
              }

              return self.DetailPropertyView.create({prop: p});
            })));
      }));
    },

    function actionBorder(e) {
      if ( ! this.showActions || ! this.actions.length ) return e;

      return this.E().add(e).
        start('div').cssClass(this.myCls('toolbar')).add(this.actions).end();
    },

    function elementForFeature(fName) {
      var f = this.cls_.getFeature(fName) || this.X.data.model_.getFeature(fName);
      return f ? f.toE(this.Y) : this.E('Unknown feature: ' + fName).style({color: 'red'});
    }
  ]
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google 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.
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'DetailView',
  extends: 'foam.u2.View',

  documentation: 'A generic property-sheet style View for editing an FObject.',

  requires: [
    'foam.core.Property',
    'foam.u2.DetailPropertyView'
  ],

  exports: [
    'currentData as data',
    'controllerMode'
  ],

  axioms: [
    foam.pattern.Faceted.create()
  ],

  properties: [
    {
      name: 'data',
      attribute: true,
      preSet: function(_, data) {
        var of = data && data.cls_;
        if ( of !== this.of ) {
          this.of = of;
        } else {
          this.currentData = data;
        }
        return data;
      }
    },
    'currentData',
    {
      class: 'Class',
      name: 'of'
    },
    {
      class: 'Boolean',
      name: 'showActions'
    },
    {
      name: 'properties',
      // TODO: Make an FObjectArray when it validates properly
      preSet: function(_, ps) {
        foam.assert(ps, 'Properties required.');
        for ( var i = 0 ; i < ps.length ; i++ ) {
          foam.assert(
              foam.core.Property.isInstance(ps[i]),
              "Non-Property in 'properties' list:",
              ps);
        }
        return ps;
      },
      expression: function(of) {
        if ( ! of ) return [];
        return this.of.getAxiomsByClass(foam.core.Property).
          // TODO: this is a temporary fix, but Visibility.HIDDEN should be included and could be switched
          filter(function(p) { return ! ( p.hidden || p.visibility === foam.u2.Visibility.HIDDEN ); });
      }
    },
    {
      name: 'config'
      // Map of property-name: {map of property overrides} for configuring properties
      // values include 'label', 'units', and 'view'
    },
    {
      name: 'actions',
      expression: function(of) {
        return this.of.getAxiomsByClass(foam.core.Action);
      }
    },
    {
      name: 'title',
      attribute: true,
      expression: function(of) { return this.of ? this.of.model_.label : ''; },
      // documentation: function() {/*
      //
The display title for the $$DOC{ref:'foam.ui.View'}.
      //

      //*/}
    },
    [ 'nodeName', 'div' ]
  ],

  templates: [
    function CSS() {/*
      ^ {
        background: #fdfdfd;
        border: solid 1px #dddddd;
        box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
        display: inline-block;
        margin: 5px;
        padding: 3px;
      }
      ^ table {
        padding-bottom: 2px;
      }
      ^title {
        color: #333;
        float: left;
        font-size: 14px;
        font-weight: bold;
        margin-bottom: 8px;
        padding: 2px;
      }
      ^toolbar {
        margin-left: 5px;
      }
      ^ input {
        border: solid 1px #aacfe4;
        font-size: 10px;
        margin: 2px 0 0px 2px;
        padding: 4px 2px;
      }
      ^ textarea {
        border: solid 1px #aacfe4;
        float: left;
        font-size: 10px;
        margin: 2px 0 0px 2px;
        overflow: auto;
        padding: 4px 2px;
        width: 98%;
      }
      ^ select {
        border: solid 1px #aacfe4;
        font-size: 10px;
        margin: 2px 0 0px 2px;
        padding: 4px 2px;
      }
    */}
  ],

  methods: [
    function initE() {
      var self = this;
      this.add(this.slot(function(of, properties) {
        if ( ! of ) return '';

        // Binds view to currentData instead of data because there
        // is a delay from when data is updated until when the UI
        // is rebuilt if the data's class changes. Binding directly
        // to data causes views and actions from the old class to get
        // bound to data of a new class, which causes problems.
        self.currentData = self.data;

        var title = self.title && this.E('tr').
          start('td').addClass(this.myClass('title')).attrs({colspan: 2}).
            add(self.title$).
          end();

        return self.actionBorder(
          this.
            E('table').
            addClass(this.myClass()).
            add(title).
            forEach(properties, function(p) {
              var config = self.config && self.config[p.name];

              if ( config ) {
                p = p.clone();
                for ( var key in config ) {
                  p[key] = config[key];
                }
              }

              this.tag(self.DetailPropertyView, { prop: p });
            }));
      }));
    },

    function actionBorder(e) {
      if ( ! this.showActions || ! this.actions.length ) return e;

      return this.E().add(e).
        start('div').addClass(this.myClass('toolbar')).add(this.actions).end();
    }
  ]
});
<commit_msg>foam.u2.DetailView: Update actions expression to not fail if 'of' isn't set yet.<commit_after>/**
 * @license
 * Copyright 2015 Google 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.
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'DetailView',
  extends: 'foam.u2.View',

  documentation: 'A generic property-sheet style View for editing an FObject.',

  requires: [
    'foam.core.Property',
    'foam.u2.DetailPropertyView'
  ],

  exports: [
    'currentData as data',
    'controllerMode'
  ],

  axioms: [
    foam.pattern.Faceted.create()
  ],

  properties: [
    {
      name: 'data',
      attribute: true,
      preSet: function(_, data) {
        var of = data && data.cls_;
        if ( of !== this.of ) {
          this.of = of;
        } else {
          this.currentData = data;
        }
        return data;
      }
    },
    'currentData',
    {
      class: 'Class',
      name: 'of'
    },
    {
      class: 'Boolean',
      name: 'showActions'
    },
    {
      name: 'properties',
      // TODO: Make an FObjectArray when it validates properly
      preSet: function(_, ps) {
        foam.assert(ps, 'Properties required.');
        for ( var i = 0 ; i < ps.length ; i++ ) {
          foam.assert(
              foam.core.Property.isInstance(ps[i]),
              "Non-Property in 'properties' list:",
              ps);
        }
        return ps;
      },
      expression: function(of) {
        if ( ! of ) return [];
        return this.of.getAxiomsByClass(foam.core.Property).
          // TODO: this is a temporary fix, but Visibility.HIDDEN should be included and could be switched
          filter(function(p) { return ! ( p.hidden || p.visibility === foam.u2.Visibility.HIDDEN ); });
      }
    },
    {
      name: 'config'
      // Map of property-name: {map of property overrides} for configuring properties
      // values include 'label', 'units', and 'view'
    },
    {
      name: 'actions',
      expression: function(of) {
        if ( ! of ) return [];
        return this.of.getAxiomsByClass(foam.core.Action);
      }
    },
    {
      name: 'title',
      attribute: true,
      expression: function(of) { return this.of ? this.of.model_.label : ''; },
      // documentation: function() {/*
      //
The display title for the $$DOC{ref:'foam.ui.View'}.
      //

      //*/}
    },
    [ 'nodeName', 'div' ]
  ],

  templates: [
    function CSS() {/*
      ^ {
        background: #fdfdfd;
        border: solid 1px #dddddd;
        box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
        display: inline-block;
        margin: 5px;
        padding: 3px;
      }
      ^ table {
        padding-bottom: 2px;
      }
      ^title {
        color: #333;
        float: left;
        font-size: 14px;
        font-weight: bold;
        margin-bottom: 8px;
        padding: 2px;
      }
      ^toolbar {
        margin-left: 5px;
      }
      ^ input {
        border: solid 1px #aacfe4;
        font-size: 10px;
        margin: 2px 0 0px 2px;
        padding: 4px 2px;
      }
      ^ textarea {
        border: solid 1px #aacfe4;
        float: left;
        font-size: 10px;
        margin: 2px 0 0px 2px;
        overflow: auto;
        padding: 4px 2px;
        width: 98%;
      }
      ^ select {
        border: solid 1px #aacfe4;
        font-size: 10px;
        margin: 2px 0 0px 2px;
        padding: 4px 2px;
      }
    */}
  ],

  methods: [
    function initE() {
      var self = this;
      this.add(this.slot(function(of, properties, actions) {
        if ( ! of ) return '';

        // Binds view to currentData instead of data because there
        // is a delay from when data is updated until when the UI
        // is rebuilt if the data's class changes. Binding directly
        // to data causes views and actions from the old class to get
        // bound to data of a new class, which causes problems.
        self.currentData = self.data;

        var title = self.title && this.E('tr').
          start('td').addClass(this.myClass('title')).attrs({colspan: 2}).
            add(self.title$).
          end();

        return self.actionBorder(
          this.
            E('table').
            addClass(this.myClass()).
            add(title).
            forEach(properties, function(p) {
              var config = self.config && self.config[p.name];

              if ( config ) {
                p = p.clone();
                for ( var key in config ) {
                  p[key] = config[key];
                }
              }

              this.tag(self.DetailPropertyView, { prop: p });
            }));
      }));
    },

    function actionBorder(e) {
      if ( ! this.showActions || ! this.actions.length ) return e;

      return this.E().add(e).
        start('div').addClass(this.myClass('toolbar')).add(this.actions).end();
    }
  ]
});
<|endoftext|>


<commit_before>// Operations for free words over the natural numbers (i.e. positive integers).
// Inverses are represented as negative numbers.
// Words are represented as immutable.js Lists.

import * as I from 'immutable';

const _isInteger = x => typeof x == 'number' && x % 1 == 0;

const _overlap = function _overlap(w1, w2) {
  w1 = word(w1);
  w2 = word(w2);

  const n1 = w1.size;
  const n2 = w2.size;
  const n  = Math.min(n1, n2);

  for (let k = 0; k < n; ++k) {
    if (w2.get(k) != -w1.get(n1 - 1 - k))
      return k;
  }
  return n;
};

const _repeat = (w, m) => I.Range(0, m).reduce(r => r.concat(w), empty);

export const empty = I.List();

export function word(w) {
  return w.reduce(
    function(w, x) {
      if (!_isInteger(x))
        throw new Error('illegal word '+w);
      if (x == 0)
        return w;
      else if (w.last() == -x)
        return w.pop();
      else
        return w.push(x);
    },
    empty
  );
};

export function inverse(w) {
  return word(w).reverse().map(x => -x);
};

export function raisedTo(m, w) {
  w = word(w);

  if (m == 0)
    return empty;
  else if (m < 0)
    return raisedTo(-m, inverse(w));
  else {
    const n = w.size;
    const k = _overlap(w, w);

    if (k == 0)
      return _repeat(w, m);
    else if (k == n)
      return (m % 2 == 0) ? empty : w;
    else {
      const head = w.slice(0, k);
      const tail = w.slice(n - k);
      const mid  = w.slice(k, n - k);

      return head.concat(_repeat(mid, m)).concat(tail);
    }
  }
};

export function product(words) {
  return words.map(word).reduce(
    function(w1, w2) {
      const k = _overlap(w1, w2);
      return w1.slice(0, w1.size - k).concat(w2.slice(k));
    },
    empty
  );
};

export function commutator(a, b) {
  return product([a, b, inverse(a), inverse(b)]);
};

if (require.main == module) {
  console.log(product([[1,2,3], [-3,-2,4]]));
  console.log(raisedTo(3, [1,2,3,4,5,-2,-1]));
  console.log(commutator([1,2], [3,2]));
}
<commit_msg>start optimizing freeWords<commit_after>// Operations for free words over the natural numbers (i.e. positive integers).
// Inverses are represented as negative numbers.
// Words are represented as immutable.js Lists.

import * as I from 'immutable';

const _isInteger = x => typeof x == 'number' && x % 1 == 0;

const _overlap = function _overlap(w1, w2) {
  const n1 = w1.size;
  const n2 = w2.size;
  const n  = Math.min(n1, n2);

  for (let k = 0; k < n; ++k) {
    if (w2.get(k) != -w1.get(n1 - 1 - k))
      return k;
  }
  return n;
};

const _repeat = (w, m) => I.Range(0, m).reduce(r => r.concat(w), empty);

export const empty = I.List();

export function word(w) {
  const out = [];

  for (const x of w) {
    if (!_isInteger(x))
      throw new Error('illegal word '+w);
    else if (x == 0)
      break;
    else if (out[out.length-1] == -x)
      out.pop();
    else
      out.push(x);
  }

  return I.List(out);
};

export function inverse(w) {
  return word(w).reverse().map(x => -x);
};

export function raisedTo(m, w) {
  w = word(w);

  if (m == 0)
    return empty;
  else if (m < 0)
    return raisedTo(-m, inverse(w));
  else {
    const n = w.size;
    const k = _overlap(w, w);

    if (k == 0)
      return _repeat(w, m);
    else if (k == n)
      return (m % 2 == 0) ? empty : w;
    else {
      const head = w.slice(0, k);
      const tail = w.slice(n - k);
      const mid  = w.slice(k, n - k);

      return head.concat(_repeat(mid, m)).concat(tail);
    }
  }
};

export function product(words) {
  return words.map(word).reduce(
    function(w1, w2) {
      const k = _overlap(w1, w2);
      return w1.slice(0, w1.size - k).concat(w2.slice(k));
    },
    empty
  );
};

export function commutator(a, b) {
  return product([a, b, inverse(a), inverse(b)]);
};

if (require.main == module) {
  const timer = require('../common/util').timer();

  console.log(product([[1,2,3], [-3,-2,4]]));
  console.log(raisedTo(3, [1,2,3,4,5,-2,-1]));
  console.log(commutator([1,2], [3,2]));

  console.log();
  console.log(`Computation time: ${timer()} msec`);
}
<|endoftext|>


<commit_before>define([
	'ash',
	'game/constants/LogConstants',
	'game/constants/PerkConstants',
	'game/constants/ItemConstants',
	'game/constants/PositionConstants',
	'game/components/player/BagComponent',
	'game/components/player/ExcursionComponent',
	'game/components/player/VisionComponent',
	'game/components/player/StaminaComponent',
	'game/components/sector/ReputationComponent',
	'game/components/player/RumoursComponent',
	'game/components/player/EvidenceComponent',
	'game/components/player/DeityComponent',
	'game/components/player/FollowersComponent',
	'game/components/player/ItemsComponent',
	'game/components/player/PerksComponent',
	'game/components/type/GangComponent',
	'game/components/type/PlayerComponent',
	'game/components/type/TribeComponent',
	'game/components/type/LevelComponent',
	'game/components/type/SectorComponent',
	'game/components/common/PositionComponent',
	'game/components/player/PlayerActionComponent',
	'game/components/common/ResourcesComponent',
	'game/components/common/CurrencyComponent',
	'game/components/common/ResourceAccumulationComponent',
	'game/components/common/VisitedComponent',
	'game/components/common/RevealedComponent',
	'game/components/common/LogMessagesComponent',
	'game/components/common/SaveComponent',
	'game/components/sector/improvements/BeaconComponent',
	'game/components/sector/improvements/SectorImprovementsComponent',
	'game/components/sector/improvements/SectorCollectorsComponent',
	'game/components/sector/improvements/WorkshopComponent',
	'game/components/sector/events/RecruitComponent',
	'game/components/sector/events/TraderComponent',
	'game/components/sector/SectorStatusComponent',
	'game/components/sector/SectorControlComponent',
	'game/components/sector/EnemiesComponent',
	'game/components/sector/events/CampEventTimersComponent',
	'game/components/sector/OutgoingCaravansComponent',
	'game/components/sector/MovementOptionsComponent',
	'game/components/sector/PassagesComponent',
	'game/components/sector/SectorFeaturesComponent',
	'game/components/sector/SectorLocalesComponent',
	'game/components/sector/LastVisitedCampComponent',
	'game/components/common/CampComponent',
	'game/components/tribe/UpgradesComponent',
	'game/components/level/LevelPassagesComponent'
], function (
	Ash,
	LogConstants,
	PerkConstants,
	ItemConstants,
	PositionConstants,
	BagComponent,
	ExcursionComponent,
	VisionComponent,
	StaminaComponent,
	ReputationComponent,
	RumoursComponent,
	EvidenceComponent,
	DeityComponent,
	FollowersComponent,
	ItemsComponent,
	PerksComponent,
	GangComponent,
	PlayerComponent,
	TribeComponent,
	LevelComponent,
	SectorComponent,
	PositionComponent,
	PlayerActionComponent,
	ResourcesComponent,
	CurrencyComponent,
	ResourceAccumulationComponent,
	VisitedComponent,
	RevealedComponent,
	LogMessagesComponent,
	SaveComponent,
	BeaconComponent,
	SectorImprovementsComponent,
	SectorCollectorsComponent,
	WorkshopComponent,
	RecruitComponent,
	TraderComponent,
	SectorStatusComponent,
	SectorControlComponent,
	EnemiesComponent,
	CampEventTimersComponent,
	OutgoingCaravansComponent,
	MovementOptionsComponent,
	PassagesComponent,
	SectorFeaturesComponent,
	SectorLocalesComponent,
	LastVisitedCampComponent,
	CampComponent,
	UpgradesComponent,
	LevelPassagesComponent
) {
	var EntityCreator = Ash.Class.extend({

		engine: null,

		constructor: function (engine) {
			this.engine = engine;
		},

		destroyEntity: function (entity) {
			this.engine.removeEntity(entity);
		},

		createPlayer: function (saveKey) {
			var player = new Ash.Entity()
				.add(new PlayerComponent())
				.add(new BagComponent(0))
				.add(new VisionComponent(0))
				.add(new ItemsComponent())
				.add(new FollowersComponent())
				.add(new PerksComponent())
				.add(new StaminaComponent(1000))
				.add(new ResourcesComponent(ItemConstants.PLAYER_DEFAULT_STORAGE))
				.add(new CurrencyComponent(0))
				.add(new ResourceAccumulationComponent(saveKey))
				.add(new RumoursComponent())
				.add(new EvidenceComponent())
				.add(new PositionComponent(13, 0, 0, false))
				.add(new LogMessagesComponent())
				.add(new PlayerActionComponent())
				.add(new SaveComponent(saveKey, [
					ResourcesComponent,
					CurrencyComponent,
					VisionComponent,
					ItemsComponent,
					FollowersComponent,
					PerksComponent,
					StaminaComponent,
					PositionComponent,
					RumoursComponent,
					EvidenceComponent,
					LogMessagesComponent,
					PlayerActionComponent,
					ExcursionComponent,
					DeityComponent
				]));

			this.engine.addEntity(player);
			return player;
		},

		createLevel: function (saveKey, pos, levelVO) {
			var level = new Ash.Entity()
				.add(new LevelComponent(pos, levelVO.isCampable, levelVO.isHard, levelVO.notCampableReason, levelVO.populationFactor, levelVO.raidDangerFactor, levelVO.minX, levelVO.maxX, levelVO.minY, levelVO.maxY))
				.add(new PositionComponent(pos))
				.add(new LevelPassagesComponent())
				.add(new SaveComponent(saveKey, [CampComponent, VisitedComponent]));
			this.engine.addEntity(level);
			return level;
		},

		createSector: function (saveKey, level, posX, posY, passageOptions, movementBlockers, sectorFeatures, locales, criticalPaths, enemies, hasRegularEnemies, localeEnemyNum) {
			var sector = new Ash.Entity()
				.add(new SectorComponent())
				.add(new ResourcesComponent(0))
				.add(new ResourceAccumulationComponent(saveKey))
				.add(new EnemiesComponent(hasRegularEnemies, enemies))
				.add(new SectorImprovementsComponent())
				.add(new PositionComponent(level, posX, posY))
				.add(new SectorControlComponent(localeEnemyNum))
				.add(new MovementOptionsComponent())
				.add(new SectorStatusComponent())
				.add(new PassagesComponent(
					passageOptions.passageUpType,
					passageOptions.passageDownType,
					movementBlockers))
				.add(new SectorFeaturesComponent(
					level,
					sectorFeatures.criticalPaths,
					sectorFeatures.zone,
					sectorFeatures.buildingDensity,
					sectorFeatures.wear,
					sectorFeatures.damage,
					sectorFeatures.sectorType,
					sectorFeatures.sunlit,
					sectorFeatures.ground,
					sectorFeatures.hazards,
					sectorFeatures.isCamp,
					sectorFeatures.notCampableReason,
					sectorFeatures.resourcesScavengable,
					sectorFeatures.resourcesCollectable,
					sectorFeatures.itemsScavengeable,
					sectorFeatures.hasSpring,
					sectorFeatures.hasTradeConnectorSpot,
					sectorFeatures.stashes,
					sectorFeatures.waymarks
				))
				.add(new SectorLocalesComponent(locales))
				.add(new SaveComponent(saveKey, [
					ResourcesComponent,
					CampComponent,
					CurrencyComponent,
					ReputationComponent,
					OutgoingCaravansComponent,
					CampEventTimersComponent,
					SectorImprovementsComponent,
					SectorStatusComponent,
					SectorControlComponent,
					RecruitComponent,
					TraderComponent,
					VisitedComponent,
					RevealedComponent,
					LastVisitedCampComponent
				]));

			if (sectorFeatures.hasWorkshop) {
				 sector.add(new WorkshopComponent(sectorFeatures.workshopResource, sectorFeatures.hasClearableWorkshop, sectorFeatures.hasBuildableWorkshop));
			}

			this.engine.addEntity(sector);
			return sector;
		},

		createTribe: function (saveKey) {
			var tribe = new Ash.Entity()
				.add(new TribeComponent())
				.add(new ResourcesComponent(0))
				.add(new CurrencyComponent(0))
				.add(new UpgradesComponent())
				.add(new ResourceAccumulationComponent(saveKey))
				.add(new SaveComponent(saveKey, [UpgradesComponent, ResourcesComponent, CurrencyComponent]));
			this.engine.addEntity(tribe);
			return tribe;
		},

		createGang: function (saveKey, level, posX, posY, gangVO) {
			var gang = new Ash.Entity()
				.add(new PositionComponent(level, posX, posY))
				.add(new GangComponent(gangVO))
				.add(new SaveComponent(saveKey, [GangComponent]));
			this.engine.addEntity(gang);
			return gang;
		},

		initPlayer: function (entity) {
			var defaultInjury = PerkConstants.perkDefinitions.injury[0].clone();
			defaultInjury.startTimer = PerkConstants.TIMER_DISABLED;
			defaultInjury.removeTimer = PerkConstants.TIMER_DISABLED;
			var perksComponent = entity.get(PerksComponent);
			perksComponent.addPerk(defaultInjury);
			entity.add(new ExcursionComponent());

			var logComponent = entity.get(LogMessagesComponent);
			logComponent.addMessage(LogConstants.MSG_ID_START, "You are alone in a massive dark corridor, far below sunlight.");
		},

		syncPlayer: function (entity) {
			var inCamp = entity.get(PositionComponent).inCamp;
			if (!inCamp && !entity.has(ExcursionComponent)) {
				entity.add(new ExcursionComponent());
			}
		},

		syncSector: function (entity) {
			if (entity.has(CampComponent) && !entity.has(CampEventTimersComponent)) {
				entity.add(new CampEventTimersComponent());
			}
			if (entity.has(CampComponent) && !entity.has(ReputationComponent)) {
				entity.add(new ReputationComponent());
			}
			if (entity.has(CampComponent) && !entity.has(CurrencyComponent)) {
				entity.add(new CurrencyComponent(0));
			}
			if (entity.has(CampComponent) && !entity.has(OutgoingCaravansComponent)) {
				entity.add(new OutgoingCaravansComponent());
			}

			let improvementsComponent = entity.get(SectorImprovementsComponent);

			if (improvementsComponent.hasCollectors() && !entity.has(SectorCollectorsComponent)) {
				entity.add(new SectorCollectorsComponent());
			}
			if (improvementsComponent.getCount(improvementNames.beacon) > 0 && !entity.has(BeaconComponent)) {
				entity.add(new BeaconComponent);
			}
		},
	});

	return EntityCreator;
});
<commit_msg>[start] fix health/thirst perk at start having a start timer<commit_after>define([
	'ash',
	'game/constants/LogConstants',
	'game/constants/PerkConstants',
	'game/constants/ItemConstants',
	'game/constants/PositionConstants',
	'game/components/player/BagComponent',
	'game/components/player/ExcursionComponent',
	'game/components/player/VisionComponent',
	'game/components/player/StaminaComponent',
	'game/components/sector/ReputationComponent',
	'game/components/player/RumoursComponent',
	'game/components/player/EvidenceComponent',
	'game/components/player/DeityComponent',
	'game/components/player/FollowersComponent',
	'game/components/player/ItemsComponent',
	'game/components/player/PerksComponent',
	'game/components/type/GangComponent',
	'game/components/type/PlayerComponent',
	'game/components/type/TribeComponent',
	'game/components/type/LevelComponent',
	'game/components/type/SectorComponent',
	'game/components/common/PositionComponent',
	'game/components/player/PlayerActionComponent',
	'game/components/common/ResourcesComponent',
	'game/components/common/CurrencyComponent',
	'game/components/common/ResourceAccumulationComponent',
	'game/components/common/VisitedComponent',
	'game/components/common/RevealedComponent',
	'game/components/common/LogMessagesComponent',
	'game/components/common/SaveComponent',
	'game/components/sector/improvements/BeaconComponent',
	'game/components/sector/improvements/SectorImprovementsComponent',
	'game/components/sector/improvements/SectorCollectorsComponent',
	'game/components/sector/improvements/WorkshopComponent',
	'game/components/sector/events/RecruitComponent',
	'game/components/sector/events/TraderComponent',
	'game/components/sector/SectorStatusComponent',
	'game/components/sector/SectorControlComponent',
	'game/components/sector/EnemiesComponent',
	'game/components/sector/events/CampEventTimersComponent',
	'game/components/sector/OutgoingCaravansComponent',
	'game/components/sector/MovementOptionsComponent',
	'game/components/sector/PassagesComponent',
	'game/components/sector/SectorFeaturesComponent',
	'game/components/sector/SectorLocalesComponent',
	'game/components/sector/LastVisitedCampComponent',
	'game/components/common/CampComponent',
	'game/components/tribe/UpgradesComponent',
	'game/components/level/LevelPassagesComponent'
], function (
	Ash,
	LogConstants,
	PerkConstants,
	ItemConstants,
	PositionConstants,
	BagComponent,
	ExcursionComponent,
	VisionComponent,
	StaminaComponent,
	ReputationComponent,
	RumoursComponent,
	EvidenceComponent,
	DeityComponent,
	FollowersComponent,
	ItemsComponent,
	PerksComponent,
	GangComponent,
	PlayerComponent,
	TribeComponent,
	LevelComponent,
	SectorComponent,
	PositionComponent,
	PlayerActionComponent,
	ResourcesComponent,
	CurrencyComponent,
	ResourceAccumulationComponent,
	VisitedComponent,
	RevealedComponent,
	LogMessagesComponent,
	SaveComponent,
	BeaconComponent,
	SectorImprovementsComponent,
	SectorCollectorsComponent,
	WorkshopComponent,
	RecruitComponent,
	TraderComponent,
	SectorStatusComponent,
	SectorControlComponent,
	EnemiesComponent,
	CampEventTimersComponent,
	OutgoingCaravansComponent,
	MovementOptionsComponent,
	PassagesComponent,
	SectorFeaturesComponent,
	SectorLocalesComponent,
	LastVisitedCampComponent,
	CampComponent,
	UpgradesComponent,
	LevelPassagesComponent
) {
	var EntityCreator = Ash.Class.extend({

		engine: null,

		constructor: function (engine) {
			this.engine = engine;
		},

		destroyEntity: function (entity) {
			this.engine.removeEntity(entity);
		},

		createPlayer: function (saveKey) {
			var player = new Ash.Entity()
				.add(new PlayerComponent())
				.add(new BagComponent(0))
				.add(new VisionComponent(0))
				.add(new ItemsComponent())
				.add(new FollowersComponent())
				.add(new PerksComponent())
				.add(new StaminaComponent(1000))
				.add(new ResourcesComponent(ItemConstants.PLAYER_DEFAULT_STORAGE))
				.add(new CurrencyComponent(0))
				.add(new ResourceAccumulationComponent(saveKey))
				.add(new RumoursComponent())
				.add(new EvidenceComponent())
				.add(new PositionComponent(13, 0, 0, false))
				.add(new LogMessagesComponent())
				.add(new PlayerActionComponent())
				.add(new SaveComponent(saveKey, [
					ResourcesComponent,
					CurrencyComponent,
					VisionComponent,
					ItemsComponent,
					FollowersComponent,
					PerksComponent,
					StaminaComponent,
					PositionComponent,
					RumoursComponent,
					EvidenceComponent,
					LogMessagesComponent,
					PlayerActionComponent,
					ExcursionComponent,
					DeityComponent
				]));

			this.engine.addEntity(player);
			return player;
		},

		createLevel: function (saveKey, pos, levelVO) {
			var level = new Ash.Entity()
				.add(new LevelComponent(pos, levelVO.isCampable, levelVO.isHard, levelVO.notCampableReason, levelVO.populationFactor, levelVO.raidDangerFactor, levelVO.minX, levelVO.maxX, levelVO.minY, levelVO.maxY))
				.add(new PositionComponent(pos))
				.add(new LevelPassagesComponent())
				.add(new SaveComponent(saveKey, [CampComponent, VisitedComponent]));
			this.engine.addEntity(level);
			return level;
		},

		createSector: function (saveKey, level, posX, posY, passageOptions, movementBlockers, sectorFeatures, locales, criticalPaths, enemies, hasRegularEnemies, localeEnemyNum) {
			var sector = new Ash.Entity()
				.add(new SectorComponent())
				.add(new ResourcesComponent(0))
				.add(new ResourceAccumulationComponent(saveKey))
				.add(new EnemiesComponent(hasRegularEnemies, enemies))
				.add(new SectorImprovementsComponent())
				.add(new PositionComponent(level, posX, posY))
				.add(new SectorControlComponent(localeEnemyNum))
				.add(new MovementOptionsComponent())
				.add(new SectorStatusComponent())
				.add(new PassagesComponent(
					passageOptions.passageUpType,
					passageOptions.passageDownType,
					movementBlockers))
				.add(new SectorFeaturesComponent(
					level,
					sectorFeatures.criticalPaths,
					sectorFeatures.zone,
					sectorFeatures.buildingDensity,
					sectorFeatures.wear,
					sectorFeatures.damage,
					sectorFeatures.sectorType,
					sectorFeatures.sunlit,
					sectorFeatures.ground,
					sectorFeatures.hazards,
					sectorFeatures.isCamp,
					sectorFeatures.notCampableReason,
					sectorFeatures.resourcesScavengable,
					sectorFeatures.resourcesCollectable,
					sectorFeatures.itemsScavengeable,
					sectorFeatures.hasSpring,
					sectorFeatures.hasTradeConnectorSpot,
					sectorFeatures.stashes,
					sectorFeatures.waymarks
				))
				.add(new SectorLocalesComponent(locales))
				.add(new SaveComponent(saveKey, [
					ResourcesComponent,
					CampComponent,
					CurrencyComponent,
					ReputationComponent,
					OutgoingCaravansComponent,
					CampEventTimersComponent,
					SectorImprovementsComponent,
					SectorStatusComponent,
					SectorControlComponent,
					RecruitComponent,
					TraderComponent,
					VisitedComponent,
					RevealedComponent,
					LastVisitedCampComponent
				]));

			if (sectorFeatures.hasWorkshop) {
				 sector.add(new WorkshopComponent(sectorFeatures.workshopResource, sectorFeatures.hasClearableWorkshop, sectorFeatures.hasBuildableWorkshop));
			}

			this.engine.addEntity(sector);
			return sector;
		},

		createTribe: function (saveKey) {
			var tribe = new Ash.Entity()
				.add(new TribeComponent())
				.add(new ResourcesComponent(0))
				.add(new CurrencyComponent(0))
				.add(new UpgradesComponent())
				.add(new ResourceAccumulationComponent(saveKey))
				.add(new SaveComponent(saveKey, [UpgradesComponent, ResourcesComponent, CurrencyComponent]));
			this.engine.addEntity(tribe);
			return tribe;
		},

		createGang: function (saveKey, level, posX, posY, gangVO) {
			var gang = new Ash.Entity()
				.add(new PositionComponent(level, posX, posY))
				.add(new GangComponent(gangVO))
				.add(new SaveComponent(saveKey, [GangComponent]));
			this.engine.addEntity(gang);
			return gang;
		},

		initPlayer: function (entity) {
			var defaultInjury = PerkConstants.perkDefinitions.injury[0].clone();
			defaultInjury.startTimer = PerkConstants.TIMER_DISABLED;
			defaultInjury.removeTimer = PerkConstants.TIMER_DISABLED;
			var perksComponent = entity.get(PerksComponent);
			perksComponent.addPerk(defaultInjury);
			perksComponent.addPerk(PerkConstants.getPerk(PerkConstants.perkIds.hunger));
			perksComponent.addPerk(PerkConstants.getPerk(PerkConstants.perkIds.thirst));
			entity.add(new ExcursionComponent());

			var logComponent = entity.get(LogMessagesComponent);
			logComponent.addMessage(LogConstants.MSG_ID_START, "You are alone in a massive dark corridor, far below sunlight.");
		},

		syncPlayer: function (entity) {
			var inCamp = entity.get(PositionComponent).inCamp;
			if (!inCamp && !entity.has(ExcursionComponent)) {
				entity.add(new ExcursionComponent());
			}
		},

		syncSector: function (entity) {
			if (entity.has(CampComponent) && !entity.has(CampEventTimersComponent)) {
				entity.add(new CampEventTimersComponent());
			}
			if (entity.has(CampComponent) && !entity.has(ReputationComponent)) {
				entity.add(new ReputationComponent());
			}
			if (entity.has(CampComponent) && !entity.has(CurrencyComponent)) {
				entity.add(new CurrencyComponent(0));
			}
			if (entity.has(CampComponent) && !entity.has(OutgoingCaravansComponent)) {
				entity.add(new OutgoingCaravansComponent());
			}

			let improvementsComponent = entity.get(SectorImprovementsComponent);

			if (improvementsComponent.hasCollectors() && !entity.has(SectorCollectorsComponent)) {
				entity.add(new SectorCollectorsComponent());
			}
			if (improvementsComponent.getCount(improvementNames.beacon) > 0 && !entity.has(BeaconComponent)) {
				entity.add(new BeaconComponent);
			}
		},
	});

	return EntityCreator;
});
<|endoftext|>


<commit_before>import Phaser from 'phaser-ce';
import swal from 'sweetalert2';
import { tissueColor, boneMarrowColor, bloodColor, bloodColorCSS } from './constants';
import { modalService } from '../modals';

export default class TutorialState extends Phaser.State {

    modalFactor = 1;
    isDead = false;

    health = {
        bar: null,
        text: null,
        percent: 100
    };

    tissue = {
        bacteria: {
            amount: 0,
            group: null,
            sprite: null,
            text: null
        },
        macrophage: null,
        neutrophil: null
    };

    boneMarrow = {
        backdrop: null,
        macrophage: null,
        neutrophil: null
    };

    preload() {
        this.game.load.image('bacteria', 'assets/sprites/bacteria.png');
        this.game.load.image('macrophage', 'assets/sprites/macrophage.png');
        this.game.load.image('macrophage_active', 'assets/sprites/macrophage_active.png');
        this.game.load.image('neutrophil', 'assets/sprites/neutrophil.png');
    }

    create() {
        console.log(this);

        this.createHealth();
        this.createTissue();
        this.createBoneMarrow();
        this.startBacteriaTimer();
    }

    createHealth() {
        this.health.bar = this.game.add.graphics(0, 0);
        this.health.text = this.game.add.text(1, 0, '100%', { font: 'normal 12pt Arial' });
    }

    createSprite(x, y, key) {
        const sprite = this.game.add.sprite(x, y, key);

        sprite.anchor.set(0.5);

        return sprite;
    }

    createBacteria(x, y) {
        return this.createSprite(x, y, 'bacteria');
    }

    createMacrophage(x, y) {
        return this.createSprite(x, y, 'macrophage');
    }

    createNeutrophil(x, y) {
        return this.createSprite(x, y, 'neutrophil');
    }

    createTissue() {
        this.stage.backgroundColor = tissueColor;

        this.createTissueBacteria();
        this.createTissueMacrophageAnimation();

        this.createTissueMacrophage();
        this.createTissueNeutrophil();
    }

    createTissueBacteria() {
        const bacteria = this.createBacteria(this.world.centerX, 100);
        bacteria.rotation += 0.5;
        bacteria.data.rotation = 0.01;

        const scale = (this.world.width * 0.6) / bacteria.width;
        bacteria.scale.setTo(scale);

        const text = this.game.add.text(bacteria.centerX, bacteria.centerY / 2, 'x' + this.tissue.bacteria.amount, { font: 'normal 12pt Arial' });

        const group = this.game.add.group();
        group.visible = false;

        group.add(bacteria);
        group.add(text);

        this.tissue.bacteria.group = group;
        this.tissue.bacteria.sprite = bacteria;
        this.tissue.bacteria.text = text;
    }

    createTissueMacrophage() {
        const x = this.world.centerX * 0.6;
        const macrophage = this.createMacrophage(x, this.world.centerY);
        macrophage.rotation -= 0.8;

        const scale = (this.world.width * 0.5) / macrophage.width;
        macrophage.scale.setTo(scale);

        macrophage.visible = false;

        this.tissue.macrophage = macrophage;
    }

    createTissueMacrophageAnimation() {
        this.time.events.loop(Phaser.Timer.QUARTER * 2, () => {
            const { macrophage } = this.tissue;
            const key = macrophage.key === 'macrophage' ? 'macrophage_active' : 'macrophage';

            macrophage.loadTexture(key);
        });
    }

    createTissueNeutrophil() {
        const x = this.world.centerX * 1.4;
        const y = this.world.centerY * 1.25;
        const neutrophil = this.createNeutrophil(x, y);

        const scale = (this.world.width * 0.4) / neutrophil.width;
        neutrophil.scale.setTo(scale);

        neutrophil.visible = false;

        this.tissue.neutrophil = neutrophil;
    }

    createBoneMarrow() {
        this.createBoneMarrowBackdrop();
        this.createBoneMarrowMacrophage();
        this.createBoneMarrowNeutrophil();
    }

    createBoneMarrowBackdrop() {
        const graphics = this.game.add.graphics(0, 0);

        graphics.beginFill(boneMarrowColor);
        graphics.drawRect(0, 0, this.world.width, this.world.height / 5);
        graphics.endFill();

        this.boneMarrow.backdrop = this.game.add.sprite(0, this.world.height - this.world.height / 5, graphics.generateTexture());
        this.boneMarrow.backdrop.visible = false;

        graphics.destroy();
    }

    createBoneMarrowMacrophage() {
        const { backdrop } = this.boneMarrow;
        const x = backdrop.centerX / 2;
        const macrophage = this.createMacrophage(x, backdrop.centerY);

        const scale = (backdrop.height * 0.8) / macrophage.height;
        macrophage.scale.setTo(scale);

        macrophage.visible = false;
        macrophage.inputEnabled = true;

        macrophage.events.onInputDown.add(() => this.addMacrophageToTissue());

        this.boneMarrow.macrophage = macrophage;
    }

    createBoneMarrowNeutrophil() {
        const { backdrop, macrophage } = this.boneMarrow;
        const x = backdrop.width - macrophage.x;
        const neutrophil = this.createNeutrophil(x, backdrop.centerY);

        const scale = (backdrop.height * 0.9) / neutrophil.height;
        neutrophil.scale.setTo(scale);

        neutrophil.visible = false;
        neutrophil.inputEnabled = true;

        neutrophil.events.onInputDown.add(() => this.addNeutrophilToTissue());

        this.boneMarrow.neutrophil = neutrophil;
    }

    startBacteriaTimer() {
        this.time.events.add(Phaser.Timer.SECOND * 2 * this.modalFactor, () => {
            const { bacteria } = this.tissue;
            bacteria.group.visible = true;
            bacteria.amount = 1;

            this.startBacteriaGrowthTimer();
            this.startHealthTimer();
            this.startBacteriaModalTimer();
        });
    }

    startBacteriaGrowthTimer() {
        this.time.events.loop(Phaser.Timer.QUARTER, () => {
            const { bacteria } = this.tissue;
            const growth = Math.floor(1 + Math.log(bacteria.amount));
            bacteria.amount += growth;
        });
    }

    startHealthTimer() {
        this.time.events.loop(Phaser.Timer.QUARTER, () => {
            const { bacteria } = this.tissue;
            const damage = 0.1 + Math.log10(bacteria.amount) * 0.1;
            this.health.percent -= damage;
        });
    }

    startBacteriaModalTimer() {
        this.time.events.add(Phaser.Timer.SECOND * 2 * this.modalFactor, () => {
           this.showBacteriaModal();
        });
    }

    showBacteriaModal() {
        modalService.showBacteriaModal()
            .then(() => this.startMacrophageModalTimer());
    }

    startMacrophageModalTimer() {
        this.time.events.add(Phaser.Timer.SECOND * 3 * this.modalFactor, () => {
            this.showMacrophageModal();
        });
    }

    showMacrophageModal() {
        modalService.showMacrophageModal()
            .then(() => {
                this.boneMarrow.backdrop.visible = true;
                this.boneMarrow.macrophage.visible = true;
            });
    }

    update() {
        this.tissue.neutrophil.rotation += 0.02;

        this.updateBacteria();
        this.updateHealth();
    }

    updateBacteria() {
        const { bacteria } = this.tissue;
        const { sprite } = bacteria;
        sprite.rotation += sprite.data.rotation;

        if (sprite.rotation < 0.25 || sprite.rotation > 0.75) {
            sprite.data.rotation *= -1;
        }

        bacteria.text.text = 'x' + bacteria.amount;
    }

    updateHealth() {
        if (this.health.percent < 0) {
            this.health.percent = 0;
        }

        const { percent } = this.health;

        let rounded = Math.floor(percent);

        if (percent > 0 && rounded === 0) {
            rounded = 1;
        }

        this.health.text.text = rounded + '%';

        this.health.bar.clear();
        this.health.bar.beginFill(bloodColor);
        this.health.bar.drawRect(0, 0, this.world.width * (this.health.percent / 100), 20);
        this.health.bar.endFill();

        if (!this.isDead && rounded === 0) {
            this.isDead = true;

            this.time.events.add(Phaser.Timer.QUARTER, () => this.gameOver());
        }
    }

    addMacrophageToTissue() {
        const { macrophage } = this.tissue;

        if (!macrophage.visible) {
            macrophage.visible = true;

            this.startNeutrophilModalTimer();
        }
    }

    startNeutrophilModalTimer() {
        this.time.events.add(Phaser.Timer.SECOND * 5 * this.modalFactor, () => {
            this.showNeutrophilModal();
        });
    }

    showNeutrophilModal() {
        modalService.showNeutrophilModal()
            .then(() => {
                this.boneMarrow.neutrophil.visible = true;
            });
    }

    addNeutrophilToTissue() {
        this.tissue.neutrophil.visible = true;
    }

    gameOver() {
        this.gameOver = true;
        this.game.paused = true;
        const toMainMenu = () => this.game.router.push({ name: 'MainMenuScreen' });

        swal({
            title: 'Game Over',
            text: 'The body suffered fatal damage from bacteria cells.',
            type: 'error',
            confirmButtonText: 'Sorry 😰',
            confirmButtonColor: bloodColorCSS
        }).then(toMainMenu).catch(toMainMenu);
    }

}<commit_msg>Create groups for all cells<commit_after>import Phaser from 'phaser-ce';
import swal from 'sweetalert2';
import { tissueColor, boneMarrowColor, bloodColor, bloodColorCSS } from './constants';
import { modalService } from '../modals';

export default class TutorialState extends Phaser.State {

    modalFactor = 1;
    isDead = false;

    health = {
        bar: null,
        text: null,
        percent: 100
    };

    tissue = {
        bacteria: {
            amount: 0,
            group: null,
            sprite: null,
            text: null
        },
        macrophage: {
            amount: 0,
            group: null,
            sprite: null,
            text: null
        },
        neutrophil: {
            amount: 0,
            group: null,
            sprite: null,
            text: null
        }
    };

    boneMarrow = {
        backdrop: null,
        macrophage: {
            amount: 0,
            group: null,
            sprite: null,
            text: null
        },
        neutrophil: {
            amount: 0,
            group: null,
            sprite: null,
            text: null
        }
    };

    preload() {
        this.game.load.image('bacteria', 'assets/sprites/bacteria.png');
        this.game.load.image('macrophage', 'assets/sprites/macrophage.png');
        this.game.load.image('macrophage_active', 'assets/sprites/macrophage_active.png');
        this.game.load.image('neutrophil', 'assets/sprites/neutrophil.png');
    }

    create() {
        console.log(this);

        this.createHealth();
        this.createTissue();
        this.createBoneMarrow();
        this.startBacteriaTimer();
    }

    createHealth() {
        this.health.bar = this.game.add.graphics(0, 0);
        this.health.text = this.game.add.text(1, 0, '100%', { font: 'normal 12pt Arial' });
    }

    createSprite(x, y, key) {
        const sprite = this.game.add.sprite(x, y, key);

        sprite.anchor.set(0.5);

        return sprite;
    }

    createBacteria(x, y) {
        return this.createSprite(x, y, 'bacteria');
    }

    createMacrophage(x, y) {
        return this.createSprite(x, y, 'macrophage');
    }

    createNeutrophil(x, y) {
        return this.createSprite(x, y, 'neutrophil');
    }

    createTissue() {
        this.stage.backgroundColor = tissueColor;

        this.createTissueBacteria();
        this.createTissueMacrophageAnimation();

        this.createTissueMacrophage();
        this.createTissueNeutrophil();
    }

    createTissueBacteria() {
        const bacteria = this.createBacteria(this.world.centerX, 100);
        bacteria.rotation += 0.5;
        bacteria.data.rotation = 0.01;

        const scale = (this.world.width * 0.6) / bacteria.width;
        bacteria.scale.setTo(scale);

        const text = this.game.add.text(bacteria.centerX, bacteria.centerY / 2, 'x' + this.tissue.bacteria.amount, { font: 'normal 12pt Arial' });

        const group = this.game.add.group();
        group.visible = false;

        group.add(bacteria);
        group.add(text);

        this.tissue.bacteria.group = group;
        this.tissue.bacteria.sprite = bacteria;
        this.tissue.bacteria.text = text;
    }

    createTissueMacrophage() {
        const x = this.world.centerX * 0.6;
        const macrophage = this.createMacrophage(x, this.world.centerY);
        macrophage.rotation -= 0.8;

        const scale = (this.world.width * 0.5) / macrophage.width;
        macrophage.scale.setTo(scale);

        const group = this.game.add.group();
        group.visible = false;

        group.add(macrophage);

        this.tissue.macrophage.group = group;
        this.tissue.macrophage.sprite = macrophage;
        //this.tissue.bacteria.text = text;
    }

    createTissueMacrophageAnimation() {
        this.time.events.loop(Phaser.Timer.QUARTER * 2, () => {
            const macrophage = this.tissue.macrophage.sprite;
            const key = macrophage.key === 'macrophage' ? 'macrophage_active' : 'macrophage';

            macrophage.loadTexture(key);
        });
    }

    createTissueNeutrophil() {
        const x = this.world.centerX * 1.4;
        const y = this.world.centerY * 1.25;
        const neutrophil = this.createNeutrophil(x, y);

        const scale = (this.world.width * 0.4) / neutrophil.width;
        neutrophil.scale.setTo(scale);

        const group = this.game.add.group();
        group.visible = false;

        group.add(neutrophil);

        this.tissue.neutrophil.group = group;
        this.tissue.neutrophil.sprite = neutrophil;
    }

    createBoneMarrow() {
        this.createBoneMarrowBackdrop();
        this.createBoneMarrowMacrophage();
        this.createBoneMarrowNeutrophil();
    }

    createBoneMarrowBackdrop() {
        const graphics = this.game.add.graphics(0, 0);

        graphics.beginFill(boneMarrowColor);
        graphics.drawRect(0, 0, this.world.width, this.world.height / 5);
        graphics.endFill();

        this.boneMarrow.backdrop = this.game.add.sprite(0, this.world.height - this.world.height / 5, graphics.generateTexture());
        this.boneMarrow.backdrop.visible = false;

        graphics.destroy();
    }

    createBoneMarrowMacrophage() {
        const { backdrop } = this.boneMarrow;
        const x = backdrop.centerX / 2;
        const macrophage = this.createMacrophage(x, backdrop.centerY);

        const scale = (backdrop.height * 0.8) / macrophage.height;
        macrophage.scale.setTo(scale);

        macrophage.inputEnabled = true;

        macrophage.events.onInputDown.add(() => this.addMacrophageToTissue());

        const group = this.game.add.group();
        group.visible = false;

        group.add(macrophage);

        this.boneMarrow.macrophage.group = group;
        this.boneMarrow.macrophage.sprite = macrophage;
    }

    createBoneMarrowNeutrophil() {
        const { backdrop, macrophage } = this.boneMarrow;
        const x = backdrop.width - macrophage.sprite.x;
        const neutrophil = this.createNeutrophil(x, backdrop.centerY);

        const scale = (backdrop.height * 0.9) / neutrophil.height;
        neutrophil.scale.setTo(scale);

        neutrophil.inputEnabled = true;

        neutrophil.events.onInputDown.add(() => this.addNeutrophilToTissue());

        const group = this.game.add.group();
        group.visible = false;

        group.add(neutrophil);

        this.boneMarrow.neutrophil.group = group;
        this.boneMarrow.neutrophil.sprite = neutrophil;
    }

    startBacteriaTimer() {
        this.time.events.add(Phaser.Timer.SECOND * 2 * this.modalFactor, () => {
            const { bacteria } = this.tissue;
            bacteria.group.visible = true;
            bacteria.amount = 1;

            this.startBacteriaGrowthTimer();
            this.startHealthTimer();
            this.startBacteriaModalTimer();
        });
    }

    startBacteriaGrowthTimer() {
        this.time.events.loop(Phaser.Timer.QUARTER, () => {
            const { bacteria } = this.tissue;
            const growth = Math.floor(1 + Math.log(bacteria.amount));
            bacteria.amount += growth;
        });
    }

    startHealthTimer() {
        this.time.events.loop(Phaser.Timer.QUARTER, () => {
            const { bacteria } = this.tissue;
            const damage = 0.1 + Math.log10(bacteria.amount) * 0.1;
            this.health.percent -= damage;
        });
    }

    startBacteriaModalTimer() {
        this.time.events.add(Phaser.Timer.SECOND * 2 * this.modalFactor, () => {
           this.showBacteriaModal();
        });
    }

    showBacteriaModal() {
        modalService.showBacteriaModal()
            .then(() => this.startMacrophageModalTimer());
    }

    startMacrophageModalTimer() {
        this.time.events.add(Phaser.Timer.SECOND * 3 * this.modalFactor, () => {
            this.showMacrophageModal();
        });
    }

    showMacrophageModal() {
        modalService.showMacrophageModal()
            .then(() => {
                this.boneMarrow.backdrop.visible = true;
                this.boneMarrow.macrophage.group.visible = true;
            });
    }

    update() {
        this.tissue.neutrophil.sprite.rotation += 0.02;

        this.updateBacteria();
        this.updateHealth();
    }

    updateBacteria() {
        const { bacteria } = this.tissue;
        const { sprite } = bacteria;
        sprite.rotation += sprite.data.rotation;

        if (sprite.rotation < 0.25 || sprite.rotation > 0.75) {
            sprite.data.rotation *= -1;
        }

        bacteria.text.text = 'x' + bacteria.amount;
    }

    updateHealth() {
        if (this.health.percent < 0) {
            this.health.percent = 0;
        }

        const { percent } = this.health;

        let rounded = Math.floor(percent);

        if (percent > 0 && rounded === 0) {
            rounded = 1;
        }

        this.health.text.text = rounded + '%';

        this.health.bar.clear();
        this.health.bar.beginFill(bloodColor);
        this.health.bar.drawRect(0, 0, this.world.width * (this.health.percent / 100), 20);
        this.health.bar.endFill();

        if (!this.isDead && rounded === 0) {
            this.isDead = true;

            this.time.events.add(Phaser.Timer.QUARTER, () => this.gameOver());
        }
    }

    addMacrophageToTissue() {
        const { macrophage } = this.tissue;

        if (!macrophage.group.visible) {
            macrophage.group.visible = true;

            this.startNeutrophilModalTimer();
        }
    }

    startNeutrophilModalTimer() {
        this.time.events.add(Phaser.Timer.SECOND * 5 * this.modalFactor, () => {
            this.showNeutrophilModal();
        });
    }

    showNeutrophilModal() {
        modalService.showNeutrophilModal()
            .then(() => {
                this.boneMarrow.neutrophil.group.visible = true;
            });
    }

    addNeutrophilToTissue() {
        this.tissue.neutrophil.group.visible = true;
    }

    gameOver() {
        this.gameOver = true;
        this.game.paused = true;
        const toMainMenu = () => this.game.router.push({ name: 'MainMenuScreen' });

        swal({
            title: 'Game Over',
            text: 'The body suffered fatal damage from bacteria cells.',
            type: 'error',
            confirmButtonText: 'Sorry 😰',
            confirmButtonColor: bloodColorCSS
        }).then(toMainMenu).catch(toMainMenu);
    }

}<|endoftext|>


<commit_before>/** internal
 *  class Base
 *
 *  Base class for [[Environment]] and [[Index]].
 *
 *
 *  ##### INCLUDES
 *
 *  - [[Engines]]
 *  - [[Mime]]
 *  - [[Processing]]
 *  - [[Paths]]
 **/

'use strict';

// stdlib
var fs      = require('fs');
var path    = require('path');
var crypto  = require('crypto');

// 3rd-party
var _     = require('underscore');
var async = require('async');

// internal
var getter          = require('./common').getter;
var prop            = require('./common').prop;
var mixin           = require('./common').mixin;
var isAbsolute      = require('./common').isAbsolute;
var VERSION         = require('./version');
var AssetAttributes = require('./asset_attributes');
var StaticAsset     = require('./assets/static');
var ProcessedAsset  = require('./assets/processed');
var BundledAsset    = require('./assets/bundled');

////////////////////////////////////////////////////////////////////////////////

/**
 *  new Base()
 **/
var Base = module.exports = function Base() {};

mixin(Base.prototype, require('./helpers/paths'));
mixin(Base.prototype, require('./helpers/mime'));
mixin(Base.prototype, require('./helpers/processing'));
mixin(Base.prototype, require('./helpers/engines'));

// helper that defines property which fires expireIndex()
// each time it gets changed
function attr_with_expire_index(name, value) {
  var __name__ = '__' + name + '__';

  // set underlying value
  prop(Base.prototype, __name__, value, {writable: true});

  // provide getters/setter
  Object.defineProperty(Base.prototype, name, {
    get: function () {
      return this[__name__];
    },
    set: function (val) {
      this.expireIndex();
      this[__name__] = val;
    }
  });
}

/**
 *  Base#digestAlgorithm -> String
 *
 *  Digest algorithm: `sha1` or `md5`.
 *  See Node manual on `crypto` module.
 *
 *  Default: `md5`.
 **/
attr_with_expire_index('digestAlgorithm', 'md5');

/**
 *  Base#version -> String
 *
 *  Environment version.
 *
 *      environment.version = '2.0'
 **/
attr_with_expire_index('version', '');

/**
 *  Base#digest -> crypto.Hash
 *
 *  Returns a `crypto.Hash` instance for the `Environment`.
 *
 *  This value serves two purposes. If two `Environment`s have the
 *  same digest value they can be treated as equal. This is more
 *  useful for comparing environment states between processes rather
 *  than in the same. Two equal `Environment`s can share the same
 *  cached assets.
 *
 *  The value also provides a seed digest for all `Asset` digests.
 *  Any change in the environment digest will affect all of its assets.
 **/
getter(Base.prototype, 'digest', function () {
  // Do not cache, so the caller can safely mutate it with `.update`
  var digest = crypto.createHash(this.digestAlgorithm);

  // Mixin Mincer release version and custom environment version.
  // So any new releases will affect all your assets.
  digest.update(VERSION,      'utf8');
  digest.update(this.version, 'utf8');

  return digest;
});

// helper that overrides defined method `name` with wrapper that expireIndex()
// and calls `func` when provided before calling original method
function func_proxy_with_expire_index(name, func) {
  var orig = Base.prototype[name];

  Base.prototype[name] = function () {
    this.expireIndex();
    if (func) {
      func.apply(this, arguments);
    }
    orig.apply(this, arguments);
  };
}

//
// override [[Paths]] mixin methods
//

func_proxy_with_expire_index('prependPath');
func_proxy_with_expire_index('appendPath');
func_proxy_with_expire_index('clearPaths');

//
// override [[Mime]] mixin methods
//

func_proxy_with_expire_index('registerMimeType', function (mimeType, ext) {
  this.__trail__.extensions.append(ext);
});

//
// override [[Engines]] mixin methods
//

func_proxy_with_expire_index('registerEngine', function (ext, klass) {
  this.addEngineToTrail(ext, klass);
});

//
// override [[Processing]] mixin methods
//

func_proxy_with_expire_index('registerPreprocessor');
func_proxy_with_expire_index('registerPostprocessor');
func_proxy_with_expire_index('registerBundleProcessor');
func_proxy_with_expire_index('unregisterPreprocessor');
func_proxy_with_expire_index('unregisterPostprocessor');
func_proxy_with_expire_index('unregisterBundleProcessor');

/**
 *  Base#resolve(logicalPath[, options = {}[, fn]]) -> String
 *  - logicalPath (String)
 *  - options (Object)
 *  - fn (Function)
 *
 *  Finds the expanded real path for a given logical path by
 *  searching the environment's paths.
 *
 *      env.resolve("application.js")
 *      # => "/path/to/app/javascripts/application.js.coffee"
 *
 *  An Error with `code = 'FileNotFound'` is raised if the file does not exist.
 **/
Base.prototype.resolve = function (logicalPath, options, fn) {
  var err, resolved, args;

  if (fn) {
    args      = this.attributesFor(logicalPath).searchPaths;
    resolved  = this.__trail__.find(args, options, fn);
  } else {
    resolved = this.resolve(logicalPath, options, function (pathname) {
      return pathname;
    });

    if (!resolved) {
      err = new Error("couldn't find file '" + logicalPath + "'");
      err.code = 'FileNotFound';
      throw err;
    }
  }

  return resolved;
};

/**
 *  Base#entries(pathname) -> Array
 *  - pathname (String)
 *
 *  Proxy to `Hike.Trail#entries`. Works like `fs.readdirSync`.
 *  Subclasses may cache this method.
 **/
Base.prototype.entries = function (pathname) {
  return this.__trail__.entries(pathname);
};

/**
 *  Base#stat(pathname) -> fs.Stats
 *  - pathname (String)
 *
 *  Proxy to `Hike.Trail#stat`. Works like `fs.statSync`.
 *  Subclasses may cache this method.
 **/
Base.prototype.stat = function (pathname) {
  return this.__trail__.stat(pathname);
};

/**
 *  Base#getFileDigest(pathname) -> String
 *  - pathname (String)
 *
 *  Read and compute digest of filename.
 *  Subclasses may cache this method.
 **/
Base.prototype.getFileDigest = function (pathname) {
  var stat = this.stat(pathname);

  if (stat && stat.isFile()) {
    // If its a file, digest the contents
    return this.digest.update(fs.readFileSync(pathname)).digest('hex');
  } else if (stat && stat.isDirectory()) {
    // If its a directive, digest the list of filenames
    return this.digest.update(this.entries(pathname).join(',')).digest('hex');
  }
};

/** internal
 *  Base#attributesFor(pathname) -> AssetAttributes
 *  - pathname (String)
 *
 *  Returns a `AssetAttributes` for `pathname`
 **/
Base.prototype.attributesFor = function (pathname) {
  return new AssetAttributes(this, pathname);
};

/** internal
 *  Base#contentTypeOf(pathname) -> String
 *  - pathname (String)
 *
 *  Returns content type of `pathname`
 **/
Base.prototype.contentTypeOf = function (pathname) {
  return this.attributesFor(pathname).contentType;
};

/**
 *  Base#findAsset(pathname[, options = {}]) -> Asset
 *  - pathname (String)
 *  - options (Object)
 *
 *  Find asset by logical path or expanded path.
 **/
Base.prototype.findAsset = function (pathname, options) {
  var logical_path = pathname;

  if (isAbsolute(pathname)) {
    if (!this.stat(pathname)) {
      return;
    }

    logical_path = this.attributesFor(pathname).logicalPath;
  } else {
    try {
      pathname = this.resolve(logical_path);
    } catch (err) {
      if ('FileNotFound' === err.code) {
        return null;
      }

      throw err;
    }
  }

  return this.buildAsset(logical_path, pathname, options);
};

/**
 *  Base#precompile(files[, callback]) -> Void
 *  - files (Array):
 *  - callback (Function):
 *
 *  Helper to make sure that given list of `files` were compiled.
 *  Similar to [[Manifest#compile]], but does not write anything to disk.
 *
 *      environment.precompile(["app.js"], function (err, data) {
 *        //  data => {
 *        //    files: {
 *        //      "app.js" : "app-2e8e9a7c6b0aafa0c9bdeec90ea30213.js"
 *        //    },
 *        //    assets: {
 *        //      "app-2e8e9a7c6b0aafa0c9bdeec90ea30213.js" : {
 *        //        "logical_path"  : "app.js",
 *        //        "mtime"         : "2011-12-13T21:47:08-06:00",
 *        //        "digest"        : "2e8e9a7c6b0aafa0c9bdeec90ea30213"
 *        //      }
 *        //    }
 *        //  }
 *      });
 *
 *  Needed when you want to render HTML with some JavaScript injected right
 *  into your page (e.g. single-page offline documentation) and your template
 *  engine does not support asynchronous helpers (e.g. Jade requires helpers
 *  to be synchronous).
 **/
Base.prototype.precompile = function (files, callback) {
  var self     = this,
      data     = {files: {}, assets: {}},
      paths    = [];

  this.eachLogicalPath(files, function (pathname) {
    paths.push(pathname);
  });

  paths = _.union(paths, _.select(files, isAbsolute));
  async.forEachSeries(paths, function (pathname, next) {
    var asset = self.findAsset(pathname, {bundle: true});

    if (!asset) {
      next(new Error("Can not find asset '" + pathname + "'"));
      return;
    }

    asset.compile(function (err, asset) {
      if (err) {
        next(err);
        return;
      }

      data.assets[asset.logicalPath]  = asset.digestPath;
      data.files[asset.digestPath]    = {
        logical_path:   asset.logicalPath,
        mtime:          asset.mtime.toISOString(),
        size:           asset.length,
        digest:         asset.digest
      };

      next();
    });
  }, function (err) {
    callback(err, data);
  });
};

/**
 *  Base#eachEntry(root, iterator) -> Void
 *  - root (String)
 *  - iterator (Function)
 *
 *  Calls `iterator` on each found file or directory in alphabetical order:
 *
 *      env.eachEntry('/some/path', function (entry) {
 *        console.log(entry);
 *      });
 *      // -> "/some/path/a"
 *      // -> "/some/path/a/b.txt"
 *      // -> "/some/path/a/c.txt"
 *      // -> "/some/path/b.txt"
 **/
Base.prototype.eachEntry = function (root, iterator) {
  var self = this, paths = [];

  this.entries(root).forEach(function (filename) {
    var pathname  = path.join(root, filename);

    paths.push(pathname);

    if (self.stat(pathname).isDirectory()) {
      self.eachEntry(pathname, function (subpath) {
        paths.push(subpath);
      });
    }
  });

  paths.sort().forEach(iterator);
};

/**
 *  Base#eachFile(iterator) -> Void
 *  - iterator (Function)
 *
 *  Calls `iterator` for each file found within all registered paths.
 **/
Base.prototype.eachFile = function (iterator) {
  var self = this;

  this.paths.forEach(function (root) {
    self.eachEntry(root, function (pathname) {
      if (!self.stat(pathname).isDirectory()) {
        iterator(pathname);
      }
    });
  });
};

// Returns true if there were no filters, or `filename` matches at least one
function matches_filter(filters, filename) {
  if (0 === filters.length) {
    return true;
  }

  return _.any(filters, function (filter) {
    if (_.isRegExp(filter)) {
      return filter.test(filename);
    }

    if (_.isFunction(filter)) {
      return filter(filename);
    }

    // prepare string to become RegExp.
    // mimics shell's globbing
    filter = filter.toString().replace(/\*\*|\*|\?/, function (m) {
      switch (m[0]) {
        case "**":  return ".+?";
        case "*":   return "[^/]+";
        case "?":   return "[^/]?";
        default:    return m[0];
      }
    });

    // prepare RegExp
    filter = new RegExp('^' + filter + '$');
    return filter.test(filename);
  });
}

// Returns logicalPath for `filename` if it mtches given filters
function logical_path_for_filename(self, filters, filename) {
  var logical_path = self.attributesFor(filename).logicalPath;

  if (matches_filter(filters, logical_path)) {
    return logical_path;
  }

  // If filename is an index file, retest with alias
  if ('index' === path.basename(filename).split('.').shift()) {
    logical_path = logical_path.replace(/\/index\./, '.');
    if (matches_filter(filters, logical_path)) {
      return logical_path;
    }
  }
}

/**
 *  Base#eachLogicalPath(filters, iterator) -> Void
 *  - filters (Array)
 *  - iterator (Function)
 *
 *  Calls `iterator` on each found logical path (once per unique path) that
 *  matches at least one of the given filters.
 *
 *  Each filter might be a `String`, `RegExp` or a `Function`.
 **/
Base.prototype.eachLogicalPath = function (filters, iterator) {
  var self = this, files = {};

  this.eachFile(function (filename) {
    var logical_path = logical_path_for_filename(self, filters, filename);
    if (logical_path && !files[logical_path]) {
      iterator(logical_path);
      files[logical_path] = true;
    }
  });
};

// circular call  protection helper.
// keeps array of required pathnames until the function
// that originated protection finishes it's execution
var circular_calls = null;
function circular_call_protection(pathname, callback) {
  var reset   = (null === circular_calls),
      calls   = circular_calls || (circular_calls = []),
      result  = null,
      error   = null;

  if (0 <= calls.indexOf(pathname)) {
    if (reset) { circular_calls = null; }
    throw new Error("Circular dependency detected: " + pathname +
                    " has already been required");
  }

  calls.push(pathname);

  try {
    result = callback();
  } catch (err) {
    error = err;
  }

  if (reset) {
    circular_calls = null;
  }

  if (error) {
    throw error;
  }

  return result;
}

// creates instance of [[StaticAsset]], [[BundledAsset]] or [[ProcessedAsset]]
Base.prototype.buildAsset = function (logicalPath, pathname, options) {
  var self = this;

  options = options || {};

  // If there are any processors to run on the pathname, use
  // `BundledAsset`. Otherwise use `StaticAsset` and treat is as binary.

  if (0 === this.attributesFor(pathname).processors.length) {
    return new StaticAsset(this.index, logicalPath, pathname);
  }

  if (options.bundle) {
    return new BundledAsset(this.index, logicalPath, pathname);
  }

  return circular_call_protection(pathname, function () {
    return new ProcessedAsset(self.index, logicalPath, pathname);
  });
};

// Returns cache key for given `pathname` based on options
Base.prototype.cacheKeyFor = function (pathname, options) {
  return pathname + String(options.bundle ? 1 : 0);
};
<commit_msg>Fix for filetrs pattern preprocessing<commit_after>/** internal
 *  class Base
 *
 *  Base class for [[Environment]] and [[Index]].
 *
 *
 *  ##### INCLUDES
 *
 *  - [[Engines]]
 *  - [[Mime]]
 *  - [[Processing]]
 *  - [[Paths]]
 **/

'use strict';

// stdlib
var fs      = require('fs');
var path    = require('path');
var crypto  = require('crypto');

// 3rd-party
var _     = require('underscore');
var async = require('async');

// internal
var getter          = require('./common').getter;
var prop            = require('./common').prop;
var mixin           = require('./common').mixin;
var isAbsolute      = require('./common').isAbsolute;
var VERSION         = require('./version');
var AssetAttributes = require('./asset_attributes');
var StaticAsset     = require('./assets/static');
var ProcessedAsset  = require('./assets/processed');
var BundledAsset    = require('./assets/bundled');

////////////////////////////////////////////////////////////////////////////////

/**
 *  new Base()
 **/
var Base = module.exports = function Base() {};

mixin(Base.prototype, require('./helpers/paths'));
mixin(Base.prototype, require('./helpers/mime'));
mixin(Base.prototype, require('./helpers/processing'));
mixin(Base.prototype, require('./helpers/engines'));

// helper that defines property which fires expireIndex()
// each time it gets changed
function attr_with_expire_index(name, value) {
  var __name__ = '__' + name + '__';

  // set underlying value
  prop(Base.prototype, __name__, value, {writable: true});

  // provide getters/setter
  Object.defineProperty(Base.prototype, name, {
    get: function () {
      return this[__name__];
    },
    set: function (val) {
      this.expireIndex();
      this[__name__] = val;
    }
  });
}

/**
 *  Base#digestAlgorithm -> String
 *
 *  Digest algorithm: `sha1` or `md5`.
 *  See Node manual on `crypto` module.
 *
 *  Default: `md5`.
 **/
attr_with_expire_index('digestAlgorithm', 'md5');

/**
 *  Base#version -> String
 *
 *  Environment version.
 *
 *      environment.version = '2.0'
 **/
attr_with_expire_index('version', '');

/**
 *  Base#digest -> crypto.Hash
 *
 *  Returns a `crypto.Hash` instance for the `Environment`.
 *
 *  This value serves two purposes. If two `Environment`s have the
 *  same digest value they can be treated as equal. This is more
 *  useful for comparing environment states between processes rather
 *  than in the same. Two equal `Environment`s can share the same
 *  cached assets.
 *
 *  The value also provides a seed digest for all `Asset` digests.
 *  Any change in the environment digest will affect all of its assets.
 **/
getter(Base.prototype, 'digest', function () {
  // Do not cache, so the caller can safely mutate it with `.update`
  var digest = crypto.createHash(this.digestAlgorithm);

  // Mixin Mincer release version and custom environment version.
  // So any new releases will affect all your assets.
  digest.update(VERSION,      'utf8');
  digest.update(this.version, 'utf8');

  return digest;
});

// helper that overrides defined method `name` with wrapper that expireIndex()
// and calls `func` when provided before calling original method
function func_proxy_with_expire_index(name, func) {
  var orig = Base.prototype[name];

  Base.prototype[name] = function () {
    this.expireIndex();
    if (func) {
      func.apply(this, arguments);
    }
    orig.apply(this, arguments);
  };
}

//
// override [[Paths]] mixin methods
//

func_proxy_with_expire_index('prependPath');
func_proxy_with_expire_index('appendPath');
func_proxy_with_expire_index('clearPaths');

//
// override [[Mime]] mixin methods
//

func_proxy_with_expire_index('registerMimeType', function (mimeType, ext) {
  this.__trail__.extensions.append(ext);
});

//
// override [[Engines]] mixin methods
//

func_proxy_with_expire_index('registerEngine', function (ext, klass) {
  this.addEngineToTrail(ext, klass);
});

//
// override [[Processing]] mixin methods
//

func_proxy_with_expire_index('registerPreprocessor');
func_proxy_with_expire_index('registerPostprocessor');
func_proxy_with_expire_index('registerBundleProcessor');
func_proxy_with_expire_index('unregisterPreprocessor');
func_proxy_with_expire_index('unregisterPostprocessor');
func_proxy_with_expire_index('unregisterBundleProcessor');

/**
 *  Base#resolve(logicalPath[, options = {}[, fn]]) -> String
 *  - logicalPath (String)
 *  - options (Object)
 *  - fn (Function)
 *
 *  Finds the expanded real path for a given logical path by
 *  searching the environment's paths.
 *
 *      env.resolve("application.js")
 *      # => "/path/to/app/javascripts/application.js.coffee"
 *
 *  An Error with `code = 'FileNotFound'` is raised if the file does not exist.
 **/
Base.prototype.resolve = function (logicalPath, options, fn) {
  var err, resolved, args;

  if (fn) {
    args      = this.attributesFor(logicalPath).searchPaths;
    resolved  = this.__trail__.find(args, options, fn);
  } else {
    resolved = this.resolve(logicalPath, options, function (pathname) {
      return pathname;
    });

    if (!resolved) {
      err = new Error("couldn't find file '" + logicalPath + "'");
      err.code = 'FileNotFound';
      throw err;
    }
  }

  return resolved;
};

/**
 *  Base#entries(pathname) -> Array
 *  - pathname (String)
 *
 *  Proxy to `Hike.Trail#entries`. Works like `fs.readdirSync`.
 *  Subclasses may cache this method.
 **/
Base.prototype.entries = function (pathname) {
  return this.__trail__.entries(pathname);
};

/**
 *  Base#stat(pathname) -> fs.Stats
 *  - pathname (String)
 *
 *  Proxy to `Hike.Trail#stat`. Works like `fs.statSync`.
 *  Subclasses may cache this method.
 **/
Base.prototype.stat = function (pathname) {
  return this.__trail__.stat(pathname);
};

/**
 *  Base#getFileDigest(pathname) -> String
 *  - pathname (String)
 *
 *  Read and compute digest of filename.
 *  Subclasses may cache this method.
 **/
Base.prototype.getFileDigest = function (pathname) {
  var stat = this.stat(pathname);

  if (stat && stat.isFile()) {
    // If its a file, digest the contents
    return this.digest.update(fs.readFileSync(pathname)).digest('hex');
  } else if (stat && stat.isDirectory()) {
    // If its a directive, digest the list of filenames
    return this.digest.update(this.entries(pathname).join(',')).digest('hex');
  }
};

/** internal
 *  Base#attributesFor(pathname) -> AssetAttributes
 *  - pathname (String)
 *
 *  Returns a `AssetAttributes` for `pathname`
 **/
Base.prototype.attributesFor = function (pathname) {
  return new AssetAttributes(this, pathname);
};

/** internal
 *  Base#contentTypeOf(pathname) -> String
 *  - pathname (String)
 *
 *  Returns content type of `pathname`
 **/
Base.prototype.contentTypeOf = function (pathname) {
  return this.attributesFor(pathname).contentType;
};

/**
 *  Base#findAsset(pathname[, options = {}]) -> Asset
 *  - pathname (String)
 *  - options (Object)
 *
 *  Find asset by logical path or expanded path.
 **/
Base.prototype.findAsset = function (pathname, options) {
  var logical_path = pathname;

  if (isAbsolute(pathname)) {
    if (!this.stat(pathname)) {
      return;
    }

    logical_path = this.attributesFor(pathname).logicalPath;
  } else {
    try {
      pathname = this.resolve(logical_path);
    } catch (err) {
      if ('FileNotFound' === err.code) {
        return null;
      }

      throw err;
    }
  }

  return this.buildAsset(logical_path, pathname, options);
};

/**
 *  Base#precompile(files[, callback]) -> Void
 *  - files (Array):
 *  - callback (Function):
 *
 *  Helper to make sure that given list of `files` were compiled.
 *  Similar to [[Manifest#compile]], but does not write anything to disk.
 *
 *      environment.precompile(["app.js"], function (err, data) {
 *        //  data => {
 *        //    files: {
 *        //      "app.js" : "app-2e8e9a7c6b0aafa0c9bdeec90ea30213.js"
 *        //    },
 *        //    assets: {
 *        //      "app-2e8e9a7c6b0aafa0c9bdeec90ea30213.js" : {
 *        //        "logical_path"  : "app.js",
 *        //        "mtime"         : "2011-12-13T21:47:08-06:00",
 *        //        "digest"        : "2e8e9a7c6b0aafa0c9bdeec90ea30213"
 *        //      }
 *        //    }
 *        //  }
 *      });
 *
 *  Needed when you want to render HTML with some JavaScript injected right
 *  into your page (e.g. single-page offline documentation) and your template
 *  engine does not support asynchronous helpers (e.g. Jade requires helpers
 *  to be synchronous).
 **/
Base.prototype.precompile = function (files, callback) {
  var self     = this,
      data     = {files: {}, assets: {}},
      paths    = [];

  this.eachLogicalPath(files, function (pathname) {
    paths.push(pathname);
  });

  paths = _.union(paths, _.select(files, isAbsolute));
  async.forEachSeries(paths, function (pathname, next) {
    var asset = self.findAsset(pathname, {bundle: true});

    if (!asset) {
      next(new Error("Can not find asset '" + pathname + "'"));
      return;
    }

    asset.compile(function (err, asset) {
      if (err) {
        next(err);
        return;
      }

      data.assets[asset.logicalPath]  = asset.digestPath;
      data.files[asset.digestPath]    = {
        logical_path:   asset.logicalPath,
        mtime:          asset.mtime.toISOString(),
        size:           asset.length,
        digest:         asset.digest
      };

      next();
    });
  }, function (err) {
    callback(err, data);
  });
};

/**
 *  Base#eachEntry(root, iterator) -> Void
 *  - root (String)
 *  - iterator (Function)
 *
 *  Calls `iterator` on each found file or directory in alphabetical order:
 *
 *      env.eachEntry('/some/path', function (entry) {
 *        console.log(entry);
 *      });
 *      // -> "/some/path/a"
 *      // -> "/some/path/a/b.txt"
 *      // -> "/some/path/a/c.txt"
 *      // -> "/some/path/b.txt"
 **/
Base.prototype.eachEntry = function (root, iterator) {
  var self = this, paths = [];

  this.entries(root).forEach(function (filename) {
    var pathname  = path.join(root, filename);

    paths.push(pathname);

    if (self.stat(pathname).isDirectory()) {
      self.eachEntry(pathname, function (subpath) {
        paths.push(subpath);
      });
    }
  });

  paths.sort().forEach(iterator);
};

/**
 *  Base#eachFile(iterator) -> Void
 *  - iterator (Function)
 *
 *  Calls `iterator` for each file found within all registered paths.
 **/
Base.prototype.eachFile = function (iterator) {
  var self = this;

  this.paths.forEach(function (root) {
    self.eachEntry(root, function (pathname) {
      if (!self.stat(pathname).isDirectory()) {
        iterator(pathname);
      }
    });
  });
};

// Returns true if there were no filters, or `filename` matches at least one
function matches_filter(filters, filename) {
  if (0 === filters.length) {
    return true;
  }

  return _.any(filters, function (filter) {
    if (_.isRegExp(filter)) {
      return filter.test(filename);
    }

    if (_.isFunction(filter)) {
      return filter(filename);
    }

    // prepare string to become RegExp.
    // mimics shell's globbing
    filter = filter.toString().replace(/\*\*|\*|\?|\\.|\./g, function (m) {
      switch (m[0]) {
        case "*": return "**" === m ? ".+?" : "[^/]+?";
        case "?": return "[^/]?";
        case ".": return "\\.";
        // handle `\\.` part
        default:  return m;
      }
    });

    // prepare RegExp
    filter = new RegExp('^' + filter + '$');
    return filter.test(filename);
  });
}

// Returns logicalPath for `filename` if it mtches given filters
function logical_path_for_filename(self, filters, filename) {
  var logical_path = self.attributesFor(filename).logicalPath;

  if (matches_filter(filters, logical_path)) {
    return logical_path;
  }

  // If filename is an index file, retest with alias
  if ('index' === path.basename(filename).split('.').shift()) {
    logical_path = logical_path.replace(/\/index\./, '.');
    if (matches_filter(filters, logical_path)) {
      return logical_path;
    }
  }
}

/**
 *  Base#eachLogicalPath(filters, iterator) -> Void
 *  - filters (Array)
 *  - iterator (Function)
 *
 *  Calls `iterator` on each found logical path (once per unique path) that
 *  matches at least one of the given filters.
 *
 *  Each filter might be a `String`, `RegExp` or a `Function`.
 **/
Base.prototype.eachLogicalPath = function (filters, iterator) {
  var self = this, files = {};

  this.eachFile(function (filename) {
    var logical_path = logical_path_for_filename(self, filters, filename);
    if (logical_path && !files[logical_path]) {
      iterator(logical_path);
      files[logical_path] = true;
    }
  });
};

// circular call  protection helper.
// keeps array of required pathnames until the function
// that originated protection finishes it's execution
var circular_calls = null;
function circular_call_protection(pathname, callback) {
  var reset   = (null === circular_calls),
      calls   = circular_calls || (circular_calls = []),
      result  = null,
      error   = null;

  if (0 <= calls.indexOf(pathname)) {
    if (reset) { circular_calls = null; }
    throw new Error("Circular dependency detected: " + pathname +
                    " has already been required");
  }

  calls.push(pathname);

  try {
    result = callback();
  } catch (err) {
    error = err;
  }

  if (reset) {
    circular_calls = null;
  }

  if (error) {
    throw error;
  }

  return result;
}

// creates instance of [[StaticAsset]], [[BundledAsset]] or [[ProcessedAsset]]
Base.prototype.buildAsset = function (logicalPath, pathname, options) {
  var self = this;

  options = options || {};

  // If there are any processors to run on the pathname, use
  // `BundledAsset`. Otherwise use `StaticAsset` and treat is as binary.

  if (0 === this.attributesFor(pathname).processors.length) {
    return new StaticAsset(this.index, logicalPath, pathname);
  }

  if (options.bundle) {
    return new BundledAsset(this.index, logicalPath, pathname);
  }

  return circular_call_protection(pathname, function () {
    return new ProcessedAsset(self.index, logicalPath, pathname);
  });
};

// Returns cache key for given `pathname` based on options
Base.prototype.cacheKeyFor = function (pathname, options) {
  return pathname + String(options.bundle ? 1 : 0);
};
<|endoftext|>