How to exit loop after certain number of trials with JavaScript?

Oops, sorry about that. The correct version is online now:
https://run.pavlovia.org/j.adams/photonorms-2/html/

My survey flows looks like this, with the user getting stuck in the Welcome__Repeat__Loop (x99 times) if they press back on the Demographics page (which enters them into the loop - if they press Next, it’s skipped):

And the current JS I have for the Welcome routine:

for (let stimulus of [Welcome__Next]) {
    if ((stimulus.contains(Welcome__Mouse)) & (ShowClickedWelcomeNext == 0)) {
        stimulus.opacity= 0.8;
    }
    else {
        stimulus.opacity= 1;
    }
}


for (let buttonName in Welcome__Mouse.clicked_name) {
    if (Welcome__Mouse.clicked_name[buttonName] == 'Welcome__Next') { 
        ShowClickedWelcomeNext = 1;
        if (ClickedWelcome__Next.status == PsychoJS.Status.FINISHED) {
            if (nReps__Welcome__Repeat__Loop > 1) {
                Welcome__Repeat__Loop.finished = true;
                }
            continueRoutine = false;
        }
    }
}

As ever - many thanks for your time and patience for looking into this! :slight_smile:

Ok, I think this might just be small issue with your for loop syntax:

First, you should use two ampersands for the if conditional in the first loop

