Code not continuing after hitting one combination of conditions (logic issue)

Would something like this mock code work, without the code stopping once one condition is met at 30 trials? At this point, I just need the four conditions to be shuffled, equal is trials (30), but not the same order for each time I run the code.

Min = 1
max = 120
threshold1 = 30
threshold2 = 60 // Not really needed but it can help to visualize
threshold3 = 90

loop 120 times
    rand = generate a random integer between min and max
    if rand > threshold3
        Do action 1
        max = max - 1
    else if rand > threshold2
        Do action 2
        threshold3 = threshold3 - 1
        max = max - 1
    else if rand > threshold1
        Do action 3
        threshold1 = threshold1 + 1
        min = min + 1
    else
        Do action 4
        min = min + 1

that would not work - try walking through it manually a few times

1 Like

Ok, this code makes sense. The array gets shuffled, stored in the same array, and then you have it so it can be printed. How would you implement this into my original code to pull each count of the array? I think my problem is seeing how you can 'walk' through the array to get a condition to apply for each of the 120 trials.

You walk through arrays by maintaining an index, just an integer variable that starts at 0, to which you add 1 to access the next card, and which signals the end of the deck when kncremting the index results in 120.

void loop()
{
  static int cardIndex;

  int nextCard = cardArray[cardIndex];

  Serial.print("the next card is ");
  Serail.println(nextCard);

  cardIndex += 1;  // next card next time unless…

  if (cardIndex >= 120) {
    Serial.println(" out of cards ");
    Serial.flush();

    while (1);     // forever. die here.
  }
}

So you see the loop runs freely, every time through you can handle the next card, and when you run out of cards you can decide what to do about that better than dying.


When I asked about who wrote the code, I was referring to that late obtuse fill and shuffle code, not you original code what was aiming for the real goal. I haven't even looked at that to see what the larger problem is, was focused laser like on the shuffling thing.

I look later through a bigger window.

a7

well instead of printing the "card" name just test the sequence value cardNames[sequence[i]]) it will be 0,1,2 or 3 if you have 4 "cards". so a switch() / case on this could let you do what you want


or you could use an array of action functions that you call as you traverse the sequence

// declare our action functions
void actionLL();
void actionLR();
void actionRL();
void actionRR();

// define an array of function pointers
void (*actionFunctions[])() = {actionLL, actionLR, actionRL, actionRR};

// count of actions
constexpr size_t actionCount = sizeof actionFunctions / sizeof * actionFunctions;

// how many draws for each action
constexpr size_t drawCount = 30;

// the array defining the sequence 
byte sequence[drawCount * actionCount]; // holding the index of the actions

void fisherYatesShuffle(byte arr[], size_t size) {
  randomSeed(analogRead(A0));
  for (size_t i = size - 1; i > 0; --i) {
    size_t j = random(i + 1);
    byte temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
  }
}

void actionLL() {
  Serial.println("action LL");
}

void actionLR() {
  Serial.println("action LR");
}

void actionRL() {
  Serial.println("action RL");
}

void actionRR() {
  Serial.println("action RR");
}

void action() {
  Serial.println("Handling sequence:");
  for (size_t actionIndex = 0; actionIndex < drawCount * actionCount; ++actionIndex) {
    Serial.print(actionIndex); Serial.write('\t');
    actionFunctions[sequence[actionIndex]](); // call the functions
  }
}

void generateSequence() {
  // initialize the array
  for (size_t actionIndex = 0; actionIndex < actionCount; actionIndex++)
    for (size_t i = 0; i < drawCount; i++)
      sequence[actionIndex * drawCount + i] = actionIndex;
  // shuffle the array
  fisherYatesShuffle(sequence, drawCount * actionCount);
}

void setup() {
  Serial.begin(115200);
  generateSequence();
  action();
}

void loop() {}

(typed here, untested)

Yes, thank you. A complete it compiles and we can try it for ourselves is best.

@hapticnovice001 you might ever want to read the wikipedia article on Fisher Yates. When you have more programming skillz, this description

-- To shuffle an array a of n elements (indices 0..n-1):
for i from 0 to nβˆ’2 do
     j ← random integer such that i ≀ j < n
     exchange a[i] and a[j]

will be all you need to run off with the suggestion to use that algorithm.

a7

What would be the output array for this code? I tried just actionFunctions in this line of code below but it did not like that.

