Beginner question


(Miloslav Číž) #21

I’ve been thinking something like this

Summary
#include <Arduboy2.h>

Arduboy2 arduboy;

#define MAX_MENU_ITEMS 8

class Menu
{
protected:
  uint8_t selectedItem;
  uint8_t itemCount;
  const char *items[MAX_MENU_ITEMS];
  Menu *parentMenu;

public:
  Menu(Menu *parentMenu = 0)
  {
    this->itemCount = 0;
    this->selectedItem = 0;

    for (uint8_t i = 0; i < MAX_MENU_ITEMS; ++i)
      items[i] = 0;

    this->parentMenu = parentMenu;
  }

  addItem(const char *item)
  {
    if (itemCount >= MAX_MENU_ITEMS)
      return;

    items[itemCount] = item;
    this->itemCount++;
  }

  uint8_t getItemCount()
  {
    return this->itemCount;
  }

  const char *getItem(uint8_t index)
  {
    if (index >= MAX_MENU_ITEMS)
      return 0;

    return this->items[index];
  }

  void moveCursor(bool up)
  {
    if (up)
    {
      if (this->selectedItem < this->itemCount - 1)
        this->selectedItem += 1;
    }
    else
    {
      if (this->selectedItem > 0)
        this->selectedItem -= 1;
    }
  }

  void draw()
  {
    arduboy.setCursor(1,1);

    for (uint8_t i = 0; i < this->itemCount; ++i)
    {
      arduboy.print(i == this->selectedItem ? '>' : ' ');
      arduboy.print(' ');
      arduboy.print(this->items[i]);
      arduboy.print('\n'); // new line
    }
  }

  virtual Menu *confirm()
  {
    return this;
  }

  virtual Menu *back()
  {
    return this->parentMenu == 0 ? this : parentMenu;
  }
};

class MenuSaveLoad: public Menu
{
public:
  MenuSaveLoad(Menu *parentMenu = 0): Menu(parentMenu)
  {
    this->addItem("1"); // better move strings to PROGMEM
    this->addItem("2");
    this->addItem("3");
  }
};

class MenuSave: public MenuSaveLoad
{
public:
  virtual Menu *confirm() override
  {
    // save here, can have another own confirm submenu
    return this;
  }
};

class MenuLoad: public MenuSaveLoad
{
public:
  virtual Menu *confirm() override
  {
    // load here, can have another own confirm submenu
    return this;
  }
};

class MenuConfirm: public Menu
{
public:
  MenuConfirm(Menu *parentMenu = 0): Menu(parentMenu)
  {
    this->addItem("yes");
    this->addItem("no");
  }
};

class MenuConfirmNew: public MenuConfirm
{
public:
  MenuConfirmNew(Menu *parentMenu = 0): MenuConfirm(parentMenu)
  {
  }

  virtual Menu *confirm() override
  {
    // new game
    return this;
  }
};

class MenuMain: public Menu
{
protected:
  MenuSaveLoad   menuSave;
  MenuSaveLoad   menuLoad;
  MenuConfirmNew menuConfirmNew;

public:
  MenuMain(Menu *parentMenu = 0): Menu(parentMenu)
  {
    menuSave = MenuSaveLoad(this);
    menuLoad = MenuSaveLoad(this);
    menuConfirmNew = MenuConfirmNew(this);

    this->addItem("new game");
    this->addItem("save");
    this->addItem("load");
    this->addItem("exit");
  }

  virtual Menu *confirm()
  {
    switch (this->selectedItem)
    {
      case 0: return &this->menuConfirmNew; break;
      case 1: return &this->menuSave; break;
      case 2: return &this->menuLoad; break;
      default: return this; break;
    }
  }
};

Menu *currentMenu;
MenuMain menuMain;

void setup()
{
  arduboy.begin();
  arduboy.setFrameRate(60);
  currentMenu = &menuMain;
}

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

  if (arduboy.justPressed(UP_BUTTON))
    currentMenu->moveCursor(false);
  else if (arduboy.justPressed(DOWN_BUTTON))
    currentMenu->moveCursor(true);
  else if (arduboy.justPressed(A_BUTTON))
    currentMenu = currentMenu->confirm();
  else if (arduboy.justPressed(B_BUTTON))
    currentMenu = currentMenu->back();

  arduboy.clear();
  currentMenu->draw();
  arduboy.display();
}

