Advertisement

Console Menu Structure - How to get node type

Started by July 02, 2019 09:04 PM
30 comments, last by Geosesarma 5 years, 2 months ago

This is just a part of the standard C++ convention, but it's still useful in your case.

Quote

How should I actually get the type of my MenuItem? Lets say I want to find the options SubMenu and add some ActionItems at runtime

Also you should probably use objects than just pointers to objects in a vector<> . This way you reduce memory usage and a vector actually cleans itself automatically when out of range.

This could lead to loss of pointers, which can bring to confusion as smarter pointers are defined as "better"("smarter") by C++ standard than new and delete.

Smart pointers prevent you from having too many pointers with complicated connections across data structures(functions). Also they are always automatically deleted when completely out of scope.

Nodes are parts of a linked list, so you should make a link list structure(just a match map in Unreal Tournament 2004 - Onslaught mode).
A node represents a part of a linked list, but it is also a building block of a linked structure.

Linked nodes in the aforementioned Onslaught mode work independently. They are linked to each other, but each of them contains their own independent information.

Nodes connect between each other, they check other nodes like connected to other nodes. However, making such a structure can very expensive what regards implementation time investment and RAM usage.

So my suggestion would be, use the independence of each node. So whenever a node's link has changed, only the array of connections is changed, meaning before the connected node, it was set from one third note

If both second and third are connected to the first node, you would apply the same process as the third node is independent as well.

I have tried this: (personal preference, but I'm making a menu system in my console-text game myself) :P

main.cpp


int main()
{
	//load menus from data files here

	Menu submenu1;
	submenu1.addOption("New game");

	MenuSystem menu(submenu1, 1);

	for (;;)
	{
		menu.loop();

		if (menu.getExit())
		{
			break;
		}
	}
}

Menu.h


#include <iostream>
#include <vector>

class Menu
{
	std::vector<std::string> options;
	int id{ 0 };						//sort through
	int target_id{ 0 };
};

MenuSystem.h


#pragma once
#include "Menu.h"

class MenuSystem
{
	std::vector<Menu> menu_list;
	int number_of_menus{ 0 };
	const int starting_menu{ 1 };
	static bool exit;
	int current_menu{ starting_menu };

	int current_target{ 0 };

	//sort through menu IDs
};

I would do this:(in your case)

Basically you get the Node name, by sorting through using its private: data identifier and once you get the correct ID,
take the std::string from the Node which contains the node's name.

7 minutes ago, Acosix said:

This could lead to loss of pointers, which can bring to confusion as smarter pointers are defined as "better"("smarter") by C++ standard than new and delete.

Smart pointers prevent you from having too many pointers with complicated connections across data structures(functions). Also they are always automatically deleted when completely out of scope

First things first: Ofc you cant know it but the code I used in my example is just a quick "expression of my thoughts". I´m aware of possible memory leaks and that I should use smart pointers instead of raw pointers ^^

 

10 minutes ago, Acosix said:

I have tried this: (personal preference, but I'm making a menu system in my console-text game myself) :P

 

Maybe I do not quite understand your code or how it translates to my problem but I think it´s not far away from registering an enum or am unique identifier in the ctor of my classes and give me the right type back based on these "tags". On the other hand I could use your "Linked List approach" then I could say: A Submenu can have a parent (if its nullptr then I know I´m at the root level) AND a Submenu can have either a pointer to another Submenu OR a pointer to an ActionItem. Thus I only have to check which pointer is not a nullptr. 

Advertisement
1 hour ago, Geosesarma said:

Maybe I do not quite understand your code or how it translates to my problem but I think it´s not far away from registering an enum or am unique identifier in the ctor of my classes and give me the right type back based on these "tags". On the other hand I could use your "Linked List approach" then I could say: A Submenu can have a parent (if its nullptr then I know I´m at the root level) AND a Submenu can have either a pointer to another Submenu OR a pointer to an ActionItem. Thus I only have to check which pointer is not a nullptr. 

So did I answer your question then?

What I presented in my approach is naming / getting a node.by using an enum (or string in my case). 

In most cases an ActionItem is just a target to another specific menu. When it comes to selecting profile and such, you just use a class Profile and ProfileHandler.

So you would basically get the NodeType the same way. You can use action items as I described ActionItem:

This would be the code in which I actually suggested for you(not only what I have tried):

ActionItem.h


#include "nodetypes.h"
class ActionItem
{
	bool menu_target{true};
	//basically two types of action items: a node which switches to another(true)
	//and a node which makes extra data handling(false)

	console_game::All_Node_Types selected_node_type;

	void tranlate_from_file(ActionItemData& data);		
	//this then loads an action item from a file
};

nodetypes.h


namespace console_game
{
  	enum class All_Node_Types
  	{
  		SwitchMenu = 1,
  		ProfileMenu = 2
  		//...
  	};
}

Node.h


#include "NodeType.h"
#include <string>
  
class Node
{
	//what I just called "menu option"
  	
  	NodeType type;
  	
  	std::string node_name;
};

NodeType.h


#include "ActionType.h"
  
class NodeType
{  	
  	ActionItem a;
};

main.cpp


#include "Node.h"
  
int main()
{
	//load node names and types; and ActionItems from files or source code
}
Quote

Ofc you cant know it but the code I used in my example is just a quick "expression of my thoughts".

I still don't like exemplary code, other than pseudo-code. It is hard to understand and read to me.

To get a NodeType then:

 -you define nodes or in the main() or load them from a file in the start of the main function

-load the NodeType when you need it


class ActionItemData
{
	std::string file_name;
};


//main.cpp example

#include "Node.h"

void getNodeType(NodeType& set_type, console_game::All_Node_Types& result)
{
	if (set_type.menu_target)
	{
		result = set_type.selected_node_type;console_game::All_Node_Types::Switch_Menu;
	}
	else
	{
		result = set_type.selected_node_type;
	}
}

int main()
{
	Node a1;
	NodeType type;
	ActionItem a_item;
	
	ActionItemData data;
	data.file_name="C:\\data.txt";

	a_item.load(data);

	type.a=a_item;
	
	console_game::All_Node_Types final_result;

	getNodeType(type, final_result);
}

 

If S2 exists (and you draw it so it's likely there), then I don't see why it should not be trusted. You could add submenus too at runtime, probably with a (skip adding if it already exists), to avoid all kinds of mess if two new actions both need the same submenu.

A different approach is to skip drawing empty submenus (as well as submenus that contain only empty submenus, recursivley). Then you could add the entire submenu structure you ever need in advance. One step further, if you add all actions as well, with a flag "don't show", you don't need to add actions in runtime either (other than clearing the flag).

 

What I provided was just a sample code. Just rename "Node" into "Submenu"

My point is I use composition ( building objects from other objects ) over inheritance whenever possible, especially over polymorphism.

If you really want to use polymorphism though:


class MenuItem {
public:
  virtual void action() = 0;
  virtual string& getItemName() = 0;
}

class ActionItem : public MenuItem {
public:
  void action() override {
  	//Trigger stuff
  }
  string& getItemName() {
  	return itemName;
  }
}

class SubMenuItem : public MenuItem {
public:
  void action() override {
  	//The problem is here. You are adding an action, even though a submenu item
	//doesn't have an "action" where you trigger something.	It just switches to
	//another menu.

	//Also you have only two types of menu items, and probably won't have more
	//than that if I got you.

	//An ActionItem actually does work outside of the scope of the menu system
	//operating(like selecting an option).
	
	//So it's confusing.

	switchMenus();
  }
   string& getItemName() {
  	return itemName;
  }
	
  void switchMenus()
  {
	//Here the problem arises, you can switch menus this way by just
	//setting the target ID and the current ID
	
	//but this leads to composition

	the source of the problem is you should have provided us with ChildItems,
	in this case menu options(note: not game options)
	
	a submenu doesn't exactly have an action item...a node therefore isn't an
	submenu as type of a menu

	a node should be a child item of a submenu
  }

  int target{0};	//targeted SubMenuItem
  int ID{0};		//current SubMenuItem
};
    

What I wanted to show you in the "Node" code is a node should be actually a child option.

All in all, I think this is the easiest way to solve your problem:


class ChildItem: public MenuItem
{
	void action() override
	{
		//check if the child item needs an ActionItem
		//or if just switches the SubMenuItems to be displayed
	}
};

class SubMenuItem : public MenuItem {
private:
  vector<ChildItem*> childItems;
public:
  void action() override {
  	//display childItems
  }
   string& getItemName() {
  	return itemName;
  }
};

What is ItemName used for? Name of the submenu?

Advertisement

If you insist on trying a node to be a MenuItem and not a menu option; you could try this:

MenuItem


class MenuItem {
public:
  virtual void action() = 0;
  virtual string& getItemName() = 0;
  virtual const char* getType() = 0;
};

SubMenuItem


class SubMenuItem
{
private:
    vector<MenuItem*> childItems;
public:
    void action() override {
      //What to do?
  	}
     string& getItemName() {
      return itemName;
 	}
  
	virtual const char* getType() pverride
	{
		return "SubMenuItem";
	}
};

ActionMenuItem


class ActionItem : public MenuItem {
public:
  void action() override {
  	//Trigger stuff
  }
  string& getItemName() {
  	return itemName;
  }
	virtual const char* getType() pverride
	{
		return "SubMenuItem";
	}
}

main.cpp


#include <iostream>

int main()
{
	SubMenuType root;
	MenuItem& m_1=root;
	std::cout << "MenuItem type is " << m_1.getName();
}

 

Correction for the previous above post main.cpp:


std::cout << "MenuItem type is " << m_1.getType();

 

Maybe I should be more precise in what I want to achieve.

First things first: I´m only interessted in building the menu structure (no navigating or drawing logic).

A SubmenuItem is just a container which contains other "MenuItems".

A ActionItem is just a class, which triggers a function/an event.

Another class, maybe MenuNavigator should be able to iterate through the Menu. It also should be able to keep the current state (current selected, current SubmenuItem ect). Navigation could look like this: Player presses arrow down: cursor gets one "MenuItem" down. Player presses arrow up: cursor gets one "MenuItem" up. Player presses X: if current selected MenuItem is a SubmenuItem set its childs as the current "Submenu", if current selected MenuItem is an ActionItem, trigger a function/ an event. 

 

7 hours ago, Acosix said:

What is ItemName used for? Name of the submenu?

It´s just a name which should be displayed (like the windows filesystem -> a folder(=SubmenuItem) has a name, a file(=ActionItem) has a name )

Maybe I should just store pointers from classes which I want to use in a "Menu" class and use them directly. 

 

Quote

A SubmenuItem is just a container which contains other "MenuItems".

A container class would something like this:

MenuItem.h


class MenuItem
{
	int type_ID{0};
public:
	int getType_ID();

	MenuItem(int type){type_ID=type};
};

SubmenuItem.h


#include "MenuItem.h"
#include <vector>

class SubmenuItem
{
	std::vector<MenuItem*> menu_addresses;
  	int fixed_set{0};
  	int current_index{o};
public:
  	void add(const MenuItem* set);
  
	MenuItem* getAdress(int index)
	{
		return menu_addresses[index-1];
	}
  
  	int getSubMenuSize()
  	{
  		return static_cast<int>(manu_addresses.size());
  	}
};

SubmenuItem.cpp


#include "SubmenuItem.h"
  
void SubmenuItem::add(const MenuItem* set)
{
	menu_addresses.push_back(set); 
  	setType();
}

MenuNavigator.h


MenuNavigator.h
  
//and then just use:
#include "SubmenuItem.h"
  
class MenuNavigator
{
public:
	//SubmenuItem iterator

    enum class MenuNavigator::subTypes
    {
    	action_call=1,
		root=2,
		//...
    }

	//possible applications:
  	
	int findFirstType(SubmenuItem& loop_box, int type_wanted)
	{
		//finds the first of a type

		for(int iterator{1};iterator<=loop_box.getSubMenuSize();iterator+=1)
		{
        	if (type_wanted == static_cast<int>(loop_box.getAddress(iterator)->getType()))
          	{
          		return iterator;
          	}
        }
		
		return 0;
	}

  	int findAllOfSameType(SubmenuItem& loop_box, int type_wanted)
	{
		//finds all of the same type, stops after finding 5th menu item
	
		int results{0};
		
		for(int iterator{1};(iterator<=loop_box.getSubMenuSize() && iterator <= 5);iterator+=1)
		{
        	results+=1;
		}             
		
		return results;
	}
};

main.cpp


#include "MenuNavigator.h"

int main()
{
	MenuItem a{1};
	MenuItem b{1};
	MenuItem c{2};

	SubmenuItem D;
		
	D.add(&a);
	D.add(&b);
	D.add(&c);

	MenuNavigator E;

	//possible use:	

	//the 1 here means action call
	int first_index{ E.loop_findFirstType(D, 1)};		
	
	//here it means finding the node type
	//this should return 1(searching index result) as MenuItem
	
	//a is the first added MenuItem which has type 1

The problem is how you define a MenuItem container.

This topic is closed to new replies.

Advertisement