<commit_before>/* jspsych-staircase-categorize.js
 *	Josh de Leeuw, Sep. 2013
 *
 *
 *
 */

(function($) {
    jsPsych.staircase_categorize = (function() {

        var plugin = {};

        plugin.create = function(params) {
            var trials = []; // everything is a single trial, since it is unknown how many presentations it will take.

            trials[0] = {};
            trials[0].type = "staircase_categorize";
            // items structure is four nested arrays. top level array is separate staircases that should be interleaved.
            // second level array is difficulty. third arrays are sets of options. inner most array are stimuli of equivalent
            // difficulty and category.
            trials[0].items = params.items;
            // two level nested array. top level is separate staircases.
            // second (inner) level is list of key responses in order that stimuli are ordered for third level of items array.
            trials[0].choices = params.choices;
            trials[0].n_up = params.n_up || 1;
            trials[0].n_down = params.n_down || 1;
            trials[0].n_turns = params.n_turns || 10;
            trials[0].n_turns_average = params.n_turns_average || 6;
            // timing
            trials[0].timing_post_trial = params.timing_post_trial || 1000; // default 1000ms between trials.
            trials[0].timing_item = params.timing_item || 1000;

            // optional parameters
            if (params.data !== undefined) {
                trials[0].data = params.data;
            }
            if (params.prompt !== undefined) {
                trials[0].prompt = params.prompt;
            }

            return trials;
        };

        plugin.trial = function(display_element, block, trial, part) {

            var interleaver = new StaircaseInterleaver(display_element, trial, block);
            interleaver.start();
        };

        function StaircaseInterleaver(display_element, trial, block) {
            this.staircases = [];

            for(var i=0; i<trial.items.length; i++)
            {
                var s_trial = $.extend(true, {}, trial);
                s_trial.items = s_trial.items[i];
                var staircase = new StaircaseController(display_element, s_trial, this, block);
                this.staircases.push(staircase);
            }

            this.start = function() {
                this.next_trial();
            };

            this.next_trial = function() {
                var eligible_staircases = [];
                for (var i = 0; i < this.staircases.length; i++) {
                    if (this.staircases[i].is_complete === false) {
                        eligible_staircases.push(this.staircases[i]);
                    }
                }

                if (eligible_staircases.length === 0) {
                    this.finish();
                }
                else {
                    var which_staircase = Math.floor(Math.random() * eligible_staircases.length);

                    eligible_staircases[which_staircase].next_trial();
                }
            };

            this.finish = function()
            {
                // end the whole staircase process
                block.next();
            }
        }

        function StaircaseController(display_element, trial, interleaver, block) {
            this.current_item = 0;
            this.total_turns = 0;
            this.current_direction = 1;
            this.turns = [];
            this.consecutive_correct = 0;
            this.consecutive_incorrect = 0;
            this.items = trial.items;
            this.is_complete = false;
            this.interleaver = interleaver;

            this.next_trial = function() {

                var item_set = this.items[this.current_item];

                // pick which category to show randomly
                var which_category = Math.floor(Math.random()*item_set.length);

                // pick which exemplar to show randomly
                var which_exemplar = Math.floor(Math.random()*item_set[which_category].length);

                var this_trial = {
                    a_path: item_set[which_category][which_exemplar],
                    choices: trial.choices,
                    correct_response: trial.choices[which_category]
                };

                this.do_trial(this_trial);

            };

            this.finish_trial = function(correct) {
                // change the counters
                if (correct) {
                    this.consecutive_incorrect = 0;
                    this.consecutive_correct++;
                }
                else {
                    this.consecutive_incorrect++;
                    this.consecutive_correct = 0;
                }

                var new_direction;

                if (correct) {
                    if (this.consecutive_correct >= trial.n_up) {
                        new_direction = 1; // make it harder
                        this.consecutive_correct = 0; // reset this because we changed difficulty.
                    }
                    else {
                        // do the same
                        new_direction = 0;
                    }
                }
                else {
                    if (this.consecutive_incorrect >= trial.n_down) {
                        // make it easier
                        new_direction = -1;
                        this.consecutive_incorrect = 0; // reset because we changed difficulty
                    }
                    else {
                        // do the same
                        new_direction = 0;
                    }
                }

                // if there is a turn, add it.
                if (new_direction !== 0) {
                    if (this.current_direction != new_direction) {
                        this.turns.push(this.current_item);
                    }
                    this.current_direction = new_direction;
                }

                // check if we are done
                if (this.turns.length >= trial.n_turns ){
                    this.is_complete = true;
                }

                // now figure out what the next item is
                this.current_item = this.current_item + new_direction;
                if (this.current_item < 0) {
                    this.current_item = 0;
                }
                if (this.current_item >= trial.items.length) {
                    this.current_item = trial.items.length - 1;
                }

                // tell the interleaver that we are done
                this.interleaver.next_trial();
            };

            this.do_trial = function(this_trial) {

                var trial_complete = false;

                // show image
                display_element.append($('<img>', {
                    "src": this_trial.a_path,
                    "class": 'staircase-categorize'
                }));

                if (trial.prompt) {
                    display_element.append(trial.prompt);
                }

                var startTime = (new Date()).getTime();

                // set timer to remove image
                if (trial.timing_item > 0) {
                    setTimeout(function() {
                        if (!trial_complete) {
                            $('.staircase-categorize').css('visibility', 'hidden');
                        }
                    }, trial.timing_item);
                }

                var resp_func = function(e) {

                    var flag = false;
                    var correct = false;

                    if ($.inArray(e.which, this_trial.choices) > -1) {
                        flag = true;
                        correct = (e.which == this_trial.correct_response);
                    }

                    if (flag) {
                        var endTime = (new Date()).getTime();
                        var rt = (endTime - startTime);
                        var t_idx = block.data.length;

                        var trial_data = {
                            "trial_type": "staircase-categorize",
                            "trial_index": t_idx,
                            "rt": rt,
                            "correct": correct,
                            "a_path": trial.a_path,
                            "staircase_index": this.current_item,
                            "staircase_direction": this.current_direction,
                            "key_press": e.which
                        };
                        block.data.push($.extend({},trial_data,trial.data));
                        $(document).unbind('keyup', resp_func);
                        display_element.html(''); // remove all
                        trial_complete = true;
                        setTimeout(function() {
                            e.data.iterator_object.finish_trial(correct);
                        }, trial.timing_post_trial);
                    }
                }
                $(document).keyup({iterator_object: this}, resp_func);
            }
        }

        return plugin;
    })();
})(jQuery);
<commit_msg>fixing bugs in staircase-categorize<commit_after>/* jspsych-staircase-categorize.js
 *	Josh de Leeuw, Sep. 2013
 *
 *
 *
 */

