Menu w programie konsolowym, tablica string czy zmienna z ifami ? Co lepsze.

0

Witam, piszę sobie mały obiektowy programik w konsoli w c++. I wiadomo jak to w programowaniu do jednej rzeczy można podejść na kilka sposobów i zastanawiam się który ze sposobów jest lepszy i jaki wpływ na szybkość działania by to miało (czy ma to w ogóle jakieś znaczenie). Piszę Menu.

LITTLERPG MENU

 ->  Nowa Gra  <-

 Wczytaj Gre

 O Autorze

 Zakończ

-> <- - oznacza która opcja jest wybrana i getchem za pomocą strzałek zmienia się i odświeża. Wiadomo metode z getch zwraca 1 wartość ale chodzi mi o samo stworzenie menu.

Zrobić można to za pomocą tablicy stringow.

Albo korzystając z pętli while i if'ów i po prostu inkrementacja :P Czyli np. domyślnie a=0 "Nowa Gra" po naciśnięciu klawisza UP robi a--; po naciśnięciu DOWN a++'

Zastanawiam się który sposób jest lepszy dla komputera ? Czy dużo pętli go bardziej boli niż tablica stringów. Mimo wszystko wydaje mi się że pętle są bardziej wydajniejsze.

1

Przecież to co opisujesz to są dwa oddzielne problemy.
W tablicy będziesz trzymał opcję.
A w pętli będziesz obsługiwał logikę.
Nie wiem z czego chcesz tu wybierać.

0

Zmapowane pozycje z akcją i cycled indekser modyfikowany przez wciśnięcie określonych klawiszy, nie ma co dalej wymyślać.

0

Czyli prawie jak zrobiłem for wyświetlający menu cout i na ifach wartości, przesuwanie określonym klawiszem zmiana wartości przy odpowiedniej wartość przesuwania wciśniecie enter powoduje akcje. Bardziej zastanawiało mnie czy wykorzystanie tablic string do wyświetlania to jest dobry pomysł ? Czy zwykłe couty to jest dobra i wystarczająca rzecz.

0

Co mają tablice do coutów?

0

Przesyłam kod wykonałem to tak, zastawiam czy da się prościej i lepiej ? Bo szukam zawsze różnych punktów widzenia.

MAIN.cpp

Menu m;

	while(a!=6){
	m.wyswietl_menu(m);
	a=m.przesuwaj_menu();
	};
 

Menu.h

 
#include "stdafx.h"
#include <iostream>
#include <conio.h>

using namespace std;

class Menu
{
	private:
		int c;
	public:
		int menu_poruszanie;
		Menu(int liczba_menu=0, int c=1)
		{
			menu_poruszanie=liczba_menu;
		};
		void wyswietl_menu(Menu& menu_poruszanie);
		int przesuwaj_menu();
}

Menu.cpp

#include "Menu.h"

#define KEY_UP 72
#define KEY_DOWN 80
#define KEY_LEFT 75
#define KEY_RIGHT 77
#define ENTER 13

void Menu::wyswietl_menu(Menu& menu)
{
	system("cls");
	for(int i=0;i<11;i++)
	{
		for(int j=0;j<3;j++)
		{
			if(i==1&&j>1){
				cout << " *****LITERPG MENU***** ";
			}else if(i==4&&j>1){
				cout << "      NOWA GRA      ";
				if(menu.menu_poruszanie==0){
				cout << " <- ";
				}
			}else if(i==6&&j>1){
				cout << "      WCZYTAJ GRE   ";
				if(menu.menu_poruszanie==1){
					cout << " <- ";
				}
			}else if(i==8&&j>1){
				cout << "      O AUTORZE     ";
				if(menu.menu_poruszanie==2){
					cout << " <- ";
				}
			}else if(i==10&&j>1){
				cout << "      WYJSCIE       ";
				if(menu.menu_poruszanie==3){
					cout << " <- ";
				}
			}else if(menu.menu_poruszanie>3){
				menu.menu_poruszanie=3;
			}else if(menu.menu_poruszanie<0){
				menu.menu_poruszanie=0;
			}
			cout << "   ";
		}
		cout  << endl;
	}
	cout  << endl;
}