But I wonder if there is a simpler way. I’ve never found a dead simple way to implement simple menus, they’re difficult to me :smile: Maybe someone can suggest a better way?


(Pharap) #22

That won’t work.
You can’t just do Menu = 2; after changing it to an enum class, you’d have to do Menu = Menu::ConfirmMenu.

Likewise, you can’t do if (Menu_choice < 4) Menu_choice++; anymore, you have to do:

	if(Menu_choice < MenuChoice::Save)
		++Menu_choice;

Like I demonstrated, as well as making sure you include:

Menu & operator++(Menu & menu)
{
	menu = static_cast<Menu>(static_cast<uint8_t>(menu) + 1);
	return menu;
}

To be able to do ++Menu.

When you switch to an enum class, it’s all or nothing - you have to get rid of all the magic numbers or go back to not using an enum class.


Depends what’s most important: elegance, simplicity, flexibility or cheapness.

I know a very flexible technique using functions pointers, but it’s not necesarily simple and it’s not the cheapest way.

The simplest and cheapest way is through using switch statements and enum classes.

I don’t think a linked list is the best approach.
I think a stack of menus would be better.

By maintaining a stack of menus, you have more flexibility because a menu doesn’t need to know what its parent is, it just needs to be able to signal that it’s ready to be removed from the stack, and its parent will be whatever was on the stack before it.

(Menus are secretly just states.)

Also, the constructor should be using nullptr, not 0.

Menu(Menu *parentMenu = nullptr)
  {
    this->itemCount = 0;
    this->selectedItem = 0;

    for (uint8_t i = 0; i < MAX_MENU_ITEMS; ++i)
      items[i] = nullptr;

    this->parentMenu = parentMenu;
  }

Glad to see pre-increment being used.
But you’re going to have problems with this if you try to store the items in progmem.
When you pass a const char * to print, it expects it to be in RAM, so if you pass it something in progmem it will be trying to use the wrong assembly instruction and you’ll end up with gibberish.

Also, virtual functions and inheritance eat a lot of progmem, function pointers are much better.
(I know I used both in Minesweeper and Easter Panic, but that’s why I know they eat a lot.)


(Miloslav Číž) #23

Thanks, obviously my code was just an unoptimized draft, but it’s good to point out what would have to be improved.

For @ArduL I’d suppose something simple rather than flexible and efficient would be best.

Maybe we could design the menu so that there are no submenus, which would make this much easier? Something like

  back
  new
  save: 1    <--- right/left arrow here changes the slot number
  load: 2

Then each action except back will trigger a generic confirmation dialog.


(Simon) #24

I did not change Menu_choice to an enum. I left it as an integer.

I think the OP suggested that this was beyond his current skill set.


(NL) #25

I will ask the question once again.

ino file :

#include <Arduboy2.h>

#include "Functions.h"


