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

Hi all. I am making an experiment that has four possible conditions (LL, LR, RL, RR). I need each of these conditions to have 30 trials each but also be random (thus random, but equal trails among four conditions). I think there is something wrong with the logic of the code. It works for some combinations of trials for the four conditions but I believe I just got lucky on the combination of trials not hitting 30 before another combinations of LL, LR, RL, RR hit 30 trials. Thus the issue with the code is it will stop short of 120 trials when one or two conditions are met. Any ideas how to update this code to get all four conditions to be random but 30 equal trials? (p.s.; the beginning of the code gives low to high and high to low frequencies so participants know what a low and high frequency should feel like to make a response).

int viboutL = 5;

int viboutR = 6;

int ButtonL = 4;

int ButtonR = 7;

int buttonStateL;

int buttonStateR;

float time;

int run;

int count;

int NUM_TRIALS = 120;

int lower_limit = 0;

int upper_limit = NUM_TRIALS;

int zero = 0;

int max = NUM_TRIALS;

int LL = 0;

int LH = 0;

int RL = 0;

int RH = 0;

void setup()
{

  int count = 0;

  Serial.begin(9600);

  randomSeed(analogRead(0));

  pinMode(viboutL, OUTPUT);

  pinMode(viboutR, OUTPUT);

  pinMode(ButtonL, INPUT_PULLUP);

  pinMode(ButtonR, INPUT_PULLUP);

  run = 0;
}

void loop()
{
 if ((digitalRead(ButtonL) == 0) && (digitalRead(ButtonR) == 0))
  {
    count = 0;

    delay(500);

   analogWrite(viboutL, 64);

   analogWrite(viboutR, 64);

   delay(2000);

   analogWrite(viboutL, 0);

   analogWrite(viboutR, 0);

   delay(1000);

   analogWrite(viboutL, 255);

   analogWrite(viboutR, 255);

   delay(2000);

   analogWrite(viboutL, 0);

   analogWrite(viboutR, 0);

   delay(2000);

   analogWrite(viboutL, 255);

   analogWrite(viboutR, 255);

   delay(2000);

   analogWrite(viboutL, 0);

   analogWrite(viboutR, 0);

   delay(1000);

   analogWrite(viboutL, 64);

   analogWrite(viboutR, 64);

   delay(2000);

   analogWrite(viboutL, 0);

   analogWrite(viboutR, 0);

   delay(5000);

    if (run == 0) // if run = 0 it will move onto next line
    {
      run = 255; // sets run to 255
    }
    else // if run did not = 0 then it goes to this line
    {
      run = 0; // sets run to 0 i.e. one can press both buttons again to start program
    }
  }

  // if run is > 0
  if (run > 0) // if run is higher than 0 then it will move onto the experiment

  {

    if (count < NUM_TRIALS)
    {

      delay(500);

      int SIDE = random(lower_limit, upper_limit);

      int LOHI = random(zero, max);

      if (SIDE < NUM_TRIALS / 2)
      {
        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 ((LOHI < NUM_TRIALS / 2) && LL < 30) {

          zero++;

          analogWrite(viboutL, 64);

          Serial.print("Low");

          Serial.print(",");

          LL = LL + 1;
        }
        else if (LH < 30)
        {
          max--;
          
          analogWrite(viboutL, 255);

          Serial.print("High");

          Serial.print(",");

          LH = LH + 1;
        }
        else if ((LH >= 30) && (LL >= 30)) 
        {return;}

        // Keep track of if they got it wrong or not
        int IncompatibleButton = 0;

        while (digitalRead(ButtonL) == 1)
        {
          // While you are waiting on a left click check also for a right click. If they click right then
          // remember that my setting the variable
          if (digitalRead(ButtonR) == 0)
          {
            IncompatibleButton = 1;

            // This magic keyword will force the while loop to end
            break;
          }
        }

        if (IncompatibleButton == 1)
        {
        analogWrite(viboutL, 0);

        analogWrite(viboutR, 0);

          Serial.print("Right Button");

          Serial.print(",");

          time = micros() - time;

          Serial.print(time / 1000000, 6);

          Serial.print(",");

          Serial.println(input_delay/1000); //last thing printed for current trial

          delay(2000);
        }
        else
        {
          // This is your currently existing happy path
         analogWrite(viboutL, 0);

         analogWrite(viboutR, 0);

          Serial.print("Left button");

          Serial.print(",");

          time = micros() - time;

          Serial.print(time / 1000000, 6);

          Serial.print(",");

          Serial.println(input_delay/1000); //last thing printed for current trial

          delay(2000);
        }

       
      }

      if (SIDE >= NUM_TRIALS / 2)
      {
        upper_limit--;
        
        Serial.print("0,");

        Serial.print(count + 1);

        Serial.print(",");

        Serial.print("Right");

        Serial.print(",");

        float input_delay = random(0, 5000);

        delay(input_delay);

        time = micros();

        if ((LOHI < NUM_TRIALS / 2) && (RL < 30)){

          zero++;

          analogWrite(viboutR, 64);

          Serial.print("Low");

          Serial.print(",");

          RL = RL + 1;
        }
        else if (RH < 30) {
          max--;

          analogWrite(viboutR, 255);

          Serial.print("High");

          Serial.print(",");

          RH = RH + 1;
        }
        else if ((RH >= 30) && (RL >= 30)) 
        {return;}

        // Keep track of if they got it wrong or not
        int IncompatibleButton = 0;

        while (digitalRead(ButtonR) == 1)
        {
          // While you are waiting on a left click check also for a right click. If they click right then
          // remember that my setting the variable
          if (digitalRead(ButtonL) == 0)
          {
            IncompatibleButton = 1;

            // This magic keyword will force the while loop to end
            break;
          }
        }

        if (IncompatibleButton == 1)
        {
         analogWrite(viboutR, 0);

         analogWrite(viboutL, 0);

          Serial.print("Left button");

          Serial.print(",");

          time = micros() - time;

          Serial.print(time / 1000000, 6);

          Serial.print(",");

          Serial.println(input_delay/1000); //last thing printed for current trial

          delay(2000);
        }
        else
        {
          // This is your currently existing happy path
          analogWrite(viboutR, 0);

          analogWrite(viboutL, 0);

          Serial.print("Right button");

          Serial.print(",");

          time = micros() - time;

          Serial.print(time / 1000000, 6);

          Serial.print(",");

          Serial.println(input_delay/1000); //last thing printed for current trial

          delay(2000);
        }
      }

      count = count + 1;
    }
  }
}

Can you explain what the user needs to do and what the circuit looks like?

You need to be clear about what this means because what you describe is not random, especially towards the later trials because the states of the later trials becomes increasingly dependent on the outcome of the earlier trials. For a simple example consider the outcome of 10 tosses of a fair coin with the restriction that heads and tails should be equal. The first outcome can be randomly heads or tails. After 9 tosses with 5 heads and 4 tails the condition requires the 10th toss to be tails, which is not at all random.

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.

I have used the same circuit for four other experiments so I know the circuit is not the issue. The circuit has two buttons and two wires for two response buttons. The motors run off an external battery. I don't have the updated schematic and I am not an engineer so I don't know the lingo. I can get more detailed information soon and update.

I think OP means a random sequence of 120 draws

An easy way to do this at the expense of memory is to take an array with 120 entries

enum Choice {LL, LR, RL, RR};
Choice sequence[120];

And initialize the array with 30 LL, 30 LR, 30 RL and 30 RR, then you apply a fisher Yates shuffle to the array and you have a random sequence with the exact count of each choice,

Then you only need to walk through the array from index 0 to index 119, the content tells you what choice was selected

If memory is an issue then you can divide the size of the array by 4 by using 4x two bits per byte to represent 4 choices. It makes the shuffle and walkthrough a bit more complicated but not rocket science

1 Like

