Arduboy Classes and Header files?

I still have no idea how to compare a uint8_t to an enum value.

Why are you trying to compare a uint8_t to an enum class?
(There might be a better solution to the problem you’re trying to solve.)

You can convert between an enum class and a uint8_t using a simple static_cast.
E.g.

const char emptyText[] PROGMEM = "Empty";
const char noughtText[] PROGMEM = "Nought";
const char crossText[] PROGMEM = "Cross";

const char * const cellNames[] PROGMEM
{
  emptyText, noughtText, crossText,
};

const __FlashStringHelper * getNameOf(Cell cell)
{
  // Simple static cast
  uint8_t index = static_cast<uint8_t>(cell);
  return reinterpret_cast<const __FlashStringHelper *>(pgm_read_word(&cellNames[index]));
}

Or more simply:

if(static_cast<uint8_t>(cell) > static_cast<uint8_t>(Cell::Empty))
{
  // Cell is Nought or Cross
}

But that’s a bit of a waste because you could just do:

if(cell != Cell::Empty)
{
  // Cell is Nought or Cross
}

Im comparing it with a variable in a for loop i have. The code is this:

if (player == yi) {//do stuff}
So i guess static_cast?

Remind me why you’re trying to loop through the values again?

Like I say, there might be a better way.
(Since player can only really be Cell::Nought or Cell::Cross, a loop might be overkill.)

Alternatively if you really want int-like functionality you could write some custom operators.
(@filmote’s Scoped Enumerations article on Volume 9 page 29 of the magazine covers how to do this, but I can demonstrate as well if you wanted.)

Its for generating the player panel.

Generating how exactly?
Is the panel the same as the board?

Its very similar to drawGridAt. Heres the code.

void Game::updatePlayerPanel(uint16_t x, uint16_t y) {
  for (uint8_t yi = 0; yi < 2; yi++) {
    uint16_t yPos = y + (yi * 15);

    arduboy.setCursor(x + 15, yPos + 2);
    arduboy.drawRect(x, yPos, 10, 10);
    
    arduboy.print("P");
    arduboy.print(yi + 1);
    
    if (player == yi) {arduboy.fillRect(x + 2, yPos + 2, 6, 6);}
  }
}

In that case yeah,

if(static_cast<uint8_t>(player) - 1 == yi)
{
  arduboy.fillRect(x + 2, yPos + 2, 6, 6);
}

would work.

You might want to consider maintaining the player’s index and the player’s cell/symbol separately though, especially if you later plan to add the ability to let a player choose whether they’re noughts or crosses.

You could have something like:

class PlayerInfo
{
  Cell playerCells[2];
  uint8_t playerIndex;

  const Cell & getPlayerCell(void) const
  {
    return this->playerCells[this->playerIndex];
  }
};

To hold the player state.

I really just want to add saving now, not much else since i dont think a game as simple as this needs a character select

Fair enough.

I’d recommend going back to it at a later date to add stuff though.
Noughts and crosses is a good game for practicing code design on.

Ive been trying to add a system that checks if the board is full and with no matches, I made this:

  // If game returns no winner
  if (winrar == Cell::Empty) {
    // Do 2 iterations, y has a second conditional to break its loop when catsGame is not 1 anymore
    for (uint8_t y; y < 3 and isCatsGame == 1; y++) {
      for (uint8_t x; x < 3; x++) {
        // If the selected grid value is equal to Cell::Empty, then make isCatsGame 0 then break, therefore breaking the outer loop i think
        if (grid[y][x] == Cell::Empty) {
          isCatsGame--;
          break;
        
        }
      }
    }

    // If the variable has not changed then that means no empty spaces are on the board, therefore cats game
    if (isCatsGame == 1) {
      sound.tone(400, 100, 300, 100, 200, 100);
      gstate = 2;
      // else, normal place sound.
    } else {sound.tone(200, 100, 150, 100);}

the issue is that it does not ever change the variable to zero at all I think, which means that it will register that there is no more spaces left and make the game output that it was a cats game. Not sure why.

The best solution for solving issues like this is to write down how you would solve the problem, identify the logical conditions involved, formalise that into pseudocode and then turn that into proper C++ code.
(That’s pretty much what most programmers do, but usually they do it in their head unless it’s a particularly hard problem.)

E.g. You might describe the solution as:

  • Look at every cell from top left to bottom right
    • if any cell is empty, it’s not a draw
    • else, it’s a draw

Or to put it another way, you know that there’s a draw if no cells are empty and it’s not a draw if there are any empty cells, so what you’re really asking is “are there any empty cells?”.

The first bit is your loop, the second parts are what the condition would be.

bool hasEmptyCell(void)
{
	// Look at every cell
	for (uint8_t y; y < 3; y++)
	for (uint8_t x; x < 3; x++)
	{
		// If a cell is empty...
        	if (grid[y][x] == Cell::Empty)
		{
			// Found an empty
        		return true;
		}
	}
	return false;
}

Then you can do:

if (hasEmptyCell())
{
	sound.tone(200, 100, 150, 100);
}
else
{
	sound.tone(400, 100, 300, 100, 200, 100);
	gstate = 2;
}

Note that you could turn that loop into its own “isADraw()” function, or more accurately “hasNoEmptyCells” (or invert the logic and call it “hasEmptyCells”).


I was going to blather on about how this loop is effectively a more optimal generic version of the || operator combined with a !, but I’ll leave that for now.

Basically the short circuiting logic operators || and && can be generalised to work over the entire contents of a data structure.
(Normal C++ has a thing for doing that in its stdlib, but Arduino doesn’t have a version of the stdlib for various reasons.)


Lastly: if you find yourself wanting to call a variable is something, then it should probably be a bool. If it shouldn’t, you’ve probably picked the wrong name.

Turns out it was just that I keep forgetting to set my for loop variables to zero, expecting c++ to do it for me.

I’m pretty sure not setting them to 0 by default is supposed to be an optimisation from back in the days where it made a big impact (before optimisers were clever).

It’s better to always be explicit anyway, then you (generally) don’t have to remember what the default it.

Hey uh, Im having trouble with enums again. I ended up putting the enums into their own header/cpp pair files.

I put this in classes.h
enum class States : uint8_t;

And put this in classes.cpp

enum class States : uint8_t {
  Title, Game, // Game screens
  
  Main, Paused, Winner // Game states
};

I included classes.h in the game.h file and wrote this:
States GameState = States::Main;

For some reason it raises this error even though everything is correct.

exit status 1
'Main' is not a member of 'States'

Scoped enums don’t need to be separated into .h and cpp files.
If you separate them then things including the .h don’t know what values are available to assign to the enum.

Separating into .h and cpp is mainly for (non-template) functions (and sometimes progmem stuff).

But when I put the stuff in classes.cpp into classes.h I get this error:

multiple definition of 'enum class States'

Is the .h file missing include guards or a #pragma once by any chance?

(#pragma once is easier.)

yeah, pragma fixed it for some reason.

If you don’t have include guards or pragma once then a file can accidentally have its contents included twice.

For definitions (e.g. a full enum class definition, a class definition or a functiondefinition) that results in an error because things are supposed to only be defined once (but can be declared multiple times).

#pragma once is a compiler extension (available on pretty much all compilers because of how popular it is) that prevents a file being included twice.

Include guards are another way to prevent something being defined twice, but they’re a bit more olf fashioned and take longer to write (plus you have to manually make sure there aren’t any clashes).