void setup() {

  Serial.begin(9600);

  arduboy.begin();

  arduboy.setFrameRate(25);
  
}


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

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

  /////////////////HANDLING//////////////////////////
  if (Menu == 1) {

    if (arduboy.justPressed(B_BUTTON)) {
      Menu = 2;
    }
    /////////////////////Do stuff//////////
  }

  else if (Menu == 2) {

    if (arduboy.justPressed(DOWN_BUTTON)) {
      Menu_choice++;
      if (Menu_choice >= 4) Menu_choice = 4;
    }

    if (arduboy.justPressed(UP_BUTTON)) {
      Menu_choice--;
      if (Menu_choice <= 1) Menu_choice = 1;
    }

    if (arduboy.justPressed(B_BUTTON)) {

      switch (Menu_choice) {

        case 1:
          Menu = 1;
          break;

        case 2:
          // update the screen for the New function
          Menu = 3;
          Last_menu_choice = 2;
          break;

        case 3:
          // update the screen for the Load function
          Menu = 4;
          Last_menu_choice = 3;
          break;

        case 4:
          // update the screen for the Save function
          Menu = 4;
          Last_menu_choice = 4;
          break;

      }
    }
  }

  else if (Menu == 3) {

    if (arduboy.justPressed(DOWN_BUTTON)) {
      Confirm_menu_choice++;
      if (Confirm_menu_choice >= 1) Confirm_menu_choice = 1;
    }

    if (arduboy.justPressed(UP_BUTTON)) {
      Confirm_menu_choice--;
      if (Confirm_menu_choice <= 0) Confirm_menu_choice = 0;
    }

    if (arduboy.justPressed(B_BUTTON)) {

      switch (Confirm_menu_choice) {

        case 0:
          // update the screen for the New function
          Confirm_menu_choice = 0;
          Menu = 2;
          break;

        case 1:

          if (Last_menu_choice == 2) {
            //Stuff for the 'NEW' choice
          }

          if (Last_menu_choice == 3) {
            //Stuff for the 'LOAD' choice
          }

          if (Last_menu_choice == 4) {
            //Stuff for the 'SAVE' choice
          }

          Menu = 1;
          break;
      }
    }
  }

  else if (Menu == 4) {

    if (arduboy.justPressed(DOWN_BUTTON)) {
      Slot_Menu_choice++;
      if (Slot_Menu_choice >= 4) Slot_Menu_choice = 4;
    }

    if (arduboy.justPressed(UP_BUTTON)) {
      Slot_Menu_choice--;
      if (Slot_Menu_choice <= 1) Slot_Menu_choice = 1;
    }

    if (arduboy.justPressed(B_BUTTON)) {
      Menu = 3;
    }
  }

  ///////////////////////////DISPLAY//////////////////////

  switch (Menu) {

    case 1:
      arduboy.setCursor(10, 10);
      arduboy.print(F("Screen 1"));
      break;

    case 2:
      display_menu();
      display_menu_cursor();
      break;

    case 3:
      display_confirm_menu();
      display_confirm_menu_cursor();
      break;

    case 4:
      display_slots_menu();
      display_slots_menu_cursor();
      break;

  }

  arduboy.display();
}

Functions.h file :

Arduboy2 arduboy;

int Menu = 1;

int Menu_choice = 1;

int Confirm_menu_choice = 0;

int Last_menu_choice;

int Slot_Menu_choice = 1;


void display_menu()
{
  arduboy.setCursor(10, (1 * 10));
  arduboy.print(F("Back"));
  arduboy.setCursor(10, (2 * 10));
  arduboy.print(F("New"));
  arduboy.setCursor(10, (3 * 10));
  arduboy.print(F("Load"));
  arduboy.setCursor(10, (4 * 10));
  arduboy.print(F("Save"));
}

void display_menu_cursor()
{
  arduboy.setCursor(0, (Menu_choice * 10));
  arduboy.print(F(">"));
}

void display_confirm_menu()
{
  arduboy.setCursor(10, (1 * 10));
  arduboy.print(F("Are you sure?"));
  arduboy.setCursor(10, (2 * 10));
  arduboy.print(F("NO"));
  arduboy.setCursor(10, (3 * 10));
  arduboy.print(F("YES"));
}

void display_confirm_menu_cursor()
{
  arduboy.setCursor(0, ((Confirm_menu_choice * 10) + 20));
  arduboy.print(F(">"));
}

void display_slots_menu()
{
  arduboy.setCursor(10, (1 * 10));
  arduboy.print(F("Slot 1"));
  arduboy.setCursor(10, (2 * 10));
  arduboy.print(F("Slot 2"));
  arduboy.setCursor(10, (3 * 10));
  arduboy.print(F("Slot 3"));
  arduboy.setCursor(10, (4 * 10));
  arduboy.print(F("Slot 4"));
}

void display_slots_menu_cursor()
{
  arduboy.setCursor(0, (Slot_Menu_choice * 10));
  arduboy.print(F(">"));
}

If you compile, you’ll see how i want the arduboy react, do not matter the code.
I consider what i want to do is simple no?
I Believe that for a simple question there is a simple answer.

When you write:

Menu & operator++(Menu & menu) { menu = static_cast<Menu>(static_cast<uint8_t>(menu) + 1); return menu; }

or :

> Menu(Menu *parentMenu = nullptr)

{
this->itemCount = 0;
this->selectedItem = 0;

for (uint8_t i = 0; i < MAX_MENU_ITEMS; ++i)
  items[i] = nullptr;

this->parentMenu = parentMenu;

}

It’s like you are talking chinese to me!
I’m almost sure there is a simple answer to my question, otherwise how do you want to not discourage the beginner people in arduboy when they start?
Is it necessary to understand C++ perfectly?