(function($) {
    jsPsych.staircase_categorize = (function() {

        var plugin = {};

        plugin.create = function(params) {
            var trials = []; // everything is a single trial, since it is unknown how many presentations it will take.

            trials[0] = {};
            trials[0].type = "staircase_categorize";
            // items structure is four nested arrays. top level array is separate staircases that should be interleaved.
            // second level array is difficulty. third arrays are sets of options. inner most array are stimuli of equivalent
            // difficulty and category.
            trials[0].items = params.items;
            // two level nested array. top level is separate staircases.
            // second (inner) level is list of key responses in order that stimuli are ordered for third level of items array.
            trials[0].choices = params.choices;
            trials[0].n_up = params.n_up || 1;
            trials[0].n_down = params.n_down || 1;
            trials[0].n_turns = params.n_turns || 10;
            trials[0].n_turns_average = params.n_turns_average || 6;
            // timing
            trials[0].timing_post_trial = params.timing_post_trial || 1000; // default 1000ms between trials.
            trials[0].timing_item = params.timing_item || 1000;

            // optional parameters
            if (params.data !== undefined) {
                trials[0].data = params.data;
            }
            if (params.prompt !== undefined) {
                trials[0].prompt = params.prompt;
            }

            return trials;
        };

        plugin.trial = function(display_element, block, trial, part) {

            var interleaver = new StaircaseInterleaver(display_element, trial, block);
            interleaver.start();
        };

        function StaircaseInterleaver(display_element, trial, block) {
            this.staircases = [];

            for(var i=0; i<trial.items.length; i++)
            {
                var s_trial = $.extend(true, {}, trial);
                s_trial.items = s_trial.items[i];
                s_trial.choices = s_trial.choices[i];
                var staircase = new StaircaseController(display_element, s_trial, this, block);
                this.staircases.push(staircase);
            }

            this.start = function() {
                this.next_trial();
            };

            this.next_trial = function() {
                var eligible_staircases = [];
                for (var i = 0; i < this.staircases.length; i++) {
                    if (this.staircases[i].is_complete === false) {
                        eligible_staircases.push(this.staircases[i]);
                    }
                }

                if (eligible_staircases.length === 0) {
                    this.finish();
                }
                else {
                    var which_staircase = Math.floor(Math.random() * eligible_staircases.length);

                    eligible_staircases[which_staircase].next_trial();
                }
            };

            this.finish = function()
            {
                // end the whole staircase process
                block.next();
            }
        }

        function StaircaseController(display_element, trial, interleaver, block) {
            this.current_item = 0;
            this.total_turns = 0;
            this.current_direction = 1;
            this.turns = [];
            this.consecutive_correct = 0;
            this.consecutive_incorrect = 0;
            this.items = trial.items;
            this.is_complete = false;
            this.interleaver = interleaver;

            this.next_trial = function() {

                var item_set = this.items[this.current_item];

                // pick which category to show randomly
                var which_category = Math.floor(Math.random()*item_set.length);

                // pick which exemplar to show randomly
                var which_exemplar = Math.floor(Math.random()*item_set[which_category].length);

                var this_trial = {
                    a_path: item_set[which_category][which_exemplar],
                    choices: trial.choices,
                    correct_response: trial.choices[which_category]
                };

                this.do_trial(this_trial);

            };

            this.finish_trial = function(correct) {
                // change the counters
                if (correct) {
                    this.consecutive_incorrect = 0;
                    this.consecutive_correct++;
                }
                else {
                    this.consecutive_incorrect++;
                    this.consecutive_correct = 0;
                }

                var new_direction;

                if (correct) {
                    if (this.consecutive_correct >= trial.n_up) {
                        new_direction = 1; // make it harder
                        this.consecutive_correct = 0; // reset this because we changed difficulty.
                    }
                    else {
                        // do the same
                        new_direction = 0;
                    }
                }
                else {
                    if (this.consecutive_incorrect >= trial.n_down) {
                        // make it easier
                        new_direction = -1;
                        this.consecutive_incorrect = 0; // reset because we changed difficulty
                    }
                    else {
                        // do the same
                        new_direction = 0;
                    }
                }

                // if there is a turn, add it.
                if (new_direction !== 0) {
                    if (this.current_direction != new_direction) {
                        this.turns.push(this.current_item);
                    }
                    this.current_direction = new_direction;
                }

                // check if we are done
                if (this.turns.length >= trial.n_turns ){
                    this.is_complete = true;
                }

                // now figure out what the next item is
                this.current_item = this.current_item + new_direction;
                if (this.current_item < 0) {
                    this.current_item = 0;
                }
                if (this.current_item >= trial.items.length) {
                    this.current_item = trial.items.length - 1;
                }

                // tell the interleaver that we are done
                this.interleaver.next_trial();
            };

            this.do_trial = function(this_trial) {

                var trial_complete = false;

                // show image
                display_element.append($('<img>', {
                    "src": this_trial.a_path,
                    "class": 'staircase-categorize'
                }));

                if (trial.prompt) {
                    display_element.append(trial.prompt);
                }

                var startTime = (new Date()).getTime();

                // set timer to remove image
                if (trial.timing_item > 0) {
                    setTimeout(function() {
                        if (!trial_complete) {
                            $('.staircase-categorize').css('visibility', 'hidden');
                        }
                    }, trial.timing_item);
                }

                var resp_func = function(e) {

                    var flag = false;
                    var correct = false;

                    if ($.inArray(e.which, this_trial.choices) > -1) {
                        flag = true;
                        correct = (e.which == this_trial.correct_response);
                    }

                    if (flag) {
                        var endTime = (new Date()).getTime();
                        var rt = (endTime - startTime);
                        var t_idx = block.data.length;

                        var trial_data = {
                            "trial_type": "staircase-categorize",
                            "trial_index": t_idx,
                            "rt": rt,
                            "correct": correct,
                            "a_path": trial.a_path,
                            "staircase_index": this.current_item,
                            "staircase_direction": this.current_direction,
                            "key_press": e.which
                        };
                        block.data.push($.extend({},trial_data,trial.data));
                        $(document).unbind('keyup', resp_func);
                        display_element.html(''); // remove all
                        trial_complete = true;
                        setTimeout(function() {
                            e.data.iterator_object.finish_trial(correct);
                        }, trial.timing_post_trial);
                    }
                }
                $(document).keyup({iterator_object: this}, resp_func);
            }
        }

        return plugin;
    })();
})(jQuery);
<|endoftext|>


<commit_before>var nano = require('./database');
var Cache = require('./cache');

// Grab Steam DB
var Games = nano.db.use('steam');

// Update the indexes
var indexes = {
    /**
     * Total achievement count index
     * @param doc {{}}
     */
    achievements: {
        views: {
            achievements: {
                map: function(doc){
                    emit(typeof doc.achievements !== 'undefined' ? doc.achievements.total : 0,null);
                }
            }
        },
        indexes: {
            achievements: {
                analyzer: 'standard',
                index: function(doc){
                    index('achievements',typeof doc.achievements !== 'undefined' ? doc.achievements.total : 0);
                }
            }
        }
    },

    /**
     * Game has demo or not
     * @param doc {{}}
     */
    demo: {
        views: {
            demo: {
                map: function(doc){
                    emit(typeof doc.demos === 'object',null);
                }
            }
        },
        indexes: {
            demo: {
                analyzer: 'standard',
                index: function(doc){
                    index('demo',typeof doc.demos === 'object');
                }
            }
        }
    },

    /**
     * Categories types index by id
     * @param doc {{}}
     */
    categoryId: {
        views: {
            categoryId: {
                map: function(doc){
                    if(typeof doc.categories !== 'undefined'){
                        var i = 0;
                        var len = doc.categories.length;

                        while(i < len){
                            var category = doc.categories[i];
                            emit(parseInt(category.id,10),null);
                            i++;
                        }
                    }
                }
            }
        },
        indexes: {
            categoryId: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.categories !== 'undefined'){
                        var i = 0;
                        var len = doc.categories.length;

                        while(i < len){
                            var category = doc.categories[i];
                            index('categoryId',parseInt(category.id,10));
                            i++;
                        }
                    }
                }
            }
        }
    },

    /**
     * Categories types index by name
     * @param doc {{}}
     */
    categoryName: {
        views: {
            categoryName: {
                map: function(doc){
                    if(typeof doc.categories !== 'undefined'){
                        var i = 0;
                        var len = doc.categories.length;

                        while(i < len){
                            var category = doc.categories[i];
                            emit(category.description,null);
                            i++;
                        }
                    }
                }
            }
        },
        indexes: {
            categoryName: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.categories !== 'undefined'){
                        var i = 0;
                        var len = doc.categories.length;

                        while(i < len){
                            var category = doc.categories[i];
                            index('categoryName',category.description);
                            i++;
                        }
                    }
                }
            }
        }
    },

    /**
     * Developers index
     * @param doc {{}}
     */
    developers: {
        views: {
            developers: {
                map: function(doc){
                    if(typeof doc.developers !== 'undefined'){
                        var i = 0;
                        var len = doc.developers.length;

                        while(i < len){
                            emit(doc.developers[i],null);
                            i++;
                        }
                    }
                }
            }
        },
        indexes: {
            developers: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.developers !== 'undefined'){
                        var i = 0;
                        var len = doc.developers.length;

                        while(i < len){
                            index('developer',doc.developers[i]);
                            i++;
                        }
                    }
                }
            }
        }
    },

    /**
     * Genres types index by id
     * @param doc {{}}
     */
    genreId: {
        views: {
            genreId: {
                map: function(doc){
                    if(typeof doc.genres !== 'undefined'){
                        var i = 0;
                        var len = doc.genres.length;
                        while(i < len){
                            var genre = doc.genres[i];
                            emit(parseInt(genre.id,10),null);
                            i++;
                        }
                    }
                }
            }
        },
        indexes: {
            genreId: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.genres !== 'undefined'){
                        var i = 0;
                        var len = doc.genres.length;
                        while(i < len){
                            var genre = doc.genres[i];
                            index('genreId', parseInt(genre.id,10));
                            i++;
                        }
                    }
                }
            }
        }
    },

    /**
     * Genres types index by name
     * @param doc {{}}
     */
    genreName: {
        views: {
            genreName: {
                map: function(doc){
                    if(typeof doc.genres !== 'undefined'){
                        var i = 0;
                        var len = doc.genres.length;
                        while(i < len){
                            var genre = doc.genres[i];
                            emit(genre.description,null);
                            i++;
                        }
                    }
                }
            }
        },
        indexes: {
            genreName: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.genres !== 'undefined'){
                        var i = 0;
                        var len = doc.genres.length;
                        while(i < len){
                            var genre = doc.genres[i];
                            index('genreName', genre.description);
                            i++;
                        }
                    }
                }
            }
        }
    },

    /**
     * Metacritic index
     * @param doc {{}}
     */
    metacritic: {
        views: {
            metacritic: {
                map: function(doc){
                    if(typeof doc.metacritic !== 'undefined'){
                        emit(game.metacritic.score,null);
                    }
                }
            }
        },
        indexes: {
            metacritic: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.metacritic !== 'undefined'){
                        index('metacritic',game.metacritic.score);
                    }
                }
            }
        }
    },

    /**
     * Platform index
     * @param doc {{}}
     */
    platform: {
        views: {
            platform: {
                map: function(doc){
                    if(typeof doc.platforms === 'object'){
                        for(var platform in doc.platforms){
                            emit(doc.platforms[platform],null);
                        }
                    }
                }
            }
        },
        indexes: {
            platform: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.platforms === 'object'){
                        for(var platform in doc.platforms){
                            index('platform',doc.platforms[platform]);
                        }
                    }
                }
            }
        }
    },

    /**
     * Publishers index
     * @param doc {{}}
     */
    publishers: {
        views: {
            publishers: {
                map: function(doc){
                    if(typeof doc.publishers === 'object'){
                        var i = 0;
                        var len = doc.publishers.length;

                        while(i < len){
                            emit(doc.publishers[i],null);
                            i++;
                        }
                    }
                }
            }
        },
        indexes: {
            publishers: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.publishers === 'object'){
                        var i = 0;
                        var len = doc.publishers.length;

                        while(i < len){
                            index('publisher',doc.publishers[i]);
                            i++;
                        }
                    }
                }
            }
        }
    },

    /**
     * Recommendations count index
     * @param doc {{}}
     */
    recommendations: {
        views: {
            recommendations: {
                map: function(doc){
                    if(typeof doc.recommendations === 'object'){
                        emit(parseInt(doc.recommendations.total,10),null);
                    }
                }
            }
        },
        indexes: {
            recommendations: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.recommendations === 'object'){
                        index('recommendations',parseInt(doc.recommendations.total,10));
                    }
                }
            }
        }
    },

    /**
     * Languages index
     * @param doc {{}}
     */
    languages: {
        views: {
            languages: {
                map: function(doc){
                    if(typeof doc.supported_languages === 'string'){
                        doc.supported_languages.split(',').forEach(function(lang){
                            emit(lang.split('<')[0].trim(),null);
                        });
                    }
                }
            }
        },
        indexes: {
            languages: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.supported_languages === 'string'){
                        doc.supported_languages.split(',').forEach(function(lang){
                            index('language',lang.split('<')[0].trim());
                        });
                    }
                }
            }
        }
    }
};

