Game of life -- By Steph

Big Tuto on OOP & MVC with the Game Of Life

Author :  Steph

Complete Tutorial on Game of Life



I propose here a very complete tutorial based on a small mathematical recreation, instead of a real game in the classical sense of the term, for the Gamebuino META: the famous Game of Life, designed by John Conway in the 1970s. You will see that this is a great little exercise, which will allow us to discuss many programming concepts and techniques. The main idea that guided me throughout the writing of this tutorial was to propose an exercise that was both simple and rich in possibilities, that we would be able to treat in a different way, through a gradual approach in the complexity of the programming, to achieve a finalized application with a controlled architecture.

I apologize to the English-speakers, my tutorial is written in French ... but you can get the English version here with Google Translate.

However, the French version of tutorial is available here. And you'll need it, because unfortunately Google Translate breaks the layout of code snippets and doesn't correctly show the YouTube videos...

What you will learn in this tutorial :

  • program a cellular automaton,
  • optimize your code for:
    • speed up the execution of your code,
    • save the memory space,
  • better organize your code with Object Oriented Programming,
  • design your applications with a Model View Controller architecture,
  • intercept events when the user presses the buttons,
  • handle menus in your applications,
  • program a LED manager for the META,
  • program a sound effects manager.

Feel free to send me your comments, or ask me for clarification on things you didn't understand correctly.

I would like to point out that this application is my first programming experience in C++... and yes ;-) I'm learning like you! My code is most likely to be perfectible, so I rely on the experienced coders in the community to let me know about possible (or necessary!) improvements. I will try to integrate them as soon as possible... because I still have a job in real life... and I can't spend all the time I want to have fun with this great little console!

Author :  jicehel

Bon autant te le dire tout de suite, le jeu de la vie, ce n'est pas mon truc, mais par contre bravo. Quel boulot sur le tuto... J'ai vu une petite erreur dans le graphique de "Calcul du voisinage", il est écrit i différent de 0 ou j différent de 0 alors que c'est et à la place du ou (d'ailleurs c'est bien marqué dans le texte en dessous) par contre c'est faux dans le programme d'exemple (c'est un ou de marqué alors que ça devrait être un et).

J'ai regard rapidement le reste car je vais me faire √† manger mais le reste me parait tr√®s bien et quel beau tuto. Bien divis√© en chapitre, tr√®s clair. En fait je pense que les tuto de la META devrais un peu s'inspirer des tiens pour la lisibilit√© et l‚Äôencha√ģnement. Je trouve la navigation dan les tutos beaucoup plus intuitive sur ton site. Si tu veux int√©grer les miens √† ta sauce, fais toi plaisir.¬†

Donc, bon le programme d'exemple, désolé, ce n'est pas mon truc mais les tutos, j'adore et j'espère que tu va continuer à en faire.  :)

Author :  Steph

Merci pour ton commentaire fort sympathique. C'est très encourageant ! La rédaction m'a en effet demandé pas mal de boulot et je commençais à être impatient de voir le bout du tunnel...

Concernant ta remarque au sujet ¬†d'une petite erreur que tu aurais relev√©e concernant le calcul du voisinage d'une cellule, et bien... j'en conclue que ma figure n'est pas assez claire. Je la referai donc demain (√† La R√©union il est d√©j√† 1:00 du matin pass√©e et je dois me lever t√īt demain pour aller bosser).

Il ne s'agit pas d'une erreur ! Le voisinage d'une cellule est bien défini par :

grid[x+j][y+i] lorsque i ‚ąą [-1,1] et j ‚ąą [-1,1] et que (i ‚Ȇ 0) OU (j ‚Ȇ 0)

Le cas o√Ļ (i=0) ET (j=0) concerne la cellule centrale qui ne fait donc pas partie du voisinage ;-)

Je n'ai pas d√Ľ √™tre assez clair (ou tu as lu trop vite ?)... je reformulerai ce passage demain.