(Miloslav Číž) #26

No, most people here (me including) are very far from it :slight_smile:

Sadly this is not always true, but don’t worry. Let me try something out.


(Miloslav Číž) #27

How about something like this?

#include <Arduboy2.h>

Arduboy2 arduboy;

enum GameState: uint8_t
{
  STATE_MENU,
  STATE_CONFIRM,
};

GameState state = STATE_MENU;

int8_t menuPosition = 0; // current menu item
int8_t slot = 0;         // for both save and load

#define MENU_ITEMS 4     // number of items in the main mene
#define SLOTS 5          // number of save/load slots

void drawMainMenu()
{
  for (int8_t i = 0; i < MENU_ITEMS; ++i)
  {
    arduboy.setCursor(1,i * 8);

    arduboy.print(i == menuPosition ? '>' : ' '); // draw arrow in front of selected item
    arduboy.print(' ');

    switch (i)
    {
      case 0: arduboy.print(F("back")); break;
      case 1: arduboy.print(F("new")); break;
      case 2: arduboy.print(F("load ")); if (i == menuPosition) arduboy.print(slot); break;
      case 3: arduboy.print(F("save ")); if (i == menuPosition) arduboy.print(slot); break;
      default: break;
    }
  }
}

void drawConfirm()
{
  arduboy.setCursor(1,1);
  arduboy.print(F("really?\nA: yes, B: no"));
}

void draw()
{
  arduboy.clear();

  switch (state)
  {
    case STATE_MENU: drawMainMenu(); break;
    case STATE_CONFIRM: drawConfirm(); break;
    default: break;
  }

  arduboy.display();
}

void handleMainMenu()
{
  if (arduboy.justPressed(DOWN_BUTTON))
    menuPosition++;
  else if (arduboy.justPressed(UP_BUTTON))
    menuPosition--;

  menuPosition = min(MENU_ITEMS - 1,max(0,menuPosition)); // keep the menu item within allowed range

  if (menuPosition == 2 || menuPosition == 3)
  {
    if (arduboy.justPressed(RIGHT_BUTTON))
      slot = min(SLOTS - 1, slot + 1);   

    if (arduboy.justPressed(LEFT_BUTTON))
      slot = max(0, slot - 1);
  }

  if (arduboy.justPressed(A_BUTTON))
  {
    if (menuPosition != 0)
      state = STATE_CONFIRM;
  }
}

void handleConfirm()
{
  if (arduboy.justPressed(B_BUTTON))
    state = STATE_MENU;
}

void handleMenus()
{
  switch (state)
  {
    case STATE_MENU: handleMainMenu(); break;
    case STATE_CONFIRM: handleConfirm(); break;
    default: break;
  }
}

void setup()
{
  arduboy.begin();
  arduboy.setFrameRate(60);
}

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

  handleMenus();
  draw();
}

It works a little different from what you specified – there is only one main menu and a confirm menu/dialog. You change the save/load slots with left/right arrows. Try it out yourself, it’s all just this one file.

It’s dirty but if you don’t intend to expand the menu beyond this, it can work, and I think you’ll be able to understand how it works. Anyway, feel free to ask.


(Pharap) #28

I’m still not sure what exactly you want to do.
You have a menu system, what part of it isn’t working?

I cleaned it up slightly, this time without enum classes.

#include <Arduboy2.h>

Arduboy2 arduboy;

uint8_t menu = 0;
uint8_t menuChoice = 0;
uint8_t confirmMenuChoice = 0;
uint8_t lastMenuChoice = 0;
uint8_t slotMenuChoice = 0;

void updateScreen1()
{
	if (arduboy.justPressed(B_BUTTON))
	{
		menu = 1;
	}
}

void drawScreen1()
{
	arduboy.setCursor(10, 10);
	arduboy.print(F("Screen 1"));
}

void updateMenu()
{
	if (arduboy.justPressed(DOWN_BUTTON))
	{
		if(menuChoice < 3)
			++menuChoice;
	}

	if (arduboy.justPressed(UP_BUTTON))
	{
		if(menuChoice > 0)
			--menuChoice;
	}

	if (arduboy.justPressed(B_BUTTON))
	{
		lastMenuChoice = menu;
		switch (menuChoice)
		{
		case 0:
			menu = 0;
			break;
		case 1:
			menu = 2;
			break;
		case 2:
			menu = 3;
			break;
		case 3:
			menu = 3;
			break;
		}
	}
}

void displayMenu()
{
	arduboy.setCursor(10, (1 * 10));
	arduboy.print(F("Back"));
	arduboy.setCursor(10, (2 * 10));
	arduboy.print(F("New"));
	arduboy.setCursor(10, (3 * 10));
	arduboy.print(F("Load"));
	arduboy.setCursor(10, (4 * 10));
	arduboy.print(F("Save"));
	
	arduboy.setCursor(0, ((menuChoice + 1) * 10));
	arduboy.print(F(">"));
}

void updateConfirmMenu()
{
	if (arduboy.justPressed(DOWN_BUTTON))
	{
		if(confirmMenuChoice < 1)
			++confirmMenuChoice;
	}

	if (arduboy.justPressed(UP_BUTTON))
	{
		if(confirmMenuChoice > 0)
			--confirmMenuChoice;
	}

	if (arduboy.justPressed(B_BUTTON))
	{
		switch (confirmMenuChoice)
		{
		case 0:
			confirmMenuChoice = 0;
			menu = 1;
			break;
		case 1:
			if (lastMenuChoice == 1)
			{
			}

			if (lastMenuChoice == 2)
			{
			}

			if (lastMenuChoice == 3)
			{
			}
			menu = 0;
			break;
		}
	}
}

void displayConfirmMenu()
{
	arduboy.setCursor(10, (1 * 10));
	arduboy.print(F("Are you sure?"));
	arduboy.setCursor(10, (2 * 10));
	arduboy.print(F("NO"));
	arduboy.setCursor(10, (3 * 10));
	arduboy.print(F("YES"));
	
	arduboy.setCursor(0, (10 + ((confirmMenuChoice + 1) * 10)));
	arduboy.print(F(">"));
}

void updateSlotsMenu()
{
	if (arduboy.justPressed(DOWN_BUTTON))
	{
		if(slotMenuChoice < 3)
			++slotMenuChoice;
	}

	if (arduboy.justPressed(UP_BUTTON))
	{
		if(slotMenuChoice > 0)
			--slotMenuChoice;
	}

	if (arduboy.justPressed(B_BUTTON))
	{
		menu = 2;
	}
}

void displaySlotsMenu()
{
	arduboy.setCursor(10, (1 * 10));
	arduboy.print(F("Slot 1"));
	arduboy.setCursor(10, (2 * 10));
	arduboy.print(F("Slot 2"));
	arduboy.setCursor(10, (3 * 10));
	arduboy.print(F("Slot 3"));
	arduboy.setCursor(10, (4 * 10));
	arduboy.print(F("Slot 4"));
	
	arduboy.setCursor(0, ((slotMenuChoice + 1) * 10));
	arduboy.print(F(">"));
}

void setup()
{
	arduboy.begin();
	arduboy.setFrameRate(30);  
}

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

	arduboy.pollButtons();

	arduboy.clear();

	switch(menu)
	{
		case 0:
			updateScreen1();
			break;
		case 1:
			updateMenu();
			break;
		case 2:
			updateConfirmMenu();
			break;
		case 3:
			updateSlotsMenu();
			break;
	}

	switch (menu)
	{
		case 0:
			drawScreen1();
			break;
		case 1:
			displayMenu();
			break;
		case 2:
			displayConfirmMenu();
			break;
		case 3:
			displaySlotsMenu();
			break;
	}

	arduboy.display();
}

When it comes to programming that isn’t always the case.
Some things that seem simple to describe are actually very difficult to implement.

Rule 1 of programming is that the computer is an idiot.
It will only do something if you explain how to do it,
and if you forget to tell it something - it won’t do that thing, because you didn’t tell it to.

Even something as deceptively simple as putting text on the screen is actually really complicated - print is a very complex function.

No, but you still shouldn’t shy away from code you don’t understand.
If there is some code you don’t understand yet, don’t avoid it.
Ask someone what it does, poke it with a stick, dissect it.


If you’re going to used named values, why not just use enum class instead of enum?

Using an outdated unscoped enum doesn’t give you any benefits here because you’re not converting it to an int at any point.


