Ceci est une ancienne révision du document !
Le sujet de cette série d'exercice est disponible sur Javaquarium - Zeste de Savoir
Voici également quelques réalisations en C++ :
Pour mes implémentations, j'utilise une approche Entity-Component-System, le but étant d'étudier un peu cette approche sur un exemple concret simple.
Les codes sont testés directement sur Coliru.com.
Dans cette première partie, le bu est de simplement créer une liste de poisson et d'algues, et de les afficher.
#include <iostream> #include <vector> #include <string> #include <algorithm> #include <numeric> #include <iterator> namespace ecs { namespace entity { using id = size_t; using entities = std::vector<id>; } namespace component { using name = std::string; using is_male = bool; using detail = std::pair<name, is_male>; using detail_component = std::pair<entity::id, detail>; enum class type { algue, poisson }; using type_component = std::pair<entity::id, type>; } namespace system { using details = std::vector<component::detail_component>; using types = std::vector<component::type_component>; } namespace internal { entity::entities _entities; system::details _details; system::types _types; } entity::id create_entity() { const auto id = internal::_entities.empty() ? 1 : internal::_entities.back() + 1; internal::_entities.push_back(id); return id; } void add_algue() { const auto id = create_entity(); internal::_types.push_back(std::make_pair(id, component::type::algue)); } void add_poisson(component::name n, component::is_male m) { const auto id = create_entity(); internal::_types.push_back(std::make_pair(id, component::type::poisson)); internal::_details.push_back(std::make_pair(id, make_pair(n, m))); } void print_algues() { const auto count = std::count_if(begin(internal::_types), end(internal::_types), [](auto p){ return (p.second == component::type::algue); }); std::cout << count << " algues"; } void print_poissons() { if (internal::_details.empty()) { std::cout << "No poissons"; return; } std::cout << internal::_details.size() << " poissons: "; auto print = [](auto p){ return (p.second.first + " [" + (p.second.second ? 'M' : 'F') + ']'); }; std::transform(begin(internal::_details), end(internal::_details) - 1, std::ostream_iterator<std::string>(std::cout, ", "), print); std::cout << print(internal::_details.back()); } void print() { print_algues(); std::cout << ". "; print_poissons(); std::cout << '.' << std::endl; } } int main() { ecs::add_algue(); ecs::add_algue(); ecs::add_poisson("toto", true); ecs::add_algue(); ecs::add_poisson("titi", false); ecs::add_poisson("tata", true); ecs::add_algue(); ecs::print(); return 0; }
Affiche :
4 algues. 3 poissons: toto [M], titi [F], tata [M].
Cette implémentation peut paraître surprenante : je ne crées aucune classe. Surtout que cet exercice est à la base un exercice de programmation orientée objet. Mais le but est justement d'expliquer l'intérêt de la programmation orientée objet.
Quel est le problème avec cette approche ?
Une première critique que l'on peut faire est l’utilisation de variables globales (dans internal
). Cela implique que si on veut avoir plusieurs ECS en même temps, ce n'est pas possible. Et bien sur, le code est moins réutilisable, il est écrit spécifiquement autour de ces variables globales, les fonctions ne sont pas réutilisables sans elles.
Il est facile de corriger ce problème, en passant ces variables en paramètres de fonctions et en déclarant les variables globales dans main
.
entity::id create_entity(entity::entities & entites) { const auto id = entities.empty() ? 1 : entities.back() + 1; entities.push_back(id); return id; }
Le code devient un peu plus lourd dans les fonctions qui utilisent plusieurs variables globales, mais le principe est le même.
Au final, c'est l'approche que l'on utilise en C pour faire de l'objet : on crées des structures contenant les données et des fonctions qui prennent un pointeur sur un objet. (J'utilise std::pair
et std::tuple
, qui sont en pratique des classes dont les variables membres n'ont pas de noms, et des références, mais ça serait la même chose si j'utilisais struct
et des pointeurs).
Quel est l'intérêt de l'approche objet du C++ par rapport à celle utilisée en C ?
Une première réponse serait de dire que l'on est obligé de donner des noms différents à des fonctions qui font la même chose, comme par exemple print
, print_algues
et print_poissons
dans l'exemple. En mettant les fonctions comme membre des classes plutôt que des fonctions libres, on pourrait utiliser le même nom.
algues.print(); poissons.print(); print();
Mais en fait, ce n'est pas un argument valide. En C++, il est possible de surcharger des fonctions, contrairement au C. Il est donc possible de donner le même nom à plusieurs fonctions, le compilateur appellera la fonction correcte en utilisant les paramètres.
print(algues); print(poissons); print();
Une seconde est de dire qu'il n'est pas possible en C de regrouper (“encapsuler”) ensemble les données et les fonctions qui manipulent ces données ensemble, pour avoir un tout cohérent et plus lisible.
class Poissons { public: entity::id add(); void print(); private: std::vector<Poisson> poissons; };
L'argument n'est pas faux, mais est limité. Pourquoi par exemple ne pas remplacer la création d'une classe dans le code précédent par un namespace ? Quelle sera la différence ?
Il y a bien sûr l'accessibilité des membres, publique ou privée. Mais cela sera vite limité aussi : dans un ECS (et dans de nombreux cas), les données sont manipulées par plusieurs classes, ce qui implique de mettre les membres en publique ou d'ajouter des mutateurs (getters et setters)… ce qui est fait en pratique dans de nombreux langages de programmation.
Mais il y a plus à attendre d'une encapsulation. Qu'est-ce que c'est ? Quel problème veut-on corriger avec la programmation orientée objets ?
La réponse est simple : on veut que les données soient utilisées correctement.
Par exemple, dans un ECS, chaque entité doit être unique. Il est nécessaire et indispensable de ne pas avoir de doublons dans la liste des entités. Or, dans l’implémentation proposée, rien n'interdit à l’utilisateur d'écrire le code suivant :
int main() { ecs::internal::_entities.push_back(0); ecs::internal::_entities.push_back(0); }
Ce qui violerait la règle de l'entité unique.
Les règles imposées aux données sont appelées les invariants en programmation par contrat. Les invariants sont importants, puisqu'ils signifient : “si les invariants sont respectés, le programme fera ce qu'on attend de lui. Si les invariants ne sont pas respectés, il n'est pas possible de garantir le comportement du programme”.
L'autre aspect de la programmation par contrat est l'utilisation des pré et post-conditions. Ces conditions s'appliquent sur les fonctions et garantissent que si les pré-conditions et les invariants sont respectés lors de l'appel d'une fonction, alors les post-conditions et les invariants seront aussi respectés à la fin de l'appel de la fonction.
On arrive donc naturellement au principe d'encapsulation. Une encapsulation correcte est donc une encapsulation qui apportera des garanties fortes sur l'utilisation correcte des données. Cela peut être résumé par la phrase suivante :
Facile a utiliser correctement, difficile a utiliser de façon incorrect.
Scott Meyers
Beaucoup pensent que l'encapsulation consiste simplement a mettre des variables en private
et des fonctions en public
dans une classe. Mais en pratique, ce n'est aussi simple : des fonctions libres peuvent respecter l’encapsulation (voir par exemple les fonctions std::begin
et std::end
) et au contraire des fonctions membres peuvent briser l'encapsulation (en premier lieu, les mutateurs : les setters et getters).
Pour concevoir correctement les classes, on conseille souvent de penser les classes en termes de services rendus, pas en termes d'agrégation de données. Dit autrement, il faut se poser la question “quels sont les services que doivent rendre ma classe ?” et pas “quelles sont les données contenues dans ma classe ?”.
Dans cette seconde partie, le but est de créer différentes races de poissons et d'ajouter une fonction pour que les poisson puissent manger.
#include <iostream> #include <vector> #include <string> #include <algorithm> #include <numeric> #include <iterator> #include <cassert> namespace ecs { namespace entity { using id = size_t; using entities = std::vector<id>; } namespace component { using name = std::string; using is_male = bool; using detail = std::pair<name, is_male>; using detail_component = std::pair<entity::id, detail>; enum class type { algue, Mérou, Thon, PoissonClown, Sole, Bar, Carpe }; using type_component = std::pair<entity::id, type>; } namespace system { using details = std::vector<component::detail_component>; using types = std::vector<component::type_component>; } namespace internal { entity::entities _entities; system::details _details; system::types _types; } bool is_algue(component::type t) { return (t == component::type::algue); } bool is_poisson(component::type t) { return !is_algue(t); } bool is_carnivore(component::type t) { return (t == component::type::Mérou || t == component::type::Thon || t == component::type::PoissonClown); } bool is_herbivore(component::type t) { return (t == component::type::Sole || t == component::type::Bar || t == component::type::Carpe); } std::string to_string(component::type t) { static const std::string types[] = { "Algue", "Mérou", "Thon", "Poisson-clown", "Sole", "Bar", "Carpe" }; const auto i = static_cast<size_t>(t); return types[i]; } entity::id create_entity() { const auto id = internal::_entities.empty() ? 1 : internal::_entities.back() + 1; internal::_entities.push_back(id); return id; } entity::id add_algue() { const auto id = create_entity(); internal::_types.push_back(std::make_pair(id, component::type::algue)); return id; } entity::id add_poisson(component::type t, component::name n, component::is_male m) { assert(is_poisson(t)); const auto id = create_entity(); internal::_types.push_back(std::make_pair(id, t)); internal::_details.push_back(std::make_pair(id, make_pair(n, m))); return id; } void remove_entity(entity::id id) { const auto entities_it = std::remove(internal::_entities.begin(), internal::_entities.end(), id); internal::_entities.erase(entities_it, internal::_entities.end()); const auto details_it = std::remove_if(internal::_details.begin(), internal::_details.end(), [id](auto p){ return (p.first == id); }); internal::_details.erase(details_it, internal::_details.end()); const auto types_it = std::remove_if(internal::_types.begin(), internal::_types.end(), [id](auto p){ return (p.first == id); }); internal::_types.erase(types_it, internal::_types.end()); } void eat(entity::id eater, entity::id target) { assert(eater != target); const auto eater_it = std::find_if(begin(internal::_types), end(internal::_types), [eater](auto p){ return (p.first == eater); }); const auto target_it = std::find_if(begin(internal::_types), end(internal::_types), [target](auto p){ return (p.first == target); }); assert(is_poisson(eater_it->second)); assert(is_carnivore(eater_it->second) || (is_herbivore(eater_it->second) && is_algue(target_it->second))); remove_entity(target); //eat } void print_algues() { const auto count = std::count_if(begin(internal::_types), end(internal::_types), [](auto p){ return (p.second == component::type::algue); }); std::cout << "algues: " << count; } void print_poissons() { if (internal::_details.empty()) { std::cout << "No poissons"; return; } std::cout << internal::_details.size() << " poissons: "; auto print = [](auto p){ return (p.second.first + " [" + (p.second.second ? 'M' : 'F') + ']'); }; std::transform(begin(internal::_details), end(internal::_details) - 1, std::ostream_iterator<std::string>(std::cout, ", "), print); std::cout << print(internal::_details.back()); } void print() { print_algues(); std::cout << ". "; print_poissons(); std::cout << '.' << std::endl; } } int main() { ecs::add_algue(); ecs::add_algue(); ecs::add_algue(); ecs::add_algue(); ecs::add_algue(); ecs::add_algue(); ecs::add_algue(); const auto algue = ecs::add_algue(); const auto carnivore = ecs::add_poisson(ecs::component::type::Mérou, "toto", true); const auto poisson = ecs::add_poisson(ecs::component::type::Mérou, "titi", false); const auto herbivore = ecs::add_poisson(ecs::component::type::Mérou, "tata", true); ecs::add_poisson(ecs::component::type::Mérou, "tuto", false); ecs::add_poisson(ecs::component::type::Mérou, "tyty", true); ecs::print(); ecs::remove_entity(poisson); ecs::print(); ecs::eat(herbivore, algue); ecs::eat(carnivore, herbivore); ecs::print(); return 0; }
affiche :
algues: 8. 5 poissons: toto [M], titi [F], tata [M], tuto [F], tyty [M]. algues: 8. 4 poissons: toto [M], tata [M], tuto [F], tyty [M]. algues: 7. 3 poissons: toto [M], tuto [F], tyty [M].
Dans cette partie, j'ai choisis d'utiliser une énumération pour définir toutes les races (algues, carnivores et herbivores).
enum class type { algue, Mérou, Thon, PoissonClown, Sole, Bar, Carpe };
L'intérêt est que l'on a besoin que d'une seule variable pour manipuler tous les types. Quels sont les défauts de cette approche ? (Uniquement en termes de C++, pas d'ECS. Je reviendrais sur l'impact de l'encodage des données sur l'ECS par la suite).
Une conséquence est que l'on ne sait pas lors de la compilation si le type correspond à un algue ou à un poisson. Or, certaines fonctions sont spécifiques aux algues ou aux poissons. Par exemple, la fonction add
, qui prend des paramètres différents selon le type. Il est donc nécessaire d'avoir deux fonctions, add_algue
et add_poisson
.
void add_algue(); void add_poisson(name, is_male, type);
Je pense que vous voyez tout de suite le problème ici : type
peut être une algue, ce qui n'est pas valide.
Il n'y a pas d'autre solution que de vérifier le type à l'exécution (avec if
ou assert
).
void add_poisson(component::name n, component::is_male m, component::type t) { assert(is_poisson(t)); ... }
Cela pose plusieurs problèmes :
assert
) ;Est-il possible d’améliorer cela ?
A ce niveau des explications, je pense que la solution est évidente : il suffit de créer un type représentant uniquement les poissons, pas les algues. Par exemple :
enum class type { algue, poisson }; enum class race { Mérou, Thon, PoissonClown, Sole, Bar, Carpe }; void add_poisson(component::name n, component::is_male m, component::race r) { // assert(is_poisson(t)); pas nécessaire ... }
Dans cette situation, il n'est plus nécessaire de vérifier a l’exécution que type
n'a pas la valeur algue
, puisque par conception, ce n'est pas possible.
Cette technique est très puissante et est l'une des forces des langages de programmation a typage fort. Au lieu de devoir vérifier les valeurs a l’exécution, on construit un type qui exprime directement les contraintes sur les données, ce qui permet de faire les vérifications a la compilation (améliorer la qualité logicielle) et gagner en performances.
Dans cette partie, on ajoute un système de tours : a chaque tour, tous les poissons mangent quelque chose au hasard.
En termes d’implémentation, cela pose une petite difficulté : il faut parcourir un vector
que l'on va modifier en même temps. Si on parcours le vector
avec des itérateurs, l'itérateur courant sera invalide après avoir supprimé un élément au début. Idem en parcourant avec des indices, si la suppression a lieu a la fin de vector
, il faut décrémenter l'indice de 1.
Dans un ECS, il est préférable de séparer le traitement des entités en deux ou trois phases :
Dans ce cas, je prefere modifier legerement le gameplay, de facon a avoir quelque chose de plus simple au niveau gestion des elements. Au lieu de supprimer directement un poisson qui se fait manger, je fais un premier passage pour determiner qui mange qui, en conservant dans une liste separee les entites qui doivent etre supprimee.
Cette approche n'est pas forcement incoherente. Si A mange B et B mange A, pourquoi l'une des deux entites gagnerait plus que l'autre ? Surtout que le choix de l'entite qui gagne depend de l'ordre d'apparition dans le vector
, ce qui est un peu arbitraire. En separant en deux phases, cela est equivalent a avoir une double mort.
#include <iostream> #include <vector> #include <string> #include <algorithm> #include <numeric> #include <iterator> #include <cassert> #include <random> namespace ecs { namespace entity { using id = size_t; using entities = std::vector<id>; } namespace component { using name = std::string; using is_male = bool; using detail = std::pair<name, is_male>; using detail_component = std::pair<entity::id, detail>; enum class type { algue, Mérou, Thon, PoissonClown, Sole, Bar, Carpe }; using type_component = std::pair<entity::id, type>; } namespace system { using details = std::vector<component::detail_component>; using types = std::vector<component::type_component>; } namespace internal { entity::entities _entities; system::details _details; system::types _types; std::random_device _random_device; std::default_random_engine _random_engine { _random_device() }; } bool is_algue(component::type t) { return (t == component::type::algue); } bool is_poisson(component::type t) { return !is_algue(t); } bool is_carnivore(component::type t) { return (t == component::type::Mérou || t == component::type::Thon || t == component::type::PoissonClown); } bool is_herbivore(component::type t) { return (t == component::type::Sole || t == component::type::Bar || t == component::type::Carpe); } std::string to_string(component::type t) { static const std::string types[] = { "Algue", "Mérou", "Thon", "Poisson-clown", "Sole", "Bar", "Carpe" }; const auto i = static_cast<size_t>(t); return types[i]; } template<typename Predicat> std::vector<entity::id> get_entities(Predicat&& predicat) { std::vector<entity::id> algues; std::for_each(begin(internal::_types), end(internal::_types), [&algues, predicat](auto p){ if (predicat(p.second)) { algues.push_back(p.first); } } ); return algues; } template<typename Collection> typename Collection::value_type const& get_component(Collection const& components, entity::id id) { const auto it = find_if(begin(components), end(components), [id](auto p){ return (p.first == id); }); assert(it != end(components)); return (*it); } entity::id create_entity() { const auto id = internal::_entities.empty() ? 0 : internal::_entities.back() + 1; internal::_entities.push_back(id); return id; } entity::id add_algue() { const auto id = create_entity(); internal::_types.push_back(std::make_pair(id, component::type::algue)); return id; } entity::id add_poisson(component::type t, component::name n, component::is_male m) { assert(is_poisson(t)); const auto id = create_entity(); internal::_types.push_back(std::make_pair(id, t)); internal::_details.push_back(std::make_pair(id, make_pair(n, m))); return id; } void remove_entity(entity::id id) { const auto entities_it = std::remove(internal::_entities.begin(), internal::_entities.end(), id); internal::_entities.erase(entities_it, internal::_entities.end()); const auto details_it = std::remove_if(internal::_details.begin(), internal::_details.end(), [id](auto p){ return (p.first == id); }); internal::_details.erase(details_it, internal::_details.end()); const auto types_it = std::remove_if(internal::_types.begin(), internal::_types.end(), [id](auto p){ return (p.first == id); }); internal::_types.erase(types_it, internal::_types.end()); } void remove_entities(std::vector<ecs::entity::id> const& ids) { for (auto id: ids) remove_entity(id); } void eat(entity::id eater, entity::id target) { assert(eater != target); const auto eater_it = std::find_if(begin(internal::_types), end(internal::_types), [eater](auto p){ return (p.first == eater); }); const auto target_it = std::find_if(begin(internal::_types), end(internal::_types), [target](auto p){ return (p.first == target); }); assert(is_poisson(eater_it->second)); assert(is_carnivore(eater_it->second) || (is_herbivore(eater_it->second) && is_algue(target_it->second))); remove_entity(target); //eat } void print_algues() { const auto count = std::count_if(begin(internal::_types), end(internal::_types), [](auto p){ return (p.second == component::type::algue); }); std::cout << "algues: " << count; } void print_poissons() { if (internal::_details.empty()) { std::cout << "No poissons"; return; } std::cout << internal::_details.size() << " poissons: "; auto print = [](auto p){ return (p.second.first + " [" + (p.second.second ? 'M' : 'F') + ']'); }; std::transform(begin(internal::_details), end(internal::_details) - 1, std::ostream_iterator<std::string>(std::cout, ", "), print); std::cout << print(internal::_details.back()); } void print() { print_algues(); std::cout << ". "; print_poissons(); std::cout << '.' << std::endl; } } int main() { for (size_t i {}; i < 50; ++i) ecs::add_algue(); for (size_t i {}; i < 10; ++i) ecs::add_poisson(ecs::component::type::Sole, "toto", false); for (size_t i {}; i < 5; ++i) ecs::add_poisson(ecs::component::type::Mérou, "titi", false); ecs::print(); for (size_t tour {}; tour < 10; ++tour) { std::cout << "===== Tour " << tour << " ======" << std::endl; std::vector<ecs::entity::id> entities_to_remove; const auto poissons { ecs::get_entities(ecs::is_poisson) }; const auto algues { ecs::get_entities(ecs::is_algue) }; std::uniform_int_distribution<size_t> entities_distribution(0, ecs::internal::_entities.size() - 1); std::uniform_int_distribution<size_t> algue_distribution(0, algues.size() - 1); for (auto poisson: poissons) { auto const& poisson_type = ecs::get_component(ecs::internal::_types, poisson); if (ecs::is_herbivore(poisson_type.second) && !algues.empty()) { const auto index { algue_distribution(ecs::internal::_random_engine) }; const auto target = algues[index]; entities_to_remove.push_back(target); } else if (ecs::is_carnivore(poisson_type.second) && !ecs::internal::_entities.empty()) { const auto index { entities_distribution(ecs::internal::_random_engine) }; const auto target = ecs::internal::_entities[index]; entities_to_remove.push_back(target); } } ecs::remove_entities(entities_to_remove); ecs::print(); } return 0; }
affiche :
algues: 50. 15 poissons: toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], titi [F], titi [F], titi [F], titi [F], titi [F]. ===== Tour 0 ====== algues: 37. 15 poissons: toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], titi [F], titi [F], titi [F], titi [F], titi [F]. ===== Tour 1 ====== algues: 22. 15 poissons: toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], titi [F], titi [F], titi [F], titi [F], titi [F]. ===== Tour 2 ====== algues: 11. 14 poissons: toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], titi [F], titi [F], titi [F], titi [F]. ===== Tour 3 ====== algues: 4. 13 poissons: toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], titi [F], titi [F], titi [F], titi [F]. ===== Tour 4 ====== algues: 0. 10 poissons: toto [F], toto [F], toto [F], toto [F], toto [F], toto [F], titi [F], titi [F], titi [F], titi [F]. ===== Tour 5 ====== algues: 0. 7 poissons: toto [F], toto [F], toto [F], titi [F], titi [F], titi [F], titi [F]. ===== Tour 6 ====== algues: 0. 4 poissons: toto [F], toto [F], titi [F], titi [F]. ===== Tour 7 ====== algues: 0. 2 poissons: toto [F], titi [F]. ===== Tour 8 ====== algues: 0. 1 poissons: titi [F]. ===== Tour 9 ====== algues: 0. No poissons. ===== Tour 10 ====== algues: 0. No poissons.
#include <iostream> #include <vector> #include <string> #include <algorithm> #include <numeric> #include <iterator> #include <cassert> #include <random> #include <tuple> namespace ecs { namespace entity { using id = size_t; using entities = std::vector<id>; } namespace component { using name = std::string; using is_male = bool; using detail = std::pair<name, is_male>; using detail_component = std::pair<entity::id, detail>; enum class type { algue, Mérou, Thon, PoissonClown, Sole, Bar, Carpe }; using pv = size_t; using type_component = std::tuple<entity::id, type, pv>; bool is_algue(type t) { return (t == type::algue); } bool is_poisson(type t) { return !is_algue(t); } bool is_carnivore(type t) { return (t == type::Mérou || t == type::Thon || t == type::PoissonClown); } bool is_herbivore(type t) { return (t == type::Sole || t == type::Bar || t == type::Carpe); } std::string to_string(type t) { static const std::string types[] = { "Algue", "Mérou", "Thon", "Poisson-clown", "Sole", "Bar", "Carpe" }; const auto i = static_cast<size_t>(t); assert(true); return types[i]; } } namespace system { using details = std::vector<component::detail_component>; using types = std::vector<component::type_component>; } namespace internal { entity::entities _entities; system::details _details; system::types _types; std::random_device _random_device; std::default_random_engine _random_engine { _random_device() }; } entity::id create_entity() { const auto id = internal::_entities.empty() ? 1 : internal::_entities.back() + 1; internal::_entities.push_back(id); return id; } entity::id add_algue() { const auto id = create_entity(); internal::_types.push_back(std::make_tuple(id, component::type::algue, 10)); return id; } entity::id add_poisson(component::type t, component::name n, component::is_male m) { assert(is_poisson(t)); const auto id = create_entity(); internal::_types.push_back(std::make_tuple(id, t,10)); internal::_details.push_back(std::make_pair(id, make_pair(n, m))); return id; } void remove_entity(entity::id id) { const auto entities_it = std::remove(internal::_entities.begin(), internal::_entities.end(), id); internal::_entities.erase(entities_it, internal::_entities.end()); const auto details_it = std::remove_if(internal::_details.begin(), internal::_details.end(), [id](auto p){ return (p.first == id); }); internal::_details.erase(details_it, internal::_details.end()); const auto types_it = std::remove_if(internal::_types.begin(), internal::_types.end(), [id](auto p){ return (std::get<0>(p) == id); }); internal::_types.erase(types_it, internal::_types.end()); } void eat(entity::id eater, entity::id target) { assert(eater != target); const auto eater_it = std::find_if(begin(internal::_types), end(internal::_types), [eater](auto p){ return (std::get<0>(p) == eater); }); const auto target_it = std::find_if(begin(internal::_types), end(internal::_types), [target](auto p){ return (std::get<0>(p) == target); }); assert(is_poisson(std::get<1>(*eater_it))); assert(is_carnivore(std::get<1>(*eater_it)) || (is_herbivore(std::get<1>(*eater_it)) && is_algue(std::get<1>(*target_it)))); remove_entity(target); //eat } entity::id random_entity() { std::uniform_int_distribution<size_t> distribution(0, internal::_entities.size() - 1); const auto index = distribution(internal::_random_engine); return internal::_entities[index]; } entity::id random_poisson() { std::uniform_int_distribution<size_t> distribution(0, internal::_details.size() - 1); const auto index = distribution(internal::_random_engine); return internal::_details[index].first; } void print_algues() { const auto count = std::count_if(begin(internal::_types), end(internal::_types), [](auto p){ return (std::get<1>(p) == component::type::algue); }); std::cout << "algues: " << count; } void print_poissons() { std::cout << "poissons: "; std::transform(begin(internal::_details), end(internal::_details), std::ostream_iterator<std::string>(std::cout, ", "), [](auto p){ return (p.second.first + " [" + (p.second.second ? 'M' : 'F') + ']'); }); } void print() { print_algues(); std::cout << ". "; print_poissons(); std::cout << std::endl; } } int main() { ecs::add_algue(); ecs::add_algue(); ecs::add_algue(); ecs::add_algue(); ecs::add_algue(); ecs::add_algue(); ecs::add_algue(); ecs::add_algue(); ecs::add_poisson(ecs::component::type::Mérou, "toto", true); ecs::add_poisson(ecs::component::type::Mérou, "titi", false); ecs::add_poisson(ecs::component::type::Mérou, "tata", true); ecs::add_poisson(ecs::component::type::Mérou, "tuto", false); ecs::add_poisson(ecs::component::type::Mérou, "tyty", true); ecs::print(); for (size_t i {}; i < 50; ++i) { std::uniform_int_distribution<size_t> distribution(0, ecs::internal::_types.size() - 1); auto index = distribution(ecs::internal::_random_engine); const auto eater = ecs::internal::_types[index]; index = distribution(ecs::internal::_random_engine); auto & target = ecs::internal::_types[index]; if (std::get<0>(eater) == std::get<0>(target)) continue; if (is_algue(std::get<1>(eater))) continue; if (is_herbivore(std::get<1>(eater)) && is_poisson(std::get<1>(target))) continue; std::get<2>(target) -= 2; // eat std::cout << " " << std::get<0>(eater) << " mange " << std::get<0>(target) << " reste " << std::get<2>(target) << std::endl; ecs::print(); } return 0; }