int Menu::przesuwaj_menu()
{
	 do{
        switch((c=getch())) {
			if(c=KEY_UP){
        case KEY_UP:
			c=1;
			return menu_poruszanie--;
            continue;
			}else if(c=KEY_DOWN){
        case KEY_DOWN:
			c=1;
			return menu_poruszanie++;
            continue;
			}else if(c=ENTER){
		case ENTER:
			c=2;
			return 6;
            continue;
			}
			else
				cout << endl << "![STRZALKA]!" << endl;   // key down
		}
		}while(c!=1);
}
2

Przepraszam, że nie chce poprawiać twojego kodu, ale jestem aktualnie dosyć zajęty, a potrzeba by do tego trochę czasu jak tak patrzę. Może ktoś inny się pokusi.
Mógłbyś to zrobić mniej więcej w taki sposób. Coś w stylu tego co napisał spartanPAGE.

#include <iostream>
#include <vector>
#include <string>
#include <functional>
#include <cmath>
#include "conio.h"

constexpr int KEY_UP = 72;
constexpr int KEY_DOWN = 80;
constexpr int KEY_ENTER = 13;

class Menu
{
public:

    Menu() :
        m_longestText(0u),
        m_selection(0)
    {

    }

    void draw()
    {
        system("cls");
        std::cout << " *****LITERPG MENU***** \n\n\n";

        for(int i = 0; i < m_items.size(); ++i)
        {
            const auto& item = m_items[i];

            std::string prefix = "      ";
            std::string suffix = "      ";

            if(m_selection == i)
            {
                prefix = "-->   ";
                suffix = "   <--";
            }

            int spacesAfterPrefix = (m_longestText - item.text.size()) / 2;
            int spacesBeforeSuffix = m_longestText - (spacesAfterPrefix + item.text.size());

            std::cout << prefix;
            std::cout << std::string(spacesAfterPrefix, ' ');
            std::cout << item.text;
            std::cout << std::string(spacesBeforeSuffix, ' ');
            std::cout << suffix;
            std::cout << "\n\n";
        }
    }

    void update()
    {
        int keyCode = getch();
        if(keyCode == KEY_UP)
        {
            m_selection = std::max(0, m_selection - 1);
        }
        else if(keyCode == KEY_DOWN)
        {
            m_selection = std::min(static_cast<int>(m_items.size()) - 1, m_selection + 1);
        }
        else if(keyCode == KEY_ENTER)
        {
            m_items[m_selection].callback();
        }
    }

    void addItem(std::string&& text, std::function<void()>&& callback)
    {
        m_items.emplace_back(std::move(text), std::move(callback));
        m_longestText = std::max(m_longestText, text.size());
    }

protected:
    struct Item
    {
        Item(std::string&& _text, std::function<void()>&& _callback) :
            text(_text),
            callback(_callback)
        {

        }

        std::string text;
        std::function<void()> callback;
    };

    std::vector<Item> m_items;
    size_t m_longestText;
    int m_selection;
};

//wszystko ponizej bylyby w glownej klasie gry
void onNewGame()
{
    std::cout << "New Game selected...";
    for(;;);
}
void onLoadGame()
{
    std::cout << "Load Game selected...";
    for(;;);
}
void onAbout()
{
    std::cout << "About selected...";
    for(;;);
}
void onExit()
{
    std::cout << "Exit selected...";
    for(;;);
}

int main()
{
    Menu menu;
    menu.addItem("New Game", onNewGame);
    menu.addItem("Load Game", onLoadGame);
    menu.addItem("About", onAbout);
    menu.addItem("Exit", onExit);

    for(;;)
    {
        menu.draw();
        menu.update();
    }
    return 0;
}

(wybaczcie conio.h, getch() i system())
Jedyny problem z bawieniem się w konsoli jest taki, że ciężko jest zrobić cokolwiek zgodnie ze standardem i ładnie. Musiałbyś skorzystać z jakiejś zewnętrznej biblioteki. Np. ncurses.

0

