[Userscript] Easier Typing Exercise to bridge gap between Multiple Choice and Typing Exercises

To bridge the gap of difficulty between multiple choice and typing exercises this script changes the box color (green/red) depending on whether the answer is correct so far and displays a keyboard of keys for the remaining characters to get to the correct answer.

I am sharing in case someone finds it useful and wants to build on it.

// ==UserScript==
// @name         Memrise - TypeHelper
// @namespace    http://tampermonkey.net/
// @version      0.1
// @match        https://www.memrise.com/course/*/garden/*
// @match        https://www.memrise.com/garden/review/*
// @grant        none
// ==/UserScript==

$(document).ready(function() {

    //Events
    MEMRISE.garden._events.start.push(() => {

        $('body').on('.typing input', 'input', function(e) {
            console.debug(e);
            mark();
            keyboard();
        });

        $('body').on('select', function(e) {
            console.debug(e);
            mark();
            keyboard();
        });

    })

    MEMRISE.garden._events.activate.push(() => {
        console.debug('activate');
        keyboard(true);
    })

    //Marking
    function mark(){
        if(MEMRISE.garden.box.template === 'typing'){
            var input = MEMRISE.garden.box.$input.input.val();
            var answer = MEMRISE.garden.box.testData.answer.value;
            if(input === ''){
                markNone();
            }else if(answer.startsWith(input)){
                markCorrect();
            }else{
                markIncorrect();
            }
        }
    }

    function markCorrect(){
        MEMRISE.garden.box.$input.input.addClass('correct');
        MEMRISE.garden.box.$input.input.removeClass('incorrect');
    }

    function markIncorrect(){
        MEMRISE.garden.box.$input.input.addClass('incorrect');
        MEMRISE.garden.box.$input.input.removeClass('correct');
    }

    function markNone(){
        MEMRISE.garden.box.$input.input.removeClass('correct');
        MEMRISE.garden.box.$input.input.removeClass('incorrect');
    }


    ///Keyboard
    var shuffled_answer; //to maintain the same order/shuffle once
    function keyboard(shuffle = false){
        if(MEMRISE.garden.box.template === 'typing'){
            var input = MEMRISE.garden.box.$input.input.val();
            var answer = MEMRISE.garden.box.testData.answer.value;
            var rest = answer.rest(input);

            if(shuffle){
                shuffled_answer = answer.shuffle();
            }

            display(shuffled_answer.keep(rest));
        }
    }

    function empty(){
        $('.keyboard.clearfix').empty();
    }

    function addKey(c){
        $('.keyboard.clearfix').append('<a class="shiny-box">' + c + '</a>')
    }

    function display(str){
        empty();
        for (var i = 0; i < str.length; i++) {
            var c = str.charAt(i);
            addKey(c);
        }
    }
})