// Find existing design docs
Games.list({startkey:'_design',endkey:'_e'},function(err,data){
    updateDesignDocs(data);
});

function updateDesignDocs(revisions){
    var rows = {};
    if(revisions && revisions.rows && revisions.rows.length){
        var i = 0;
        var len = revisions.rows.length;
        while(i < len){
            var row = revisions.rows[i];
            rows[row.id] = row.value.rev;
            i++;
        }
    }

    // Build the design documents for bulk insert/update
    var docs = [];
    for(var name in indexes){
        var indexObj = indexes[name];
        indexObj._id = '_design/' + name;
        indexObj.language = 'javascript';

        // Check if document exists so we can specify the revision
        if(rows[indexObj._id]){
            indexObj._rev = rows[indexObj._id];
            delete rows[indexObj._id];
        }

        docs.push(indexObj);
    }

    // Delete any remaining docs
    if(Object.keys(rows).length){
        for(var id in rows){
            docs.push({
                _id: id,
                _rev: rows[id],
                _deleted: true
            });
        }
    }

    // Bulk insert/update/delete the docs
    Games.bulk({docs:docs}, function(err){
        if(err){
            console.log('Failed updating design docs',err);
        } else {
            console.log('Design docs updated');
        }
    });
}

/**
 * Parses information from a game to build the search indexes
 * @param id number
 * @param game {{}}
 */
exports.parseGame = function(id,game){
    // Fetch head only to see if game already exists
    Games.head('app-'+id,function(err, _, headers){
        // Append current revision if document found
        if(typeof headers !== 'undefined'){
            game._rev = headers.etag.replace(/"/g,'');
        }

        // Insert/update game
        Games.insert(game,'app-'+id,function(err, body){
            if(err){
                console.log('Error inserting item',err);
            } else {
                console.log('Game inserted', id, body.id);
            }

            // Invalidate filter cache
            Cache.remove('filters');
        });
    });
};<commit_msg>Removed forEach for performance<commit_after>var nano = require('./database');
var Cache = require('./cache');

// Grab Steam DB
var Games = nano.db.use('steam');

// Update the indexes
var indexes = {
    /**
     * Total achievement count index
     * @param doc {{}}
     */
    achievements: {
        views: {
            achievements: {
                map: function(doc){
                    emit(typeof doc.achievements !== 'undefined' ? doc.achievements.total : 0,null);
                }
            }
        },
        indexes: {
            achievements: {
                analyzer: 'standard',
                index: function(doc){
                    index('achievements',typeof doc.achievements !== 'undefined' ? doc.achievements.total : 0);
                }
            }
        }
    },

    /**
     * Game has demo or not
     * @param doc {{}}
     */
    demo: {
        views: {
            demo: {
                map: function(doc){
                    emit(typeof doc.demos === 'object',null);
                }
            }
        },
        indexes: {
            demo: {
                analyzer: 'standard',
                index: function(doc){
                    index('demo',typeof doc.demos === 'object');
                }
            }
        }
    },

    /**
     * Categories types index by id
     * @param doc {{}}
     */
    categoryId: {
        views: {
            categoryId: {
                map: function(doc){
                    if(typeof doc.categories !== 'undefined'){
                        var i = 0;
                        var len = doc.categories.length;

                        while(i < len){
                            var category = doc.categories[i];
                            emit(parseInt(category.id,10),null);
                            i++;
                        }
                    }
                }
            }
        },
        indexes: {
            categoryId: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.categories !== 'undefined'){
                        var i = 0;
                        var len = doc.categories.length;

                        while(i < len){
                            var category = doc.categories[i];
                            index('categoryId',parseInt(category.id,10));
                            i++;
                        }
                    }
                }
            }
        }
    },

    /**
     * Categories types index by name
     * @param doc {{}}
     */
    categoryName: {
        views: {
            categoryName: {
                map: function(doc){
                    if(typeof doc.categories !== 'undefined'){
                        var i = 0;
                        var len = doc.categories.length;

                        while(i < len){
                            var category = doc.categories[i];
                            emit(category.description,null);
                            i++;
                        }
                    }
                }
            }
        },
        indexes: {
            categoryName: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.categories !== 'undefined'){
                        var i = 0;
                        var len = doc.categories.length;

                        while(i < len){
                            var category = doc.categories[i];
                            index('categoryName',category.description);
                            i++;
                        }
                    }
                }
            }
        }
    },

    /**
     * Developers index
     * @param doc {{}}
     */
    developers: {
        views: {
            developers: {
                map: function(doc){
                    if(typeof doc.developers !== 'undefined'){
                        var i = 0;
                        var len = doc.developers.length;

                        while(i < len){
                            emit(doc.developers[i],null);
                            i++;
                        }
                    }
                }
            }
        },
        indexes: {
            developers: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.developers !== 'undefined'){
                        var i = 0;
                        var len = doc.developers.length;

                        while(i < len){
                            index('developer',doc.developers[i]);
                            i++;
                        }
                    }
                }
            }
        }
    },

    /**
     * Genres types index by id
     * @param doc {{}}
     */
    genreId: {
        views: {
            genreId: {
                map: function(doc){
                    if(typeof doc.genres !== 'undefined'){
                        var i = 0;
                        var len = doc.genres.length;
                        while(i < len){
                            var genre = doc.genres[i];
                            emit(parseInt(genre.id,10),null);
                            i++;
                        }
                    }
                }
            }
        },
        indexes: {
            genreId: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.genres !== 'undefined'){
                        var i = 0;
                        var len = doc.genres.length;
                        while(i < len){
                            var genre = doc.genres[i];
                            index('genreId', parseInt(genre.id,10));
                            i++;
                        }
                    }
                }
            }
        }
    },

    /**
     * Genres types index by name
     * @param doc {{}}
     */
    genreName: {
        views: {
            genreName: {
                map: function(doc){
                    if(typeof doc.genres !== 'undefined'){
                        var i = 0;
                        var len = doc.genres.length;
                        while(i < len){
                            var genre = doc.genres[i];
                            emit(genre.description,null);
                            i++;
                        }
                    }
                }
            }
        },
        indexes: {
            genreName: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.genres !== 'undefined'){
                        var i = 0;
                        var len = doc.genres.length;
                        while(i < len){
                            var genre = doc.genres[i];
                            index('genreName', genre.description);
                            i++;
                        }
                    }
                }
            }
        }
    },

    /**
     * Metacritic index
     * @param doc {{}}
     */
    metacritic: {
        views: {
            metacritic: {
                map: function(doc){
                    if(typeof doc.metacritic !== 'undefined'){
                        emit(game.metacritic.score,null);
                    }
                }
            }
        },
        indexes: {
            metacritic: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.metacritic !== 'undefined'){
                        index('metacritic',game.metacritic.score);
                    }
                }
            }
        }
    },

    /**
     * Platform index
     * @param doc {{}}
     */
    platform: {
        views: {
            platform: {
                map: function(doc){
                    if(typeof doc.platforms === 'object'){
                        for(var platform in doc.platforms){
                            emit(doc.platforms[platform],null);
                        }
                    }
                }
            }
        },
        indexes: {
            platform: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.platforms === 'object'){
                        for(var platform in doc.platforms){
                            index('platform',doc.platforms[platform]);
                        }
                    }
                }
            }
        }
    },

    /**
     * Publishers index
     * @param doc {{}}
     */
    publishers: {
        views: {
            publishers: {
                map: function(doc){
                    if(typeof doc.publishers === 'object'){
                        var i = 0;
                        var len = doc.publishers.length;

                        while(i < len){
                            emit(doc.publishers[i],null);
                            i++;
                        }
                    }
                }
            }
        },
        indexes: {
            publishers: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.publishers === 'object'){
                        var i = 0;
                        var len = doc.publishers.length;

                        while(i < len){
                            index('publisher',doc.publishers[i]);
                            i++;
                        }
                    }
                }
            }
        }
    },

    /**
     * Recommendations count index
     * @param doc {{}}
     */
    recommendations: {
        views: {
            recommendations: {
                map: function(doc){
                    if(typeof doc.recommendations === 'object'){
                        emit(parseInt(doc.recommendations.total,10),null);
                    }
                }
            }
        },
        indexes: {
            recommendations: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.recommendations === 'object'){
                        index('recommendations',parseInt(doc.recommendations.total,10));
                    }
                }
            }
        }
    },

    /**
     * Languages index
     * @param doc {{}}
     */
    languages: {
        views: {
            languages: {
                map: function(doc){
                    if(typeof doc.supported_languages === 'string'){
                        var languages = doc.supported_languages.split(',');
                        var i = 0;
                        var len = languages.length;
                        while(i < len){
                            var lang = languages[i];
                            emit(lang.split('<')[0].trim(),null);
                            i++;
                        }
                    }
                }
            }
        },
        indexes: {
            languages: {
                analyzer: 'standard',
                index: function(doc){
                    if(typeof doc.supported_languages === 'string'){
                        var languages = doc.supported_languages.split(',');
                        var i = 0;
                        var len = languages.length;
                        while(i < len){
                            var lang = languages[i];
                            emit(lang.split('<')[0].trim(),null);
                            i++;
                        }
                    }
                }
            }
        }
    }
};