if (sequence[actionFunctions] == 0 || sequence[actionFunctions] == 1) // 0 is LL; 1 is LH
// the array defining the sequence 
byte sequence[drawCount * actionCount]; // holding the index of the actions

The byte array sequence holds the indices of the actions, here it is just holding 0, 1, 2 or 3 which are enough for everyone to exploit as a selection of one of the four things.

Which in the later @J-M-L code is then used as an index into a table of functions to call, one for each of 0..3:

actionFunctions[     sequence[actionIndex]          ](); // call the functions

I spaced it out so you can see the inside reference to an element of sequence.

A different function is available for each of the four kinds of things.

If you can exploit just the number 0..3 in your code, forget about the table of pointers to functions. That does qualify as not beginner-ish.

Just use the number you find at sequence[K] to do the Kth of the 120 available.

a7

Yeah, I get that but if I am trying to pull from the sequence for this code below, what should the output sequence be called after shuffling? The code is giving me this error

Compilation error: expected primary-expression before ']' token

for the lines of code like this in the larger code I have at the end.

if (sequence[] == 0 || sequence[] == 1) // 0 is LL; 1 is LH
 if (count < NUM_TRIALS)
    {

      delay(500);

      int SIDE = random(lower_limit, upper_limit);

      int LOHI = random(zero, max);

      if (sequence[] == 0 || sequence[] == 1) // 0 is LL; 1 is LH
      {
        lower_limit++;
        
        Serial.print("0,");
        
        Serial.print(count + 1);
        
        Serial.print(",");

        Serial.print("Left");

        Serial.print(",");

        float input_delay = random(0, 5000);

        delay(input_delay);

        time = micros();

        if (sequence[] == 0 || sequence[] == 2) {  // 0 is LL; 2 is RL

          zero++;

          analogWrite(viboutL, 64);

          Serial.print("Low");

          Serial.print(",");

          LL = LL + 1;
        }
        else if (sequence[] == 1 || sequence[] == 3) // 1 is LH; 3 is RH
        {
          max--;
          
          analogWrite(viboutL, 255);

          Serial.print("High");

          Serial.print(",");

          LH = LH + 1;
        }

You need to say which element, here I am testing the one at K, presumably my variable being used to step through the array:

if (sequence[K] == 0 || sequence[K] == 1) // 0 is LL; 1 is LH

Read again #24 imagining sequence where I used czrdArray.

a7

it would be really helpful if you would take a step back a describe in details the expected requirements for the code from a user perspective, what should happen ?

you said

The user is completing 120 trials that are a mix of the four conditions (LL, RL, LR, RR). They are responding to a high or low frequency. They respond with the left button when they receive a low frequency and respond with the right button for a high frequency. The frequencies can occur on either the left or right forearm (where the vibration motors are placed). I need the four conditions to be random but equal (for statistical analysis at the end of the experiment). Thus the total trials should be 120 and then 30 for each condition (divisible by 4). I think the issue is the code is running into a wall by choosing a random number that meets the conditions and then maxes out somehow.

➜ how LL LR RL RR match this? does one mean left or right arm and the other high or low freq?
➜ how long does the user have to press the answer button ?

Can do! I tried the putting the count for the index for the array sequence that was shuffled but it is not liking that.

Participants have a left and right vibration motor attached to their forearms. Participants have to press a button as fast as they can based on the frequency of the vibration. The vibration can be low or high and can appear on either the left or right forearm. Participant then press the left button when it is a low frequency, when it occurs on either the left OR right forearm. And participants press the right button when it is a high frequency, no matter if it occurs on either the left OR right forearm. I need reaction time in seconds and there to be a random delay between trials, as well as keep track of each trial number the response is made based on the four combinations. The output would look like this in excel from the data streamer.

Previously, we only had the vibration occur on either the left or right forearm with the same frequency but adding this second condition of low and high frequency is a pain to get it to be puedo-random. The four conditions need to be mixed between the 120 trials but have equal number of conditions which is why 30 was chosen.

OK, I think that part is in the bag.

When you say things like

You would get better help if you shared the code. Either make a small sketch that is not liked in the same way, or share the hole thing. Don't worry about using up a few thousand bytes. It really is best to analyze compiler errors or logic faults in a full context.

If it is indeed compiler errors, copy and paste the first few dozen lines.

I will be trying to build your project virtually, the additional or repeated explanation is good.

I wonder however about the importance of getting exactly 30 each every 120 trials. If subjects are going to be, um, subjected to multiple runs, it seems like just getting close enough to 0.25 each would suffice.

On the other hand, it's your trip, so.

This will be easy code, relatively, as it is the same process every time through the loop() for 120 times, then some reporting.

How short to how long a random delay are you planning for? Of course for now those should be expressed as adjustable limits - during testing you may find you'll want to turn down things that make testing take longer than they will when finally tested and deployed.

a7

So in pseudocode

A random time will be waited out

A randomized vibration will be made

User input will be awaited for (what happens if too long goes by? wait forever?)

The trial #, stimulus, frequency, response, time, and delay will be (printed for now)

Rinse and repeat until 120

Is the delay printed on line N the time before line N + 1 was conducted, or is it the time that was delayed before line N?

There will be no neeed for any loops or delays within this pattern. This is where the finite state machine FSM will come in - every loop pass will be in some state, and decide based on the passge of time or the appearance of user input (or both) what to do and whether the state (step) needs to be bumped along to the next step, or if it is time to bump to the next of the 120.

I am compelled to mention also the IPO model for process control. A good fit to this goal and a good fit with FSMs.

Read about it in the abstract

IPO Model for Process Control

a7

here is an example on how you could deal with it. I'm using a finite state machine to walk through the different states. (Here is a small introduction to the topic: Yet another Finite State Machine introduction)

you can try the code here

you start a session by clicking one of the button, the number of steps per possibility is set in

constexpr size_t stepCountPerStimulus = 3; // change to 30 if you want 30 x 4 steps in the sequence

I used 3 to make it short to test (12 stimuli).

once you are done you get a report formatted for the forum, it looks like this

Trial Location Prompt Answer Reaction time(ms) Delay(ms)
1 Right High Low 783 1534
2 Left Low Low 353 1847
3 Left High Low 297 668
4 Right High Low 281 790
5 Right High Low 292 752
6 Right Low Low 365 740
7 Left Low Low 360 855
8 Left High Low 318 1767
9 Left High Low 295 1250
10 Left Low Low 290 1980
11 Right Low Low 341 1779
12 Right Low Low 290 1225

(I kept pressing the same answer)

I replaced the motors by two buzzers but you can use your motors (just need to modify the startAction() and stopAction() functions)

click to see the code
/* ============================================
  code is placed under the MIT license
  Copyright (c) 2024 J-M-L
  For the Arduino Forum : https://forum.arduino.cc/u/j-m-l

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  ===============================================
*/

#include <Toggle.h> // https://github.com/Dlloydev/Toggle

constexpr size_t stepCountPerStimulus = 3; // change to 30 if you want 30 x 4 steps in the sequence
constexpr unsigned int lowFrequency = 300;
constexpr unsigned int highFrequency = 1000;

constexpr unsigned int minDelay = 500;
constexpr unsigned int maxDelay = 2000;

constexpr byte rightArmBuzzerPin = 5;
constexpr byte leftArmBuzzerPin = 6;

constexpr byte highFreqButtonPin = 2;
constexpr byte lowFreqButtonPin = 3;
Toggle highFreqButton;
Toggle lowFreqButton;

enum : byte {LEFT_ARM, RIGHT_ARM};
enum : byte {LOW_FREQ, HIGH_FREQ};
enum : byte {LOW_FREQ_BUTTON, HIGH_FREQ_BUTTON, NO_RESPONSE};
enum : byte {IDLE, DELAY, ACTION, NEXT, SEQUENCE_TERMINATED,} state = IDLE;

struct __attribute__ ((packed)) Step {                 // 7 bytes
  byte location: 1;
  byte frequency: 1;
  byte response: 2;
  uint16_t initialDelay;
  uint32_t reactionTime;
};

constexpr size_t sequenceLength = 4 * stepCountPerStimulus;
Step sequence[sequenceLength];    // 840 bytes
size_t sequencePosition;
uint32_t startTime;

void fisherYatesShuffle(Step arr[], size_t size) {
  for (size_t i = size - 1; i > 0; --i) {
    size_t j = random(i + 1);
    Step temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
  }
}

void generateReport() {

  Serial.println(F("|Trial|Location|Prompt|Answer|Reaction time(ms)|Delay(ms)|"));
  Serial.println(F("|-|-|-|-|-|-|"));
  for (size_t i = 0; i < sequenceLength; i++) {
    Serial.write('|'); Serial.print(i + 1);
    Serial.write('|'); Serial.print(sequence[i].location == LEFT_ARM ? F("Left") : F("Right"));
    Serial.write('|'); Serial.print(sequence[i].frequency == LOW_FREQ ? F("Low") : F("High"));
    Serial.write('|'); Serial.print(sequence[i].response == LOW_FREQ_BUTTON ? F("Low") : F("High"));
    Serial.write('|'); Serial.print(sequence[i].reactionTime);
    Serial.write('|'); Serial.print(sequence[i].initialDelay);
    Serial.println('|');
  }
  Serial.println();
}

void generateSequence() {
  // initialize the array
  randomSeed(analogRead(A0));

  for (size_t i = 0; i < sequenceLength; i++) {
    if (i >= 3 *  stepCountPerStimulus) {
      sequence[i].location = LEFT_ARM;
      sequence[i].frequency = LOW_FREQ;
    }
    else if (i >= 2 * stepCountPerStimulus) {
      sequence[i].location = LEFT_ARM;
      sequence[i].frequency = HIGH_FREQ;
    }
    else if (i >= stepCountPerStimulus) {
      sequence[i].location = RIGHT_ARM;
      sequence[i].frequency = LOW_FREQ;
    }
    else {
      sequence[i].location = RIGHT_ARM;
      sequence[i].frequency = HIGH_FREQ;
    }
    sequence[i].initialDelay = random(minDelay, maxDelay + 1);
    sequence[i].response = NO_RESPONSE;
    sequence[i].reactionTime = 0;
  }
  // shuffle the array
  fisherYatesShuffle(sequence, sequenceLength);
}

void startAction(byte loc, byte freq) {
  byte buzzerPin = (loc == LEFT_ARM) ? leftArmBuzzerPin : rightArmBuzzerPin;
  unsigned int frequency = (freq == LOW_FREQ) ? lowFrequency : highFrequency;
  tone(buzzerPin, frequency);
}

void stopAction(byte loc, byte freq) {
  byte buzzerPin = (loc == LEFT_ARM) ? leftArmBuzzerPin : rightArmBuzzerPin;
  noTone(buzzerPin);
}


void setup() {
  highFreqButton.begin(highFreqButtonPin);
  lowFreqButton.begin(lowFreqButtonPin);
  pinMode(rightArmBuzzerPin, OUTPUT);
  pinMode(leftArmBuzzerPin, OUTPUT);

  Serial.begin(115200);
  Serial.println(F("➜ Press a button to get started."));
}

void loop() {
  highFreqButton.poll();
  lowFreqButton.poll();
  switch (state) {
    case IDLE:
      if (highFreqButton.onPress() || lowFreqButton.onPress()) {
        // one of the two button is pressed, we start
        generateSequence();
        sequencePosition = 0;
        startTime = millis();
        state = DELAY;
      }
      break;

    case DELAY:
      if (millis() - startTime >= sequence[sequencePosition].initialDelay) {
        // we waited long enough, action
        startAction(sequence[sequencePosition].location, sequence[sequencePosition].frequency);
        startTime = millis();
        state = ACTION;
      }
      break;

    case ACTION:
      if (highFreqButton.onPress()) {
        sequence[sequencePosition].reactionTime = millis() -  startTime;
        sequence[sequencePosition].response = HIGH_FREQ_BUTTON;
        state = NEXT;
      }
      else if (lowFreqButton.onPress()) {
        sequence[sequencePosition].reactionTime = millis() -  startTime;
        sequence[sequencePosition].response = LOW_FREQ_BUTTON;
        state = NEXT;
      }
      break;

    case NEXT:
      stopAction(sequence[sequencePosition].location, sequence[sequencePosition].frequency);
      sequencePosition++;
      if (sequencePosition >= sequenceLength) {
        state = SEQUENCE_TERMINATED;
      } else {
        startTime = millis();
        state = DELAY;
      }
      break;

    case SEQUENCE_TERMINATED:
      generateReport();
      state = IDLE;
      Serial.println(F("➜ Press a button to start a new session."));
      break;
  }
}

hope this helps

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.