New game programming assistance

It doesn’t really make sense to use such an archaic feature to implement constants when you can just have a proper constant.

I strive to have as few macros as possible. Ideally zero.

I’ve run into several.

When writing FixedPoints I ended up having to have floorFixed, ceilFixed et cetera because the Arduino library decided to implement floor and ceil as macros.
If they had been functions I could have just created some new overloads and everybody (especially me) would have been happy.

And don’t forget that really obscure one-off where abs being a macro caused it to use more progmem than a replacement template function absT.

And over on the Pokitto the library defines min and max in an attempt to match Arduino, which then causes havok when someone wants to #include <algorithm> to use the proper std::min and std::max.

Granted those were all function macros,
but the same answer/solution holds for both function macros and non-function macros:
There’s a much better feature that’s actually part of the language instead of the preprocessor,
use the language feature (function, template function, constant) instead.

I am an old man and cannot be taught new tricks. Besides #define has less typing (of the keyboard sort).

Exactly.

If you’re complaining about typing you’re in the wrong profession.
Also, that’s what autocomplete is for.

Good IDEs will even let you set up shortcuts to insert common snippets if you’re desparate.

I’m waiting for the day someone someone tries to do this:

#define JAN 0
#define FEB 1
#define MAR 2
#define APR 3
#define MAY 4
#define JUN 5
#define JUL 6
#define AUG 7
#define SEP 8
#define OCT 9
#define NOV 10
#define DEC 11

And then wonders why they have loads of subtle bugs in their code.

I threw that out there because I wanted you to jump on the ‘constexpr are type safe’ … :slight_smile:

Welp, you asked for it. :P

Technically macros have a type after substitution, but they can be a bit tricky.

For example if you had:

#define A 10
#define B (A + 1)

Then decltype(B) would be int, but if A was later redefined as 10.0 then decltype(B) would change to double.

With constexpr you can force the type:

constexpr auto a = 10;
constexpr int b = (a + 1);

At which point the definition of a changing to 10.0 won’t introduce a subtle bug because b's type has been set in stone.

I seem to have gotten it working with the new functions you wrote, and I renamed initializeGame() to resetGame(), just for you (and also because it does make more sense :stuck_out_tongue:).

I tried putting it in setup(), but as I feared it doesn’t reset everything each time I restart the game. @filmote has it running in every game loop in his tutorial, which made sense to me since it’s just another game state and needs to be run every time the game is restarted. Doesn’t setup() only run once when the Arduboy is turned on?

The save, load and clear ones?

I approve immensely.

Ah, I thought you already had some other mechanism in place for doing that.

Basically you need a way to run it precisely once at the start of a new game.
You have several options for how to do that:

  • One option is to call it at the same time that you change the state to the gameplay state. This isn’t a very robust or flexible solution, but it’s perfectly adequate if this is going to be a one-time-only affair.
  • One option is to have a ‘dummy’ state that calls resetGame() and then changes the state to the proper gameplay state. Technically it wastes a frame if you don’t do case fallthrough (and ideally you want to avoid case fallthrough because it comes with its own set of problems) and isn’t really the correct use of a ‘state’, but it works and it’s somewhat more scalable.
  • Another option is to develop a more complex state system so that each state has an associated ‘activate’, ‘update’, ‘render’ and ‘deactivate’ function (which I did in Minesweeper), but you probably won’t need something that complex for a simple game like this.

Yes, and you only want to initialise the save state once,
so initialiseEEPROM() should definitely be in setup().

loadEEPROM() and resetGame() on the other hand should be used as I mentioned earlier - called only once at the start of a game.

Essentially you’ll end up with having something akin to a setup and loop for the gameplay state itself.
Perhaps not necessarily in that format, but in terms of abstract functionality that’s what it would be similar to.


By the way, bonus points for anyone who can figure out why my ‘define the months’ code snippet would potentially cause problems on Arduino.

oct and dec are defined in the std library as 8 and 10 respectively.

So what you have done is correct - moving the initialiseEEPROM() into the setup means it will test that the EEPROm is initialised when the game is first run. This doesn’t need to be done every time you enter the introduction() routine.

Having said that, as the initialiseEEPROM() only updates the EEPROM the if the caharcters ‘R’ and ‘B’ are not found, it could be put into the introduction() routine but it would be checking for no reason.

The reset() function only needs to be called once before the game is started. Having it in the introduction() routine means that it would be called many times but there is no harm done. You could change your code to the following:

enum class GameStatus : uint8_t {
  Reset,                                      < New state!!
  Introduction,
  PlayGame,
  GameOver,
};

Change the loop() to:

void loop() {

  if (!arduboy.nextFrame()) return;

  arduboy.clear();
  arduboy.pollButtons();

  switch (gameStatus) {

    case GameStatus::Reset:
      resetGame();
      gameStatus = GameStatus::Introduction:
     
    case GameStatus::Introduction:
      introduction();
      break;

    case GameStatus::PlayGame:
      playGame();
      break;

    case GameStatus::GameOver:
      gameOver();
      break;
      
  }

  arduboy.display();
}

And remove the following:

void introduction() {
  
  \\ loadEEPROM();      < Removed as this is done in resetGame() already.
  \\ arduboy.clear();   < Removed as this is already done in loop()  
  \\ resetGame();       < Removed as this is already done in loop()
  
  
  Sprites::drawOverwrite(0, 0, intro, 0);
  
  Sprites::drawOverwrite(95, 43, (arduboy.audio.enabled() ? sound_on ...
  

Now when you want to start the game or restart the game after game over, set the gameStatus = GameStatus::Reset instead of GameStatus::Introduction. It will then perform the resetGame() function within the switch case and immediately change the gameStatus to GameStatus::Introduction so that the actions are performed once.

Note that the GameStatus::Reset case has no break - this is intentional and when the case is executed, it will not stop after it has executed the code and will automatically fall through to the next case (the ‘Introduction’ case) and execute that as well. You could add a break but it will introduce a single frame where nothing is rendered. You probably would not even notice!

The above code is not necessary for your game as it works fine without it. However I am showing you this so you can see how you might implement some code that needs to happen once in a state engine. Besides, I am pretty convinced that @pharap will highlight the evils of dropping through cases (actually the evils are more that it is not obvious and someone might re-organise the order of the cases and this would break the logic).

Not in the C++ standard library thank heavens.
It does have std::oct and std::dec, but not OCT or DEC.

The Arduino library on the other hand…

I’m just glad there aren’t any months called Bintober or Hexember.

This is essentially the second of the three options I mentioned, but with added demo code.

I already slightly preempted you there:

It’s not just the lack of obviousness, it’s that it can sometimes upset optimisation.

If each case is simply calling void f()-style functions then theoretically the compiler can turn that into a lookup table (or jump table) embedded in progmem, which makes the branching much cheaper than having an if-chain.
(For those who know/care about big-O, the former is O(1), the latter is O(n).)

But the moment you introduce case fallthrough it complicates matters.
Theoretically the compiler could get around that by adding a function call to one case from another case,
but I have doubts about whether it’s smart enough to know that or whether its heuristics would even let it.

But as you say, there is a readability problem.

Rambling on about readability issues...

Fallthrough can be hard to spot because it’s marked by the absense of a break; rather than some kind of fallthrough; statement.

The fact that the common case requires an extra keyword and the rare case is actually the default behaviour is a bit of a design mistake really.
(And it’s yet another item on the ever-growing list of ‘bad stuff that was inherited from C’.)

C++17 introduced the fallthrough attribute in an attempt to make fallthrough a bit more obvious.
Unfortunately that’s not available on the Arduboy, but I think if you’re going to use fallthrough you should at least add a comment to that effect so that it’s easier to spot what’s going on.


Personally I think that since there’s only one place where the gameplay state is being switched to it would be easier to just call resetGame() from there for now.

A more robust solution (although possibly not so cheap depending on how the compiler behaves) would be to provide an actual changeState(GameState) function,
and then when the target state is the gameplay state do a call to resetGame(),
essentially working much like having an activate() function would,
but with the call being branched rather than virtual.

It would be possible to abuse template functions to make it more likely that the functions would be inlined when needed,
but probably better to not get into that as it would mean explaining templates and specialisation.

I’ll leave the code here in case anyone is interested:

The code...

This works because all the state changes are changed to a constant value.
If it were necessary to change to a variable value then this would no longer be a viable option.

template<GameStatus gameState> void changeState()
{
	gameStatus = gameState;
}

template<> void changeState<GameStatus::PlayGame>()
{
	resetGame();
	gameState = GameStatus::PlayGame;
}

// Example change
void introduction()
{
	// Et cetera...

	// Theoretically this will be compiled with an inlined version of the specialised function
	if (arduboy.justPressed(A_BUTTON))
		changeState<GameStatus::PlayGame>();
}

Sorry, I read @raspberrybrain’s response and his reference to my tutorial and started answering the post without fully absorbing your response. Yes, I have fleshed out the second option you gave.

Don’t worry, I’m not annoyed that you missed it, I was merely pointing that out for the sake of clarity.

LOL … it looked like I was taking credit for the idea when I really just are a poor reader :slight_smile:

Half the reason I mentioned it in the first place was to preempt you giving it as a solution. :P
(I half wish I’d added a “which filmote will come along and tell you about later” to that option.)

I’ve seen you use it one or two times so I knew it would be the first solution you’d offer up.
So in a way it was your idea, though I suspect you’re not the first to think of it, nor will you be the last.

No I cannot claim credit for the ‘phantom’ case. I do think its a reasonable solution though.

All three!

Then I incorporated @filmote’s Reset game state (thank you @filmote!) and I broke it, and it took me while to realize that it wasn’t loading any EEPROM because I hadn’t changed gameState to equal Reset when the system is turned on.

I know you mentioned an easier way to fix the introduction running every loop problem, but I incorporated the second option because it made sense and I’ll probably use it again in the future in case it needs to be at a bigger scale.

I’ve incorporated a good number of your advice and tips (hugely appreciated), which I’ll continue doing while I (maybe) try to expand upon this game at some point. I’m at the point where I think it’s good enough to share though, so I’m going to make a new thread and announce it.

Thank you both a TON for all the help with this.

1 Like

If I have time, I might have a play around with it and compare the options to see which uses more progmem.
My gut instict is that the ‘dummy state’ option should upset optimisation because of the case fallthrough, but I’ve been surprised before.

I’ve spotted the thread. There’s a few last things you should do before calling it done though…

Firstly choose a licence for it.
Without a licence, technically nobody is allowed to modify or redistribute the code (there’s a good explanation of that here).
Personally I recommend either:

  • MIT - The user can basically do what they like with the code as long as they include the licence and copyright notices when they redistribute the code (the original version or an unmodified version)
  • Apache 2.0 - Like MIT, but also requires that if someone modifies the code they leave a notice saying it’s been modified so that modified versions don’t get confused with the original (and so that people effectively take credit for the fact they’ve made a modification)
  • BSD 3-clause - Like MIT, but expressly forbids the user from using the names of the code’s autors/contributors to endorse any modified versions of the code (unless they have written permission to do so)

And secondly, you should make a release on your GitHub repo,
preferably using the semver scheme for versioning.
(E.g. make sure the release tag is something like v1.0.0 if this is a stable release, or v0.1.0 or v1.0.0-alpha if you think it’s still a prerelease.)

Done and done! Thank you, I never would’ve thought to do these things.

1 Like

No problem.

Most people who make their first games don’t actually know about these things since not many game development tutorials (here or elsewhere on the internet) bother to mention them.


Remember to also make new releases when you fix bugs or add features.

It doesn’t really matter when you make a release,
you don’t have to rush to make a new release every time you fix or add something,
you can take your time about it,
but I would recommend applying bug fixes before features if possible.

Why bugfix first?

You’re probably unlikely to have a problem where it becomes an issue,
but large-scale projects sometimes end up having to ‘back-port’ bugfixes to fix more serious issues,
and prioritising bugfixes over features mean that you can sometimes avoid back-porting bugfixes because you applied them as a patch increase before upping the minor number.
If you applied the minor increase (feature change) first then you could end up having to back-port the following bugfixes.

If you haven’t guessed, this is yet another one of my ‘get into good habits to avoid potentially getting bitten later’ suggestions.
Preventing problems can be just as important as solving them. :P

If you’re really organised then you’d plan all your bugfixes and features in advance and prioritise them as much as possible.
(GitHub has a lot of nifty advanced tools for this.)
Most people aren’t quite that organised when it comes to hobby projects though, they prefer to just add stuff as they think of doing it/feel like doing it.

1 Like

Whenever you get a chance I’m a little stumped on something and could use a nudge in the right direction.

I’m creating a level using code from the magazine.

  void drawLevel()
  {
    uint8_t column = 0;
    uint8_t startPoint = (camera.y / tileSize);
    uint8_t row = 0;
    
    while ((column + (row * mapWidthInTiles)) < mapWidthInTiles * (screenHeightInTiles + 1))
    {
      if (column > (mapWidthInTiles + 1))
      {
        column = 0;
        ++row;
      }
      Sprites::drawSelfMasked(
        column * tileSize,
        (row * tileSize) - camera.y + (startPoint * tileSize),
        tileSheet,
        pgm_read_byte(&levelOneArray[((startPoint + row) * mapWidthInTiles) + column])
      );
      ++column;
    }
  }

It uses a one-dimensional array of numbers corresponding to the frames in a tile sheet from PROGMEM.

const unsigned char PROGMEM levelOneArray[] = {
  7, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,  8,
  14, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 14,
  14, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 14,
  14, 0,  0,  0,  0, 12,  1, 11, 10,  0,  0, 12,  8,  0,  0, 14,
  14, 0,  0,  0,  0,  0, 14,  0,  0,  0,  0,  0, 14,  0,  0, 14,
  14, 0,  0, 13,  0,  0, 14,  0,  0,  0,  0,  0, 14,  0,  0, 14,
  14, 0,  0,  2, 11, 11,  5, 11, 11, 11, 11, 11,  4,  0,  0, 14,
  14, 0,  0, 14,  0,  0, 14,  0,  0,  0,  0,  0, 14,  0,  0, 14,
  14, 0,  0, 14,  0,  0, 15,  0,  0,  0,  0,  0, 14,  0,  0, 14,
  14, 0,  0, 14,  0,  0,  0,  0,  0,  0,  0,  0, 14,  0,  0, 14,
  14, 0,  0, 14,  0,  0,  0,  0,  0,  0,  0,  0, 14,  0,  0, 14,
  14, 0,  0, 14,  0,  0,  0,  0,  0,  0,  0,  0, 14,  0,  0, 14,
  14, 0,  0,  9, 11, 11,  1, 10,  0,  0, 12, 11,  6,  0,  0, 14,
  14, 0,  0,  0,  0,  0, 14,  0,  0,  0,  0,  0, 14,  0,  0, 14,
  14, 0,  0,  0,  0,  0, 14,  0,  0,  0,  0,  0, 14,  0,  0, 14,
  9, 11, 11, 11, 11, 11,  3, 11, 11, 11, 11, 11, 11, 11, 11,  6,
};

I have a little camera system working that follows the player around and whatnot. My question is about tracking where the player is and what tile he’s standing on based on the tile array index. I’m printing out all the numbers and tried something like this:

arduboy.print(pgm_read_byte(&levelOneArray[(player.x / tileSize) * (player.y / tileSize)]));

Is this at all in the right direction? It’s definitely not entirely correct, and I’m wondering if this is even possible in a one-dimensional array. The few other code examples I looked at (including your 2D platformer example) build the levels using 2D arrays, but I kind of like this 1D approach because it’s an easy visual representation in numbers of what the level layout will look like.

The goal of getting the ability to track the player working is to get tile collision working, so that’s where I’m at.

I am guessing you will need to know the size of the array (or at least the width). In this case its 16.

Then to work out the element to read, you need to multiply the Y coord by the width to account for the rows you are skipping. Then you add the x coord, sort of like this:

constexpr uint8_t width = 16;
arduboy.print(pgm_read_byte(&levelOneArray[ (player.x / tileSize) + ((player.y / tileSize) * width) ]));