<commit_before>"use strict";

/**
 * Mapper that automatically maps keyboard / gamepad input to different player numbers.
 * This can be used to implement keyboard / gamepad controls for a single player or a local
 * multiplayer game that allows players on the keyboard to play against players on gamepads.
 * Requires gamepad.js, Mousetrap and MouseTrap-global-bind to be included.
 * @param {Object} callbackObj Object on which the callback functions will be called.
 * @param {number} maxPlayers Maximum number of players. If there are more active controllers
 * than this, then two controllers may be mapped to the same player.
 */
var InputMapper = function(callbackObj, maxPlayers) {
    this.gamepads = new Gamepads(this);
    this.callbackObj = callbackObj;
    this.maxPlayers = maxPlayers;
    this.resetPlayerMap();
    this.keysDown = []; // Keyboard keys that are currently down
    this.callbacks = []; // Callback information for mapping callbacks back to buttons
};

// Controller types
InputMapper.GAMEPAD = 0;
InputMapper.KEYBOARD = 1;

/**
 * Helper class to store the controller config each player has.
 * @param {number} controllerType Controller type: either InputMapper.GAMEPAD or InputMapper.KEYBOARD
 * @param {number} controllerIndex Controller index: in case of keyboard, index into the array of keyboard keys given
 * to addListener. In case of gamepad, index of the gamepad.
 */
InputMapper.Controller = function(controllerType, controllerIndex) {
    this.controllerType = controllerType;
    this.controllerIndex = controllerIndex;
    this.lastUsed = 0; // A timestamp for when this controller was last used
};

/**
 * Reset the map between controllers and player numbers.
 */
InputMapper.prototype.resetPlayerMap = function() {
    this.players = []; // An array of arrays of controllers. Each player can have multiple controllers.
    for (var i = 0; i < this.maxPlayers; ++i) {
        this.players.push([]);
    }
};

/**
 * Update the controller state and call listeners based on that.
 */
InputMapper.prototype.update = function() {
    this.gamepads.update();
};

/**
 * Return a player index for a player using a given controller.
 * @param {number} controllerType Controller type: either InputMapper.GAMEPAD or InputMapper.KEYBOARD
 * @param {number} controllerIndex Controller index: in case of keyboard, index into the array of keyboard keys given
 * to addListener. In case of gamepad, index of the gamepad.
 */
InputMapper.prototype.getPlayerIndex = function(controllerType, controllerIndex) {
    for (var i = 0; i < this.players.length; ++i) {
        var player = this.players[i];
        for (var j = 0; j < player.length; ++j) {
            if (player[j].controllerType == controllerType && player[j].controllerIndex == controllerIndex) {
                player[j].lastUsed = Date.now();
                return i;
            }
        }
    }
    var controller = new InputMapper.Controller(controllerType, controllerIndex);
    controller.lastUsed = Date.now();
    // Map the controller for a player without a controller if there is one
    for (var i = 0; i < this.players.length; ++i) {
        var player = this.players[i];
        if (player.length === 0) {
            player.push(controller);
            return i;
        }
    }
    // Map the controller for the first player without this type of a controller
    for (var i = 0; i < this.players.length; ++i) {
        var player = this.players[i];
        var hasSameTypeController = false;
        for (var j = 0; j < player.length; ++j) {
            if (player[j].controllerType == controllerType) {
                hasSameTypeController = true;
            }
        }
        if (!hasSameTypeController) {
            player.push(controller);
            return i;
        }
    }
    // Just map the controller for the first player
    this.players[0].push(controller);
    return 0;
};

/**
 * @param {number} gamepadButton A button from Gamepads.BUTTONS
 * @param {Array} keyboardBindings List of bindings for different players, for example ['up', 'w']
 * @param {function} downCallback Callback when the button is pressed down, that takes a player number as a parameter.
 * @param {function} upCallback Callback when the button is released, that takes a player number as a parameter.
 */
