<commit_before>//Translated from game_logic.rb using babyruby2js
'use strict';

var main = require('./main');
var Grid = require('./Grid');
var Stone = require('./Stone');
var Group = require('./Group');
var Goban = require('./Goban');
var SgfReader = require('./SgfReader');
var HandicapSetter = require('./HandicapSetter');

/** @class GameLogic enforces the game logic.
 *  public read-only attribute: goban, komi, curColor, gameEnded, gameEnding, whoResigned
 */
function GameLogic() {
    this.console = false;
    this.history = [];
    this.errors = [];
    this.handicap = 0;
    this.whoResigned = null;
    this.goban = null;
}
module.exports = GameLogic;

GameLogic.prototype.newGame = function (gsize, handicap, komi) {
    if (gsize === undefined) gsize = null;
    if (handicap === undefined) handicap = 0;
    if (komi === undefined) komi = null;
    this.history.clear();
    this.errors.clear();
    this.numPass = 0;
    this.curColor = main.BLACK;
    this.gameEnded = this.gameEnding = false;
    this.whoResigned = null;
    if (!this.goban || (gsize && gsize !== this.goban.gsize)) {
        this.goban = new Goban(gsize);
    } else {
        this.goban.clear();
    }
    this.komi = (( komi ? komi : (( handicap === 0 ? 6.5 : 0.5 )) ));
    return this.setHandicap(handicap);
};

// Initializes the handicap points
// h can be a number or a string
// string examples: "3" or "3=d4-p16-p4" or "d4-p16-p4"
GameLogic.prototype.setHandicap = function (h) {
    if (this.history.length > 0) {
        throw new Error('Handicap cannot be changed during a game');
    }
    this.handicap = HandicapSetter.setHandicap(this.goban, h);
    // White first when handicap
    if (this.handicap !== 0) {
        this.curColor = main.WHITE;
    }
    return true;
};

// game is a series of moves, e.g. "c2,b2,pass,b4,b3,undo,b4,pass,b3"
GameLogic.prototype.loadMoves = function (game) {
    try {
        game = this.sgfToGame(game);
        for (var move, move_array = game.split(','), move_ndx = 0; move=move_array[move_ndx], move_ndx < move_array.length; move_ndx++) {
            if (!this.playOneMove(move)) {
                throw new Error('Failed playing the loaded move: ' + move);
            }
        }
        return true;
    } catch (err) {
        this.errorMsg('Failed loading moves. Please double check the format of your input.');
        this.errorMsg('Error: ' + err.message + ' (' + err.constructor.name + ')');
        main.log.error('Error while loading moves:\n' + err + '\n' + err.stack);
        return false;
    }
};

// Handles a regular move + the special commands (pass, resign, undo, load, hand, log)
// Returns false if a problem occured. In this case the error message is available.
GameLogic.prototype.playOneMove = function (move) {
    if (this.gameEnded) {
        return this.errorMsg('Game already ended');
    }
    // $log.debug("GameLogic playing #{Grid.color_name(@cur_color)}: #{move}") if $debug
    if (/^[a-z][1-2]?[0-9]$/.test(move)) {
        return this.playAStone(move);
    } else if (move === 'undo') {
        return this.requestUndo();
    } else if (move.startWith('resi')) {
        return this.resign();
    } else if (move === 'pass') {
        return this.passOneMove();
    } else if (move.startWith('hand')) {
        return this.setHandicap(move.split(':')[1]);
    } else if (move.startWith('load:')) {
        return this.loadMoves(move.range(5, -1));
    } else if (move.startWith('log')) {
        return this.setLogLevel(move.split(':')[1]);
    } else {
        return this.errorMsg('Invalid command: ' + move);
    }
};

// Handles a new stone move (not special commands like "pass")
GameLogic.prototype.playAStone = function (move) {
    var i, j;
    var _m = Grid.parseMove(move);
    i = _m[0];
    j = _m[1];

    if (!Stone.validMove(this.goban, i, j, this.curColor)) {
        return this.errorMsg('Invalid move: ' + move);
    }
    Stone.playAt(this.goban, i, j, this.curColor);
    this.storeMoveInHistory(move);
    this.nextPlayer();
    this.numPass = 0;
    return true;
};

// One player resigns.
GameLogic.prototype.resign = function () {
    this.storeMoveInHistory('resign');
    this.whoResigned = this.curColor;
    this.gameEnded = true;
    return true;
};

// Call this when the current player wants to pass.
// If all (remaining) players pass, we go into "ending mode".
// Caller is responsible of checking the GameLogic#game_ending flag:
// If the flag goes to true, the method accept_ending (below) should be called next.
GameLogic.prototype.passOneMove = function () {
    this.storeMoveInHistory('pass');
    this.numPass += 1;
    if (this.numPass >= 2) {
        this.gameEnding = true;
    }
    this.nextPlayer();
    return true;
};

// Call this each time GameLogic#game_ending goes to true (ending mode).
// The score should be counted and proposed to players.
// "accept" parameter should be true if all players accept the proposed ending (score count).
// Only after this call will the game be really finished.
// If accept=false, this means a player refuses to end here
// => the game should continue until the next time all players pass.
GameLogic.prototype.acceptEnding = function (accept) {
    if (!this.gameEnding) {
        return this.errorMsg('The game is not ending yet');
    }
    if (!accept) {
        this.gameEnding = false; // exit ending mode; we will play some more...
    } else {
        this.gameEnded = true; // ending accepted. Game is finished.
    }
    return true;
};

// Returns how many moves have been played so far
// (can be bigger than the stone count since "pass" or "resign" are also moves)
GameLogic.prototype.moveNumber = function () {
    return this.history.length;
};

// Returns a text representation of the list of moves played so far
GameLogic.prototype.historyString = function () {
    return (( this.handicap > 0 ? 'handicap:' + this.handicap + ',' : '' )) + this.history.join(',') + ' (' + this.history.length + ' moves)';
};

// Returns an array with the prisoner count per color
// e.g. [3,5] means 3 black stones are prisoners, 5 white stones
GameLogic.prototype.prisoners = function () {
    return Group.prisoners(this.goban);
};

// If called with on=true, error messages will be directly displayed on the console.
// If not called, the default behavior needs the caller to use get_errors method.
GameLogic.prototype.messagesToConsole = function (on) {
    if (on === undefined) on = true;
    this.console = on;
};

// Returns the error messages noticed until now and clears the list.
GameLogic.prototype.getErrors = function () {
    var errors = main.clone(this.errors);
    this.errors.clear();
    return errors;
};

GameLogic.prototype.setLogLevel = function (cmd) {
    try {
        var a = cmd.split('=');
        var flag = parseInt(a[1]) !== 0;
        if (!flag && a[1] !== '0') {
            throw new Error(0);
        }
        switch (a[0]) {
        case 'group':
            main.debugGroup = flag;
            break;
        case 'ai':
            main.debugAi = flag;
            break;
        case 'all':
            main.debug = main.debugGroup = main.debugAi = flag;
            break;
        default:
            throw new Error(1);
        }
        return true;
    } catch (_exc) {
        return this.errorMsg('Invalid log command: ' + cmd);
    }
};

// ===============================================================================
//private;
// ===============================================================================
GameLogic.prototype.nextPlayer = function () {
    this.curColor = (this.curColor + 1) % 2;
};

// Always returns false
GameLogic.prototype.errorMsg = function (msg) {
    if (!this.console) {
        this.errors.push(msg);
    } else {
        console.log(msg);
    }
    return false;
};

GameLogic.prototype.storeMoveInHistory = function (move) {
    return this.history.push(move);
};

// undo one full game turn (e.g. one black move and one white)
GameLogic.prototype.requestUndo = function () {
    if (this.history.length < 2) {
        return this.errorMsg('Nothing to undo');
    }
    for (var _i = 0; _i < 2; _i++) {
        if (!this.history[this.history.length-1].endWith('pass')) { // no stone to remove for a pass
            Stone.undo(this.goban);
        }
        this.history.pop();
    }
    this.numPass = 0;
    return true;
};

// Converts a game (list of moves) from SGF format to our internal format.
// Returns the game unchanged if it is not an SGF one.
// Returns an empty move list if nothing should be played (a game is pending).
GameLogic.prototype.sgfToGame = function (game) {
    if (!game.startWith('(;FF')) { // are they are always the 1st characters?
        return game;
    }
    var reader = new SgfReader(game);
    this.newGame(reader.boardSize);
    this.komi = reader.komi;
    return reader.toMoveList();
};
<commit_msg>No need to clone the error list in GameLogic#getErrors<commit_after>//Translated from game_logic.rb using babyruby2js
'use strict';

