New game programming assistance

Hello much-more-intelligent-than-me friends (for example, @Pharap),

I’ve been working on my first Arduboy game after going through the tutorials and learning general C++, and after using some of what I’ve learned I’ve managed to kind of get the meat of it working. I’d love some assistance to get it even further, since I think I’ve spent enough time on the same thing to swallow some pride and ask for help.

Basically, I want to make a game that’s an endless scroller with the player’s goal being to click the A button when colliding with a synapse. Pieces of brain matter randomly spawn and act as barriers, giving the player obstacles to overcome when trying to make it to each synapse. If the player misses a synapse it’s game over.

The player character is a raspberry, or at least a growth in the brain that looks like one (technically called a cavernous angioma, I used to have one and it caused all kinds of problems so that’s my idea behind this game).

Anyway, the difficulties I’m currently having:

  • Using the built-in collision detection arduboy.collide seems to work well for the small sized obstacle, but the medium size has difficulties. Its hit box seems to be off from the actual image size. I’m printing a few things to the screen to try debugging this but I’ve run out of possible fixes here.

  • Related to the first issue, @filmote - I tried using your custom collide code and it worked well in the emulator, but when trying to compile in Arduino IDE it threw errors regarding const uint8_t PROGMEM lookup[].

  • Making sure the synapse instance will never spawn on top of an obstacle is important, but I can’t wrap my head around comparing their positions when they’re both being spawned randomly and simultaneously. How could I check the coordinates of the currently active obstacles before spawning a synapse, so I can make sure they never collide?

Once I get this stuff settled I’ll move onto the rest, like scoring and saving it to memory, sound, a nice intro screen, etc.

Here’s the game’s code. I apologize in advance for it.

I just had a quick look at your code. Its looking good!.

Where you had your collision detection I added a drawRect() so I could see what is going on.

Rect playerRect = Rect{ player.x, 
                     player.y - getImageHeight(player.image),
                     getImageWidth(player.image),
                     getImageHeight(player.image) };
                        
arduboy.drawRect(playerRect.x, playerRect.y, playerRect.width, playerRect.height );

Rect obsRect =  Rect{ matters[i].x, 
                      matters[i].y - getImageHeight(matters[i].image),
                      getImageWidth(matters[i].image), 
                      getImageHeight(matters[i].image) };
                            
arduboy.drawRect(obsRect.x, obsRect.y, obsRect.width, obsRect.height );

ArduboyRecording%20(37)

In my game, I keep the coordinates of the bottom of the dinosaur so I can test when he has landed on the ground and other things. Hence, I take the height off the image off the Y value when calculating the dinosaur’s position.

If you simply change your rectangle calcs to look like:

Rect playerRect = Rect{ player.x, 
                     player.y,
                     getImageWidth(player.image),
                     getImageHeight(player.image) };

It will work!

2 Likes

I have just made a note of this in the tutorial :slight_smile:

1 Like

Whoa! I love drawRect for this! I had no idea about that, or how helpful it is. Indeed, getting rid of subtracting those height values from everything solved the collision issue!

44%20PM

Now these rectangles actually know what they’re doing :stuck_out_tongue:

1 Like

Do you think you can solve the synapses problem now that you have a handle on the collisions?

Hopefully. I’ll keep chipping away at it. Now that the collision is working properly I’m seeing that I’ll have to change a few more things. It’s possible for a medium sized obstacle to spawn and make it impossible to pass it on either side, which would obviously be unfair!

Yep … a little unfair I guess !

It looks like the code formatting in Part 6 got a little out of whack. Sometimes markdown can be the worst!

Just press refresh!

Don’t confuse intelligence with experience or knowledge.
Presently you know more about programming than I did last decade. :P

I can’t reproduce this problem,
so I can’t help with this without an error message and/or the exact code.

Since you’re spawning everything on the right hand side of the screen and letting it slide in,
the simplest option is probably to just make sure you don’t spawn a new object before the previous one is fully onscreen.

