<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd

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.
*/
"use strict";
/**
 * @module models/room-member
 */
const EventEmitter = require("events").EventEmitter;
const ContentRepo = require("../content-repo");

const utils = require("../utils");

/**
 * Construct a new room member.
 *
 * @constructor
 * @alias module:models/room-member
 *
 * @param {string} roomId The room ID of the member.
 * @param {string} userId The user ID of the member.
 * @prop {string} roomId The room ID for this member.
 * @prop {string} userId The user ID of this member.
 * @prop {boolean} typing True if the room member is currently typing.
 * @prop {string} name The human-readable name for this room member. This will be
 * disambiguated with a suffix of " (@user_id:matrix.org)" if another member shares the
 * same displayname.
 * @prop {string} rawDisplayName The ambiguous displayname of this room member.
 * @prop {Number} powerLevel The power level for this room member.
 * @prop {Number} powerLevelNorm The normalised power level (0-100) for this
 * room member.
 * @prop {User} user The User object for this room member, if one exists.
 * @prop {string} membership The membership state for this room member e.g. 'join'.
 * @prop {Object} events The events describing this RoomMember.
 * @prop {MatrixEvent} events.member The m.room.member event for this RoomMember.
 */
function RoomMember(roomId, userId) {
    this.roomId = roomId;
    this.userId = userId;
    this.typing = false;
    this.name = userId;
    this.rawDisplayName = userId;
    this.powerLevel = 0;
    this.powerLevelNorm = 0;
    this.user = null;
    this.membership = null;
    this.events = {
        member: null,
    };
    this._lazyLoadAvatarUrl = null;
    this._isLazilyLoaded = false;
    this._updateModifiedTime();
}
utils.inherits(RoomMember, EventEmitter);

/**
 * Update this room member's membership event. May fire "RoomMember.name" if
 * this event updates this member's name.
 * @param {MatrixEvent} event The m.room.member event
 * @param {RoomState} roomState Optional. The room state to take into account
 * when calculating (e.g. for disambiguating users with the same name).
 * @fires module:client~MatrixClient#event:"RoomMember.name"
 * @fires module:client~MatrixClient#event:"RoomMember.membership"
 */
RoomMember.prototype.setMembershipEvent = function(event, roomState) {
    if (event.getType() !== "m.room.member") {
        return;
    }
    this.events.member = event;

    const oldMembership = this.membership;
    this.membership = event.getDirectionalContent().membership;

    const oldName = this.name;
    this.name = calculateDisplayName(
        this.userId,
        event.getDirectionalContent().displayname,
        roomState);

    this.rawDisplayName = event.getDirectionalContent().displayname || this.userId;
    if (oldMembership !== this.membership) {
        this._updateModifiedTime();
        this.emit("RoomMember.membership", event, this, oldMembership);
    }
    if (oldName !== this.name) {
        this._updateModifiedTime();
        this.emit("RoomMember.name", event, this, oldName);
    }
};
/**
 * Update this room member from a lazily loaded member
 */
RoomMember.prototype.setAsJoinedMember = function(displayName, avatarUrl, roomState) {
    this.membership = "join";
    this.name = calculateDisplayName(this.userId, displayName, roomState);
    this.rawDisplayName = displayName || this.userId;
    this._lazyLoadAvatarUrl = avatarUrl;
    this._isLazilyLoaded = true;
    //TODO: race condition between existing membership events since started syncing
}

/**
 * Update this room member's power level event. May fire
 * "RoomMember.powerLevel" if this event updates this member's power levels.
 * @param {MatrixEvent} powerLevelEvent The m.room.power_levels
 * event
 * @fires module:client~MatrixClient#event:"RoomMember.powerLevel"
 */
RoomMember.prototype.setPowerLevelEvent = function(powerLevelEvent) {
    if (powerLevelEvent.getType() !== "m.room.power_levels") {
        return;
    }

    const evContent = powerLevelEvent.getDirectionalContent();

    let maxLevel = evContent.users_default || 0;
    utils.forEach(utils.values(evContent.users), function(lvl) {
        maxLevel = Math.max(maxLevel, lvl);
    });
    const oldPowerLevel = this.powerLevel;
    const oldPowerLevelNorm = this.powerLevelNorm;

    if (evContent.users && evContent.users[this.userId] !== undefined) {
        this.powerLevel = evContent.users[this.userId];
    } else if (evContent.users_default !== undefined) {
        this.powerLevel = evContent.users_default;
    } else {
        this.powerLevel = 0;
    }
    this.powerLevelNorm = 0;
    if (maxLevel > 0) {
        this.powerLevelNorm = (this.powerLevel * 100) / maxLevel;
    }

    // emit for changes in powerLevelNorm as well (since the app will need to
    // redraw everyone's level if the max has changed)
    if (oldPowerLevel !== this.powerLevel || oldPowerLevelNorm !== this.powerLevelNorm) {
        this._updateModifiedTime();
        this.emit("RoomMember.powerLevel", powerLevelEvent, this);
    }
};

/**
 * Update this room member's typing event. May fire "RoomMember.typing" if
 * this event changes this member's typing state.
 * @param {MatrixEvent} event The typing event
 * @fires module:client~MatrixClient#event:"RoomMember.typing"
 */
RoomMember.prototype.setTypingEvent = function(event) {
    if (event.getType() !== "m.typing") {
        return;
    }
    const oldTyping = this.typing;
    this.typing = false;
    const typingList = event.getContent().user_ids;
    if (!utils.isArray(typingList)) {
        // malformed event :/ bail early. TODO: whine?
        return;
    }
    if (typingList.indexOf(this.userId) !== -1) {
        this.typing = true;
    }
    if (oldTyping !== this.typing) {
        this._updateModifiedTime();
        this.emit("RoomMember.typing", event, this);
    }
};

/**
 * Update the last modified time to the current time.
 */
RoomMember.prototype._updateModifiedTime = function() {
    this._modified = Date.now();
};

/**
 * Get the timestamp when this RoomMember was last updated. This timestamp is
 * updated when properties on this RoomMember are updated.
 * It is updated before firing events.
 * @return {number} The timestamp
 */
RoomMember.prototype.getLastModifiedTime = function() {
    return this._modified;
};

RoomMember.prototype.isKicked = function() {
    return this.membership === "leave" &&
        this.events.member.getSender() !== this.events.member.getStateKey();
};

/**
 * If this member was invited with the is_direct flag set, return
 * the user that invited this member
 * @return {string} user id of the inviter
 */
RoomMember.prototype.getDirectChatInviter = function() {
    // when not available because that room state hasn't been loaded in,
    // we don't really know, but more likely to not be a direct chat
    if (this.events.member) {
        // TODO: persist the is_direct flag on the member as more member events
        //       come in caused by displayName changes.

        // the is_direct flag is set on the invite member event.
        // This is copied on the prev_content section of the join member event
        // when the invite is accepted.

        const memberEvent = this.events.member;
        let memberContent = memberEvent.getContent();
        let inviteSender = memberEvent.getSender();

        if (memberContent.membership === "join") {
            memberContent = memberEvent.getPrevContent();
            inviteSender = memberEvent.getUnsigned().prev_sender;
        }

        if (memberContent.membership === "invite" && memberContent.is_direct) {
            return inviteSender;
        }
    }
}

/**
 * Get the avatar URL for a room member.
 * @param {string} baseUrl The base homeserver URL See
 * {@link module:client~MatrixClient#getHomeserverUrl}.
 * @param {Number} width The desired width of the thumbnail.
 * @param {Number} height The desired height of the thumbnail.
 * @param {string} resizeMethod The thumbnail resize method to use, either
 * "crop" or "scale".
 * @param {Boolean} allowDefault (optional) Passing false causes this method to
 * return null if the user has no avatar image. Otherwise, a default image URL
 * will be returned. Default: true.
 * @param {Boolean} allowDirectLinks (optional) If true, the avatar URL will be
 * returned even if it is a direct hyperlink rather than a matrix content URL.
 * If false, any non-matrix content URLs will be ignored. Setting this option to
 * true will expose URLs that, if fetched, will leak information about the user
 * to anyone who they share a room with.
 * @return {?string} the avatar URL or null.
 */
RoomMember.prototype.getAvatarUrl =
        function(baseUrl, width, height, resizeMethod, allowDefault, allowDirectLinks) {
    if (allowDefault === undefined) {
        allowDefault = true;
    }

    const rawUrl = this._getRawAvatarMxcUrl();

    if (!rawUrl && !allowDefault) {
        return null;
    }
    const httpUrl = ContentRepo.getHttpUriForMxc(
        baseUrl, rawUrl, width, height, resizeMethod, allowDirectLinks,
    );
    if (httpUrl) {
        return httpUrl;
    } else if (allowDefault) {
        return ContentRepo.getIdenticonUri(
            baseUrl, this.userId, width, height,
        );
    }
    return null;
};
/**
 * get the mxc avatar url, either from a state event, or from a lazily loaded member
 * @return {string} the mxc avatar url
 */
RoomMember.prototype._getRawAvatarMxcUrl = function() {
    if (this._lazyLoadAvatarUrl) {
        return this._lazyLoadAvatarUrl;
    } else if(this.events.member) {
        return this.events.member.getContent().avatar_url;
    }
    return null;
}

function calculateDisplayName(selfUserId, displayName, roomState) {
    if (!displayName) {
        return selfUserId;
    }

    if (!roomState) {
        return displayName;
    }

    // First check if the displayname is something we consider truthy
    // after stripping it of zero width characters and padding spaces
    const strippedDisplayName = utils.removeHiddenChars(displayName);
    if (!strippedDisplayName) {
        return selfUserId;
    }

    // Next check if the name contains something that look like a mxid
    // If it does, it may be someone trying to impersonate someone else
    // Show full mxid in this case
    // Also show mxid if there are other people with the same displayname
    // ignoring any zero width chars (unicode 200B-200D)
    // if their displayname is made up of just zero width chars, show full mxid
    let disambiguate = /@.+:.+/.test(displayName);
    if (!disambiguate) {
        const userIds = roomState.getUserIdsWithDisplayName(strippedDisplayName);
        disambiguate = userIds.some((u) => u !== selfUserId);
    }

    if (disambiguate) {
        return displayName + " (" + selfUserId + ")";
    }
    return displayName;
}

/**
 * The RoomMember class.
 */
module.exports = RoomMember;

/**
 * Fires whenever any room member's name changes.
 * @event module:client~MatrixClient#"RoomMember.name"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomMember} member The member whose RoomMember.name changed.
 * @param {string?} oldName The previous name. Null if the member didn't have a
 *    name previously.
 * @example
 * matrixClient.on("RoomMember.name", function(event, member){
 *   var newName = member.name;
 * });
 */

/**
 * Fires whenever any room member's membership state changes.
 * @event module:client~MatrixClient#"RoomMember.membership"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomMember} member The member whose RoomMember.membership changed.
 * @param {string?} oldMembership The previous membership state. Null if it's a
 *    new member.
 * @example
 * matrixClient.on("RoomMember.membership", function(event, member, oldMembership){
 *   var newState = member.membership;
 * });
 */

/**
 * Fires whenever any room member's typing state changes.
 * @event module:client~MatrixClient#"RoomMember.typing"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomMember} member The member whose RoomMember.typing changed.
 * @example
 * matrixClient.on("RoomMember.typing", function(event, member){
 *   var isTyping = member.typing;
 * });
 */

/**
 * Fires whenever any room member's power level changes.
 * @event module:client~MatrixClient#"RoomMember.powerLevel"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomMember} member The member whose RoomMember.powerLevel changed.
 * @example
 * matrixClient.on("RoomMember.powerLevel", function(event, member){
 *   var newPowerLevel = member.powerLevel;
 *   var newNormPowerLevel = member.powerLevelNorm;
 * });
 */
<commit_msg>use more consistent naming<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd

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.
*/
"use strict";
/**
 * @module models/room-member
 */
const EventEmitter = require("events").EventEmitter;
const ContentRepo = require("../content-repo");

const utils = require("../utils");

/**
 * Construct a new room member.
 *
 * @constructor
 * @alias module:models/room-member
 *
 * @param {string} roomId The room ID of the member.
 * @param {string} userId The user ID of the member.
 * @prop {string} roomId The room ID for this member.
 * @prop {string} userId The user ID of this member.
 * @prop {boolean} typing True if the room member is currently typing.
 * @prop {string} name The human-readable name for this room member. This will be
 * disambiguated with a suffix of " (@user_id:matrix.org)" if another member shares the
 * same displayname.
 * @prop {string} rawDisplayName The ambiguous displayname of this room member.
 * @prop {Number} powerLevel The power level for this room member.
 * @prop {Number} powerLevelNorm The normalised power level (0-100) for this
 * room member.
 * @prop {User} user The User object for this room member, if one exists.
 * @prop {string} membership The membership state for this room member e.g. 'join'.
 * @prop {Object} events The events describing this RoomMember.
 * @prop {MatrixEvent} events.member The m.room.member event for this RoomMember.
 */
function RoomMember(roomId, userId) {
    this.roomId = roomId;
    this.userId = userId;
    this.typing = false;
    this.name = userId;
    this.rawDisplayName = userId;
    this.powerLevel = 0;
    this.powerLevelNorm = 0;
    this.user = null;
    this.membership = null;
    this.events = {
        member: null,
    };
    this._lazyLoadAvatarUrl = null;
    this._isLazilyLoaded = false;
    this._updateModifiedTime();
}
utils.inherits(RoomMember, EventEmitter);

/**
 * Update this room member's membership event. May fire "RoomMember.name" if
 * this event updates this member's name.
 * @param {MatrixEvent} event The m.room.member event
 * @param {RoomState} roomState Optional. The room state to take into account
 * when calculating (e.g. for disambiguating users with the same name).
 * @fires module:client~MatrixClient#event:"RoomMember.name"
 * @fires module:client~MatrixClient#event:"RoomMember.membership"
 */
RoomMember.prototype.setMembershipEvent = function(event, roomState) {
    if (event.getType() !== "m.room.member") {
        return;
    }
    this.events.member = event;

    const oldMembership = this.membership;
    this.membership = event.getDirectionalContent().membership;

    const oldName = this.name;
    this.name = calculateDisplayName(
        this.userId,
        event.getDirectionalContent().displayname,
        roomState);

    this.rawDisplayName = event.getDirectionalContent().displayname || this.userId;
    if (oldMembership !== this.membership) {
        this._updateModifiedTime();
        this.emit("RoomMember.membership", event, this, oldMembership);
    }
    if (oldName !== this.name) {
        this._updateModifiedTime();
        this.emit("RoomMember.name", event, this, oldName);
    }
};
/**
 * Update this room member from a lazily loaded member
 */
RoomMember.prototype.setAsJoinedMember = function(displayName, avatarUrl, roomState) {
    this.membership = "join";
    this.name = calculateDisplayName(this.userId, displayName, roomState);
    this.rawDisplayName = displayName || this.userId;
    this._lazyLoadAvatarUrl = avatarUrl;
    this._isLazilyLoaded = true;
    //TODO: race condition between existing membership events since started syncing
}

/**
 * Update this room member's power level event. May fire
 * "RoomMember.powerLevel" if this event updates this member's power levels.
 * @param {MatrixEvent} powerLevelEvent The m.room.power_levels
 * event
 * @fires module:client~MatrixClient#event:"RoomMember.powerLevel"
 */
RoomMember.prototype.setPowerLevelEvent = function(powerLevelEvent) {
    if (powerLevelEvent.getType() !== "m.room.power_levels") {
        return;
    }

    const evContent = powerLevelEvent.getDirectionalContent();

    let maxLevel = evContent.users_default || 0;
    utils.forEach(utils.values(evContent.users), function(lvl) {
        maxLevel = Math.max(maxLevel, lvl);
    });
    const oldPowerLevel = this.powerLevel;
    const oldPowerLevelNorm = this.powerLevelNorm;

    if (evContent.users && evContent.users[this.userId] !== undefined) {
        this.powerLevel = evContent.users[this.userId];
    } else if (evContent.users_default !== undefined) {
        this.powerLevel = evContent.users_default;
    } else {
        this.powerLevel = 0;
    }
    this.powerLevelNorm = 0;
    if (maxLevel > 0) {
        this.powerLevelNorm = (this.powerLevel * 100) / maxLevel;
    }

    // emit for changes in powerLevelNorm as well (since the app will need to
    // redraw everyone's level if the max has changed)
    if (oldPowerLevel !== this.powerLevel || oldPowerLevelNorm !== this.powerLevelNorm) {
        this._updateModifiedTime();
        this.emit("RoomMember.powerLevel", powerLevelEvent, this);
    }
};

/**
 * Update this room member's typing event. May fire "RoomMember.typing" if
 * this event changes this member's typing state.
 * @param {MatrixEvent} event The typing event
 * @fires module:client~MatrixClient#event:"RoomMember.typing"
 */
RoomMember.prototype.setTypingEvent = function(event) {
    if (event.getType() !== "m.typing") {
        return;
    }
    const oldTyping = this.typing;
    this.typing = false;
    const typingList = event.getContent().user_ids;
    if (!utils.isArray(typingList)) {
        // malformed event :/ bail early. TODO: whine?
        return;
    }
    if (typingList.indexOf(this.userId) !== -1) {
        this.typing = true;
    }
    if (oldTyping !== this.typing) {
        this._updateModifiedTime();
        this.emit("RoomMember.typing", event, this);
    }
};

/**
 * Update the last modified time to the current time.
 */
RoomMember.prototype._updateModifiedTime = function() {
    this._modified = Date.now();
};

/**
 * Get the timestamp when this RoomMember was last updated. This timestamp is
 * updated when properties on this RoomMember are updated.
 * It is updated before firing events.
 * @return {number} The timestamp
 */
RoomMember.prototype.getLastModifiedTime = function() {
    return this._modified;
};

RoomMember.prototype.isKicked = function() {
    return this.membership === "leave" &&
        this.events.member.getSender() !== this.events.member.getStateKey();
};

/**
 * If this member was invited with the is_direct flag set, return
 * the user that invited this member
 * @return {string} user id of the inviter
 */
RoomMember.prototype.getDMInviter = function() {
    // when not available because that room state hasn't been loaded in,
    // we don't really know, but more likely to not be a direct chat
    if (this.events.member) {
        // TODO: persist the is_direct flag on the member as more member events
        //       come in caused by displayName changes.

        // the is_direct flag is set on the invite member event.
        // This is copied on the prev_content section of the join member event
        // when the invite is accepted.

        const memberEvent = this.events.member;
        let memberContent = memberEvent.getContent();
        let inviteSender = memberEvent.getSender();

        if (memberContent.membership === "join") {
            memberContent = memberEvent.getPrevContent();
            inviteSender = memberEvent.getUnsigned().prev_sender;
        }

        if (memberContent.membership === "invite" && memberContent.is_direct) {
            return inviteSender;
        }
    }
}

/**
 * Get the avatar URL for a room member.
 * @param {string} baseUrl The base homeserver URL See
 * {@link module:client~MatrixClient#getHomeserverUrl}.
 * @param {Number} width The desired width of the thumbnail.
 * @param {Number} height The desired height of the thumbnail.
 * @param {string} resizeMethod The thumbnail resize method to use, either
 * "crop" or "scale".
 * @param {Boolean} allowDefault (optional) Passing false causes this method to
 * return null if the user has no avatar image. Otherwise, a default image URL
 * will be returned. Default: true.
 * @param {Boolean} allowDirectLinks (optional) If true, the avatar URL will be
 * returned even if it is a direct hyperlink rather than a matrix content URL.
 * If false, any non-matrix content URLs will be ignored. Setting this option to
 * true will expose URLs that, if fetched, will leak information about the user
 * to anyone who they share a room with.
 * @return {?string} the avatar URL or null.
 */
RoomMember.prototype.getAvatarUrl =
        function(baseUrl, width, height, resizeMethod, allowDefault, allowDirectLinks) {
    if (allowDefault === undefined) {
        allowDefault = true;
    }

    const rawUrl = this._getRawAvatarMxcUrl();

    if (!rawUrl && !allowDefault) {
        return null;
    }
    const httpUrl = ContentRepo.getHttpUriForMxc(
        baseUrl, rawUrl, width, height, resizeMethod, allowDirectLinks,
    );
    if (httpUrl) {
        return httpUrl;
    } else if (allowDefault) {
        return ContentRepo.getIdenticonUri(
            baseUrl, this.userId, width, height,
        );
    }
    return null;
};
/**
 * get the mxc avatar url, either from a state event, or from a lazily loaded member
 * @return {string} the mxc avatar url
 */
RoomMember.prototype._getRawAvatarMxcUrl = function() {
    if (this._lazyLoadAvatarUrl) {
        return this._lazyLoadAvatarUrl;
    } else if(this.events.member) {
        return this.events.member.getContent().avatar_url;
    }
    return null;
}

function calculateDisplayName(selfUserId, displayName, roomState) {
    if (!displayName) {
        return selfUserId;
    }

    if (!roomState) {
        return displayName;
    }

    // First check if the displayname is something we consider truthy
    // after stripping it of zero width characters and padding spaces
    const strippedDisplayName = utils.removeHiddenChars(displayName);
    if (!strippedDisplayName) {
        return selfUserId;
    }

    // Next check if the name contains something that look like a mxid
    // If it does, it may be someone trying to impersonate someone else
    // Show full mxid in this case
    // Also show mxid if there are other people with the same displayname
    // ignoring any zero width chars (unicode 200B-200D)
    // if their displayname is made up of just zero width chars, show full mxid
    let disambiguate = /@.+:.+/.test(displayName);
    if (!disambiguate) {
        const userIds = roomState.getUserIdsWithDisplayName(strippedDisplayName);
        disambiguate = userIds.some((u) => u !== selfUserId);
    }

    if (disambiguate) {
        return displayName + " (" + selfUserId + ")";
    }
    return displayName;
}

/**
 * The RoomMember class.
 */
module.exports = RoomMember;

/**
 * Fires whenever any room member's name changes.
 * @event module:client~MatrixClient#"RoomMember.name"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomMember} member The member whose RoomMember.name changed.
 * @param {string?} oldName The previous name. Null if the member didn't have a
 *    name previously.
 * @example
 * matrixClient.on("RoomMember.name", function(event, member){
 *   var newName = member.name;
 * });
 */

/**
 * Fires whenever any room member's membership state changes.
 * @event module:client~MatrixClient#"RoomMember.membership"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomMember} member The member whose RoomMember.membership changed.
 * @param {string?} oldMembership The previous membership state. Null if it's a
 *    new member.
 * @example
 * matrixClient.on("RoomMember.membership", function(event, member, oldMembership){
 *   var newState = member.membership;
 * });
 */

/**
 * Fires whenever any room member's typing state changes.
 * @event module:client~MatrixClient#"RoomMember.typing"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomMember} member The member whose RoomMember.typing changed.
 * @example
 * matrixClient.on("RoomMember.typing", function(event, member){
 *   var isTyping = member.typing;
 * });
 */

/**
 * Fires whenever any room member's power level changes.
 * @event module:client~MatrixClient#"RoomMember.powerLevel"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomMember} member The member whose RoomMember.powerLevel changed.
 * @example
 * matrixClient.on("RoomMember.powerLevel", function(event, member){
 *   var newPowerLevel = member.powerLevel;
 *   var newNormPowerLevel = member.powerLevelNorm;
 * });
 */
<|endoftext|>


<commit_before>
// TODO
const defaultOptions = {};

function mobxPluginFactory(routerStore, options = defaultOptions) {

  function mobxPlugin(router, dependencies) {

    // NOTE: cross-referencing objects
    router.setDependency('routerStore', routerStore);
    routerStore.setRouter(router);

    // Implemented methods
    return {
      onTransitionStart(toState, fromState) {
        routerStore.onTransitionStart(toState, fromState);
      },
      onTransitionSuccess(toState, fromState, opts) {
        routerStore.onTransitionSuccess(toState, fromState);
      },
      onTransitionCancel(toState, fromState){
        routerStore.onTransitionCancel(toState, fromState);
      },
      onTransitionError(toState, fromState, err) {
        routerStore.onTransitionError(toState, fromState, err);
      }
    };
  }

  mobxPlugin.pluginName = 'MOBX_PLUGIN';

  return mobxPlugin;
}

export default mobxPluginFactory;

<commit_msg>bug fix<commit_after>
// TODO
const defaultOptions = {};

function mobxPluginFactory(routerStore, options = defaultOptions) {

  function mobxPlugin(router, dependencies) {

    // NOTE: cross-referencing objects
    router.setDependency('routerStore', routerStore);
    routerStore.setRouter(router);

    // Implemented methods
    return {
      onTransitionStart(toState, fromState) {
        routerStore.onTransitionStart(toState, fromState);
      },
      onTransitionSuccess(toState, fromState, opts) {
        routerStore.onTransitionSuccess(toState, fromState, opts);
      },
      onTransitionCancel(toState, fromState){
        routerStore.onTransitionCancel(toState, fromState);
      },
      onTransitionError(toState, fromState, err) {
        routerStore.onTransitionError(toState, fromState, err);
      }
    };
  }

  mobxPlugin.pluginName = 'MOBX_PLUGIN';

  return mobxPlugin;
}

export default mobxPluginFactory;

<|endoftext|>


<commit_before>(function(window, _, Backbone){
    'use strict';

    var Monaco = window.Monaco = (window.Monaco || {});

    /* -- APPLICATION ------------------------------------------------------ */
    var App = Monaco.Application = function(appName, options) {
        if (!appName) {
            throw new Error('required application parameter: appName');
        }

        options = options || {};

        // Application Unique Name
        this.name = appName;
        this.options = options;

        // Application Client Id
        this.cid = _.uniqueId('app');

        this._settings      = {}; // used internally to store app settings ( get/set )
        this.models         = {}; // initialize model namespace
        this.collections    = {}; // initialize collection namespace
        this.views          = {}; // initialize view namespace

        // create a router instance
        var RouterClass = options.RouterClass || Monaco.Router;
        this.router = new RouterClass({ routes: options.routes });

        // trigger a global event for application module setup
        Monaco.trigger('app:built', this, options);
    };

    _.extend(App.prototype, Backbone.Events, {
        // Start the Monaco application
        start : function(options) {
            options = options || {};
            options.pushState = options.pushState || false;

            // when starting the application an instance of a router should exist
            if (!_.has(this, 'router')) {
                throw new Error('missing router instance');
            }

            // Add all unregistered routes before starting the history
            this.router._addRoutes();

            Monaco.history.start({pushState: options.pushState});

            // trigger a custom event after the application has started
            this.trigger('started', this);
        },

        // Interface used to add objects (models, collections, views and transitions) to your application
        add : function(className, object) {
            // all objects added through this method needs a namespace
            if (!object.prototype || !object.prototype.namespace) {
                throw new Error('missing required object property \'namespace\'');
            }

            // fail on duplicated objects being created on the same namespace
            if (this[object.prototype.namespace][className]) {
                throw new Error(className + ' have already been defined in ' + this.name + '.' + object.prototype.namespace);
            }

            // injects the app reference in the object prototype
            object.prototype._app = this;

            // adds the object using the proper namespacing
            this[object.prototype.namespace][className] = object;
        },

        // App setting get method, returning undefined if not found or any error occurs
        // todo: return null instead of undefined to comply with the localStorage api
        get : function(key) {
            // searchs for the key in memory
            if (_.has(this._settings, key)) {
                return this._settings[key];
            }

            // searchs for the key in local storage
            var result = null;
            try {
                result = JSON.parse(window.localStorage.getItem(key));
            } catch( e ) {
                return void 0;
            }

            if (result === null) {
                return void 0;
            }

            // store the key/value in memory
            this._settings[key] = result;

            // return the value
            return result;
        },

        // App setting set method
        set : function(key, value, persist) {
            persist = persist || false;
            // validation
            if (key === void 0 || value === void 0) {
                throw new Error('set method required both key and value parameters');
            }

            // if persist is set to true then store the key/value in localStorage
            if (persist === true) {
                try {
                    window.localStorage.setItem(key, JSON.stringify(value));
                } catch(e) {
                    // console.log( 'Warning: fail to persist key: ' + key );
                    return false;
                }
            }

            // store the key/value in memory
            this._settings[key] = value;
        }
    });

    // Allow the `Monaco` object to serve as a global event bus
    _.extend(Monaco, Backbone.Events);

    // wraps the error callback to trigger the proper events
    var fetchError = function(options) {
        options = options || {};

        var error = options.error,
            router = ( this._app & this._app.router ) ? this._app.router : null;

        // custom error method that will trigger a custom event before executing
        // the error callback
        options.error = function(obj, resp, options) {
            if (router) {
                router.trigger('route:fetch.error', obj, resp, options);
            }
            if (error) {
                error.apply(this, arguments);
            }
        };
        return options;
    };

    /* -- COLLECTION ------------------------------------------------------- */
    Monaco.Collection = Backbone.Collection.extend({
        // application collection namespace
        namespace : 'collections',

        // override the fetch method to add the default error router
        fetch : function(options) {
            options = fetchError.call(this, options);
            return Backbone.Collection.prototype.fetch.call(this, options);
        }
    });

    /* -- MODEL ------------------------------------------------------------ */
    Monaco.Model = Backbone.Model.extend({
        // application model namespace
        namespace : 'models',

        // override the fetch method to add the default error router
        fetch : function(options) {
            options = fetchError.call(this, options);
            return Backbone.Model.prototype.fetch.call(this, options);
        }
    });

    /* -- VIEW ------------------------------------------------------------- */
    Monaco.View = Backbone.View.extend({
        // application view namespace
        namespace : 'views'
    });

    /* -- ROUTER ----------------------------------------------------------- */
    Monaco.Router = Backbone.Router.extend({
        // undefined routes
        _uroutes : [],

        // defined routes
        _routes : [],

        // override the Backbone Router constructor
        constructor : function() {
            var initialize = this.initialize; // keep the original initialize method
            // wrap the initialize method to store each route definition
            this.initialize = function(options) {
                _.each(options.routes, function(options, route) {
                    this._routes.push({key: route, value: options});
                }, this);
                initialize.apply(this, arguments);
            };
            Backbone.Router.prototype.constructor.apply(this, arguments);
        },

        // stores routes that will be added when the application starts
        add : function(routes) {
            var route, routeList;
            if (!routes || !_.isObject(routes)) {
                throw new Error('invalid routes format - please check the docs');
            }
            routeList = _.keys(routes);

            while ((route = routeList.pop()) != null) {
                this._uroutes.unshift({key: route, value: routes[route]});
            }
        },

        // adds all undefined routes into Backbone.Router
        _addRoutes : function() {
            var route;
            while((route = this._uroutes.pop()) != null) {
                // this needs to be added to the _route list before the route gets added to the Router.
                this._routes.push(route);
                this._addRoute(route.key, route.value);
            }
        },

        // adds one route into Backbone.Router
        _addRoute : function(route, options) {
            options = options || {};

            if (route !== '' && !route) {
                throw new Error('invalid route: ' + route);
            }

            var callback = null;
                // name = null; // todo: verify if it should be null or empty string

            if (_.isString(options)) {
                callback = options;
            } else if (_.isArray(options) && options.length > 0) {
                callback = options[0];
                // todo: I don't think we need to care about the name in this method,
                //       since Backbone uses the name just to point to a router instance callback method
                // if ((options.length > 1) && _.isString(options[1])) {
                //     name = options[1];
                // } else if (options.length > 1 && _.isObject(options[1])) {
                //     name = options[1].name;
                // }
            }

            if (!callback) {
                throw new Error('invalid callback from route: ' + route);
            }

            return this.route(route, callback);
            // return this.route(route, name, callback);
        }
    });

    /* -- HISTORY ----------------------------------------------------------- */
    Monaco.History = Backbone.History;

    // Creates a reference to Backbone history instance in Monaco
    Monaco.history = Backbone.history;

}(window, window._, window.Backbone));
<commit_msg>improved monaco application with better options support and some browser bug patches<commit_after>(function(window, _, Backbone){
    'use strict';

    var Monaco = window.Monaco = (window.Monaco || {});

    /* -- APPLICATION ------------------------------------------------------ */
    var App = Monaco.Application = function(appName, options) {
        if (!appName) {
            throw new Error('required application parameter: appName');
        }

        options = options || {};

        // Application Unique Name
        this.name = appName;
        this.options = options;

        // Application Client Id
        this.cid = _.uniqueId('app');

        this._settings      = {}; // used internally to store app settings ( get/set )
        this.models         = {}; // initialize model namespace
        this.collections    = {}; // initialize collection namespace
        this.views          = {}; // initialize view namespace

        // create a router instance
        var RouterClass = options.RouterClass || Monaco.Router;
        this.router = new RouterClass({app: this, routes: options.routes});

        // trigger a global event for application module setup
        Monaco.trigger('app:built', this, options);
    };

    _.extend(App.prototype, Backbone.Events, {
        // Start the Monaco application
        start : function(options) {
            options = options || {};
            options.pushState = options.pushState || false;

            // when starting the application an instance of a router should exist
            if (!_.has(this, 'router')) {
                throw new Error('missing router instance');
            }

            // Add all unregistered routes before starting the history
            this.router._addRoutes();

            Monaco.history.start(options);

            // trigger a global event for application module setup
            Monaco.trigger('app:initialized', this, options);
        },

        // Interface used to add objects (models, collections, views and transitions) to your application
        add : function(className, object) {
            // all objects added through this method needs a namespace
            if (!object.prototype || !object.prototype.namespace) {
                throw new Error('missing required object property \'namespace\'');
            }

            // fail on duplicated objects being created on the same namespace
            if (this[object.prototype.namespace][className]) {
                throw new Error(className + ' have already been defined in ' + this.name + '.' + object.prototype.namespace);
            }

            // injects the app reference in the object prototype
            object.prototype._app = this;

            // adds the object using the proper namespacing
            this[object.prototype.namespace][className] = object;
        },

        // App setting get method, returning undefined if not found or any error occurs
        // todo: return null instead of undefined to comply with the localStorage api
        get : function(key) {
            // searchs for the key in memory
            if (_.has(this._settings, key)) {
                return this._settings[key];
            }

            // searchs for the key in local storage
            var result = null;
            try {
                result = JSON.parse(window.localStorage.getItem(key));
            } catch( e ) {
                return void 0;
            }

            if (result === null) {
                return void 0;
            }

            // store the key/value in memory
            this._settings[key] = result;

            // return the value
            return result;
        },

        // App setting set method
        set : function(key, value, persist) {
            persist = persist || false;
            // validation
            if (key === void 0 || value === void 0) {
                throw new Error('set method required both key and value parameters { key: ' + key + ' | value: ' + value + ' }');
            }

            // store the key/value in memory
            this._settings[key] = value;

            // if persist is set to true then store the key/value in localStorage
            if (persist === true) {
                try {
                    window.localStorage.setItem(key, JSON.stringify(value));
                } catch(e) {
                    // console.log( 'Warning: fail to persist key: ' + key );
                    return false;
                }
            }
            return true;
         }
    });

    // Allow the `Monaco` object to serve as a global event bus
    _.extend(Monaco, Backbone.Events);

    // wraps the error callback to trigger the proper events
    var fetchError = function(options) {
        options = options || {};

        var error = options.error,
            router = ( this._app & this._app.router ) ? this._app.router : null;

        // custom error method that will trigger a custom event before executing
        // the error callback
        options.error = function(obj, resp, options) {
            if (router) {
                router.trigger('route:fetch.error', obj, resp, options);
            }
            if (error) {
                error.apply(this, arguments);
            }
        };
        return options;
    };

    /* -- COLLECTION ------------------------------------------------------- */
    try {
        Monaco.Collection = Backbone.Collection.extend({
            // application collection namespace
            namespace : 'collections',

            // override the fetch method to add the default error router
            fetch : function(options) {
                options = fetchError.call(this, options);
                return Backbone.Collection.prototype.fetch.call(this, options);
            }
        });
    }
    catch(e) {
        // this addresses an issue with some versions of google browser - better described
        // by this ticket on backbone: https://github.com/jashkenas/backbone/issues/1475
        // and this ticket on chrome forum: https://code.google.com/p/chromium/issues/detail?id=136380
        // sample of user agent where the problem was observed:
        //
        // Mozilla/5.0 (Linux; U; Android 4.1.1; tr-tr; Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30
        // Mozilla/5.0 (Linux; U; Android 4.0.4; en-us; Next7P12-8G Build/IMM76I) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30
        //
        // Note that not all Android 4.1.1 or Android 4.0.4 triggered the same misbehaviour
        Monaco.Collection = Backbone.Collection;
        Monaco.Collection.prototype.namespace = 'collections';

    }

    /* -- MODEL ------------------------------------------------------------ */
    try {
        Monaco.Model = Backbone.Model.extend({
            // application model namespace
            namespace : 'models',

            // override the fetch method to add the default error router
            fetch : function(options) {
                options = fetchError.call(this, options);
                return Backbone.Model.prototype.fetch.call(this, options);
            }
        });
    }
    catch(e) {
        // this addresses an issue with some versions of google browser - better described
        // by this ticket on backbone: https://github.com/jashkenas/backbone/issues/1475
        // and this ticket on chrome forum: https://code.google.com/p/chromium/issues/detail?id=136380
        // sample of user agent where the problem was observed:
        //
        // Mozilla/5.0 (Linux; U; Android 4.1.1; tr-tr; Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30
        // Mozilla/5.0 (Linux; U; Android 4.0.4; en-us; Next7P12-8G Build/IMM76I) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30
        //
        // Note that not all Android 4.1.1 or Android 4.0.4 triggered the same misbehaviour

        Monaco.Model = Backbone.Model;
        Monaco.Model.prototype.namespace = 'models';
    }

    /* -- VIEW ------------------------------------------------------------- */
    Monaco.View = Backbone.View.extend({
        // application view namespace
        namespace : 'views'
    });

    /* -- ROUTER ----------------------------------------------------------- */
    Monaco.Router = Backbone.Router.extend({
        // undefined routes
        _uroutes : [],

        // defined routes
        _routes : [],

        // override the Backbone Router constructor
        constructor : function(options) {
            this.app = arguments.length > 0 ? arguments[0].app : void 0;
            var initialize = this.initialize; // keep the original initialize method
            // wrap the initialize method to store each route definition
            this.initialize = function(options) {
                _.each(options.routes, function(options, route) {
                    this._routes.push({key: route, value: options});
                }, this);
                initialize.apply(this, arguments);
            };
            Backbone.Router.prototype.constructor.apply(this, arguments);
        },

        // stores routes that will be added when the application starts
        add : function(routes) {
            var route, routeList;
            if (!routes || !_.isObject(routes)) {
                throw new Error('invalid routes format - please check the docs');
            }
            routeList = _.keys(routes);

            while ((route = routeList.pop()) != null) {
                this._uroutes.unshift({key: route, value: routes[route]});
            }
        },

        // adds all undefined routes into Backbone.Router
        _addRoutes : function() {
            var route;
            while((route = this._uroutes.pop()) != null) {
                // this needs to be added to the _route list before the route gets added to the Router.
                this._routes.push(route);
                this._addRoute(route.key, route.value);
            }
        },

        // adds one route into Backbone.Router
        _addRoute : function(route, options) {
            options = options || {};

            if (route !== '' && !route) {
                throw new Error('invalid route: ' + route);
            }

            var callback = null;
                // name = null; // todo: verify if it should be null or empty string

            if (_.isString(options)) {
                callback = options;
            } else if (_.isArray(options) && options.length > 0) {
                callback = options[0];
                // todo: I don't think we need to care about the name in this method,
                //       since Backbone uses the name just to point to a router instance callback method
                // if ((options.length > 1) && _.isString(options[1])) {
                //     name = options[1];
                // } else if (options.length > 1 && _.isObject(options[1])) {
                //     name = options[1].name;
                // }
            }

            if (!callback) {
                throw new Error('invalid callback from route: ' + route);
            }

            return this.route(route, callback);
            // return this.route(route, name, callback);
        }
    });

    /* -- HISTORY ----------------------------------------------------------- */
    Monaco.History = Backbone.History;

    // Creates a reference to Backbone history instance in Monaco
    Monaco.history = Backbone.history;

}(window, window._, window.Backbone));
<|endoftext|>