var main = require('./main');
var Grid = require('./Grid');
var Stone = require('./Stone');
var Group = require('./Group');
var Goban = require('./Goban');
var SgfReader = require('./SgfReader');
var HandicapSetter = require('./HandicapSetter');

/** @class GameLogic enforces the game logic.
 *  public read-only attribute: goban, komi, curColor, gameEnded, gameEnding, whoResigned
 */
function GameLogic() {
    this.console = false;
    this.history = [];
    this.errors = [];
    this.handicap = 0;
    this.whoResigned = null;
    this.goban = null;
}
module.exports = GameLogic;

GameLogic.prototype.newGame = function (gsize, handicap, komi) {
    if (gsize === undefined) gsize = null;
    if (handicap === undefined) handicap = 0;
    if (komi === undefined) komi = null;
    this.history.clear();
    this.errors.clear();
    this.numPass = 0;
    this.curColor = main.BLACK;
    this.gameEnded = this.gameEnding = false;
    this.whoResigned = null;
    if (!this.goban || (gsize && gsize !== this.goban.gsize)) {
        this.goban = new Goban(gsize);
    } else {
        this.goban.clear();
    }
    this.komi = (( komi ? komi : (( handicap === 0 ? 6.5 : 0.5 )) ));
    return this.setHandicap(handicap);
};

// Initializes the handicap points
// h can be a number or a string
// string examples: "3" or "3=d4-p16-p4" or "d4-p16-p4"
GameLogic.prototype.setHandicap = function (h) {
    if (this.history.length > 0) {
        throw new Error('Handicap cannot be changed during a game');
    }
    this.handicap = HandicapSetter.setHandicap(this.goban, h);
    // White first when handicap
    if (this.handicap !== 0) {
        this.curColor = main.WHITE;
    }
    return true;
};

// game is a series of moves, e.g. "c2,b2,pass,b4,b3,undo,b4,pass,b3"
GameLogic.prototype.loadMoves = function (game) {
    try {
        game = this.sgfToGame(game);
        for (var move, move_array = game.split(','), move_ndx = 0; move=move_array[move_ndx], move_ndx < move_array.length; move_ndx++) {
            if (!this.playOneMove(move)) {
                throw new Error('Failed playing the loaded move: ' + move);
            }
        }
        return true;
    } catch (err) {
        this.errorMsg('Failed loading moves. Please double check the format of your input.');
        this.errorMsg('Error: ' + err.message + ' (' + err.constructor.name + ')');
        main.log.error('Error while loading moves:\n' + err + '\n' + err.stack);
        return false;
    }
};

// Handles a regular move + the special commands (pass, resign, undo, load, hand, log)
// Returns false if a problem occured. In this case the error message is available.
GameLogic.prototype.playOneMove = function (move) {
    if (this.gameEnded) {
        return this.errorMsg('Game already ended');
    }
    // $log.debug("GameLogic playing #{Grid.color_name(@cur_color)}: #{move}") if $debug
    if (/^[a-z][1-2]?[0-9]$/.test(move)) {
        return this.playAStone(move);
    } else if (move === 'undo') {
        return this.requestUndo();
    } else if (move.startWith('resi')) {
        return this.resign();
    } else if (move === 'pass') {
        return this.passOneMove();
    } else if (move.startWith('hand')) {
        return this.setHandicap(move.split(':')[1]);
    } else if (move.startWith('load:')) {
        return this.loadMoves(move.range(5, -1));
    } else if (move.startWith('log')) {
        return this.setLogLevel(move.split(':')[1]);
    } else {
        return this.errorMsg('Invalid command: ' + move);
    }
};

// Handles a new stone move (not special commands like "pass")
GameLogic.prototype.playAStone = function (move) {
    var i, j;
    var _m = Grid.parseMove(move);
    i = _m[0];
    j = _m[1];

    if (!Stone.validMove(this.goban, i, j, this.curColor)) {
        return this.errorMsg('Invalid move: ' + move);
    }
    Stone.playAt(this.goban, i, j, this.curColor);
    this.storeMoveInHistory(move);
    this.nextPlayer();
    this.numPass = 0;
    return true;
};

// One player resigns.
GameLogic.prototype.resign = function () {
    this.storeMoveInHistory('resign');
    this.whoResigned = this.curColor;
    this.gameEnded = true;
    return true;
};

// Call this when the current player wants to pass.
// If all (remaining) players pass, we go into "ending mode".
// Caller is responsible of checking the GameLogic#game_ending flag:
// If the flag goes to true, the method accept_ending (below) should be called next.
GameLogic.prototype.passOneMove = function () {
    this.storeMoveInHistory('pass');
    this.numPass += 1;
    if (this.numPass >= 2) {
        this.gameEnding = true;
    }
    this.nextPlayer();
    return true;
};

// Call this each time GameLogic#game_ending goes to true (ending mode).
// The score should be counted and proposed to players.
// "accept" parameter should be true if all players accept the proposed ending (score count).
// Only after this call will the game be really finished.
// If accept=false, this means a player refuses to end here
// => the game should continue until the next time all players pass.
GameLogic.prototype.acceptEnding = function (accept) {
    if (!this.gameEnding) {
        return this.errorMsg('The game is not ending yet');
    }
    if (!accept) {
        this.gameEnding = false; // exit ending mode; we will play some more...
    } else {
        this.gameEnded = true; // ending accepted. Game is finished.
    }
    return true;
};

// Returns how many moves have been played so far
// (can be bigger than the stone count since "pass" or "resign" are also moves)
GameLogic.prototype.moveNumber = function () {
    return this.history.length;
};

// Returns a text representation of the list of moves played so far
GameLogic.prototype.historyString = function () {
    return (( this.handicap > 0 ? 'handicap:' + this.handicap + ',' : '' )) + this.history.join(',') + ' (' + this.history.length + ' moves)';
};

// Returns an array with the prisoner count per color
// e.g. [3,5] means 3 black stones are prisoners, 5 white stones
GameLogic.prototype.prisoners = function () {
    return Group.prisoners(this.goban);
};

// If called with on=true, error messages will be directly displayed on the console.
// If not called, the default behavior needs the caller to use get_errors method.
GameLogic.prototype.messagesToConsole = function (on) {
    if (on === undefined) on = true;
    this.console = on;
};

// Returns the error messages noticed until now and clears the list.
GameLogic.prototype.getErrors = function () {
    var errors = this.errors;
    this.errors = [];
    return errors;
};

GameLogic.prototype.setLogLevel = function (cmd) {
    try {
        var a = cmd.split('=');
        var flag = parseInt(a[1]) !== 0;
        if (!flag && a[1] !== '0') {
            throw new Error(0);
        }
        switch (a[0]) {
        case 'group':
            main.debugGroup = flag;
            break;
        case 'ai':
            main.debugAi = flag;
            break;
        case 'all':
            main.debug = main.debugGroup = main.debugAi = flag;
            break;
        default:
            throw new Error(1);
        }
        return true;
    } catch (_exc) {
        return this.errorMsg('Invalid log command: ' + cmd);
    }
};

// ===============================================================================
//private;
// ===============================================================================
GameLogic.prototype.nextPlayer = function () {
    this.curColor = (this.curColor + 1) % 2;
};

// Always returns false
GameLogic.prototype.errorMsg = function (msg) {
    if (!this.console) {
        this.errors.push(msg);
    } else {
        console.log(msg);
    }
    return false;
};

GameLogic.prototype.storeMoveInHistory = function (move) {
    return this.history.push(move);
};

// undo one full game turn (e.g. one black move and one white)
GameLogic.prototype.requestUndo = function () {
    if (this.history.length < 2) {
        return this.errorMsg('Nothing to undo');
    }
    for (var _i = 0; _i < 2; _i++) {
        if (!this.history[this.history.length-1].endWith('pass')) { // no stone to remove for a pass
            Stone.undo(this.goban);
        }
        this.history.pop();
    }
    this.numPass = 0;
    return true;
};

// Converts a game (list of moves) from SGF format to our internal format.
// Returns the game unchanged if it is not an SGF one.
// Returns an empty move list if nothing should be played (a game is pending).
GameLogic.prototype.sgfToGame = function (game) {
    if (!game.startWith('(;FF')) { // are they are always the 1st characters?
        return game;
    }
    var reader = new SgfReader(game);
    this.newGame(reader.boardSize);
    this.komi = reader.komi;
    return reader.toMoveList();
};
<|endoftext|>


