<commit_before>function processMessageNodes(nodes) {
    var reportKeys = {
        sr: [],     //spy report
        cr: [],     //combat report
        rr: [],     //recycle report
        mr: []      //missile report
    };

    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var apiElement = node.querySelector(".icon_apikey");
        if (apiElement !== null) {
            var key = apiElement.parentNode.href.replace(/.*?:\/\//g, "");
            var type = key.substring(0, 2);
            if (reportKeys.hasOwnProperty(type)) {
                reportKeys[type].push(key);
            }
        }
    }

    postData({
        endpoint: "keys",
        data: {
            reportKeys: reportKeys
        }
    });
}

function processPlanetNodes(nodes) {
    var planets = [];

    for (var i = 0; i < nodes.length; i++) {
        var planetDiv = nodes[i];
        var planetId = planetDiv.id.replace("planet-", "");
        var planetName = planetDiv.querySelector(".planet-name").innerHTML;
        var planetCoordsRaw = planetDiv.querySelector(".planet-koords").innerHTML;
        var planetCoordsList = planetCoordsRaw.slice(1, -1).split(":");
        planets.push({
            id: planetId,
            name: planetName,
            galaxy: planetCoordsList[0],
            solarSystem: planetCoordsList[1],
            position: planetCoordsList[2]
        });
    }

    executeIfNotCached("planets", planets, function () {
        postData({
            endpoint: "planets",
            data: {
                planets: planets
            }
        });
    });
}

function processResearchNodes(nodes) {
    var researches = [];

    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var detailButtonElement = node.querySelector(".detail_button");
        if (detailButtonElement !== null) {
            var researchId = detailButtonElement.getAttribute("ref");
            var cloneLevelNode = detailButtonElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var researchLevel = cloneLevelNode.innerHTML.trim();

            researches.push({
                id: researchId,
                level: researchLevel
            });
        }
    }

    executeIfNotCached("researches", researches, function () {
        postData({
            endpoint: "researches",
            data: {
                researches: researches
            }
        });
    });
}

function processResourceBuildingNodes(supplyNodes, itemBoxNodes) {
    var buildings = [];

    for (var i = 0; i < supplyNodes.length; i++) {
        var node = supplyNodes[i];
        var detailButtonElement = node.querySelector("[id='details']");
        if (detailButtonElement !== null) {
            var buildingId = detailButtonElement.getAttribute("ref");
            var cloneLevelNode = detailButtonElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var buildingLevel = cloneLevelNode.innerHTML.trim();

            buildings.push({
                id: buildingId,
                level: buildingLevel
            });
        }
    }

    for (var i = 0; i < itemBoxNodes.length; i++) {
        var node = itemBoxNodes[i];
        var detailButtonElement = node.querySelector("[id^='details']");
        if (detailButtonElement !== null) {
            var buildingId = detailButtonElement.getAttribute("ref");
            var cloneLevelNode = detailButtonElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var buildingLevel = cloneLevelNode.innerHTML.trim();

            buildings.push({
                id: buildingId,
                level: buildingLevel
            });
        }
    }

    executeIfNotCachedForPlanet("resource_buildings", buildings, function () {
        var data = {
            buildings: buildings
        }
        addPlanetData(data);
        postData({
            endpoint: "resourceBuildings",
            data: data
        });
    });
}

function processFacilityBuildingNodes(nodes) {
    var buildings = [];

    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var detailButtonElement = node.querySelector(".detail_button");
        if (detailButtonElement !== null) {
            var buildingId = detailButtonElement.getAttribute("ref");
            var cloneLevelNode = detailButtonElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var buildingLevel = cloneLevelNode.innerHTML.trim();

            buildings.push({
                id: buildingId,
                level: buildingLevel
            });
        }
    }

    executeIfNotCachedForPlanet("facility_buildings", buildings, function () {
        var data = {
            buildings: buildings
        }
        addPlanetData(data);
        postData({
            endpoint: "facilityBuildings",
            data: data
        });
    });
}

function processDefenceNodes(nodes) {
    var defences = [];

    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var detailButtonElement = node.querySelector(".detail_button");
        if (detailButtonElement !== null) {
            var defenceId = detailButtonElement.getAttribute("ref");
            var cloneLevelNode = detailButtonElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var defenceAmount = cloneLevelNode.innerHTML.trim();

            defences.push({
                id: defenceId,
                amount: defenceAmount
            });
        }
    }

    executeIfNotCachedForPlanet("defences", defences, function () {
        var data = {
            defences: defences
        }
        addPlanetData(data);
        postData({
            endpoint: "defences",
            data: data
        });
    });
}

function processFleetNodes(nodes) {
    var fleet = [];

    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var aTooltipElement = node.querySelector(".tooltip");
        if (aTooltipElement !== null) {
            var fleetId = node.id.replace(/\D/g, "");
            var cloneLevelNode = aTooltipElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var fleetAmount = cloneLevelNode.innerHTML.trim();

            fleet.push({
                id: fleetId,
                amount: fleetAmount
            });
        }
    }

    executeIfNotCachedForPlanet("fleet", fleet, function () {
        var data = {
            fleet: fleet
        }
        addPlanetData(data);
        postData({
            endpoint: "fleet",
            data: data
        });
    });
}

function processShipyardNodes(nodes) {
    var shipyard = [];

    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var detailButtonElement = node.querySelector(".detail_button");
        if (detailButtonElement !== null) {
            var fleetId = detailButtonElement.getAttribute("ref");
            var cloneLevelNode = detailButtonElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var fleetAmount = cloneLevelNode.innerHTML.trim();

            shipyard.push({
                id: fleetId,
                amount: fleetAmount
            });
        }
    }

    executeIfNotCachedForPlanet("shipyard", shipyard, function () {
        var data = {
            shipyard: shipyard
        }
        addPlanetData(data);
        postData({
            endpoint: "shipyard",
            data: data
        });
    });
}

function postData(object) {
    addPlayerData(object.data);
    console.log(JSON.stringify(object.data));
    GM_xmlhttpRequest({
        method: "POST",
        url: oLogInstanceUrl + "api/userscript/" + object.endpoint,
        data: JSON.stringify(object.data),
        headers: {
            "Content-Type": "application/json"
        },
        onload: function(response) {
            console.log("load");
        },
        onerror: function(response) {
            console.log("error");
        }
    });
}

function addPlayerData(data) {
    data.serverGroup = getWindowVariable("constants.language");
    data.universe = getWindowVariable("constants.name");
    data.playerId = getWindowVariable("playerId");
    data.playerName = getWindowVariable("playerName");
}

function addPlanetData(data) {
    data.planetId = getMetaValue("ogame-planet-id");
    data.planetName = getMetaValue("ogame-planet-name");
    var coordinate = getMetaValue("ogame-planet-coordinates").split(":");
    data.planetGalaxy = coordinate[0];
    data.planetSolarSystem = coordinate[1];
    data.planetPosition = coordinate[2];
}

/**
 * Returns a global variable.
 *
 * Needed because a @grant other than none puts the script
 * in a separate scope.
 *
 * @param {string} name The name of the global variable.
 */
function getWindowVariable(name) {
    return window.eval(name);
}

function getMetaValue(name) {
    return document.querySelector("meta[name='" + name + "']").content;
}

function showSuccessMessage(message) {
    getWindowVariable("fadeBox")(message, false);
}

function showErrorMessage(message) {
    getWindowVariable("fadeBox")(message, true);
}

function saveSetting(key, value) {
    GM_setValue(getFullSettingsKey(key), value);
}

function getSetting(key, defaultValue) {
    return GM_getValue(getFullSettingsKey(key), defaultValue);
}

function getFullSettingsKey(key) {
    return getWindowVariable("constants.language") + ":" + getWindowVariable("constants.name") + ":" + getWindowVariable("playerId") + ":" + key;
}

function executeIfNotCached(cacheKey, value, callback) {
    var valueString = JSON.stringify(value);
    var fullCacheKey = getFullCacheKey(cacheKey);
    if (getSetting(fullCacheKey, "") !== valueString) {
        callback();
        saveSetting(fullCacheKey, valueString);
    }
}