//Utils
String.prototype.shuffle = function () {
    var a = this.split(''),
        n = a.length;

    for(var i = n - 1; i > 0; i--) {
        var j = Math.floor(Math.random() * (i + 1));
        var tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
    return a.join('');
}

String.prototype.rest = function(str) {
    var index = 0;
    for(var i = 0; i < this.length; i++) {
        if(this.charAt(i) === str.charAt(i)){
            index++;
        }else{
            break;
        }
    }
    return this.substring(index, this.length);
}


String.prototype.keep = function(str) {
    var r = [];
    var cs = str.split('');
    for(var i = 0; i < this.length; i++){
        var c = this.charAt(i);
        if(cs.includes(c)){
            r.push(c);
            const index = cs.indexOf(c);
            if (index > -1) {
                cs.splice(index, 1);
            }
        }
    }
    return r.join('');
}
1 Like

Great idea, thanks for sharing!

@lurkmoophy: what do you think, wouldn’t this be a great extension for the web app?

Nice, is this script supposed to be typed in the console when you inspect the code.

My pleasure, thanks! I improved it a little (for example, if the current_streak < 5, the first letter is colored green, there will be at least 7 keys even if the word is small)

$(document).ready(function() {

    function main(first=false){
        if(MEMRISE.garden.box.template === 'typing'){
            var current_streak = streak();
            if(current_streak < 10){
                mark();
                keyboard(first);
            }else if(current_streak < 20){
                mark();
            }else{
                empty();
            }
        }
    }

    function streak(){
        return MEMRISE.garden.box.thinguser.current_streak;
    }

    //Events
    MEMRISE.garden._events.start.push(() => {

        $('body').on('.typing input', 'input', function(e) {
            console.debug(e);
            main();
        });

        $('body').on('select', function(e) {
            console.debug(e);
            main();
        });

    })

    MEMRISE.garden._events.activate.push(() => {
        console.debug('activate');
        main(true);
    })

    //Marking
    function mark(){
        var input = MEMRISE.garden.box.$input.input.val();
        var answer = MEMRISE.garden.box.testData.answer.value;
        if(input === ''){
            markNone();
        }else if(answer.startsWith(input)){
            markCorrect();
        }else{
            markIncorrect();
        }
    }

    function markCorrect(){
        MEMRISE.garden.box.$input.input.addClass('correct');
        MEMRISE.garden.box.$input.input.removeClass('incorrect');
    }

    function markIncorrect(){
        MEMRISE.garden.box.$input.input.addClass('incorrect');
        MEMRISE.garden.box.$input.input.removeClass('correct');
    }

    function markNone(){
        MEMRISE.garden.box.$input.input.removeClass('correct');
        MEMRISE.garden.box.$input.input.removeClass('incorrect');
    }


    ///Keyboard
    var shuffled_answer;
    function keyboard(shuffle = false){
        var input = MEMRISE.garden.box.$input.input.val();
        var answer = MEMRISE.garden.box.testData.answer.value;

        if(shuffle){
            var choices = MEMRISE.garden.box.testData.choices;
            console.debug(choices);
            if(/*choices !== undefined &&*/ choices.length < 10){
                console.debug('CHOICES');
                answer = choices.join('').addMissingChars(answer);
            }
            if(answer.length < 7){
                var add = 7 - answer.length;
                answer = answer + choices.join('').shuffle().substr(0, add);
            }
            shuffled_answer = answer.shuffle();
            console.debug(shuffled_answer);
        }

        display(shuffled_answer.remove(input));
        if(shuffle && streak() < 5) {
            colorChar(MEMRISE.garden.box.testData.answer.value.charAt(0));
        }
    }

    function empty(){
        $('.keyboard.clearfix').empty();
    }

    function colorChar(c){
        $('.keyboard.clearfix').find("a:contains('" + c + "')").eq(0).css("background-color", "chartreuse");
    }

    function addKey(c){
        if(c === ' ') return;
        var box = $('<a class="shiny-box">' + c + '</a>');
        $('.keyboard.clearfix').append(box);
    }

    function display(str){
        empty();
        for (var i = 0; i < str.length; i++) {
            var c = str.charAt(i);
            addKey(c);
        }
    }
})


//Utils
String.prototype.shuffle = function () {
    var a = this.split(''),
        n = a.length;

    for(var i = n - 1; i > 0; i--) {
        var j = Math.floor(Math.random() * (i + 1));
        var tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
    return a.join('');
}

String.prototype.rest = function(str) {
    var index = 0;
    for(var i = 0; i < this.length; i++) {
        if(this.charAt(i) === str.charAt(i)){
            index++;
        }else{
            break;
        }
    }
    return this.substring(index, this.length);
}

String.prototype.keep = function(str) {
    var r = [];
    var cs = str.split('');
    for(var i = 0; i < this.length; i++){
        var c = this.charAt(i);
        if(cs.includes(c)){
            r.push(c);
            const index = cs.indexOf(c);
            if (index > -1) {
                cs.splice(index, 1);
            }
        }
    }
    return r.join('');
}

String.prototype.remove = function(str) {
    var r = [];
    var cs = str.split('');
    for(var i = 0; i < this.length; i++){
        var c = this.charAt(i);
        if(cs.includes(c)){
            const index = cs.indexOf(c);
            if(index > -1) {
                cs.splice(index, 1);
            }
        }else{
            r.push(c);
        }
    }
    return r.join('');
}

String.prototype.addMissingChars = function(str) {
    var cs = str.split('');
    for(var i = 0; i < this.length; i++){
        var c = this.charAt(i);
        if(cs.includes(c)){
            const index = cs.indexOf(c);
            if(index > -1) {
                cs.splice(index, 1);
            }
        }
    }
    return this + cs.join('');
}

I use Tampermonkey (Firefox addon), which handles all the injection for you image

Thank you so much it is working now :slight_smile:

1 Like