Correct. To clarify, I originally had the code to be actually random but realized I need it to be pseudo-random. Thus, a count was added to "fill in" the conditions that did not hit 30 trials and to stop the conditions that did hit 30 trials.

I thought about doing this but tried to add something simple to my prior code to avoid changing things a lot. I have an Arduino uno rev3. Would this arduino be limited in memory to do this?

Do you not think it is possible to just adjust this quota system to work?

Did you write this code? In what way did you think through the logic and plan for what you ended up writing?

It may sound boring or old fashioned, but I suggest you write a thorough text description of how this game plays out, then draw a flowchart for how a process might implement the playing out.

No. The use of loops within the loop() function and the dependence upon delay() for any timing I can see happening in your sketch makes this a very poor point from which to progress.

For the simple question your title asks, I would have said exactly what @J-M-L told you. There are shuffling algorithms that are easier to understand and implement, but a shuffle of a deck of 120 with 30 each of four different cards is def the way to go.

As mentioned, if space for such an array becomes unavailable, there are ways to code around that in a special case like this.

But you should be able to get a shuffle of 120 elements 30 each of four different things going as an independent sketch. This would be a nice side exercise, and the resultant sketch would have things that could be ripped out and transplanted to your larger context.

When/if space became tight, that side sketch could be brought out and enhanced for purposes of reducing the memory requirements, again without the context of the larger goal.

If you think about the entire project, you might be able to divide it into smaller pieces all of which could be developed and tested independently.

Divide and conquer.

a7

What is the effect you want to get by using return inside loop()?

Thank you for the clarification.

I got the array to populate but not the shuffle. Any errors in this?

enum Choice {L1,L2,R1,R2};
Choice sequence[120];

int rand_range(int n)
{
  int r, ul;
  ul = RAND_MAX - RAND_MAX % n;
  while ((r = random(RAND_MAX+1)) >= ul);
  r % n;
}

void shuffle_swap(int index_a, int index_b, Choice *array, int size)
{
  char *x, *y, tmp[size];

  if (index_a == index_b) return;

  x = (char*)array + index_a * size;
  y = (char*)array + index_b * size;

  memcpy(tmp, x, size);
  memcpy(x,y,size);
  memcpy(y,tmp,size);
}

void shuffle (Choice *array, int nmemb, int size)
{
  int r;

  while (nmemb > 1)
  {
    r = rand_range(nmemb--);
    shuffle_swap(nmemb, r, array, size);
  }
}

Only that you used the most code you could to make a simple job hard.

This should be very simple code. Here's some pseudocode:

  for each 0, 1, 2, and 3
     put 30 copies of that into the next 30 elements of the deck

  for a few 10s of thousands of times
     swap two random elements of the deck

Fisher-Yates it is not, but it will get the job done. I'll leave it Mr. Spock to comment on the quality of the shuffle in terms I wouldn't understand anyway.

I have to ask where you got this code. It does not seem like something you could have written, based on if you could have written it, you would not have done.

a7

I have trained an AI on 50000 lines of code I wrote from the Arduino and this is the low memory just what you'd think might work idea on the matter.

I don't know how to evaluate the quality of the deal. Obvsly the 120th card is not going to be random.

I put thing in there first to just deal at random. Somehow perhaps blinded by the glare of the sun off the water those cards look more random.

But that's I think just the nature of random - no one really wants random, they always want to tinker with it, like in this case. Understandable, but very ad hoc fixes and tinkering frequently ensues.

Random Enough Example
// serve 30 each of four different cards. Here 0, 1, 2 and 3

void setup()
{
  Serial.begin(115200);
  Serial.println("serve me!\n");

  resetServer();    // first deal
}

byte nLeft[4];

char *tags[] = {"Ace", "King", "Queen", "Jack", "No card!"};
enum cardType {ACE, KING, QUEEN, JACK, NO_CARD};

void resetServer()
{
  for (byte ii = 0; ii < 4; ii++)
    nLeft[ii] = 30;
}

byte deal0()
{
  return random(4);
}