function executeIfNotCachedForPlanet(cacheKey, value, callback) {
    var valueString = JSON.stringify(value);
    var planetCacheKey = getPlanetCacheKey(cacheKey);
    var fullCacheKey = getFullCacheKey(planetCacheKey);
    if (getSetting(fullCacheKey, "") !== valueString) {
        callback();
        saveSetting(fullCacheKey, valueString);
        addPlanetCacheKeyToUsedCacheKeys(planetCacheKey);
    }
}

function getPlanetCacheKey(cacheKey) {
    return getMetaValue("ogame-planet-id") + "_" + cacheKey;
}

function getFullCacheKey(cacheKey) {
    return "cache." + cacheKey;
}

function addPlanetCacheKeyToUsedCacheKeys(planetCacheKey) {
    var planetCacheKeys = getSetting("planetCacheKeys", "");
    if (planetCacheKeys.indexOf(planetCacheKey) === -1) {
        planetCacheKeys = planetCacheKeys + ";" + planetCacheKey;
        saveSetting("planetCacheKeys", planetCacheKeys);
    }
}

function getUsedPlanetCacheKeys() {
    var planetCacheKeys = getSetting("planetCacheKeys", "");
    return planetCacheKeys.split(";").filter(function (value) {
        return value !== "";
    });
}<commit_msg>Update API endpoints to be prefixed with "planet".<commit_after>function processMessageNodes(nodes) {
    var reportKeys = {
        sr: [],     //spy report
        cr: [],     //combat report
        rr: [],     //recycle report
        mr: []      //missile report
    };

    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var apiElement = node.querySelector(".icon_apikey");
        if (apiElement !== null) {
            var key = apiElement.parentNode.href.replace(/.*?:\/\//g, "");
            var type = key.substring(0, 2);
            if (reportKeys.hasOwnProperty(type)) {
                reportKeys[type].push(key);
            }
        }
    }

    postData({
        endpoint: "keys",
        data: {
            reportKeys: reportKeys
        }
    });
}

function processPlanetNodes(nodes) {
    var planets = [];

    for (var i = 0; i < nodes.length; i++) {
        var planetDiv = nodes[i];
        var planetId = planetDiv.id.replace("planet-", "");
        var planetName = planetDiv.querySelector(".planet-name").innerHTML;
        var planetCoordsRaw = planetDiv.querySelector(".planet-koords").innerHTML;
        var planetCoordsList = planetCoordsRaw.slice(1, -1).split(":");
        planets.push({
            id: planetId,
            name: planetName,
            galaxy: planetCoordsList[0],
            solarSystem: planetCoordsList[1],
            position: planetCoordsList[2]
        });
    }

    executeIfNotCached("planets", planets, function () {
        postData({
            endpoint: "planets",
            data: {
                planets: planets
            }
        });
    });
}

function processResearchNodes(nodes) {
    var researches = [];

    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var detailButtonElement = node.querySelector(".detail_button");
        if (detailButtonElement !== null) {
            var researchId = detailButtonElement.getAttribute("ref");
            var cloneLevelNode = detailButtonElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var researchLevel = cloneLevelNode.innerHTML.trim();

            researches.push({
                id: researchId,
                level: researchLevel
            });
        }
    }

    executeIfNotCached("researches", researches, function () {
        postData({
            endpoint: "researches",
            data: {
                researches: researches
            }
        });
    });
}

function processResourceBuildingNodes(supplyNodes, itemBoxNodes) {
    var buildings = [];

    for (var i = 0; i < supplyNodes.length; i++) {
        var node = supplyNodes[i];
        var detailButtonElement = node.querySelector("[id='details']");
        if (detailButtonElement !== null) {
            var buildingId = detailButtonElement.getAttribute("ref");
            var cloneLevelNode = detailButtonElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var buildingLevel = cloneLevelNode.innerHTML.trim();

            buildings.push({
                id: buildingId,
                level: buildingLevel
            });
        }
    }

    for (var i = 0; i < itemBoxNodes.length; i++) {
        var node = itemBoxNodes[i];
        var detailButtonElement = node.querySelector("[id^='details']");
        if (detailButtonElement !== null) {
            var buildingId = detailButtonElement.getAttribute("ref");
            var cloneLevelNode = detailButtonElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var buildingLevel = cloneLevelNode.innerHTML.trim();

            buildings.push({
                id: buildingId,
                level: buildingLevel
            });
        }
    }

    executeIfNotCachedForPlanet("resource_buildings", buildings, function () {
        var data = {
            buildings: buildings
        }
        addPlanetData(data);
        postData({
            endpoint: "planetResourceBuildings",
            data: data
        });
    });
}

function processFacilityBuildingNodes(nodes) {
    var buildings = [];

    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var detailButtonElement = node.querySelector(".detail_button");
        if (detailButtonElement !== null) {
            var buildingId = detailButtonElement.getAttribute("ref");
            var cloneLevelNode = detailButtonElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var buildingLevel = cloneLevelNode.innerHTML.trim();

            buildings.push({
                id: buildingId,
                level: buildingLevel
            });
        }
    }

    executeIfNotCachedForPlanet("facility_buildings", buildings, function () {
        var data = {
            buildings: buildings
        }
        addPlanetData(data);
        postData({
            endpoint: "planetFacilityBuildings",
            data: data
        });
    });
}

function processDefenceNodes(nodes) {
    var defences = [];

    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var detailButtonElement = node.querySelector(".detail_button");
        if (detailButtonElement !== null) {
            var defenceId = detailButtonElement.getAttribute("ref");
            var cloneLevelNode = detailButtonElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var defenceAmount = cloneLevelNode.innerHTML.trim();

            defences.push({
                id: defenceId,
                amount: defenceAmount
            });
        }
    }

    executeIfNotCachedForPlanet("defences", defences, function () {
        var data = {
            defences: defences
        }
        addPlanetData(data);
        postData({
            endpoint: "planetDefences",
            data: data
        });
    });
}

function processFleetNodes(nodes) {
    var fleet = [];

    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var aTooltipElement = node.querySelector(".tooltip");
        if (aTooltipElement !== null) {
            var fleetId = node.id.replace(/\D/g, "");
            var cloneLevelNode = aTooltipElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var fleetAmount = cloneLevelNode.innerHTML.trim();

            fleet.push({
                id: fleetId,
                amount: fleetAmount
            });
        }
    }

    executeIfNotCachedForPlanet("fleet", fleet, function () {
        var data = {
            fleet: fleet
        }
        addPlanetData(data);
        postData({
            endpoint: "planetFleet",
            data: data
        });
    });
}

function processShipyardNodes(nodes) {
    var shipyard = [];

    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var detailButtonElement = node.querySelector(".detail_button");
        if (detailButtonElement !== null) {
            var fleetId = detailButtonElement.getAttribute("ref");
            var cloneLevelNode = detailButtonElement.querySelector(".level").cloneNode(true);
            var children = [].slice.call(cloneLevelNode.children);

            for (var j = 0; j < children.length; j++) {
                var child = children[j];
                if (child.className === "textlabel" || child.className === "undermark") {
                    cloneLevelNode.removeChild(child);
                }
            }
            var fleetAmount = cloneLevelNode.innerHTML.trim();

            shipyard.push({
                id: fleetId,
                amount: fleetAmount
            });
        }
    }

    executeIfNotCachedForPlanet("shipyard", shipyard, function () {
        var data = {
            shipyard: shipyard
        }
        addPlanetData(data);
        postData({
            endpoint: "planetShipyard",
            data: data
        });
    });
}

function postData(object) {
    addPlayerData(object.data);
    console.log(JSON.stringify(object.data));
    GM_xmlhttpRequest({
        method: "POST",
        url: oLogInstanceUrl + "api/userscript/" + object.endpoint,
        data: JSON.stringify(object.data),
        headers: {
            "Content-Type": "application/json"
        },
        onload: function(response) {
            console.log("load");
        },
        onerror: function(response) {
            console.log("error");
        }
    });
}

function addPlayerData(data) {
    data.serverGroup = getWindowVariable("constants.language");
    data.universe = getWindowVariable("constants.name");
    data.playerId = getWindowVariable("playerId");
    data.playerName = getWindowVariable("playerName");
}

function addPlanetData(data) {
    data.planetId = getMetaValue("ogame-planet-id");
    data.planetName = getMetaValue("ogame-planet-name");
    var coordinate = getMetaValue("ogame-planet-coordinates").split(":");
    data.planetGalaxy = coordinate[0];
    data.planetSolarSystem = coordinate[1];
    data.planetPosition = coordinate[2];
}

