ArduboyTones - Plays sounds forever?

I’ve been following through Crait’s excellent beginner Arduboy tutorials, and have been working on extending the program from Part 5: Higher / Lower Game.

To summarise the additions I’ve made:

  • Split out end screen into its own function. endScreen() is passed a variable to determine if the game was won or lost. I haven’t changed the program structure from the tutorial, just pulled out the code into its own function.
  • Flashing the built-in RGB LED red on an incorrect guess, using digitalWriteRGB
  • Play beeps and boops using ArduboyTones. Things like a little startup jingle, a boop when you change the number, a losing “bup-bow”, etc.

I’m having trouble with the ArduboyTones library - though I expect I’m missing something. Some of the implementations it works perfectly (such as sound.tone(NOTE_D2,80); when the user changes their guessed number), but my “end of game” jingle sounds just one note, which repeats indefinitely.

I suspect this could be to my use of calling endScreen() during program main loop(), but I can’t figure out an alternative flow. I could set a flag to only call sound.tone() on the first pass through, but this seems inelegant and like I’m missing something more fundamental in program structure.

Here is my endScreen() function:

// endScreen() - a final screen when the game is over, win or lose
void endScreen(bool playerWin) {
    a.setCursor(0,0);
    if (playerWin) {
        a.print("\n\n      YOU WIN!");
        a.digitalWriteRGB(RGB_OFF,RGB_ON,RGB_OFF);
    } else {
        sound.tone(NOTE_D5,300, NOTE_G4,300, NOTE_C4,200);
        a.print("\n\n      YOU LOSE!");
    }

    a.print("\n  Correct Number: ");
    a.print(randomnumber);
    a.print("\n\n\n");
    a.print("Press A to play again");

    // Let the player start a new game - reinitialise variables
    if (a.justPressed(A_BUTTON)) {
        restartGame();
    }
}

And where it’s called from the main program. Full code is also below in the dropdown.

// Main code, to run repeatedly so long as the program is running
void loop() {
    a.clear(); // clear the screen
    a.pollButtons(); // track button presses since the last loop - used to only register single button presses at a time

    if (playerwin) {
        // Tell the player that they won!
        endScreen(playerwin);

    } else {
        // Ask the player for a number and play the game
        if (attempts == 7) {
            // Too many guesses - game over!
            endScreen(playerwin);
        } else {
            // Player has more attempts
[... truncated ...]
Full Code Below
// TUTORIAL: HIGHER LOWER GAME
// https://community.arduboy.com/t/7928
//
// 15 August 2023
// Nathan Chapman -- @nchpmn

// Library Setup
#include <Arduboy2.h>
Arduboy2 a;
#include <ArduboyTones.h>
#include <ArduboyTonesPitches.h>
ArduboyTones sound(a.audio.enabled);

// Variable Setup
bool playerwin;
int attempts;
int guessednumber;
int randomlimit;
int randomnumber;
int lastguess;

// Setup code, to run once
void setup() {
    a.begin();
    a.setFrameRate(60);

    randomlimit = 11; // Upper limit of guessable numbers (+1 to account for random())
    a.initRandomSeed(); // Seeds the random() function - remove this line to produce identical results every time

    restartGame();
}

// restartGame() - to reset variables and prepare for a new game
void restartGame() {
    randomnumber = random(1,randomlimit); // random() generates lower to upper-1
    // randomnumber = 0;
    attempts = 0;
    playerwin = false;
    guessednumber = 0;
    a.digitalWriteRGB(RGB_OFF,RGB_OFF,RGB_OFF);

    // Happy little chiptune to start the game!
    // const uint16_t song1[] PROGMEM = {
    //     NOTE_GS3,1000, NOTE_REST,250, NOTE_C3,500, NOTE_B3,200,
    //     TONES_END };

    // sound.tones(song1);
}

// endScreen() - a final screen when the game is over, win or lose
void endScreen(bool playerWin) {
    a.setCursor(0,0);
    if (playerWin) {
        a.print("\n\n      YOU WIN!");
        a.digitalWriteRGB(RGB_OFF,RGB_ON,RGB_OFF);
    } else {
        sound.tone(NOTE_D5,300, NOTE_G4,300, NOTE_C4,200);
        a.print("\n\n      YOU LOSE!");
    }

    a.print("\n  Correct Number: ");
    a.print(randomnumber);
    a.print("\n\n\n");
    a.print("Press A to play again");

    // Let the player start a new game - reinitialise variables
    if (a.justPressed(A_BUTTON)) {
        restartGame();
    }
}

// Main code, to run repeatedly so long as the program is running
void loop() {
    if (!a.nextFrame()) {
        return;
    }
    a.clear(); // clear the screen
    a.pollButtons(); // track button presses since the last loop - used to only register single button presses at a time

    if (playerwin) {
        // Tell the player that they won!
        endScreen(playerwin);

    } else {
        // Ask the player for a number and play the game
        if (attempts == 7) {
            // Too many guesses - game over!
            endScreen(playerwin);
        } else {
            // Player has more attempts
            if (a.justPressed(UP_BUTTON)) { // Guessed number higer
                guessednumber = guessednumber + 1;
                a.digitalWriteRGB(RGB_OFF,RGB_OFF,RGB_OFF);
                sound.tone(NOTE_E2,80);
            }

            if (a.justPressed(DOWN_BUTTON)) { // Guessed number lower
                guessednumber = guessednumber -1;
                a.digitalWriteRGB(RGB_OFF,RGB_OFF,RGB_OFF);
                sound.tone(NOTE_D2,80);
            }

            if (a.justPressed(A_BUTTON)) { // Submit guess
                if (guessednumber == randomnumber) {
                    playerwin = true;
                } else {
                    attempts = attempts + 1;
                    lastguess = guessednumber;
                    a.digitalWriteRGB(RGB_ON,RGB_OFF,RGB_OFF);
                    sound.tone(NOTE_D1,60, NOTE_REST,20, NOTE_D1,60);
                }

            }

            a.setCursor(0,0);
            a.print("   GUESS THE NUMBER");
            a.print("\n  BETWEEN 1 AND ");
            a.print(randomlimit - 1);
            a.print("\n\n     Attempt: ");
            a.print(attempts);
            a.print("\n Number to Guess: ");
            a.print(guessednumber);

            if(attempts > 0) {
                // After the first guess, give a hint
                a.print("\n\n\n    ");
                a.print(lastguess);
                if (lastguess > randomnumber) {
                    a.print(" was too high");
                } else {
                    a.print(" was too low");
                }
            }
        }
    }

    a.display();

}

Any thoughts as to where I’ve gone wrong?

I suspect the problem is that you’re calling the tone function from within loop so every time loop restarts you’re ending up calling the function again before the tune has completed.

One thing you could do is:

if(!sound.playing())
  sound.tone(NOTE_D5,300, NOTE_G4,300, NOTE_C4,200);

Which will let the tune complete, but as soon as the tune has ended you’ll end up playing it again.

You could create a bool tuneHasPlayed; variable, but that’ll end up requiring more conditions and getting more complicated and soon you’ll end up with spaghetti logic.

Thankfully there’s a simpler option if you’re prepared to learn a new technique (and a few new language features)…


What you’re missing is the idea of a state machine.

Basically the idea is that your game can be in only one of a fixed number of states at any time. For example you might have the states ‘playing’ and ‘player won’ and ‘player lost’.

First off you’ll want an enumeration type to help you differentiate your states:

enum class GameState
{
	Playing,
	PlayerWon,
	PlayerLost,
};

This introduces the new datatype GameState, which you can then create a variable of:

GameState gameState = GameState::Playing;

The GameState type introduced by the enumeration declaration above can only hold one of three values: GameState::Playing, GameState::PlayerWon, or GameState::PlayerLost.

Then you can decide which game logic to run depending on what state you’re in:

void loop()
{
	// If it's not time to draw the next frame
	if (!a.nextFrame())
		// Exit the loop function early
		return;
	
	// Update the buttons
	arduboy.pollButtons();
	
	// Clear the screen
	arduboy.clear();

	// Choose what to do based on
	// the value of the 'gameState' variable
	switch(gameState)
	{
		// If 'gameState' is 'GameState::Playing'
		case GameState::Playing:
			{
				// Update and draw the game
			}
			break;
			
		// If 'gameState' is 'GameState::PlayerWon'
		case GameState::PlayerWon:
			{
				// Update and draw the win screen
			}
			break;
			
		// If 'gameState' is 'GameState::PlayerLost'
		case GameState::PlayerLost:
			{
				// Update and draw the lose screen
			}
			break;
	}
	
	// Update the screen
	arduboy.display();
}

Note that the switch statement behaves like having several different ifs and equality tests in a row. (E.g. if(gameState == GameState::Playing) { /*...*/ } else if(gameState == GameState::PlayerWon) { /*...*/ } et cetera.) It’s handy to use when you’re wanting to do one of many different things depending on the value of a single variable.

So essentially after the player has made their guess, you check whether they’ve met the conditions for winning or losing, and if either of those conditions has been met, you’d set gameState to the appropriate state.

So how does this help your tones issue?

Well, instead of setting the tones to play from within the ‘player lost’ state, you can instead initiate the tune at the point at which you change the state to the ‘player lost’ state.

So something like this:

if(/*player has lost*/)
{
	// Transition the game into the 'player lost' state
	gameState = GameState::PlayerLost;
	
	// Initiate the 'you lose' tune
	sound.tone(NOTE_D5,300, NOTE_G4,300, NOTE_C4,200);
}

That way the tune will only play once, starting on the frame before the ‘you lose’ screen, and continuing from there as the ‘you lose’ screen is drawn.

Structuring your code this way will make it easier to do other things later on, like adding a title screen, or high score screen.

It also makes it easier to design and plan a game because you can draw a state diagram to help you decide how your game needs to behave.

6 Likes

Great! Looks like I was along the right lines trying to diagnose this earlier…

The two methods here (!sound.playing() and bool tuneHasPlayed) were also things I had considered, but I felt…

Bam! There was a new and “correct” way of doing this. State machine - great!

Thank you for the pointers, Pharap. I’ll read up on this code structure, and refactor everything to use the State Machine.

I guess that serves me right for jumping ahead beyond the tutorial. Enough knowledge to be dangerous, not enough knowledge to know what I’m doing!

1 Like

Jumping ahead isn’t necessarily a bad thing, sometimes applying logic and being inventive can get you good results, but it also pays to do your research as well.

Particularly as programming is an old enough (for want of a better word) skill that there will almost always be someone who has had the same problem and already come up with a solution.

“Always remember, however, that there’s usually a simpler and better way to do something than the first way that pops into your head.” - Donald Knuth

Fortunately ‘dangerous’ when writing non-professional software usually just means a bug or a crash.

Feel sorry for the programmers who are responsible for writing pacemaker firmware, or the software used in a million-dollar space probe.

The-Two-States

1 Like

A lot of the stuff I do tends to be on my own (with at least a few google searches), I never really just sit down and watch a 4 hour video. I prefer it like that since I feel like it gives you a better understanding of what you’re actually doing, and then you do it better than just copying off a tutorial.

I relate too deeply with this one.

Personally I find written articles tend to be better than video tutorials.

Though I have seen a few video lectures/presentations that I’ve found particularly insightful. (Particularly one Bjarne Stroustrup did in 2012.)

It gives you practice with problem solving, which is a good thing, but it won’t give you a better understanding than reading articles about the language and programming techniques would. Ideally you’d do both to get both an abstract understanding and an ‘in-practice’ understanding.

I wasn’t intending to quote Knuth again, but:

“If you find that you’re spending almost all your time on theory, start turning some attention to practical things; it will improve your theories. If you find that you’re spending almost all your time on practice, start turning some attention to theoretical things; it will improve your practice.” - Donald Knuth

Tutorials are a mixed bag. Some tutorials are good for demonstrating a technique, but you still need to develop enough understanding to be able to generalise the demonstrated techniques to other situations.

Often it’s better to read more than one tutorial demonstrating the same technique to help understand the generalisable aspects.

Either way though, you should never just copy-and-paste code from a tutorial, you should ideally type it out manually, and at the very least mess around with it until you understand precisely what each part is doing. After all, the end goal is to understand what the tutorial is trying to teach you, not to simply replicate the code that the author has written (that’s what open source is for).

1 Like

In the sprit of completion, here’s the finished(?) game!

05_HigherLower.ino.hex (38.9 KB)

I’m sure I could be more efficient with the graphics - for example, shrinking the graphic size to just the bounding box of the text, and using Arduboy2 library functions to draw the remainder of the white background - but I’m quite happy with this.

*The emulator doesn’t seem to give my bleep-boops but no matter. Just imagine :stuck_out_tongue:


This is exactly my cycle when programming - about every 10 minutes. I love it!

Exactly why I undertook to extend and play with the tutorial program. Unfortunately I couldn’t describe my strucutral issue (state machine) well enough for a search engine to present that solution, but y’all here on the forum have been really helpful. Thanks!

2 Likes

Very cool!

I love the graphics you have put in - it really spices it up.

As suggestion or two. It would be awesome if you could hold the button down to scroll through the numbers - arduboy.pressed() - as I think I pressed the up and down buttons 60 or 70 times throughout a game! Secondly, maybe start the player at some random number rather than 1 as the first thing the player will do is want to scroll up to around 50.

You can play it online here with sounds.

2 Likes

Without seeing the code’s current state, I couldn’t really say either way.

Though that (i.e. speaking of efficiency) does remind me: Something Crait’s tutorial won’t tell you…

You can conserve RAM by wrapping any string literals that you pass to print or println in the F macro. E.g. arduboy.print(F("Attempt: "); instead of arduboy.print("Attempt: ");.
Normally any string literal is kept in progmem and then copied into RAM before use for technical reasons, but the ‘magic’ underlying the F macro both prevents the copy and makes it unnecessary.

Be sure to enjoy it while it lasts.

(Which fortunately should be quite a while, depending on how deep down the rabbit hole you want to go.)

Proof, if ever it was needed, that humans still have an advantage over machines.


As soon as I saw that I suddenly realised that all the experienced programmers are going to have the same unfair advantage.

Most of the graphics are fullscreen (128x64px) images, embedded in the code as uint8_t PROGMEM array[]. So I’m assuming it would be more space efficient to only make the minimum resolution necessary a graphic (smallest bounding box around text), and use built-in drawing tools to “extend” the plain white background / diagonal lines etc.

I read up on this, and it’s a very interesting macro. I’ll keep it in mind!


For anyone following this pimp-my-tutorial game, I’ve posted it as a GitHub repo, and release v1.1.0 includes held button support (I’m not entirely satisfied yet) and random starting point.

1 Like

The held button (and sound effect!) works well.

1 Like

Actually, it depends on how many graphics you’ve got and how much space they waste.

The drawing functions also occupy progmem when you use them, and don’t occupy any space when you don’t. (There’s a saying that in C++ “you don’t pay for what you don’t use”.)

So ultimately one approach outweighs the other only depending on the actual usage. If you’ve got lots of 128x64 images and you can potentially cut them down either by a multiple of 8 vertically or by any amount horizontally then you’ll likely save space by drawing rectangles as appropriate, but if you’ve only got a few images that can be sufficiently shrunk then it might actually be cheaper to leave the images as-is.

On a quick glance, it looks like the top row of your credits image is all 0xFFs and the bottom two rows are all 0x00s, (128 \times 3 = 384 bytes in total,) so that’s likely a good candidate to be shrunk and supplemented with fillRect and actually result in a memory saving overall, at which point it would likely be worth looking at some of the other images for similar savings.

Though it’s probably not worth trying to look at diagonal lines or trying to break up anything more than solid vertical or horizontal lines that can be trimmed away simply because the number of function calls and/or complexity of code you’d need to replicate the shapes would likely outweight the benefits/savings.

Programming optimisation and efficiency are very much a balancing act between space (i.e. memory), time (i.e. CPU cylces spent), and flexibility/functionality. As a general rule, you can’t benefit one without hampering another. (And if you ever can improve all three, you probably implemented something in a really naive and inefficient way in the first place.)

The very act of writing (procedural) code spends memory and CPU time in order to produce functionality.


A few more points of advice:

Firstly, the loop is actually redundant since you’d get the same effect from just doing a.delayShort(30 * 5); (if you aren’t sure why, think about how the CPU would actually run this code, and how many times a.delayShort(30); is actually being called).

Secondly, it’s actually best to avoid having any kind of delay, be it delay or delayShort.

The cheap (though not 100% accurate) way that most games simulate delays is to use arduboy.everyXFrame(n), which returns true when the frame counter is on a multiple of n and false otherwise. For example, at 60fps, 1 frame runs at roughly 1/60th of a second, so if you were to use if(arduboy.everyXFrame(6)) { /*do something*/ }, the /*do something*/ would happen every 6/60ths (i.e. 1/10th) of a second.

It’s not entirely accurate because A) the frame counter resets after 255, so it’ll get a bit wonky around that threshold and B) the frame counter runs every frame, not just where you’re using the everyXFrame logic, so the first time might happen a bit quicker, but generally it’s a quick and easy way to do that sort of logic.

The alternative is to actually manually count frames or time since some event (e.g. a particular button being pressed), which is what a desktop game would do, but can seem like a bit of a waste of resources on Arduboy, unless you can afford to do so.

Additionally…

By returning from loop here you’re actually skipping over the call to display at the end, which you probably can just about get away with here but you should be careful because in other cases it might mean you skip drawing a frame, which in something more complicated could lead to graphical artefacts.

There’s two ways to fix this:

  1. Use break instead, which will just break you out of that particular case in the switch
  2. Move each state’s logic into a separate function so the return will actually return from that function and thus won’t skip over the display call.

Option 2 is much better from an organisational perspective. It’s better to have a single function per state than to try to cram the implementation for half a dozen different states into the same loop function.

(As a rule of thumb, if you ever find yourself faced with a function that’s approaching 100-150 lines, you should stop and ask yourself if there are any obvious ways it can be broken down into smaller logical pieces.)

Ideally you should even split the states into both ‘update’ and ‘render’ functions, both for the benefit of ‘separation of concerns’ and because sometimes you can reuse the same rendering logic for two different states that differ only by control logic, or vice-versa.

Hmmm, this is a fun little optimisation puzzle… this is tickling my programming brain in just the right ways. I’m going to have a fiddle…


Thanks, Pharap! I’m still playing with these to implement and understand, but I wanted to reply so you don’t think I’m ignoring sage wisdom.

1 Like