InputMapper.prototype.addListener = function(gamepadButton, keyboardButtons, downCallback, upCallback) {
    var gamepadDownCallback = function(gamepadNumber) {
        var player = this.getPlayerIndex(InputMapper.GAMEPAD, gamepadNumber);
        if (downCallback !== undefined) {
            downCallback.call(this.callbackObj, player);
        }
    };
    var gamepadUpCallback = function(gamepadNumber) {
        var player = this.getPlayerIndex(InputMapper.GAMEPAD, gamepadNumber);
        if (upCallback !== undefined) {
            upCallback.call(this.callbackObj, player);
        }
    };
    this.gamepads.addButtonChangeListener(gamepadButton, gamepadDownCallback, gamepadUpCallback);

    if (downCallback !== undefined) {
        this.callbacks.push({key: Gamepads.BUTTON_INSTRUCTION[gamepadButton], callback: downCallback, controllerType: InputMapper.GAMEPAD});
    }
    if (upCallback !== undefined) {
        this.callbacks.push({key: Gamepads.BUTTON_INSTRUCTION[gamepadButton], callback: upCallback, controllerType: InputMapper.GAMEPAD});
    }

    var that = this;
    for (var i = 0; i < keyboardButtons.length; ++i) {
        (function(kbIndex) {
            that.keysDown[keyboardButtons[kbIndex]] = false;
            // TODO: down events get generated multiple times while a key is down. Work around this...
            var keyDownCallback = function() {
                var player = that.getPlayerIndex(InputMapper.KEYBOARD, kbIndex);
                if (!that.keysDown[keyboardButtons[kbIndex]]) {
                    that.keysDown[keyboardButtons[kbIndex]] = true;
                    if (downCallback !== undefined) {
                        downCallback.call(that.callbackObj, player);
                    }
                }
            };
            var keyUpCallback = function() {
                var player = that.getPlayerIndex(InputMapper.KEYBOARD, kbIndex);
                that.keysDown[keyboardButtons[kbIndex]] = false;
                if (upCallback !== undefined) {
                    upCallback.call(that.callbackObj, player);
                }
            };
            Mousetrap.bindGlobal(keyboardButtons[kbIndex], keyDownCallback, 'keydown');
            Mousetrap.bindGlobal(keyboardButtons[kbIndex], keyUpCallback, 'keyup');
        })(i);
        if (downCallback !== undefined) {
            this.callbacks.push({key: keyboardButtons[i], callback: downCallback, controllerType: InputMapper.KEYBOARD, kbIndex: i});
        }
        if (upCallback !== undefined) {
            this.callbacks.push({key: keyboardButtons[i], callback: upCallback, controllerType: InputMapper.KEYBOARD, kbIndex: i});
        }
    }
};

/**
 * Check if a given callback uses a given type of controller. Doesn't care about gamepad indices.
 * @protected
 * @param {InputMapper.Controller} controller
 * @param {Object} cbInfo Information on the callback, with keys controllerType and kbIndex in case of a keyboard.
 * @return {boolean} True if the given callback uses the given type of a controller.
 */
InputMapper.usesController = function(controller, cbInfo) {
    if (cbInfo.controllerType === controller.controllerType) {
        if (cbInfo.controllerType === InputMapper.KEYBOARD && controller.controllerIndex !== cbInfo.kbIndex) {
            // Each keyboard "controller" has different key bindings.
            return false;
        }
        return true;
    }
};

/**
 * From an array of controllers, determine the one that was most recently used.
 * @protected
 * @param {Array.<InputMapper.Controller>} player Array of controllers to check.
 * @return {InputMapper.Controller} The most recently used controller.
 */
InputMapper.prototype.getLastUsedController = function(player) {
    var controller;
    var lastUsed = 0;
    for (var j = 0; j < player.length; ++j) {
        if (player[j].lastUsed > lastUsed) {
            controller = player[j];
            lastUsed = player[j].lastUsed;
        }
    }
    return controller;
};

