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