Game screen # 1

We buy Arduboy to build and play games. And games have different screens (like a title screen, a game screen, a winning screen, and a failing screen.) Now, games do not need to have all of these, but if you have them, what loops will you use?

  • IF loops for every single screen
  • SWITCH loop to take care of everything
  • Other “fancy” things

0 voters

I can do both of the “not-fancy” things, so you are going to help me decide!

Actually, not just games. Half of me want to create a tool kit with stop watch, flash light, counter and pressure sensor, maybe a vuilt-in game. Now for screens I mean some BIG (BIG) difference in what is being done && displayed, like a stop watch screen, a flashlight screen, a game screen, a game’s high-score screen, a vibration-sensor screen and a counter screen and so on.

Switches should do the trick!

6 Likes

Depends what you’re comfortable with and how much you care about good design.

Personally I tend to use a complex state-machine system that makes use of scoped enumerations (enum classes) and template classes, but most people here stick to switch-based systems.

switch is definitely better than a long chain of if statements.

Personally I think a nice simple system (without using classes) would be something like this:

enum class GameState : uint8_t
{
 None,
 TitleScreen,
 GameScreen,
 // Other states
};

GameState currentState = GameState::TitleScreen;

// Having a nextState variable means that currentState
// stays valid until the next loop,
// which can be useful
GameState nextState = currentState;

// Use this function to change state
// If you decide that nextState isn't needed then it's
// easier to switch the implementation of this function
// than it is to go around changing all the cases of `nextState = something;`
void changeState(GameState gameState)
{
	nextState = gameState;
}

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

	arduboy.pollButtons();

	if(nextState != currentState)
		currentState = nextState;

	// Handle updating
	switch(currentState)
	{
		case GameState::TitleScreen:
			updateTitleScreen();
		break;
		case GameState::GameScreen:
			updateGameScreen();
		break;
		default:
		break;
	}

	// Handle drawing
	arduboy.clear();
	switch(currentState)
	{
		case GameState::TitleScreen:
			drawTitleScreen();
		break;
		case GameState::GameScreen:
			drawGameScreen();
		break;
		default:
		break;
	}
	arduboy.display();
}

There are many other ways to do this and there’s no single right answer, but I think this solution has a good balance of simplicity and flexability.

3 Likes

Thanks @Pharap , now I understand the question.
It is not about loops but about state handling.

The problem is that you have a current state and want to change it to a different one. In your example, it’s a screen change.

So you have to remember a state A and make a transition to a state B.
Key here is:

  1. Remember states
  2. Handle transitions

In @Pharap sample it’s handled by storing the state in an enum GameState and the transition is handled by a switch-statement and a simple function changeState.
This is very clean and the base for a so called state machine.

As @Pharap said there are limitless ways, but routing with if- or switch-statements is always the foundation of them.

There is no right answer, but most possibly an optimal solution for a certain case.

2 Likes

An other “fancy” things example:

For the SetNameAndID example sketch in the Arduboy2 library, I used an index for the current state, that’s set to values in an enum. The index is used to select an element in arrays of function pointers. For each state, there’s an array for handling the buttons and another to draw the screen.

So, screen selection is done by which function pointer is indexed and thus which function is called.

The SetNameAndID sketch can be examined, compiled and uploaded in the Arduino IDE using:
File > Examples > Arduboy2 > SetNameAndID

2 Likes

I hope that’s what it was about since that’s what I’ve answered.
It would be awkward if I’ve misinterpreted it. :P

This is a good point. I left transitions out to keep the example simple, but they are indeed important.

In the more complicated system I like to use (the only published example being Easter Panic), all state classes have an activate and deactivate function, (though in my case it’s not often I have to use them because class constructors and destructors are involved).

If transitions are needed, a modified version of my example would be as thus:

enum class GameState : uint8_t
{
 None,
 TitleScreen,
 GameScreen,
 // Other states
};

GameState currentState = GameState::TitleScreen;

// Having a nextState variable means that currentState
// stays valid until the next loop,
// which can be useful
GameState nextState = currentState;