byte deal()
{
  byte ii;

  for (ii = 0; ii < 4; ii++)
    if (nLeft[ii]) break;

  if (ii >= 4) {
    Serial.println("Error, no more cards!");
    for (; ; );
  }

  byte card = NO_CARD;
  while (card == NO_CARD) {
    card = random(4);
    if (nLeft[card]) {
      nLeft[card]--;
    }
    else card = NO_CARD;
  }
  
  return card;
}

void loop() {
  static byte line = 0;

  Serial.print(line);
  Serial.print(" ");
  Serial.println(tags[deal()]);

  line++;
  if (!(line % 120)) {
    Serial.flush();
//    exit(0);
    resetServer();
  }
}

Added: I thought maybe ,and running both algorithms does result in identical "shuffle" up to a certain point; as I type my claim about why that happens and where, I am simultaneously debunking it, so never mind for now.

And with minutes to spare - they agree until the fancy shuffle runs out of one kind of card. You can see the point at which each card dies - the last two standing are of course the last two in two stacks. The Jack was the winner of the tontine. In the random deal, two Aces were at a standoff.

a7

It depends what the rest of the code needs to do. 120 bytes of RAM is manageable easily if you don’t do much else.

Am I Mr Spock?

With fisher Yates you don’t need to do

Only one walk through the array

The Fisher-Yates shuffle algorithm randomly shuffles an array by iteratively swapping each element with a randomly chosen element from the remaining unshuffled portion of the array. This process ensures that each element has an equal probability of being placed in any position within the shuffled array, resulting in a uniform distribution (which means the sequence is random - if the random generator is tezlly good enough randomizer).

The algorithm is

  • Start iterating through the array from the last element to the second element.
  • For each iteration, generate a random index within the range of the unshuffled portion of the array.
  • Swap the current element with the element at the randomly chosen index.
  • repeat for next iteration

➜ The array is now randomly shuffled.

1 Like

No, no. If I was gonna call you out, I wouldn't hide. :expressionless:

I am familiar with Fisher Yates and use it when I need to shuffle. It is beautiful and runs in the linear time and only needs N random numbers, or N - 1

The 100000 swaps thing is def low rent, and I only used it for expedience. Processors are fast enough so this is a viable if ugly algorithm.

And the new one i am working in has the same problem of having to stab wildly near the end of the deck, but I have found a simple idea to complicate the code and avoid that.

So I hope it will run in linear time. Just quite a bit more time than Fisher Yates but, in this special,case of a 30 * 4 = 120 card deck woukd not need to store 120 cards - it can deal one of the four cards each time it is called.

It is equivalent to random until it can't be, so the comprise is crummy randomness at the end of the deck. But something has to give if you can't have the array of 120 and shuffle it properly, whether it be Fisher Yates or not.

Irony defined would be someone using my shuffle dealer to fill an array of 120 which would end up with 30 each of four different cards…

a7

ok :slight_smile: :+1:

If the condition of the loop is not met, it can exit.

I got this code from a forum on here explaining how to do the fisher yates shuffle. It seems a lot of people suggest this method but there is no great way to code in arduino, as least what I have been searching through.

I helped write this code. It has been a partner project. Neither of us are arduino experts and learning as we go. I am finding it difficult to interpret how to actually code the fisher yates shuffle so if anyone has some mock code for doing this with an array that is much easier, that would be very helpful :slight_smile:

here is a piece of code you can use as an example :

const char * cardNames[] = {"LL", "LR", "RL", "RR"};
constexpr size_t cardCount = sizeof cardNames / sizeof * cardNames;
constexpr size_t drawCount = 30;
byte sequence[drawCount * cardCount]; // holding the index of the cardName

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 printSequence() {
  Serial.println("Generated sequence:");
  for (size_t i = 0; i < drawCount * cardCount; i++) {
    Serial.print(cardNames[sequence[i]]);
    Serial.write(' ');
  }
  Serial.println();
}

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

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

void loop() {}