For that case, you should probably just limit the range it can spawn in to be even more precise.

There are two ways you can go about this,
but for either case you need to figure out the coordinates that are valid and the ones that aren’t.

Once you have those, you can either rewrite your spawning code so that it tries again if the first coordinate it picked would be one of the ‘unfair’ ones,
or you can create a lookup table of valid coordinates and select a coordinate from that table.


Looking at your code I can see a number of improvements that could be made,
but I’ll just focus on the more important stuff for now.

boot()

If you call boot() instead of begin() then you should also make sure to call flashlight() to make it easier for people to upload and systemButtons() so that people can adjust their sound on startup.

Dead logic

You have some ‘dead’ logic in updateSynapses().
Your test is if(targets[i].hit == false), and then later within that block you have tagets[i].hit == true.
If the first was true then the latter cannot possibly be true, so it’s redundant (or ‘dead’).
The compiler might not be able to elide the second test because it can’t guarantee that hit hasn’t been changed some other way.

This may not actually be important in your case, but I’m considering it important because this is the kind of mistake that sometimes leads to bugs,
particularly because it implies you might not be seeing ‘the full picture’ or might be assuming something that isn’t correct.

A lot of bugs happen because people assume something to be one thing when it’s actually the opposite,
which means any logic based on that assumption ends up being flawed and you end up with bugs that seem confusing because you don’t realise that your assumption is incorrect.

x and y

Having your x and y variables be different types is somewhat surprising and probably likely to end up causing confusion.
It’s best to have them both be the same type if possible.

unsigned char vs byte vs uint8_t

Try to not mix unsigned char, byte and uint8_t.
On Arduboy they all have the same meaning, but they have different semantics.

unsigned char means ‘the smallest addressable type that is at least 8 bits’. On some systems this type could in fact be more than 8 bits, so make sure to only use it when being larger than 8 bits doesn’t matter.
uint8_t means ‘exactly 8 bits’. If a target system isn’t capable of handling exactly 8 bits then this type won’t exist, they’ll get a compiler error and hopefully realise that the code isn’t designed for a system that doesn’t have 8-bit types.
byte is Arduino only so avoid it like the plague because unlike the other two it won’t exist in a non-Arduino environment.
The most important thing is to not treat them as being interchangeable. E.g. don’t use a const byte * as a function argument and then pass a const unsigned char array into it - assume they’re incompatible types.

It won’t really matter which you use on the Arduboy, but it’s better to get into good habits earlier on, and you should at least aim to be consistent.
If in doubt, opt for uint8_t because you can rely on it to be precisely 8 bits.

Rect initialisation

Instead of doing:

Rect playerRect = Rect {
	x, y, width, height
};

You can just do:

Rect playerRect { x, y, width, height };

Or

Rect playerRect
{
	x,
	y,
	width,
	height
};

Or if you prefer

Rect playerRect(x, y, width, height);

Or

Rect playerRect = Rect(x, y, width, height);
Pointer arithmetic

Avoid doing arithmetic on pointers because it’s easy to accidentally think it’s normal arithmetic (which in turn makes it harder to spot).
I.e. instead of pointer + n prefer to do &pointer[n] (‘address of the nth element’).

Image vs Size

In drawObstacles() you do:

if (matters[i].size == Size::Medium) {
	matters[i].image = matterMedium;
} else {
	matters[i].image = matterSmall;
}

Ideally you should either set matters[i].image when the Matter is actually created,
or not store the image and just determine the right image when you need to draw it.

In your case you’re using the image to determine the size for collision, so you’re probably best off taking the route where you set image once when you create the Matter and then don’t modify it after that.

I can think of a number of other things, but they’re mostly style issues so I don’t want to bombard you with those.
(Or at least not until later when you’re nearer completion.)
Quite a few of them are about consistency.

One last thing I will say, purely as a piece of advice:

if vs continue