// Use this function to change state
// If you decide that nextState isn't needed then it's
// easier to switch the implementation of this function
// than it is to go around changing all the cases of `nextState = something;`
void changeState(GameState gameState)
{
	nextState = gameState;
}

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

	arduboy.pollButtons();

	if(nextState != currentState)
	{
		// Deactivate the old state
		// currentState is technically 'previousState' here
		switch(currentState)
		{
			case GameState::TitleScreen:
				deactivateTitleScreen();
			break;
			case GameState::GameScreen:
				deactivateGameScreen();
			break;
			default:
			break;
		}
		
		// Make the transition now the old state is deactivated
		currentState = nextState;
		
		// Activate the next/current state
		switch(nextState)
		{
			case GameState::TitleScreen:
				activateTitleScreen();
			break;
			case GameState::GameScreen:
				activateGameScreen();
			break;
			default:
			break;
		}
	}

	// Handle updating
	switch(currentState)
	{
		case GameState::TitleScreen:
			updateTitleScreen();
		break;
		case GameState::GameScreen:
			updateGameScreen();
		break;
		default:
		break;
	}

	// Handle drawing
	arduboy.clear();
	switch(currentState)
	{
		case GameState::TitleScreen:
			drawTitleScreen();
		break;
		case GameState::GameScreen:
			drawGameScreen();
		break;
		default:
		break;
	}
	arduboy.display();
}

The downside to the example I’ve given is that it’s a bit more difficult to add new states than in other systems, but I think the simplicity makes up for that a bit. (I would hope that it’s simple enough that even less experienced programmers can understand the general idea.)


@MLXXXp’s suggestion of an array of function pointers (sometimes referred to as a ‘jump table’, ‘branch table’ or ‘dispatch table’) is also a good one since it is quite easy for the compiler to optimise and will possibly generate less code in many cases.

However, it is important to remember that the values of the enum are significant and the array ought to be bounds-checked to prevent indexing with an invalid address, which could lead to some very strange bugs.
(On certain non-Arduboy systems, a lack of bounds checking could be exploited as an attack vector for arbitrary code execution.)

I think ultimately the main reason people don’t use jump tables more often is that a lot of people struggle with C++'s function pointer notation. The technique itself is fairly flexible and straightforward.

2 Likes

I originally had this approach in 1943 but found that a simple switch statement / enum combination actually took significantly less memory. I love the simplicity of jump table but alas memory is king.

1 Like

It’s possible that the calls were being inlined because the functions were only being used once so some of the overhead of function calls was eliminated, and subsequently other optimisations could be done.

It really pays to try different approaches to see which works best for which case. Sometimes the compiler gets it right, sometimes the code’s too complex and the compiler stops trying.

Memory wasn’t a concern for me for a simple utility sketch.

1 Like

Absolutely, the weirdest things cause memory jumps. I have given up trying to understand sometimes.

1 Like

Actually if you include some int-8-t or some enum in the code that is considered as “fancy” for me.
Well, if you guys said so…a SWITCH()… CASE() is the one.
Expect interesting things…

Calling it complex…
Indeed, I DO NOT know what is going on…

I guess that is why the “tutorial” that let you create a PONG clone let you stick with a SWITCH…CASE() loop

NO idea what the sample program “ArduBreakout” was doing…
Like, they have the “enterHighScore” screen set up to be a BOOLEAN???
And they also have titleScreen() to be a boolean…
Good, newLevel was finally a void…

Actually if I want something to only run ONCE inside the loop should I use a this?
do arduboy.tune.tone(200,200); while(false);

I you want something to execute once, you could either do as you suggested:

do {
  arduboy.tune.tone(200,200); 
} while(false);

… or simply …

arduboy.tune.tone(200,200);

I am not sure if you were taking the piss on this question or not.

1 Like

They’re not really fancy, they’re just more modern.
It’s worth learning about them, they’re very useful things to know.

Some useful resources for learning about them:

Fixed width integers (e.g. int8_t)

Scoped enumerations (i.e. enum class)

With the sample code or in Easter Panic?

See @filmote’s comment.

A good compiler will realise that do /* something */ while(false); is the same as /* something */ and produce the same machine code for both, so really you would just be making more work for yourself and your code would look weirder as a result.

1 Like

Yeah most of the time the program is a continuous loop, no matter it is a partial loop inside a while(true) or the void loop() itself, so that does NOT do it. That is also why the SWITCH…CASE loop is so useful in this case.
I added a boolean “nonsense” that will toggle the while loop off after running it XD

And after the Switch…case loop, the entire program shrunk from more than 1,000 line to not even 400, and I do not even need to declare two extra voids and just put them inside case 2 and case 3. It also saved roughly 5% of total memory AND 10(!)% RAM, not using arduboy2 libraries.
I kind of hate the partial loop of while(true) for entering names for high score…
But after that loop is a ONE-TIME ONLY bunch of lines of code…
FINE.
Ah, can put those “clear name” lines under a “nonsense” one-time loop…Solved.