/**
 * Returns a global variable.
 *
 * Needed because a @grant other than none puts the script
 * in a separate scope.
 *
 * @param {string} name The name of the global variable.
 */
function getWindowVariable(name) {
    return window.eval(name);
}

function getMetaValue(name) {
    return document.querySelector("meta[name='" + name + "']").content;
}

function showSuccessMessage(message) {
    getWindowVariable("fadeBox")(message, false);
}

function showErrorMessage(message) {
    getWindowVariable("fadeBox")(message, true);
}

function saveSetting(key, value) {
    GM_setValue(getFullSettingsKey(key), value);
}

function getSetting(key, defaultValue) {
    return GM_getValue(getFullSettingsKey(key), defaultValue);
}

function getFullSettingsKey(key) {
    return getWindowVariable("constants.language") + ":" + getWindowVariable("constants.name") + ":" + getWindowVariable("playerId") + ":" + key;
}

function executeIfNotCached(cacheKey, value, callback) {
    var valueString = JSON.stringify(value);
    var fullCacheKey = getFullCacheKey(cacheKey);
    if (getSetting(fullCacheKey, "") !== valueString) {
        callback();
        saveSetting(fullCacheKey, valueString);
    }
}

function executeIfNotCachedForPlanet(cacheKey, value, callback) {
    var valueString = JSON.stringify(value);
    var planetCacheKey = getPlanetCacheKey(cacheKey);
    var fullCacheKey = getFullCacheKey(planetCacheKey);
    if (getSetting(fullCacheKey, "") !== valueString) {
        callback();
        saveSetting(fullCacheKey, valueString);
        addPlanetCacheKeyToUsedCacheKeys(planetCacheKey);
    }
}

function getPlanetCacheKey(cacheKey) {
    return getMetaValue("ogame-planet-id") + "_" + cacheKey;
}

function getFullCacheKey(cacheKey) {
    return "cache." + cacheKey;
}

function addPlanetCacheKeyToUsedCacheKeys(planetCacheKey) {
    var planetCacheKeys = getSetting("planetCacheKeys", "");
    if (planetCacheKeys.indexOf(planetCacheKey) === -1) {
        planetCacheKeys = planetCacheKeys + ";" + planetCacheKey;
        saveSetting("planetCacheKeys", planetCacheKeys);
    }
}

function getUsedPlanetCacheKeys() {
    var planetCacheKeys = getSetting("planetCacheKeys", "");
    return planetCacheKeys.split(";").filter(function (value) {
        return value !== "";
    });
}<|endoftext|>


<commit_before>/*
WHAT: SublimeText-like Fuzzy Search

USAGE:
  require('fuzzysort').single('fs', 'Fuzzy Search')
  // {score: 0.1, highlighted: 'Fuzzy Search'}

  require('fuzzysort').single('test', 'test')
  // {score: 0, highlighted}

  require('fuzzysort').single('doesnt exist', 'target')
  // {}
*/

;(function() {
  var fuzzysort = {

    noMatchLimit: 100, // if there's no match for a span this long, give up
    highlightMatches: true,
    highlightOpen: '',
    highlightClose: '',
    limit: null, // don't return more results than this

    single: (search, target) => {
      const result = fuzzysort.info(search, target)
      if(result == null) return null

      if(fuzzysort.highlightMatches) {
        result.highlighted = fuzzysort.highlight(result)
      }
      return result
    },

    go: (search, targets) => {
      var i = targets.length-1
      const results = []
      for(; i>=0; i-=1) {
        const result = fuzzysort.info(search, targets[i])
        if(result) results.push(result)
      }

      const len = results.length
      quickSortResults(results, 0, len)

      results.total = len
      if(fuzzysort.limit!=null && len > fuzzysort.limit) {
        results.length = fuzzysort.limit
      }
      if(fuzzysort.highlightMatches) {
        for (i = results.length - 1; i >= 0; i--) {
          const result = results[i]
          result.highlighted = fuzzysort.highlight(result)
        }
      }

      return results
    },

    goAsync: (search, targets) => {
      var canceled = false
      const p = new Promise((resolve, reject) => {
        var i = targets.length-1
        const results = []
        const itemsPerCheck = 1000
        function step() {
          if(canceled) return reject('canceled')

          const startMs = Date.now()

          for(; i>=0; i-=1) {
            const result = fuzzysort.info(search, targets[i])
            if(result) results.push(result)

            if(i%itemsPerCheck===0) {
              if(Date.now() - startMs >= 16) {
                ;(typeof setImmediate !== 'undefined')?setImmediate(step):setTimeout(step)
                return
              }
            }
          }

          const len = results.length
          quickSortResults(results, 0, len)

          results.total = len
          if(fuzzysort.limit!=null && len > fuzzysort.limit) {
            results.length = fuzzysort.limit
          }
          if(fuzzysort.highlightMatches) {
            for (i = results.length - 1; i >= 0; i--) {
              const result = results[i]
              result.highlighted = fuzzysort.highlight(result)
            }
          }

          resolve(results)
        }

        // step()
        ;(typeof setImmediate !== 'undefined')?setImmediate(step):setTimeout(step)
      })
      p.cancel = () => {
        canceled = true
      }
      return p
    },

    info: (search, target) => {
      var searchI = 0 // where we at
      var targetI = 0 // where you at

      var noMatchCount = 0 // how long since we've seen a match
      const matches = [] // target indexes

      const lowerSearch = search.toLowerCase()
      const lowerTarget = target.toLowerCase()
      const searchLength = search.length
      const targetLength = target.length

      // very basic fuzzy match; to remove targets with no match ASAP
      // walk through search and target. find sequential matches.
      // if all chars aren't found then exit
      while(true) {
        const isMatch = lowerSearch[searchI] === lowerTarget[targetI]

        if(isMatch) {
          matches.push(targetI)

          searchI += 1
          if(searchI === searchLength) break
          noMatchCount = 0
        } else {
          noMatchCount += 1
          if(noMatchCount >= fuzzysort.noMatchLimit) return null
        }

        targetI += 1
        if(targetI === targetLength) return null
      }

      // Let's try a more advanced and strict test to improve the score
      // only count it as a match if it's consecutive or a beginning character!
      // we use information about previous matches to skip around here and improve performance

      var strictSuccess = false
      const strictMatches = []
      const upperTarget = target.toUpperCase()

      var wasUpper = null
      var wasChar = false
      var isConsec = false

      searchI = 0
      noMatchCount = 0

      if(matches[0]>0) {
        // skip and backfill history
        targetI = matches[0]
        wasUpper = target[targetI-1] === upperTarget[targetI-1]
        const targetCharCode = lowerTarget[targetI-1].charCodeAt(0)
        wasChar = targetCharCode>=48&&targetCharCode<=57 || targetCharCode>=97&&targetCharCode<=122
      } else {
        targetI = 0
      }

      while(true) {

        if (targetI >= targetLength) {
          // We failed to find a good spot for the search char, go back to the previous search char and force it forward
          if (searchI <= 0) break
          searchI -= 1

          const lastMatch = strictMatches.pop()
          targetI = lastMatch + 1

          isConsec = false
          // backfill history
          wasUpper = target[targetI-1] === upperTarget[targetI-1]
          const targetCharCode = lowerTarget[targetI-1].charCodeAt(0)
          wasChar = targetCharCode>=48&&targetCharCode<=57 || targetCharCode>=97&&targetCharCode<=122
          continue
        }

        const isUpper = target[targetI] === upperTarget[targetI]
        const targetChar = lowerTarget[targetI]
        const targetCharCode = targetChar.charCodeAt(0)
        const isChar = targetCharCode>=48&&targetCharCode<=57 || targetCharCode>=97&&targetCharCode<=122
        const isBeginning = isUpper && !wasUpper || isChar && !wasChar
        wasUpper = isUpper
        wasChar = isChar
        if (!isBeginning && !isConsec) {
          targetI += 1
          continue
        }

        const isMatch = lowerSearch[searchI] === targetChar

        if(isMatch) {
          strictMatches.push(targetI)

          searchI += 1
          if(searchI === searchLength) {
            strictSuccess = true
            break
          }

          targetI += 1
          isConsec = true
          const wouldSkipAhead = matches[searchI] > targetI
          if(wouldSkipAhead) {
            const nextMatchIsNextTarget = matches[searchI] == targetI
            if(!nextMatchIsNextTarget) {
              // skip and backfill history
              targetI = matches[searchI]
              isConsec = false
              wasUpper = target[targetI-1] === upperTarget[targetI-1]
              const targetCharCode = lowerTarget[targetI-1].charCodeAt(0)
              wasChar = targetCharCode>=48&&targetCharCode<=57 || targetCharCode>=97&&targetCharCode<=122
            }
          }

          noMatchCount = 0
        } else {
          noMatchCount += 1
          if(noMatchCount >= fuzzysort.noMatchLimit) break
          isConsec = false
          targetI += 1
        }

      }

      const ret = {}

      // tally up the score & keep track of matches for highlighting later
      ret.score = 0
      var lastTargetI = -1
      const theMatches = strictSuccess ? strictMatches : matches
      for(const targetI of theMatches) {
        // score only goes up if they not consecutive
        if(lastTargetI !== targetI - 1) ret.score += targetI

        lastTargetI = targetI
      }
      if(strictSuccess) ret.score /= 1000
      ret.score += targetLength/1000 - searchLength/1000

      if(fuzzysort.highlightMatches) {
        ret.target = target
        ret.theMatches = strictSuccess ? strictMatches : matches
      }

      return ret
    },

    highlight: (result) => {
      var highlighted = ''
      var matchesIndex = 0
      var opened = false
      const target = result.target
      const targetLength = target.length
      const theMatches = result.theMatches
      for(var i=0; i<targetLength; i++) {
        if(theMatches[matchesIndex] === i) {
          matchesIndex += 1
          if(!opened) {
            highlighted += fuzzysort.highlightOpen
            opened = true
          }

          if(matchesIndex === theMatches.length) {
            highlighted += `${target[i]}${fuzzysort.highlightClose}${target.substr(i+1)}`
            break
          }
        } else {
          if(opened) {
            highlighted += fuzzysort.highlightClose
            opened = false
          }
        }
        highlighted += target[i]
      }

      return highlighted
    }
  }

  function quickSortPartition(results, left, right) {
    const cmp = results[right-1].score
    var minEnd = left
    var maxEnd = left
    for (; maxEnd < right-1; maxEnd += 1) {
      if (results[maxEnd].score <= cmp) {
        swap(results, maxEnd, minEnd)
        minEnd += 1
      }
    }
    swap(results, minEnd, right-1)
    return minEnd
  }

  function swap(results, i, j) {
    const temp = results[i]
    results[i] = results[j]
    results[j] = temp
  }

  function quickSortResults(results, left, right) {
    if (left < right) {
      var p = quickSortPartition(results, left, right)
      quickSortResults(results, left, p)
      quickSortResults(results, p + 1, right)
    }
  }

  // Export fuzzysort
    if(typeof module !== 'undefined' && module.exports) {
      module.exports = fuzzysort
    } else if(typeof window !== 'undefined') {
      window.fuzzysort = fuzzysort
    }
})()