/**
 * Get instruction for a key. Prioritizes gamepad over keyboard if keyboard hasn't been used.
 * @param {function} callback A callback that has been previously attached to a button.
 * @param {playerIndex} index of the player to return information for. Set to undefined if the listener doesn't care
 * about the player number.
 * @return {string} String identifying the button for the player.
 */
InputMapper.prototype.getKeyInstruction = function(callback, playerIndex) {
    var controller;
    if (playerIndex !== undefined) {
        if (this.players[playerIndex].length > 0) {
            controller = this.getLastUsedController(this.players[playerIndex]);
        } else {
            // Gamepad instructions by default
            controller = new InputMapper.Controller(InputMapper.GAMEPAD, 0);
        }
    }
    var returnStr = [];
    for (var i = 0; i < this.callbacks.length; ++i) {
        var cbInfo = this.callbacks[i];
        if (cbInfo.callback === callback) {
            if (controller === undefined) {
                // Listener doesn't care about the player number.
                // Determine all keys mapped to that callback from different controllers.
                for (var j = 0; j < this.players.length; ++j) {
                    for (var k = 0; k < this.players[j].length; ++k) {
                        if (InputMapper.usesController(this.players[j][k], cbInfo)) {
                            var hasInstruction = false;
                            var instruction = cbInfo.key.toUpperCase();
                            for (var l = 0; l < returnStr.length; ++l) {
                                if (returnStr[l] == instruction) {
                                    hasInstruction = true;
                                }
                            }
                            if (!hasInstruction) {
                                returnStr.push(instruction);
                            }
                        }
                    }
                }
            } else {
                if (InputMapper.usesController(controller, cbInfo)) {
                    return cbInfo.key.toUpperCase();
                }
            }
        }
    }
    if (controller === undefined) {
        return returnStr.join('/');
    }
    return '';
};
<commit_msg>Fix gamepad getKeyInstruction for combined analog/dpad input<commit_after>"use strict";

/**
 * Mapper that automatically maps keyboard / gamepad input to different player numbers.
 * This can be used to implement keyboard / gamepad controls for a single player or a local
 * multiplayer game that allows players on the keyboard to play against players on gamepads.
 * Requires gamepad.js, Mousetrap and MouseTrap-global-bind to be included.
 * @param {Object} callbackObj Object on which the callback functions will be called.
 * @param {number} maxPlayers Maximum number of players. If there are more active controllers
 * than this, then two controllers may be mapped to the same player.
 */
var InputMapper = function(callbackObj, maxPlayers) {
    this.gamepads = new Gamepads(this);
    this.callbackObj = callbackObj;
    this.maxPlayers = maxPlayers;
    this.resetPlayerMap();
    this.keysDown = []; // Keyboard keys that are currently down
    this.callbacks = []; // Callback information for mapping callbacks back to buttons
};

// Controller types
InputMapper.GAMEPAD = 0;
InputMapper.KEYBOARD = 1;

/**
 * Helper class to store the controller config each player has.
 * @param {number} controllerType Controller type: either InputMapper.GAMEPAD or InputMapper.KEYBOARD
 * @param {number} controllerIndex Controller index: in case of keyboard, index into the array of keyboard keys given
 * to addListener. In case of gamepad, index of the gamepad.
 */
InputMapper.Controller = function(controllerType, controllerIndex) {
    this.controllerType = controllerType;
    this.controllerIndex = controllerIndex;
    this.lastUsed = 0; // A timestamp for when this controller was last used
};

/**
 * Reset the map between controllers and player numbers.
 */
InputMapper.prototype.resetPlayerMap = function() {
    this.players = []; // An array of arrays of controllers. Each player can have multiple controllers.
    for (var i = 0; i < this.maxPlayers; ++i) {
        this.players.push([]);
    }
};

/**
 * Update the controller state and call listeners based on that.
 */
InputMapper.prototype.update = function() {
    this.gamepads.update();
};

/**
 * Return a player index for a player using a given controller.
 * @param {number} controllerType Controller type: either InputMapper.GAMEPAD or InputMapper.KEYBOARD
 * @param {number} controllerIndex Controller index: in case of keyboard, index into the array of keyboard keys given
 * to addListener. In case of gamepad, index of the gamepad.
 */