Kilka pytań:
1.
callback - czytam, czytam no ale wujek nie w pełni umie mi to przekazać albo rozumiem tylko nie wiem kiedy wykorzystać.

function<void()> callback; ? ? ? deklaracja funkcji ?

Nie mam w zwyczaju używać 'const' dlaczego się z niego korzysta ? Zwykła nie zmienna deklaracja nie wystarczy ?

auto& - przeczytałem ale wolał bym się upewnić. Ja przeczytałem że automatycznie dobiera typy które zostaną użyte.

Kontener struct Item ??

Dopiero co zrozumiałem kontener napisałem takie przykładowe ale jeden problem ze zrozumieniem mi został:

void addItem(std::string&& text, std::function<void()>&& callback)
{
    m_items.emplace_back(std::move(text), std::move(callback));
    m_longestText = std::max(m_longestText, text.size());
}

po pierwsze zmienna m_items i m_longestText nie istnieje dla tej metody (jest niezdefiniowana) po 2 dlaczego dwa ampersandy ;P ?

te linijki niby wiem co powinny robić na podstawie tego z main ale mam problem ze zrozumieniem ich ;P

emplace_back - to takie push_back bez tempa przez co nie stworzy kopii.

zastanawiam się co te move robią dokładnie :P i druga linika. Bada długość tekstu ale kiedy i po co ? Dlaczego jest w max ? skoro text size to zwraca ?

1
  1. function<void()> callback w tym przypadku trzyma wskaźnik na funkcję, która ma się wywołać po wciśnięciu entera
    http://en.cppreference.com/w/cpp/utility/functional/function
    Jeśli chciałbyś, żeby callback to była metoda to musiałbyś jako callback podać
    std::bind(&Klasa::metoda, &obiektKlasy)
  2. const (czy też constexpr, który jest 'silniejszy') używa się, żeby zarówno programista, jak i kompilator wiedział, że wartość nie ulegnie zmianie. Umożliwia lepszą optymalizację kodu, polepsza czytelność i zapewnia, że nie zostanie ona niepożądanie zmieniona.
    http://en.cppreference.com/w/cpp/language/constexpr
  3. auto inferuje typ na podstawie wyrażenia. Działa na takich zasadach jak inferencja w szablonach.
    http://en.cppreference.com/w/cpp/language/auto
  4. Struktura w tym przypadku jest po to, żeby pogrupować dane.
0

Niestety nie potrafię zrozumieć tego callback'a i jego implementacji. Wiem co chciałeś zrobić ale nie wiem jak z tego korzystać no i brak mi przykładu do jego zrozumienia.

Z Vectorami już ogarnąłem zrobiłem podobnie, nie implementowałem tego w strukturze tylko bezpośrednio w kontenerze.

Można prosić o jakiś prosty przykład programu z callback ?
MainMenu.cpp

 
#include "stdafx.h"
#include <iostream>
#include "Menu.h"

using namespace std;

void onNewGame()
{
    std::cout << "New Game selected...";
    for(;;);
}
void onLoadGame()
{
    std::cout << "Load Game selected...";
    for(;;);
}
void onAbout()
{
    std::cout << "About selected...";
    for(;;);
}
void onExit()
{
    std::cout << "Exit selected...";
    for(;;);
}
 

int _tmain(int argc, _TCHAR* argv[])
{
	Menu menu;
    menu.dodajopcje("New Game");
    menu.dodajopcje("Load Game");
    menu.dodajopcje("About");
    menu.dodajopcje("Exit");
	while(menu.m_wybrane!=menu.opcje.size()+1)
	{
		system("cls");
		menu.rysuj();
		menu.przesuwaj();
		cout << menu.m_wybrane;
	};

	return 0;
}

Menu.cpp

 
#include "stdafx.h"
#include "Menu.h"

using namespace std;

void Menu::rysuj()
{
	std::cout << " ******LITERPG MENU****** \n\n\n";
		for(int i=0;i < opcje.size(); i++)
		{
			string prefix = "       ";
			string suffix = "       ";


			if(m_wybrane == i)
			{
				prefix = " -->   ";
				suffix = "   <-- ";
			}

			cout << prefix;
			cout << opcje[i];
			cout << suffix;
			cout << endl;
		}
}
 
