Tuto Complet: POO & MVC avec le Jeu de la Vie

Créations

Codnpix

NEW il y a 5 ans

Ça a l'air excellent, j'ai hâte de m'y plonger.

Merci pour ce travail !

Steph

il y a 5 ans

J'espère qu'il sera à la hauteur de tes attentes :-)

Steph

NEW il y a 5 ans

Codnpix Codnpix

J'espère qu'il sera à la hauteur de tes attentes :-)

Codnpix

il y a 5 ans

Je suis quasiment au bout (il me reste à implémenter les patterns) !

Vraiment excellent tuto, un grand merci pour la qualité, la clarté et la précision des explications tout au long des étapes. C'est vraiment cool d'avoir été explorer chaque aspect de façon aussi complète.

Encore une fois j'ai appris plein de trucs, j'ai les idées bien plus claires au niveau du raisonnement objet et MVC (ça tombe bien il me semble que c'était le but !).

J'étais super content entre autre de découvrir un exemple d'utilisation des opérateurs bitwise, je n'avais jamais osé mettre le nez là-dedans et c'est vraiment chouette de lever le voile là-dessus et d'en découvrir l'utilité.

J'ai peut-être une petite réserve sur les fonctions de conversions de couleur HSB->RGB que j'ai trouvé un peu obscures, les noms des variables sont assez peu parlants (f, v, q, t, etc...) du coup je n'ai pas vraiment réussi à comprendre ce qui se passait là-dedans en détail.


Sinon j'ai une petite question (que j'avais déjà posé à Chris Scientist dans son tuto POO Sokoban) : 

J'ai remarqué que, comme dans le code de Chris, tu utilises les instances de tes classes exclusivement à travers des pointeurs. Si j'ai bien compris, a priori on pourrait aussi choisir de les manipuler directement, du coup je voudrait savoir si il y a une raison précise, ou une convention, qui fait qu'on ne les accède que par des pointeurs.


Encore un grand merci pour ce magnifique travail !

(d'ailleurs il faudrait l'ajouter à la page académie, ça me semble clairement s'imposer)

Codnpix

NEW il y a 5 ans

Steph Steph

Je suis quasiment au bout (il me reste à implémenter les patterns) !

Vraiment excellent tuto, un grand merci pour la qualité, la clarté et la précision des explications tout au long des étapes. C'est vraiment cool d'avoir été explorer chaque aspect de façon aussi complète.

Encore une fois j'ai appris plein de trucs, j'ai les idées bien plus claires au niveau du raisonnement objet et MVC (ça tombe bien il me semble que c'était le but !).

J'étais super content entre autre de découvrir un exemple d'utilisation des opérateurs bitwise, je n'avais jamais osé mettre le nez là-dedans et c'est vraiment chouette de lever le voile là-dessus et d'en découvrir l'utilité.

J'ai peut-être une petite réserve sur les fonctions de conversions de couleur HSB->RGB que j'ai trouvé un peu obscures, les noms des variables sont assez peu parlants (f, v, q, t, etc...) du coup je n'ai pas vraiment réussi à comprendre ce qui se passait là-dedans en détail.


Sinon j'ai une petite question (que j'avais déjà posé à Chris Scientist dans son tuto POO Sokoban) : 

J'ai remarqué que, comme dans le code de Chris, tu utilises les instances de tes classes exclusivement à travers des pointeurs. Si j'ai bien compris, a priori on pourrait aussi choisir de les manipuler directement, du coup je voudrait savoir si il y a une raison précise, ou une convention, qui fait qu'on ne les accède que par des pointeurs.


Encore un grand merci pour ce magnifique travail !

(d'ailleurs il faudrait l'ajouter à la page académie, ça me semble clairement s'imposer)

Steph

il y a 5 ans

Merci pour ton commentaire... élogieux ! C'est trop :-)

Je suis très content que tu aies pu profiter pleinement de l'ensemble des éléments que j'ai développés dans ce tutoriel. C'était précisément le but  que je m'étais fixé. Et bravo pour être arrivé au bout en ayant (presque) tout compris ! C'est somme toute un tuto relativement dense où de très nombreuses notions sont abordées, certaines sont fondamentales et te serviront sans nul doute dans tes développements.

En ce qui concerne la conversion des espaces colorimétriques, je t'invite à lire les deux articles suivants qui détaillent davantage les choses :

Ma préférence va indiscutablement au second, qui est une discussion sur StackOverFlow. Rends-toi directement à la première réponse pour avoir une présentation très détaillée et très pertinente sur la méthode de conversion. Accroche-toi ... ce sont des maths : de la géométrie dans un espace à trois dimensions ;-)

Concernant ta question sur l'intérêt d'utiliser des pointeurs pour manipuler des structures de données comme les objets, je vais essayer d'être synthétique. Considérons les définitions suivantes :

class Base { ... };
class Derived : public Base { ... };

void foo(Base  b) { ... } // passage par valeur de l'argument
void bar(Base* b) { ... } // passage par pointeur de l'argument
void fum(Base& b) { ... } // passage par référence de l'argument

L'intérêt premier concerne le polymorphisme. Une instance de la classe Derived revêt en effet plusieurs formes : elle peut être considérée à la fois comme un objet de type Derived, mais également comme un objet de type Base (conséquence de l'héritage). Par conséquent, examinons les cas suivants :

