Issues with Char* Array

(Andrew) #1

I hate strings in C++, I’m sure there is something silly that I’m missing here. I would appreciate any help.

#include <Arduboy2.h>
#include "arduino.h"

Arduboy2 arduboy;

const char * const LIZARD_NAMES[] PROGMEM = {"Iv\0","Jhox\0","Edres\0","Urthi\0","Odhu\0","Dulrurth\0","Irthi\0","Nurhticun\0","Netirre\0","Rudegroch\0","Shosj\0","Vaess\0","Thacherk\0","Korhti\0","Nirhty\0","Boshu\0","Repestri\0","Asharu\0","Tork\0","Shisk\0","Doskos\0","Maslo\0","Ogge\0","Otes\0","Daltekhi\0","Thothrica\0","Lo\0","Esh\0","Nalthis\0","Etrin\0","Thurdyss\0","Thargox\0","Thoggy\0","Egisort\0","Shetistink\0","Lortignonk\0"};

//lizard racing variables
byte racer[4] = {40,40,40,40};

void setup() {
  arduboy.begin();
  arduboy.setFrameRate(30);
  arduboy.initRandomSeed();
}

void loop() {
  arduboy.clear();
  if (!(arduboy.nextFrame()))
    return;
  lizardRacing();
}


void lizardRacing()
{
  //make racer list
  arduboy.clear();
  //this works fine
  arduboy.println(LIZARD_NAMES[0]);
  //get four random, unique numbers
  while (racer[1] == racer[2] || racer[1] == racer[3] || racer[1] == racer[4] || racer[2] == racer[3] || racer[2] == racer[4] || racer[3] == racer[4]){
    for(byte  i = 0; i < 4; i++) {
      racer[i] = random(1,35);
    }
  }
  //good so far
  for(byte i = 0; i < 4; i++) {
    arduboy.println(racer[i]); 
  }
  //doesn't work
  arduboy.println(LIZARD_NAMES[racer[0]]);
  arduboy.println(LIZARD_NAMES[racer[1]]);
  arduboy.println(LIZARD_NAMES[racer[2]]);
  arduboy.println(LIZARD_NAMES[racer[3]]);
  arduboy.display();
}
0 Likes

(Scott) #2

When posting code, please format it by starting with a line containing three backticks (usually the key below the ESC key at the top left, at least on US keyboards) followed by the letters cpp ,
then your code,
then add another line with 3 more backticks:

```cpp
Your code goes here
```

I edited your previous post to add this, but please do it yourself in the future.
Thanks

1 Like

(Pharap) #3

Firstly, you don’t put the null terminator in by hand, the compiler does it for you.
So "Jhox\0" -> "Jhox" etc.

Secondly, you need to store your strings like this:

const char lizardName0[] PROGMEM = "Iv";
const char lizardName1[] PROGMEM = "Jhox";
// etc

// Both consts are important
const char * const lizardNames[] PROGMEM
{
	lizardName0,
	lizardName1,
	// etc
};

const char * getLizardName(uint8_t index)
{
	return reinterpret_cast<const char *>(pgm_read_ptr(&lizardNames[index]));
}

And finally for printing a string declared as an array in progmem instead of inline with the F macro you need the __FlashStringHelper technique:

So you might want to modify the affore-demonstrated code to include the following function:

const __FlashStringHelper * getPrintableLizardName(uint8_t index)
{
	return reinterpret_cast<const __FlashStringHelper *>(pgm_read_ptr(&lizardNames[index]));
}

Also, you should be including Arduino.h as:

#include <Arduino.h>

Though that’s only needed for .h and .cpp files,
.ino files automatically inject the include at the top because of the extra processing they do.

1 Like

(Andrew) #4

Thank you for the help, your FlashStringHelper function fixed my issue.

1 Like

(Pharap) #5

Some other tips.
(Sorry, this is going to be a big info dump.)


Array indices are 0-based - the first item is item 0, not item 1.
That means racer[1] is actually the second item in the array and racer[4] is actually ouside the bounds of the array (so you’re trying to access an invalid array element).
This also means that random(1, 35) will always skip the first item in the array (item 0).

Your array has 36 entries, so to properly use random to get a random index you’d either need to do random(36) or random(0, 36).
With the random function, the first argument (the lower bound) is inclusive but the second argument (the upper bound) is exclusive (i.e. the upper bound is excluded from the possible outputs), so random(0, 36) will output a number between 0 and 35 (inclusive, i.e. 0 and 35 are included).

However, instead of having to remember how big your array is,
if you use the getSize function from here:

Then writing getSize(lizardNames) will give you the number of entries in the lizardNames array.
(Don’t worry about how it works for now (it’s effectively high-level arcane wizardry), instead just worry about how to use it.)

Then you can do random(getSize(lizardNames)) to get a valid index between 0 and getSize(lizardNames) - 1 (i.e. all valid indices).

And lastly, instead of rewriting the entire array each time, there’s a better (more scalable) way to ensure unique numbers:


// Checks whether the given nameIndex is unique in the given range of elements
bool isNameIndexUnique(uint8_t nameIndex, uint8_t count)
{
	// For all chosen elements
	for(uint8_t index = 0; index < count; ++index)
	{
		// If the nameIndex matches
		if(racers[index] == nameIndex)
		{
			// The nameIndex is not unique
			return false;
		}
	}
	
	// None of the nameIndexes matched
	// So the nameIndex is unique
	return true;
}

// Generates unique name indices into the racers array
void generateUniqueNameIndices()
{
	// For each item in racers
	for(uint8_t index = 0; index < getSize(racers); ++index)
	{
		// Stores whether or not the name index is unique
		bool unique = false;

		// Start an infinite loop
		while(true)
		{
			// Generate a name index
			uint8_t nameIndex = random(getSize(lizardNames));
			
			// If name is unique
			if(isNameIndexUnique(nameIndex, index))
			{
				// Write the nameIndex to the array
				racer[index] = nameIndex;
			
				// Break out of the infinite loop
				break;
			}
		}
	}
}

It might look like a lot more code, but it’s actually not that much larger and this should hopefully be a bit more efficient.
And most importantly, it will still work if you change the number of racers.

(I’ll admit I haven’t actually tested this, but I’m fairly confident that it should work.)


And some final minor points:

  • You don’t need to put arduboy.clear() before if(!arduboy.nextFrame()) because you clear the buffer later on.
  • You should move arduboy.clear and arduboy.display outside lizardRacing.
  • ALL_CAPS should only be used for macros (#defines)
    • (Hence why I keep writing lizardNames instead of LIZARD_NAMES)
  • Arrays should (almost) always have plural names because they’re a collection of more than one object.
    • (Hence why I wrote racers instead of racer)
0 Likes

(Pharap) #6

For future reference, it’s in the same place on British keyboards:

Grave

On various Canadian keyboards it’s near the right shift (precisely which key depends on the variant).

And for keyboards that don’t have a grave or for people who can’t find it, there’s always BB code:

[CODE]
[/CODE]
0 Likes

(Scott) #7

You can also copy and paste one or more from a post here or from a Web search.

0 Likes

(Scott) #8

That’s a matter of preference. I tend to use singular names because it usually reads better when referencing an element.

racer[0], racer[1]

0 Likes

(Pharap) #9

Based on the answers to this SO question and a quick skim of what some of the other Arduboy users do (e.g. 0, 1, 2), I’d say you’re probably the exception rather than the rule.

The only exceptions seem to be when the array represents a singular object such as maze, world or lookupTable.

0 Likes