InputMapper.prototype.getPlayerIndex = function(controllerType, controllerIndex) {
    for (var i = 0; i < this.players.length; ++i) {
        var player = this.players[i];
        for (var j = 0; j < player.length; ++j) {
            if (player[j].controllerType == controllerType && player[j].controllerIndex == controllerIndex) {
                player[j].lastUsed = Date.now();
                return i;
            }
        }
    }
    var controller = new InputMapper.Controller(controllerType, controllerIndex);
    controller.lastUsed = Date.now();
    // Map the controller for a player without a controller if there is one
    for (var i = 0; i < this.players.length; ++i) {
        var player = this.players[i];
        if (player.length === 0) {
            player.push(controller);
            return i;
        }
    }
    // Map the controller for the first player without this type of a controller
    for (var i = 0; i < this.players.length; ++i) {
        var player = this.players[i];
        var hasSameTypeController = false;
        for (var j = 0; j < player.length; ++j) {
            if (player[j].controllerType == controllerType) {
                hasSameTypeController = true;
            }
        }
        if (!hasSameTypeController) {
            player.push(controller);
            return i;
        }
    }
    // Just map the controller for the first player
    this.players[0].push(controller);
    return 0;
};

/**
 * @param {number} gamepadButton A button from Gamepads.BUTTONS
 * @param {Array} keyboardBindings List of bindings for different players, for example ['up', 'w']
 * @param {function} downCallback Callback when the button is pressed down, that takes a player number as a parameter.
 * @param {function} upCallback Callback when the button is released, that takes a player number as a parameter.
 */
InputMapper.prototype.addListener = function(gamepadButton, keyboardButtons, downCallback, upCallback) {
    var gamepadDownCallback = function(gamepadNumber) {
        var player = this.getPlayerIndex(InputMapper.GAMEPAD, gamepadNumber);
        if (downCallback !== undefined) {
            downCallback.call(this.callbackObj, player);
        }
    };
    var gamepadUpCallback = function(gamepadNumber) {
        var player = this.getPlayerIndex(InputMapper.GAMEPAD, gamepadNumber);
        if (upCallback !== undefined) {
            upCallback.call(this.callbackObj, player);
        }
    };
    this.gamepads.addButtonChangeListener(gamepadButton, gamepadDownCallback, gamepadUpCallback);

    var gamepadInstruction;
    
    if (gamepadButton < 100) {
        gamepadInstruction = Gamepads.BUTTON_INSTRUCTION[gamepadButton];
    } else {
        gamepadInstruction = Gamepads.BUTTON_INSTRUCTION[gamepadButton - 100];
    }
    
    if (downCallback !== undefined) {
        this.callbacks.push({key: gamepadInstruction, callback: downCallback, controllerType: InputMapper.GAMEPAD});
    }
    if (upCallback !== undefined) {
        this.callbacks.push({key: gamepadInstruction, callback: upCallback, controllerType: InputMapper.GAMEPAD});
    }

    var that = this;
    for (var i = 0; i < keyboardButtons.length; ++i) {
        (function(kbIndex) {
            that.keysDown[keyboardButtons[kbIndex]] = false;
            // TODO: down events get generated multiple times while a key is down. Work around this...
            var keyDownCallback = function() {
                var player = that.getPlayerIndex(InputMapper.KEYBOARD, kbIndex);
                if (!that.keysDown[keyboardButtons[kbIndex]]) {
                    that.keysDown[keyboardButtons[kbIndex]] = true;
                    if (downCallback !== undefined) {
                        downCallback.call(that.callbackObj, player);
                    }
                }
            };
            var keyUpCallback = function() {
                var player = that.getPlayerIndex(InputMapper.KEYBOARD, kbIndex);
                that.keysDown[keyboardButtons[kbIndex]] = false;
                if (upCallback !== undefined) {
                    upCallback.call(that.callbackObj, player);
                }
            };
            Mousetrap.bindGlobal(keyboardButtons[kbIndex], keyDownCallback, 'keydown');
            Mousetrap.bindGlobal(keyboardButtons[kbIndex], keyUpCallback, 'keyup');
        })(i);
        if (downCallback !== undefined) {
            this.callbacks.push({key: keyboardButtons[i], callback: downCallback, controllerType: InputMapper.KEYBOARD, kbIndex: i});
        }
        if (upCallback !== undefined) {
            this.callbacks.push({key: keyboardButtons[i], callback: upCallback, controllerType: InputMapper.KEYBOARD, kbIndex: i});
        }
    }
};

