3

I'm currently using JsPsych to create a behavioral experiment, particularly Matrix Reasoning, so the user chooses from a set of options based on a logic pattern.

My problem is that I need to repeat certain trials if the user fails when answering the first two trials after the practice blocks. Let's say, the first two trials are for practice purposes, and then the repetition rule is applied.

Since JsPsych's "timeline" is an array, its elements (aka trials) are created when the whole experiment starts, so looks like I can't just insert a new trial in between after the experiment has started.

Is there any way to force the execution of certain trials based on the user input? In other words, if the user chooses the right option, go to the next trial, if he/she chooses any of the wrong ones, return to a certain previous trial.

What I've tried so far:

(Based on JsPsych documentation and posts from JsPsych Google Group)

A) Creating a var using the call-function plugin and creating a boolean flag, which gets True if the answer is right, or wrong if isn't. Then I push another trial if the flag is false (ie. experiment.push(another_trial)). Example:

var decide_next = {
   type: 'call-function',
   func: myfunction,
} 

function myfunction(response_flag){
   if (response_flag == False){
      experiment.push(survey);
   }else{
    //just continue as normal  
   }
}

var survey={
   //Here goes the specified trial if the user fails
}

This solutions fails completely, because of how the timeline is generated, as said earlier.

B) Using nested timelines. This is more like a brute force approach, where the main timeline contains the first questions, plus a flag, so if the flag gets certain value, I push a particular timeline. This one went really wrong, so I won't bother you with pointless code.

C) Using conditional timelines. Pretty much another brute force approach, but with a little more success. I've used this feature to display different feedback screens, for right and wrong answers respectively. Example:

//Conditional rules
           var condition1 = false;
           var condition2 = false;

           var m_conditional1_1 = {
                timeline: [m_wrong1],
                conditional_function: function(data){
                    if(condition1 === false){
                        return true;
                    } else {
                        return false;
                    }
                }
            };

            var m_conditional1_2 = {
                timeline: [m_right1],
                conditional_function: function(data){
                    if(condition1 === true){
                        return true;
                    } else {
                        return false;
                    }
                }
            };

            var m_conditional2_1 = {
                timeline: [m_wrong2],
                conditional_function: function(data){
                    if(condition2 === false){
                        return true;
                    } else {
                        return false;
                    }
                }
            };

            var m_conditional2_2 = {
                timeline: [m_right2],
                conditional_function: function(data){
                    if(condition2 === true){
                        return true;
                    } else {
                        return false;
                    }
                }
            };