Derived d;
foo(d);  // ici d sera considéré comme une instance de Base et perdra toute sa spécificité
bar(&d); // ici d conservera sa nature spécifique : une instance de Derived
fum(d);  // ici d conservera également sa nature spécifique

Le second intérêt concerne la potentielle copie en mémoire.

Base b;
foo(b);  // cas n°1
bar(&b); // cas n°2
fum(b);  // cas n°3

cas n° 1

Ici la fonction foo reçoit une copie de l'objet b. Autrement dit, si la fonction foo modifie l'état de l'objet copié, cela n'aura aucune incidence sur l'objet b.

cas n° 2

Ici la fonction bar reçoit une référence à l'objet b. Autrement dit, l'adresse mémoire à laquelle est stocké l'objet. Par conséquent, toute modification de l'état de b sera permanente, même après être ressorti de la fonction bar. La fonction travaille donc directement sur l'instance.

cas n°3

Identique au cas n°2.

Voilà... j'espère avoir été assez clair... il s'agit de notions absolument FONDAMENTALES du langage qui doivent être parfaitement maîtrisées !!!

Encore merci pour ton commentaire. C'est toujours très gratifiant de constater qu'on a réussi à transmettre un peu de ses connaissances à ceux qui ont envie d'apprendre :-) Et ça nous encourage à continuer !

Steph

NEW il y a 5 ans

Codnpix Codnpix

Merci pour ton commentaire... élogieux ! C'est trop :-)

Je suis très content que tu aies pu profiter pleinement de l'ensemble des éléments que j'ai développés dans ce tutoriel. C'était précisément le but  que je m'étais fixé. Et bravo pour être arrivé au bout en ayant (presque) tout compris ! C'est somme toute un tuto relativement dense où de très nombreuses notions sont abordées, certaines sont fondamentales et te serviront sans nul doute dans tes développements.

En ce qui concerne la conversion des espaces colorimétriques, je t'invite à lire les deux articles suivants qui détaillent davantage les choses :

Ma préférence va indiscutablement au second, qui est une discussion sur StackOverFlow. Rends-toi directement à la première réponse pour avoir une présentation très détaillée et très pertinente sur la méthode de conversion. Accroche-toi ... ce sont des maths : de la géométrie dans un espace à trois dimensions ;-)

Concernant ta question sur l'intérêt d'utiliser des pointeurs pour manipuler des structures de données comme les objets, je vais essayer d'être synthétique. Considérons les définitions suivantes :

class Base { ... };
class Derived : public Base { ... };

void foo(Base  b) { ... } // passage par valeur de l'argument
void bar(Base* b) { ... } // passage par pointeur de l'argument
void fum(Base& b) { ... } // passage par référence de l'argument

L'intérêt premier concerne le polymorphisme. Une instance de la classe Derived revêt en effet plusieurs formes : elle peut être considérée à la fois comme un objet de type Derived, mais également comme un objet de type Base (conséquence de l'héritage). Par conséquent, examinons les cas suivants :