int Menu::przesuwaj()
{

const int KEY_UP = 72;
const int KEY_DOWN = 80;
const int KEY_ENTER = 13;

	int nrPrzycisku = getch();
		if(nrPrzycisku == KEY_UP)
		{
			if(m_wybrane==0)
				{
					m_wybrane += 1;
				}

			return m_wybrane -= 1;
		}
		else if(nrPrzycisku == KEY_DOWN)
		{
			if(m_wybrane == opcje.size() - 1)
				{
					m_wybrane -= 1;
				}

			return m_wybrane += 1; 
		}
		else if(nrPrzycisku == KEY_ENTER)
		{
			return opcje.size()+1;
		}

}

void Menu::dodajopcje(string text)
{
        opcje.emplace_back( move(text));
}

Menu.h

 
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <string>
#include <functional>
#include <cmath>
#include "conio.h"
using namespace std;
 
class Menu
{     
    public:
	string text;
        Menu():
			m_wybrane(0)
		{
		}

        void rysuj();
        int przesuwaj();
		void dodajopcje(string text);
		vector<string> opcje;
		function<void()> callback;
		int m_wybrane;
};
0

Nie potrafisz zrozumieć, to znaczy konkretnie z czym masz problem?
Poza tym, lepiej zrób to w sposób który zaproponował ci @Sopelek. To co dzieje się u ciebie w głównej pętli to jawne łamanie enkapsulacji, mniej więcej chodzi o to, że jeśli masz klasę menu, to ona powinna zajmować się rysowaniem i obsługą menu, a nie mieć pół logiki w jakimś innym miejscu a pół jescze gdzie indziej.
Generalnie callbacka możesz sobie na razie odpuścić, ale nie rób tak jak masz teraz.
Ale jeśli już koniecznie chcesz tego callbacka, to.

  • W ifie który wykona się gdy wcisniesz enter wstaw callback().
  • Utwórz sobie w klasie Menu funckje registerCallback(std::function<void()> ..)
  • Utwórz w klasie Menu vector (czy mape z indexami jako nazwy wpisów w menu, co tam chcesz) std::function
  • I po prostu w zależności co tam wybierzesz, wywołaj dany callback.
    Jeśli chodzi o idee std::function, to jest to taki ładnie opakowany pointer na funkcje.
3

Spójrzmy najpierw na przykładową implementację zaplętlonego indeksera:

class CycledIndexer{
    using Number = int;
public:
    CycledIndexer(Number max_val, Number val):
        max_value(max_val), value(fixed(val)){}
public:
    Number max() const{ return max_value; }
    void update_max(Number max){
        max_value = max;
        value = fixed(value);
    }

    void add(int val){ value = fixed(value + val); }
    void sub(int val){ add(-val); }

    void set(int val){ value = fixed(val); }

    CycledIndexer operator+(Number num){ return CycledIndexer(max_value, value + num); }
    CycledIndexer operator-(Number num){ return CycledIndexer(max_value, value - num); }

    CycledIndexer &operator=(Number num){
        set(num);
        return *this;
    }

    CycledIndexer &operator+=(Number num){
        add(num);
        return *this;
    }
    CycledIndexer &operator-=(Number num){
        sub(num);
        return *this;
    }

    CycledIndexer &operator++(){
        add(1);
        return *this;
    }
    CycledIndexer &operator--(){
        sub(1);
        return *this;
    }

    CycledIndexer operator++(int){
        auto result = *this;
        add(1);
        return result;
    }
    CycledIndexer operator--(int){
        auto result = *this;
        sub(1);
        return result;
    }

    operator Number() const
    { return value; }
private:
    Number fixed(Number val){
        auto mod = val%max_value;
        return mod<0 ? max_value + mod : mod;
    }
private:
    Number max_value, value;
};

Oraz na jego przykładowe działanie:

int main(){
	vector<string> strings = { "Ala", "ma", "kota", ",", "a", "kot", "ma", "Ale", "." };
	CycledIndexer indexer(strings.size(), 0);
	
	auto test = [&](auto desc, auto func){
		cout << desc << endl;
		indexer = 0;
		for(size_t i = 0; i < 20; ++i){
			const auto &text = strings.at(func());
			cout << text << (text == "."? "\n" : " ");
		}
		cout << endl;
	};
	
	test("(indexer++)", [&]{ return indexer++; });
	test("(++indexer)", [&]{ return ++indexer; });
	test("(indexer--)", [&]{ return indexer--; });
	test("(--indexer)", [&]{ return --indexer; });
}

http://ideone.com/C7KLH3
Co widzimy?

(indexer++)
Ala ma kota , a kot ma Ale .
Ala ma kota , a kot ma Ale .
Ala ma 
(++indexer)
ma kota , a kot ma Ale .
Ala ma kota , a kot ma Ale .
Ala ma kota 
(indexer--)
Ala .
Ale ma kot a , kota ma Ala .
Ale ma kot a , kota ma Ala .

(--indexer)
.
Ale ma kot a , kota ma Ala .
Ale ma kot a , kota ma Ala .
Ale 

Wszystko działa, a indeksery trzymają się zakresu <0, max), tak więc możemy zająć się kolejną sprawą.

Pozycja listy! Szybka burza myśli, "jeśli wybiorę jakiś tekst, to coś się stanie''; i wychodzi coś takiego:

struct MenuItem{
    using Text = string;
    using Action = function<void()>;

    Text text;
    Action callback;
};

Nic więcej od samej pozycji nie chcemy.

Menu; Czym jest menu? No zbiorem pozycji; Opakujmy więc to ździebko, używając naszego zapętlonego indeksera.

class Menu{
	using Items = vector<MenuItem>;
    using MenuSelectionProxy = MenuSelectionProxyTemplate<Menu>;
    friend MenuSelectionProxy;
public:
    Menu(): indexer(0, 0){}
    Menu(initializer_list<MenuItem> items_list):
        items(items_list), indexer(items.size(), 0){}
public:
    void add_item(const MenuItem &item){ 
        items.push_back(item); 
        update_indexer();
    }
    void add_item(MenuItem &&item){ 
        items.push_back(item); 
        update_indexer();
    }
    
    Items &all_items()
    { return items; }
    const Items &all_items() const
    { return items; }

    MenuSelectionProxy selection()
    { return MenuSelectionProxy(*this); }
    const MenuSelectionProxy selection() const
    { return MenuSelectionProxy(const_cast<Menu&>(*this)); }
private:
    void update_indexer(){
        indexer.update_max(items.size());
    }
private:
    Items items;
    CycledIndexer indexer;
};

Głęboka woda? Spokojnie. Opakowaliśmy to tak, by zawrzeć szereg użytecznych metod.

Co to za przyjaźnie, jakieś proxy? Jak ja mam się dorwać do mojej zaznaczonej pozycji?!

Gdy myślisz o "zaznaczeniu" w menu, na myśl może Ci przyjść dosłownie kilka metod:

selection.call();
selection.text();
selection.move_up();
selection.mov_down();

Jak się pewnie domyślasz, dokładnie to przedstawia nasze wydzielone proxy.

template<typename MenuType>
class MenuSelectionProxyTemplate{
public:
    using Menu = MenuType;
public:
    MenuSelectionProxyTemplate(Menu &menu): menu(menu){}
public:
    operator MenuItem&()
    { return menu.items[menu.indexer]; }
    operator const MenuItem&() const
    { return menu.items[menu.indexer]; }

    void call()
    { static_cast<MenuItem&>(*this).callback(); }
    
    auto &text()
    { return static_cast<MenuItem&>(*this).text; }
    const auto &text() const
    { return static_cast<const MenuItem&>(*this).text; }

    void move_up()
    { --menu.indexer; }
    void move_down()
    { ++menu.indexer; }
private:
    Menu &menu;
};

Po co to wszystko? Czas na podsumowujący przykład. jest późno, o szczegóły możesz pytać w komentarzach;