// Feedback trials to display upon conditional trails's result

                   var m_wrong1 = {
                    type: "text",
                    text: "<div class = matrizlimit><div class= centered><div class='centered'><img src ='img/1A.png' /></div><br /><br /><div class='centered'><img src ='img/1B.png' </img></div><br /><br /><div class='matrizfeedback'>Eso no es correcto. <br />Para responder correctamente debe mirar de izquierda a derecha en la fila de arriba.<br /><br />"+
                             "Cuando usted mira la fila de arriba la estrella azul cambia a un circulo amarillo. <br />"+
                             "Esto quiere decir que cuando usted va de izquierda a derecha en la fila de abajo la estrella azul tambi&eacute;n deberia cambiar a un circulo amarillo.<br /><br />"+
                             "Para obtener la respuesta correcta yendo de arriba hacia abajo, usted debe mirar los cuadros de la columna izquierda. "+
                             "Cuando usted va de arriba hacia abajo en la primera columna los cuadros tienen la misma forma y el mismo color: estrellas azules. <br /><br />"+
                             "Esto quiere decir que cuando usted va de arriba hacia abajo en la columna derecha los cuadros tambi&eacute;n deberian tener la misma forma y el mismo color: circulo amarillo. "+
                             "Usted obtiene la misma respuesta yendo de izquierda a derecha y yendo de arriba hacia abajo.<br /><br /><br /><br />Presione una tecla para continuar<br /><br /><br /><br /></div>"
                };

                var m_right1 = {
                    type: "text",
                    text: "<div class = matrizlimit><div class= centered><div class='centered'><img src ='img/1A.png' /></div><br /><br /><div class='centered'><img src ='img/1B.png' </img></div><br /><br /><div class='matrizfeedback'>Eso es correcto.<br />Cuando usted va e izquierda a derecha en la fila de arriba la estrella azul cambia a un circulo amarillo. <br /><br />"+
                             "Esto quiere decir que cuando usted va de izquierda a derecha en la de abajo la estrella azul tambi&eacute;n deberia cambiar a un circulo amarillo. "+
                             "Cuando usted va de arriba hacia abajo en la primera columna los cuadros tienen la misma forma y el mismo color: estrellas azules. <br /><br />"+
                             "Esto quiere decir que cuando usted va de arriba hacia abajo en la segunda columna los cuadros tambi&eacute;n deben tener la misma forma y el mismo color: circulos amarillos. "+
                             "Usted obtiene la misma respuesta yendo de izquierda a derecha y yendo de arriba hacia abajo.<br /><br /><br /><br />Presione una tecla para continuar<br /><br /><br /><br /></div>"
                };

                var m_wrong2 = {
                    type: "text",
                    text: "<div class = matrizlimit><div class= centered><div class='centered'><img src ='img/2A.png' /></div><br /><br /><div class='centered'><img src ='img/2B.png' </img></div><br /><br /><div class='matrizfeedback'>Eso no es correcto. <br />Cuando usted mira los cuadros de izquierda a derecha, usted puede ver que ellos<br />"+
                             "est&aacute;n en el siguiente orden: c&iacute;rculo grande, c&iacute;rculo peque&ntilde;o, c&iacute;rculo grande, c&iacute;rculo peque&ntilde;o, c&iacute;rculo grande.<br /><br />"+
                             "El c&iacute;rculo peque&ntilde;o va en el cuadro con un signo de interrogaci&oacute;n porque es la opci&oacute;n que mantiene el orden: un c&iacute;rculo peque&ntilde;o va luego de un c&iacute;rculo grande.<br />"+
                             "<br /><br /><br /><br />Presione una tecla para continuar<br /><br /><br /><br /></div>"
                };

                var m_right2 = {
                    type: "text",
                    text: "<div class = matrizlimit><div class= centered><div class='centered'><img src ='img/2A.png' /></div><br /><br /><div class='centered'><img src ='img/2B.png' </img></div><br /><br /><div class='matrizfeedback'>Eso es correcto.<br />Cuando usted mira los cuadros de izquierda a derecha, puede ver que ellos siguen este orden: "+
                             "c&iacute;rculo grande, c&iacute;rculo peque&ntilde;o, c&iacute;rculo grande, c&iacute;rculo peque&ntilde;o, c&iacute;rculo grande. <br /><br />"+
                             "El c&iacute;rculo peque&ntilde;o va en el cuadro con un signo de interrogaci&oacute;n porque es lo que mantiene el mismo orden que los anteriores.<br /><br />"+
                             "<br /><br /><br /><br />Presione una tecla para continuar<br /><br /><br /><br /></div>"
                };

// The two practice trials, here I capture the chosen answer and give it to the conditional rules variables. Matriz_practice_1 is associated to m_right_1 and
// m_wrong_1, which are its possible feedback screens. 
// The same goes for Matriz_practice_2, with m_right_2 and m_wrong_2  

                  var matriz_practice_1={
                    type: "survey-multi-choice1",
                    timeline:[
                        {
                            questions: ["<div class = centerbox>"+
                           "<p class = justified>"+
                           "Mire la siguiente figura. Usted debe escoger cual de las opciones que se encuentran abajo va en el"+
                            "cuadro con un signo de interrogaci&oacute;n. La respuesta correcta es aquella que encaja yendo de "+
                            "izquierda a derecha y yendo de arriba hacia abajo. Usted s&oacute;lo debe mirar de izquierda a derecha y "+
                            "de arriba hacia abajo. No mire diagonalmente. &iquest;Cu&aacute;l de las opciones que se encuentran abajo va en"+
                            " el cuadro con un signo de interrogaci&oacute;n?"+
                           "</p><br /><br /></div>"+
                           "<div class= centered><div class='centered'><img src ='img/1A.png' /></div><br /><br /><div class='centered'><img src ='img/1B.png' </img></div>"],
                            data: {trialid: "P_MP_01"},
                            horizontal: true
                            },
                    ],
                    options: [["Opcion1","Opcion2","Opcion3","Opcion4","Opcion5"]],
                    horizontal: true,
                    required: 'true',

                    on_finish: function(data){
                       var test = data.responses;                       
                        if(test =='{"Q0":"Opcion5"}'){
                            condition1 = true;
                        } else {
                            condition1 = false;
                        }
                    }
                };

                var matriz_practice_2={
                    type: "survey-multi-choice1",
                    timeline:[
                        {
                            questions: ["<div class = centerbox>"+
                           "<p class = justified>"+
                           "&Eacute;ste es otro tipo de problema. Los cuadros solo van de izquierda a derecha. La respuesta correcta "+
                            "seguir&aacute; el mismo orden que usted ve en los cuadros. &iquest;Cu&aacute;l de las opciones que se encuentran abajo va "+
                            "en el cuadro con un signo de interrogaci&oacute;n?"+
                           "</p><br /><br /></div>"+
                           "<div class= centered><div class='centered'><img src ='img/2A.png' /></div><br /><br /><div class='centered'><img src ='img/2B.png' </img></div>"],
                            data: {trialid: "P_MP_02"},
                            horizontal: true
                            },
                    ],
                    options: [["Opcion1","Opcion2","Opcion3","Opcion4","Opcion5"]],
                    horizontal: true,
                    required: 'true',

                    on_finish: function(data){
                       var test = data.responses;                       
                        if(test =='{"Q0":"Opcion4"}'){
                            condition2 = true;
                        } else {
                            condition2 = false;
                        }
                    }
                };