if ((stimulus.contains(Welcome__Mouse)) && (ShowClickedWelcomeNext == 0)) {

But, i think the real issue is the use of in to iterate through your clicked_name list. You should use of to loop through the list elements, whereas in loops through the array properties:

for (let buttonName of Welcome__Mouse.clicked_name) {

Ahh thanks, okay - I’ll definitely have to read up on best practice between ‘of’ and ‘in’.

After changing ‘in’ to ‘of’ in this instance though, the ‘Next’ button has become unresponsive, and does not register as an item that’s been clicked.

Ah, yes that breaks the rest of the loop, so you need:

for (let buttonName of Welcome__Mouse.clicked_name) {
    if (buttonName == 'Welcome__Next') { 

I need to look into why this is, but to get the behaviour I think you want, you need to go into your inner loop and select the is trials option. Now the back and forward functionality works, in addition to above change.

1 Like

Ah perfect - clicking Is Trials worked! I’m not sure I would have ever stumbled upon that just by experimenting myself.

Thanks again for the help!

Great, by the way I have put in a pull request on GitHub to have this fixed.

1 Like

Hi,

I’m also trying to implement a conditional exit to a loop in JS. Every 3rd trial, if two of the 3 trials were incorrect (determined by assigned counters, which seem to be working) the message shown should change and the loop should exit. The message seems to be changing as I would like it to, it’s only the loop exit (which I have attempted using currentLoop.finished=true;) that is not working.

The JS code I have is as follows (and I have tried with and without having Is trials selected - this does not seem to make any difference here).

if (trialCounterSTM ===2||trialCounterSTM ===5||trialCounterSTM ===8||trialCounterSTM ===11||trialCounterSTM ===14||trialCounterSTM ===17||trialCounterSTM ===20){
    if (correctCount<2){
        STM.finished=true;
        msg= 'Great, now lets try a different game';
    }
    else if (correctCount>2){
        msg = 'You are so good, lets see if you can remember an extra one!';
        correctCount=0;
    }
}
else {
    msg = 'Get ready!';
}

trialCounterSTM = trialCounterSTM+1;

As always, thank you for your help!

@jretz, I deleted the code in the End routine of your pause_2 routine as it was a duplicate of the code in the begin routine tab. I replaced your if statement with something shorter for readability. When I run this using currentLoop in the begin routine tab of pause_2, it works. So, the loop ends if I get less that 2 correct by trial 2 (3rd trial presentation):

if ([2, 5, 8, 11, 14, 17, 20].indexOf(trialCounterSTM) > -1) {
    if (correctCount<2){
        currentLoop.finished = true;
        msg= 'Great, now lets try a different game';
    }
    else if (correctCount>2){
        msg = 'You are so good, lets see if you can remember an extra one!';
        correctCount=0;
    }
}
else {
    msg = 'Get ready!';
}

Perfect - that seems to be working now - thank you.

1 Like

For some reason I seem to be having more issues with this. I have another loop which does the same thing (on every 3rd trial it should exit if >=2 of the preceding 3 trials was incorrect) later in the same experiment that does not seem to be working despite me using the same code.

Depending on whether or not ‘is trials’ is selected, I get different behaviour. When ‘is trials’ is selected, it exits the loop after a single trial whether or not the trial is correct. When ‘is trials’ is not selected, the message changes as intended depending on amount correct of preceding 3 trials, but loop does not exit. FYI, in my other very similar loop you helped me with before, ‘is trials’ is selected. Both loops I am trying to exit also have nested loops within them, but I am trying to exit outside of the nested loop so I don’t think this is the issue.

The code for the loop I am having the issues with is pretty much as above but I’ll paste it below in case there is something obvious I’ve missed.

Begin experiment:

trialCounterWM = 0;

Begin routine:

console.log("trial num", trialCounterWM);
console.log("corrCount", correctCount);

if ([2, 5, 8, 11, 14, 17].indexOf(trialCounterWM) > -1) {
    if (correctCount<2){
        currentLoop.finished=true;
        msg= "Well done!";
    }
    else if (correctCount>=2){
        msg = "You are so good, let's see if you can remember an extra one!";
        correctCount=0;
    }
}
else if ([20].indexOf(trialCounterWM) > -1) {
    msg= "Well done!";
    correctCount=0;
}
else {
    msg = "Get ready!";
}

totalTime = 0;

End routine:

trialCounterWM = trialCounterWM+1;

The output in the console shows that the counters are working fine, and the messages change, so it just seems to be the currentLoop.finished = true that is not working. I also tried using the name for the loop instead of ‘currentLoop’ but that didn’t fix it. You’ll note another variable (totalTime) is being reset - that is for a nested loop within this loop and I don’t think could be interfering (but could easily be wrong!).

Sorry for all the questions - and thanks as always for your help :slight_smile:

1 Like

Hi all,

I’ve had success with:

InnerLoopName.finished = true;

when ‘IsTrials’ for the particular loop is checked. However, this also breaks out of any outer loops too.

If one loop is nested in another, does anyone know of a way to only break out of the inner loop, but remain in the outer one? I’ve tried things like:

if (condition) {
    InnerLoopName.finished = true;
    OuterLoopName.finished = false;
{

but, this second line is seemingly ignored, and we break out of all loops.

Any help appreciated!

Thanks for the information @Jamie, I have created an issue on GitHub for this loop breaking behaviour to be fixed.

2 Likes

Hi again,

I’m back trying to sort this task after a holiday and teaching -induced hiatus. @dvbridges does it sound as though this loop breaking behaviour is the culprit for the issue I have been having with the second version of my loop? I realised that a (potentially important) difference between the version that works and the version that doesn’t, is that the version that doesn’t includes an additional nested loop with code component to exit after 5 seconds. The outer loop continues for the rest of the cycle but does not then repeat as it should.

Thanks,
Jenny

Hi @Jamie,

Did you get it to work finally? It looks like I have a similar issue with nested loops:

Thanks!

Hi, I have a similar issue. I want to end my inner loop (counting) after 10 sec without breaking the outer loop. The experiment works perfectly with builder offline. Did anyone had a similar problem? Thanks in advance!

This is my js code.

In the routine before the inner loop

Begin Routine
totalTime = 0;
timeLimit = 10;
incrementalClock = new util.Clock();

in the inner loop

Begin routine
incrementalClock.reset()

EachFrame
if (((totalTime + incrementalClock.getTime())) >= timeLimit)
{
counting.finished=true;
continueRoutine =false;
}

EndRoutine
totalTime+=incrementalClock.getTime()

@Giulia_Cristoforetti, if you click the link above, there is example code showing how to end loops. The issue is that you need to use trials.trinished = true; rather than using the actual loop name.

Thank you so much @dvbridges! it works now!

Hello,

I am also having difficulty ending a conditional loop on Pavlovia.
Here is my JS code in the Begin Experiment tab:

respcorrs = [];
minNrPractice = 30;
considerNrTrials = 30;
minAccuracy = 0.8;

And in my End Routine Tab:

respcorrs.append(resp.corr);
if ((respcorrs.length >= minNrPractice)) {
    respcorrsRecent = respcorrs.slice((- considerNrTrials));
    respcorrsSum = [respcorrsRecent].reduce( function(x,y) { return x+y; }
);
    accuracy = (Number.parseFloat(respcorrsSum) / considerNrTrials);
    if ((accuracy > minAccuracy)) {
        trials.finished = true;
        continueRoutine = false;
    }
}

The code works perfectly in Psychopy, but the routine does not end when accuracy is reached in Pavlovia. I have also tried moving the elements of code to different tabs, similar to what has been outlined above, but that hasn’t worked either. Any help would be greatly appreciated.

Many thanks,
Ellen

@dvbridges I’m using this older version of PsychoPy online and I can see that currentLoop.finished=true is being evaluated but it’s not causing the loop to end. Do you have any ideas what could be happening?

https://run.pavlovia.org/Am8302/exp3/html/