<commit_msg>micro optimization str.charCodeAt(i) is faster than str[i]<commit_after>/*
WHAT: SublimeText-like Fuzzy Search

USAGE:
  require('fuzzysort').single('fs', 'Fuzzy Search')
  // {score: 0.1, highlighted: 'Fuzzy Search'}

  require('fuzzysort').single('test', 'test')
  // {score: 0, highlighted}

  require('fuzzysort').single('doesnt exist', 'target')
  // {}
*/

;(function() {
  var fuzzysort = {

    noMatchLimit: 100, // if there's no match for a span this long, give up
    highlightMatches: true,
    highlightOpen: '',
    highlightClose: '',
    limit: null, // don't return more results than this

    single: (search, target) => {
      const result = fuzzysort.info(search, target)
      if(result === null) return null

      if(fuzzysort.highlightMatches) {
        result.highlighted = fuzzysort.highlight(result)
      }
      return result
    },

    go: (search, targets) => {
      var i = targets.length-1
      const results = []
      for(; i>=0; i-=1) {
        const result = fuzzysort.info(search, targets[i])
        if(result) results.push(result)
      }

      const len = results.length
      quickSortResults(results, 0, len)

      results.total = len
      if(fuzzysort.limit!=null && len > fuzzysort.limit) {
        results.length = fuzzysort.limit
      }
      if(fuzzysort.highlightMatches) {
        for (i = results.length - 1; i >= 0; i--) {
          const result = results[i]
          result.highlighted = fuzzysort.highlight(result)
        }
      }

      return results
    },

    goAsync: (search, targets) => {
      var canceled = false
      const p = new Promise((resolve, reject) => {
        var i = targets.length-1
        const results = []
        const itemsPerCheck = 1000
        function step() {
          if(canceled) return reject('canceled')

          const startMs = Date.now()

          for(; i>=0; i-=1) {
            const result = fuzzysort.info(search, targets[i])
            if(result) results.push(result)

            if(i%itemsPerCheck===0) {
              if(Date.now() - startMs >= 16) {
                ;(typeof setImmediate !== 'undefined')?setImmediate(step):setTimeout(step)
                return
              }
            }
          }

          const len = results.length
          quickSortResults(results, 0, len)

          results.total = len
          if(fuzzysort.limit!=null && len > fuzzysort.limit) {
            results.length = fuzzysort.limit
          }
          if(fuzzysort.highlightMatches) {
            for (i = results.length - 1; i >= 0; i--) {
              const result = results[i]
              result.highlighted = fuzzysort.highlight(result)
            }
          }

          resolve(results)
        }

        // step()
        ;(typeof setImmediate !== 'undefined')?setImmediate(step):setTimeout(step)
      })
      p.cancel = () => {
        canceled = true
      }
      return p
    },

    info: (search, target) => {
      var searchI = 0 // where we at
      var targetI = 0 // where you at

      var noMatchCount = 0 // how long since we've seen a match
      var matches // target indexes

      const lowerSearch = search.toLowerCase()
      const lowerTarget = target.toLowerCase()
      const searchLength = search.length
      const targetLength = target.length
      var currentSearchCode = lowerSearch.charCodeAt(0)
      var currentTargetCode = lowerTarget.charCodeAt(0)

      // very basic fuzzy match; to remove targets with no match ASAP
      // walk through search and target. find sequential matches.
      // if all chars aren't found then exit
      while(true) {
        const isMatch = currentSearchCode === currentTargetCode

        if(isMatch) {
          if(matches === undefined) {
            matches = [targetI]
          } else {
            matches.push(targetI)
          }

          searchI += 1
          if(searchI === searchLength) break
          currentSearchCode = lowerSearch.charCodeAt(searchI)
          noMatchCount = 0
        } else {
          noMatchCount += 1
          if(noMatchCount >= fuzzysort.noMatchLimit) return null
        }

        targetI += 1
        if(targetI === targetLength) return null
        currentTargetCode = lowerTarget.charCodeAt(targetI)
      }

      // Let's try a more advanced and strict test to improve the score
      // only count it as a match if it's consecutive or a beginning character!
      // we use information about previous matches to skip around here and improve performance

      var strictSuccess = false
      var strictMatches
      const upperTarget = target.toUpperCase()

      var wasUpper = null
      var wasChar = false
      var isConsec = false

      searchI = 0
      noMatchCount = 0

      if(matches[0]>0) {
        // skip and backfill history
        targetI = matches[0]
        wasUpper = target.charCodeAt(targetI-1) === upperTarget.charCodeAt(targetI-1)
        const targetCharCode = lowerTarget.charCodeAt(targetI-1)
        wasChar = targetCharCode>=48&&targetCharCode<=57 || targetCharCode>=97&&targetCharCode<=122
      } else {
        targetI = 0
      }

      while(true) {

        if (targetI >= targetLength) {
          // We failed to find a good spot for the search char, go back to the previous search char and force it forward
          if (searchI <= 0) break
          searchI -= 1

          const lastMatch = strictMatches.pop()
          targetI = lastMatch + 1

          isConsec = false
          // backfill history
          const upperCharCode = upperTarget.charCodeAt(targetI-1)
          wasUpper = target.charCodeAt(targetI-1) === upperCharCode
          wasChar = upperCharCode>=48&&upperCharCode<=57 || upperCharCode>=65&&upperCharCode<=90
          continue
        }

        const isUpper = target.charCodeAt(targetI) === upperTarget.charCodeAt(targetI)
        const targetCharCode = lowerTarget.charCodeAt(targetI)
        const isChar = targetCharCode>=48&&targetCharCode<=57 || targetCharCode>=97&&targetCharCode<=122
        const isBeginning = isUpper && !wasUpper || isChar && !wasChar
        wasUpper = isUpper
        wasChar = isChar
        if (!isBeginning && !isConsec) {
          targetI += 1
          continue
        }

        const isMatch = lowerSearch.charCodeAt(searchI) === targetCharCode

        if(isMatch) {
          if(strictMatches === undefined) {
            strictMatches = [targetI]
          } else {
            strictMatches.push(targetI)
          }

          searchI += 1
          if(searchI === searchLength) {
            strictSuccess = true
            break
          }

          targetI += 1
          isConsec = true
          const wouldSkipAhead = matches[searchI] > targetI
          if(wouldSkipAhead) {
            const nextMatchIsNextTarget = matches[searchI] === targetI
            if(!nextMatchIsNextTarget) {
              // skip and backfill history
              targetI = matches[searchI]
              isConsec = false
              const upperCharCode = upperTarget.charCodeAt(targetI-1)
              wasUpper = target.charCodeAt(targetI-1) === upperCharCode
              wasChar = upperCharCode>=48&&upperCharCode<=57 || upperCharCode>=65&&upperCharCode<=90
            }
          }

          noMatchCount = 0
        } else {
          noMatchCount += 1
          if(noMatchCount >= fuzzysort.noMatchLimit) break
          isConsec = false
          targetI += 1
        }

      }

      const ret = {}

      // tally up the score & keep track of matches for highlighting later
      ret.score = 0
      var lastTargetI = -1
      const theMatches = strictSuccess ? strictMatches : matches
      for(const targetI of theMatches) {
        // score only goes up if they not consecutive
        if(lastTargetI !== targetI - 1) ret.score += targetI

        lastTargetI = targetI
      }
      if(strictSuccess) ret.score /= 1000
      ret.score += targetLength/1000 - searchLength/1000

      if(fuzzysort.highlightMatches) {
        ret.target = target
        ret.theMatches = strictSuccess ? strictMatches : matches
      }

      return ret
    },

    highlight: (result) => {
      var highlighted = ''
      var matchesIndex = 0
      var opened = false
      const target = result.target
      const targetLength = target.length
      const theMatches = result.theMatches
      for(var i=0; i<targetLength; i++) {
        if(theMatches[matchesIndex] === i) {
          matchesIndex += 1
          if(!opened) {
            highlighted += fuzzysort.highlightOpen
            opened = true
          }

          if(matchesIndex === theMatches.length) {
            highlighted += `${target[i]}${fuzzysort.highlightClose}${target.substr(i+1)}`
            break
          }
        } else {
          if(opened) {
            highlighted += fuzzysort.highlightClose
            opened = false
          }
        }
        highlighted += target[i]
      }

      return highlighted
    }
  }

  function quickSortPartition(results, left, right) {
    const cmp = results[right-1].score
    var minEnd = left
    var maxEnd = left
    for (; maxEnd < right-1; maxEnd += 1) {
      if (results[maxEnd].score <= cmp) {
        swap(results, maxEnd, minEnd)
        minEnd += 1
      }
    }
    swap(results, minEnd, right-1)
    return minEnd
  }

  function swap(results, i, j) {
    const temp = results[i]
    results[i] = results[j]
    results[j] = temp
  }

  function quickSortResults(results, left, right) {
    if (left < right) {
      var p = quickSortPartition(results, left, right)
      quickSortResults(results, left, p)
      quickSortResults(results, p + 1, right)
    }
  }

  // Export fuzzysort
    if(typeof module !== 'undefined' && module.exports) {
      module.exports = fuzzysort
    } else if(typeof window !== 'undefined') {
      window.fuzzysort = fuzzysort
    }
})()