Derived d;
foo(d);  // ici d sera considéré comme une instance de Base et perdra toute sa spécificité
bar(&d); // ici d conservera sa nature spécifique : une instance de Derived
fum(d);  // ici d conservera également sa nature spécifique

Le second intérêt concerne la potentielle copie en mémoire.

Base b;
foo(b);  // cas n°1
bar(&b); // cas n°2
fum(b);  // cas n°3

cas n° 1

Ici la fonction foo reçoit une copie de l'objet b. Autrement dit, si la fonction foo modifie l'état de l'objet copié, cela n'aura aucune incidence sur l'objet b.

cas n° 2

Ici la fonction bar reçoit une référence à l'objet b. Autrement dit, l'adresse mémoire à laquelle est stocké l'objet. Par conséquent, toute modification de l'état de b sera permanente, même après être ressorti de la fonction bar. La fonction travaille donc directement sur l'instance.

cas n°3

Identique au cas n°2.

Voilà... j'espère avoir été assez clair... il s'agit de notions absolument FONDAMENTALES du langage qui doivent être parfaitement maîtrisées !!!

Encore merci pour ton commentaire. C'est toujours très gratifiant de constater qu'on a réussi à transmettre un peu de ses connaissances à ceux qui ont envie d'apprendre :-) Et ça nous encourage à continuer !

Codnpix

il y a 5 ans

Merci beaucoup !

Ce n'est pas évident de se familiariser avec ces notions de pointeurs, les autres langages que je connais un peu (js, php, java..) font ces choses de façon plus ou moins implicite selon le cas, du coup c'est perturbant de se retrouver avec ces choix possibles. Mais c'est intéressant.

En tout cas ça devient plus clair. Merci !

Bonne journée :).

Codnpix

NEW il y a 5 ans

Steph Steph

Merci beaucoup !

Ce n'est pas évident de se familiariser avec ces notions de pointeurs, les autres langages que je connais un peu (js, php, java..) font ces choses de façon plus ou moins implicite selon le cas, du coup c'est perturbant de se retrouver avec ces choix possibles. Mais c'est intéressant.

En tout cas ça devient plus clair. Merci !

Bonne journée :).

Steph

il y a 5 ans

Oui ! C'est le problème de ces langages de plus haut niveau... ils masquent la réalité des choses...

Néanmoins, en PHP par exemple, il faut se méfier... par défaut les passages se font par référence... SAUF pour les tableaux ! Donc il faut le spécifier explicitement dans ce cas à l'aide de l'esperluette & :

function foo(array &$data) : void {
    // ...
}

Donc, bien lire la doc avant de présumer d'un comportement dont on a l'habitude avec d'autres langages ;-)

Steph

NEW il y a 5 ans

Codnpix Codnpix

Oui ! C'est le problème de ces langages de plus haut niveau... ils masquent la réalité des choses...

Néanmoins, en PHP par exemple, il faut se méfier... par défaut les passages se font par référence... SAUF pour les tableaux ! Donc il faut le spécifier explicitement dans ce cas à l'aide de l'esperluette & :

function foo(array &$data) : void {
    // ...
}

Donc, bien lire la doc avant de présumer d'un comportement dont on a l'habitude avec d'autres langages ;-)

Codnpix

il y a 5 ans

function foo(array &$data)

J'ai effectivement vu passer cette chose là quelques fois ^^.

Codnpix

il y a 5 ans

J'ai une autre petite question à laquelle j'ai dû mal à trouver une réponse moi même :

J'ai vu que tu utilises souvent le type size_t itérer sur des tableaux. Je ne comprends pas bien pourquoi...

Codnpix

NEW il y a 5 ans

Steph Steph

function foo(array &$data)

J'ai effectivement vu passer cette chose là quelques fois ^^.

Codnpix

NEW il y a 5 ans

Steph Steph

J'ai une autre petite question à laquelle j'ai dû mal à trouver une réponse moi même :

J'ai vu que tu utilises souvent le type size_t itérer sur des tableaux. Je ne comprends pas bien pourquoi...

jicehel

NEW il y a 5 ans

La taille des différents types n'est pas garantie par le langage C. Un int n'as pas forcément la même taille sur toutes les architectures. Les types size_t permettent d'avoir une taille fixe quelle que soit l'architecture, à partir du moment où ils sont définis.