Sometimes intead of adding an if inside a loop and ending up with an extra level of nesting, it can be better to invert the if condition and use a continue.
E.g. instead of

for(uint_t i = 0; i < numberOfObstacles; ++i)
{
	if(matters[i].enabled)
	{
		// Blah
		// Blah
		// Blah
	}
}

Try:

for(uint_t i = 0; i < numberOfObstacles; ++i)
{
	if(!matters[i].enabled)
		continue;
	
	// Blah
	// Blah
	// Blah
}

It can take some getting used to, but I find it’s often easier to read “if(this is true) then skip the rest of the loop and try again with the next item” rather than “if(this is true) then do this long list of things in yet another level of nesting”.

Awesome idea. I’ll maybe add a justSpawned attribute to the obstacles struct and set it to true by default, then set it to false when it’s a few pixels on the screen and only spawn a synapse when that happens.

It’s funny, I considered using a lookup table not long after I sent this, so imagine my surprise this morning when I saw that you recommended it and we both thought of the same thing :stuck_out_tongue:

Thanks for all the improvements, I’ll go through those and apply them. This is another reason I started this thread in the first place, so I know I’m doing these things in good practice the first time.

1 Like

It would probably be cheaper to have a global counter since only the spawner needs to know how long to wait before spawning the next thing.

Also maybe it should be a uint8_t so it’s actually a counter,
then you could set it when you spawn an object (e.g. to the object’s width ± a small random amount) and then count it down every frame and don’t attempt to spawn anything else until the counter hits zero.

I don’t know if I’ve said this yet, but buy a duck.


A look up table is the solution that uses more memory but less time.

Retrying the randomness until it’s acceptable is slower but uses less memory.

Neither is necessarily easier than the other.

This is a good example of one of the golden rules of programming:
“Everything is a trade-off”.

(An effective programmer chooses the solution that has the right balance of wants versus needs.
Though there’s more to being good than being effective.)

My game is nearing completion! I fixed the stuff that you mentioned (that I could find, anyway; I’m not sure what the “Pointer arithmetic” part applies to).

The last hurdle I’m having trouble with is saving the high score to EEPROM. I’m using it to save a setting - whether or not a screen flash should happen, since I want to have accessibility considered in my games - and it’s working fine. When trying to implement @filmote’s score saving utility it messed with that setting. Trying to save the score to EEPROM with just a simple EEPROM.put turns it into some random huge number, which @filmote mentioned in his tutorial. How do you guys usually go about saving multiple things per game to EEPROM without them clashing with each other?

Here’s the link to my updated code.

1 Like

Sorry, this is going to be a long post.

Ah, possibly because you didn’t write that function…

Did someone else give you the getImageWidth() and getImageHeight() functions?

Basically they should look more like this:

// Retrieve the width of a Sprites-format image.
uint8_t getImageWidth(const uint8_t * image)
{
	return pgm_read_byte(&image[0]);
}

// Retrieve the height of a Sprites-format image.
uint8_t getImageHeight(const uint8_t * image)
{
	return pgm_read_byte(&image[1]);
}