<|endoftext|>


<commit_before>'use strict'

function swapGameGuide (gameSelector, selector) {
  let game = document.querySelector(gameSelector)
  game.querySelectorAll('.game-guide').forEach(function (element) {
    element.classList.add('hidden')
  })

  if (selector) {
    game.querySelector(selector).classList.remove('hidden')
  }
}

function swapGameBackground (gameSelector, background) {
  document.querySelector(gameSelector).dataset.background = background
}
<commit_msg>Use Array.prototype.forEach for compatibility<commit_after>'use strict'

function swapGameGuide (gameSelector, selector) {
  let game = document.querySelector(gameSelector)
  let guides = game.querySelectorAll('.game-guide')
  Array.prototype.forEach.call(guides, function (element) {
    element.classList.add('hidden')
  })

  if (selector) {
    game.querySelector(selector).classList.remove('hidden')
  }
}

function swapGameBackground (gameSelector, background) {
  document.querySelector(gameSelector).dataset.background = background
}
<|endoftext|>


<commit_before>/**
 * Game main file
 */

// Debug configuration
var debug = {
    enable: true,
    contexts: {
        'rune': true,
        'spell': true,
        'life': true
    }
};

// Configurable variable
var config = {
  'wrongSpellLifeCost': 5
};

// Init all game global vars
var game;
var waves = {
    'aggresiveWavesLeftPlayer': undefined,
    'defensiveWavesLeftPlayer': undefined,
    'aggresiveWavesRightPlayer': undefined,
    'defensiveWavesRightPlayer': undefined
};
var players = {
    'player1': { 'name': 'player1', 'pad': undefined, 'defenseSpell': [], 'attackSpell': [], life: 50, 'enemyName': 'player2' },
    'player2': { 'name': 'player2', 'pad': undefined, 'defenseSpell': [], 'attackSpell': [], life: 50, 'enemyName': 'player1' }
};
var player1Hitbox;
var player2Hitbox;
var wavemehamehaLeftPlayer;
var wavemehamehaRightPlayer;