Concernant la mise en page des tutos du site officiel, effectivement, je trouve qu'elle n'est pas mise en valeur et les contenus sont difficilement appr√©ciables sous cette forme. C'est pourquoi j'ai pr√©f√©r√© publier le mien sur un site perso, o√Ļ je ma√ģtrise la feuille de styles et la navigation :-)

Pour terminer, je con√ßois que le Jeu de la Vie ne passionne pas tout le monde. N√©anmoins, dans le cadre de ce tuto, il ne faut pas le consid√©rer en tant que tel, mais plut√īt l'envisager comme une base p√©dagogique abordable par sa simplicit√©, et qui permet d'illustrer de nombreux concepts de programmation int√©ressants (voire fondamentaux) sans se heurter √† la difficult√© intrins√®que d'un cas d'√©tude plus complexe.

Encore merci pour ton commentaire !

Author :  jicehel

Hum, je ne comprends pas en effet. Si i=0 et j=-1 par exemple, la cellule fais bien partie du voisinage. Seul le cas ou i=0 et j=0 est √† exclure du voisinage. Enfin c'est ce que je comprends moi. Donc le voisinage est i ‚ąą [-1,1] et j ‚ąą [-1,1] et i,j ‚Ȇ (0,0) soit i‚Ȇ0 et j‚Ȇ0. Si i=-1 ou 1 et j=0, la cellule fais bien partie du voisinage.

Si oui, je comprends tout à fais la base pédagogique et ça, c'est très bien fait et en effet, c'est un cas qui permet de bien illustrer différentes choses au travers des différents chapitre.

Author :  Steph

Hi hi ... décidément...


Hum, je ne comprends pas en effet. Si i=0 et j=-1 par exemple, la cellule fais bien partie du voisinage. Seul le cas ou i=0 et j=0 est à exclure du voisinage. Enfin c'est ce que je comprends moi.

Tout √† fait ! Le cas "i=0 ET j=-1" fait bien partie des cas o√Ļ "i‚Ȇ0 OU j‚Ȇ0"


Seul le cas ou i=0 et j=0 est à exclure du voisinage. Enfin c'est ce que je comprends moi.

C'est exactement ça.


Donc le voisinage est i ‚ąą [-1,1] et j ‚ąą [-1,1] et i,j ‚Ȇ (0,0) soit i‚Ȇ0 et j‚Ȇ0.

Et non justement... (i,j) ‚Ȇ (0,0) <=> ! [i=0 ET j=0] <=> i‚Ȇ0 OU j‚Ȇ0 ;-)

Author :  jicehel

Bon je n'insiste pas, c'est peut √™tre moi qui me trompe mais si je suis d'accord avec ! [i=0 ET j=0] pour moi, le r√©sultat est diff√©rent de i‚Ȇ0 OU j‚Ȇ0 ou du coup tu exclues la ligne i=0 et la colonne j=0 donc tu exclues 5 cases au lieu d'une selon moi dont la centrale mais aussi 4 autres.¬†


Author :  Steph

Je t'assure que non !... simple règle de logique :

NON(A ET B) = NON(A) OU NON(B)

Author :  jicehel

ok, oui, il faut bien A=0 et B=0 pour avoir un résultat à 0, sinon on a 1, c'est vrai. Désolé, je dois être fatigué sans doute, mais oui, c'est vrai...

Author :  chris-scientist

Très bon tutoriel !

Tu as très bien décrit la POO et le MVC, c'est un bon complément à ce que j'ai écris. Je pense d'ailleurs que je vais ajouter un lien vers ton tutoriel.

Je pense que pour le framework que j'écris je vais m'inspirer de tes effets de lumières.

J'ai repéré deux petites coquilles, dans la partie "son et lumière", dans le dernier exemple de code :

  • tu indique qu'il s'agit du fichier GameController.h mais je pense qu'il s'agit du .cpp.
  • et pour la derni√®re m√©thode de cette classe tu as √©crit le commentaire suivant "// idem lrosqu'il sort du mode √©dition :".
Author :  Steph

Merci pour ton commentaire ! Je viens de corriger les coquilles :-)