Par exemple: int8_t => entier sur 8 bits  (soit de (-(2) puissance 7 - 1) à (2 puissance 7 moins 1)) et int16_t => int stocké sur 16 bits

tu rajoutes un u devant pour les non signés => uint8_t (de 0 à 2 puissance 8 - 1), etc ...

Codnpix

il y a 5 ans

Par exemple: int8_t => entier sur 8 bits  (soit de (-(2) puissance 7 - 1) à (2 puissance 7 moins 1)) et int16_t => int stocké sur 16 bits

tu rajoutes un u devant pour les non signés => uint8_t (de 0 à 2 puissance 8 - 1), etc ...

Je comprends l'intérêt de spécifier la taille de l'entier qu'on utilise avec un uint8_t, int16_t etc, mais je ne comprends toujours pas bien ce qu'apporte le fait d'utiliser précisément "size_t". De ce que j'ai vu dans les références C++ ça renvoie la valeur de l'opérateur sizeof sur n'importe quel objet, donc ça peut contenir quelque chose d'à peu près n'importe quel taille selon ce qu'on met dedans (?). Mais ça ne m'a pas beaucoup éclairé...

Codnpix

NEW il y a 5 ans

jicehel jicehel

Par exemple: int8_t => entier sur 8 bits  (soit de (-(2) puissance 7 - 1) à (2 puissance 7 moins 1)) et int16_t => int stocké sur 16 bits

tu rajoutes un u devant pour les non signés => uint8_t (de 0 à 2 puissance 8 - 1), etc ...

Je comprends l'intérêt de spécifier la taille de l'entier qu'on utilise avec un uint8_t, int16_t etc, mais je ne comprends toujours pas bien ce qu'apporte le fait d'utiliser précisément "size_t". De ce que j'ai vu dans les références C++ ça renvoie la valeur de l'opérateur sizeof sur n'importe quel objet, donc ça peut contenir quelque chose d'à peu près n'importe quel taille selon ce qu'on met dedans (?). Mais ça ne m'a pas beaucoup éclairé...

Steph

il y a 5 ans

Le type size_t est un entier non signé. Il convient donc parfaitement à la représentation des tailles, des dimensions de tableaux, des index croissants et non négatifs. Bien entendu, un uint8_t ou même un uint32_t feraient également l'affaire, mais le fait d'utiliser size_t indique plus clairement l'usage qu'on lui réserve et donc la nature de la variable ainsi déclarée. C'est d'ailleurs le type qui est retourné par la fonction sizeof().

Par ailleurs, il a la particularité de pouvoir s'adapter à l'architecture pour laquelle on compile. Sur une architecture 8 bits, il sera codé sur 8 bits, alors que si on passe sur une architecture 32 bits (comme la META), il sera codé sur 32 bits. Son intérêt majeur est justement cette adaptabilité : on se fout complètement de l'intervalle de valeurs qu'il permet de représenter car, quelle que soit l'architecture ciblée, ce sera toujours le plus grand nombre que l'architecture permet de représenter :-)

Est-ce que c'est plus clair pour toi ?

Steph

NEW il y a 5 ans

Codnpix Codnpix

Le type size_t est un entier non signé. Il convient donc parfaitement à la représentation des tailles, des dimensions de tableaux, des index croissants et non négatifs. Bien entendu, un uint8_t ou même un uint32_t feraient également l'affaire, mais le fait d'utiliser size_t indique plus clairement l'usage qu'on lui réserve et donc la nature de la variable ainsi déclarée. C'est d'ailleurs le type qui est retourné par la fonction sizeof().

Par ailleurs, il a la particularité de pouvoir s'adapter à l'architecture pour laquelle on compile. Sur une architecture 8 bits, il sera codé sur 8 bits, alors que si on passe sur une architecture 32 bits (comme la META), il sera codé sur 32 bits. Son intérêt majeur est justement cette adaptabilité : on se fout complètement de l'intervalle de valeurs qu'il permet de représenter car, quelle que soit l'architecture ciblée, ce sera toujours le plus grand nombre que l'architecture permet de représenter :-)

