Partage
  • Partager sur Facebook
  • Partager sur Twitter

multiples choix par une frappe alphanumérique

possibilité avec sscanf() ?

    15 décembre 2023 à 0:24:42

    Bonjour, je viens chercher un peu d'aide parce que là je sèche depuis un moment.

    Je code un jeu puissance 4 et j'aimerais pouvoir lancer une sauvegarde de la partie en cours de route ,cette possibilité étant disponible à chaque tour de chaque joueur quand il indique dans quelle colonne il souhaite jouer.

    Du coup , il a la possibilité d'entrer un type int pour le numéro de la colonne ou un type char : 's' comme sauvegarde.

    Je pensais utiliser la fonction sscanf() pour combiner ces possibilités en une frappe.

    Malheureusement en essayant un code sommaire pour voir, je m’aperçois que char *choix est fluctuant à chaque frappes numériques même si elles sont identiques, donc susceptible de tomber sur 's' accidentellement.

    Il faudrait que *choix reste fixe et pas sur 's' si possible quand je tape un nombre.

    Est ce que vous voyez une solution? ou bien une autre piste le cas échéant?

    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        char*choix=malloc(2);
        int col=0;
        printf("entrer une valeur\n");
        scanf("%2c",choix);
        sscanf(choix,"%d %c",&col,choix);
        printf("choix:%c et col: %d",*choix,col);
    return 0; }
    • Partager sur Facebook
    • Partager sur Twitter
      15 décembre 2023 à 0:32:32

      Hello,

      Pourquoi utiliser scanf() dans le cas présent ? getchar() fera parfaitement l'affaire puisque le joueur ne doit entrer qu'un seul caractère:

      #include <stdio.h>
      
      void GameBackup(void) {
      	puts("game backup");
      }
      
      int main(void) {
          char choice;
          fputs("entrer une valeur: ", stdout);
          choice=getchar();
      	if(choice=='s' || choice=='S')
      		GameBackup();
      	else
      		if(choice<'1' || choice>'9')
      			printf("%c : entrée invalide", choice);
      		else {
      			int col=choice-'0';
      			printf("col=%d\n", col);
      			// jouer
      		}
      
          return(0);
      }

      La ligne 17 permet de 'convertir' un char en int: puisque les caractères de '0' à '9' sont codés en ascii de 48 à 57 inclus, faire choice - '0' donne un nombre entier entre 0 et 9 inclus.

      -
      Edité par edgarjacobs 15 décembre 2023 à 0:53:30

      • Partager sur Facebook
      • Partager sur Twitter

      On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent

        15 décembre 2023 à 11:28:05

        Je ne suis pas certain de bien comprendre :

        Sans sauvegarde : Tu rentres le N° de colonne suivie de 'Entrée'. 

        Avec sauvegarde : Tu rentres le N° de colonne suivie de 's' suivie de 'Entrée'. 

        C'est ça ?



        • Partager sur Facebook
        • Partager sur Twitter
        ...
          15 décembre 2023 à 11:30:04

          En fait on pose une question à l'utilisateur, et il répond par une ligne (terminée par retour-charriot).

          Donc ce que je conseille c'est de commencer par lire la ligne complète, (*) soit par un tampon qu'on suppose assez grand, soit par getline qui va allouer ce qu'il faut, à charge pour le programmeur de libérer ensuite.

          Ceci fait, on épluche la ligne. Ca peut se faire par sscanf, un ou plusieurs. et des strcmp pour regarder si c'est un des "mots" qu'on attendait

          Une démo simple (qui présente en théorie les problèmes liés aux lignes trop grandes, mais est-ce le sujet ici ?)

          #include <stdio.h>
          #include <stdlib.h>
          #include <string.h>
          #include <stdbool.h>
          
          bool lire_et_agir()
          {
          	char tampon[1024];           
          	bool continuer = true;
          
          	printf("tapez quit ou un nombre entier : ");
          	fgets(tampon, 1024, stdin);
          
          	// on regarde si on arrive à en tirer un nombre entier
          	int nombre;
          	if (1 == sscanf(tampon, "%d", &nombre)) {
          		printf("=> l'entier %d\n", nombre);
          	} else {
          		char mot[1024];   // on voit grand
          		if (1 == sscanf(tampon, "%s", mot)) {
          			if (strcmp(mot, "quit") == 0) {
          				continuer = false; 
          			} else {
          				printf("je ne comprends pas \"%s\"\n", mot);
          			}
          		} else {
          			printf("Répondez !\n");
          		}
          	}
          	return continuer;
          }
          
          int main() 
          {	
          	while (lire_et_agir()) continue;
          	printf("Fini !\n");
          
          	return EXIT_SUCCESS;
          }
          


          Exécution

          Une exécution, qui passe par tous les cas :

          $ gcc -std=c17 -Wall -Wextra -pedantic -Werror -Wno-unused -1D_XOPEN_SOURCE=700 -g    prog.c   -o prog
          
          $ ./prog
          tapez quit ou un nombre entier : 
          Répondez !
          tapez quit ou un nombre entier : 123
          => l'entier 123
          tapez quit ou un nombre entier : 
          Répondez !
          tapez quit ou un nombre entier : -45
          => l'entier -45
          tapez quit ou un nombre entier : gloup
          je ne comprends pas "gloup"
          tapez quit ou un nombre entier : quit
          Fini !



          (*) à moins d'être en train d'essayer de fourrer un programme compliqué sur un microcontrôleur qui a juste 4Ko de RAM pour tout faire, faut arrêter de se compliquer la vie : un tampon provisoire pour coller toute une ligne, c'est plus avantageux que se faire suer à essayer de traiter les caractères au vol. 

          -
          Edité par michelbillaud 15 décembre 2023 à 11:37:09

          • Partager sur Facebook
          • Partager sur Twitter
            15 décembre 2023 à 12:21:34

            Pour contourner le problème : pourquoi pas une sauvegarde systématique de la partie à chaque tour (sans intervention du joueur).
            • Partager sur Facebook
            • Partager sur Twitter
              15 décembre 2023 à 14:15:26

              Bonjour,

              j'ai bien fait une sauvegarde automatique pour commencer, mais l’énoncé de mon devoir précise qu'il faut proposer de faire une sauvegarde en cours de partie que l'on pourra reprendre si besoin via le menu de départ.

              J'ai tout de même fini par réussir à faire une code qui fonction avec atoi() ce matin, il semblerait qu'il est quelque soucis avec l'utilisation de scanf() mais un simple espace avant % a permis de débloquer la situation.

              Cependant mon code présente encore quelques défauts de robustesse car une saisie inadaptée peut le faire partir en sucette !

              Par exemple si j'envoie un char au lieu de int dans la boucle while à la fin

              Ou bien quand je frappe '14' au premier scanf, il me joue un o dans la colonne 1 et direct un x dans la colonne 4, pas bien compris pourquoi il ne s'arrête pas en revenant au scanf puisque je fais une boucle au tour par tour!?

              struct puiss4 ajout_pion(struct puiss4 jeu,int joueur)
                  {
                      struct puiss4 remp=jeu;
                      char choix;//choix de jouer
                      int col=0;//colonne choisie par le joueur
              
                      switch(joueur)
                          {case -1:
                              printf("Tour du Joueur 1, joue les pions 'o'.\n");
                              break;
                          case 1:
                              printf("Tour du Joueur 2, joue les pions 'x'.\n");
                              break;
                          }
              
                      printf("Choississez une colonne libre entre 1 et 7.\nOu tapez 's' pour sauvegarder.\n");
                      scanf(" %c",&choix);
              
                      if (choix=='s')
                      {
                          char nom_fichier[20];
                          printf("Entrez le nom de la sauvegarde\n");
                          scanf(" %s",nom_fichier);
                          FILE *sauvegarde=fopen(nom_fichier,"w");
                          if (sauvegarde==NULL)
                              {printf("Problème avec la sauvegarde.");};
                          for (int i=0;i<=6;i++)
                              {fprintf(sauvegarde,"%d %d %d %d %d %d %d\n",remp.grille[0][i],remp.grille[1][i],remp.grille[2][i],remp.grille[3][i],remp.grille[4][i],remp.grille[5][i],remp.grille[6][i],remp.dernier_pion[i]);
                              };
                          fclose(sauvegarde);
                          printf("Partie sauvegardée\n");
                          printf("Pour continuer à jouer,choissisez une colonne libre entre 1 et 7.\n");
                          scanf(" %c",&choix);
                      };
              
                      col=atoi(&choix);//Extrait la valeur int d'un char
                      while(col<1||col>7||remp.dernier_pion[col-1]==6)
                          {
                              printf("Impossible de jouer cette colonne car pleine ou inexistante.\nVeuillez choissir une colonne encore libre.\n");
                              scanf(" %d",&col);
                          }
                      remp.grille[remp.dernier_pion[col-1]][col-1]=joueur;
                      remp.dernier_pion[col-1]++;
                      return remp;
                  }


              Bon bref , pour l'instant je pense plus avoir assez de temps pour approfondir et comprendre les problèmes car je dois encore faire le rapport et un autre devoir.

              En tout cas merci pour votre aide ,je verrais si j'ai encore un peu de temps en fin de WE pour rectifier cela.

              • Partager sur Facebook
              • Partager sur Twitter
                15 décembre 2023 à 14:43:19

                On peut gérer la saisie avec fgets(), mais on peut le faire avec scanf() aussi.

                        /* is the user input an int between 1 and MAX_GRID_COL? */
                        int col;
                        if (scanf("%d", &col) == 1) {
                        (...)
                        }

                à ce stade on a tenté de récupérer un int, et si c'est le cas ce qui est dans stdin et qui y correspond sera consommé.

                si c'est autre chose qui n'est pas interprétable comme un int, scanf() laisse le flux intact.

                après, cela, on peut donc aussi utiliser scanf() pour voir ce qui s'y trouve et le capturer en tant que chaîne, tout en veillant à ne pas dépasser la capacité de la chaîne, pour vérifier si c'est une commande qui a été tapée et non un entier :

                        /* is the user input a command? */
                        char st[MAX_COMMAND_LEN + 1] = { '\0' };
                        int is_valid_command = 0;
                        if (scanf("%" STR(MAX_COMMAND_LEN) "[^\n]", st) == 1) {
                        (...)
                        }
                

                STR() est une macro qu'on peut définir pour transformer au stade de la compilation la valeur numérique d'un #define comme MAX_COMMAND_LEN en chaîne pour les besoins de la délimitation de ce que doit capturer scanf() en tant que chaîne. Par exemple si MAX_COMMAND_LEN vaut 20, alors le préprocesseur transformera le scanf() en ligne 4 ci-dessus en scanf("%20[^\n]", st) pour ne capturer que 20 char au plus avant un éventuel retour à la ligne.

                https://cplusplus.com/reference/cstdio/scanf/

                ("20" correspond à la partie "width" dans le spécificateur de format)

                Comme en plus de la sauvegarde il peut y avoir besoin d'autres commandes possibles et pour faciliter l'ajout de commandes, j'aime bien mettre de l'intelligence dans les données en utilisant astucieusement un enum des différentes commandes possibles et un tableau de ces commandes.

                enum valid_commands { SAVE, HELP, QUIT, NB_VALID_COMMANDS };
                
                const char valid_commands[NB_VALID_COMMANDS][MAX_COMMAND_LEN] = {
                        [SAVE] = "save",
                        [HELP] = "help",
                        [QUIT] = "quit"
                };
                

                pour insérer une nouvelle commande, il suffit de le faire avant NB_VALID_COMMANDS dans l'enum et d'ajouter une ligne correspondante dans le tableau.

                Cela permet assez facilement de faire une boucle sur les commandes et un switch / case des commandes, car les enum sont des types entiers.

                On pourrait aller plus loin en faisant du tableau de commandes un tableau de struct, initialisant non seulement les chaînes correspondantes, mais aussi un pointeur de fonction vers la fonction à exécuter selon la commande saisie (mais là, c'est plus trop niveau débutant).

                Voilà ce que cela donne :

                #include <stdio.h>
                #include <stdlib.h>
                #include <string.h>
                
                #define MAX_GRID_COL 7
                #define MAX_COMMAND_LEN 20
                #define STR_INDIR(x) #x
                #define STR(x) STR_INDIR(x)
                
                enum valid_commands { SAVE, HELP, QUIT, NB_VALID_COMMANDS };
                
                const char valid_commands[NB_VALID_COMMANDS][MAX_COMMAND_LEN] = {
                        [SAVE] = "save",
                        [HELP] = "help",
                        [QUIT] = "quit"
                };
                
                void play_column(int col);
                void help(void);
                void save(void);
                void quit(void);
                
                void get_user_input_and_commands(void) {
                        /* display prompt */
                        printf("? ");
                        fflush(stdout);
                
                        /* is the user input an int between 1 and MAX_GRID_COL? */
                        int col;
                        if (scanf("%d", &col) == 1) {
                                if (!(col > 0 && col <= MAX_GRID_COL))
                                        printf("Error: wrong column number, please use a number "
                                                        "between 1 and %d\n", MAX_GRID_COL);
                                else
                                        play_column(col);
                        }
                
                        /* is the user input a command? */
                        char st[MAX_COMMAND_LEN + 1] = { '\0' };
                        int is_valid_command = 0;
                        if (scanf("%" STR(MAX_COMMAND_LEN) "[^\n]", st) == 1) {
                                for (int i = 0; i < NB_VALID_COMMANDS; i++) {
                                        if (strcmp(st, valid_commands[i]) == 0) {
                                                is_valid_command = 1;
                                                switch (i) {
                                                case SAVE: save(); break;
                                                case HELP: help(); break;
                                                case QUIT: quit(); break;
                                                }
                                        }
                                }
                                if (!is_valid_command) {
                                        printf("Error: %s is not a valid command, ignored\n", st);
                                        printf("Type %s for instructions\n", valid_commands[HELP]);
                                }
                        }
                }
                
                int main(void) {
                        while (1)
                                get_user_input_and_commands();
                
                    return 0;
                }
                
                
                /* implementation of declared functions */
                
                void play_column(int col) {
                        /* TODO */
                        printf("You play column %d\n", col);
                }
                
                void help(void) {
                        printf("You can type a number between 1 and %d, or one "
                                        "of the following commands:\n", MAX_GRID_COL);
                        for (int i = 0; i < NB_VALID_COMMANDS; i++) {
                                printf("\t%s\n", valid_commands[i]);
                        }
                }
                
                void save(void) {
                        /* TODO */
                        printf("Current game saved.\n");
                }
                
                void quit(void) {
                        printf("Exiting the program.\n");
                        exit(EXIT_SUCCESS);
                }
                

                Cela se compile et s'exécute comme ceci, en montrant les différents cas d'exécution :

                $ gcc -Wall -Werror ThibautArmand.c
                $ ./a.out 
                ? 0
                Error: wrong column number, please use a number between 1 and 7
                ? 8
                Error: wrong column number, please use a number between 1 and 7
                ? 2,50
                You play column 2
                Error: ,50 is not a valid command, ignored
                Type help for instructions
                ? 3
                You play column 3
                ? toto
                Error: toto is not a valid command, ignored
                Type help for instructions
                ? help
                You can type a number between 1 and 7, or one of the following commands:
                	save
                	help
                	quit
                ? save
                Current game saved.
                ? quit
                Exiting the program.
                

                Le switch / case qui exécute les commandes est ligne 45 dans ce code.

                ----

                @ThibautArmand :

                J'ai posté sans voir ton post ni ton code.

                Après scanf(" %c",&choix); purge stdin ainsi :

                int c;
                while ((c = getchar()) != '\n' && c != EOF)
                    /* discard */ ;


                Ta condition while(col<1||col>7 ... empêche le joueur de jouer la colonne 7.

                En faisant un prototype "struct puiss4 ajout_pion(struct puiss4 jeu,int joueur)" ta fonction obtient déjà une copie de la struct puiss4 jeu. Il n'est donc pas utile d'en faire une autre copie dans "remp".

                --

                Edits : corrections de coquilles et formulations et réponse rapide à ThibautArmand

                -
                Edité par Dlks 15 décembre 2023 à 15:47:55

                • Partager sur Facebook
                • Partager sur Twitter
                  15 décembre 2023 à 18:41:09

                  Juste pour le principe de chipoter pour quelques octets, mais un tableau comme

                  const char valid_commands[NB_VALID_COMMANDS][MAX_COMMAND_LEN] = {
                          [SAVE] = "save",
                          [HELP] = "help",
                          [QUIT] = "quit"
                  };

                  avec #define  MAX_COMMAND_LEN 20,  ça occupe 60 octets,

                  là où

                  const char * const valid_commands2[NB_VALID_COMMANDS] = {
                          [SAVE] = "save",
                          [HELP] = "help",
                          [QUIT] = "quit"
                  };
                  

                  en prendra moins, à savoir

                  • pour chaque chaine, juste ce qu'il faut (4+1 octets = 5)
                  • et un pointeur pour chaque, soit 4 ou 8, selon l'architecture
                  soit 3*9 = 27, ou 3*13 = 39

                  Y a des const comme il en pleut parce que c'est un tableau de pointeurs qu'on ne doit pas modifier, vers des caractères de chaines qu'on ne doit pas modifier non plus.

                  --- au passage, on peut faire un tableau d'actions

                  typedef  void (*Action)();
                  
                  Action actions [NB_VALID_COMMANDS]  = {
                      [HELP] = & help,
                      ....
                  };
                  
                  


                  et même, un tableau qui regroupe les noms des actions et les fonctions associées (là j'ai la flemme).



                  -
                  Edité par michelbillaud 15 décembre 2023 à 18:48:04

                  • Partager sur Facebook
                  • Partager sur Twitter

                  multiples choix par une frappe alphanumérique

                  × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                  • Editeur
                  • Markdown