(If you get an error after that, you might need to #include <stdint.h> at the top of that file.)

You’re probably overwriting the setting with the score, or vice versa.

Firstly I’d like to mention that you should avoid using EEPROM_STORAGE_SPACE_START directly and should instead choose a semi-random offset from that point.
Purely because if every game used EEPROM_STORAGE_SPACE_START then that part of the EEPROM chip would wear out faster than the rest - the data of all Arduboy games needs to be spread out to ‘wear level’ the chip.

Option 1:
Be very careful with where you’re saving stuff too.

Be aware of how many bytes each object you’re saving uses,
and manually figure out which objects will occupy which EEPROM addresses.

Option 2:
Write your code to carefully calculate the address of each object by taking the address of the previous object and adding the size of that object.

E.g.

constexpr uint16_t object2SaveAddress = (object1SaveAddress + sizeof(object1));
constexpr uint16_t object3SaveAddress = (object2SaveAddress + sizeof(object2));

Option 3:
Make a single struct that stores the whole of your save state.

The advantage is that you can just load the whole thing once at the start of the program and then when saving you don’t need to worry about what specifically you’re saving, you just save the whole thing.

The downside is that to save any single part of the struct you must save the whole struct.
This doesn’t wear out EEPROM because the functions that can save the struct will only write to EEPROM if the value has changed,
but it does mean that you have to be careful about when you save and edit the struct.

For example, if you tried to store the player’s current score in the struct and you offered an option on the pause menu to change a particular setting then when you saved the setting you’d also end up saving the player’s score.
The chances of this being an issue depend on the game.

Also you have to make sure to never remove or reorder any of the member variables (unless you know what you’re doing).


Personally I tend to stick with option 3 because I’ve never been in a situation where any of the theoretical issues with it actually become issues and it’s by far the easiest option.


Here’s another list of suggested improvements.
If you want you can leave looking at this until later
(Some are more pertinent than others.)

More urgent issues
  • launchObstacle:
    • Avoid sizeof(array) / sizeof(*array) and prefer to use the getSize(array) function provided by this header that I wrote. There’s a long explanation in the README.md about why it’s superior - TL;DR: it’s easier to read and makes it harder to accidentally create hard to find bugs.
    • This for loop is redundant:
      1. i starts at 0
      2. i < launchTimer is true
      3. --launchTimer
      4. i--, causes i to underflow, so i becomes 255
      5. i < launchTimer is false so the loop ends
      • The compiler most likely can’t optimise the loop away
      • Instead you should just put --launchTimer in the launchTimer > 0 branch.
  • playGame:
    • Should probably be broken down into more functions. The fact you have a big /*--- Begin obstacle process ---*/ comment should be a big hint. Any time you feel the need to write a big ‘sectioning’ comment like that, it’s usually a sign that your function has grown to big and should be broken up into smaller pieces.
Advice/tips
  • toggleSoundSettings
    • Firstly, this is what audio.toggle() is for. (You still have to call audio.saveOnOff() afterwards.)
    • Some more general advice: You call audio.saveOnOff() as the last statement of each branch of the if statement, which means that you could just move it outside the if since it’s being called either way. I’m pretty sure that the compiler will notice this and perform that optimisation for you, but you should still do that yourself when possible because it makes the code clearer and something might change that means the compiler can’t make the optimisation.
  • toggleScreenFlash:
    • Similar situation to toggleSoundSettings() - you could move the EEPROM.put calls to afterwards.
Minor optimisations
  • collide & collisionTarget:
    • playerRect could be moved to the start of the function to prevent it being constructed every iteration of the loop.
    • Instead of doing if (matters[i].enabled) { you could do the if(!matters[i].enabled) continue; trick.
  • detectHit:
    • arduboy.justPressed should come before collisionTarget because arduboy.justPressed is a much cheaper function. (If justPressed returns false then you can completely avoid the for loop in collisionTarget.)
  • introduction:
    • Instead of drawing the sound icon here and then drawing it again here and then again here, just do the drawing after the ifs.
      • This is one of the reasons why ideally you should avoid mixing updating and rendering. (See “Separation of concerns”.)
Less urgent issues
  • drawBackground:
    • Shouldn’t also be moving the ground, that functionality should be a separate function. In general updating and drawing should be kept separate wherever possible. (See “Separation of concerns”.)
  • launchObstacle:
    • Avoid using both rand and random, pick one and stick with it if possible.
  • updateSynapses:
    • The if statements here end up repeating a condtion and having one condition be the inverse of another. This could be rewritten to make the code simpler.
      • E.g.
        if (targets[i].x < -getImageWidth(targets[i].image))
        {
        	if(targets[i].hit)
        		targets[i].enabled = false;
        	else
        		gameStatus = GameStatus::GameOver;
        }
        

A few stylistic notes:

Naming

Keep your naming consistent. At the moment you’ve got a type called Matter whose launch function is launchObstacle and your array of Synapse is called targets.
Ideally it should be:

  • Matter, matters, launchMatter (or maybe spawnMatter)
  • Synapse, synapses, launchSynapse (or maybe spawnSynapse)

Some other specific things:

  • initializeGame() should probably be called resetGame() if it’s being called again to reset the game after a game over.
  • collide should specify what is colliding with what.
Blank lines

You have far too many blank lines in your code.

A few blank lines to break things up is good, but too many makes the code just as hard to read as having too few.
In particular you typically don’t need a blank line immediately after a start brace or end brace.
E.g. places like this use far too many lines for what little they actually do.

(For an if, for, while etc statement that is. For variables like your Rects it makes more sense to have the space.)

(If you’re struggling to see where a block starts and ends, give Allman style bracing a try instead - it’s much easier to pair up the braces because they’ll be vertically aligned.)

Comments

Don’t bother with all the hyphens around your comments, you’re just wasting your time by typing them.
A single line comment above your function is fine.
If you really need something more obvious, just use three single-line comments and leave the first and last line blank.
E.g.

//
// Retrieve the width of an image
//

And if the function is self-explanatory (as getImageWidth is) then you may not even need it.

i++ vs ++i

Personally I recommend getting used to using ++i instead of i++ for two reasons.

Firstly because the former reads more like ‘increment i’, and secondly because technically i++ creates a copy of i.
The compiler will actually elide that temporary copy for simple types like integers, but in more advanced C++ ++i is used for incrementing objects called ‘iterators’ and those tend to have more expensive copying operations that the compiler can’t elide, at which point using i++ can actually have a performance penalty.

This is a ‘good habits now to avoid problems later’ kind of thing.

Either way, pick a single style and stick to it.

Really nitpicky stuff

The brackets around arduboy.nextFrame() are a waste of time, you’re best off just doing if(!arduboy.nextFrame()).
A unary operator (e.g. ! or ~) will always be evaluated after a function call because otherwise it won’t have a value to operate on,
and a function call will always be evaluated after a member access operator (.) because it depends on the member access to make sense.

Most of all it’s an issue just because it makes the code harder to read than it needs to be.
This is much easier to see because the ! isn’t sandwiched between two (:

if (!arduboy.nextFrame())
  return;

Personally speaking I don’t bother with the = for arrays anymore because it’s effectively redundant, but that’s not really a big deal.
E.g.

const uint8_t image[] PROGMEM
{
	// data...
};

To avoid sounding too critical or too negative, here is a list of good things:

  • updateSynapses and drawSynapses are separate functions - good separation of concerns
  • Using enum class for tracking states
  • Most of the behaviour is out of the main .ino file
  • In most cases the smallest suitable type is in use
  • The int8_t/uint8_t x/y problem has been fixed
  • Rects are being initialised in a better way than before
  • The game is (mostly) using EEPROM properly
  • All strings appear to be wrapped in F macros
  • Obstacle sizes are now set at creation time instead of at regular intervals
  • launchObstacle is randomly selecting a Size in a scalable way
    • Technically if you only ever have Small and Medium then this is actually pretty overkill (Size size = (rand() % 2 == 0 ? Size::Small : Size::Medium); would be simpler and cheaper), but if you intend to have more then this will scale up better.

Definitely don’t apologize for the long post! I appreciate its length, and I’ll be taking all of it into account. I’ll fix everything I can understand before announcing the game, then go back to it and dig deeper.

I mostly just want to make sure nothing is screwing with any Arduboys before I release it, and it sounds like EEPROM could do that the way I currently have it.

They’re from @filmote’s Dino Smasher tutorial.

I understand this idea, but where should I be saving to (the first argument in EEPROM.put) if not EEPROM_STORAGE_SPACE_START? Does having the struct allow for using that location since it doesn’t wear out the EEPROM as much? Or would I need to choose an offset regardless?

And thanks for the list of good things, though I’m pretty sure everything in that list was gleaned from somebody else :P.

Anything you don’t understand, be sure to ask about it.

It’s quite hard for the software to cause the user issues,
and almost impossible for the software to completely brick an Arduboy.

Probably the worst thing you could do is accidentally generate a ‘with bootloader’ .hex,
and that’s not technically a software mistake,
it’s a ‘press the wrong button on the UI’ mistake.

Your game alone couldn’t.

It would take lots of different games using the same area over a long period of time to wear out a particular area faster than all the others.

The odd game here and there forgetting to pick a different area wouldn’t make much difference,
but the more games that remember to do so, the better.

Plus it decreases the odds of your game’s data wiping out the data of another game.
(That’s pretty much an occupational hazard, but it’s nice when it can be avoided.)

In that case they’re probably avoiding the ‘address of’ form so the tutorials don’t have to explain the ‘address of’ operator.

You’d still have to choose an offset.
E.g.

constexpr uint16_t saveDataAddress = (EEPROM_STORAGE_SPACE_START + 168);

And then

SaveData saveData;
EEPROM.get(saveDataAddress, saveData);

“A good scientist is a person with original ideas. A good engineer is a person who makes a design that works with as few original ideas as possible. There are no prima donnas in engineering.”
- Freeman Dyson

It’s called ‘software engineering’ for a reason. :P

If you ever think you’ve created something original,
chances someone else has already created it before and are you just aren’t aware of who or what they called it.

I implemented the struct and the offset on EEPROM_STORAGE_SPACE_START. It still works for the screen flashing, which is good, but I’m still seeing the weird random large numbers for the high score. I knew that could happen when just using EEPROM_STORAGE_SPACE_START, but what do you think is still causing it when using the random offset?

I fixed this up in my code, along with some other style tips (like making variables and function names more consistent), but I’m curious about something. Is it also recommended to have something like ++player.x, or could I keep player.x++.? The latter just makes more sense to me, maybe just because that’s all I’ve ever seen in JavaScript stuff.

I also tried implementing your getSize() function, but was getting errors. I added the Size.h file, #included it in my global.h file and replaced the sizeof instances with getSize(). Is there something glaringly obvious that I’m missing there? If not, I’ll do that again and send you the exact error I was getting.

When the program first starts, it calls introduction() which in turn calls initializeGame() every loop - this will 1) overwrite any high score you had and 2) wear out the EEPROM eventually.

Secondly, you have created a struct to contain the two things you wish to write to the to EEPROM but then actually write the two components out inidividually. @pharap was suggesting that you can simply write the struct itself out and the program will take care to write each component (the flash screen boolean and the high score) without them overwriting each other.

So the large high score is actually because your high score variable is not initialised. You could change
your struct to :slight_smile:

struct SaveData {
  bool shouldScreenFlash;
  uint16_t highScore = 0;
}; 

Which would at least make the high score zero but the way you have set the code up, this would immediately be overwritten by the initializeGame() call. What you want to do (and is shown in the Steve code) is to have a check in your setup() code to see if the EEPROM has been initialized.

void setup() {

  initEEPROM();
  ,,,
  
}

Which calls the following code:

#define EEPROM_START_C1                 EEPROM_STORAGE_SPACE_START
#define EEPROM_START_C2                 EEPROM_START_C1 + 1
#define EEPROM_SCORE                    EEPROM_START_C1 + 2


/* ----------------------------------------------------------------------------
 *   Is the EEPROM initialised? 
 *   
 *   Looks for the characters 'S' and 'T' in the first two bytes of the EEPROM
 *   memory range starting from byte EEPROM_STORAGE_SPACE_START.  If not found,
 *   it resets the settings ..
 * ----------------------------------------------------------------------------
 */
void initEEPROM() {

  uint8_t c1 = EEPROM.read(EEPROM_START_C1);
  uint8_t c2 = EEPROM.read(EEPROM_START_C2);

  if (c1 != 'S' || c2 != 'T') { 
  
    EEPROM.update(EEPROM_START_C1, 'S');
    EEPROM.update(EEPROM_START_C2, 'T');
    EEPROM.put(EEPROM_SCORE, (uint16_t)0);
      
  }

}

See how I use two characters at the start of the data I am saving to store (in my case) ‘S’ and ‘T’. If I find them I know the EEPROM has been setup previously,. If I do not find them, I initialise the EEPROM and write them out ready for the next check.

Yes, because player.x is the whole expression that ++ is applied to.
. is evaluated before ++, as are ->, () and [].
I.e. ++player.x is valid, ++player.someFunction() would be valid, ++someArray[index] is valid and ++playerPointer->x would be valid.

JavaScript actually has the same problem, but less people care about it…

It’s probably partly because JavaScript doesn’t support operator overloading whereas C++ does, so JavaScript won’t have the situations where iterator++ ends up being quite a bit more expensive than ++iterator because in JavaScript ++ it only ever used on numbers (as far as I’m aware) and most compilers/interpreters know how to optimise that.

The difference in speed on integers wouldn’t make that much difference for JavaScript anyway,
the amount of processing overhead involved in the JavaScript VM dwarfs the small amount of processing power that would be saved by doing ++i instead of i++ on a mere integer (or float).

It matters a lot more in C++ because in C++ the expensive cases do exist and can be a problem.

Unfortunately the old fashioned i++ approach is really deeply ingrained and it’s taking a lot of time and effort to get people using better habits.

I can’t answer that because I don’t know what you wrote.
It should have been:

int index = (rand() % getSize(spawnCoords));

The only problem I can think of is that there might be some typing issues (getSize returns size_t, which is unsigned).


Good catch. I hadn’t actually got round to following the call chain to see what calls what.

The easiest fix would be to call initializeGame() (which I’m still convinced should be resetGame()) from setup().

Actually is seems to be writing them fine,
it’s the reading them part where they’re being handled separately.

But yes, that’s an issue, it should just be:

EEPROM.get(saveDataAddress, saveData);

I was slightly surprised that the code was compiling,
but then I realised the person who wrote EEPROM decided that both get and put should return their second arguments as a reference (T&).

Weird.

That, but preferably without the nasty defines.

Here’s a variant using SaveData with some extra functions:

constexpr uint16_t saveOffset = 164;
constexpr uint16_t startAddress = (EEPROM_STORAGE_SPACE_START + saveOffset);

constexpr uint16_t byte1Address = startAddress;
constexpr uint16_t byte2Address = (byte1Address + sizeof(uint8_t));
constexpr uint16_t dataAddress = (byte2Address + sizeof(uint8_t));

// Some more suitable letters :P
constexpr uint8_t checkByte1 = 'R';
constexpr uint8_t checkByte2 = 'B';

void clearEEPROM()
{
	EEPROM.update(byte1Address, checkByte1);
	EEPROM.update(byte2Address, checkByte2);

	SaveData blankSaveData { false, 0 };
	EEPROM.put(dataAddress, blankSaveData);
}

void initialiseEEPROM()
{
	uint8_t byte1 = EEPROM.read(byte1Address);
	uint8_t byte2 = EEPROM.read(byte2Address);

	if ((byte1 != checkByte1) || (byte2 != checkByte2))
	{
		clearEEPROM()
	}
}

void loadEEPROM()
{
	EEPROM.get(dataAddress, saveData);
}

void saveEEPROM()
{
	EEPROM.put(dataAddress, saveData);
}

(Interesting fact: you can actually read a uint16_t instead of reading the two bytes separately, but it usually doesn’t make much difference in terms of the amount of progmem used.)

I left them as per the articles he is learning from. I am not adverse to using defines for simply constants - the typical examples people use for the evil #defines don’t really worry me as I have never, ever run into them in practice.

Besides its fun to rev you up :slight_smile: