J’essaie de coder un jeu de devinettes numériques

Je suis arrivé jusqu’ici, mais je dois toujours utiliser la boucle While d’une manière ou d’une autre. “vouloir rejouer (y / n)” et “devinette illégale. Votre devinette doit être comprise entre 1 et 200. essayez à nouveau. Votre devinette?” ne semble pas fonctionner. Aidez-moi s’il vous plaît avec la boucle while / do-while et corrigez mes deux problèmes ci-dessus. Je vous remercie.

#include  int main() { int i,number,guess,sortinges=5,answer; printf("Welcome to the game of Guess It!\nI will choose a number between 1 and 200."); printf("\nYou will try to guess that number.If you guess wrong, I will tell you if you guessed too high or too low."); printf("\nYou have 5 sortinges to get the number.\n\nOK, I am thinking of a number. Try to guess it."); srand(time(NULL)); number = rand() % 200 + 1; for (i=0;inumber) { printf("Too high!"); } else if (guess200); { printf("Illegal guess. Your guess must be between 1 and 200.\nTry again. Your guess?"); } } printf("\n\nSorry, you ran out of sortinges.\n\nWant to play again?(y/n) "); scanf("%i",&answer); if (answer=='y') { return (i=0); } else if (answer=='n'); { printf("Goodbye, It was fun. Play again soon."); } return 0; } 

Tout d’abord, et le plus important, activez les avertissements . Votre code comporte plusieurs erreurs élémentaires qui pourraient être interceptées avec des avertissements du compilateur. Ils sont malheureusement désactivés par défaut. -Wall active les avertissements de base. Ce n’est pas “tous” les avertissements, parce que c’est C! -fsanitize=address -Wall -Wshadow -Wwrite-ssortingngs -Wextra -Wconversion -std=c99 -pedantic est un bon ensemble d’avertissements à -fsanitize=address -Wall -Wshadow -Wwrite-ssortingngs -Wextra -Wconversion -std=c99 -pedantic .


Vous pouvez boucler la boucle, mais cela devient rapidement difficile à maintenir. Au lieu de cela, mettez le jeu dans une fonction et faites-le en boucle.

 void do_game(int sortinges) { int number = rand() % 200 + 1; for (int i=0; i < tries; i++) { int guess; printf("\n\nYour guess? "); scanf("%i",&guess); if (guess == number) { printf("**** CORRECT ****\n\n"); return; } else if (guess > number) { printf("Too high!"); } else if (guess < number) { printf("Too low!"); } else if (guess > 200) { printf("Illegal guess. Your guess must be between 1 and 200.\nTry again. Your guess?"); } } puts("\n\nSorry, you ran out of sortinges.\n\n"); return; } 

Notez que le jeu ne doit concerner que le jeu. Aucune autre logique ou question sur le fait de jouer à un autre jeu. Et il peut revenir immédiatement lorsque le jeu est terminé.

Ensuite, le rest du programme est assez simple. Exécutez le jeu dans une boucle infinie, sortez-en lorsque vous avez terminé.

 int main() { printf("Welcome to the game of Guess It!\nI will choose a number between 1 and 200."); printf("\nYou will try to guess that number.If you guess wrong, I will tell you if you guessed too high or too low."); printf("\nYou have 5 sortinges to get the number.\n\nOK, I am thinking of a number. Try to guess it."); srand(time(NULL)); while(1) { do_game(5); char answer; printf("Want to play again?(y/n) "); scanf("%c",&answer); if (answer == 'n') { printf("Goodbye, It was fun. Play again soon."); break; } } return 0; } 

Il y a un problème, et c’est scanf . C’est toujours scanf . scanf est un tel problème, il y a toute une FAQ à ce sujet .

scanf("%i") lit un entier unique mais pas la nouvelle ligne suivante . Cette nouvelle ligne, et toute autre entrée supplémentaire, traîne sur stdin. Un scanf("%c", &answer); ultérieur scanf("%c", &answer); pourrait alors lire cette nouvelle ligne au lieu de leur réponse.

scanf("%i\n") ne résout pas le problème. Cela indique à scanf de lire un entier, puis une nouvelle ligne, puis de rechercher un autre caractère non-blanc. scanf est bizarre.

Vous feriez bien mieux de lire toute la ligne avec fgets et de l’parsingr avec sscanf . Vous pouvez écrire une petite fonction utilitaire pour ce qui entre dans des arguments variadiques .

 void line_scanf( const char *fmt, ... ) { // Get the list of arguments. va_list args; va_start(args, fmt); // Read a line. char line[256]; fgets(line, sizeof(line), stdin); // Scan the line like sscanf() but with a va_list. vsscanf( line, fmt, args ); // Close the list of arguments. va_end(args); } 

Ensuite, utilisez-le comme scanf . Cela garantit de lire toute la ligne et de ne pas laisser de nouvelles lignes ou d’entrée partielle dans la mémoire tampon.

  int guess; printf("\n\nYour guess? "); line_scanf("%i",&guess); 

Ce n’est qu’une réponse partielle, mais cela peut être un sharepoint départ. Vous devriez vraiment avoir une fonction d’entrée fiable . Votre scanf() ne le fera pas, même si vous corrigez les erreurs évidentes qui tentent d’obtenir un caractère en utilisant %i , qui correspond à des entiers. Je ne vais pas entrer dans les détails ici, j’ai écrit un document à ce sujet . (Fondamentalement, vous rencontrerez au moins des problèmes d’entrée non analysable que scanf() laissera simplement non lus.)

Voici un exemple de la manière dont vous pourriez effectuer une saisie fiable pour votre cas d’utilisation avec des commentaires:

 #include  #include  #include  #include  #define INVALIDNUMBER -1 #define READERROR -2 int readPositiveNumber(void) { char buf[64]; // read a line: if (!fgets(buf, 64, stdin)) return READERROR; size_t len = strlen(buf); // line was empty -> invalid: if (!len) return INVALIDNUMBER; // line was not complete (last character isn't newline): if (buf[len-1] != '\n') { // read the rest of the line do { if (!fgets(buf, 64, stdin)) return READERROR; } while (!buf[strcspn(buf, "\n")]); // input was invalid return INVALIDNUMBER; } // convert to number: char *endptr; long num = strtol(buf, &endptr, 10); // endptr == buf means no characters could be parsed as a number, // endptr doesn't point to newline means there were non-numeric characters later: if (endptr == buf || *endptr != '\n') return INVALIDNUMBER; // if result is out of range of int or is negative -> invalid: if (num > INT_MAX || num < 0) return INVALIDNUMBER; return (int)num; } int main(void) { fputs("Enter a number between 1 and 200: ", stdout); int number = readPositiveNumber(); if (number == READERROR) return EXIT_FAILURE; while (number < 1 || number > 200) { fputs("Enter a valid number between 1 and 200: ", stdout); number = readPositiveNumber(); if (number == READERROR) return EXIT_FAILURE; } printf("You entered %d.\n", number); return EXIT_SUCCESS; } 

Essayez de comprendre cette fonction, lisez les manuels relatifs aux fonctions que vous ne connaissez pas ou ne comprenez pas (Google ” man strtol “, par exemple, vous trouvera une page de manuel pour strtol() ).

Pour lire votre réponse oui / non, utilisez également fgets() , mais bien entendu, cette fonction aura un aspect différent, comme vérifier si le fgets() qu’un caractère (le deuxième doit être '\n' ) et renvoyer ce caractère. .


juste parce que c’est un peu amusant, voici un jeu entier possible qui fonctionne de manière robuste:

 #include  #include  #include  #include  #include  #define INVALIDINPUT -1 #define READERROR -2 static int readLine(char *buf, size_t bufsize) { if (!fgets(buf, bufsize, stdin)) return READERROR; size_t len = strlen(buf); if (!len) return INVALIDINPUT; if (buf[len-1] != '\n') { do { if (!fgets(buf, bufsize, stdin)) return READERROR; } while (!buf[strcspn(buf, "\n")]); return INVALIDINPUT; } return 0; } static int readPositiveNumber(void) { char buf[64]; int rc = readLine(buf, 64); if (rc < 0) return rc; char *endptr; long num = strtol(buf, &endptr, 10); if (endptr == buf || *endptr != '\n') return INVALIDINPUT; if (num > INT_MAX || num < 0) return INVALIDINPUT; return (int)num; } static int readYesNo(void) { char buf[64]; int rc = readLine(buf, 64); if (rc < 0) return rc; if (buf[0] == 'y' || buf[0] == 'Y') { if (buf[1] == '\n') return 1; if ((buf[1] == 'e' || buf[1] == 'E') && (buf[2] == 's' || buf[2] == 'S') && buf[3] == '\n') return 1; return INVALIDINPUT; } if (buf[0] == 'n' || buf[0] == 'N') { if (buf[1] == '\n') return 0; if ((buf[1] == 'o' || buf[1] == 'O') && buf[2] == '\n') return 0; return INVALIDINPUT; } return INVALIDINPUT; } int main(void) { srand(time(0)); for (;;) { int number = rand() % 200 + 1; int tries = 5; int found = 0; while (tries--) { int guess = INVALIDINPUT; while (guess < 1 || guess > 200) { fputs("guess [1..200]: ", stdout); guess = readPositiveNumber(); if (guess == READERROR) return EXIT_FAILURE; } if (guess == number) { puts("Correct!"); found = 1; break; } else if (guess < number) puts ("Too low!"); else puts("Too high!"); } if (!found) { puts("No luck!"); } int yn = INVALIDINPUT; while (yn < 0) { fputs("play again (y/n)? ", stdout); yn = readYesNo(); if (yn == READERROR) return EXIT_FAILURE; } if (!yn) { puts("Bye!"); return EXIT_SUCCESS; } } } 

Cet exercice est un exercice à intégrer dans votre esprit, pourquoi scanf est généralement un mauvais choix pour prendre des entrées mixtes d’utilisateurs! Vous pouvez le faire, mais vous devez être très prudent pour tenir compte des caractères qui restnt dans le tampon d’entrée (c’est-à-dire stdin ) – en particulier lors de la saisie de caractères … Pourquoi?

Lorsque vous entrez une valeur lue par scanf , le '\n' restra toujours dans le tampon d’entrée (sauf s’il est comptabilisé dans votre chaîne de format ). En outre, en cas d’ échec de la conversion , tous les caractères restront dans la mémoire tampon d’entrée. De plus, l’utilisateur peut faire quelque chose de stupide, comme entrer "4 is my guess" quand is my guess\n partir is my guess\n .

De plus, que se passe-t-il si l’utilisateur annule la saisie en appuyant sur ctrl + d (ou ctrl + z sous windoze) en générant une EOF manuelle? Vous devez tenir compte de toutes les possibilités pour chaque entrée .

Vous devez également utiliser le spécificateur de format correct pour lire les entrées. Vous n’allez pas lire 'y' ou 'n' avec %d ou %i . Lorsque vous voulez lire une utilisation int %d lorsque vous voulez lire un caractère, utilisez %c . Vous devez également prendre en compte le fait que %c ne passe jamais au premier plan.

(vous commencez à comprendre pourquoi il est préférable d’utiliser fgets puis d’appeler sscanf pour une saisie utilisateur?)

Comment gérez-vous les caractères qui restnt dans le tampon d’entrée? En règle générale, vous utiliserez getchar() pour lire jusqu’à ce que vous ayez lu '\n' (généré en appuyant sur Entrée ) ou jusqu’à ce que EOF soit rencontré. Vous pouvez vous simplifier la tâche en écrivant une courte fonction comme celle-ci:

 /* empty characters that remain in stdin */ void fflushstdin () { for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {} } 

Si vous appelez fflushstdin après chaque entrée, vous vous fflushstdin toujours des caractères restants. Si vous savez que des caractères d’une entrée précédente n’ont pas été supprimés, appelez-le avant de prendre une entrée.

N’utilisez pas de nombres magiques dans votre code (par exemple 1, 5, 200 ), définissez plutôt les constantes nécessaires au début de votre code et utilisez-les dans votre code. Pourquoi? S’ils changent, vous disposez d’un seul endroit facilement accessible pour les changer et vous n’avez pas à aller chercher votre code pour le trouver. Vous pouvez utiliser un #define ou un enum comme ceci:

 enum {LOW = 1, TRIES = 5, HIGH = 200 }; 

Le rest de vos problèmes sont simplement des problèmes de logique que vous pouvez résoudre. En incorporant ce qui précède, vous pouvez gérer (ce que je pense que vous essayez de faire) comme suit:

 #include  #include  #include  enum {LOW = 1, TRIES = 5, HIGH = 200 }; /* empty characters that remain in stdin */ void fflushstdin () { for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {} } int main (void) { int i, number, guess, ret; char answer; printf ("Welcome to the game of Guess It!\n" "I will choose a number between %d and %d.\n" "You will try to guess that number.\n" "I will tell you if you guessed too high or too low.\n" "You have %d sortinges to get the number.\n\n" "OK, I am thinking of a number. Try to guess it.\n\n", LOW, HIGH, TRIES); srand(time(NULL)); while (1) { /* outer loop until user quits */ number = rand() % HIGH + 1; /* set number INSIDE loop */ for (i = 0; i< TRIES; i++) { /* loop for set number of TRIES */ while (1) { /* validate user guess, handle cancelation */ printf ("Your guess no. %d? ", i + 1); /* prompt */ if ((ret = scanf (" %d", &guess)) != 1) { /* chk return */ if (ret == EOF) { /* check for cancelation */ printf ("input canceled, exiting.\n"); return 0; } fprintf (stderr, " error: invalid input.\n"); fflushstdin(); /* empty chars remaining in stdin */ continue; } if (guess < LOW || guess > HIGH) /* check limits */ printf("Illegal guess. Your guess must be between " "%d and %d.\nTry again. Your guess?", LOW, HIGH); break; } if (guess == number) { /* correct answer */ printf ("\n**** CORRECT ****\n\nWant to play again(y/n) "); fflushstdin(); /* validate answer, you are reading a `char` NOT `int` */ while ((ret = scanf (" %c", &answer)) != 1 || (answer != 'y' && answer != 'n')) { fprintf (stderr, "error: invalid answer, play again (y/n) "); if (ret == EOF) { /* check for cancelation */ printf ("input canceled, exiting.\n"); return 0; } fflushstdin(); /* empty chars remaining in stdin */ } if (answer == 'y') /* use goto for breaking nested loops */ goto done; printf ("Goodbye, It was fun. Play again soon.\n"); /* no */ return 0; } if (guess > number) /* provide > and < feedback */ printf ("Too high!\n"); if (guess < number) printf("Too low!\n"); } printf ("Sorry, you exhausted all your tries, number was: %d\n" "play again (y/n) ", number); fflushstdin(); /* validate answer, you are reading a `char` NOT `int` */ while ((ret = scanf (" %c", &answer)) != 1 || (answer != 'y' && answer != 'n')) { fprintf (stderr, "error: invalid answer, play again (y/n) "); if (ret == EOF) { printf ("input canceled, exiting.\n"); return 0; } fflushstdin(); } if (answer != 'y') break; done:; /* goto lable to play again after correct asnwer */ } return 0; } 

Exemple d'utilisation / de sortie

 $ ./bin/guess Welcome to the game of Guess It! I will choose a number between 1 and 200. You will try to guess that number. I will tell you if you guessed too high or too low. You have 5 sortinges to get the number. OK, I am thinking of a number. Try to guess it. Your guess no. 1? onehundred error: invalid input. Your guess no. 1? 100 Too low! Your guess no. 2? 150 Too high! Your guess no. 3? 125 Too low! Your guess no. 4? 137 Too high! Your guess no. 5? 131 Too low! Sorry, you exhausted all your sortinges, number was: 132 play again (y/n) y Your guess no. 1? 100 Too low! Your guess no. 2? 150 Too low! Your guess no. 3? 175 Too low! Your guess no. 4? 187 Too high! Your guess no. 5? 181 **** CORRECT **** Want to play again(y/n) y Your guess no. 1? 100 Too low! Your guess no. 2? 150 Too high! Your guess no. 3? 125 Too high! Your guess no. 4? 112 Too high! Your guess no. 5? 106 Too low! Sorry, you exhausted all your sortinges, number was: 110 play again (y/n) n 

Notez que ce qui précède gère une entrée utilisateur stupide (comme un onehundred ) et ajoute un number à la sortie d'échec pour indiquer à l'utilisateur ce qu'il a manqué.

Regardez les choses et laissez-moi savoir si vous avez d'autres questions.

scanf (“% i”, …) lit les nombres entiers en base 10, pas les caractères ni les chaînes.

Vous devez organiser vos boucles. Vous avez 2 boucles principales, une qui s’exécute pendant que l’utilisateur souhaite continuer à jouer et une autre qui s’exécute pendant le jeu.

Vous programmez en quelques mots:

 int main() { // loop until player has had enough // pick a number // game loop : // get a number from user: // user entry loop: // print prompt // get user entry // validate // loop number from user: until 0 <= entry <= 200 // if number is ok // user has won, exit game loop // if too low // say 'low' // if too high // say high // if # of attempts > MAX // say 'lost' exit game loop // end game loop // want to contine? // user entry loop: // print prompt // get user entry // validate // loop user entry loop until 0 <= entry <= 200 // end loop } 

Vous pourriez commencer vos boucles dans main un peu comme ceci:

 int attempts; char yesno = 0; int guess; do // loop until player has had enough { // generate a number here attempts = 0; while(1) // loop while game is on { while (1) // loop until user entered a valid entry { // prompt // get user guess if (0 <= guess && guess <= 200) break; } if (guessed right) { // game over! break; } // tell if high or low. if (++attempts <= MAX) { // game over! break; } } do // loop until user entered a valid entry. { printf("Another game (y/n)?"); yesno = fgetc(); } while(yesno != 'y' && yesno != 'n'); // could make case-insensitive ? } while (yesno != 'n'); 

Il existe probablement autant de façons de le faire que de chiffres entre 0 et 200. Une bonne stratégie consiste à commencer par écrire dans votre fichier C des commentaires décrivant, étape par étape, ce que le programme doit faire. Il est beaucoup plus facile de les parcourir un par un que d’avoir le programme uniquement dans votre tête, en particulier lorsque vous commencez à coder. Cela deviendra plus facile avec le temps, car vous aurez l'habitude de jongler entre les concepts et les blocages fondamentaux de votre esprit.