Est-ce que c'est plus clair pour toi ?

Codnpix

il y a 5 ans

Son intérêt majeur est justement cette adaptabilité : on se fout complètement de l'intervalle de valeurs qu'il permet de représenter car, quelle que soit l'architecture ciblée, ce sera toujours le plus grand nombre que l'architecture permet de représenter :-)

C'est déjà un peu plus clair oui :). Mais du coup, en terme d'occupation de la mémoire, si tu t'en sers de variable d'itération pour un tableau de disons 50 éléments, alors tu aura une variable sur 32 bits (si c'est le maximum que l'environnement permet) alors qu'un entier sur 8 bit aurait suffit ? C'est là que ça reste un peu flou pour moi..

Codnpix

NEW il y a 5 ans

Steph Steph

Son intérêt majeur est justement cette adaptabilité : on se fout complètement de l'intervalle de valeurs qu'il permet de représenter car, quelle que soit l'architecture ciblée, ce sera toujours le plus grand nombre que l'architecture permet de représenter :-)

C'est déjà un peu plus clair oui :). Mais du coup, en terme d'occupation de la mémoire, si tu t'en sers de variable d'itération pour un tableau de disons 50 éléments, alors tu aura une variable sur 32 bits (si c'est le maximum que l'environnement permet) alors qu'un entier sur 8 bit aurait suffit ? C'est là que ça reste un peu flou pour moi..

Steph

il y a 5 ans

Question logique :-) ... si t'es un maniaque de l'occupation mémoire, tu utiliseras un itérateur codé sur le nombre de bits suffisants (dans le cas que tu proposes, un uint8_t suffira amplement)... sinon tu te dis que, pour une variable volatile (qui sera détruite à la sortie de ta fonction), réserver 1 ou 4 octets... y a pas de quoi s'alarmer ;-)

Steph

NEW il y a 5 ans

Codnpix Codnpix

Question logique :-) ... si t'es un maniaque de l'occupation mémoire, tu utiliseras un itérateur codé sur le nombre de bits suffisants (dans le cas que tu proposes, un uint8_t suffira amplement)... sinon tu te dis que, pour une variable volatile (qui sera détruite à la sortie de ta fonction), réserver 1 ou 4 octets... y a pas de quoi s'alarmer ;-)

Codnpix

il y a 5 ans

Ok merci ! 

si t'es un maniaque de l'occupation mémoire

Je pense que je m'en préoccupais pas tant que ça avant, mais ton tuto propose une belle sensibilisation sur le sujet ! C'est assez chouette de découvrir qu'en poussant un peu les meubles, même une petite machine comme la META a des vraies possibilités de calcul. J'adore cet environnement minimaliste^^. (ça change par rapport aux monstres d'ordi qu'on a l'habitude d'utiliser aujourd'hui)


Codnpix

NEW il y a 5 ans

Steph Steph

Ok merci ! 

si t'es un maniaque de l'occupation mémoire

Je pense que je m'en préoccupais pas tant que ça avant, mais ton tuto propose une belle sensibilisation sur le sujet ! C'est assez chouette de découvrir qu'en poussant un peu les meubles, même une petite machine comme la META a des vraies possibilités de calcul. J'adore cet environnement minimaliste^^. (ça change par rapport aux monstres d'ordi qu'on a l'habitude d'utiliser aujourd'hui)


jicehel

NEW il y a 5 ans

Perso, quand je sais ce que j'aurais dans ma variable, je préfère utiliser les notation type int8_t ou uint8_t que je trouve plus parlante et même un bon vieux char quand c'est un caractère. On peut faire autrement et size_t peut avoir son utilité notamment par ce qu'il transporte une idée de taille et que dans une fonction, comme le disait Steph si on met se type pour dimensionner un index de tableau, on est certain d'avoir le plus grand tableau possible. De plus comme il est non signé, il permet de 'bloquer' un accès à un emplacement négatif  suite à une anomalie dans le code qui provoquerait une erreur. Pour moi, c'est bien dans des librairies que l'on veut réutiliser car on ne connait pas forcément la taille des tableaux que l'on utilisera lors de la réutilisation mais en règle générale, je code pour moi (même si je partage) et du coup, je ne l'utilise pas.