(NL) #29

Thank you pharap.
I put some more code. The menu is now finished and Following what you said it’s a dirty code, so i would like to ask you if there are any little tricks that could shrink it.
I know pharap could find a lot of things to do but my programming level will maybe not be sufficient (I’m only beginning to understand the pointers) but I’m ready to see.

#include <Arduboy2.h>

Arduboy2 arduboy;

uint8_t menuChoice = 0;
uint8_t confirmMenuChoice = 0;
uint8_t lastMenuChoice = 0;
uint8_t slotMenuChoice = 0;


enum class Menu : uint8_t
{
  Screen1,
  Menu,
  ConfirmMenu,
  SlotsMenu,
};

Menu menu = Menu::Screen1;

void updateScreen1()
{
  if (arduboy.justPressed(B_BUTTON))
  {
    menu = Menu::Menu;
  }
}

void drawScreen1()
{
  arduboy.setCursor(10, 10);
  arduboy.print(F("Screen 1"));
}

void updateMenu()
{
  if (arduboy.justPressed(DOWN_BUTTON))
  {
    if (menuChoice < 3)
      ++menuChoice;
  }

  if (arduboy.justPressed(UP_BUTTON))
  {
    if (menuChoice > 0)
      --menuChoice;
  }

  if (arduboy.justPressed(B_BUTTON))
  {
    lastMenuChoice = menuChoice;
    switch (menuChoice)
    {
      case 0:
        menu = Menu::Screen1;
        break;
      case 1:
        menu = Menu::ConfirmMenu;
        break;
      case 2:
        menu = Menu::SlotsMenu;
        break;
      case 3:
        menu = Menu::SlotsMenu;
        break;
    }
  }
}

void displayMenu()
{
  arduboy.setCursor(10, (1 * 10));
  arduboy.print(F("Back"));
  arduboy.setCursor(10, (2 * 10));
  arduboy.print(F("New"));
  arduboy.setCursor(10, (3 * 10));
  arduboy.print(F("Load"));
  arduboy.setCursor(10, (4 * 10));
  arduboy.print(F("Save"));

  arduboy.setCursor(0, ((menuChoice + 1) * 10));
  arduboy.print(F(">"));
}

void updateConfirmMenu()
{
  if (arduboy.justPressed(DOWN_BUTTON))
  {
    if (confirmMenuChoice < 1)
      ++confirmMenuChoice;
  }

  if (arduboy.justPressed(UP_BUTTON))
  {
    if (confirmMenuChoice > 0)
      --confirmMenuChoice;
  }

  if (arduboy.justPressed(B_BUTTON))
  {
    switch (confirmMenuChoice)
    {
      case 0:

        if (lastMenuChoice == 1)
        {
          menu = Menu::Menu;
        }

        if (lastMenuChoice == 2)
        {
          menu = Menu::SlotsMenu;
        }

        if (lastMenuChoice == 3)
        {
          menu = Menu::SlotsMenu;
        }
        break;

      case 1:

        if (lastMenuChoice == 1)
        {
          menu = Menu::Screen1;
        }

        if (lastMenuChoice == 2)
        {
          menu = Menu::Screen1;
        }

        if (lastMenuChoice == 3)
        {
          menu = Menu::Screen1;
        }
        break;
    }
  }
}

void displayConfirmMenu()
{
  arduboy.setCursor(10, (1 * 10));
  arduboy.print(F("Are you sure?"));
  arduboy.setCursor(10, (2 * 10));
  arduboy.print(F("NO"));
  arduboy.setCursor(10, (3 * 10));
  arduboy.print(F("YES"));

  arduboy.setCursor(0, (10 + ((confirmMenuChoice + 1) * 10)));
  arduboy.print(F(">"));
}

void updateSlotsMenu()
{
  if (arduboy.justPressed(DOWN_BUTTON))
  {
    if (slotMenuChoice < 4)
      ++slotMenuChoice;
  }

  if (arduboy.justPressed(UP_BUTTON))
  {
    if (slotMenuChoice > 0)
      --slotMenuChoice;
  }

  if (arduboy.justPressed(B_BUTTON))
  {
    if (slotMenuChoice == 0) menu = Menu::Menu;
    else menu = Menu::ConfirmMenu;
  }
}