Je pense effectivement que, pour les  apprenants, tes tutos et celui-ci constituent une bonne base pour se lancer dans la POO et l'architecture MVC. Ils ont tout à y gagner lorsque l'application prend de l'ampleur...

Concernant les lumi√®res, oui, c'est plus facile de g√©rer √ßa dans un contr√īleur d√©di√©... et surtout... dans le r√©f√©rentiel HSL ! J'ai d'ailleurs sugg√©r√© √† Soru (sur Discord) d'ajouter les fonctions rgb2hsl et hsl2rgb dans l'API de la biblioth√®que officielle.

Author :  sautax

Par rapport √† mon d√©p√īt j'ai fait une branche qui restera la m√™me pour toujours comme √ßa je peux faire des changements ^^

https://github.com/sautax/game-of-life-gamebuino/tree/0.4

Author :  Party_Pete

C'est incroyable! Le tuto est trés complet, et il a connaissance beaucoup pour moi apprendre C++ sur la Gamebuino. Je ne parle pas français bien, mais je comprende assez de français essayer lire le tuto en français. Merci beaucoup pour vos poste!

Author :  Steph

Merci sautax ! C'est tr√®s sympathique de ta part. Je pousserai la mise √† jour du lien vers la bonne branche de ton d√©p√īt d√®s ce soir.

Edit: c'est fait :-)

Author :  YQN

MERCI steph :D

Author :  YQN

Une petite question : je vais essayer d'utiliser cette méthode pour créer un clone de Breakout, du coup la logique est d'avoir un MVC pour chaque élément ? (ex. Pad, PadView, PadController, Ball, BallView, BallController etc. ?)

Merci d'avance ! :)

Edit : Après avoir essayé j'imagine que ce n'est pas la bonne méthode, je suis un peu perdu :/

Author :  Steph

Merci pour tes commentaires YQN :-)

Désolé pour le délai de réponse... mais quelques impondérables m'empêchent actuellement d'être très présent auprès de la communauté.

Pour r√©pondre √† ta question... il ne faut pas consid√©rer que l'architecture MVC est la solution id√©ale qui s'applique √† tous les cas de figures que tu vas rencontrer lorsque tu vas chercher √† organiser ton code ! Loin de l√† ! Il est tout √† fait inutile d'employer un marteau-piqueur pour enfoncer une punaise ;-) Cette architecture se pr√™te tr√®s bien √† la modularisation des composants de ton code lorsque celui-ci se complexifie. Il en facilite l'organisation et la compr√©hension en s√©parant bien la logique qui les articule. L'architecture que je propose dans le Jeu de la Vie en illustre la plupart des aspects. Mais vouloir √† tout prix adopter cette organisation peut, au contraire, alourdir ton code et s'av√©rer parfaitement inutile. Dans des cas simples comme la gestion d'un paddle dans un jeu de casse-briques, le mod√®le et la vue peuvent √™tre rassembl√©s au sein d'un m√™me et unique composant (Paddle). Un contr√īleur des √©v√©nements utilisateurs (pour intercepter l'appui sur les boutons de la console) sera par contre tr√®s utile pour isoler la gestion de ce type d'interactions dans ton code. Le contr√īleur pourra alors ensuite agir directement sur ton composant Paddle en le d√©pla√ßant (c'est-√†-dire en envoyant un ordre ayant pour effet de modifier sa coordonn√©e selon l'axe horizontal). Et lorsque ce sera le moment (pr√©voir un point d'entr√©e √† partir de la boucle de contr√īle global loop), ce m√™me Paddle pourra simplement se dessiner en fonction de ses coordonn√©es (qui repr√©sentent finalement son mod√®le). Il sera, √† lui seul, son mod√®le et sa vue...

Est-ce que j'ai répondu à ta question ?

Author :  Codnpix

√áa a l'air excellent, j'ai h√Ęte de m'y plonger.

Merci pour ce travail !

Author :  Steph

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

Author :  Codnpix

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)

Author :  Steph

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 !