int main(){
	Menu menu{
		{"Hello, World!", [&]{ cout << "I have just said hello to World." << endl; }},
		{"Hello, Fred!", [&]{ cout << "I have just said hello to Fred." << endl; }},
		{"Start game!", [&]{ cout << "I have just stared the game(?)." << endl; }},
		{"exit program!", [&]{ cout << "Ciao!"; exit(EXIT_SUCCESS); }}
	};
	
	using Action = function<void()>;
	using ActionsMap = map<string, Action>;
	ActionsMap actions_map{
		{"up", [&]{ menu.selection().move_up(); }},
		{"down", [&]{menu.selection().move_down(); }},
		{"call", [&]{menu.selection().call(); }},
		{"wait", []{}}
	};
	
	string command;
	while(cin>>command){
		cout << "#--Woobly, woobly, the next step--#" << endl;
		cout << "#<<" << command << endl;
		if(actions_map.count(command) == false){
			cout << "STOP PASSING WRONG COMMANDS!" << endl;
			continue;
		}
		actions_map[command]();
		
		for(const auto &item : menu.all_items()){
			cout << item.text;
			if(item.text == menu.selection().text())
				cout << " <-- (thats our selected buddy)";
			cout << endl;
		}
	}
	return 0;
}

Chcę z tego miejsca zaznaczyć, że sposób wejścia czy wyjścia jest absolutnie dowolny. Możesz używać getch, możesz czyścic ekran, ale w zasadzie nic nie musisz.

Prześledźmy jeszcze tylko wykonanie z przykładowym wejściem :)
Tak prezentuje się ono w całości:

wait
call
down
call
down
call
down
down
down
down
up
down
down
foobar
call

Ale lećmy po kolei

wait

#--Woobly, woobly, the next step--#
#<<wait
Hello, World! <-- (thats our selected buddy)
Hello, Fred!
Start game!
exit program!
call

#--Woobly, woobly, the next step--#
#<<call
I have just said hello to World.
Hello, World! <-- (thats our selected buddy)
Hello, Fred!
Start game!
exit program!
down

#--Woobly, woobly, the next step--#
#<<down
Hello, World!
Hello, Fred! <-- (thats our selected buddy)
Start game!
exit program!
call

#--Woobly, woobly, the next step--#
#<<call
I have just said hello to Fred.
Hello, World!
Hello, Fred! <-- (thats our selected buddy)
Start game!
exit program!
down

#--Woobly, woobly, the next step--#
#<<down
Hello, World!
Hello, Fred!
Start game! <-- (thats our selected buddy)
exit program!
call

#--Woobly, woobly, the next step--#
#<<call
I have just stared the game(?).
Hello, World!
Hello, Fred!
Start game! <-- (thats our selected buddy)
exit program!
down

#--Woobly, woobly, the next step--#
#<<down
Hello, World!
Hello, Fred!
Start game!
exit program! <-- (thats our selected buddy)
down

#--Woobly, woobly, the next step--#
#<<down
Hello, World! <-- (thats our selected buddy)
Hello, Fred!
Start game!
exit program!
down

#--Woobly, woobly, the next step--#
#<<down
Hello, World!
Hello, Fred! <-- (thats our selected buddy)
Start game!
exit program!
down

#--Woobly, woobly, the next step--#
#<<down
Hello, World!
Hello, Fred!
Start game! <-- (thats our selected buddy)
exit program!
up

#--Woobly, woobly, the next step--#
#<<up
Hello, World!
Hello, Fred! <-- (thats our selected buddy)
Start game!
exit program!
down

#--Woobly, woobly, the next step--#
#<<down
Hello, World!
Hello, Fred!
Start game! <-- (thats our selected buddy)
exit program!
down

#--Woobly, woobly, the next step--#
#<<down
Hello, World!
Hello, Fred!
Start game!
exit program! <-- (thats our selected buddy)
foobar

#--Woobly, woobly, the next step--#
#<<foobar
STOP PASSING WRONG COMMANDS!
call

#--Woobly, woobly, the next step--#
#<<call
Ciao!

http://ideone.com/2M0NuK

1

Operatory, template, indexery, wskazniki, referencje.

user image

user image

Walcze!!

1 użytkowników online, w tym zalogowanych: 0, gości: 1