<commit_before>// Generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %>
/*jshint camelcase: false */
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {
    // show elapsed time at the end
    require('time-grunt')(grunt);
    // load all grunt tasks
    require('load-grunt-tasks')(grunt);

    grunt.initConfig({
        // configurable paths
        yeoman: {
            dev: 'dev',
            server: 'server',
            dist: 'dist'
        },
        watch: {
            options: {
                nospawn: false
            },
            jade: {
                files: ['<%%= yeoman.dev %>/markup/{,*/}{,*/}*.jade'],
                tasks: ['jade:server']
            }<% if (cssOption === 'SASS') { %>,
            sass: {
                files: ['<%%= yeoman.dev %>/styles/{,*/}{,*/}*.{scss,sass}'],
                tasks: ['sass:server', 'copy:server']
            }<% } %><% if (cssOption === 'LESS') { %>,
            less: {
                files: ['<%%= yeoman.dev %>/styles/{,*/}{,*/}*.less'],
                tasks: ['less:server', 'copy:server']
            }<% } %>,
            js: {
                files: ['<%%= yeoman.dev %>/scripts/{,*/}{,*/}*.js', '<%%= yeoman.dev %>/bower_components/{,*/}{,*/}*.js'],
                tasks: ['copy:server'<% if (jshint) { %>, 'jshint'<% } %>]
            },
            images: {
                files: ['<%%= yeoman.dev %>/images/{,*/}{,*/}*.{png,jpg,gif}'],
                tasks: ['copy:server']
            },
            root: {
                files: [
                    '<%%= yeoman.dev %>/*.{ico,png,txt,html}',
                    '<%%= yeoman.dev %>/.htaccess',
                    '<%%= yeoman.dev %>/images/{,*/}*.{webp}',
                    '<%%= yeoman.dev %>/styles/fonts/{,*/}*.*'
                ],
                tasks: ['copy:server']
            },
            livereload: {
                options: {
                    livereload: '<%%= connect.options.livereload %>'
                },
                files: [
                    '<%%= yeoman.server %>/*.{ico,png,txt,html}',
                    '<%%= yeoman.server %>/.htaccess',
                    '<%%= yeoman.server %>/styles/fonts/{,*/}*.*',
                    '<%%= yeoman.server %>/markup/{,*/}{,*/}*.html',
                    '<%%= yeoman.server %>/styles/{,*/}{,*/}*.css'<% if (cssOption === 'SASS') { %>,
                    '<%%= yeoman.server %>/styles/{,*/}{,*/}*.{sass,scss}'<% } %><% if (cssOption === 'LESS') { %>,
                    '<%%= yeoman.server %>/styles/{,*/}{,*/}*.less'<% } %>,
                    '<%%= yeoman.server %>/scripts/{,*/}{,*/}*.js',
                    '<%%= yeoman.server %>/images/{,*/}{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
                ]
            }
        },
        connect: {
            options: {
                port: 9000,
                livereload: 35729,
                // change this to '0.0.0.0' to access the server from outside
                hostname: '0.0.0.0'
            },
            livereload: {
                options: {
                    open: true,
                    base: '<%%= yeoman.server %>'
                }
            }
        },
        clean: {
            server: ['<%%= yeoman.server %>/'],
            dist: ['<%%= yeoman.dist %>/']
        },
        // Put files not handled in other tasks here
        copy: {
            server: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/',
                    dest: '<%%= yeoman.server %>/',
                    src: [
                        '*.{ico,png,txt,html}',
                        '.htaccess',
                        'images/{,*/}*.{webp,gif}',
                        'styles/fonts/{,*/}*.*',
                    ]
                }, {
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/',
                    dest: '<%%= yeoman.server %>/',
                    src: [
                        'scripts/**'
                    ]
                }, {
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/',
                    dest: '<%%= yeoman.server %>/',
                    src: [
                        'images/**'
                    ]
                }, {
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/',
                    dest: '<%%= yeoman.server %>/',
                    src: [
                        'bower_components/{,*/}{,*/}*.js'
                    ]
                }, {
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/styles',
                    dest: '<%%= yeoman.server %>/styles',
                    src: [
                        '{,*/}{,*/}*.{scss,less}'
                    ]
                }]
            },
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/',
                    dest: '<%%= yeoman.dist %>/',
                    src: [
                        'bower_components/requirejs/require.js',
                        'bower_components/modernizr/modernizr.js'
                    ]
                }, {
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/',
                    dest: '<%%= yeoman.dist %>/',
                    src: [
                        '*.{ico,txt}',
                        '.htaccess',
                        'images/{,*/}*.{webp,gif}',
                        'styles/fonts/{,*/}*.*',
                    ]
                }, {
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/styles',
                    dest: '<%%= yeoman.dist %>/styles',
                    src: [
                        '{,*/}{,*/}*.{scss,less}'
                    ]
                }]
            }
        },
        jade: {
            server: {
                options: {
                    pretty: true,
                    client: false,
                    data: {
                        debug: true
                    }
                },
                expand: true,
                cwd: '<%%= yeoman.dev %>/',
                dest: '<%%= yeoman.server %>/',
                src: ['markup/pages/*.jade'],
                ext: '.html'
            },
            dist: {
                options: {
                    pretty: true,
                    client: false,
                    data: {
                        debug: false
                    }
                },
                expand: true,
                cwd: '<%%= yeoman.dev %>/',
                dest: '<%%= yeoman.dist %>/',
                src: ['markup/pages/*.jade'],
                ext: '.html'
            }
        }<% if (jshint) { %>,
        jshint: {
            options: {
                jshintrc: '.jshintrc',
                reporter: require('jshint-stylish')
            },
            all: [
                'Gruntfile.js',
                '<%%= yeoman.server %>/scripts/{,*/}{,*/}*.js',
                '!<%%= yeoman.server %>/scripts/vendor/{,*/}*',
            ]
        }<% } %>,
        'string-replace': {
            sassMapFixServer: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: '../../dev/styles/',
                            replacement: ''
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.server %>/styles/main.css.map' : '<%%= yeoman.server %>/styles/main.css.map'
                }
            },
            lessMainFixServer: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: '=./server/styles/',
                            replacement: '='
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.server %>/styles/main.css' : '<%%= yeoman.server %>/styles/main.css'
                }
            },
            lessMapFixServer: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: './dev/styles/',
                            replacement: ''
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.server %>/styles/main.css.map' : '<%%= yeoman.server %>/styles/main.css.map'
                }
            },
            sassMapFixDist: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: '../../dev/styles/',
                            replacement: ''
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.dist %>/styles/main.css.map' : '<%%= yeoman.dist %>/styles/main.css.map'
                }
            },
            lessMainFixDist: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: '=./dist/styles/',
                            replacement: '='
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.dist %>/styles/main.css' : '<%%= yeoman.dist %>/styles/main.css'
                }
            },
            lessMapFixDist: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: './dev/styles/',
                            replacement: ''
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.dist %>/styles/main.css.map' : '<%%= yeoman.dist %>/styles/main.css.map'
                }
            },
            requireDist: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: 'data-main="../../scripts/main"',
                            replacement: 'data-main="../../scripts/main.min"'
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.dist %>/markup/pages/index.html' : '<%%= yeoman.dist %>/markup/pages/index.html'
                }
            }
        },
        autoprefixer: {
            options: {
                browsers: ['last 5 versions', '> 1%', 'ff 17', 'opera 11.1', 'ie 8']
            },
            server: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.server %>/styles',
                    src: '{,*/}*.css',
                    dest: '<%%= yeoman.server %>/styles'
                }]
            },
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.dist %>/styles',
                    src: '{,*/}*.css',
                    dest: '<%%= yeoman.dist %>/styles'
                }]
            }
        },
        // gzip assets 1-to-1 for production
        compress: {
            main: {
                options: {
                    mode: 'zip',
                    pretty: true,
                    archive: '<%%= yeoman.dist %>/build.zip'
                },
                expand: true,
                cwd: '<%%= yeoman.dist %>/',
                src: ['*/**', '*.{txt,html}', '!**/*.zip', '!**/*.psd', '!**/.git/']
            }
        },<% if (useFTP) { %>
        'ftp-deploy': {
            build: {
                auth: {
                    host: '<%%= secret.host %>',
                    port: 21,
                    authKey: 'key1'
                },
                src: '<%%= yeoman.dist %>',
                dest: '<%%= secret.serverPath %>',
                exclusions: ['*.svn', '.svn/', '.svn', '*.git', '.git/', '.git'],
                server_sep: '/'
            }
        },<% } %>
        uglify: {
            dist: {
                options: {
                    mangle: true,
                    preserveComments: 'some',
                },
                expand: true,
                cwd: '<%%= yeoman.dist %>/bower_components/',
                dest: '<%%= yeoman.dist %>/bower_components/',
                src: ['{,*/}{,*/}*.js'],
                ext: '.js'
            }
        },
        requirejs: {
            dist: {
                options: {
                    name: 'main',
                    baseUrl: '<%%= yeoman.dev %>/scripts/',
                    mainConfigFile: '<%%= yeoman.dev %>/scripts/main.js',
                    out: '<%%= yeoman.dist %>/scripts/main.min.js',
                    optimize: 'uglify2',
                    generateSourceMaps: true,
                    preserveLicenseComments: false,
                }
            }
        }<% if (cssOption === 'SASS') { %>,
        sass: {
            server: {
                options: {
                    style: 'expanded',
                    lineNumbers: true,
                    sourcemap: true,
                    trace: true
                },
                expand: true,
                cwd: '<%%= yeoman.dev %>/',
                dest: '<%%= yeoman.server %>/',
                src: ['styles/*.scss'],
                ext: '.css'
            },
            dist: {
                options: {
                    style: 'compressed',
                    lineNumbers: false,
                    sourcemap: true,
                    trace: true
                },
                expand: true,
                cwd: '<%%= yeoman.dev %>/',
                dest: '<%%= yeoman.dist %>/',
                src: ['styles/*.scss'],
                ext: '.css'
            }
        }<% } %><% if (cssOption === 'LESS') { %>,
        less: {
            server: {
                options: {
                    paths: ['<%%= yeoman.dev %>/'],
                    sourceMap: true,
                    sourceMapFilename: '<%%= yeoman.server %>/styles/main.css.map',
                    sourceMapBasepath: './',
                    sourceMapRootpath: './',
                    dumpLineNumbers: 'comments'
                },
                expand: true,
                cwd: '<%%= yeoman.dev %>/',
                dest: '<%%= yeoman.server %>/',
                src: ['styles/*.less'],
                ext: '.css'
            },
            dist: {
                options: {
                    paths: ['<%%= yeoman.dev %>/'],
                    sourceMap: true,
                    sourceMapFilename: '<%%= yeoman.dist %>/styles/main.css.map',
                    sourceMapBasepath: './',
                    sourceMapRootpath: './',
                    compress: true
                },
                expand: true,
                cwd: '<%%= yeoman.dev %>/',
                dest: '<%%= yeoman.dist %>/',
                src: ['styles/*.less'],
                ext: '.css'
            }
        }<% } %>
    });<% if (useFTP) { %>

    grunt.registerTask('ftpinfo', 'Grab FTP info for deployment; If valid, then deploy to FTP server', function () {
        var ftpJSON = grunt.file.readJSON('.ftppass');
        if (ftpJSON.host === '') {
            console.log('ERROR: Deploy will not work until you fill out FTP server info in the ".ftppass" file!');
        }
        else {
            grunt.config.set('secret', ftpJSON);
            grunt.task.run(['ftp-deploy']);
        }
    });<% } %>

    grunt.registerTask('server', 'Open a developement server within your browser', [
        'clean:server',
        'copy:server'<% if (jshint) { %>,
        'jshint'<% } %>,
        'jade:server',<% if (cssOption === 'LESS') { %>
        'less:server',
        'string-replace:lessMapFixServer',
        'string-replace:lessMainFixServer',<% } %><% if (cssOption === 'SASS') { %>
        'sass:server',
        'string-replace:sassMapFixServer',<% } %>
        'autoprefixer:server',
        'connect:livereload',
        'watch'
    ]);

    grunt.registerTask('build', 'Build a production ready version of your site.', [
        'clean:dist',
        'copy:dist',
        'jade:dist',<% if (cssOption === 'LESS') { %>
        'less:dist',
        'string-replace:lessMapFixDist',
        'string-replace:lessMainFixDist',<% } %><% if (cssOption === 'SASS') { %>
        'sass:dist',
        'string-replace:sassMapFixDist',<% } %>
        'autoprefixer:dist',
        'requirejs',
        'string-replace:requireDist', // change require main path to 'main.min'
        'uglify'<% if (jshint) { %>,
        'jshint'<% } %>
    ]);

    grunt.registerTask('zip', 'Build a production ready version of your site and zip it up', [
        'build',
        'compress'
    ]);<% if (useFTP) { %>

    grunt.registerTask('deploy', 'Build a production ready version of your site and deploy it to a desired FTP server.', [
        'zip',
        'ftpinfo',
    ]);<% } %>

};<commit_msg>updated jshint task to have a server, dist, and test task. So now Js can be linted on all three dev environments: dev, server, and dist<commit_after>// Generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %>
/*jshint camelcase: false */
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {
    // show elapsed time at the end
    require('time-grunt')(grunt);
    // load all grunt tasks
    require('load-grunt-tasks')(grunt);

    grunt.initConfig({
        // configurable paths
        yeoman: {
            dev: 'dev',
            server: 'server',
            dist: 'dist'
        },
        watch: {
            options: {
                nospawn: false
            },
            jade: {
                files: ['<%%= yeoman.dev %>/markup/{,*/}{,*/}*.jade'],
                tasks: ['jade:server']
            }<% if (cssOption === 'SASS') { %>,
            sass: {
                files: ['<%%= yeoman.dev %>/styles/{,*/}{,*/}*.{scss,sass}'],
                tasks: ['sass:server', 'copy:server']
            }<% } %><% if (cssOption === 'LESS') { %>,
            less: {
                files: ['<%%= yeoman.dev %>/styles/{,*/}{,*/}*.less'],
                tasks: ['less:server', 'copy:server']
            }<% } %>,
            js: {
                files: ['<%%= yeoman.dev %>/scripts/{,*/}{,*/}*.js', '<%%= yeoman.dev %>/bower_components/{,*/}{,*/}*.js'],
                tasks: ['copy:server'<% if (jshint) { %>, 'jshint'<% } %>]
            },
            images: {
                files: ['<%%= yeoman.dev %>/images/{,*/}{,*/}*.{png,jpg,gif}'],
                tasks: ['copy:server']
            },
            root: {
                files: [
                    '<%%= yeoman.dev %>/*.{ico,png,txt,html}',
                    '<%%= yeoman.dev %>/.htaccess',
                    '<%%= yeoman.dev %>/images/{,*/}*.{webp}',
                    '<%%= yeoman.dev %>/styles/fonts/{,*/}*.*'
                ],
                tasks: ['copy:server']
            },
            livereload: {
                options: {
                    livereload: '<%%= connect.options.livereload %>'
                },
                files: [
                    '<%%= yeoman.server %>/*.{ico,png,txt,html}',
                    '<%%= yeoman.server %>/.htaccess',
                    '<%%= yeoman.server %>/styles/fonts/{,*/}*.*',
                    '<%%= yeoman.server %>/markup/{,*/}{,*/}*.html',
                    '<%%= yeoman.server %>/styles/{,*/}{,*/}*.css'<% if (cssOption === 'SASS') { %>,
                    '<%%= yeoman.server %>/styles/{,*/}{,*/}*.{sass,scss}'<% } %><% if (cssOption === 'LESS') { %>,
                    '<%%= yeoman.server %>/styles/{,*/}{,*/}*.less'<% } %>,
                    '<%%= yeoman.server %>/scripts/{,*/}{,*/}*.js',
                    '<%%= yeoman.server %>/images/{,*/}{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
                ]
            }
        },
        connect: {
            options: {
                port: 9000,
                livereload: 35729,
                // change this to '0.0.0.0' to access the server from outside
                hostname: '0.0.0.0'
            },
            livereload: {
                options: {
                    open: true,
                    base: '<%%= yeoman.server %>'
                }
            }
        },
        clean: {
            server: ['<%%= yeoman.server %>/'],
            dist: ['<%%= yeoman.dist %>/']
        },
        // Put files not handled in other tasks here
        copy: {
            server: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/',
                    dest: '<%%= yeoman.server %>/',
                    src: [
                        '*.{ico,png,txt,html}',
                        '.htaccess',
                        'images/{,*/}*.{webp,gif}',
                        'styles/fonts/{,*/}*.*',
                    ]
                }, {
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/',
                    dest: '<%%= yeoman.server %>/',
                    src: [
                        'scripts/**'
                    ]
                }, {
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/',
                    dest: '<%%= yeoman.server %>/',
                    src: [
                        'images/**'
                    ]
                }, {
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/',
                    dest: '<%%= yeoman.server %>/',
                    src: [
                        'bower_components/{,*/}{,*/}*.js'
                    ]
                }, {
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/styles',
                    dest: '<%%= yeoman.server %>/styles',
                    src: [
                        '{,*/}{,*/}*.{scss,less}'
                    ]
                }]
            },
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/',
                    dest: '<%%= yeoman.dist %>/',
                    src: [
                        'bower_components/requirejs/require.js',
                        'bower_components/modernizr/modernizr.js'
                    ]
                }, {
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/',
                    dest: '<%%= yeoman.dist %>/',
                    src: [
                        '*.{ico,txt}',
                        '.htaccess',
                        'images/{,*/}*.{webp,gif}',
                        'styles/fonts/{,*/}*.*',
                    ]
                }, {
                    expand: true,
                    cwd: '<%%= yeoman.dev %>/styles',
                    dest: '<%%= yeoman.dist %>/styles',
                    src: [
                        '{,*/}{,*/}*.{scss,less}'
                    ]
                }]
            }
        },
        jade: {
            server: {
                options: {
                    pretty: true,
                    client: false,
                    data: {
                        debug: true
                    }
                },
                expand: true,
                cwd: '<%%= yeoman.dev %>/',
                dest: '<%%= yeoman.server %>/',
                src: ['markup/pages/*.jade'],
                ext: '.html'
            },
            dist: {
                options: {
                    pretty: true,
                    client: false,
                    data: {
                        debug: false
                    }
                },
                expand: true,
                cwd: '<%%= yeoman.dev %>/',
                dest: '<%%= yeoman.dist %>/',
                src: ['markup/pages/*.jade'],
                ext: '.html'
            }
        }<% if (jshint) { %>,
        jshint: {
            options: {
                jshintrc: '.jshintrc',
                reporter: require('jshint-stylish')
            },
            server: [
                'Gruntfile.js',
                '<%%= yeoman.server %>/scripts/{,*/}{,*/}*.js',
                '!<%%= yeoman.server %>/scripts/vendor/{,*/}*',
            ],
            build: [
                'Gruntfile.js',
                '<%%= yeoman.dist %>/scripts/{,*/}{,*/}*.js',
                '!<%%= yeoman.dist %>/scripts/vendor/{,*/}*',
            ],
            test: [
                'Gruntfile.js',
                '<%%= yeoman.dev %>/scripts/{,*/}{,*/}*.js',
                '!<%%= yeoman.dev %>/scripts/vendor/{,*/}*',
            ],
        }<% } %>,
        'string-replace': {
            sassMapFixServer: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: '../../dev/styles/',
                            replacement: ''
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.server %>/styles/main.css.map' : '<%%= yeoman.server %>/styles/main.css.map'
                }
            },
            lessMainFixServer: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: '=./server/styles/',
                            replacement: '='
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.server %>/styles/main.css' : '<%%= yeoman.server %>/styles/main.css'
                }
            },
            lessMapFixServer: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: './dev/styles/',
                            replacement: ''
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.server %>/styles/main.css.map' : '<%%= yeoman.server %>/styles/main.css.map'
                }
            },
            sassMapFixDist: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: '../../dev/styles/',
                            replacement: ''
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.dist %>/styles/main.css.map' : '<%%= yeoman.dist %>/styles/main.css.map'
                }
            },
            lessMainFixDist: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: '=./dist/styles/',
                            replacement: '='
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.dist %>/styles/main.css' : '<%%= yeoman.dist %>/styles/main.css'
                }
            },
            lessMapFixDist: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: './dev/styles/',
                            replacement: ''
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.dist %>/styles/main.css.map' : '<%%= yeoman.dist %>/styles/main.css.map'
                }
            },
            requireDist: {
                options: {
                    replacements: [
                        // place files inline example
                        {
                            pattern: 'data-main="../../scripts/main"',
                            replacement: 'data-main="../../scripts/main.min"'
                        }
                    ]
                },
                files: {
                    '<%%= yeoman.dist %>/markup/pages/index.html' : '<%%= yeoman.dist %>/markup/pages/index.html'
                }
            }
        },
        autoprefixer: {
            options: {
                browsers: ['last 5 versions', '> 1%', 'ff 17', 'opera 11.1', 'ie 8']
            },
            server: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.server %>/styles',
                    src: '{,*/}*.css',
                    dest: '<%%= yeoman.server %>/styles'
                }]
            },
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.dist %>/styles',
                    src: '{,*/}*.css',
                    dest: '<%%= yeoman.dist %>/styles'
                }]
            }
        },
        // gzip assets 1-to-1 for production
        compress: {
            main: {
                options: {
                    mode: 'zip',
                    pretty: true,
                    archive: '<%%= yeoman.dist %>/build.zip'
                },
                expand: true,
                cwd: '<%%= yeoman.dist %>/',
                src: ['*/**', '*.{txt,html}', '!**/*.zip', '!**/*.psd', '!**/.git/']
            }
        },<% if (useFTP) { %>
        'ftp-deploy': {
            build: {
                auth: {
                    host: '<%%= secret.host %>',
                    port: 21,
                    authKey: 'key1'
                },
                src: '<%%= yeoman.dist %>',
                dest: '<%%= secret.serverPath %>',
                exclusions: ['*.svn', '.svn/', '.svn', '*.git', '.git/', '.git'],
                server_sep: '/'
            }
        },<% } %>
        uglify: {
            dist: {
                options: {
                    mangle: true,
                    preserveComments: 'some',
                },
                expand: true,
                cwd: '<%%= yeoman.dist %>/bower_components/',
                dest: '<%%= yeoman.dist %>/bower_components/',
                src: ['{,*/}{,*/}*.js'],
                ext: '.js'
            }
        },
        requirejs: {
            dist: {
                options: {
                    name: 'main',
                    baseUrl: '<%%= yeoman.dev %>/scripts/',
                    mainConfigFile: '<%%= yeoman.dev %>/scripts/main.js',
                    out: '<%%= yeoman.dist %>/scripts/main.min.js',
                    optimize: 'uglify2',
                    generateSourceMaps: true,
                    preserveLicenseComments: false,
                }
            }
        }<% if (cssOption === 'SASS') { %>,
        sass: {
            server: {
                options: {
                    style: 'expanded',
                    lineNumbers: true,
                    sourcemap: true,
                    trace: true
                },
                expand: true,
                cwd: '<%%= yeoman.dev %>/',
                dest: '<%%= yeoman.server %>/',
                src: ['styles/*.scss'],
                ext: '.css'
            },
            dist: {
                options: {
                    style: 'compressed',
                    lineNumbers: false,
                    sourcemap: true,
                    trace: true
                },
                expand: true,
                cwd: '<%%= yeoman.dev %>/',
                dest: '<%%= yeoman.dist %>/',
                src: ['styles/*.scss'],
                ext: '.css'
            }
        }<% } %><% if (cssOption === 'LESS') { %>,
        less: {
            server: {
                options: {
                    paths: ['<%%= yeoman.dev %>/'],
                    sourceMap: true,
                    sourceMapFilename: '<%%= yeoman.server %>/styles/main.css.map',
                    sourceMapBasepath: './',
                    sourceMapRootpath: './',
                    dumpLineNumbers: 'comments'
                },
                expand: true,
                cwd: '<%%= yeoman.dev %>/',
                dest: '<%%= yeoman.server %>/',
                src: ['styles/*.less'],
                ext: '.css'
            },
            dist: {
                options: {
                    paths: ['<%%= yeoman.dev %>/'],
                    sourceMap: true,
                    sourceMapFilename: '<%%= yeoman.dist %>/styles/main.css.map',
                    sourceMapBasepath: './',
                    sourceMapRootpath: './',
                    compress: true
                },
                expand: true,
                cwd: '<%%= yeoman.dev %>/',
                dest: '<%%= yeoman.dist %>/',
                src: ['styles/*.less'],
                ext: '.css'
            }
        }<% } %>
    });<% if (useFTP) { %>

    grunt.registerTask('ftpinfo', 'Grab FTP info for deployment; If valid, then deploy to FTP server', function () {
        var ftpJSON = grunt.file.readJSON('.ftppass');
        if (ftpJSON.host === '') {
            console.log('ERROR: Deploy will not work until you fill out FTP server info in the ".ftppass" file!');
        }
        else {
            grunt.config.set('secret', ftpJSON);
            grunt.task.run(['ftp-deploy']);
        }
    });<% } %>

    grunt.registerTask('server', 'Open a developement server within your browser', [
        'clean:server',
        'copy:server'<% if (jshint) { %>,
        'jshint:server'<% } %>,
        'jade:server',<% if (cssOption === 'LESS') { %>
        'less:server',
        'string-replace:lessMapFixServer',
        'string-replace:lessMainFixServer',<% } %><% if (cssOption === 'SASS') { %>
        'sass:server',
        'string-replace:sassMapFixServer',<% } %>
        'autoprefixer:server',
        'connect:livereload',
        'watch'
    ]);

    grunt.registerTask('build', 'Build a production ready version of your site.', [
        'clean:dist',
        'copy:dist',
        'jade:dist',<% if (cssOption === 'LESS') { %>
        'less:dist',
        'string-replace:lessMapFixDist',
        'string-replace:lessMainFixDist',<% } %><% if (cssOption === 'SASS') { %>
        'sass:dist',
        'string-replace:sassMapFixDist',<% } %>
        'autoprefixer:dist',
        'requirejs',
        'string-replace:requireDist', // change require main path to 'main.min'
        'uglify'<% if (jshint) { %>,
        'jshint:build'<% } %>
    ]);<% if (jshint) { %>

    grunt.registerTask('test', 'Peform tests on JavaScript', [
        'jshint:test'
    ]);<% } %>

    grunt.registerTask('zip', 'Build a production ready version of your site and zip it up', [
        'build',
        'compress'
    ]);<% if (useFTP) { %>

    grunt.registerTask('deploy', 'Build a production ready version of your site and deploy it to a desired FTP server.', [
        'zip',
        'ftpinfo',
    ]);<% } %>

};<|endoftext|>