// Find existing design docs
Games.list({startkey:'_design',endkey:'_e'},function(err,data){
    updateDesignDocs(data);
});

function updateDesignDocs(revisions){
    var rows = {};
    if(revisions && revisions.rows && revisions.rows.length){
        var i = 0;
        var len = revisions.rows.length;
        while(i < len){
            var row = revisions.rows[i];
            rows[row.id] = row.value.rev;
            i++;
        }
    }

    // Build the design documents for bulk insert/update
    var docs = [];
    for(var name in indexes){
        var indexObj = indexes[name];
        indexObj._id = '_design/' + name;
        indexObj.language = 'javascript';

        // Check if document exists so we can specify the revision
        if(rows[indexObj._id]){
            indexObj._rev = rows[indexObj._id];
            delete rows[indexObj._id];
        }

        docs.push(indexObj);
    }

    // Delete any remaining docs
    if(Object.keys(rows).length){
        for(var id in rows){
            docs.push({
                _id: id,
                _rev: rows[id],
                _deleted: true
            });
        }
    }

    // Bulk insert/update/delete the docs
    Games.bulk({docs:docs}, function(err){
        if(err){
            console.log('Failed updating design docs',err);
        } else {
            console.log('Design docs updated');
        }
    });
}

/**
 * Parses information from a game to build the search indexes
 * @param id number
 * @param game {{}}
 */