(function($) {$(document).ready(function() {

    // Init Phaser game
    game = new Phaser.Game($('#game').width(), $('#game').height(), Phaser.AUTO, 'game', {
        preload: preload,
        create: create,
        update: update
    }, true);

    /**
     * Preload all assets
     */
    function preload() {

        // Load waveméhaméha assets
        game.load.image('wavemehamehaBeamLeftPlayer', 'assets/images/wavemehamehaBeamLeftPlayer.png');
        game.load.image('wavemehamehaBeamRightPlayer', 'assets/images/wavemehamehaBeamRightPlayer.png');

        // Load attack and defense lines assets
        game.load.image('attackLine', 'assets/images/attackLine.png');
        game.load.image('defenseLine', 'assets/images/defenseLine.png');

    }

    /**
     * Create basic game
     */
    function create() {

        // Start game physics mode
        game.physics.startSystem(Phaser.Physics.ARCADE);

        // Init waves groups properties
        for(var k in waves) {
            waves[k] =  game.add.group();
            waves[k].enableBody = true;
            waves[k].physicsBodyType = Phaser.Physics.ARCADE;
        }

        // Players hitbox init
        // TODO

        // Waveméhaméha init !
        wavemehamehaLeftPlayer = game.add.tileSprite(0, heightPercent(60), widthPercent(50), 170, 'wavemehamehaBeamLeftPlayer');
        wavemehamehaRightPlayer = game.add.tileSprite(widthPercent(50), heightPercent(60), widthPercent(50), 170, 'wavemehamehaBeamRightPlayer');

        // Attack & Defense lines between players init
        attackLineLeftPlayer = game.add.tileSprite(0, heightPercent(15), widthPercent(50), 20, 'attackLine');
        defenseLineLeftPlayer = game.add.tileSprite(0, heightPercent(35), widthPercent(50), 100, 'defenseLine');
        attackLineRightPlayer = game.add.tileSprite(widthPercent(50), heightPercent(35), widthPercent(50), 20, 'attackLine');
        defenseLineRightPlayer = game.add.tileSprite(widthPercent(50), heightPercent(15), widthPercent(50), 100, 'defenseLine');
        defenseLineLeftPlayer.tileScale.y = 0.5;
        defenseLineRightPlayer.tileScale.y = 0.5;
        defenseLineLeftPlayer.tileScale.x = 0.2;
        defenseLineRightPlayer.tileScale.x = 0.2;

        // Declare pads inputs listener
        game.input.gamepad.start();
        var playerId = 1;
        for(var k in players) {
            players[k].pad = game.input.gamepad['pad' + playerId];
            players[k].pad.name = k;
            players[k].pad.onDownCallback = onDownCallback;
            players[k].pad.onAxisCallback = onAxisCallback;
            playerId +=1 ;
        }

        // Verify if gamepad are connected until they are !
        setInterval(function(){ verifyIfGamepadAreConnected(); }, 200);
    }

    /**
     * Handle buttons usage for players
     * @param buttonCode
     * @param value
     */
    function onDownCallback(buttonCode, value) {
        switch (buttonCode) {

            // Add rune in players spell (defense & attack)
            case Phaser.Gamepad.XBOX360_DPAD_UP: addRuneInSpell(this, 'defense', 'U'); break;
            case Phaser.Gamepad.XBOX360_DPAD_RIGHT: addRuneInSpell(this, 'defense', 'R'); break;
            case Phaser.Gamepad.XBOX360_DPAD_DOWN: addRuneInSpell(this, 'defense', 'D'); break;
            case Phaser.Gamepad.XBOX360_DPAD_LEFT: addRuneInSpell(this, 'defense', 'L'); break;
            case Phaser.Gamepad.XBOX360_Y: addRuneInSpell(this, 'attack', 'U'); break;
            case Phaser.Gamepad.XBOX360_B: addRuneInSpell(this, 'attack', 'R'); break;
            case Phaser.Gamepad.XBOX360_A: addRuneInSpell(this, 'attack', 'D'); break;
            case Phaser.Gamepad.XBOX360_X: addRuneInSpell(this, 'attack', 'L'); break;

            // Remove rune in players spell (defense & attack)
            case Phaser.Gamepad.XBOX360_LEFT_BUMPER: removeRuneInSpell(this, 'defense'); break;
            case Phaser.Gamepad.XBOX360_RIGHT_BUMPER: removeRuneInSpell(this, 'attack'); break;

            // Cast spell (defense & attack)
            case Phaser.Gamepad.XBOX360_LEFT_TRIGGER: castSpell(this, 'defense'); break;
            case Phaser.Gamepad.XBOX360_RIGHT_TRIGGER: castSpell(this, 'attack'); break;

            // Pause the game
            case Phaser.Gamepad.XBOX360_START: changePauseGameState(); break;

            // Do nothing for other key
            default: break;
        }
    }

    /**
     * Add rune in one of the player spell (defense or attack)
     * @param pad SinglePad
     * @param spellType string attack|defense
     * @param inputCode integer Phaser.Gamepad.BUTTON_MAPPING
     */
    function addRuneInSpell(pad, spellType, rune) {
        spellType = (spellType == 'attack') ? 'attackSpell' : 'defenseSpell';
        if(players[pad.name][spellType].length < 5) {
            players[pad.name][spellType].push(rune);
            console.gameLog(pad.name + ' cast rune for his ' + spellType + ' : ' + rune, 'rune');
        }
        else {
            console.gameLog(pad.name + ' try to cast rune but his ' + spellType + ' is full.', 'rune');
        }
    }

    /**
     * Remove rune in one of the player spell (defense or attack)
     * @param pad SinglePad
     * @param spellType string attack|defense
     */
    function removeRuneInSpell(pad, spellType) {
        spellType = (spellType == 'attack') ? 'attackSpell' : 'defenseSpell';
        players[pad.name][spellType] = [];
        console.gameLog(pad.name + ' remove all runes for his ' + spellType, 'rune');
    }

    /**
     * Cast player spell !
     */
    function castSpell(pad, spellType) {
        spellType = (spellType == 'attack') ? 'attackSpell' : 'defenseSpell';

        // Log cast spell
        console.gameLog(pad.name + ' cast spell: ' + players[pad.name][spellType].join('-'), 'spell');

        // Verify if runes mactch a real spell
        var matchRealSpell = false;
        if(matchRealSpell) {
            // TODO: Make the spell alive !
        }
        else {
            downgradePlayerLife(pad.name, config.wrongSpellLifeCost);
        }

        // Consume the runes of the spell
        players[pad.name][spellType] = [];
    }

    /**
     * Downgrade a player life from a number of point (Player life start to 50 & go to 0 (lose) to 100 (win))
     * @param playerName string
     * @param lifeToLose integer
     */
    function downgradePlayerLife(playerName, lifeToLose) {

        // Update life score of player
        var player = players[playerName];
        var enemy = players[player.enemyName];
        player.life -= lifeToLose;
        enemy.life += lifeToLose;

        // Log life score update
        console.gameLog('Players life updated:', 'life');
        console.gameLog(Array(parseInt(players['player1'].life / 5) + 1).join('=') + '> | <' + Array(parseInt(players['player2'].life / 5) + 1).join('='), 'life');
        console.gameLog('Player 1 : ' + players['player1'].life + ' | Player 2 : ' + players['player2'].life, 'life');

        // Change beam display
        // TODO

        // Handle win/lose case !
        if(player.life <= 0) {

            // Display the win screen
            $('#win_screen').addClass(enemy.name + 'Win').fadeIn();

        }

    }

    /**
     * Allow to pause the game or to resume it
     */
    function changePauseGameState() {
        // Pause the game !
        if(!game.paused) {
            game.paused = true;
            $('#pause_screen').fadeIn();

            // Start to listen for unpause using browser api (not phaser) because the game is paused
            // and do not catch for keydown anymore
            setTimeout(function(){
                stopPauseScreen = setInterval(function(){
                    for(var i = 0; i <= 1; i++) {
                        // If one of the gamepad press start we unpaused the game
                        if(navigator.getGamepads()[i].buttons[9].pressed) {
                            clearInterval(stopPauseScreen);
                            changePauseGameState();
                        }
                    }
                }, 10);
            }, 1000);

        }
        // Resume the game
        else {
            $('#pause_screen').fadeOut(function(){
                game.paused = false;
            });
        }
    }
    var stopPauseScreen = undefined;

    /**
     * Handle sticks usage for players
     * @param pad
     * @param value
     */
    function onAxisCallback(pad, value) {
        // TODO
        //console.log(this.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_X)); return;
    }

    /**
     * Update loop to handle movements & collisions
     */
    function update () {

        // Verify colission between waves
        // TODO
        //game.physics.arcade.collide(ball, paddle, ballHitPaddle, null, this);
        //game.physics.arcade.collide(ball, bricks, ballHitBrick, null, this);

        // Verify colission between waves and players hitbox

    }

    /**
     * Verify if gamepad are connected until they are !
     */
    function verifyIfGamepadAreConnected() {
        var errorsInputs = 0;

        // Gamepad is supported ?
        if(!game.input.gamepad.supported || !game.input.gamepad.active) {
            $('#pregame_screen').addClass('notSupportedGamePad');
            errorsInputs += 1;
        }

        // Players gamepads are connected ?
        for(var playerName in players) {
            if(!players[playerName].pad.connected) {
                $('#pregame_screen').addClass('missingPad' + playerName.capitalizeFirstLetter());
                errorsInputs += 1;
            }
            else {
                $('#pregame_screen').removeClass('missingPad' + playerName.capitalizeFirstLetter());
            }
        }

        // Display pregame screen with some class to tweak what is display if there is at least an error
        if(errorsInputs > 0 && !$('#pregame_screen').is(':visible')) {
            $('#pregame_screen').fadeIn();
        }
    }

    /**
     * Launch win screen
     */
    function launchWinScreen () {

        // TODO
        //ball.body.velocity.setTo(0, 0);
        //introText.text = 'Game Over!';
        //introText.visible = true;

    }

    /**
     * Handle collission between waves and waves
     * @param _wave1
     * @param _wave2
     */
    function wavesCollision(_wave1, _wave2) {

        // TODO
        /*
        _brick.kill();

        score += 10;

        scoreText.text = 'score: ' + score;

        //  Are they any bricks left?
        if (bricks.countLiving() == 0)
        {
            //  New level starts
            score += 1000;
            scoreText.text = 'score: ' + score;
            introText.text = '- Next Level -';

            //  Let's move the ball back to the paddle
            ballOnPaddle = true;
            ball.body.velocity.set(0);
            ball.x = paddle.x + 16;
            ball.y = paddle.y - 16;
            ball.animations.stop();

            //  And bring the bricks back from the dead :)
            bricks.callAll('revive');
        }
        */

    }

    /**
     * Get % of height of the game size
     * @param percent
     */
    function heightPercent(percent) {
        return $('#game').height() * percent / 100;
    }

    /**
     * Get % of width of the game size
     * @param percent
     */
    function widthPercent(percent) {
        return $('#game').width() * percent / 100;
    }

});})(jQuery);

/**
 * Util function to capitalize first letter in a string
 * @returns {string}
 */
String.prototype.capitalizeFirstLetter = function() {
    return this.charAt(0).toUpperCase() + this.slice(1);
};

/**
 * Game log function which only log if debug is enable and if context is enable
 * @param data data to log
 * @param context context of the data
 */
console.gameLog = function(data, context) {
    if(debug.enable == true && typeof debug.contexts[context] != 'undefined' && debug.contexts[context] == true) {
        console.log(data);
    }
};<commit_msg>GameController - handle stick !<commit_after>/**
 * Game main file
 */

// Debug configuration
var debug = {
    enable: true,
    contexts: {
        'rune': true,
        'spell': true,
        'life': true
    }
};

// Configurable variable
var config = {
  'wrongSpellLifeCost': 5
};

// Init all game global vars
var game;
var waves = {
    'aggresiveWavesLeftPlayer': undefined,
    'defensiveWavesLeftPlayer': undefined,
    'aggresiveWavesRightPlayer': undefined,
    'defensiveWavesRightPlayer': undefined
};
var players = {
    'player1': { 'name': 'player1', 'pad': undefined, 'defenseSpell': [], 'attackSpell': [], life: 50, 'enemyName': 'player2' },
    'player2': { 'name': 'player2', 'pad': undefined, 'defenseSpell': [], 'attackSpell': [], life: 50, 'enemyName': 'player1' }
};
var player1Hitbox;
var player2Hitbox;
var wavemehamehaLeftPlayer;
var wavemehamehaRightPlayer;

