1

I have an array of 1000 data, but for some reason, I can only access 39.9% of these data.

This issue arises in a reaction time game that I am coding using a JavaScript library called jsPsych. In the game, a user must click a button before an image disappears from their screen. The game is rigged so that the user maintains a 20% success rate across trials.

I accomplish this rigging using ex-Gaussian data. Specifically, I have an array of 1000 ex-Gaussian data, and at the start of each trial, I choose a number from the bottom 20% of these data to represent how long the image will last on the user's screen. The result is that the user will have only a 20% chance of responding to the image quickly enough. The goal of this process is to force the user to have a 20% success rate across trials.

If the user's ongoing success rate happens to be greater than 20%, I decrease the likelihood of succeeding in each trial by 5% until the success rate is 0.2. Similarly, if the user's ongoing success rate is less than 20%, I increase the likelihood of succeeding in a trial by 5% until the success rate is 0.2.

The problem is that I cannot increase the success likelihood past 0.399. It should be able to go all the way up to 1.0, in which case I would be sampling from all of my ex-Gaussian data. However, if I allow my game to run without touching the button, the success likelihood increases in increments of 0.5 - as it should - until it reaches 0.399. Then, it stops increasing.

I do not know why that is happening, or why the success likelihood is even able to be 0.399 when it is supposed to increase in increments of 0.5.

You can view my code below. Please note that you would need to check the data I log to the console to get a clear picture of the problem I am having.

// this is just some supporting code
const jsPsych = initJsPsych();
const game = {};

// the ex-Gaussian data, sorted in ascending order
const dist = [];
for (let i = 0; i < 1000; i++) {
  dist.push(jsPsych.randomization.sampleExGaussian(180, 20, 1, positive = true))
}
dist.sort(function(a, b) {
  return a - b
})
console.log("Array of ex-Gaussian data:" + dist)

// initial success likelihood = 20%
let success_likelihood = 0.2;

game.timeline = [{
  timeline: [{
      // wait for image to appear
      type: jsPsychHtmlButtonResponse,
      stimulus: "<div style='visibility:hidden;'>image</div>",
      choices: ["Click me!"],
      trial_duration: 1000,
      response_ends_trial: false,
      // place feedback element here, albeit hidden, to keep page formatting consistent
      prompt: "<div>Click the button as soon as the image appears.</div><div style='visibility:hidden; background-color:green; padding:20px;'>Success</div>",
    },
    {
      // show image
      type: jsPsychHtmlButtonResponse,
      stimulus: "<div>image</div>",
      choices: ["Click me!"],
      trial_duration: function() {
        // player must click button within trial duration to succeed
        // player has x% chance of success on each trial
        // sample trial duration from lower x% of rt distribution
        let cutoff = 1000 * success_likelihood;
        // subtract cutoff - 1 because array index starts at 0
        return dist[cutoff - 1];
      },
      data: {
        type: "game"
      },
      prompt: "<div>Click the button as soon as the image appears.</div><div style='visibility:hidden; background-color:green; padding:20px;'>Success</div>",
    },
    {
      // feedback
      type: jsPsychHtmlButtonResponse,
      stimulus: "<div style='visibility:hidden;'>image</div>",
      choices: ["Click me!"],
      trial_duration: 2000,
      response_ends_trial: false,
      prompt: function() {
        var last_trial = jsPsych.data.getLastTrialData();
        var rt = last_trial.trials[0].rt;
        // if rt === null, player failed to press button within span of trial
        if (rt != null) {
          return "<div>Click the button as soon as the image appears.</div><div style='background-color:green; padding:20px;'>Success</div>";
        } else {
          return "<div>Click the button as soon as the image appears.</div><div style='background-color:red; padding:20px;'>Missed</div>";
        }
      },
    }
  ],
  on_timeline_finish: function() {
    // re-evaluate success rate after each trial 
    let total_trials = jsPsych.data.get().filter({
      type: "game"
    });
    let success_trials = total_trials.select('rt').subset(function(x) {
      return x != null;
    });
    let success_rate = success_trials.count() / total_trials.count();
    console.log("Success rate:" + success_rate)
    // adjust success likelihood +-5% until it reaches 0.2
    if (success_rate > 0.2) {
      success_likelihood -= 0.05;
    } else if (success_rate < 0.2) {
      success_likelihood += 0.05;
    }
    console.log("Success likelihood:" + success_likelihood)
  },
  loop_function: function(data) {
    // stop game once player attains 12 success trials
    let success_trials = jsPsych.data.get().filter({
      type: "game"
    }).select('rt').subset(function(x) {
      return x != null;
    }).count();
    if (success_trials != 12) {
      return true;
    } else {
      return false;
    }
  }
}]

// run code
jsPsych.run([game])
<!DOCTYPE html>
<html lang="en-CA">

<head>
  <meta charset="UTF-8" />
  <title>Demo</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <script src="https://unpkg.com/jspsych@7.1.2/dist/index.browser.min.js"></script>
  <link href="https://unpkg.com/jspsych@7.1.2/css/jspsych.css" rel="stylesheet" />
  <script src="https://unpkg.com/@jspsych/plugin-html-button-response@1.1.0/dist/index.browser.min.js"></script>
  <script src="https://unpkg.com/@jspsych/plugin-html-keyboard-response@1.1.0/dist/index.browser.min.js"></script>
</head>

<body></body>

</html>

Does anyone know why the access to my array is restricted?

puzzleGuzzle
  • 101
  • 10
  • What keeps `success_likelihood` from falling below zero, causing `dist[cutoff - 1]` to throw an array index out of bounds error? – phatfingers Apr 10 '22 at 18:42
  • @phatfingers There is no such thing as a "index out of bounds" error in javascript. If you access a nonexistent index in an array (for instance `let x = arr[-1]`) it will just give you `undefined` but not throw an error ... – derpirscher Apr 10 '22 at 19:06

3 Answers3

3

First of all, you are not increasing your success_likelyhood at all, but that is probably only a typo.

Your second problem is this piece of code

let cutoff = 1000 * success_likelihood;
return dist[cutoff - 1];

Because of how floating point arithmetic works, sooner or later your success_likelihood will be a number that has more than 3 decimal places. And when that happens, 1000 * success_likelihood will be something like 399.999999 and thus dist[cutoff -1] will return undefined which causes your code to stop. Use

let cutoff = Math.round(1000 * success_likelihood)
derpirscher
  • 14,418
  • 3
  • 18
  • 35
1

success_likelihood only gets smaller, never bigger according to this?

if (success_rate > 0.2) {
  success_likelihood -= 0.05;
} else if (success_rate < 0.2) {
  success_likelihood;
}
KYL3R
  • 3,877
  • 1
  • 12
  • 26
1

Your success_likelihood starts at 0.2 and decreases by 0.05 each time the user succeeds. That means if the user gets 4 correct in a row, your success_likelihood drops to zero, causing trial_duration to be set to undefined. Floating point rounding errors are common, so your 0.399 is probably more like 0.3999999, or 4 right out of 10.

You might consider reducing the current remaining success_likelihood value by 5% each time by multiplying by 0.95 rather than subtracting 0.05.

phatfingers
  • 9,770
  • 3
  • 30
  • 44