Hrm… it makes sense that you’d need to subtract 1 from
PLAYER_SIZE when calculating
but needing to add
playerY to get
playerRight doesn’t make sense.
Something seems off there.
That implies that either
PLAYER_Y_OFFSET are off by one for some reason.
You have to be a bit careful with that technique.
Firstly because it doesn’t always save space in the first place.
More importantly, you have to make sure that the index is valid otherwise you go outside the bounds of the array and end up reading data that isn’t actually a function pointer as if it were a function pointer.
If you try to call an invalid function pointer then almost anything could happen.
On a desktop you’d be able to use
std::array which has a function that checks the index you’re trying to access is valid an throws an exception if it isn’t.
But the Arduboy doesn’t have access to an implementation of the C++ standard library, just a version of the C standard library.
And even if it did have C++ standard library implementation, exceptions are disabled (as they are on most embedded systems because they need quite a bit of ROM/progmem).
Technically it should actually be
You’d be best off using something like this:
// Modern C++11 type alias
using FunctionPointer = void(*)();
// Array of game state function pointers
constexpr size_t gameStateCount = 4;
const FunctionPointer gameStates[gameStateCount] PROGMEM
// API for calling a game state function safely
void callGameState(size_t index)
// Prevent attempts to read outside the array
if(index >= gameStateCount)
// Read the pointer out of the array
void * pointer = pgm_read_ptr(&mainGameLoop[index]);
// Convert it to the proper type
auto function = static_cast<FunctionPointer>(pointer);
// Call the function
If you want to be extra safe, you could replace the
return with something that prints an error message to the screen and stops execution (e.g. by using
Hopefully the way I’ve broken the code up a bit will make it easier to make sense of what the code from the magazine was doing.
Also, be aware that
void * has nothing to do with
despite looking vaguely similar.
void * is the ‘opaque pointer’ type that can be used to point to anything.
void (*)() is a pointer to a function that takes no arguments and returns no values.
I probably have explained it half a dozen times before,
but the number of times I explain something doesn’t really bother me,
particularly if it’s to a different person each time.
I’d gladly explain how it all works in full,
but I warn you that the explanation could be quite long depending how much you already know about pointers.
If you don’t know what pointers are and how they work I’ll have to explain those first - they’re fundamental to understanding how the rest works.
The influence of C...
Macros are still used by a lot of people because of the influence of C and people’s lack of familiarity with C++11.
When C++ was first created it was just an attempt to add OOP features to C, but over time it grew and diverged from C.
As a result of this, a lot of C++'s more fundamental features are inherited from C.
An unfortunate side effect of this is that people tend to think that anyone who can program in C can also write C++ and vice-versa, and similarly they think that something that’s considered good practice in C is also good practice in C++.
As a result, things that are used a lot in C often creep their way into C++ programs despite C++ having a much better option in its repertoire things 90% of the time.
One of these things is macros.
For some reason there’s a lot of people out there who love to use macros for things despite better options being available.
I suspect it’s because a lot of early C libraries set a bad precedent and people have only learned what’s good and what’s bad over time.
Even in C it’s generally considered bad to use macros for constants.
static const variables are generally preferred,
though there are some cases where they can’t be used (see this for more info).
A lot of those caveats don’t apply to C++ though - C++ fixed a number of C’s shortcomings.
Don’t just take my word for it though,
here’s a bunch of relevant StackExchange links: 1, 2, 3, 4.
Basically there’s a long list of reasons why macros are bad and the kind of bugs they can cause.
I won’t go into all that unless you particularly want to know though,
because I don’t want to dump too much info on you in one go.
Unfortunately some people won’t listen to the experts, the best practices or the horror stories.
Some change their minds when they finally get bitten or hear a compelling enough story, but others will carry on regardless.
I mean to completely do away with the
#defines, or as much as you possibly can.
Classes aren’t really a replacement for macros, but
constexpr variables are.
As of C++11,
constexpr is the proper way to make a constant in C++.
constexpr int tileWidth = 16;
There’s also ‘function macros’ which you may have not encountered yet.
90% of the time those can be replaced by template functions.
(Unfortunately 60% of the time people won’t replace them with template functions because they have an irrational fear of templates or can’t be bothered to understand templates.
And lastly, according to the C++ Core Guidelines you should (and I quote):
Scream when you see a macro that isn’t just used for source control
So I kick up a fuss about it whenever I can.
Hopefully my answer answered what you were asking. If not, ask again.