(function($) {$(document).ready(function() {

    // Init Phaser game
    game = new Phaser.Game($('#game').width(), $('#game').height(), Phaser.AUTO, 'game', {
        preload: preload,
        create: create,
        update: update
    }, true);

    /**
     * Preload all assets
     */
    function preload() {

        // Load waveméhaméha assets
        game.load.image('wavemehamehaBeamLeftPlayer', 'assets/images/wavemehamehaBeamLeftPlayer.png');
        game.load.image('wavemehamehaBeamRightPlayer', 'assets/images/wavemehamehaBeamRightPlayer.png');

        // Load attack and defense lines assets
        game.load.image('attackLine', 'assets/images/attackLine.png');
        game.load.image('defenseLine', 'assets/images/defenseLine.png');

    }

    /**
     * Create basic game
     */
    function create() {

        // Start game physics mode
        game.physics.startSystem(Phaser.Physics.ARCADE);

        // Init waves groups properties
        for(var k in waves) {
            waves[k] =  game.add.group();
            waves[k].enableBody = true;
            waves[k].physicsBodyType = Phaser.Physics.ARCADE;
        }

        // Players hitbox init
        // TODO

        // Waveméhaméha init !
        wavemehamehaLeftPlayer = game.add.tileSprite(0, heightPercent(60), widthPercent(50), 170, 'wavemehamehaBeamLeftPlayer');
        wavemehamehaRightPlayer = game.add.tileSprite(widthPercent(50), heightPercent(60), widthPercent(50), 170, 'wavemehamehaBeamRightPlayer');

        // Attack & Defense lines between players init
        attackLineLeftPlayer = game.add.tileSprite(0, heightPercent(15), widthPercent(50), 20, 'attackLine');
        defenseLineLeftPlayer = game.add.tileSprite(0, heightPercent(35), widthPercent(50), 100, 'defenseLine');
        attackLineRightPlayer = game.add.tileSprite(widthPercent(50), heightPercent(35), widthPercent(50), 20, 'attackLine');
        defenseLineRightPlayer = game.add.tileSprite(widthPercent(50), heightPercent(15), widthPercent(50), 100, 'defenseLine');
        defenseLineLeftPlayer.tileScale.y = 0.5;
        defenseLineRightPlayer.tileScale.y = 0.5;
        defenseLineLeftPlayer.tileScale.x = 0.2;
        defenseLineRightPlayer.tileScale.x = 0.2;

        // Declare pads inputs listener
        game.input.gamepad.start();
        var playerId = 1;
        for(var k in players) {
            players[k].pad = game.input.gamepad['pad' + playerId];
            players[k].pad.name = k;
            players[k].pad.onDownCallback = onDownCallback;
            players[k].pad.onAxisCallback = onAxisCallback;
            playerId +=1 ;
        }

        // Verify if gamepad are connected until they are !
        setInterval(function(){ verifyIfGamepadAreConnected(); }, 200);
    }

    /**
     * Handle buttons usage for players
     * @param buttonCode
     * @param value
     */
    function onDownCallback(buttonCode, value) {
        switch (buttonCode) {

            // Add rune in players spell (defense & attack)
            case Phaser.Gamepad.XBOX360_DPAD_UP: addRuneInSpell(this, 'defense', 'U'); break;
            case Phaser.Gamepad.XBOX360_DPAD_RIGHT: addRuneInSpell(this, 'defense', 'R'); break;
            case Phaser.Gamepad.XBOX360_DPAD_DOWN: addRuneInSpell(this, 'defense', 'D'); break;
            case Phaser.Gamepad.XBOX360_DPAD_LEFT: addRuneInSpell(this, 'defense', 'L'); break;
            case Phaser.Gamepad.XBOX360_Y: addRuneInSpell(this, 'attack', 'U'); break;
            case Phaser.Gamepad.XBOX360_B: addRuneInSpell(this, 'attack', 'R'); break;
            case Phaser.Gamepad.XBOX360_A: addRuneInSpell(this, 'attack', 'D'); break;
            case Phaser.Gamepad.XBOX360_X: addRuneInSpell(this, 'attack', 'L'); break;

            // Remove rune in players spell (defense & attack)
            case Phaser.Gamepad.XBOX360_LEFT_BUMPER: removeRuneInSpell(this, 'defense'); break;
            case Phaser.Gamepad.XBOX360_RIGHT_BUMPER: removeRuneInSpell(this, 'attack'); break;

            // Cast spell (defense & attack)
            case Phaser.Gamepad.XBOX360_LEFT_TRIGGER: castSpell(this, 'defense'); break;
            case Phaser.Gamepad.XBOX360_RIGHT_TRIGGER: castSpell(this, 'attack'); break;

            // Pause the game
            case Phaser.Gamepad.XBOX360_START: changePauseGameState(); break;

            // Do nothing for other key
            default: break;
        }
    }

    /**
     * Handle sticks usage for players
     * @param pad
     * @param value
     */
    function onAxisCallback(pad, value) {

        // Verify if left stick is triggering a rune
        if(canUseAxisLeft && pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_X) <= -0.8 && pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_X) >= -1) {
            addRuneInSpell(pad, 'defense', 'L');
            canUseAxisLeft = false;
        }
        else if(canUseAxisLeft && pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_X) >= 0.8 && pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_X) <= 1) {
            addRuneInSpell(pad, 'defense', 'R');
            canUseAxisLeft = false;
        }
        if(canUseAxisLeft && pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_Y) <= -0.8 && pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_Y) >= -1) {
            addRuneInSpell(pad, 'defense', 'U');
            canUseAxisLeft = false;
        }
        else if(canUseAxisLeft && pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_Y) >= 0.8 && pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_Y) <= 1) {
            addRuneInSpell(pad, 'defense', 'D');
            canUseAxisLeft = false;
        }

        // Return on center before accepting another direction
        if(pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_X) === false && pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_Y) === false) {
            canUseAxisLeft = true;
        }

        // Verify if right stick is triggering a rune
        if(canUseAxisRight && pad.axis(Phaser.Gamepad.XBOX360_STICK_RIGHT_X) <= -0.8 && pad.axis(Phaser.Gamepad.XBOX360_STICK_RIGHT_X) >= -1) {
            addRuneInSpell(pad, 'attack', 'L');
            canUseAxisRight = false;
        }
        else if(canUseAxisRight && pad.axis(Phaser.Gamepad.XBOX360_STICK_RIGHT_X) >= 0.8 && pad.axis(Phaser.Gamepad.XBOX360_STICK_RIGHT_X) <= 1) {
            addRuneInSpell(pad, 'attack', 'R');
            canUseAxisRight = false;
        }
        if(canUseAxisRight && pad.axis(Phaser.Gamepad.XBOX360_STICK_RIGHT_Y) <= -0.8 && pad.axis(Phaser.Gamepad.XBOX360_STICK_RIGHT_Y) >= -1) {
            addRuneInSpell(pad, 'attack', 'U');
            canUseAxisRight = false;
        }
        else if(canUseAxisRight && pad.axis(Phaser.Gamepad.XBOX360_STICK_RIGHT_Y) >= 0.8 && pad.axis(Phaser.Gamepad.XBOX360_STICK_RIGHT_Y) <= 1) {
            addRuneInSpell(pad, 'attack', 'D');
            canUseAxisRight = false;
        }

        // Return on center before accepting another direction
        if(pad.axis(Phaser.Gamepad.XBOX360_STICK_RIGHT_X) === false && pad.axis(Phaser.Gamepad.XBOX360_STICK_RIGHT_Y) === false) {
            canUseAxisRight = true;
        }

    }
    var canUseAxisLeft = true;
    var canUseAxisRight = true;

    /**
     * Add rune in one of the player spell (defense or attack)
     * @param pad SinglePad
     * @param spellType string attack|defense
     * @param inputCode integer Phaser.Gamepad.BUTTON_MAPPING
     */
    function addRuneInSpell(pad, spellType, rune) {
        spellType = (spellType == 'attack') ? 'attackSpell' : 'defenseSpell';
        if(players[pad.name][spellType].length < 5) {
            players[pad.name][spellType].push(rune);
            console.gameLog(pad.name + ' cast rune for his ' + spellType + ' : ' + rune, 'rune');
        }
        else {
            console.gameLog(pad.name + ' try to cast rune but his ' + spellType + ' is full.', 'rune');
        }
    }

    /**
     * Remove rune in one of the player spell (defense or attack)
     * @param pad SinglePad
     * @param spellType string attack|defense
     */
    function removeRuneInSpell(pad, spellType) {
        spellType = (spellType == 'attack') ? 'attackSpell' : 'defenseSpell';
        players[pad.name][spellType] = [];
        console.gameLog(pad.name + ' remove all runes for his ' + spellType, 'rune');
    }

    /**
     * Cast player spell !
     */
    function castSpell(pad, spellType) {
        spellType = (spellType == 'attack') ? 'attackSpell' : 'defenseSpell';

        // Log cast spell
        console.gameLog(pad.name + ' cast spell: ' + players[pad.name][spellType].join('-'), 'spell');

        // Verify if runes mactch a real spell
        var matchRealSpell = false;
        if(matchRealSpell) {
            // TODO: Make the spell alive !
        }
        else {
            downgradePlayerLife(pad.name, config.wrongSpellLifeCost);
        }

        // Consume the runes of the spell
        players[pad.name][spellType] = [];
    }

    /**
     * Downgrade a player life from a number of point (Player life start to 50 & go to 0 (lose) to 100 (win))
     * @param playerName string
     * @param lifeToLose integer
     */
    function downgradePlayerLife(playerName, lifeToLose) {

        // Update life score of player
        var player = players[playerName];
        var enemy = players[player.enemyName];
        player.life -= lifeToLose;
        enemy.life += lifeToLose;

        // Log life score update
        console.gameLog('Players life updated:', 'life');
        console.gameLog(Array(parseInt(players['player1'].life / 5) + 1).join('=') + '> | <' + Array(parseInt(players['player2'].life / 5) + 1).join('='), 'life');
        console.gameLog('Player 1 : ' + players['player1'].life + ' | Player 2 : ' + players['player2'].life, 'life');

        // Change beam display
        // TODO

        // Handle win/lose case !
        if(player.life <= 0) {

            // Display the win screen
            $('#win_screen').addClass(enemy.name + 'Win').fadeIn();

        }

    }

    /**
     * Allow to pause the game or to resume it
     */
    function changePauseGameState() {
        // Pause the game !
        if(!game.paused) {
            game.paused = true;
            $('#pause_screen').fadeIn();

            // Start to listen for unpause using browser api (not phaser) because the game is paused
            // and do not catch for keydown anymore
            setTimeout(function(){
                stopPauseScreen = setInterval(function(){
                    for(var i = 0; i <= 1; i++) {
                        // If one of the gamepad press start we unpaused the game
                        if(navigator.getGamepads()[i].buttons[9].pressed) {
                            clearInterval(stopPauseScreen);
                            changePauseGameState();
                        }
                    }
                }, 10);
            }, 1000);

        }
        // Resume the game
        else {
            $('#pause_screen').fadeOut(function(){
                game.paused = false;
            });
        }
    }
    var stopPauseScreen = undefined;

    /**
     * Update loop to handle movements & collisions
     */
    function update () {

        // Verify colission between waves
        // TODO
        //game.physics.arcade.collide(ball, paddle, ballHitPaddle, null, this);
        //game.physics.arcade.collide(ball, bricks, ballHitBrick, null, this);

        // Verify colission between waves and players hitbox

    }

    /**
     * Verify if gamepad are connected until they are !
     */
    function verifyIfGamepadAreConnected() {
        var errorsInputs = 0;

        // Gamepad is supported ?
        if(!game.input.gamepad.supported || !game.input.gamepad.active) {
            $('#pregame_screen').addClass('notSupportedGamePad');
            errorsInputs += 1;
        }

        // Players gamepads are connected ?
        for(var playerName in players) {
            if(!players[playerName].pad.connected) {
                $('#pregame_screen').addClass('missingPad' + playerName.capitalizeFirstLetter());
                errorsInputs += 1;
            }
            else {
                $('#pregame_screen').removeClass('missingPad' + playerName.capitalizeFirstLetter());
            }
        }

        // Display pregame screen with some class to tweak what is display if there is at least an error
        if(errorsInputs > 0 && !$('#pregame_screen').is(':visible')) {
            $('#pregame_screen').fadeIn();
        }
    }

    /**
     * Launch win screen
     */
    function launchWinScreen () {

        // TODO
        //ball.body.velocity.setTo(0, 0);
        //introText.text = 'Game Over!';
        //introText.visible = true;

    }

    /**
     * Handle collission between waves and waves
     * @param _wave1
     * @param _wave2
     */
    function wavesCollision(_wave1, _wave2) {

        // TODO
        /*
        _brick.kill();

        score += 10;

        scoreText.text = 'score: ' + score;

        //  Are they any bricks left?
        if (bricks.countLiving() == 0)
        {
            //  New level starts
            score += 1000;
            scoreText.text = 'score: ' + score;
            introText.text = '- Next Level -';

            //  Let's move the ball back to the paddle
            ballOnPaddle = true;
            ball.body.velocity.set(0);
            ball.x = paddle.x + 16;
            ball.y = paddle.y - 16;
            ball.animations.stop();

            //  And bring the bricks back from the dead :)
            bricks.callAll('revive');
        }
        */

    }

    /**
     * Get % of height of the game size
     * @param percent
     */
    function heightPercent(percent) {
        return $('#game').height() * percent / 100;
    }

    /**
     * Get % of width of the game size
     * @param percent
     */
    function widthPercent(percent) {
        return $('#game').width() * percent / 100;
    }

});})(jQuery);

/**
 * Util function to capitalize first letter in a string
 * @returns {string}
 */
String.prototype.capitalizeFirstLetter = function() {
    return this.charAt(0).toUpperCase() + this.slice(1);
};

/**
 * Game log function which only log if debug is enable and if context is enable
 * @param data data to log
 * @param context context of the data
 */
console.gameLog = function(data, context) {
    if(debug.enable == true && typeof debug.contexts[context] != 'undefined' && debug.contexts[context] == true) {
        console.log(data);
    }
};<|endoftext|>