<commit_before>
function KeyTouch(isWhite, keyIndex, velocity) {
        var _keyIndex = keyIndex;
        var _isWhite = isWhite;
        var _velocity = velocity;

        return {
            printKey: function () {
                return "note: " + _keyIndex + "
" + "isWhite: " + _isWhite +
            "
" + _velocity + "
";
            },
            setVelocity: function (velocity) {
                _velocity = velocity;
            },
            getKeyIndex: function() {
                return _keyIndex;
            },
            getVelocity: function() {
                return _velocity;
            }
        }
    }

var KeysLayer = {
	WHITEKEY_NUMBER : 7,
    OVERALL_BORDER : 0.02,
    BASE_NOTE_INDEX: 60,
    KEY_WIDTH : function() {
            return (1 - 2 * this.OVERALL_BORDER) / this.WHITEKEY_NUMBER;
        },
    KEYTOUCH_THRESHOLD : 0.05,
    getAllTouchedKeys: function(frame) {
        var resultList = [];
        var pointables = frame.pointables;
        for (var i = 0, pointable; pointable = pointables[i++]; ) {
            var interactionBox = frame.interactionBox;
            var normalizedPosition = interactionBox.normalizePoint(pointable.tipPosition, true);
            if (normalizedPosition[1] < 0.5 + this.KEYTOUCH_THRESHOLD &&
                normalizedPosition[1] > 0.5 - this.KEYTOUCH_THRESHOLD) {
                var key = this.findKey(normalizedPosition);

                if (key) {
                    key.setVelocity(interactionBox.normalizePoint(pointable.tipVelocity, true)[1]);
                    resultList.push(key);
                }
            }
        }
        return resultList;
    },
	findKey : function(pos) {
        var xIndex = Math.floor((pos[0] - this.OVERALL_BORDER) / this.KEY_WIDTH());
        if (pos[2] > 0.5 && xIndex >= 0 && xIndex < this.WHITEKEY_NUMBER) {
            return new KeyTouch(true, xIndex, 0);
        }

        if (pos[2] < 0.5 && xIndex >= 0 && xIndex < this.WHITEKEY_NUMBER
              && xIndex % 7 != 2 && xIndex % 7 != 6) {
            return new KeyTouch(false, xIndex, 0);
        }
    },
	PlayKeys: function(frame){
	    var pointableOutput = document.getElementById("result");
	    var pointableString = "";

	    var keyList = this.getAllTouchedKeys(frame);
	    for (var i = 0; i < keyList.length; i++) {
            if (keyList[i].getVelocity() > 0.5) {
			    piano.noteOn(0, this.BASE_NOTE_INDEX + keyList[i].getKeyIndex(), 127, 0);
            }
	    }
	}
}<commit_msg>Fix velocity<commit_after>
function KeyTouch(isWhite, keyIndex, velocity) {
        var _keyIndex = keyIndex;
        var _isWhite = isWhite;
        var _velocity = velocity;

        return {
            printKey: function () {
                return "note: " + _keyIndex + "
" + "isWhite: " + _isWhite +
            "
" + _velocity + "
";
            },
            setVelocity: function (velocity) {
                _velocity = velocity;
            },
            getKeyIndex: function() {
                return _keyIndex;
            },
            getVelocity: function() {
                return _velocity;
            }
        }
    }

var KeysLayer = {
	WHITEKEY_NUMBER : 14,
    OVERALL_BORDER : 0,
    BASE_NOTE_INDEX: 50,
    KEY_WIDTH : function() {
            return (1 - 2 * this.OVERALL_BORDER) / this.WHITEKEY_NUMBER;
        },
    KEYTOUCH_THRESHOLD : 0.55,
    getAllTouchedKeys: function(frame) {
        var resultList = [];
        var pointables = frame.pointables;
        for (var i = 0, pointable; pointable = pointables[i++]; ) {
            var interactionBox = frame.interactionBox;
            var normalizedPosition = interactionBox.normalizePoint(pointable.tipPosition, true);
            if (normalizedPosition[1] < 0.5 + this.KEYTOUCH_THRESHOLD &&
                normalizedPosition[1] > 0.5 - this.KEYTOUCH_THRESHOLD) {
                var key = this.findKey(normalizedPosition);

                if (key) {
                    key.setVelocity(interactionBox.normalizePoint(pointable.tipVelocity, true)[1]);
                    resultList.push(key);
                }
            }
        }
        return resultList;
    },
	findKey : function(pos) {
        var xIndex = Math.floor((pos[0] - this.OVERALL_BORDER) / this.KEY_WIDTH());
        if (pos[2] > 0.5 && xIndex >= 0 && xIndex < this.WHITEKEY_NUMBER) {
            return new KeyTouch(true, xIndex, 0);
        }

        if (pos[2] < 0.5 && xIndex >= 0 && xIndex < this.WHITEKEY_NUMBER
              && xIndex % 7 != 2 && xIndex % 7 != 6) {
            return new KeyTouch(false, xIndex, 0);
        }
    },
	PlayKeys: function(frame){
	    var pointableOutput = document.getElementById("result");
	    var pointableString = "";

	    var keyList = this.getAllTouchedKeys(frame);
	    for (var i = 0; i < keyList.length; i++) {
            if (keyList[i].getVelocity() < 0.5 && keyList[i].getVelocity() > 0) {
			    piano.noteOn(0, this.BASE_NOTE_INDEX + keyList[i].getKeyIndex(), (keyList[i].getVelocity() + 0.5) * 127, 0);
            }
	    }
	}
}<|endoftext|>


<commit_before>/* global document beforeEach afterEach describe it */
import { expect } from 'chai';
import sinon from 'sinon';

const proxyquireStrict = require('proxyquire').noCallThru();

describe('Toggle init', () => {
  const domSliderStub = {};

  domSliderStub.slideToggle = () => {};

  const {
    toggle,
  } = proxyquireStrict('../components/toggle', { 'dom-slider': domSliderStub });

  afterEach(() => {
    document.querySelectorAll.restore();
  });

  it('should call getElements once with the correct selector', () => {
    sinon.spy(document, 'querySelectorAll');
    toggle();
    expect(document.querySelectorAll.getCall(0).args[0]).eql('[data-sprk-toggle="container"]');
  });
});

describe('Toggle tests', () => {
  let container;
  let trigger;
  let triggerAccordion;
  let content;
  let contentAccordion;
  let icon;
  let containerAccordion;
  let iconAccordion;
  let iconAccordionUseElement;
  const domSliderStub = {};

  const {
    toggleIconType,
    toggleAriaExpanded,
    handleToggleClick,
    bindToggleUIEvents,
  } = proxyquireStrict('../components/toggle', { 'dom-slider': domSliderStub });

  beforeEach(() => {
    container = document.createElement('div');
    container.setAttribute('data-sprk-toggle', 'container');

    containerAccordion = document.createElement('li');
    containerAccordion.setAttribute('data-sprk-toggle', 'container');
    containerAccordion.classList.add('sprk-c-Accordion__item');

    trigger = document.createElement('a');
    trigger.setAttribute('data-sprk-toggle', 'trigger');
    trigger.setAttribute('aria-expanded', 'false');
    trigger.textContent = 'My Toggle Link Text';
    trigger.classList.add('sprk-b-TypeBodyThree', 'sprk-b-Link', 'sprk-b-Link--standalone');

    triggerAccordion = document.createElement('a');
    triggerAccordion.setAttribute('data-sprk-toggle', 'trigger');
    triggerAccordion.setAttribute('data-sprk-toggle-type', 'accordion');
    triggerAccordion.setAttribute('aria-expanded', 'false');
    triggerAccordion.textContent = 'My Toggle Accordion Link Text';
    triggerAccordion.classList.add(
      'sprk-b-TypeBodyThree',
      'sprk-b-Link',
      'sprk-b-Link--standalone',
    );

    content = document.createElement('div');
    content.setAttribute('data-sprk-toggle', 'content');
    content.textContent = 'This is the toggle content..';
    content.classList.add('sprk-b-TypeBodyFour', 'sprk-u-pts');

    contentAccordion = document.createElement('p');
    contentAccordion.setAttribute('data-sprk-toggle', 'content');
    contentAccordion.textContent = 'This is the toggle accordion content.';
    contentAccordion.classList.add('sprk-b-TypeBodyTwo', 'sprk-c-Accordion__content');
    contentAccordion.style.display = 'none';
    contentAccordion.slideToggle = () => {};

    icon = document.createElement('svg');
    icon.setAttribute('data-sprk-toggle', 'icon');

    iconAccordion = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    iconAccordion.classList.add('sprk-c-Icon');
    iconAccordion.setAttribute('data-sprk-toggle', 'icon');

    iconAccordionUseElement = document.createElement('use');
    iconAccordionUseElement.setAttribute('xlink:href', '#chevron-down');
    iconAccordionUseElement.setAttribute('data-sprk-toggle', 'svgUse');
    iconAccordion.appendChild(iconAccordionUseElement);

    trigger.append(icon);
    container.append(trigger);
    container.append(content);

    triggerAccordion.append(iconAccordion);
    containerAccordion.append(triggerAccordion);
    containerAccordion.append(contentAccordion);
  });

  it('should toggle aria-expanded attribute for toggles', () => {
    toggleAriaExpanded(trigger);
    expect(trigger.hasAttribute('aria-expanded')).eql(true);
  });

  it('should add aria-expanded attribute set to false if not found on trigger', () => {
    triggerAccordion.removeAttribute('aria-expanded');
    toggleAriaExpanded(triggerAccordion);
    expect(triggerAccordion.getAttribute('aria-expanded')).eql('false');
  });

  it('should toggle aria-expanded attribute to false', () => {
    toggleAriaExpanded(trigger); // Is true, makes it false
    toggleAriaExpanded(trigger); // Is false, makes it true
    expect(trigger.hasAttribute('aria-expanded')).eql(true);
  });

  it('should toggle accordion item open', () => {
    contentAccordion.slideToggle = () => new Promise((resolve) => { resolve('cats'); });
    handleToggleClick(contentAccordion, iconAccordion, iconAccordionUseElement, triggerAccordion);
    expect(containerAccordion.classList.contains('sprk-c-Accordion__item--open')).eql(true);
    expect(iconAccordionUseElement.getAttribute('xlink:href')).eql('#chevron-down-circle-filled');
  });

  it('should toggle accordion icon open', () => {
    toggleIconType(iconAccordion, iconAccordionUseElement, 'chevron-down-circle', 'chevron-down-circle-filled');
    expect(iconAccordionUseElement.getAttribute('xlink:href')).eql('#chevron-down-circle-filled');
  });

  it('should add listener to toggle trigger', () => {
    sinon.spy(trigger, 'addEventListener');
    bindToggleUIEvents(container);
    expect(trigger.addEventListener.getCall(0).args[0]).eql('click');
  });

  it('should disable clicks until after slide toggle animation runs', () => {
    trigger.click();
    setTimeout(() => {
      expect(trigger.style.pointerEvents).eql('none');
    }, 100);
  });

  it('should enable clicks after slide toggle animation runs', () => {
    trigger.click();
    setTimeout(() => {
      expect(trigger.style.pointerEvents).eql('auto');
    }, 300);
  });
});
<commit_msg>bring %stmts to 100%<commit_after>/* global document beforeEach afterEach describe it event window */
import { expect } from 'chai';
import sinon from 'sinon';

const proxyquireStrict = require('proxyquire').noCallThru();

describe('Toggle init', () => {
  const domSliderStub = {};
  domSliderStub.slideToggle = () => {};

  const {
    toggle,
  } = proxyquireStrict('../components/toggle', { 'dom-slider': domSliderStub });

  afterEach(() => {
    document.querySelectorAll.restore();
  });

  it('should call getElements once with the correct selector', () => {
    sinon.spy(document, 'querySelectorAll');
    toggle();
    expect(document.querySelectorAll.getCall(0).args[0]).eql('[data-sprk-toggle="container"]');
  });
});

describe('Toggle tests', () => {
  let container;
  let trigger;
  let triggerAccordion;
  let content;
  let contentAccordion;
  let icon;
  let containerAccordion;
  let iconAccordion;
  let iconAccordionUseElement;
  const domSliderStub = {};

  const {
    toggleIconType,
    toggleAriaExpanded,
    handleToggleClick,
    bindToggleUIEvents,
  } = proxyquireStrict('../components/toggle', { 'dom-slider': domSliderStub });

  beforeEach(() => {
    container = document.createElement('div');
    container.setAttribute('data-sprk-toggle', 'container');

    containerAccordion = document.createElement('li');
    containerAccordion.setAttribute('data-sprk-toggle', 'container');
    containerAccordion.classList.add('sprk-c-Accordion__item');

    trigger = document.createElement('a');
    trigger.setAttribute('data-sprk-toggle', 'trigger');
    trigger.setAttribute('aria-expanded', 'false');
    trigger.textContent = 'My Toggle Link Text';
    trigger.classList.add('sprk-b-TypeBodyThree', 'sprk-b-Link', 'sprk-b-Link--standalone');

    triggerAccordion = document.createElement('a');
    triggerAccordion.setAttribute('data-sprk-toggle', 'trigger');
    triggerAccordion.setAttribute('data-sprk-toggle-type', 'accordion');
    triggerAccordion.setAttribute('aria-expanded', 'false');
    triggerAccordion.textContent = 'My Toggle Accordion Link Text';
    triggerAccordion.classList.add(
      'sprk-b-TypeBodyThree',
      'sprk-b-Link',
      'sprk-b-Link--standalone',
    );

    content = document.createElement('div');
    content.setAttribute('data-sprk-toggle', 'content');
    content.textContent = 'This is the toggle content..';
    content.classList.add('sprk-b-TypeBodyFour', 'sprk-u-pts');

    contentAccordion = document.createElement('p');
    contentAccordion.setAttribute('data-sprk-toggle', 'content');
    contentAccordion.textContent = 'This is the toggle accordion content.';
    contentAccordion.classList.add('sprk-b-TypeBodyTwo', 'sprk-c-Accordion__content');
    contentAccordion.style.display = 'none';
    contentAccordion.slideToggle = () => {};

    icon = document.createElement('svg');
    icon.setAttribute('data-sprk-toggle', 'icon');

    iconAccordion = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    iconAccordion.classList.add('sprk-c-Icon');
    iconAccordion.setAttribute('data-sprk-toggle', 'icon');

    iconAccordionUseElement = document.createElement('use');
    iconAccordionUseElement.setAttribute('xlink:href', '#chevron-down');
    iconAccordionUseElement.setAttribute('data-sprk-toggle', 'svgUse');
    iconAccordion.appendChild(iconAccordionUseElement);

    trigger.append(icon);
    container.append(trigger);
    container.append(content);

    triggerAccordion.append(iconAccordion);
    containerAccordion.append(triggerAccordion);
    containerAccordion.append(contentAccordion);
  });

  it('should toggle aria-expanded attribute for toggles', () => {
    toggleAriaExpanded(trigger);
    expect(trigger.hasAttribute('aria-expanded')).eql(true);
  });

  it('should add aria-expanded attribute set to false if not found on trigger', () => {
    triggerAccordion.removeAttribute('aria-expanded');
    toggleAriaExpanded(triggerAccordion);
    expect(triggerAccordion.getAttribute('aria-expanded')).eql('false');
  });

  it('should toggle aria-expanded attribute to false', () => {
    toggleAriaExpanded(trigger); // Is true, makes it false
    toggleAriaExpanded(trigger); // Is false, makes it true
    expect(trigger.hasAttribute('aria-expanded')).eql(true);
  });

  it('should toggle accordion item open', () => {
    contentAccordion.slideToggle = () => new Promise((resolve) => { resolve('cats'); });
    handleToggleClick(contentAccordion, iconAccordion, iconAccordionUseElement, triggerAccordion);
    expect(containerAccordion.classList.contains('sprk-c-Accordion__item--open')).eql(true);
    expect(iconAccordionUseElement.getAttribute('xlink:href')).eql('#chevron-down-circle-filled');
  });

  it('should toggle accordion icon open', () => {
    toggleIconType(iconAccordion, iconAccordionUseElement, 'chevron-down-circle', 'chevron-down-circle-filled');
    expect(iconAccordionUseElement.getAttribute('xlink:href')).eql('#chevron-down-circle-filled');
  });

  it('should add listener to toggle trigger', () => {
    sinon.spy(trigger, 'addEventListener');
    bindToggleUIEvents(container);
    expect(trigger.addEventListener.getCall(0).args[0]).eql('click');
  });

  it('should add listener to Accordion toggle trigger', () => {
    sinon.spy(triggerAccordion, 'addEventListener');
    bindToggleUIEvents(containerAccordion);
    expect(triggerAccordion.addEventListener.getCall(0).args[0]).eql('click');
  });

  it('should show details when clicked', () => {
    bindToggleUIEvents(containerAccordion);
    const event = new window.Event('click');
    triggerAccordion.dispatchEvent(event);
    expect(containerAccordion.classList.contains('sprk-c-Accordion__item--open')).eql(true);
  });

  it('should disable clicks until after slide toggle animation runs', () => {
    bindToggleUIEvents(container);
    setTimeout(() => {
      expect(trigger.style.pointerEvents).eql('none');
    }, 100);
  });

  it('should enable clicks after slide toggle animation runs', () => {
    const event = new window.Event('click');
    trigger.dispatchEvent(event);
    setTimeout(() => {
      expect(trigger.style.pointerEvents).eql('auto');
    }, 300);
  });
});
<|endoftext|>


<commit_before>// Copyright 2014-2020, University of Colorado Boulder

/**
 * A Multilink is an instance that can be used to link to multiple properties.  It is very similar to a DerivedProperty,
 * but has no value and does not conform to the Property API because it is intended for use with callbacks that do not
 * compute a value.  Multilink should not be created through calling its constructor directly, but through the
 * Property.multilink and Property.lazyMultilink functions.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import inherit from '../../phet-core/js/inherit.js';
import axon from './axon.js';

/**
 * @param {Property[]} dependencies
 * @param {function} callback function that expects args in the same order as dependencies
 * @param {boolean} [lazy] Optional parameter that can be set to true if this should be a lazy multilink (no immediate callback)
 * @constructor
 */
function Multilink( dependencies, callback, lazy ) {

  this.dependencies = dependencies; // @private

  assert && assert( dependencies.length === _.uniq( dependencies ).length, 'duplicate dependencies' );

  const self = this;

  // @private Keep track of listeners so they can be detached
  this.dependencyListeners = [];

  // When a dependency value changes, update the list of dependencies and call back to the callback
  dependencies.forEach( function( dependency, i ) {
    const listener = function( value ) {

      // don't call listener if this Multilink has been disposed, see https://github.com/phetsims/axon/issues/192
      if ( !self.isDisposed ) {
        callback.apply( null, dependencies.map( function( property ) {return property.get();} ) );
      }
    };
    self.dependencyListeners.push( listener );
    dependency.lazyLink( listener, {

      // All other dependencies should undefer (taking deferred value) before this dependency notifies. This is
      // crucial to prevent this Multilink callback from firing with intermediate (buggy) states before all dependencies
      // have taken their final value.
      phetioDependencies: _.without( dependencies, dependency )
    } );
  } );

  // Send initial call back but only if we are non-lazy
  if ( !lazy ) {
    callback.apply( null, dependencies.map( function( property ) {return property.get();} ) );
  }

  // @private - whether the Multilink has been disposed
  this.isDisposed = false;
}

axon.register( 'Multilink', Multilink );

inherit( Object, Multilink, {

  // @public
  dispose: function() {
    assert && assert( this.dependencies, 'A Multilink cannot be disposed twice.' );

    // Unlink from dependent properties
    for ( let i = 0; i < this.dependencies.length; i++ ) {
      const dependency = this.dependencies[ i ];
      if ( !dependency.isDisposed ) {
        dependency.unlink( this.dependencyListeners[ i ] );
      }
    }
    this.dependencies = null;
    this.dependencyListeners = null;
    this.isDisposed = true;
  }
} );

export default Multilink;<commit_msg>fixes https://github.com/phetsims/axon/issues/302<commit_after>// Copyright 2014-2020, University of Colorado Boulder

/**
 * Multilink is used to link to multiple properties.  It is very similar to a DerivedProperty, but has no value and
 * does not conform to the Property API because it is intended for use with callbacks that do not compute a value.
 *
 * For situations where a reference to the Multilink is not needed (for calling dispose), use convenience method
 * Property.multilink or Property.lazyLink to avoid these types of lint errors:
 *
 * // lint error: Do not use 'new' for side effects (no-new)
 * new Multilink( ... );
 *
 * // lint error: 'multilink' is assigned a value but never used (no-unused-vars)
 * const multilink = new Multilink( ... );
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import inherit from '../../phet-core/js/inherit.js';
import axon from './axon.js';

/**
 * @param {Property[]} dependencies
 * @param {function} callback function that expects args in the same order as dependencies
 * @param {boolean} [lazy] Optional parameter that can be set to true if this should be a lazy multilink (no immediate callback)
 * @constructor
 */
function Multilink( dependencies, callback, lazy ) {

  this.dependencies = dependencies; // @private

  assert && assert( dependencies.length === _.uniq( dependencies ).length, 'duplicate dependencies' );

  const self = this;

  // @private Keep track of listeners so they can be detached
  this.dependencyListeners = [];

  // When a dependency value changes, update the list of dependencies and call back to the callback
  dependencies.forEach( function( dependency, i ) {
    const listener = function( value ) {

      // don't call listener if this Multilink has been disposed, see https://github.com/phetsims/axon/issues/192
      if ( !self.isDisposed ) {
        callback.apply( null, dependencies.map( function( property ) {return property.get();} ) );
      }
    };
    self.dependencyListeners.push( listener );
    dependency.lazyLink( listener, {

      // All other dependencies should undefer (taking deferred value) before this dependency notifies. This is
      // crucial to prevent this Multilink callback from firing with intermediate (buggy) states before all dependencies
      // have taken their final value.
      phetioDependencies: _.without( dependencies, dependency )
    } );
  } );

  // Send initial call back but only if we are non-lazy
  if ( !lazy ) {
    callback.apply( null, dependencies.map( function( property ) {return property.get();} ) );
  }

  // @private - whether the Multilink has been disposed
  this.isDisposed = false;
}

axon.register( 'Multilink', Multilink );

inherit( Object, Multilink, {

  // @public
  dispose: function() {
    assert && assert( this.dependencies, 'A Multilink cannot be disposed twice.' );

    // Unlink from dependent properties
    for ( let i = 0; i < this.dependencies.length; i++ ) {
      const dependency = this.dependencies[ i ];
      if ( !dependency.isDisposed ) {
        dependency.unlink( this.dependencyListeners[ i ] );
      }
    }
    this.dependencies = null;
    this.dependencyListeners = null;
    this.isDisposed = true;
  }
} );

export default Multilink;<|endoftext|>