exports.parseGame = function(id,game){
    // Fetch head only to see if game already exists
    Games.head('app-'+id,function(err, _, headers){
        // Append current revision if document found
        if(typeof headers !== 'undefined'){
            game._rev = headers.etag.replace(/"/g,'');
        }

        // Insert/update game
        Games.insert(game,'app-'+id,function(err, body){
            if(err){
                console.log('Error inserting item',err);
            } else {
                console.log('Game inserted', id, body.id);
            }

            // Invalidate filter cache
            Cache.remove('filters');
        });
    });
};<|endoftext|>


<commit_before>define([
	"@loader",
	"module",
	"can/view/stache/mustache_core",
	"can/view/parser/parser"
], function(loader, module, mustacheCore, parser){

	function parse(source){
		var template = mustacheCore.cleanLineEndings(source),
			tags = {
				"can-component": true,
				"style": true,
				"template": true,
				"view-model": true,
				"events": true,
				"helpers": true,
				"script": true,
				"can-import": true
			},
			areIn = {},
			texts = {
				style: "",
				"view-model": "",
				events: "",
				helpers: ""
			},
			froms = {};
			types = {},
			attrs = {},
			values = {},
			imports = [],
			ases = {},
			currentTag = "",
			currentAttr = "",
			tagName = "",
			keepToken = function(){
				// We only want to keep the template's tokens.
				return !!areIn.template;
			};

		var intermediate = parser(template, {
			start: function(tagName){
				// Mark when we are inside one of the tags.
				if(tags[tagName]) {
					currentTag = tagName;
					areIn[tagName] = true;
				}
				return keepToken();
			},
			attrStart: function(attrName){
				currentAttr = attrName;
				return keepToken();
			},
			attrEnd: function(attrName){
				currentAttr = "";
				return keepToken();
			},
			attrValue: function(value){
				if(areIn["can-component"] && currentAttr === "tag") {
					tagName = value;
				}
				if(areIn["can-import"] && currentAttr === "from") {
					imports.push(value);
				}
				if(currentAttr === "type" && tags[currentTag]) {
					types[currentTag] = (value+"").replace("text/", "");

					// <script type="events">
					if(currentTag === "script" && tags[value]) {
						currentTag = value;
					}
				} else if(currentAttr === "from" && tags[currentTag]) {
					froms[currentTag] = value;
				}
				attrs[currentAttr] = value;
				return keepToken();
			},
			end: function(tagName){
				if(tagName === "can-import") {
					if(attrs.as) {
						ases[attrs.as] = attrs.from;
					}

					attrs.as = false;
					attrs.from = false;
					areIn["can-import"] = false;
				}
				return keepToken();
			},
			close: function(tagName){
				if(tagName === "can-import") {
					imports.pop();
				}
				if(tags[tagName]) {
					areIn[tagName] = false;
				}
				return keepToken();
			},
			special: keepToken,
			chars: function(char){
				if(texts[currentTag] != null) {
					texts[currentTag] += char;
				}
				return keepToken();
			},
			done: function(){}
		}, true);

		// Remove the leading template noise.
		var idx = 0, cur = intermediate[0];
		while(cur.tokenType !== "end" || (cur.args ? cur.args[0] !== "template" : true)) {
			intermediate.shift();
			cur = intermediate[0];
		}
		// Remove the template end
		intermediate.shift();

		return {
			froms: froms,
			intermediate: intermediate,
			imports: imports,
			tagName: tagName,
			texts: texts,
			types: types
		};
	}

	// Make functions that will define virtual modules
	function makeDefineVirtualModule(loader, load, deps, args){

		function namer(loadName){
			var baseName = loadName.substr(0, loadName.indexOf("!"));

			return function(part, plugin){
				return baseName + "/" + part + (plugin ? ("." + plugin) : "");
			};
		}

		function addresser(loadAddress){
			return function(part, plugin){
				var base = loadAddress + "." + part;
				return base + (plugin ? ("." + plugin) : "");
			};
		}

		var name = namer(load.name);
		var address = addresser(load.address);

		// A function for disposing of modules during live-reload
		var disposeModule = function(moduleName){
			if(loader.has(moduleName))
				loader["delete"](moduleName);
		};
		if(loader.liveReloadInstalled || loader.has("live-reload")) {
			loader.import("live-reload", { name: module.id }).then(function(reload){
				disposeModule = reload.disposeModule || disposeModule;
			});
		}

		return function(defn){
			if(defn.condition) {
				if(defn.arg) {
					// viewModel
					args.push(defn.arg);
				}

				var moduleName = typeof defn.name === "function" ?
					defn.name(name) : name(defn.name);
				var moduleAddress = typeof defn.address === "function" ?
					defn.address(address) : address(defn.address);

				// from="something.js"
				if(defn.from) {
					deps.push(defn.from);
				}

				else if(defn.getLoad) {
					var moduleSource = defn.source();
					return defn.getLoad(moduleName).then(function(newLoad){
						moduleName = newLoad.name || moduleName;

						// For live-reload
						disposeModule(moduleName);

						loader.define(moduleName, moduleSource, {
							metadata: newLoad.metadata,
							address: moduleAddress
						});
						deps.push(moduleName);
					});
				}

				else if(defn.source) {
					deps.push(moduleName);

					if(loader.has(moduleName))
						loader["delete"](moduleName);

					loader.define(moduleName, defn.source, {
						address: address(defn.name)
					});
				}
			}
		}
	}

	function templateDefine(intermediateAndImports){
		var intermediate = intermediateAndImports.intermediate;
		var imports = intermediateAndImports.imports;
		imports.unshift("can/component/component");
		imports.unshift("can/view/stache/stache");

		return "def" + "ine(" + JSON.stringify(imports) + ", function(stache){\n" +
			"\treturn stache(" + JSON.stringify(intermediate) + ");\n" +
			"});";
	}

	function translate(load){
		var result = parse(load.source),
			tagName = result.tagName,
			texts = result.texts,
			types = result.types,
			froms = result.froms,
			deps = ["can/component/component"],
			ases = ["Component"],
			stylePromise;

		var localLoader = loader.localLoader || loader;

		var defineVirtualModule = makeDefineVirtualModule(localLoader, load,
														  deps, ases);

		// Define the template
		defineVirtualModule({
			condition: froms.template || result.intermediate.length,
			arg: "template",
			name: "template",
			from: froms.template,
			source: templateDefine(result)
		});

		// Define the viewModel
		defineVirtualModule({
			condition: froms["view-model"] || texts["view-model"],
			arg: "viewModel",
			name: "view-model",
			from: froms["view-model"],
			source: texts["view-model"]
		});

		// Define events
		defineVirtualModule({
			condition: froms.events || texts.events,
			arg: "events",
			name: "events",
			from: froms.events,
			source: texts.events
		});

		// Define helpers
		defineVirtualModule({
			condition: froms.events || texts.events,
			arg: "helpers",
			name: "helpers",
			from: froms.helpers,
			source: texts.helpers
		});

		// Define the styles
		stylePromise = defineVirtualModule({
			condition: froms.style || texts.style,
			name: function(name){
				return name("style", types.style || "css") + "!"
			},
			address: function(address){
				return address("style", types.style);
			},
			source: function(){
				var styleText = texts.style;
				if(types.style === "less") {
					var styleText = tagName + " {\n" + texts.style + "}\n";
				}
				return styleText;
			},
			getLoad: function(styleName){
				var styleLoad = {};
				var normalizePromise = localLoader.normalize(styleName, load.name);
				var locatePromise = normalizePromise.then(function(name){
					styleName = name;
					styleLoad = { name: name, metadata: {} };
					return localLoader.locate(styleLoad);
				});

				return locatePromise.then(function(){
					return {
						name: styleName,
						metadata: styleLoad.metadata
					};
				});
			}
		}) || Promise.resolve();

		return stylePromise.then(function(){
			return "def" + "ine(" + JSON.stringify(deps) + ", function(" +
				ases.join(", ") + "){\n" +
				"\tvar __interop = function(m){if(m && m['default']) {return m['default'];}else if(m) return m;};\n\n" +
				"\tvar viewModel = __interop(typeof viewModel !== 'undefined' ? viewModel : undefined);\n" +
				"\tvar ComponentConstructor = Component.extend({\n" +
				"\t\ttag: '" + tagName + "',\n" +
				"\t\ttemplate: __interop(typeof template !== 'undefined' ? template : undefined),\n" +
				"\t\tviewModel: viewModel,\n" +
				"\t\tevents: __interop(typeof events !== 'undefined' ? events : undefined),\n" +
				"\t\thelpers: __interop(typeof helpers !== 'undefined' ? helpers : undefined)\n" +
				"\t});\n\n" +
				"\treturn {\n" +
				"\t\tComponent: ComponentConstructor,\n" +
				"\t\tViewModel: ComponentConstructor.Map,\n" +
				"\t\tviewModel: viewModel\n" +
				"\t};\n" +
				"});";
		});

	}

	return {
		translate: translate
	};

});
<commit_msg>Add dependencies to load.metadata.virtualDeps<commit_after>define([
	"@loader",
	"module",
	"can/view/stache/mustache_core",
	"can/view/parser/parser"
], function(loader, module, mustacheCore, parser){

	function parse(source){
		var template = mustacheCore.cleanLineEndings(source),
			tags = {
				"can-component": true,
				"style": true,
				"template": true,
				"view-model": true,
				"events": true,
				"helpers": true,
				"script": true,
				"can-import": true
			},
			areIn = {},
			texts = {
				style: "",
				"view-model": "",
				events: "",
				helpers: ""
			},
			froms = {};
			types = {},
			attrs = {},
			values = {},
			imports = [],
			ases = {},
			currentTag = "",
			currentAttr = "",
			tagName = "",
			keepToken = function(){
				// We only want to keep the template's tokens.
				return !!areIn.template;
			};

		var intermediate = parser(template, {
			start: function(tagName){
				// Mark when we are inside one of the tags.
				if(tags[tagName]) {
					currentTag = tagName;
					areIn[tagName] = true;
				}
				return keepToken();
			},
			attrStart: function(attrName){
				currentAttr = attrName;
				return keepToken();
			},
			attrEnd: function(attrName){
				currentAttr = "";
				return keepToken();
			},
			attrValue: function(value){
				if(areIn["can-component"] && currentAttr === "tag") {
					tagName = value;
				}
				if(areIn["can-import"] && currentAttr === "from") {
					imports.push(value);
				}
				if(currentAttr === "type" && tags[currentTag]) {
					types[currentTag] = (value+"").replace("text/", "");

					// <script type="events">
					if(currentTag === "script" && tags[value]) {
						currentTag = value;
					}
				} else if(currentAttr === "from" && tags[currentTag]) {
					froms[currentTag] = value;
				}
				attrs[currentAttr] = value;
				return keepToken();
			},
			end: function(tagName){
				if(tagName === "can-import") {
					if(attrs.as) {
						ases[attrs.as] = attrs.from;
					}

					attrs.as = false;
					attrs.from = false;
					areIn["can-import"] = false;
				}
				return keepToken();
			},
			close: function(tagName){
				if(tagName === "can-import") {
					imports.pop();
				}
				if(tags[tagName]) {
					areIn[tagName] = false;
				}
				return keepToken();
			},
			special: keepToken,
			chars: function(char){
				if(texts[currentTag] != null) {
					texts[currentTag] += char;
				}
				return keepToken();
			},
			done: function(){}
		}, true);

		// Remove the leading template noise.
		var idx = 0, cur = intermediate[0];
		while(cur.tokenType !== "end" || (cur.args ? cur.args[0] !== "template" : true)) {
			intermediate.shift();
			cur = intermediate[0];
		}
		// Remove the template end
		intermediate.shift();

		return {
			froms: froms,
			intermediate: intermediate,
			imports: imports,
			tagName: tagName,
			texts: texts,
			types: types
		};
	}

	// Make functions that will define virtual modules
	function makeDefineVirtualModule(loader, load, addDep, args){

		function namer(loadName){
			var baseName = loadName.substr(0, loadName.indexOf("!"));

			return function(part, plugin){
				return baseName + "/" + part + (plugin ? ("." + plugin) : "");
			};
		}

		function addresser(loadAddress){
			return function(part, plugin){
				var base = loadAddress + "." + part;
				return base + (plugin ? ("." + plugin) : "");
			};
		}

		var name = namer(load.name);
		var address = addresser(load.address);

		// A function for disposing of modules during live-reload
		var disposeModule = function(moduleName){
			if(loader.has(moduleName))
				loader["delete"](moduleName);
		};
		if(loader.liveReloadInstalled || loader.has("live-reload")) {
			loader.import("live-reload", { name: module.id }).then(function(reload){
				disposeModule = reload.disposeModule || disposeModule;
			});
		}

		return function(defn){
			if(defn.condition) {
				if(defn.arg) {
					// viewModel
					args.push(defn.arg);
				}

				var moduleName = typeof defn.name === "function" ?
					defn.name(name) : name(defn.name);
				var moduleAddress = typeof defn.address === "function" ?
					defn.address(address) : address(defn.address);

				// from="something.js"
				if(defn.from) {
					addDep(defn.from, false);
				}

				else if(defn.getLoad) {
					var moduleSource = defn.source();
					return defn.getLoad(moduleName).then(function(newLoad){
						moduleName = newLoad.name || moduleName;

						// For live-reload
						disposeModule(moduleName);

						loader.define(moduleName, moduleSource, {
							metadata: newLoad.metadata,
							address: moduleAddress
						});
						addDep(moduleName);
					});
				}

				else if(defn.source) {
					addDep(moduleName);

					if(loader.has(moduleName))
						loader["delete"](moduleName);

					loader.define(moduleName, defn.source, {
						address: address(defn.name)
					});
				}
			}
		}
	}

	function templateDefine(intermediateAndImports){
		var intermediate = intermediateAndImports.intermediate;
		var imports = intermediateAndImports.imports;
		imports.unshift("can/component/component");
		imports.unshift("can/view/stache/stache");

		return "def" + "ine(" + JSON.stringify(imports) + ", function(stache){\n" +
			"\treturn stache(" + JSON.stringify(intermediate) + ");\n" +
			"});";
	}

	function translate(load){
		var result = parse(load.source),
			tagName = result.tagName,
			texts = result.texts,
			types = result.types,
			froms = result.froms,
			deps = ["can/component/component"],
			ases = ["Component"],
			addDep = function(depName, isVirtual){
				deps.push(depName);
				if(isVirtual !== false) load.metadata.virtualDeps.push(depName);
			},
			stylePromise;

		var localLoader = loader.localLoader || loader;

		load.metadata.virtualDeps = [];
		var defineVirtualModule = makeDefineVirtualModule(localLoader, load,
														  addDep, ases);

		// Define the template
		defineVirtualModule({
			condition: froms.template || result.intermediate.length,
			arg: "template",
			name: "template",
			from: froms.template,
			source: templateDefine(result)
		});

		// Define the viewModel
		defineVirtualModule({
			condition: froms["view-model"] || texts["view-model"],
			arg: "viewModel",
			name: "view-model",
			from: froms["view-model"],
			source: texts["view-model"]
		});

		// Define events
		defineVirtualModule({
			condition: froms.events || texts.events,
			arg: "events",
			name: "events",
			from: froms.events,
			source: texts.events
		});

		// Define helpers
		defineVirtualModule({
			condition: froms.events || texts.events,
			arg: "helpers",
			name: "helpers",
			from: froms.helpers,
			source: texts.helpers
		});

		// Define the styles
		stylePromise = defineVirtualModule({
			condition: froms.style || texts.style,
			name: function(name){
				return name("style", types.style || "css") + "!"
			},
			address: function(address){
				return address("style", types.style);
			},
			source: function(){
				var styleText = texts.style;
				if(types.style === "less") {
					var styleText = tagName + " {\n" + texts.style + "}\n";
				}
				return styleText;
			},
			getLoad: function(styleName){
				var styleLoad = {};
				var normalizePromise = localLoader.normalize(styleName, load.name);
				var locatePromise = normalizePromise.then(function(name){
					styleName = name;
					styleLoad = { name: name, metadata: {} };
					return localLoader.locate(styleLoad);
				});

				return locatePromise.then(function(){
					return {
						name: styleName,
						metadata: styleLoad.metadata
					};
				});
			}
		}) || Promise.resolve();

		return stylePromise.then(function(){
			return "def" + "ine(" + JSON.stringify(deps) + ", function(" +
				ases.join(", ") + "){\n" +
				"\tvar __interop = function(m){if(m && m['default']) {return m['default'];}else if(m) return m;};\n\n" +
				"\tvar viewModel = __interop(typeof viewModel !== 'undefined' ? viewModel : undefined);\n" +
				"\tvar ComponentConstructor = Component.extend({\n" +
				"\t\ttag: '" + tagName + "',\n" +
				"\t\ttemplate: __interop(typeof template !== 'undefined' ? template : undefined),\n" +
				"\t\tviewModel: viewModel,\n" +
				"\t\tevents: __interop(typeof events !== 'undefined' ? events : undefined),\n" +
				"\t\thelpers: __interop(typeof helpers !== 'undefined' ? helpers : undefined)\n" +
				"\t});\n\n" +
				"\treturn {\n" +
				"\t\tComponent: ComponentConstructor,\n" +
				"\t\tViewModel: ComponentConstructor.Map,\n" +
				"\t\tviewModel: viewModel\n" +
				"\t};\n" +
				"});";
		});

	}

	return {
		translate: translate
	};

});
<|endoftext|>


<commit_before>angular.module('fitness.game', [])

// directive for auto-focusing popup input when shown
.directive('focusMe', function($timeout) {
  return {
    link: function(scope, element, attrs) {
      $timeout(function() {
        element[0].focus();
      }, 150);
    }
  };
})
.controller('GameCtrl', function($ionicPopup, $timeout, $interval, gameService, $scope, $state) {

	console.log('Game Controller loaded');

	var self = this;
	var vowels = ['A', 'E', 'I', 'O', 'U'];
	var numVowels = 0;
	var numCons = 0;
	var vowelsRevealed = 0;
	var consRevealed = 0;
	var vowelStack, consStack;

	var data = "The rain in spain falls mainly on the plain";
	data = data.toUpperCase();

	// stole this from http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
	this.shuffleArray = function(array) {
	    for (var i = array.length - 1; i > 0; i--) {
	        var j = Math.floor(Math.random() * (i + 1));
	        var temp = array[i];
	        array[i] = array[j];
	        array[j] = temp;
	    }
	    return array;
	}

	// count vowels and consonants so we know the range for random numbers
	for(var i = 0; i < data.length; i++) {
		// don't care if it's a space
		if(data.charAt(i) === " ") {
			continue;
		}
		// increment vowel count
		else if(vowels.includes(data.charAt(i))) {
			numVowels++;
		}
		// increment consonant count
		else {
			numCons++;
		}
	}

	var orderVowels = [];
	for(var a = 0; a < numVowels; a++) {
		orderVowels[a] = a;
	}

	var orderCons = [];
	for(var b = 0; b < numCons; b++) {
		orderCons[b] = b;
	}

	orderVowels = self.shuffleArray(orderVowels);
	orderCons = self.shuffleArray(orderCons);

	vowelStack = new Array(numVowels);
	consStack = new Array(numCons);

	var parts = data.split(" ");
	self.phrase = [];
	parts.forEach(function(d) {
		var temp = [];
		for(var x = 0; x < d.length; x++) {
			var obj = {
				letter: d[x],
				revealed: false,
				model: "."
			};

			if(vowels.includes(d[x])) {
				obj.letterType = "V";
				obj.order = orderVowels.shift();
				vowelStack[obj.order] = obj;
			} else if(d === ' ') {
				obj.letterType = "S";
			} else {
				obj.letterType = "C";
				obj.order = orderCons.shift();
				consStack[obj.order] = obj;
			}
			temp.push(obj);
		}
		self.phrase.push(temp)
	});

	// button click handler, exhausts vowels first, then consonants
	this.reveal = function() {
		if(vowelStack.length == 0) {
			var curr = consStack.shift();
			if (curr) {
				curr.model = curr.letter;
				vowelsRevealed++;
			}
		} else {
			var curr = vowelStack.shift();
			if (curr) {
				curr.model = curr.letter;
				consRevealed++;
			}
		}
	};

	this.showPopup = function() {
		var guessPopup = $ionicPopup.show({
			template: '<input focus-me class="popup-input" ng-model="vm.guess">',
			title: 'Enter Phrase',
			scope: $scope,
			buttons: [
				{
					text: 'Cancel',
					type: 'button-dark'
				},
				{
					text: 'Guess',
					type: 'button-positive',
					onTap: function(e) {
						if(self.guess.toUpperCase() === data) {
							goodGuess();
						} else {
							badGuess();
						}
					}
				}
			]
		});
	};

	function goodGuess() {
		console.log("good guess!")
		revealAll();
	}

	function badGuess() {
		self.guess = "";
		console.log("bad guess!")
	}

	// this will replace the existing reveal function
	function reveal1(letterType) {
		var curr;
		if(letterType === "V" && vowelStack.length != 0) {
			curr = vowelStack.shift();
			if (curr) {
				curr.model = curr.letter;
				vowelsRevealed++;
			}
		} else if(letterType === "C" && consStack.length != 0) {
			curr = consStack.shift();
			if (curr) {
				curr.model = curr.letter;
				consRevealed++;
			}
		} else {
			// either we have a bad lettertype or both stacks are empty
		}
	}

	// reveals the entire phrase
	function revealAll() {
		vowelStack.forEach(function(d) {
			d.model = d.letter;
		});

		consStack.forEach(function(d) {
			d.model = d.letter;
		});
	}

	// takes the number of vowels and consonants that should be revealed and reveals them
	function catchUp(vowels, consonants) {
		if(vowels < 0 || consonants < 0) {
			return;
		}

		for(var a = 0; a < vowels; a++) {
			reveal1("V");
		}

		for(var b = 0; b < consonants; b++) {
			reveal1("C");
		}
	}

	function saveState() {
		console.log(data)
		console.log(self.phrase);
		console.log(vowelStack)
		console.log(consStack)
		// localforage.setItem("tbd_PHRASE", data);
		// localforage.setItem("tbd_VIEWMODEL", self.phrase);
		// localforage.setItem("tbd_VSTACK", vowelStack);
		// localforage.setItem("tbd_CSTACK", consStack);
	}

	function loadState() {
		return localforage.getItem("tbd_PHRASE").then(function(value1) {
			return localforage.getItem("tbd_PHRASE");
		}).then(function(value2) {

		});
	}

	this.drawFriendly = function() {

		console.log('goal steps ' + self.fitbitData.goals.steps);

		jQuery("#friend-progress").empty();

			self.friendlyProgress = jQuery("#friend-progress").radialMultiProgress("init", {
				'fill': 25,
				'font-size': 14,
				'size': 100,
				'data': [
					{'color': "#2DB1E4", 'range': [0, self.fitbitData.goals.steps]}, // steps
					{'color': "#9CCA13", 'range': [0, self.fitbitData.goals.floors]}, // floors
					{'color': "#cf2583", 'range': [0, 24]} // time
				]
			});

		var dh, dm, ds;
	//	setInterval(function() {
			console.log('in the draw circle func.');

			var date = new Date(),
	        h = date.getHours(),
	        m = date.getMinutes(),
	        s = date.getSeconds();

			self.fitbitData.time = gameService.updateTime(24-h);

			console.log('hours is ' + h);

	    if (dh !== h) {
	    	self.friendlyProgress.radialMultiProgress("to", {
	      		"index": 0, 'perc': self.fitbitData.steps, 'time': 10
	      	});
	    	dh = h;
	    }
	    if (dm !== m) {
	    	self.friendlyProgress.radialMultiProgress("to", {
	    		"index": 1, 'perc': self.fitbitData.floors, 'time': 10
	    	});
	    	dm = m;
	    }
	    if (ds !== s) {
	    	self.friendlyProgress.radialMultiProgress("to", {
	    		"index": 2, 'perc': h, 'time': 100
	    	});
	    	ds = s;
	    }
//	  }, 1000);
	};

	$scope.$on('$ionicView.enter', function(e) {

		self.fitbitData = {} ;
		self.fitbitData.goals = {} ;

		self.fitbitData = gameService.data ;

		console.log('steps in game.js: ' + self.fitbitData.steps);

		console.log('getting access token from storage');
		localforage.getItem('fitbitToken').then(function(token){
			if (!token)
				gameService.reload();

			gameService.fitbitToken = token;
			console.log('saving token to game service: ' + token);
			gameService.getFitbitData();
		});

		$interval.cancel(self.queryInterval);

		self.queryInterval = $interval( function(){ self.refreshData(); } , 120000);

		$timeout( self.drawFriendly, 750);
	});

	this.refreshData = function(){

		gameService.getFitbitData();
		$timeout( self.drawFriendly, 750);
		$scope.$broadcast('scroll.refreshComplete');
	}
});
<commit_msg>Pulled things out of controller scope. Refactored.<commit_after>angular.module('fitness.game', [])

// directive for auto-focusing popup input when shown
.directive('focusMe', function($timeout) {
  return {
    link: function(scope, element, attrs) {
      $timeout(function() {
        element[0].focus();
      }, 150);
    }
  };
})
.controller('GameCtrl', function($ionicPopup, $timeout, $interval, gameService, $scope, $state) {

	console.log('Game Controller loaded');

	var self = this;
	var vowels = ['A', 'E', 'I', 'O', 'U'];
	var numVowels = 0;
	var numCons = 0;
	var vowelsRevealed = 0;
	var consRevealed = 0;
	var vowelStack, consStack;
	var data;

	(function() {
		data = "The rain in spain falls mainly on the plain";
		data = data.toUpperCase();

		// stole this from http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
		self.shuffleArray = function(array) {
		    for (var i = array.length - 1; i > 0; i--) {
		        var j = Math.floor(Math.random() * (i + 1));
		        var temp = array[i];
		        array[i] = array[j];
		        array[j] = temp;
		    }
		    return array;
		}

		// count vowels and consonants so we know the range for random numbers
		for(var i = 0; i < data.length; i++) {
			// don't care if it's a space
			if(data.charAt(i) === " ") {
				continue;
			}
			// increment vowel count
			else if(vowels.includes(data.charAt(i))) {
				numVowels++;
			}
			// increment consonant count
			else {
				numCons++;
			}
		}

		var orderVowels = [];
		for(var a = 0; a < numVowels; a++) {
			orderVowels[a] = a;
		}

		var orderCons = [];
		for(var b = 0; b < numCons; b++) {
			orderCons[b] = b;
		}

		orderVowels = self.shuffleArray(orderVowels);
		orderCons = self.shuffleArray(orderCons);

		vowelStack = new Array(numVowels);
		consStack = new Array(numCons);

		var parts = data.split(" ");
		self.phrase = [];
		parts.forEach(function(d) {
			var temp = [];
			for(var x = 0; x < d.length; x++) {
				var obj = {
					letter: d[x],
					revealed: false,
					model: "."
				};

				if(vowels.includes(d[x])) {
					obj.letterType = "V";
					obj.order = orderVowels.shift();
					vowelStack[obj.order] = obj;
				} else if(d === ' ') {
					obj.letterType = "S";
				} else {
					obj.letterType = "C";
					obj.order = orderCons.shift();
					consStack[obj.order] = obj;
				}
				temp.push(obj);
			}
			self.phrase.push(temp)
		});
	}());

	// button click handler, exhausts vowels first, then consonants
	this.reveal = function() {
		if(vowelStack.length == 0) {
			var curr = consStack.shift();
			if (curr) {
				curr.model = curr.letter;
				vowelsRevealed++;
			}
		} else {
			var curr = vowelStack.shift();
			if (curr) {
				curr.model = curr.letter;
				consRevealed++;
			}
		}
	};

	this.showPopup = function() {
		var guessPopup = $ionicPopup.show({
			template: '<input focus-me class="popup-input" ng-model="vm.guess">',
			title: 'Enter Phrase',
			scope: $scope,
			buttons: [
				{
					text: 'Cancel',
					type: 'button-dark'
				},
				{
					text: 'Solve',
					type: 'button-positive',
					onTap: function(e) {
						if(self.guess.toUpperCase() === data) {
							self.goodGuess();
						} else {
							self.badGuess();
						}
					}
				}
			]
		});
	};

	this.goodGuess = function() {
		console.log("good guess!")
		self.revealAll();
	};

	this.badGuess = function() {
		self.guess = "";
		console.log("bad guess!")
	};

	// this will replace the existing reveal function
	this.reveal1 = function(letterType) {
		var curr;
		if(letterType === "V" && vowelStack.length != 0) {
			curr = vowelStack.shift();
			if (curr) {
				curr.model = curr.letter;
				vowelsRevealed++;
			}
		} else if(letterType === "C" && consStack.length != 0) {
			curr = consStack.shift();
			if (curr) {
				curr.model = curr.letter;
				consRevealed++;
			}
		} else {
			// either we have a bad lettertype or both stacks are empty
		}
	}

	// reveals the entire phrase
	this.revealAll = function() {
		vowelStack.forEach(function(d) {
			d.model = d.letter;
		});

		consStack.forEach(function(d) {
			d.model = d.letter;
		});
	}

	// takes the number of vowels and consonants that should be revealed and reveals them
	this.catchUp = function(vowels, consonants) {
		if(vowels < 0 || consonants < 0) {
			return;
		}

		for(var a = 0; a < vowels; a++) {
			self.reveal1("V");
		}

		for(var b = 0; b < consonants; b++) {
			self.reveal1("C");
		}
	}

	this.saveState = function() {
		console.log(data)
		console.log(self.phrase);
		console.log(vowelStack)
		console.log(consStack)
		// localforage.setItem("tbd_PHRASE", data);
		// localforage.setItem("tbd_VIEWMODEL", self.phrase);
		// localforage.setItem("tbd_VSTACK", vowelStack);
		// localforage.setItem("tbd_CSTACK", consStack);
	}

	this.loadState = function() {
		return localforage.getItem("tbd_PHRASE").then(function(value1) {
			return localforage.getItem("tbd_PHRASE");
		}).then(function(value2) {

		});
	}

	this.drawFriendly = function() {

		console.log('goal steps ' + self.fitbitData.goals.steps);

		jQuery("#friend-progress").empty();

			self.friendlyProgress = jQuery("#friend-progress").radialMultiProgress("init", {
				'fill': 25,
				'font-size': 14,
				'size': 100,
				'data': [
					{'color': "#2DB1E4", 'range': [0, self.fitbitData.goals.steps]}, // steps
					{'color': "#9CCA13", 'range': [0, self.fitbitData.goals.floors]}, // floors
					{'color': "#cf2583", 'range': [0, 24]} // time
				]
			});

		var dh, dm, ds;
	//	setInterval(function() {
			console.log('in the draw circle func.');

			var date = new Date(),
	        h = date.getHours(),
	        m = date.getMinutes(),
	        s = date.getSeconds();

			self.fitbitData.time = gameService.updateTime(24-h);

			console.log('hours is ' + h);

	    if (dh !== h) {
	    	self.friendlyProgress.radialMultiProgress("to", {
	      		"index": 0, 'perc': self.fitbitData.steps, 'time': 10
	      	});
	    	dh = h;
	    }
	    if (dm !== m) {
	    	self.friendlyProgress.radialMultiProgress("to", {
	    		"index": 1, 'perc': self.fitbitData.floors, 'time': 10
	    	});
	    	dm = m;
	    }
	    if (ds !== s) {
	    	self.friendlyProgress.radialMultiProgress("to", {
	    		"index": 2, 'perc': h, 'time': 100
	    	});
	    	ds = s;
	    }
//	  }, 1000);
	};

	$scope.$on('$ionicView.enter', function(e) {

		self.fitbitData = {} ;
		self.fitbitData.goals = {} ;

		self.fitbitData = gameService.data ;

		console.log('steps in game.js: ' + self.fitbitData.steps);

		console.log('getting access token from storage');
		localforage.getItem('fitbitToken').then(function(token){
			if (!token)
				gameService.reload();

			gameService.fitbitToken = token;
			console.log('saving token to game service: ' + token);
			gameService.getFitbitData();
		});

		$interval.cancel(self.queryInterval);

		self.queryInterval = $interval( function(){ self.refreshData(); } , 120000);

		$timeout( self.drawFriendly, 750);
	});

	this.refreshData = function(){

		gameService.getFitbitData();
		$timeout( self.drawFriendly, 750);
		$scope.$broadcast('scroll.refreshComplete');
	}
});
<|endoftext|>


<commit_before>function apiLogs(user) {
  // FIXME: obey limit and skip
  // FIXME: filter out user_id
  var results = Data.find({user_id: user._id}).fetch();
  return [200, JSON.stringify(results)];
}

function apiCategories(user) {
  // FIXME: obey limit and skip
  var defaults = Categories.find({default_category: true}).fetch();
  var results = Categories.find({creator: user._id}).fetch();
  return [200, JSON.stringify(defaults.concat(results))];
}

var entries = [
  {
    url: 'logs',
    type: 'POST',
    func: apiLogs,
    inputs: {
      user: {
        required: true,
        type: 'str',
      },
      apikey: {
        required: true,
        type: 'str',
      },
      skip: {
        required: false,
        type: 'int',
        default: 0,
        min: 0,
      },
      limit: {
        required: false,
        type: 'int',
        default: 200,
        min: 1,
        max: 200,
      },
    },
  },
  {
    url: 'categories',
    type: 'POST',
    func: apiCategories,
    inputs: {
      user: {
        required: true,
        type: 'str',
      },
      apikey: {
        required: true,
        type: 'str',
      },
      skip: {
        required: false,
        type: 'int',
        default: 0,
        min: 0,
      },
      limit: {
        required: false,
        type: 'int',
        default: 200,
        min: 1,
        max: 200,
      },
    },
  },
];

function validate(entry, params) {
  var paramKeys = _.keys(params);
  var validKeys = _.keys(entry.inputs);

  var error;

  _.each(paramKeys, function(key) {
    if (!_.contains(validKeys, key)) {
      error = [400, 'Unknown key: ' + key];
      return;
    }

    var paramInfo = entry.inputs[key];
    var paramValue = params[key];
    if (paramInfo.type === 'int') {
      var intValue = parseInt(paramValue);
      if (parseFloat(paramValue) !== intValue || isNaN(paramValue)) {
        error = [400, 'Invalid value for key: ' + key];
        return;
      }
      if ('min' in paramInfo && intValue < paramInfo.min) {
        error = [400, 'Min value exceeded for key: ' + key]
      }
      if ('max' in paramInfo && intValue > paramInfo.max) {
        error = [400, 'Max value exceeded for key: ' + key]
      }
    }
  });

  _.each(validKeys, function(key) {
    if (entry.inputs[key].required && !_.contains(paramKeys, key)) {
      error = [400, 'Missing required key: ' + key];
    }
  });

  return error;
}

function authenticate(entry, params) {
  var user = Meteor.users.findOne(params.user);
  // FIXME: check apikey
  return user;
}

_.each(entries, function(entry) {
  Meteor.Router.add('/api/' + entry.url, entry.type, function() {
    var queryParams = this.request.query;
    if (_.size(queryParams) > 0) {
      return [400, 'All parameters must be passed in request body\n'];
    }

    var params = this.request.body;
    var error = validate(entry, params);
    if (error) {
      return error;
    }

    var user = authenticate(entry, params);
    if (!user) {
      return [403, 'Invalid user and/or apikey specified']
    }

    return entry.func(user);
  });
});

Meteor.Router.add('/api/*', [404, "Unknown API entry"]);
<commit_msg>Obey limit and skip<commit_after>function apiLogs(user, params) {
  // FIXME: filter out user_id
  var results = Data.find({
    user_id: user._id,
  }, {
    skip: params.skip,
    limit: params.limit,
    sort: { timestamp: 1 },
  }).fetch();
  return [200, JSON.stringify(results)];
}

function apiCategories(user, params) {
  var results = Categories.find({
    $or: [
      { default_category: true},
      { creator: user._id },
    ],
  }, {
    skip: params.skip,
    limit: params.limit,
    sort: { timestamp: 1 },
  }).fetch();
  return [200, JSON.stringify(results)];
}

var entries = [
  {
    url: 'logs',
    type: 'POST',
    func: apiLogs,
    inputs: {
      user: {
        required: true,
        type: 'str',
      },
      apikey: {
        required: true,
        type: 'str',
      },
      skip: {
        required: false,
        type: 'int',
        default: 0,
        min: 0,
      },
      limit: {
        required: false,
        type: 'int',
        default: 200,
        min: 1,
        max: 200,
      },
    },
  },
  {
    url: 'categories',
    type: 'POST',
    func: apiCategories,
    inputs: {
      user: {
        required: true,
        type: 'str',
      },
      apikey: {
        required: true,
        type: 'str',
      },
      skip: {
        required: false,
        type: 'int',
        default: 0,
        min: 0,
      },
      limit: {
        required: false,
        type: 'int',
        default: 200,
        min: 1,
        max: 200,
      },
    },
  },
];

function validate(entry, params) {
  var paramKeys = _.keys(params);
  var validKeys = _.keys(entry.inputs);

  var error;

  _.each(paramKeys, function(key) {
    if (!_.contains(validKeys, key)) {
      error = [400, 'Unknown key: ' + key];
      return;
    }

    var paramInfo = entry.inputs[key];
    var paramValue = params[key];
    if (paramInfo.type === 'int') {
      var intValue = parseInt(paramValue);
      if (parseFloat(paramValue) !== intValue || isNaN(paramValue)) {
        error = [400, 'Invalid value for key: ' + key];
        return;
      }
      if ('min' in paramInfo && intValue < paramInfo.min) {
        error = [400, 'Min value exceeded for key: ' + key]
      }
      if ('max' in paramInfo && intValue > paramInfo.max) {
        error = [400, 'Max value exceeded for key: ' + key]
      }
    }
  });

  _.each(validKeys, function(key) {
    if (_.contains(paramKeys, key))
      return;
    if (entry.inputs[key].required) {
      error = [400, 'Missing required key: ' + key];
    } else if ('default' in entry.inputs[key]) {
      params[key] = entry.inputs[key].default;
    }
  });

  return error;
}

function authenticate(entry, params) {
  var user = Meteor.users.findOne(params.user);
  // FIXME: check apikey
  return user;
}

_.each(entries, function(entry) {
  Meteor.Router.add('/api/' + entry.url, entry.type, function() {
    var queryParams = this.request.query;
    if (_.size(queryParams) > 0) {
      return [400, 'All parameters must be passed in request body\n'];
    }

    var params = this.request.body;
    var error = validate(entry, params);
    if (error) {
      return error;
    }

    var user = authenticate(entry, params);
    if (!user) {
      return [403, 'Invalid user and/or apikey specified']
    }

    return entry.func(user, params);
  });
});

Meteor.Router.add('/api/*', [404, "Unknown API entry"]);
<|endoftext|>