Planète Casio - Tout le forum - Flux RSS http://www.planet-casio.com Programmes Casio, Jeux, Cours pour Calculatrices Casio fr-FR https://www.planet-casio.com/images/logo.gif Planète Casio - Tout le forum - Flux RSS http://www.planet-casio.com 55 50 Programmes Casio, Jeux, Cours pour Calculatrices Casio. Wed, 22 Jul 2026 11:45:38 GMT Wed, 22 Jul 2026 11:45:38 GMT contact@planet-casio.com (Planet Casio) contact@planet-casio.com (Planet Casio) 5 Tutoriel de création d'un jeu de pong avec le fxsdk/gint https://www.planet-casio.com/Fr/forums/topic19641--.html NOTE : Le tutoriel est aussi disponible ici : https://git.planet-casio.com/mibi88/pong_fxsdk/src/branch/master/TUTORIAL_fr.md. Tutoriel de développement d'un jeu de pong avec le fxsdk Dans ce tutoriel nous allons coder un jeu de pong en C avec gint et le fxsdk. Préréquis Des connaissances solides de C sont requises. Pour pouvoir suivre ce tutoriel, vous avez besoin du fxsdk et de gint. Vous avez donc besoin d'un système d'exploitation UNIX-like compatible avec celui-ci. Vous avez bien sûr besoin d'une calculatrice CASIO monochrome ou couleur pour pouvoir tester l'add-in. Pour tester l'add-in régulièrement, vous pouvez utiliser mq (https://www.planet-casio.com/Fr/forums/topic18782-4-mq-emulateur-add-ins-universel.html). Une version en ligne de mq existe aussi : https://www.planet-casio.com/mq/index.html Je trouve cependant qu'il est plus pratique d'utiliser la build Linux pour pouvoir tester l'add-in en saisissant la commande suivante dans un shell : $ <chemin vers le binaire de mq> <chemin vers l'add-in> NOTE : Dans ce tutoriel je marquerais tout le texte à replacer entre chevrons. Ci-dessus, `<chemin vers le binaire de mq>` doit être replacé par le chemin vers ce binaire, et `<chemin vers l'add-in>` doit être replacé par le chemin vers l'add-in à exécuter. Je vous conseille de quand même de tester régulièrement vos add-ins on-calc, mq est encore assez nouveau, et ne reproduit pas à 100 % le comportement de la calculatrice. Par exemple, le jeu ne s'exécutera probablement pas à la même vitesse dans mq que sur la vraie calculatrice. Quelques infos utiles Le coin en haut à gauche est l'origine du repère. Le pixel en haut à gauche est aux coordonnées (0; 0) et le pixel en bas à droite aux coordonnées (127; 63) ou (395; 223) pour les calculatrices monochromes ou couleur respectivement. L'écran des calculatrices monochromes a une résolution de 128 × 64 et celui des calculatrices couleur une résolution de 396 × 224 (sans le bandeau). Création du projet Pour créer un nouveau projet, il suffit d'exécuter la commande suivante dans le dossier où vous souhaitez créer le projet : $ fxsdk new <nom du projet> Par exemple : $ fxsdk new pong Entrez le répertoire crée avec ce nom. Ensuite vous pouvez compiler votre projet. Pour le compiler pour des calculatrices monochromes : $ fxsdk build-fx Pour des calculatrices couleur : $ fxsdk build-cg Pour compiler la version monochrome pour une calculatrice couleur : $ fxsdk build-fxg3a Pour tester l'add-in avec Addin push (https://www.planet-casio.com/Fr/programmes/programme4384-2-addin-push-heath-utilitaires-add-ins.html#65954) : $ fxsdk build-cg-push et pour l'envoyer (nécessite d'avoir lancé Addin push sur la calculatrice) : $ fxlink -p build-cg-push build-cg-push/<nom de l'add-in>.bin Premier test Vérifier que le projet compile et que l'add-in produit affiche bien « Sample fxSDK add-in. » en haut à gauche de l'écran. Structure du projet Nous allons structurer le projet comme ceci: `main.c` Lancement du jeu et boucle du jeu. `game.c` et `game.h` logique du jeu et rendu. `game.h` contiendra toutes les structures. `ball.c` et `ball.h` mouvement, collisions et rendu de la balle. `paddle.c` et `paddle.h` gestion des collisions entre la balle et la raquette, mouvement et rendu de la raquette. `fixed.h` Calculs à virgule fixe (j'explique ce que c'est dans la suite du tutoriel). Créez ces fichiers dans `src`. Dans assets-fx et assets-cg, nous stockerons l'écran de titre, ayant une dimension de 128 × 64 ou 396 × 224 pour calculatrices monochromes ou couleur respectivement. Votre projet devrait avoir la structure suivante : $ tree . ├── assets-cg │   ├── fxconv-metadata.txt │   ├── icon-cg.xcf │   ├── icon-sel.png │   ├── icon-uns.png │   └── title.png ├── assets-fx │   ├── fxconv-metadata.txt │   ├── icon.png │   └── title.png ├── CMakeLists.txt └── src ├── ball.c ├── ball.h ├── fixed.h ├── game.c ├── game.h ├── main.c ├── paddle.c └── paddle.h 4 directories, 17 files Ajout des fichiers de code source et des assets à CMakeLists.txt CMake est utilisé pour compiler les projets utilisant le fxsdk. Ajout des fichiers de code source Pour que nos fichiers `.c` soient bien compilés, il faut les ajouter ici : set(SOURCES src/main.c # ... ) On a donc : set(SOURCES src/main.c src/ball.c src/game.c src/paddle.c # ... ) Ajout des assets Pour ajouter nos assets au projet, il faut les ajouter à la propriété `ASSETS`, `ASSETS_fx` ou `ASSETS_cg`, pour qu'ils soient inclus lorsque l'on compile l'add-in pour n'importe quel modèle de calculatrice, une calculatrice avec un écran monochrome ou une calculatrice avec un écran couleur respectivement. Pour nous, les lignes set(ASSETS # ... ) set(ASSETS_fx assets-fx/example.png # ... ) set(ASSETS_cg assets-cg/example.png # ... ) deviennent donc : set(ASSETS # ... ) set(ASSETS_fx assets-fx/title.png # ... ) set(ASSETS_cg assets-cg/title.png # ... ) Ensuite, il faut changer les fichiers `fxconv_metadata.txt` dans `assets-fx` et `assets-cg`. Ils contiendront : title.png: type: bopti-image name: title_img Ces fichiers permettent de configurer comment les images sont converties vers le format utilisé par gint par l'utilitaire du fxsdk appelé fxconv. Nous allons utiliser la même configuration que celle utilisée par l'image d'exemple. Nous changeons juste les noms. C'est aussi à l'aide de ce fichier qu'il est possible d'ajouter des polices d'écriture (sous forme de bitmap, d'image) à un projet, mais nous ne le ferons pas dans ce projet. Code Nous sommes enfin prêts à rentrer dans le vif du sujet, le code ! `main.c`, la boucle du jeu Nous allons commencer par faire la boucle du jeu. Pour que notre jeu soit agréable à jouer, nous allons limiter le nombre d'images par seconde auquel il peut tourner, car la calculatrice est en mesure de l'exécuter très très vite. Nous allons quitter la boucle si EXIT est appuyé, ce que nous pouvons vérifier avec `keydown(KEY_EXIT)` définie dans `<gint/keyboard.h>`. L'ensemble des codes associés à des touches peuvent être lus dans le fichier d'en-tête `<gint/keycodes.h>` (https://git.planet-casio.com/Lephenixnoir/gint/src/branch/dev/include/gint/keycodes.h). Ce fichier est inclus par `<gint/keyboard.h>`. J'ai donc choisi de limiter le nombre d'images par seconde à 50 (cela vous rappelle peut-être les jeux vidéos PAL des années 80 en Europe). NOTE : Sur PC, on calcule le temps qu'une image prend pour être produite pour adapter la vitesse des éléments qui se déplacent, pour que le jeu se comporte pareil indépendamment du nombre d'images par seconde. Le jeu est assez simple, on ne limitera donc que le nombre d'images par seconde, il est très probable qu'une image soit produite assez rapidement, et étant donné le peu de différences entre les différents modèles de calculatrices, en testant sur une calculatrice couleur et une calculatrice monochrome, la vitesse à laquelle s'exécutera le jeu est assez prévisible. Utilisation de timer Pour compter le temps qui s'est écoulé entre deux images, nous allons créer un timer. Le processeur, à un intervalle de temps régulier, arrêtera ce qu'il est en train d'exécuter, et exécutera une fonction que nous passons à `timer_configure`, puis retournera à ce qu'il était en train d'exécuter. Les fonctions liées à la gestion de timer sont définies dans `<gint/timer.h>`. D'abord il faut le configurer avec `timer_configure`, puis le lancer avec `timer_start`. Ensuite, lorsque l'on n'en a plus besoin, on l'arrête avec `timer_stop`. Cela libère aussi les ressources associées à ce timer. La macro `GINT_CALL` permet d'obtenir un `gint_call_t` à passer à `timer_configure` pour passer la fonction à appeler en argument. Cette fonction doit renvoyer l'entier (`int`) `TIMER_CONTINUE` ou `TIMER_STOP`, pour que le timer continue d'interrompre le processeur à intervalles réguliers, ou pour qu'il s'arrête. Pour qu'une fonction ayant pour prototype `int timer_call(void)` soit appelée toute les millisecondes, on peut écrire le code suivant : int timer; timer = timer_configure(TIMER_ANY, 1000, GINT_CALL(timer_call)); if(timer < 0){ /* Afficher un message d'erreur. */ dclear(C_WHITE); dtext(1, 1, C_BLACK, "Failed to create timer!"); dupdate(); getkey(); return 0; } timer_start(timer); /* Du code..., par exemple la boucle d'un jeu. */ timer_stop(timer); NOTE : `getkey` permet d'attendre l'appui d'une touche, et renvoie un objet `key_event_t` décrivant la touche qui a été appuyée (plus d'informations dans `<gint/keyboard.h>`. NOTE : Au lieu de `TIMER_ANY` on peut aussi passer `TIMER_TMU` pour un timer très précis, ou `TIMER_ETMU`, pour un timer un peu moins précis. Pour plus d'informations sur les timer, regardez le fichier d'en-tête `<gint/timer.h>`. Quelques fonctions de dessin Dans la boucle de jeu, nous allons effacer l'écran avec `dclear(C_WHITE);` et afficher ce que nous avons dessiné avec `dupdate();`. On peut afficher du texte avec `dtext`, par exemple : dtext(1, 1, C_BLACK, "Sample fxSDK add-in."); Boucle de jeu Au début de chaque itération de cette boucle, nous allons appeler `clearevents`, pour que gint traite tout les événements en attente (pour pouvoir vérifier si une touche est enfoncée avec `keydown`), et `cleareventflips`, pour que gint sache que l'on commence une nouvelle image du jeu, et qu'il mette à jour les états des touches pour que l'on puisse utiliser `keypressed` et `keyreleased`, qui permet de savoir si une touche a été relâchée ou enfoncée depuis le dernier appel à `cleareventflips`, donc depuis la dernière image. NOTE : Sur la calculatrice, `main` doit renvoyer 1 en cas de succès et pas zéro comme c'est le cas habituellement. Essayez d'écrire la boucle de jeu, puis regardez mon implémentation. #include <gint/keyboard.h> #include <gint/display.h> #include <gint/timer.h> #include <gint/cpu.h> static volatile unsigned long int ms = 0; static int timer_call(void) { ms++; return TIMER_CONTINUE; } struct game game; int main(void) { int timer; timer = timer_configure(TIMER_TMU, 1000, GINT_CALL(timer_call)); if(timer < 0){ dclear(C_WHITE); dtext(1, 1, C_BLACK, "Failed to create timer!"); dupdate(); getkey(); return 0; } timer_start(timer); /* Initialisation du jeu. */ game_init(&game); /* Boucle de jeu. */ do{ cleareventflips(); clearevents(); /* Generation d'une image du jeu. */ dclear(C_WHITE); game_logic(&game); game_draw(&game); dupdate(); /* Attendre que 20ms se soient ecoulees. */ while(ms < 20) sleep(); ms = 0; }while(!keydown(KEY_EXIT)); timer_stop(timer); /* On retourne 1, car c'est ce que des add-ins doivent renvoyer en cas de * succes. */ return 1; } Testez. Si vous arrivez bien à quitter le jeu en appuyant sur EXIT, continuez. `game.c` et `game.h` : initialisation, rendu et logique du jeu J'aime bien structurer mes jeux en séparant l'initialisation, la logique et le dessin, en stockant l'état dans lequel le jeu se trouve et en traitant chaque état dans des grands `switch` dans les fonctions s'occupant de la logique et du dessin. Dans `game.h` Nous allons commencer par créer une structure contenant toutes les données du jeu, contenant pour l'instant seulement l'état dans lequel le jeu se trouve : struct game { unsigned char state; }; Nous allons définir ces différents états avec une `enum` : enum { S_INGAME, /* Etat dans lequel on est lorsque le joueur est en train de * jouer. */ S_AMOUNT }; Nous ajouterons d'autres états au fur et à mesure ou nous développerons le jeu. Dans `game.c` Ensuite, nous allons créer trois fonctions: `game_init` pour initialiser le jeu. `game_logic` pour la logique du jeu. `game_draw` pour le rendu. `game.c` contient donc : void game_init(struct game *game) { game->state = S_INGAME; } void game_logic(struct game *game) { switch(game->state){ case S_INGAME: /* TODO */ break; default: /* Cela ne devrait jamais arriver. */ game->state = S_INGAME; } } void game_draw(struct game *game) { switch(game->state){ case S_INGAME: /* TODO */ break; default: /* Cela ne devrait jamais arriver. */ game->state = S_INGAME; } } On peut ensuite appeler `game_logic`, puis `game_draw` dans la boucle du jeu, et `game_init` avant. `paddle.c` : mouvement et dessin de la raquette Nous allons maintenant commencer à écrire le code exécuté lorsque l'on se trouve dans l'état `S_INGAME`. Nous allons commencer par coder le code pour le dessin et le mouvement de la raquette du joueur. Nous allons en premier lieu créer une structure contenant des données décrivant la raquette dans `game.h` : struct paddle { /* La position de la raquette. */ int x; int y; }; Les membres `x` et `y` de `struct paddle` contiennent la position du coin en haut à gauche de la raquette. Comme dans `game.c`, nous allons créer une fonction pour initialiser une structure `paddle` : `paddle_init`. /* Initialise la raquette. */ void paddle_init(struct paddle *paddle, int x); Ensuite, nous allons écrire plusieurs fonctions utilisant cette structure, en commençant par les fonctions suivantes : /* Deplace la raquette avec les touches du clavier. */ void paddle_move(struct paddle *paddle); /* Dessine la raquette. */ void paddle_draw(struct paddle *paddle); Initialisation de la raquette. Dans `paddle_init` nous allons centrer la raquette sur l'axe des ordonnées, et mettre son abscisse à `x` (un paramètre de `paddle_init`). Pour calculer le centre de l'écran, nous pouvons utiliser les object-like macros `DWIDTH` et `DHEIGHT` définies dans `<gint/display-fx.h>` ou `<gint/display-cg.h>`. Ces en-têtes sont inclues par `<gint/display.h>`, la bonne en-tête est choisie en fonction de la plateforme visée. Dans `paddle.h` nous allons inclure `<gint/config.h>` pour identifier pour quelle plateforme nous compilons le jeu, afin de définir des macros avec des valeurs différentes pour chaque plateforme, permettant d'avoir une expérience similaire sur les calculatrices couleur ou monochrome, malgré les différences de taille d'écran. Le jeu est compilé pour une calculatrice monochrome (ou couleur avec `fxsdk build-fxg3a`), si `GINT_RENDER_MONO` est non nul. Le jeu est compilé pour une calculatrice couleur si `GINT_RENDER_RGB` est défini. Ce tutoriel n'est pas adapté aux calculatrices ClassPad, car je n'ai pas une telle machine. Cependant, il y a sûrement peu de choses à ajuster, entre autres ces macros, pour le suivre pour le développement d'un add-in pour ClassPad. L'add-in est compilé pour ClassPad si `GINT_HW_CP` est défini, et pour Graph 90+e/CG50 ou Prizm si `GINT_HW_CG` est défini. Nous allons définir la largeur de la raquette sous le nom `PADDLE_WIDTH`, sa hauteur avec `PADDLE_HEIGHT` et sa vitesse de déplacement avec `PADDLE_SPEED`. Voici comment je les ai définis : #if GINT_RENDER_MONO /* La vitesse de la raquette. */ #define PADDLE_SPEED 1 /* La taille de la raquette. */ #define PADDLE_WIDTH 3 #define PADDLE_HEIGHT 16 #elif GINT_RENDER_RGB && GINT_HW_CG /* La vitesse de la raquette. */ #define PADDLE_SPEED 3 /* La taille de la raquette. */ #define PADDLE_WIDTH 9 #define PADDLE_HEIGHT 48 #else #error "Unsupported!" #endif Essayez maintenant de coder `paddle_init`. Ensuite, vous pouvez regarder comment je l'ai implémenté : void paddle_init(struct paddle *paddle, int x) { /* Initialisation de la position de la raquette. */ paddle->x = x; paddle->y = DHEIGHT/2-PADDLE_HEIGHT/2; } Ajoutez deux `struct paddle` à `struct game` : une pour le joueur, et une pour l'ordinateur. NOTE : Lorsque j'écris « l'ordinateur », je désigne le joueur joué par la calculatrice. Ma `struct game` contient les membres suivants maintenant : struct game { /* La raquette du joueur. */ struct paddle paddle1; /* La raquette de l'ordinateur. */ struct paddle paddle2; /* L'etat dans lequel se trouve le jeu. */ unsigned char state; }; Appelez ensuite `paddle_init` dans `game_init` pour initialiser les deux raquettes. J'ai placé mes raquettes à 16 px du bord de l'écran. NOTE : On aurait aussi pu définir une object-like macro pour changer cette position d'une plateforme à l'autre. Essayez déjà de le faire par vous-même. Voici mon implémentation : void game_init(struct game *game) { /* Initialisation des raquettes aux deux cotes de l'ecran. */ paddle_init(&game->paddle1, 16); paddle_init(&game->paddle2, DWIDTH-16-PADDLE_WIDTH); game->state = S_INGAME; } Dessin de la raquette Pour vérifier que nos calculs sont corrects, nous allons dessiner la raquette. Nous allons dessiner la raquette dans `paddle_draw`, défini dans `paddle.c`. Nous allons dessiner un rectangle blanc avec un bord noir d'un pixel pour la raquette. Il est donc le bon moment de s'intéresser à quelques fonctions de dessin de gint (dont les prototypes sont dans `<gint/display.h>`) : `drect` : dessin d'un rectangle rempli `drect_border` : dessin de la bordure d'un rectangle `dpixel` : dessin d'un seul pixel `dline` : dessin d'une ligne `dcircle` : dessin d'un cercle `dellipse` : dessin d'un ellipse `dpoly` : dessin d'un polygone `dtext`, `dtext_opt` et `dprint` : affichage de texte `dimage` : dessin d'une image `dsubimage` : dessin d'une partie d'une image NOTE : Ces fonctions sont très bien documentées dans l'en-tête `<gint/display.h>`. Pour le dessin de la raquette, nous allons utiliser `drect_border`. Le prototype de `drect_border` dans `<gint/display.h>` est : void drect_border(int x1, int y1, int x2, int y2, int fill_color, int border_width, int border_color); Implémentez `paddle_draw`. Voici mon implémentation de `paddle_draw` : void paddle_draw(struct paddle *paddle) { /* Dessin d'un rectangle representant la raquette. */ drect_border(paddle->x, paddle->y, paddle->x+PADDLE_WIDTH, paddle->y+PADDLE_HEIGHT, C_NONE, 1, C_BLACK); } Pour voir les raquettes, nous allons appeler `paddle_draw` dans `game_draw`. Ma fonction `game_draw` ressemble maintenant à : void game_draw(struct game *game) { switch(game->state){ case S_INGAME: /* Dessin des raquettes. */ paddle_draw(&game->paddle1); paddle_draw(&game->paddle2); break; default: /* Cela ne devrait jamais arriver. */ game->state = S_INGAME; } } Testez votre code, et continuez si vous voyez bien les deux rectangles représentant les raquettes aux deux côtés de l'écran. Mouvement de la raquette Maintenant que le joueur voit les raquettes, nous allons lui permettre de déplacer la sienne. Nous allons utiliser `keydown` pour vérifier si les touche `KEY_UP` ou `KEY_DOWN` sont pressées pour déplacer la raquette vers le haut ou vers le bas respectivement. Nous allons aussi vérifier, que la raquette ne sort pas de l'écran (que `paddle->y >= 0 && paddle->y < DHEIGHT-PADDLE_HEIGHT`). Je vous laisse donc implémenter la fonction `paddle_move` qui a le prototype suivant : void paddle_move(struct paddle *paddle); Voici mon implémentation : void paddle_move(struct paddle *paddle) { /* Deplacement de la raquette vers le haut si la fleche "haut" est pressee. */ if(keydown(KEY_UP)){ if(paddle->y-PADDLE_SPEED >= 0){ paddle->y -= PADDLE_SPEED; }else{ paddle->y = 0; } } /* Deplacement de la raquette vers le bas si la fleche "bas" est pressee. */ if(keydown(KEY_DOWN)){ if(paddle->y+PADDLE_SPEED < DHEIGHT-PADDLE_HEIGHT){ paddle->y += PADDLE_SPEED; }else{ paddle->y = DHEIGHT-PADDLE_HEIGHT-1; } } } Nous allons appeler cette fonction dans `game_logic`, pour pouvoir voir la raquette se déplacer en fonction des actions de l'utilisateur ! Essayez par vous-même. Voici à quoi ressemble ma fonction `game_logic` maintenant : void game_logic(struct game *game) { switch(game->state){ case S_INGAME: /* Permettre au joueur de deplacer sa raquette. */ paddle_move(&game->paddle1); break; default: /* Cela ne devrait jamais arriver. */ game->state = S_INGAME; } } `fixed.h` : calculs à virgule fixe Les écrans des calculatrices sont petits : 128 × 64 pour les calculatrices monochromes, et 396 × 224 pour les calculatrices couleur (hors ClassPad). Il est donc difficile de contrôler précisément la vitesse d'objets dans un jeu en les stockant sous forme d'entiers. Une vitesse de deux pixels par image est beaucoup plus rapide qu'une vitesse d'un seul pixel par image. Nous aurons besoin de précision supplémentaire dans le déplacement de la balle. On pourrait donc utiliser des `floats`. Cependant, les calculatrices CASIO SH-3 ou SH-4 n'ont pas de FPU, une unité de calcul avec des nombres à virgule flottante dans le MPU. Ils sont donc effectués en software, et sont donc assez lents. Notre jeu est assez simple, donc cela ne pose pas de gros problème, en dehors de la taille d'add-in un peu plus grande. Dans des jeux plus complexes, cela peut poser problème. Nous allons donc faire des calculs à virgule fixe, bien moins coûteux : nous allons réserver quelque bits dans un entier pour les chiffres après la virgule. Exemple : 19,2 comme entier 16 bit non signé avec une précision de 8 bits après la virgule : Avant la virgule Après la virgule 00010011 00110011 Si nous reconvertissons ce chiffre en nombre à virgule, nous obtenons environ 19,199. Nous avons perdu en précision, mais ce n'est pas grave dans un jeu vidéo (19,199 c'est quand même très proche de 19,2 !). Codons quelque macros pour convertir des nombres à virgule en nombres à virgule fixe, et vice-versa, et d'autres pour la multiplication et la division (nous pouvons faire les additions et les soustractions comme d'habitude). La conversion est très facile, il suffit d'effectuer des décalages de bits. /* La precision des nombres a virgule fixe. */ #define PRECISION 8 /* Conversion d'un nombre vers un nombre a virgule fixe. */ #define TO_FIXED(num) (int)((num)*(1<<PRECISION)) /* Conversion d'un nombre a virgule fixe vers un entier. */ #define TO_INT(num) ((num)>>PRECISION) NOTE : Je multiplie `num` par `(1*PRECISION)` pour pouvoir passer des `float` à la macro, on ne peut pas faire des décalages de bits sur un `float`. Les macros pour la multiplication et la division sont très simples, elles aussi : /* Multiplication de deux nombres a virgule fixe. */ #define MUL(a, b) ((a*b)>>PRECISION) /* Divise le nombre a virgule fixe a par le nombre a virgule fixe b. */ #define DIV(a, b) ((a<<PRECISION)/b) Nous allons finir par définir un type `fixed_t`, pour bien les distinguer des autres entiers : /* Type virgule fixe (pour les distinguer des autres entiers). */ typedef long fixed_t; NOTE : Ce code suppose que le décalage de bits vers la droite est arithmétique (ce n'est pas forcément le cas en C), et il y a un risque de débordement lors des multiplications et des divisions si `a` et/ou `b` sont trop grands. `ball.c` : mouvement de la balle et collisions avec la raquette Tout d'abord, nous allons créer une structure contenant des données associées à la balle. Nous allons y stocker la position de la balle (`x` et `y`), la direction dans laquelle elle se déplace (`x_inc` et `y_inc`) et sa vitesse comme des `fixed_t`. Ma structure : /* La structure representant la balle (utilisee dans ball.c). */ struct ball { /* La position de la balle. */ fixed_t x; fixed_t y; /* La direction dans laquelle on deplace la balle. */ fixed_t x_inc; fixed_t y_inc; /* La vitesse de la balle. */ fixed_t speed; }; `x` et `y` contiennent la position du milieu de la balle. Initialisation et dessin de la balle Nous allons ensuite implémenter les fonctions suivantes dans `ball.c` : /* Initialisation de la balle. */ void ball_init(struct ball *ball, fixed_t x_inc, fixed_t y_inc, int speed); /* Dessin de la balle. */ void ball_draw(struct ball *ball); Commençons par `ball_init`. Nous allons placer la balle au milieu de l'écran, et initialiser les autres membres de `struct ball` aux valeurs de `x_inc`, `y_inc` et `speed`. Voici mon implémentation : void ball_init(struct ball *ball, fixed_t x_inc, fixed_t y_inc, int speed) { /* Mettre la balle au milieu de l'ecran. */ ball->x = TO_FIXED(DWIDTH/2); ball->y = TO_FIXED(DHEIGHT/2); /* Initialisation de la direction dans laquelle la balle se deplace et sa * vitesse. */ ball->x_inc = x_inc; ball->y_inc = y_inc; ball->speed = speed; } Maintenant dessinons la balle. Nous la dessinerons comme un rectangle rempli. Nous définirons une macro – `BALL_SIZE` – dans `ball.h`, contenant la moitié de la taille de la balle moins 1 (e.g. pour une balle d'une taille de trois pixels, `BALL_SIZE` est égal à (3 − 1) ÷ 2). Nous allons lui donner une valeur différente en fonction de la plateforme. Ma balle a une taille de trois pixels sur calculatrices monochromes, et une taille de sept pixels sur calculatrices couleur (hors ClassPad). Mon fichier `ball.h` contient maintenant le code suivant : #if GINT_RENDER_MONO /* La taille de la balle. */ #define BALL_SIZE 1 #elif GINT_RENDER_RGB && GINT_HW_CG /* La taille de la balle. */ #define BALL_SIZE 3 #else #error "Unsupported!" #endif Codez `ball_draw` en utilisant `BALL_SIZE`. Ma fonction `ball_draw` est la suivante : void ball_draw(struct ball *ball) { /* Get the position of the ball on screen. */ int screen_x = TO_INT(ball->x); int screen_y = TO_INT(ball->y); /* Draw a rectangle and fill it to make the ball more visible. */ drect(screen_x-BALL_SIZE, screen_y-BALL_SIZE, screen_x+BALL_SIZE, screen_y+BALL_SIZE, C_BLACK); } Il est maintenant temps de tester si notre nouveau code fonctionne bien ! Pour cela, ajoutons une balle à `struct game`. Ma `struct game` avec cet ajout : struct game { /* La balle. */ struct ball ball; /* La raquette du joueur. */ struct paddle paddle1; /* La raquette de l'ordinateur. */ struct paddle paddle2; /* L'etat dans lequel se trouve le jeu. */ unsigned char state; }; Appelez `ball_init` dans `game_init`. Définissons une macro – `DEFAULT_SPEED` – dans `ball.h`, contenant la vitesse de la balle au début d'une partie. Définitions de macros dans mon fichier `ball.h` : #if GINT_RENDER_MONO /* La vitesse de la balle par defaut. */ #define DEFAULT_SPEED 0.2 /* La taille de la balle. */ #define BALL_SIZE 1 #elif GINT_RENDER_RGB && GINT_HW_CG /* La vitesse de la balle par defaut. */ #define DEFAULT_SPEED 0.6 /* La taille de la balle. */ #define BALL_SIZE 3 #else #error "Unsupported!" #endif Ma fonction `game_init` avec cet ajout : void game_init(struct game *game) { /* Initialisation de la balle et initialisation de sa direction pour un * deplacement en direction du joueur. */ ball_init(&game->ball, TO_FIXED(-1), TO_FIXED(1), TO_FIXED(DEFAULT_SPEED)); /* Initialisation des raquettes aux deux cotes de l'ecran. */ paddle_init(&game->paddle1, 16); paddle_init(&game->paddle2, DWIDTH-16-PADDLE_WIDTH); game->state = S_INGAME; } NOTE : Notre vecteur de direction dans laquelle la balle se déplace n'est pas normalisé, et la vitesse de la balle diffèrera un peu avec la direction dans laquelle la balle se déplace. Ce n'est pas trop grave dans ce jeu, et cela évite de devoir faire des calculs un peu plus compliqués. Appelez `ball_draw` dans `game_draw`. Ma fonction `game_draw` : void game_draw(struct game *game) { switch(game->state){ case S_INGAME: /* Dessin des raquettes. */ paddle_draw(&game->paddle1); paddle_draw(&game->paddle2); /* Dessin de la balle. */ ball_draw(&game->ball); break; default: /* Cela ne devrait jamais arriver. */ game->state = S_INGAME; } } Testez le jeu. Est-ce que votre jeu compile sans erreurs ? Est-ce que vous voyez bien une balle au centre de l'écran avec des raquettes des deux côtés de l'écran que vous pouvez déplacer ? Si oui, continuez, sinon cherchez les erreurs dans votre code. Mouvement de la balle et rebonds aux bords de l'écran Nous allons maintenant programmer `ball_move` pour déplacer notre balle. /* Mouvement de la balle. */ char ball_move(struct ball *ball); Nous allons calculer la nouvelle position de la balle en ajoutant à sa position actuelle (`ball->x_inc` × `ball->speed`; `ball->y_inc` × `ball->speed`). Nous ajusterons cette nouvelle position pour faire rebondir la balle sur les bords en haut et en bas de l'écran. Si la balle sort de l'écran à gauche, nous retournons 2 (l'ordinateur marque un point). Si elle sort de l'écran à droite, nous retournons 1 (le joueur marque un point). Sinon nous retournons 0. Cela nous permettra de compter les points. Codez `ball_move`. Voici ma fonction `ball_move` : char ball_move(struct ball *ball) { /* Les nouveaux coordonnés de la balle (à l'écran). */ int dest_x = TO_INT(ball->x+MUL(ball->x_inc, ball->speed)); int dest_y = TO_INT(ball->y+MUL(ball->y_inc, ball->speed)); /* Si dest_x > DWIDTH le joueur marque un point. */ if(dest_x > DWIDTH-BALL_SIZE) return 1; /* Si dest_x < 0 l'ordinateur marque un point. */ if(dest_x < BALL_SIZE) return 2; /* Faire rebondir la balle sur les bords de l'écran. */ if(dest_y < BALL_SIZE){ ball->y_inc = -ball->y_inc; ball->y = TO_FIXED(BALL_SIZE); } if(dest_y > DHEIGHT-BALL_SIZE){ ball->y_inc = -ball->y_inc; ball->y = TO_FIXED(DHEIGHT-BALL_SIZE); } /* Déplacement de la balle. */ ball->x += MUL(ball->x_inc, ball->speed); ball->y += MUL(ball->y_inc, ball->speed); return 0; } Nous allons tester ce nouveau bout de code en appelant dans `game_logic`. Pour l'instant, nous ferrons rebondir la balle sur les bords de l'écran si la valeur renvoyée par `ball_move` est non nulle. Appelez `ball_move` dans `game_logic`. Mon code de `game_logic` : void game_logic(struct game *game) { switch(game->state){ case S_INGAME: { char move; /* Permettre au joueur de deplacer sa raquette. */ paddle_move(&game->paddle1); /* Deplacement de la balle. */ move = ball_move(&game->ball); if(move == 1){ game->ball.x = TO_FIXED(DWIDTH-BALL_SIZE); game->ball.x_inc = -game->ball.x_inc; }else if(move == 2){ game->ball.x = TO_FIXED(BALL_SIZE); game->ball.x_inc = -game->ball.x_inc; } } break; default: /* Cela ne devrait jamais arriver. */ game->state = S_INGAME; } } Testez le jeu. Est-ce que le code compile et est-ce que la balle rebondit bien sur les bords de l'écran ? Rebonds sur la raquette Notre jeu est presque fini (si l'on exclut l'écran de titre et la fin d'une partie) ! Faisons rebondir la balle sur les raquettes ! Pour cela nous allons coder `paddle_will_collide_with_ball` et `ball_bounce`, qui ont les prototypes suivants : /* Verifier si la balle heurte la raquette. */ char paddle_will_collide_with_ball(struct paddle *paddle, struct ball *ball); /* Faire rebondir la balle sur une raquette. */ void ball_bounce(struct ball *ball, struct paddle *paddle); Programmez `paddle_will_collide_with_ball` qui renvoie 1 si la balle va heurter la raquette lors de son déplacement (on ne vérifie pas si elle heurte le haut ou le bas de la raquette pour que notre code soit plus simple). Voici mon implémentation : char paddle_will_collide_with_ball(struct paddle *paddle, struct ball *ball) { /* On calcule la nouvelle abscisse de la balle. */ fixed_t dest_x = ball->x+MUL(ball->x_inc, ball->speed); /* NOTE: Un seul *if statement* serait suffisant, mais le code serait * peut-etre un peu plus dur a comprendre. */ /* Est-ce que la balle est sur la raquette sur l'axe des ordonnees ? */ if(ball->y+TO_FIXED(BALL_SIZE) >= TO_FIXED(paddle->y) && ball->y-TO_FIXED(BALL_SIZE) <= TO_FIXED(paddle->y+PADDLE_HEIGHT)){ /* Est-ce que la balle traverserait la raquette (sur l'axe des * abscisses) ? */ if((ball->x > TO_FIXED(paddle->x+PADDLE_WIDTH+BALL_SIZE) && dest_x <= TO_FIXED(paddle->x+PADDLE_WIDTH+BALL_SIZE)) || (ball->x < TO_FIXED(paddle->x-BALL_SIZE) && dest_x >= TO_FIXED(paddle->x-BALL_SIZE))){ /* The ball will hit the paddle. */ return 1; } } return 0; } NOTE : Je ne calcule pas la nouvelle ordonnée de la balle. Si nous la calculons, la différence ne se remarquerait sûrement pas. Une méthode plus exacte pour vérifier s'il y a une collision serait de vérifier si le segment formé par la position actuelle et la prochaine position de la balle coupe l'un des segments verticaux du rectangle qu'est notre raquette. Dans `ball_bounce` nous allons calculer la nouvelle direction dans laquelle la balle se déplace en fonction de la position où la balle heurte la raquette. Nous allons juste inverser `x_inc`, c'est `y_inc` que nous ajusterons en fonction de la position à laquelle la balle heurte la raquette. Nous allons définir quelque macros pour pouvoir ajuster comment `y_inc` est calculé. `ANGLE_MIN` : La valeur de `y_inc` après que la balle heurte le haut de la raquette `ANGLE_MAX` : La valeur de `y_inc` après que la balle heurte le bas de la raquette `ANGLE_RANGE` : La valeur absolue de la différence entre `ANGLE_MIN` et `ANGLE_MAX` Nous calculerons `y_inc` en effectuant de l'interpolation linéaire entre `ANGLE_MIN` et `ANGLE_MAX` avec la position à laquelle la balle heurte la raquette. Nous allons aussi définir `SPEED_INC` et `MAX_SPEED` pour augmenter la vitesse de la balle à chaque rebond, en la limitant à un certain seuil. Je définis maintenant les macros suivantes dans `ball.h` : #if GINT_RENDER_MONO /* La vitesse de la balle par defaut. */ #define DEFAULT_SPEED 0.2 /* La vitesse maximale de la balle. */ #define MAX_SPEED 2 /* L'augmentation de la vitesse de la balle a chaque rebond. */ #define SPEED_INC 0.1 /* La taille de la balle. */ #define BALL_SIZE 1 #elif GINT_RENDER_RGB && GINT_HW_CG /* La vitesse de la balle par defaut. */ #define DEFAULT_SPEED 0.6 /* La vitesse maximale de la balle. */ #define MAX_SPEED 6 /* L'augmentation de la vitesse de la balle a chaque rebond. */ #define SPEED_INC 0.3 /* La taille de la balle. */ #define BALL_SIZE 3 #else #error "Unsupported!" #endif /* La valeur de y_inc lorque la balle heurte le haut de la raquette. */ #define ANGLE_TOP -1 /* La valeur de y_inc lorque la balle heurte le bas de la raquette. */ #define ANGLE_BOTTOM 1 /* La l'ecart entre les valeurs de y_inc pour le haut et le bas de la * raquette. */ #define ANGLE_RANGE 2 Codez maintenant `ball_bounce`. Voici mon implémentation de `ball_bounce` : void ball_bounce(struct ball *ball, struct paddle *paddle) { /* Linear interpolation to calculate the new angle of the ball. */ int pos_on_the_paddle = ball->y-TO_FIXED(paddle->y); /* Clamp pos_on_the_paddle between 0 and TO_FIXED(PADDLE_HEIGHT). */ if(pos_on_the_paddle < 0){ pos_on_the_paddle = 0; }else if(pos_on_the_paddle > TO_FIXED(PADDLE_HEIGHT)){ pos_on_the_paddle = TO_FIXED(PADDLE_HEIGHT); } /* Perform the linear interpolation. */ ball->y_inc = TO_FIXED(ANGLE_TOP)+DIV(ANGLE_RANGE*pos_on_the_paddle, TO_FIXED(PADDLE_HEIGHT)); /* Flip ball->x_inc's sign to have it going towards the other paddle. */ ball->x_inc = -ball->x_inc; /* Increase the ball speed because we hit a paddle. */ if(ball->speed < TO_FIXED(MAX_SPEED)){ ball->speed += TO_FIXED(SPEED_INC); } } Nous allons ensuite utiliser nos deux nouvelles fonctions dans `game_logic`. Faites rebondir la balle sur les raquettes et réinitialisez la position de la balle lorsqu'un joueur marque un point. Si le joueur marque, elle ira en direction de l'ordinateur. Sinon elle ira en direction de la raquette controllée par la calculatrice. Voici ma fonction `game_logic` maintenant : void game_logic(struct game *game) { switch(game->state){ case S_INGAME: { char move; /* Permettre au joueur de deplacer sa raquette. */ paddle_move(&game->paddle1); /* Si la balle heurte la raquette du joueur, on la fait * rebondir. */ if(paddle_will_collide_with_ball(&game->paddle1, &game->ball)){ ball_bounce(&game->ball, &game->paddle1); } /* Si la balle heurte la raquette de l'ordinateur, on la fait * rebondir. */ if(paddle_will_collide_with_ball(&game->paddle2, &game->ball)){ ball_bounce(&game->ball, &game->paddle2); } /* Deplacement de la balle. */ move = ball_move(&game->ball); if(move == 1){ /* On remet la balle au milieu de l'ecran et on l'envoie en * direction de l'ordinateur. */ ball_init(&game->ball, TO_FIXED(1), TO_FIXED(1), TO_FIXED(DEFAULT_SPEED)); }else if(move == 2){ /* On remet la balle au milieu de l'ecran et on l'envoie en * direction du joueur. */ ball_init(&game->ball, TO_FIXED(-1), TO_FIXED(1), TO_FIXED(DEFAULT_SPEED)); } } break; default: /* Cela ne devrait jamais arriver. */ game->state = S_INGAME; } } Testez le jeu, vérifiez qu'il compile bien et que vous pouvez renvoyer la balle. Mouvement de la raquette de l'ordinateur. La raquette à droite ne se déplace pas pour l'instant. Corrigeons ça pour que la calculatrice joue contre nous. Nous allons écrire ce code dans la fonction `paddle_computer_move` : /* Déplacement de la raquette par l'ordinateur. */ void paddle_computer_move(struct paddle *paddle, struct ball *ball); Nous déplacerons la raquette vers le bas, si cela permet au milieu de la raquette de se rapprocher de la balle, ou vers le haut, si cela rapproche le milieu de la raquette de la balle. Nous ferrons attention que la raquette reste bien à l'intérieur de l'écran. Programmez `paddle_computer_move`. Mon implémentation de cette fonction : void paddle_computer_move(struct paddle *paddle, struct ball *ball) { /* Distance between the center of the paddle and the ball on the y axis. */ int distance = abs(TO_INT(ball->y)-(paddle->y+PADDLE_HEIGHT/2)); /* Move the paddle up if it reduces its distance to the ball. */ if(abs(TO_INT(ball->y)- (paddle->y+PADDLE_HEIGHT/2-PADDLE_SPEED)) < distance){ if(paddle->y-PADDLE_SPEED >= 0){ paddle->y -= PADDLE_SPEED; }else{ paddle->y = 0; } }else if(abs(TO_INT(ball->y)- (paddle->y+PADDLE_HEIGHT/2+PADDLE_SPEED)) < distance){ /* Move the paddle down if the ball y position is bigger than the * paddle y position. */ if(paddle->y+PADDLE_SPEED < DHEIGHT-PADDLE_HEIGHT){ paddle->y += PADDLE_SPEED; }else{ paddle->y = DHEIGHT-PADDLE_HEIGHT-1; } } } Appelez cette fonction dans `game_logic`. Voici à quoi ressemble ma fonction `game_logic` maintenant : void game_logic(struct game *game) { switch(game->state){ case S_INGAME: { char move; /* Permettre au joueur de deplacer sa raquette. */ paddle_move(&game->paddle1); /* Deplacement de la raquette de l'ordinateur. */ paddle_computer_move(&game->paddle2, &game->ball); /* Si la balle heurte la raquette du joueur, on la fait * rebondir. */ if(paddle_will_collide_with_ball(&game->paddle1, &game->ball)){ ball_bounce(&game->ball, &game->paddle1); } /* Si la balle heurte la raquette de l'ordinateur, on la fait * rebondir. */ if(paddle_will_collide_with_ball(&game->paddle2, &game->ball)){ ball_bounce(&game->ball, &game->paddle2); } /* Deplacement de la balle. */ move = ball_move(&game->ball); if(move == 1){ /* On remet la balle au milieu de l'ecran et on l'envoie en * direction de l'ordinateur. */ ball_init(&game->ball, TO_FIXED(1), TO_FIXED(1), TO_FIXED(DEFAULT_SPEED)); }else if(move == 2){ /* On remet la balle au milieu de l'ecran et on l'envoie en * direction du joueur. */ ball_init(&game->ball, TO_FIXED(-1), TO_FIXED(1), TO_FIXED(DEFAULT_SPEED)); } } break; default: /* Cela ne devrait jamais arriver. */ game->state = S_INGAME; } } Testez le jeu. Maintenant, nous avons enfin un jeu de pong où l'on peut s'échanger la balle avec la raquette de droite ! Il ne reste plus qu'à compter les points, ajouter un écran de titre et un écran de fin pour afficher qui a gagné, et le jeu est fini ! `game.c` : comptage des points, écran de fin et écran de titre Comptage des points Pour compter les points marqués par le joueur ou l'ordinateur, nous allons ajouter deux membres à `struct game` : `score1` et `score2`. Ils auront le type `unsigned char`, car nos scores ne dépasseront pas 255. Voici ma `struct game` après ces ajouts : struct game { /* La balle. */ struct ball ball; /* La raquette du joueur. */ struct paddle paddle1; /* La raquette de l'ordinateur. */ struct paddle paddle2; /* Le score du joueur. */ unsigned char score1; /* Le score de l'ordinateur. */ unsigned char score2; /* L'etat dans lequel se trouve le jeu. */ unsigned char state; }; `score1` et `score2` seront initialisés à 0 dans `game_init`. Mon implémentation de `game_init` : void game_init(struct game *game) { /* Initialisation de la balle et initialisation de sa direction pour un * deplacement en direction du joueur. */ ball_init(&game->ball, TO_FIXED(-1), TO_FIXED(1), TO_FIXED(DEFAULT_SPEED)); /* Initialisation des raquettes aux deux cotes de l'ecran. */ paddle_init(&game->paddle1, 16); paddle_init(&game->paddle2, DWIDTH-16-PADDLE_WIDTH); /* Initialisation du score a 0. */ game->score1 = 0; game->score2 = 0; game->state = S_INGAME; } Nous allons définir `MAX_SCORE`, le score maximal que l'on peut atteindre dans `game.h`. Par exemple : #define MAX_SCORE 11 Dans notre enumération d'états du jeu dans `game.h` nous allons ajouter `S_END`, pour la fin d'une partie, où le gagnant est affiché, et `S_TITLE` pour l'écran de titre. Voici mon enumération : /* The screens. */ enum { S_TITLE, S_INGAME, S_END, S_AMOUNT }; Nous allons incrémenter le score du joueur ou celui de l'ordinateur dans `game_logic` lorsqu'un point est marqué. Si le score dépasse `SCORE_MAX`, nous passerons à l'état `S_END`. Voici ma fonction `game_logic` maintenant : void game_logic(struct game *game) { switch(game->state){ case S_INGAME: { char move; /* Permettre au joueur de deplacer sa raquette. */ paddle_move(&game->paddle1); /* Deplacement de la raquette de l'ordinateur. */ paddle_computer_move(&game->paddle2, &game->ball); /* Si la balle heurte la raquette du joueur, on la fait * rebondir. */ if(paddle_will_collide_with_ball(&game->paddle1, &game->ball)){ ball_bounce(&game->ball, &game->paddle1); } /* Si la balle heurte la raquette de l'ordinateur, on la fait * rebondir. */ if(paddle_will_collide_with_ball(&game->paddle2, &game->ball)){ ball_bounce(&game->ball, &game->paddle2); } /* Deplacement de la balle. */ move = ball_move(&game->ball); if(move == 1){ /* Le joueur a un point. */ game->score1++; /* On remet la balle au milieu de l'ecran et on l'envoie en * direction de l'ordinateur. */ ball_init(&game->ball, TO_FIXED(1), TO_FIXED(1), TO_FIXED(DEFAULT_SPEED)); /* Si le joueur a eu le score maximal, il gagne, et on va a * l'ecran de fin de partie. */ if(game->score1 >= MAX_SCORE) game->state = S_END; }else if(move == 2){ /* L'ordinateur a un point. */ game->score2++; /* On remet la balle au milieu de l'ecran et on l'envoie en * direction du joueur. */ ball_init(&game->ball, TO_FIXED(-1), TO_FIXED(1), TO_FIXED(DEFAULT_SPEED)); /* Si l'ordinateur a eu le score maximal, il gagne, et on * va a l'ecran de fin de partie. */ if(game->score2 >= MAX_SCORE) game->state = S_END; } } break; default: /* Cela ne devrait jamais arriver. */ game->state = S_INGAME; } } Dans `game_draw` nous allons afficher le score avec `dprint`. `dprint` a le prototype suivant : void dprint(int x, int y, int fg, char const *format, ...); Le format est pareil que pour `printf`. Voici comment j'ai affiché le score dans `game_draw` : /* Affichage du score du joueur. */ dprint(DWIDTH/2-8-5*3, 8, C_BLACK, "%d", game->score1); /* Affichage du score de l'ordinateur. */ dprint(DWIDTH/2+8, 8, C_BLACK, "%d", game->score2); Testez le jeu, et vous devriez voir le score changer en haut de votre écran. Écran de fin Pour l'écran de fin, dans `game_logic`, nous allons vérifier si `KEY_SHIFT` est pressée. Nous allons utiliser `keypressed`, qui permet de savoir si une touche a été enfoncée depuis la dernière image (ici nous pourrions aussi utiliser `keydown`). Si `KEY_SHIFT` est pressée, nous allons aller à l'écran de titre que nous coderons plus tard. Gérez l'écran de fin de partie dans `game_logic`. Voici mon code : case S_END: /* Si shift est pressee, aller a l'ecran de titre. */ if(keypressed(KEY_SHIFT)){ game->state = S_TITLE; } break; Dans `game_draw` nous allons afficher qui a gagné, tout en affichant la balle et les raquettes avec les scores. Nous ne mettrons donc pas de `break` après avoir traité le cas `S_END`, au-dessus du code pour traiter le cas où `game->state` est égal à `S_INGAME`, pour continuer dans le code pour dessiner la balle, les raquettes et les scores. NOTE : Les raquettes, les scores et la balle sont donc dessinés au-dessus de ce que l'on dessine pour le cas où `game->state` est égal à `S_INGAME`. Pour éviter un avertissement, il suffit d'ajouter le commentaire `/* FALLTHRU */`. Pour afficher qui a gagné, nous utiliserons `dtext` pour afficher du texte. Voici son prototype dans `<gint/display.h>` : void dtext(int x, int y, int fg, char const *str); Affichez qui a gagné. Voici comment j'ai procédé : J'ai utilisé des macros pour calculer où placer le texte : #define WIN "YOU WIN!" #define GAME_OVER "GAME OVER" /* La position du texte GAME_OVER sur l'axe des abscisses. */ #define GAME_OVER_X (DWIDTH/2-5*sizeof(GAME_OVER)/2) /* La position du texte WIN sur l'axe des abscisses. */ #define WIN_X (DWIDTH/2-5*sizeof(WIN)/2) /* La position du texte WIN ou GAME_OVER sur l'axe des ordonnees. */ #define END_Y (DHEIGHT/2-7/2) Voici ma fonction `game_draw` : void game_draw(struct game *game) { switch(game->state){ case S_END: if(game->score1 > game->score2){ /* Le joueur a gagne, donc on l'affiche. */ dtext(WIN_X, END_Y, C_BLACK, WIN); }else{ /* Le joueur a perdu, on l'affiche. */ dtext(GAME_OVER_X, END_Y, C_BLACK, GAME_OVER); } /* Do not break here to draw the in-game screen. */ /* FALLTHRU */ case S_INGAME: /* Affichage du score du joueur. */ dprint(DWIDTH/2-8-5*3, 8, C_BLACK, "%d", game->score1); /* Affichage du score de l'ordinateur. */ dprint(DWIDTH/2+8, 8, C_BLACK, "%d", game->score2); /* Dessin des raquettes. */ paddle_draw(&game->paddle1); paddle_draw(&game->paddle2); /* Dessin de la balle. */ ball_draw(&game->ball); break; default: /* Cela ne devrait jamais arriver. */ game->state = S_INGAME; } } Testez, est-ce que l'écran de fin s'affiche correctement ? Écran de titre Nous allons maintenant coder la dernière chose manquante à notre jeu : l'écran de titre. Normalement vous avez déjà réalisé des jolis écrans de titre : un de 128 × 64 pour les calculatrices monochromes, et un autre d'une dimension de 396 × 224 pixels pour les calculatrices couleur. la variable qui contient les données de cette image (qui est différente selon la plateforme) s'appelle `title_img` comme nous l'avons indiqué dans `fxconv_metadata.txt`. Pour pouvoir y accéder, il suffit d'ajouter la ligne suivante à `game.c` : extern const bopti_image_t title_img; Ensuite, dans `game_draw`, lorsque `game->state` est égal à `S_TITLE`, nous allons remplir l'écran avec cette image en utilisant `dimage`. Le prototype de `dimage` dans `<gint/display.h>` : void dimage(int x, int y, bopti_image_t const *image); Gérez le cas où `game->state` est égal à `S_TITLE` dans `game_draw`. Voici mon implémentation : void game_draw(struct game *game) { switch(game->state){ case S_TITLE: /* Affichage de l'image de l'écran de titre. */ dimage(0, 0, &title_img); break; case S_END: if(game->score1 > game->score2){ /* Le joueur a gagne, donc on l'affiche. */ dtext(WIN_X, END_Y, C_BLACK, WIN); }else{ /* Le joueur a perdu, on l'affiche. */ dtext(GAME_OVER_X, END_Y, C_BLACK, GAME_OVER); } /* Do not break here to draw the in-game screen. */ /* FALLTHRU */ case S_INGAME: /* Affichage du score du joueur. */ dprint(DWIDTH/2-8-5*3, 8, C_BLACK, "%d", game->score1); /* Affichage du score de l'ordinateur. */ dprint(DWIDTH/2+8, 8, C_BLACK, "%d", game->score2); /* Dessin des raquettes. */ paddle_draw(&game->paddle1); paddle_draw(&game->paddle2); /* Dessin de la balle. */ ball_draw(&game->ball); break; default: /* Cela ne devrait jamais arriver, mais si cela arrive, nous * retournons a l'ecran de titre. */ game->state = S_TITLE; } } NOTE : J'ai changé l'état auquel nous allons lorsque `game->state` contient une valeur inattendue : on va maintenant à l'écran de titre. Traitons ce cas aussi dans `game_logic`. Si `KEY_SHIFT` est pressée, nous allons réinitialiser le jeu (avec `game_init`) et passer à l'état `S_INGAME`. NOTE : Ici nous devons utiliser `keypressed` (ou `keyreleased`) : Lorsque le jeu se trouve à l'état `S_END`, et que le joueur appuie sur shift, le jeu passe à l'état `S_TITLE` et il est très probable que le joueur n'ait pas encore relaché la touche, donc `keydown` retournerait `true`. Le jeu passerait donc directement à l'état `S_INGAME` (après avoir appelé `game_init`). Gérez le passage de l'écran de titre à l'état `S_INGAME`. Voici mon implémentation : void game_logic(struct game *game) { switch(game->state){ case S_TITLE: /* On attend que la touch shift soit pressee pour lancer une * partie. */ if(keypressed(KEY_SHIFT)){ /* Reinitialisation du jeu. */ game_init(game); game->state = S_INGAME; } break; switch(game->state){ case S_INGAME: { char move; /* Permettre au joueur de deplacer sa raquette. */ paddle_move(&game->paddle1); /* Deplacement de la raquette de l'ordinateur. */ paddle_computer_move(&game->paddle2, &game->ball); /* Si la balle heurte la raquette du joueur, on la fait * rebondir. */ if(paddle_will_collide_with_ball(&game->paddle1, &game->ball)){ ball_bounce(&game->ball, &game->paddle1); } /* Si la balle heurte la raquette de l'ordinateur, on la fait * rebondir. */ if(paddle_will_collide_with_ball(&game->paddle2, &game->ball)){ ball_bounce(&game->ball, &game->paddle2); } /* Deplacement de la balle. */ move = ball_move(&game->ball); if(move == 1){ /* Le joueur a un point. */ game->score1++; /* On remet la balle au milieu de l'ecran et on l'envoie en * direction de l'ordinateur. */ ball_init(&game->ball, TO_FIXED(1), TO_FIXED(1), TO_FIXED(DEFAULT_SPEED)); /* Si le joueur a eu le score maximal, il gagne, et on va a * l'ecran de fin de partie. */ if(game->score1 >= MAX_SCORE) game->state = S_END; }else if(move == 2){ /* L'ordinateur a un point. */ game->score2++; /* On remet la balle au milieu de l'ecran et on l'envoie en * direction du joueur. */ ball_init(&game->ball, TO_FIXED(-1), TO_FIXED(1), TO_FIXED(DEFAULT_SPEED)); /* Si l'ordinateur a eu le score maximal, il gagne, et on * va a l'ecran de fin de partie. */ if(game->score2 >= MAX_SCORE) game->state = S_END; } } break; default: /* Cela ne devrait jamais arriver, mais si cela arrive, nous * retournons a l'ecran de titre. */ game->state = S_TITLE; } } NOTE : J'ai changé l'état auquel nous allons lorsque `game->state` contient une valeur inattendue : on va maintenant à l'écran de titre. Corrigez maintenant `game_init` pour se trouver à l'écran de titre au lancement du jeu. Ma fonction `game_init` : void game_init(struct game *game) { /* Initialisation de la balle et initialisation de sa direction pour un * deplacement en direction du joueur. */ ball_init(&game->ball, TO_FIXED(-1), TO_FIXED(1), TO_FIXED(DEFAULT_SPEED)); /* Initialisation des raquettes aux deux cotes de l'ecran. */ paddle_init(&game->paddle1, 16); paddle_init(&game->paddle2, DWIDTH-16-PADDLE_WIDTH); /* Initialisation du score a 0. */ game->score1 = 0; game->score2 = 0; game->state = S_TITLE; } Testez, et vérifiez que tout fonctionne correctement. Le jeu est fini. Le mot de la fin Bravo ! Vous êtes arrivés au bout de ce tutoriel ! Vous avez développé tout un jeu avec gint ! Si vous cherchez des informations sur des fonctions de gint, l'un des meilleures ressources sont les en-têtes de gint, elles sont très bien documentées. Les topics de gint, du fxsdk et le tutoriel officiel d'utilisation de gint sont aussi très utiles : Le topic de gint : https://www.planet-casio.com/Fr/forums/topic13572-74-gint-un-noyau-pour-developper-des-add-ins.html Le topic du fxsdk : https://www.planet-casio.com/Fr/forums/topic13164-last-fxsdk-un-sdk-alternatif-pour-ecrire-des-add-ins.html Le tutoriel d'utilisation de gint : https://www.planet-casio.com/Fr/forums/topic14914-1-tutoriels-dutilisation-de-gint.html Et un grand merci à Lephé pour avoir développé ce super SDK ! Sun, 19 Jul 2026 20:47:21 +0200 Crosswords for extrapython https://www.planet-casio.com/Fr/forums/topic19639--.html As a crosswords fan, my second game in python is a crosswords program for Casio 90+E/fx-CG. https://i.imgur.com/GVGqGRU.jpeg Here is the link It wasn't easy getting a good system to view the clues, but I think it turned out pretty good. I made a dark mode and legacy mode, plus there are 100s of levels split into easy medium and hard. There's also a timer, and all progress is saved automatically. It runs on the extrapython add-in, thank you to Lephe and everyone involved in that. For future updates I'm thinking about different color themes. Let me know any feedback and what features you think I can add. Sat, 18 Jul 2026 20:58:36 +0200 Lumines - Clone du grand classique de la PSP https://www.planet-casio.com/Fr/forums/topic19624--.html Avec cette canicule qui empêche de faire ce que l'on veut à l'extérieur, il est temps de ressortir les anciens projets commencés et jamais terminés. Et bien voici un très bon exemple, j'avais fait un clone de Lumines qui tournait (mal, lentement) sur fxCG50 avec la cSDL. Et j'avais oublié de me remettre dessus. Voici donc qui corrige, avec ce port tournant cette fois sur Azur. C'est là que ça se passe : Lumines pour fxCG à télécharger Le mode pattern ne fonctionne pas encore très bien, mais c'est je pense assez à ce stade pour s'amuser un peu, je ferai des MaJ plus tard. https://youtu.be/0Ess1E23E74 @RdP quand il y aura Mon, 13 Jul 2026 08:19:29 +0200 Bien se préparer au CPC #33 https://www.planet-casio.com/Fr/forums/topic19618--.html Bonjour à toutes et tous ! Le CPC#33 est en approche et dans environ deux semaines vous pourrez vous lancer à corps perdu dans cette 33ᵉ édition du concours de Planète Casio ! Le thème sera annoncé le dimanche 26 juillet 2026 à 18h. Une semaine de code pour fournir un jeu, c'est court et c'est ce qui fait tout l'intérêt du concours. Pour le CPC#33 tous les langages sont autorisés ! Basic, C/C++, Python, etc prenez celui que vous préférez. Passons ensemble en revue quelques conseils pour bien se préparer et éviter de paniquer une fois dans le rush. ;) :here: Planifie ton temps : Divise ton temps entre recherche, planification, développement, tests et ajustements/débogage. Par exemple, consacre les deux premiers jours à la recherche de l'idée et à la planification de ce que tu veux mettre en place. Cela te permettra de ne pas partir dans toutes les directions et de perdre du temps sur des futilités. Si tu n'as pas trop l'habitude de coder, cette première phase peut tout à fait être faite sur papier afin de bien poser les fondamentaux. :here: Révise les bases et les bibliothèques spécifiques : Assure-toi de bien connaître les bibliothèques spécifiques à ta calculatrice. Par exemple, apprends à utiliser les bibliothèques disponibles sur ta Casio. S'il n'y a pas de fonction pour tracer des lignes ou des cercles, peut-être peux-tu programmer ça avant le début du concours pour les avoir sous la main le jour J. Par exemple si tu codes en Python, fait quelques tests avec le module `gint` de PythonExtra pour connaitre les fonctions de gestion des images et du clavier. Si tu te lances dans un addin en C ou C++, de même, regarde comment fonctionne `libprof` (désormais intégré dans `gint`) pour mesurer les performances de ton code, ou azur afin de booster les perfs graphiques, mais surtout ne te lance pas dans un truc que tu n'as jamais tenté auparavant durant les semaines du concours. :here: Optimise ton code : Écris du code efficace pour économiser la mémoire et le temps de calcul. Par exemple, utilise des structures de données appropriées et évite les boucles inutiles. Cela est particulièrement vrai sur les machines disposant de peu de ressources comme la Graph 35+EII. Python offre beaucoup de fonctions et de structures qui te feront gagner beaucoup de temps et d'énergie. Il y a toujours moyen de choper de la RAM ou de la puissance de calcul avec des tricks, en particulier sur Graph 90+E et Math+, mais c'est mieux si ton code tourne dans bien en conditions de base. Avec Python en particulier, sois bien attentif à l'utilisation des ressources (images, fontes), qui peuvent sévèrement faire monter l'addition de consommation de RAM. Sois donc très malin dans ton approche et réutilise tes assets au maximum. :here: Pratique les entrées/sorties : Familiarise-toi avec les méthodes d'entrée et de sortie sur la calculatrice. Par exemple, pratique la lecture des entrées utilisateur et l'affichage des résultats. Cela te sera aussi très utile pour déboguer ton programme. Entre les sorties textes ou graphiques, être en capacité de "discuter" avec ton programme lors de son fonctionnement est clairement un point très important et très souvent négligé. Si tu codes en C, je t'invite à regarder le fonctionnement du bridge gdb afin de déboguer ton programme en cours de run sur la calculatrice. :here: Prépare des snippets réutilisables : Crée des morceaux de code pour les tâches courantes. Par exemple, un snippet pour lire une entrée utilisateur et afficher un message. C'est la clé, soyez malins (ou malignes). Des petites routines graphiques, de lecture des actions claviers, déjà disponibles vous feront gagner un temps fou et vous éviteront de chercher comment faire à la dernière minute et donc de perdre un temps précieux. :here: Teste fréquemment : Teste régulièrement ton code sur la calculatrice pour détecter et corriger les erreurs rapidement. Par exemple, après chaque fonctionnalité ajoutée, vérifie son bon fonctionnement. Rien de pire que de se rendre compte après deux jours de code que rien ne fonctionne et de devoir scrapper une grosse partie de son travail. En testant régulièrement, on peut beaucoup plus vite réorienter si besoin et minimiser les pertes. Si je dois insister sur un seul conseil, c'est bien celui-ci. Chaque brique élémentaire doit bien être testée avant de passer à la suivante, c'est le nerf de la guerre. Rien de pire qu'un bug fantôme qui fait perdre deux jours de code car fait tout planter sans que l'on comprenne pourquoi. Note : même en testant bien, on n'est jamais à l'abris de ce genre de bugs, mais au moins, on minimise le risque d'apparition. :here: Fixe des objectifs quotidiens : Établis des objectifs clairs pour chaque jour pour maintenir un rythme constant. Par exemple, terminer la fonctionnalité principale le troisième jour. Cela permet d'avoir en tête un planning "idéal" mais aussi finalement de classer l'importance de tel ou tel aspect du jeu. Il est conseillé aussi de commencer petit avec un squelette fonctionnel de programme qui colle au gameplay minimal, puis d'ajouter des fonctionnalités les unes après les autres, à chaque fois en testant bien. :here: Fais des backups réguliers : Sauvegarde ton travail régulièrement pour éviter de perdre des données. Par exemple, fais une sauvegarde après chaque session de coding. Ne vous faites pas avoir par un crash ou un écrasement involontaire de portions de code. Git, archives compressées, répertoire par version, peu importe, mais ne vous faites pas avoir en perdant "bêtement" votre code. On l'a tous vécu et c'est très très rageant. :here: Trouve de beaux assests : Souvent les assets constituent la partie "creuse" du développement, en effet n'est pas graphiste qui veut. Les assets sont la première chose que l'on voit dans un programme, avant même que celui-ci ne tourne et ne révèle sont gameplay. Il existe sur internet une foultitude de ressources libres de droits, je vous invite donc à chercher du côté par exemple de OpenGameArt.com ou Itch.io pour les assets graphiques ou dafont.com ou 1001freefonts.com pour les polices par exemple. Souvent un simple crédit permet d'utiliser les assets gratuitement pour des jeux non commerciaux. Il existe bien entendu de nombreuses ressources de ce style sur Internet. Par contre, soyez fairs, indiquez quand vos assets ne sont pas maisons et créditez l'auteur pour son travail. Et voilà qui conclu ce court article ! On espère vous voir nombreuses et nombreux à participer à ce CPC#33. On se retrouve donc rapidement pour la divulgation du thème et le top départ pour la session intense de code. On se retrouve très bientôt pour une Revue des Projets, puis pour le lancement du concours. ! Un grand merci à SlyVTT pour l'aide niveau organisation et la rédaction de cet article. Sat, 11 Jul 2026 12:03:00 +0200 IA local sur la Graph 90+E https://www.planet-casio.com/Fr/forums/topic19617--.html Salut la communauté, ça fait plusieurs jours que je code une IA sur la Graph 90+E, et pour l'instant c'est franchement pas mal appart deux trois phrase qui sont encore en mode préenregistrer, et je vais y remédier, en tout cas si vous voulez je poste ça dans quelque jours, mais attention le projet entier fait déjà 8,15 Mo !!! Après ce n'est pas étonnant par le fait que c'est une IA local, en tout cas bonne journée a vous et je termine ce programme qans quelque jour. finissions Thu, 09 Jul 2026 09:57:56 +0200 Port de Flashback : A Quest for Identity sur fxCG50 / Graph Math+.... la mémoire revient https://www.planet-casio.com/Fr/forums/topic19602--.html Salut la Bande ! Cette image : vous la connaissez ? https://imgur.com/ZVwIpDZ.png Vous vous souvenez de Conrad ? Ce type qui se réveille amnésique sur une moto volante, en pleine jungle de Titan, sans la moindre idée de qui veut sa peau (spoiler : à peu près tout le monde) ? Eh bien il a posé ses valises dans votre calculatrice. Après Another World, voici Flashback sur fx-CG50 / Graph Math+ avec gint. Flashback, c'est quoi pour les jeunes du fond (les mêmes que ceux qui ne suivaient déjà pas pour Another World) ?!? En 1992, Delphine Software sort Flashback: The Quest for Identity. Dans la lignée directe d'Another World, mais en plus ambitieux : décors fouillés, animations en rotoscopie d'une fluidité hallucinante pour l'époque, cinématiques à gogo, et un vrai inventaire. On y incarne Conrad B. Hart, agent amnésique qui doit retrouver la mémoire et déjouer un complot extraterrestre — le tout en sautant, en roulant, en dégainant son pistolet et en mourant beaucoup. À sa sortie, ça a mis tout le monde d'accord : c'est resté longtemps l'un des jeux français les plus vendus de l'histoire. Et 30 ans plus tard, ça tient encore sacrément la route. https://youtu.be/ar0Yie7WHc0 Vidéo capturée sur MQ, c'est plus rapide sur calculatrice IRL. Pourquoi Flashback après Another World ? Parce que c'était… logique. Côté code, je suis reparti de la réimplémentation libre REminiscence, signée Gregory Montoir — le même homme dont la version « raw » d'Another World m'avait servi de base pour le premier portage. Et là, jackpot : 90 % du boulot était déjà fait. Le backend gint que j'avais bâti pour Another World — gestion de l'écran 396×224, conversion des couleurs en RGB565, overclock au boot, sauvegarde/chargement d'état, lecture des données depuis le stockage — se remontait quasiment tel quel sous le moteur de Flashback. Même philosophie : un moteur de jeu proprement séparé de l'affichage, donc un terrain de jeu idéal pour gint. Restait surtout à brancher le système d'entrées/sorties spécifique et à gérer les fichiers de données du jeu. Le gros de la plomberie, lui, était déjà en place. Ce qui tourne sur la bestiole - Le moteur REminiscence, fidèle : mêmes niveaux, mêmes énigmes, mêmes cinématiques. - 16 couleurs converties en RGB565, image calée sur le 396×224. - Pas de son — choix assumé (la fx-CG50 n'a pas encore les épaules), et le jeu se passe très bien des bruitages sur calculatrice. Maybe One Day (R), mais on aura un petit problème de place sur la flash alors ... - Sauvegarde / chargement d'état avec plusieurs slots (parce que oui, vous allez mourir). - Données allégées : les fichiers purement audio (inutiles ici) sont retirés à l'extraction. Votre stockage vous remercie. - Installation au choix : une archive unique Flashback.PAK (le plus simple), ou les fichiers en clair dans FB\DATA\. Un petit script fait le tri tout seul et vous demande ce que vous préférez. https://imgur.com/xCAYzQi.png Les commandes - Flèches — Déplacement - SHIFT (maintenu) — Courir - ALPHA — Dégainer / tirer - OPTN — Action / utiliser / valider - VARS — Inventaire / passer une cinématique / retour - EXIT — Retour / passer l'intro - MENU — Menu en jeu - F1 / F2 — Sauvegarder / charger - F3 / F4 — Slot suivant / précédent - F6 — Quitter Petit mot sur les données Contrairement à Another World, l'add-in ne contient que le moteur : les données de Flashback restent la propriété de leurs ayants droit et donc ne sont pas libres et ne sont pas distribuées. Procurez-vous une copie légale de la version PC / MS-DOS (via une réédition officielle, par exemple), récupérez son dossier DATA, et laissez le script d'extraction fourni vous fabriquer le tout pour la calculatrice. Honnêtement c'est pas dur à trouver et le jeu est a 0.99€ sur GOG !! Pour l'addin et le script de fabrication du datapack, c'est là que ça se trouve : Flashback pour fxCG50 Amusez-vous bien — et méfiez-vous des mutants. 👽 Tue, 30 Jun 2026 21:10:55 +0200 What are the CFX models? https://www.planet-casio.com/Fr/forums/topic19597--.html Heya, I was wondering if anybody knew anything about the CFX models of Casio calculators? I saw a listing online that has it for a seemingly reasonable price, and I wanted to know what its programming capabilities are like. It seems to have a built-in programming application, presumably for Basic programs, but I'm curious if there's some way of making my own add-ins for it; after all, the multi-color screen sounds pretty sweet for games. Sun, 28 Jun 2026 06:35:41 +0200 Port d'Another World sur fxCG50 / Graph Math + ... Je rêvais d'un autre monde .... https://www.planet-casio.com/Fr/forums/topic19596--.html Salut la compagnie ! « Je rêvais d'un autre monde… » — eh bien cet autre monde, il tient maintenant dans votre calculatrice. Vous savez ce qui manquait à votre Graph ? Un alien qui vous désintègre à la seconde où vous touchez une flaque d'eau. Bonne nouvelle : j'ai porté Another World, le monument d'Eric Chahi (1991), sur fx-CG50 / Graph Math+ avec gint. La voiture qui débarque sous l'orage, l'éclair, la bestiole avec ses petites pattes… tout y est. Le frisson rétro aussi. https://imgur.com/pAs0dz4.png C'est quoi Another World, pour les jeunes du fond ?!? En 1991, Eric Chahi sort dans son coin un jeu qui n'a peur de rien : polygones vectoriels faits maison, animations en rotoscopie, et zéro dialogue, zéro interface, zéro tuto. On balance le joueur dans un monde hostile exactement comme le héros — sans mode d'emploi — et débrouille-toi. https://imgur.com/1tSbc2V.png https://imgur.com/V6FRJfN.png À l'époque, ça a retourné des cerveaux. Aujourd'hui encore c'est une claque, et toute une lignée (Flashback, Heart of Darkness, jusqu'à la mise en scène de Half-Life) lui doit une fière chandelle. https://youtu.be/uWEuMnMpjdQ Côté code, je suis parti de la réimplémentation « raw » de Gregory Montoir (celle que Fabien Sanglard a disséquée dans son célèbre article). Sa machine virtuelle est proprement séparée de l'affichage — autrement dit : un terrain de jeu rêvé pour gint. https://imgur.com/xTBnB7M.png Ce qui tourne sur la bestiole La VM d'origine, intacte. Mêmes scripts, mêmes énigmes, mêmes morts injustes. 16 couleurs converties en RGB565, image centrée sur le 396×224. Pas de son. Choix assumé — la fx-CG50 n'a pas (encore) les épaules pour (mais certains y travaillent), et franchement, le silence d'Another World, c'est la moitié de son charme. Overclock F5 au boot (bus à 94 MHz) : ça dépote, et l'horloge revient sagement à la normale en sortant. Sauvegarde / chargement d'état (parce que oui, vous allez mourir). Codes de niveau d'époque, tapés au clavier. Données allégées : on vire les samples audio (inutiles ici) et on passe de ~1,15 Mo à ~566 Ko. Votre stockage vous dit merci surtout sur Math+ Les commandes Flèches Bouger SHIFT (ou EXE / ALPHA) Action / tir — maintenir + direction = courir OPTN Écran des codes de niveau F1 / F2 Sauvegarder / charger F3 / F4 Slot suivant / précédent F5 Pause EXIT (ou F6) Quitter (lâcheur) Détail de game design 100 % d'époque : Lester marche avec une direction, et court si on garde l'action enfoncée en même temps. Et oui, courir = tirer = le même bouton, faut assumer. SHIFT est grande et confortable à maintenir : c'est votre touche « sprint ». C'est à télécharger là : https://www.planet-casio.com/Fr/programmes/programme4641-1-another-world-slyvtt-jeux-actionsport.html Amusez vous bien. Sat, 27 Jun 2026 14:19:59 +0200 How does the OS decide whether it's a Graph Math+ or an fx-CG100? https://www.planet-casio.com/Fr/forums/topic19593--.html Hi, I own both a UK Casio fx-CG100, and a French Casio Graph Math+ Something curious I've realised is that, despite using the same OS updater exe, they still have small regional differences such as: - Format button behaviour being completely different on the GM+ - The CG100 lets you enable exam mode for the UK, IB, Portugal, and Texas, while the GM+ only lets you select French exam mode - The GM+ has a red LED by the 2.5mm port which flashes red during exam mode, while the CG100 doesn't (the traces are there on the motherboard, but the LED is completely missing) - The CG100 has an option in the System Manager to change the Country of the calculator, letting you select between International, Portugal, UK, and US, while this setting is completely missing on the GM+ - Both calculators allow you to change the language of the OS, with the options being the same Investigating further, I decided to dig into the updater exe a little bit to try and figure out more about the firmware. After a bit of digging, I did manage to extract an 11.9MB binary, which I presume to be the firmware, and a 67.5kB binary, which I presume to be the bootloader. Running the firmware through the strings utility on Linux, I eventually found a section of the firmware with every dialogue for the English language (I presume it's some sort of table, but I'd have to do much more reverse engineering on this firmware file than I have the skill for to figure out how it works). Jumping to the section containing strings pertaining to the Exam Mode menu, you'll curiously find that all five regions - France included - exist here: Exam Mode Manager Enter Exam Mode Unlock Exam Mode Re-Enter Exam Mode Exam Mode Help Unlocked Exam Mode Elapsed Time Apps & Functions How to Exit Status United Kingdom The Netherlands Texas (USA) France Portugal Enter Exam Mode? Entering Exam Mode. While doing this, I did interestingly find that both calculators identify themselves as a CP400 according to lsusb. The CG100 is running version 02.00.0202 with MPM installed, while the GM+ is running version 02.10.2202 Considering that both the GM+ and CG100 use the same updater (and hence the same firmware file), I do believe this points to the calculator itself deciding whether it's a GM+ or a CG100. I suppose the question I want to ask here is how does it decide that? For reference, I have attached an image of what shows up on either calculator when you press "Enter Exam Mode" I do apologise if this question is a little noobish or whatever - I simply find this quite intriguing Thu, 25 Jun 2026 18:43:24 +0200 projet collaboratif et nostalgique pour certains https://www.planet-casio.com/Fr/forums/topic19580--.html Bonjour a tous, c'est SLG4 et j'espère que vous allez tous très bien, et donc je rendre dans le vif du sujet, je m'explique. Récemment, je parlais dans la voiture avec mon père et il m'expliquais que quand il était petit il avais une montre Casio télécommande, qui utilisais les infrarouges pour piloter tout ce qui utilise bahhhhh, l'infrarouge. et sa ma intéresser. j'ai alors regarder le prix mais 600-1000 €, c'est un trop haut budget pour un enfant comme moi. mais comme j'essaye souvent de le faire quand même, je me suis dis qu'on pourrait faire un truc et je vais l'expliquer comme une préparation de pizza. La base : se serait la g-90 ou graph maths+, la garniture serais une esp8266 ou esp32 (que je ne possède pas), un émetteur infrarouge et un récepteur infrarouge. maintenant qu'on a les ingrédient, il faut la recette (bah oui), et la voici. on fait un programme pour l'esp32/8266, qui ferait qu'on utilise les boutons de la calto pour piloter l'add-in. le but serait de faire un add-in qui pilote l'esp et qui stocke jusqu'à 10 signaux infrarouge qu'on peut enregistrer via le récepteur. et après on sélectionnerait parmi les signaux qu'on a enregistrer pour les renvoyer via l'émetteur. l'esp pourrait s'alimenté soit par alimentation externe comme par exemple un Power Bank, mais si on peut et que sa fusille pas la calto, on pourrait l'alimenté par le port USB/3-pin. pour relier l'esp a la calto c'est pareil en fonction du port pas utiliser et en fonction de si c'est possible, par le port3-pin/USB. voilà je crois que j'ai tout dis, faite moi signe si vous trouver des trucs et si vous souhaitez participer. merci à tous ! Wed, 17 Jun 2026 10:21:14 +0200