This works for short, linear structures, as in that case, I just need to decide which screen to display, but this solution fails when trying to go back to a previous trial if needed. From this solution, I could just define a really long set of binary conditions to "simulate" the repetition of trials when the user fails. But this can't be generated dynamically, so basically I'm forced to guess how many times an user will fail to answer properly, which isn't an option when the experiment is going to be run with 200+ volunteers. Plus, such brute force strategy will increase in complexity if the repetition condition is applied to more than 2 trials, which may actually happen in the future.

I'm guessing that deep modding of JsPsych's core javascript file may lead to a solution, but that goes beyond my current javascript skills and knowledge.

Help on this subject would be much appreciated.

Some extra notes to test this code:

A short, working version of the experiment code is provided below. Aesthetic issues may occur due to CSS being ignored this time. This code requires you to have jspsych folder on the same directory as the main HTML file.

<!doctype html>
<html>
    <head>
        <title>Condition Tester</title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
        <script src="jspsych-5.0.3/jspsych.js"></script>
        <script src="jspsych-5.0.3/plugins/jspsych-survey-multi-choice.js"></script>
        <script src="jspsych-5.0.3/plugins/jspsych-text.js"></script>
        <script src="jspsych-5.0.3/plugins/jspsych-instructions.js"></script>

    </head>
    <body>
        <div class="centered">
        <script>
            var condition1 = false;
            var condition2 = false;

                /* Texto Inicial */

                var matrizexplanation={
                    type: "instructions",
                    pages: ["<div class = centerbox>"+
                           "<p class = center-block-text>"+
                           "A continuaci&oacute;n, le presentaremos una serie de figuras, donde cada una muestra un patr&oacute;n l&oacute;gico.<br />"+
                           "Tendr&aacute; que elegir entre 5 alternativas para completar ese cada patr&oacute;n."+
                           "</p></div>"],
                    allow_keys: false,
                    show_clickable_nav: true,
                    timing_post_trial: 50,
                    data:{trialid: "Instructions_Matriz"}
                };

                var matriz_practice_1={
                    type: "survey-multi-choice1",
                    timeline:[
                        {
                            questions: ["<div class = centerbox>"+
                           "<p class = justified>"+
                           "Mire la siguiente figura. Usted debe escoger cual de las opciones que se encuentran abajo va en el"+
                            "cuadro con un signo de interrogaci&oacute;n. La respuesta correcta es aquella que encaja yendo de "+
                            "izquierda a derecha y yendo de arriba hacia abajo. Usted s&oacute;lo debe mirar de izquierda a derecha y "+
                            "de arriba hacia abajo. No mire diagonalmente. &iquest;Cu&aacute;l de las opciones que se encuentran abajo va en"+
                            " el cuadro con un signo de interrogaci&oacute;n?"+
                           "</p><br /><br /></div>"+
                           "<div class= centered><div class='centered'><img src ='img/1A.png' /></div><br /><br /><div class='centered'><img src ='img/1B.png' </img></div>"],
                            data: {trialid: "P_MP_01"},
                            horizontal: true
                            },
                    ],
                    options: [["Opcion1","Opcion2","Opcion3","Opcion4","Opcion5"]],
                    horizontal: true,
                    required: 'true',

                    on_finish: function(data){
                       var test = data.responses;                       
                        if(test =='{"Q0":"Opcion5"}'){
                            condition1 = true;
                        } else {
                            condition1 = false;
                        }
                    }
                };

                var matriz_practice_2={
                    type: "survey-multi-choice1",
                    timeline:[
                        {
                            questions: ["<div class = centerbox>"+
                           "<p class = justified>"+
                           "&Eacute;ste es otro tipo de problema. Los cuadros solo van de izquierda a derecha. La respuesta correcta "+
                            "seguir&aacute; el mismo orden que usted ve en los cuadros. &iquest;Cu&aacute;l de las opciones que se encuentran abajo va "+
                            "en el cuadro con un signo de interrogaci&oacute;n?"+
                           "</p><br /><br /></div>"+
                           "<div class= centered><div class='centered'><img src ='img/2A.png' /></div><br /><br /><div class='centered'><img src ='img/2B.png' </img></div>"],
                            data: {trialid: "P_MP_02"},
                            horizontal: true
                            },
                    ],
                    options: [["Opcion1","Opcion2","Opcion3","Opcion4","Opcion5"]],
                    horizontal: true,
                    required: 'true',

                    on_finish: function(data){
                       var test = data.responses;                       
                        if(test =='{"Q0":"Opcion4"}'){
                            condition2 = true;
                        } else {
                            condition2 = false;
                        }
                    }
                };

                var m_wrong1 = {
                    type: "text",
                    text: "<div class = matrizlimit><div class= centered><div class='centered'><img src ='img/1A.png' /></div><br /><br /><div class='centered'><img src ='img/1B.png' </img></div><br /><br /><div class='matrizfeedback'>Eso no es correcto. <br />Para responder correctamente debe mirar de izquierda a derecha en la fila de arriba.<br /><br />"+
                             "Cuando usted mira la fila de arriba la estrella azul cambia a un circulo amarillo. <br />"+
                             "Esto quiere decir que cuando usted va de izquierda a derecha en la fila de abajo la estrella azul tambi&eacute;n deberia cambiar a un circulo amarillo.<br /><br />"+
                             "Para obtener la respuesta correcta yendo de arriba hacia abajo, usted debe mirar los cuadros de la columna izquierda. "+
                             "Cuando usted va de arriba hacia abajo en la primera columna los cuadros tienen la misma forma y el mismo color: estrellas azules. <br /><br />"+
                             "Esto quiere decir que cuando usted va de arriba hacia abajo en la columna derecha los cuadros tambi&eacute;n deberian tener la misma forma y el mismo color: circulo amarillo. "+
                             "Usted obtiene la misma respuesta yendo de izquierda a derecha y yendo de arriba hacia abajo.<br /><br /><br /><br />Presione una tecla para continuar<br /><br /><br /><br /></div>"
                };

                var m_right1 = {
                    type: "text",
                    text: "<div class = matrizlimit><div class= centered><div class='centered'><img src ='img/1A.png' /></div><br /><br /><div class='centered'><img src ='img/1B.png' </img></div><br /><br /><div class='matrizfeedback'>Eso es correcto.<br />Cuando usted va e izquierda a derecha en la fila de arriba la estrella azul cambia a un circulo amarillo. <br /><br />"+
                             "Esto quiere decir que cuando usted va de izquierda a derecha en la de abajo la estrella azul tambi&eacute;n deberia cambiar a un circulo amarillo. "+
                             "Cuando usted va de arriba hacia abajo en la primera columna los cuadros tienen la misma forma y el mismo color: estrellas azules. <br /><br />"+
                             "Esto quiere decir que cuando usted va de arriba hacia abajo en la segunda columna los cuadros tambi&eacute;n deben tener la misma forma y el mismo color: circulos amarillos. "+
                             "Usted obtiene la misma respuesta yendo de izquierda a derecha y yendo de arriba hacia abajo.<br /><br /><br /><br />Presione una tecla para continuar<br /><br /><br /><br /></div>"
                };

                var m_wrong2 = {
                    type: "text",
                    text: "<div class = matrizlimit><div class= centered><div class='centered'><img src ='img/2A.png' /></div><br /><br /><div class='centered'><img src ='img/2B.png' </img></div><br /><br /><div class='matrizfeedback'>Eso no es correcto. <br />Cuando usted mira los cuadros de izquierda a derecha, usted puede ver que ellos<br />"+
                             "est&aacute;n en el siguiente orden: c&iacute;rculo grande, c&iacute;rculo peque&ntilde;o, c&iacute;rculo grande, c&iacute;rculo peque&ntilde;o, c&iacute;rculo grande.<br /><br />"+
                             "El c&iacute;rculo peque&ntilde;o va en el cuadro con un signo de interrogaci&oacute;n porque es la opci&oacute;n que mantiene el orden: un c&iacute;rculo peque&ntilde;o va luego de un c&iacute;rculo grande.<br />"+
                             "<br /><br /><br /><br />Presione una tecla para continuar<br /><br /><br /><br /></div>"
                };

                var m_right2 = {
                    type: "text",
                    text: "<div class = matrizlimit><div class= centered><div class='centered'><img src ='img/2A.png' /></div><br /><br /><div class='centered'><img src ='img/2B.png' </img></div><br /><br /><div class='matrizfeedback'>Eso es correcto.<br />Cuando usted mira los cuadros de izquierda a derecha, puede ver que ellos siguen este orden: "+
                             "c&iacute;rculo grande, c&iacute;rculo peque&ntilde;o, c&iacute;rculo grande, c&iacute;rculo peque&ntilde;o, c&iacute;rculo grande. <br /><br />"+
                             "El c&iacute;rculo peque&ntilde;o va en el cuadro con un signo de interrogaci&oacute;n porque es lo que mantiene el mismo orden que los anteriores.<br /><br />"+
                             "<br /><br /><br /><br />Presione una tecla para continuar<br /><br /><br /><br /></div>"
                };

                var m_conditional1_1 = {
                    timeline: [m_wrong1],
                    conditional_function: function(data){
                        if(condition1 === false){
                            return true;
                        } else {
                            return false;
                        }
                    }
                };

                var m_conditional1_2 = {
                    timeline: [m_right1],
                    conditional_function: function(data){
                        if(condition1 === true){
                            return true;
                        } else {
                            return false;
                        }
                    }
                };

                var m_conditional2_1 = {
                    timeline: [m_wrong2],
                    conditional_function: function(data){
                        if(condition2 === false){
                            return true;
                        } else {
                            return false;
                        }
                    }
                };

                var m_conditional2_2 = {
                    timeline: [m_right2],
                    conditional_function: function(data){
                        if(condition2 === true){
                            return true;
                        } else {
                            return false;
                        }
                    }
                };

                var matrizstarter={
                    type: "instructions",
                    pages: ["<div class = centerbox>"+
                           "<p class = center-block-text>"+
                           "Ahora deber&aacute; seguir respondiendo, pero no recibir&aacute; avisos indicando <br />si su respuesta es correcta o incorrecta.<br /><br />"+
                           "</p></div>"],
                    allow_keys: false,
                    show_clickable_nav: true,
                    timing_post_trial: 50,
                    data:{trialid: "Instructions_Matriz"}
                };

                var matrices = {
                  //type: "survey-multi-choice",
                  type: "survey-multi-choice1",
                  timeline:[
                    /*{
                        questions: ["<div class='centered'><img src ='img/1A.png' /></div><br /><br /><div class='centered'><img src ='img/1B.png' </img></div>"],
                        data: {trialid: "MP_01"},
                        horizontal: true
                        //options: ["Opcion1","Opcion2","Opcion3","Opcion4","Opcion5"]
                    },
                    {
                        questions: ["<div class='centered'><img src ='img/2A.png' /></div><br /><br /><div class='centered'><img src ='img/2B.png' </img></div>"],
                        data: {trialid: "MP_02"}
                        //options: ["Opcion1","Opcion2","Opcion3","Opcion4","Opcion5"]
                    },*/
                    {
                        questions: ["<div class='centered'><img src ='img/3A.png' /></div><br /><br /><div class='centered'><img src ='img/3B.png' </img></div>"],
                        data: {trialid: "MP_03"}
                        //options: ["Opcion1","Opcion2","Opcion3","Opcion4","Opcion5"]
                    },                        
                  ],
                  options: [["Opcion1","Opcion2","Opcion3","Opcion4","Opcion5"]],

                    horizontal: true,
                    required: 'true',
                };

                var experimento_matrices = [];

                experimento_matrices.push(matrizexplanation);
                experimento_matrices.push(matriz_practice_1);
                experimento_matrices.push(m_conditional1_1);
                experimento_matrices.push(m_conditional1_2);
                experimento_matrices.push(matriz_practice_2);
                experimento_matrices.push(m_conditional2_1);
                experimento_matrices.push(m_conditional2_2);
                experimento_matrices.push(matrizstarter);
                experimento_matrices.push(matrices);


                    jsPsych.init({
                        timeline: experimento_matrices,
                        on_finish: function(){
                        jsPsych.data.localSave('second_battery_results.csv', 'csv');
                        },
                        on_trial_start: function(){
                            console.log("***************************",test);    
                        },
                        default_iti: 0
                    });                
        </script>
        </div>
    </body>
</html>
N7 Mech
  • 110
  • 7

1 Answers1

0

Option C is your best bet given the current state of the library. The array containing the timeline is converted into an internal set of TimelineNode objects when you call jsPsych.init(), and it can't be modified when the experiment is running.

Version 6.0 of the library will add the ability to insert timelines onto the end of the experiment, which would open up a new way of solving this. This feature is available on the master branch of the GitHub repository, but that branch is not stable.

Josh
  • 2,105
  • 1
  • 14
  • 14