<commit_before>'use strict';

var chalk = require('chalk');
var fs = require('fs');
var RSVP = require('rsvp');
var spawn = require('cross-spawn');
var _ = require('lodash');

var logger = require('../../logger');
var prompt = require('../../prompt');
var enableApi = require('../../ensureApiEnabled').enable;
var requireAccess = require('../../requireAccess');
var scopes = require('../../scopes');

var INDEX_TEMPLATE = fs.readFileSync(__dirname + '/../../../templates/init/functions/index.js', 'utf8');

module.exports = function(setup, config) {
  logger.info();
  logger.info('A ' + chalk.bold('functions') + ' directory will be created in your project with a Node.js');
  logger.info('package pre-configured. Functions can be deployed with ' + chalk.bold('firebase deploy') + '.');
  logger.info();

  setup.functions = {};
  var projectId = _.get(setup, 'rcfile.projects.default');
  var enableApis;
  if (projectId) {
    enableApis = requireAccess({project: projectId}, [scopes.CLOUD_PLATFORM]).then(function() {
      enableApi(projectId, 'cloudfunctions.googleapis.com');
      enableApi(projectId, 'runtimeconfig.googleapis.com');
    });
  } else {
    enableApis = RSVP.resolve();
  }
  return enableApis.then(function() {
    return config.askWriteProjectFile('functions/package.json', {
      name: 'functions',
      description: 'Cloud Functions for Firebase',
      dependencies: {
        'firebase-admin': '~4.2.1',
        'firebase-functions': '^0.5.7'
      },
      private: true
    });
  }).then(function() {
    return config.askWriteProjectFile('functions/index.js', INDEX_TEMPLATE);
  }).then(function() {
    return prompt(setup.functions, [
      {
        name: 'npm',
        type: 'confirm',
        message: 'Do you want to install dependencies with npm now?',
        default: true
      }
    ]);
  }).then(function() {
    if (setup.functions.npm) {
      return new RSVP.Promise(function(resolve) {
        var installer = spawn('npm', ['install'], {
          cwd: config.projectDir + '/functions',
          stdio: 'inherit'
        });

        installer.on('error', function(err) {
          logger.debug(err.stack);
        });

        installer.on('close', function(code) {
          if (code === 0) {
            return resolve();
          }
          logger.info();
          logger.error('NPM install failed, continuing with Firebase initialization...');
          return resolve();
        });
      });
    }
  });
};
<commit_msg>Update functions init template to latest deps. (#434)<commit_after>'use strict';

var chalk = require('chalk');
var fs = require('fs');
var RSVP = require('rsvp');
var spawn = require('cross-spawn');
var _ = require('lodash');

var logger = require('../../logger');
var prompt = require('../../prompt');
var enableApi = require('../../ensureApiEnabled').enable;
var requireAccess = require('../../requireAccess');
var scopes = require('../../scopes');

var INDEX_TEMPLATE = fs.readFileSync(__dirname + '/../../../templates/init/functions/index.js', 'utf8');

module.exports = function(setup, config) {
  logger.info();
  logger.info('A ' + chalk.bold('functions') + ' directory will be created in your project with a Node.js');
  logger.info('package pre-configured. Functions can be deployed with ' + chalk.bold('firebase deploy') + '.');
  logger.info();

  setup.functions = {};
  var projectId = _.get(setup, 'rcfile.projects.default');
  var enableApis;
  if (projectId) {
    enableApis = requireAccess({project: projectId}, [scopes.CLOUD_PLATFORM]).then(function() {
      enableApi(projectId, 'cloudfunctions.googleapis.com');
      enableApi(projectId, 'runtimeconfig.googleapis.com');
    });
  } else {
    enableApis = RSVP.resolve();
  }
  return enableApis.then(function() {
    return config.askWriteProjectFile('functions/package.json', {
      name: 'functions',
      description: 'Cloud Functions for Firebase',
      dependencies: {
        'firebase-admin': '~5.2.1',
        'firebase-functions': '^0.6.2'
      },
      private: true
    });
  }).then(function() {
    return config.askWriteProjectFile('functions/index.js', INDEX_TEMPLATE);
  }).then(function() {
    return prompt(setup.functions, [
      {
        name: 'npm',
        type: 'confirm',
        message: 'Do you want to install dependencies with npm now?',
        default: true
      }
    ]);
  }).then(function() {
    if (setup.functions.npm) {
      return new RSVP.Promise(function(resolve) {
        var installer = spawn('npm', ['install'], {
          cwd: config.projectDir + '/functions',
          stdio: 'inherit'
        });

        installer.on('error', function(err) {
          logger.debug(err.stack);
        });

        installer.on('close', function(code) {
          if (code === 0) {
            return resolve();
          }
          logger.info();
          logger.error('NPM install failed, continuing with Firebase initialization...');
          return resolve();
        });
      });
    }
  });
};
<|endoftext|>


<commit_before>const CollisionDetector = require('./collision_detector')
var detector = new CollisionDetector();

function GameEngine() {
};

let fireCounter = 0

GameEngine.prototype.takeComputerTurn = function(computerPlane){
    computerPlane.move();
    if ((fireCounter % 75 === 0 || computerPlane.y === -5)) {
	computerPlane.fire()
    };
}

GameEngine.prototype.moveBullets = function(plane){
    let bullets = plane.bullets
    bullets.forEach(function(bullet){
	if (plane.type === "player") {
	    bullet.y = bullet.y - 3;
	} else if (plane.type === "computer") {
	    bullet.y = bullet.y + 3;
	};
	if (bullet.y > 510 ){
	    bullet.destroy();
	}
    });
}

GameEngine.prototype.checkPlayerActions = function(playerPlane, otherPlanes) {
    otherPlanes.forEach(function(plane2){
	if (plane2.type === "computer" && detector.check(playerPlane, plane2)){
	    playerPlane.destroy();
	    plane2.destroy();
	    console.log("we have a collision")}

	if (plane2.type === "computer" && playerPlane.bullets.length > 0){
	    playerPlane.bullets.forEach(function(bullet){
		if (detector.check(plane2,bullet)) {
		    console.log("enemy destroyed");
		    playerPlane.increaseScore()
		    plane2.destroy()
		};
	    });
	};
    });
}

GameEngine.prototype.checkComputerActions = function(computerPlane, planes) {
    if (computerPlane.bullets.length > 0){
	planes.forEach(function(plane){
	    if (plane.type === "player" ){
		computerPlane.bullets.forEach(function(bullet){
		    if (detector.check(plane,bullet)) {
			plane.destroy()
			console.log("player destroyed")
		    };
		});
	    }
	})
    }
}

GameEngine.prototype.animate = function(planes) {
    fireCounter++
    let game = this;
    planes.forEach(function(plane1){
	game.moveBullets(plane1);
	if (plane1.type === "computer" && plane1.status == "alive") {game.takeComputerTurn(plane1)};
	if (plane1.type === "player") {game.checkPlayerActions(plane1, planes);}
	if (plane1.type === "computer"){game.checkComputerActions(plane1, planes)};
    });
};

module.exports = GameEngine;
<commit_msg>Computer plane destroyed when it leaves screen<commit_after>const CollisionDetector = require('./collision_detector')
var detector = new CollisionDetector();

function GameEngine() {
};

let fireCounter = 0

GameEngine.prototype.takeComputerTurn = function(computerPlane){
    computerPlane.move();
    if (computerPlane.y > 510){
	computerPlane.destroy;
    }
    if ((fireCounter % 75 === 0 || computerPlane.y === -5)) {
	computerPlane.fire()
    };
}

GameEngine.prototype.moveBullets = function(plane){
    let bullets = plane.bullets
    bullets.forEach(function(bullet){
	if (plane.type === "player") {
	    bullet.y = bullet.y - 3;
	} else if (plane.type === "computer") {
	    bullet.y = bullet.y + 3;
	};
	if (bullet.y > 510 ){
	    bullet.destroy();
	}
    });
}

GameEngine.prototype.checkPlayerActions = function(playerPlane, otherPlanes) {
    otherPlanes.forEach(function(plane2){
	if (plane2.type === "computer" && detector.check(playerPlane, plane2)){
	    playerPlane.destroy();
	    plane2.destroy();
	    console.log("we have a collision")}

	if (plane2.type === "computer" && playerPlane.bullets.length > 0){
	    playerPlane.bullets.forEach(function(bullet){
		if (detector.check(plane2,bullet)) {
		    console.log("enemy destroyed");
		    playerPlane.increaseScore()
		    plane2.destroy()
		};
	    });
	};
    });
}

GameEngine.prototype.checkComputerActions = function(computerPlane, planes) {
    if (computerPlane.bullets.length > 0){
	planes.forEach(function(plane){
	    if (plane.type === "player" ){
		computerPlane.bullets.forEach(function(bullet){
		    if (detector.check(plane,bullet)) {
			plane.destroy()
			console.log("player destroyed")
		    };
		});
	    }
	})
    }
}

GameEngine.prototype.animate = function(planes) {
    fireCounter++
    let game = this;
    planes.forEach(function(plane1){
	game.moveBullets(plane1);
	if (plane1.type === "computer" && plane1.status == "alive") {game.takeComputerTurn(plane1)};
	if (plane1.type === "player") {game.checkPlayerActions(plane1, planes);}
	if (plane1.type === "computer"){game.checkComputerActions(plane1, planes)};
    });
};

module.exports = GameEngine;
<|endoftext|>


<commit_before>import $ from 'jquery';
import Utils from './utils';

var Base = function () {
  this.__id__ = Utils.generateID();
};

Base.prototype = {
  asyncSupport: true, // Deprecated

  _pipeAccordingToValidationResult: function () {
    var pipe = () => {
      var r = $.Deferred();
      if (true !== this.validationResult)
        r.reject();
      return r.resolve().promise();
    };
    return [pipe, pipe];
  },

  actualizeOptions: function () {
    Utils.attr(this.element, this.options.namespace, this.domOptions);
    if (this.parent && this.parent.actualizeOptions)
      this.parent.actualizeOptions();
    return this;
  },

  _resetOptions: function (initOptions) {
    this.domOptions = Utils.objectCreate(this.parent.options);
    this.options = Utils.objectCreate(this.domOptions);
    // Shallow copy of ownProperties of initOptions:
    for (var i in initOptions) {
      if (initOptions.hasOwnProperty(i))
        this.options[i] = initOptions[i];
    }
    this.actualizeOptions();
  },

  _listeners: null,

  // Register a callback for the given event name
  // Callback is called with context as the first argument and the `this`
  // The context is the current parsley instance, or window.Parsley if global
  // A return value of `false` will interrupt the calls
  on: function (name, fn) {
    this._listeners = this._listeners || {};
    var queue = this._listeners[name] = this._listeners[name] || [];
    queue.push(fn);

    return this;
  },

  // Deprecated. Use `on` instead
  subscribe: function(name, fn) {
    $.listenTo(this, name.toLowerCase(), fn);
  },

  // Unregister a callback (or all if none is given) for the given event name
  off: function (name, fn) {
    var queue = this._listeners && this._listeners[name];
    if (queue) {
      if (!fn) {
        delete this._listeners[name];
      } else {
        for (var i = queue.length; i--; )
          if (queue[i] === fn)
            queue.splice(i, 1);
      }
    }
    return this;
  },

  // Deprecated. Use `off`
  unsubscribe: function(name, fn) {
    $.unsubscribeTo(this, name.toLowerCase());
  },

  // Trigger an event of the given name
  // A return value of `false` interrupts the callback chain
  // Returns false if execution was interrupted
  trigger: function (name, target, extraArg) {
    target = target || this;
    var queue = this._listeners && this._listeners[name];
    var result;
    var parentResult;
    if (queue) {
      for (var i = queue.length; i--; ) {
        result = queue[i].call(target, target, extraArg);
        if (result === false) return result;
      }
    }
    if (this.parent) {
      return this.parent.trigger(name, target, extraArg);
    }
    return true;
  },

  asyncIsValid: function (group, force) {
    Utils.warnOnce("asyncIsValid is deprecated; please use whenValid instead");
    return this.whenValid({group, force});
  },

  _findRelated: function () {
    return this.options.multiple ?
      this.parent.$element.find(`[${this.options.namespace}multiple="${this.options.multiple}"]`)
    : this.$element;
  }
};

export default Base;
<commit_msg>Avoid find [#1100]<commit_after>import $ from 'jquery';
import Utils from './utils';

var Base = function () {
  this.__id__ = Utils.generateID();
};

Base.prototype = {
  asyncSupport: true, // Deprecated

  _pipeAccordingToValidationResult: function () {
    var pipe = () => {
      var r = $.Deferred();
      if (true !== this.validationResult)
        r.reject();
      return r.resolve().promise();
    };
    return [pipe, pipe];
  },

  actualizeOptions: function () {
    Utils.attr(this.element, this.options.namespace, this.domOptions);
    if (this.parent && this.parent.actualizeOptions)
      this.parent.actualizeOptions();
    return this;
  },

  _resetOptions: function (initOptions) {
    this.domOptions = Utils.objectCreate(this.parent.options);
    this.options = Utils.objectCreate(this.domOptions);
    // Shallow copy of ownProperties of initOptions:
    for (var i in initOptions) {
      if (initOptions.hasOwnProperty(i))
        this.options[i] = initOptions[i];
    }
    this.actualizeOptions();
  },

  _listeners: null,

  // Register a callback for the given event name
  // Callback is called with context as the first argument and the `this`
  // The context is the current parsley instance, or window.Parsley if global
  // A return value of `false` will interrupt the calls
  on: function (name, fn) {
    this._listeners = this._listeners || {};
    var queue = this._listeners[name] = this._listeners[name] || [];
    queue.push(fn);

    return this;
  },

  // Deprecated. Use `on` instead
  subscribe: function(name, fn) {
    $.listenTo(this, name.toLowerCase(), fn);
  },

  // Unregister a callback (or all if none is given) for the given event name
  off: function (name, fn) {
    var queue = this._listeners && this._listeners[name];
    if (queue) {
      if (!fn) {
        delete this._listeners[name];
      } else {
        for (var i = queue.length; i--; )
          if (queue[i] === fn)
            queue.splice(i, 1);
      }
    }
    return this;
  },

  // Deprecated. Use `off`
  unsubscribe: function(name, fn) {
    $.unsubscribeTo(this, name.toLowerCase());
  },

  // Trigger an event of the given name
  // A return value of `false` interrupts the callback chain
  // Returns false if execution was interrupted
  trigger: function (name, target, extraArg) {
    target = target || this;
    var queue = this._listeners && this._listeners[name];
    var result;
    var parentResult;
    if (queue) {
      for (var i = queue.length; i--; ) {
        result = queue[i].call(target, target, extraArg);
        if (result === false) return result;
      }
    }
    if (this.parent) {
      return this.parent.trigger(name, target, extraArg);
    }
    return true;
  },

  asyncIsValid: function (group, force) {
    Utils.warnOnce("asyncIsValid is deprecated; please use whenValid instead");
    return this.whenValid({group, force});
  },

  _findRelated: function () {
    return this.options.multiple ?
      $(this.parent.element.querySelectorAll(`[${this.options.namespace}multiple="${this.options.multiple}"]`))
    : this.$element;
  }
};

export default Base;
<|endoftext|>


<commit_before>define([
    "jquery",
    "pat-registry"
    "pat-logger",
    "pat-parser"
], function($, patterns, logger, Parser) {
    var log = logger.getLogger("pat.sortable"),
        parser = new Parser("sortable");

    parser.add_argument("selector");

    var _ = {
        name: "sortable",
        trigger: ".pat-sortable",

        init: function($el) {
            if ($el.length > 1)
                return $el.each(function() { _.init($(this)); });

            var cfgs = parser.parse($el, true);
            $el.data("patterns.sortable", cfgs);

            // use only direct descendants to support nested lists
            var $sortables = $el.children().filter(cfgs[0].selector);

            // add handles and make them draggable for HTML5 and IE8/9
            // it has to be an "a" tag (or img) to make it draggable in IE8/9
            var $handles = $("").appendTo($sortables);
            if("draggable" in document.createElement("span"))
                $handles.attr("draggable", true);
            else
                $handles.bind("selectstart", function(event) {
                    event.preventDefault();
                });

            // invisible scroll activation areas
            var scrollup = $("

"),
                scrolldn = $("

"),
                scroll = $().add(scrollup).add(scrolldn);

            scrollup.css({ top: 0 });
            scrolldn.css({ bottom: 0 });
            scroll.css({
                position: "fixed", zIndex: 999999,
                height: 32, left: 0, right: 0
            });

            scroll.bind("dragover", function(event) {
                event.preventDefault();
                if ($("html,body").is(":animated")) return;

                var newpos = $(window).scrollTop() +
                    ($(this).attr("id")==="pat-scroll-up" ? -32 : 32);

                $("html,body").animate({scrollTop: newpos}, 50, "linear");
            });

            $handles.bind("dragstart", function(event) {
                // Firefox seems to need this set to any value
                event.originalEvent.dataTransfer.setData("Text", "");
                event.originalEvent.dataTransfer.effectAllowed = ["move"];
                if ("setDragImage" in event.originalEvent.dataTransfer)
                    event.originalEvent.dataTransfer.setDragImage(
                        $(this).parent()[0], 0, 0);
                $(this).parent().addClass("dragged");

                // Scroll the list if near the borders
                $el.bind("dragover.pat-sortable", function(event) {
                    event.preventDefault();
                    if ($el.is(":animated")) return;

                    var pos = event.originalEvent.clientY + $("body").scrollTop();

                    if (pos - $el.offset().top < 32)
                        $el.animate({scrollTop: $el.scrollTop()-32}, 50, "linear");
                    else if ($el.offset().top+$el.height() - pos < 32)
                        $el.animate({scrollTop: $el.scrollTop()+32}, 50, "linear");
                });

                // list elements are only drop targets when one element of the
                // list is being dragged. avoids dragging between lists.
                $sortables.bind("dragover.pat-sortable", function(event) {
                    var $this = $(this),
                        midlineY = $this.offset().top - $(document).scrollTop() +
                            $this.height()/2;

                    // bail if dropping on self
                    if ($(this).hasClass("dragged"))
                        return;

                    $this.removeClass("drop-target-above drop-target-below");
                    if (event.originalEvent.clientY > midlineY)
                        $this.addClass("drop-target-below");
                    else
                        $this.addClass("drop-target-above");
                    event.preventDefault();
                });

                $sortables.bind("dragleave.pat-sortable", function() {
                    $sortables.removeClass("drop-target-above drop-target-below");
                });

                $sortables.bind("drop.pat-sortable", function(event) {
                    if ($(this).hasClass("dragged"))
                        return;

                    if ($(this).hasClass("drop-target-below"))
                        $(this).after($(".dragged"));
                    else
                        $(this).before($(".dragged"));
                    $(this).removeClass("drop-target-above drop-target-below");
                    event.preventDefault();
                });

                //XXX: Deactivate document scroll areas, as DnD affects only
                //     scrolling of parent element
                //scroll.appendTo("body");
            });

            $handles.bind("dragend", function() {
                $(".dragged").removeClass("dragged");
                $sortables.unbind(".pat-sortable");
                $el.unbind(".pat-sortable");
                $("#pat-scroll-up, #pat-scroll-dn").detach();
            });

            return $el;
        }
    };

    patterns.register(_);
    return _;
});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<commit_msg>Remove unused logger variable.<commit_after>define([
    "jquery",
    "pat-registry"
    "pat-logger",
    "pat-parser"
], function($, patterns, logger, Parser) {
    var parser = new Parser("sortable");

    parser.add_argument("selector");

    var _ = {
        name: "sortable",
        trigger: ".pat-sortable",

        init: function($el) {
            if ($el.length > 1)
                return $el.each(function() { _.init($(this)); });

            var cfgs = parser.parse($el, true);
            $el.data("patterns.sortable", cfgs);

            // use only direct descendants to support nested lists
            var $sortables = $el.children().filter(cfgs[0].selector);

            // add handles and make them draggable for HTML5 and IE8/9
            // it has to be an "a" tag (or img) to make it draggable in IE8/9
            var $handles = $("").appendTo($sortables);
            if("draggable" in document.createElement("span"))
                $handles.attr("draggable", true);
            else
                $handles.bind("selectstart", function(event) {
                    event.preventDefault();
                });

            // invisible scroll activation areas
            var scrollup = $("

"),
                scrolldn = $("

"),
                scroll = $().add(scrollup).add(scrolldn);

            scrollup.css({ top: 0 });
            scrolldn.css({ bottom: 0 });
            scroll.css({
                position: "fixed", zIndex: 999999,
                height: 32, left: 0, right: 0
            });

            scroll.bind("dragover", function(event) {
                event.preventDefault();
                if ($("html,body").is(":animated")) return;

                var newpos = $(window).scrollTop() +
                    ($(this).attr("id")==="pat-scroll-up" ? -32 : 32);

                $("html,body").animate({scrollTop: newpos}, 50, "linear");
            });

            $handles.bind("dragstart", function(event) {
                // Firefox seems to need this set to any value
                event.originalEvent.dataTransfer.setData("Text", "");
                event.originalEvent.dataTransfer.effectAllowed = ["move"];
                if ("setDragImage" in event.originalEvent.dataTransfer)
                    event.originalEvent.dataTransfer.setDragImage(
                        $(this).parent()[0], 0, 0);
                $(this).parent().addClass("dragged");

                // Scroll the list if near the borders
                $el.bind("dragover.pat-sortable", function(event) {
                    event.preventDefault();
                    if ($el.is(":animated")) return;

                    var pos = event.originalEvent.clientY + $("body").scrollTop();

                    if (pos - $el.offset().top < 32)
                        $el.animate({scrollTop: $el.scrollTop()-32}, 50, "linear");
                    else if ($el.offset().top+$el.height() - pos < 32)
                        $el.animate({scrollTop: $el.scrollTop()+32}, 50, "linear");
                });

                // list elements are only drop targets when one element of the
                // list is being dragged. avoids dragging between lists.
                $sortables.bind("dragover.pat-sortable", function(event) {
                    var $this = $(this),
                        midlineY = $this.offset().top - $(document).scrollTop() +
                            $this.height()/2;

                    // bail if dropping on self
                    if ($(this).hasClass("dragged"))
                        return;

                    $this.removeClass("drop-target-above drop-target-below");
                    if (event.originalEvent.clientY > midlineY)
                        $this.addClass("drop-target-below");
                    else
                        $this.addClass("drop-target-above");
                    event.preventDefault();
                });

                $sortables.bind("dragleave.pat-sortable", function() {
                    $sortables.removeClass("drop-target-above drop-target-below");
                });

                $sortables.bind("drop.pat-sortable", function(event) {
                    if ($(this).hasClass("dragged"))
                        return;

                    if ($(this).hasClass("drop-target-below"))
                        $(this).after($(".dragged"));
                    else
                        $(this).before($(".dragged"));
                    $(this).removeClass("drop-target-above drop-target-below");
                    event.preventDefault();
                });

                //XXX: Deactivate document scroll areas, as DnD affects only
                //     scrolling of parent element
                //scroll.appendTo("body");
            });

            $handles.bind("dragend", function() {
                $(".dragged").removeClass("dragged");
                $sortables.unbind(".pat-sortable");
                $el.unbind(".pat-sortable");
                $("#pat-scroll-up, #pat-scroll-dn").detach();
            });

            return $el;
        }
    };

    patterns.register(_);
    return _;
});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<|endoftext|>