void displaySlotsMenu()
{
  arduboy.setCursor(50, 0);
  if (lastMenuChoice == 2) arduboy.print(F("Load"));
  else if (lastMenuChoice == 3) arduboy.print(F("Save"));
  arduboy.setCursor(10, (1 * 10));
  arduboy.print(F("Back"));
  arduboy.setCursor(10, (2 * 10));
  arduboy.print(F("Slot 1"));
  arduboy.setCursor(10, (3 * 10));
  arduboy.print(F("Slot 2"));
  arduboy.setCursor(10, (4 * 10));
  arduboy.print(F("Slot 3"));
  arduboy.setCursor(10, (5 * 10));
  arduboy.print(F("Slot 4"));

  arduboy.setCursor(0, ((slotMenuChoice + 1) * 10));
  arduboy.print(F(">"));
}



void setup()
{
  arduboy.begin();
  arduboy.setFrameRate(30);
}

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

  arduboy.pollButtons();

  arduboy.clear();

  switch (menu)
  {
    case Menu::Screen1:
      updateScreen1();
      break;
    case Menu::Menu:
      updateMenu();
      break;
    case Menu::ConfirmMenu:
      updateConfirmMenu();
      break;
    case Menu::SlotsMenu:
      updateSlotsMenu();
      break;
  }

  switch (menu)
  {
    case Menu::Screen1:
      drawScreen1();
      break;
    case Menu::Menu:
      displayMenu();
      break;
    case Menu::ConfirmMenu:
      displayConfirmMenu();
      break;
    case Menu::SlotsMenu:
      displaySlotsMenu();
      break;
  }

  arduboy.display();
}

(Pharap) #30

I wouldn’t call it ‘dirty’ per se.
It’s simpler than some other approaches,
but it’s not doing anything bad as such.

I usually reserve the term “dirty” for code that is hard to read,
doing dangerous things (e.g. relying on implementation-specific or undefined behaviour),
or using particular inferior techniques (e.g. using #define SOME_CONST 42 instead of constexpr uint8_t someConst = 42;).

When you say ‘shrink’, do you mean the source code size or the compiled code size?

If you mean the compiled code, I can think of at least one very notable way to make it smaller,
but it involves doing something you probably don’t understand yet (using reinterpret_cast to alter the type of a pointer).
If you’re willing to try it then I’d be happy to explain how it works.

If you mean the source code, smaller source code isn’t necessarily better.
What really matters is whether the code is easy to read and whether it makes sense.

The key to understanding pointers is understanding RAM,
and the key to understanding RAM (as a concept) is to think of it as an array of bytes.

If you think of RAM as an array of bytes then every pointer is actually an index into that array,
and thus all pointers are in fact just regular integers, but with special syntax and behaviour.

Also, most variables and objects are just blocks of bytes in RAM.
(I say most, because sometimes they exist in registers instead, but that’s getting even deeper into hardware territory.)


A small note:
“code” is uncountable.
This means you don’t have ‘a code’, you have ‘some code’,
just like you don’t have ‘a water’ or ‘a sand’, you have ‘some water’ or ‘some sand’.
(I especially like the implication that code ‘flows’ like sand and water.)
You’re not the first person to make that mistake and sadly you won’t be the last.


(NL) #31

I meant the source code.
I think i will wait a little again before going right to more complex things.

My code is readable but maybe there are hints i can learn.


(Pharap) #32

My top tip at the moment would be to try to get comfortable with enum classes now that you’ve been introduced to them.
They make code a lot more readable and they’re just as cheap as regular integers.
They’re a very underrated language feature.

I will post it anyway, so you can come back and use it when you feel ready to do so:

using FlashString = const __FlashStringHelper *;

// Taken from Pharap's Minesweeper game
inline FlashString AsFlashString(const char * flashString)
{
	return reinterpret_cast<FlashString>(flashString);
}

const char * const displayMenuOptions[] =
{
	"Back", "New", "Load", "Save"
};

void displayMenu()
{
	for(uint8_t i = 0; i < 4; ++i)
	{
		if(menuChoice == i)
		{
			arduboy.setCursor(0, ((i + 1) * 10));
			arduboy.print('>');
		}
		
		arduboy.setCursor(10, ((i + 1) * 10));
		arduboy.print(FlashString(&displayMenuOptions[i]));
	}
}