/**
 * Check if a given callback uses a given type of controller. Doesn't care about gamepad indices.
 * @protected
 * @param {InputMapper.Controller} controller
 * @param {Object} cbInfo Information on the callback, with keys controllerType and kbIndex in case of a keyboard.
 * @return {boolean} True if the given callback uses the given type of a controller.
 */
InputMapper.usesController = function(controller, cbInfo) {
    if (cbInfo.controllerType === controller.controllerType) {
        if (cbInfo.controllerType === InputMapper.KEYBOARD && controller.controllerIndex !== cbInfo.kbIndex) {
            // Each keyboard "controller" has different key bindings.
            return false;
        }
        return true;
    }
};

/**
 * From an array of controllers, determine the one that was most recently used.
 * @protected
 * @param {Array.<InputMapper.Controller>} player Array of controllers to check.
 * @return {InputMapper.Controller} The most recently used controller.
 */
InputMapper.prototype.getLastUsedController = function(player) {
    var controller;
    var lastUsed = 0;
    for (var j = 0; j < player.length; ++j) {
        if (player[j].lastUsed > lastUsed) {
            controller = player[j];
            lastUsed = player[j].lastUsed;
        }
    }
    return controller;
};

/**
 * Get instruction for a key. Prioritizes gamepad over keyboard if keyboard hasn't been used.
 * @param {function} callback A callback that has been previously attached to a button.
 * @param {playerIndex} index of the player to return information for. Set to undefined if the listener doesn't care
 * about the player number.
 * @return {string} String identifying the button for the player.
 */
InputMapper.prototype.getKeyInstruction = function(callback, playerIndex) {
    var controller;
    if (playerIndex !== undefined) {
        if (this.players[playerIndex].length > 0) {
            controller = this.getLastUsedController(this.players[playerIndex]);
        } else {
            // Gamepad instructions by default
            controller = new InputMapper.Controller(InputMapper.GAMEPAD, 0);
        }
    }
    var returnStr = [];
    for (var i = 0; i < this.callbacks.length; ++i) {
        var cbInfo = this.callbacks[i];
        if (cbInfo.callback === callback) {
            if (controller === undefined) {
                // Listener doesn't care about the player number.
                // Determine all keys mapped to that callback from different controllers.
                for (var j = 0; j < this.players.length; ++j) {
                    for (var k = 0; k < this.players[j].length; ++k) {
                        if (InputMapper.usesController(this.players[j][k], cbInfo)) {
                            var hasInstruction = false;
                            var instruction = cbInfo.key.toUpperCase();
                            for (var l = 0; l < returnStr.length; ++l) {
                                if (returnStr[l] == instruction) {
                                    hasInstruction = true;
                                }
                            }
                            if (!hasInstruction) {
                                returnStr.push(instruction);
                            }
                        }
                    }
                }
            } else {
                if (InputMapper.usesController(controller, cbInfo)) {
                    return cbInfo.key.toUpperCase();
                }
            }
        }
    }
    if (controller === undefined) {
        return returnStr.join('/');
    }
    return '';
};

{'fraction_non_alphanumeric': 0.1000233263354327, 'fraction_numerical': 0.002239328201539538, 'mean_word_length': 2.42976, 'pattern_counts': {'":': 0, '<': 30, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '55230', 'n_tokens_mistral': 5673, 'n_tokens_neox': 5546, 'n_words': 1773}