unintended loop (C) - c

I keep getting a duplicate loop when running a Rock Paper Scissors program in C:
#include <stdio.h>
#include <stdlib.h>
int getUserInput(userInput);
int getComputerInput(cpuInput);
int pickWinner(int player, int cpu);
int main()
{
int playerWins = 0;
int compWins = 0;
int ties = 0;
int userInput;
int cpuInput;
int userChoice =1;
int compChoice;
int decision;
while (userChoice != 4)
{
compChoice = getComputerInput();
userChoice = getUserInput(userInput);
printf("computer chose %d\n", compChoice); //for debugging
printf("you chose %d \n", userChoice);//for debugging
decision = pickWinner(userChoice, compChoice);
if (decision == 1)
{
playerWins++;
}
else if (decision == 2)
{
compWins++;
}
else if (decision == 3)
{
ties++;
}
}
printf("Final score is: \nPLAYER: %d \nCOMPUTER: %d \n", playerWins, compWins);
}
//generates a random number for computer, 1=rock 2= paper 3=scissors
int getComputerInput (int cpuInput)
{
srand(time(NULL));
int r = rand() %3 +1;
return r;
}
//prompts user for character input, then converts input into a number to return back to main
int getUserInput(userInput)
{
char playerPick ;
printf("Please choose R, P, or S. (Q for quit)\n");
playerPick = getchar();
switch(playerPick)
{
case 'R' | 'r':
printf("Player chose R. \n");
return 1;
break;
case 'p':
case 'P':
printf("Player chose P. \n");
return 2;
break;
case 's':
case 'S':
printf("Player chose S. \n");
return 3;
break;
case 'q':
case 'Q':
printf("player quit");
return 4;
break;
default:
printf("Invalid choice, choose again \n");
break;
}
}
//method for determining winner
int pickWinner(int player, int cpu)
{
if (player ==1 && cpu ==1)
{
printf("tie\n\n");
return 3;
}
else if (player==1 && cpu ==2)
{
printf("you lose, paper beats rock\n\n");
return 2;
}
else if (player ==1 && cpu ==3)
{
printf("you win, rock beats scissors\n\n");
return 1;
}
else if (player ==2 && cpu ==1)
{
printf("you win, paper beats rock\n\n");
return 1;
}
else if (player ==2 && cpu ==2)
{
printf("tie\n\n");
return 3;
}
else if (player ==2 && cpu ==3)
{
printf("you lose, scissors beats paper\n\n");
return 2;
}
else if (player ==3 && cpu ==1)
{
printf("you lose, rock beats scissors\n\n");
return 2;
}
else if (player ==3 && cpu ==2)
{
printf("you win, scissors beat paper\n\n");
return 1;
}
else if (player ==3 && cpu ==3)
{
printf("tie\n\n");
return 3;
}
}
Output:
it seems to be passing a zero back to the getUserInput method and I cant figure out why.
any hints in the right direction would be VASTLY appreciated.
apologies if this post is not formatted correctly.
thanks in advance

When you enter input, you end the input by pressing the Enter key. That key is actually placed into the input queue as a newline.
So when you read the first character, you will get the character entered (for example 'r'). However, the next character in the input queue is the newline, and that will be what the second call to getchar will give you.
There are ways to skip that. The simplest is simply doing an extra getchar call to discard the next character. However, if that's not the newline (for example if the user entered more than one letter as input) then that will not work.
Another possible solution is to use fgets to read a whole line, and the just get the first character from that line. The problem is if you don't provide a big enough buffer to store the line, you will have the same problem as before.
The only safe solution is to read the input from the user as a character. Then in a loop read until you get the newline character. The characters you read in the loop is simply discarded.
Perhaps something like this function:
void skip_past_newline(void)
{
int c;
while ((c = getchar()) != '\n' && c != EOF)
{
// Do nothing
}
}
Then just call it after you read the user input:
playerPick = getchar();
skip_past_newline();
Please do note a couple of things with the skip_past_newline function as presented above:
The variable that receives the result of getchar is an int variable. This is actually important.
I not only check for the newline in the loop, I also check for EOF. This indicates the used pressed the end-of-file key-combination (Ctrl-Z in the Windows console window) and that you should exit your program. This check is why you need to make the variable an int.

AFAICS, there are at least two problems with getUserInput()
When the user inputs an invalid choice, getUserInput returns without a value, because there is no loop around the code to ask again. I am pretty sure, the compiler warns about this.
To input a value, the user must enter a character and a newline. This is the character read the second time, which produces the output "invalid choice". To prevent this, you must skip the whitespace before reading the next input char.

Related

I'm trying to make a program in C in which you can play hangman but as soon as it prints the option to guess the letter the program terminates

The program isn't printing after giving me the first chance to guess.
#include <stdio.h>
#include <string.h>
int main() {
char menu;
int c = 0, flag = 0, life = 8;
printf("\nWelcome to Hangman!!!");
printf("\nThis is a game of hangman.");
printf("Player 1 enters a random word and the other has to guess it.");
printf("You get 8 lives in total i.e. you can have a maximum of 8 wrong guesses.");
printf("\n");
printf("Press n for new game\n");
printf("Press q to quit\n");
printf("\n");
scanf("%c", &menu);
int i = 0, j = 0;
char w[20], ch;
if (menu == 'q') {
printf("Exiting...");
printf("Thanks for playing");
}
else if (menu == 'n') {
printf("Player 1 enters a word\n");
scanf("%s", w);
int len = strlen(w);
for (int i = 0; i < len; i++) {
toupper(w[i]);
}
printf("\e[1;1H\e[2J");
char arr[len - 1];
for (int i = 0; i < len - 1; i++) {
arr[i] = '_';
printf("%c", arr[i]);
}
printf("\n");
while (life != 0) {
for (int i = 0; i < len - 1; i++) {
if (arr[i] == '_') {
flag = 1;
break;
}
else {
flag = 0;
}
}
if (flag == 0) {
printf("You Won!!\n");
printf("You Guessed The Word: %s", w);
break;
}
else {
char ans;
printf("Enter a letter between A-Z");
scanf("%c", ans);
toupper(ans);
for (int j = 0; j < len; j++) {
if (ans == w[j]) {
arr[j] = ans;
c++;
}
}
if (c == 0) {
life--;
}
c = 0;
for (int j = 0; j < len; j++) {
printf("%c", arr[j]);
}
printf("\n Lives Remaining= %d \n", life);
}
}
if (life == 0) {
printf("\n You Lost!!! \n");
printf("The Word Was: %s", w);
}
}
else {
printf("Invalid Character");
}
}
Output:
Welcome to Hangman!!!
This is a game of hangman.Player 1 enters a random word and the other has to >guess it.You get 8 lives in total i.e. you can have a maximum of 8 wrong >guesses.
Press n for new game
Press q to quit
n
Player 1 enters a word
Hello
Enter a letter between A-Z
PS C:\Users\arora\Desktop\Programs\C>
There are quite a few problems with your program. Here are the major ones:
You want to use use space prefix in the format string for scanf(" %c", ...) to ensure previous newlines are ignored.
scanf("%c", ans); should be &ans. It causes scanf() to fail rendering the remain of the program non-interactive. Without input from the user the core game logic doesn't work.
Here are some of the other issues:
#include <ctype.h>.
(not fixed) Consider changing the menu logic so 'q' quits, and any other letter starts a game.
Game prompt contains long lines that are hard to read for the player(s).
You use a printf() per line which makes it hard to read. Use a single call and multi-line strings as input.
Try to branch your code less by making use of early return. It makes it easier to read.
Check the return value of scanf(). If it fails then whatever variable it read doesn't have a well defined value.
Ensure that scanf() read no more than 19 bytes into a 20 byte array w. It takes a little macro magic to generate the 19 so I didn't make this change but it's a good idea to #define constants for magic values like the 20.
arr is not \0 terminated (len-1). Most c programmers expect a string so it's not worth the confusion to save 1 byte.
Use a function or macro for the ANSI escape to clear the screen.
Eliminate unused variables i, j.
Reduce scope of variables (declare variables close to where you use them).
The calculation of the flag variable is cumbersome.
(not fixed) The prompt "Enter a letter between A-Z" is somewhat ambiguous. Suggest "... between A and Z".
It's generally a good idea to leave user input as you read. If you care about the repeated toupper() you can create a copy of the user input with letters in upper case, and create another variable to hold the upper case version of the player's guess. This avoid you saying things like you entered the word "BOB" when the actual input was "bob".
You attempt to use toupper() to convert each letter to upper case but don't assign the result to anything so it does not do anything constructive.
Consider some functions to document what each your code does. I added some comments for now.
(mostly not fixed) Consider using better variable names (c, w, arr, flag).
(not fixed) Should you reject a word with your magic '_' value? In general should you validate that the word is reasonable (a-z, len > 0, len < 20)?
(not fixed) Consider, in arr, just storing if a letter was correctly guess (boolean). When evaluating the state show the letter from w if it is already guessed otherwise the _.
(not fixed) If you guess a correct letter again, it's considered a good guess. Should it?
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#define clear() printf("\e[1;1H\e[2J")
int main() {
printf(
"Welcome to Hangman!!!\n"
"\n"
"This is a game of hangman.\n"
"Player 1 enters a random word and the other has to guess it.\n"
"You get 8 lives in total i.e. you can have a maximum of 8 wrong guesses.\n"
"\n"
"Press n for new game\n"
"Press q to quit\n"
);
char menu;
if(scanf(" %c",&menu) != 1) {
printf("scanf failed\n");
return 1;
}
switch(menu) {
case 'q':
printf(
"Exiting..."
"Thanks for playing\n"
);
return 0;
case 'n':
break;
default:
printf("Invalid Character");
return 1;
}
printf("Player 1 enters a word\n");
char w[20];
if(scanf("%19s", w) != 1) {
printf("scanf failed\n");
return 1;
}
clear();
char arr[20];
int len=strlen(w);
for(int i=0;i<len;i++) {
arr[i]='_';
}
arr[len] = '\0';
int life=8;
for(;;) {
printf("%d Lives Remaining\n", life);
// read a guess from player
for(int i = 0; i < len; i++) {
printf("%c", arr[i]);
}
printf(" Enter a letter between A-Z ");
char guess;
if(scanf(" %c", &guess) != 1) {
printf("scanf failed\n");
return 1;
}
// determine if any of the letters are in the secret word
int c = 0;
for(int i=0; i<len; i++) {
if(toupper(guess) == toupper(w[i])) {
arr[i]=guess;
c = 1;
}
}
if(c==0) {
life--;
}
// game over?
int flag = 0;
for(int i = 0; i<len; i++) {
if(arr[i]=='_') {
flag=1;
break;
}
}
if(flag==0) {
printf("You Won!!\n");
printf("You Guessed The Word: %s\n",w);
break;
}
if(life==0) {
printf("\n You Lost!!!\n");
printf("The Word Was: %s\n", w);
break;
}
}
}

Scanf in visual studio not accepting multiple cases of characters

I'm creating a conversion project for letters/numbers ASCII table. My code is supposed to be 'interactive', so the user would type 'y' or 'n' to answer questions on the screen. However, it doesn't want to do this twice...
I have tried:
Just trying numbers instead of characters, but it's not exactly what I want
The %[\n]*c, and %[\n]c, and %[\n]*s ... technique but it doesn't help ;-;
Testing in a different project, but the only way I am able to do it is for multiple scanf()s to be in a row.
Here is the code:
printf("Would you like to convert a number today? \n");
printf("Please press Y or N \n");
scanf("%c", &input);
if (input == 'y' || input == 'Y') { //compare input if they said 'yes'
printf("\nThank you! \nWhat number?\n");
scanf("%d", &number);
flag = test(number);
if (flag == 0) { //if there is an equivalent letter
letter = conversion(number); //find the equivalent letter
printf("\nYour Number \t ASCII letter\n");
printf("%d\t %c\n", number, letter);
}
}
else if (input == 'n' || input == 'N') {
printf("\nWould you like to convert a letter instead? This time enter 0 or 1\!\n\n"); //problem here!!
printf("I wish I could say it was to \' Spice things up \' ...but it\'s not ;-; \n\n");
scanf("%d", &input2);
if (input2 == 0) { //this needs to be checking whether the user input Y/y
printf("Great choice adventurer!\n");
printf("What letter will it be today?\n\n");
//..I would go to a different funtion here ie: test2(letter)...
scanf("%d", &number); //I showed that it worked with multiple numbers, but I can't get this to work with multiple letters
printf("%d", number);
}
if (input2 == 1) { //this needs to be checking whether the user input N/n
printf("Difficult to please, I see...\n\n");
printf("I suggest you move on with that attitude!\n\n");
printf("Bye bye then\n");
}
}
else { //if they tried to break the code
printf("Sorry I did not recognise your command...please retry\n");
printf("Press Y or N next time!\n");
}
The first check works perfectly, I just want the second check to be like the first!
Some 'solutions' caused a overflow, which I don't want if possible
Even if someone could explain why this isn't working the way I intended would be very helpful!
I'm not sure what confuses you.
Use
char foo;
scanf(" %c", &foo);
for single characters, eg. letters and
int bar;
scanf("%d", &bar);
for numbers, integers. If you type a letter instead, scanf() will fail.
%[...] is for strings.
scanf() returns the number of successful conversions (or EOF), so for
int height;
int width;
scanf("%d %d", &height, &width);
it returns 2 if successful. It might return 1 if only height could be read.
So to check for errors on user input you should do:
int height;
int width;
if (scanf("%d %d", &height, &width) != 2) {
// handle the error, maybe exit the program.
}
Your code could look like that (without error handling):
#define _CRT_SECURE_NO_WARNINGS // you said Visual Studio? Without it you should get
// warnings about some functions being insecure.
#include <ctype.h> // isalpha() returns true if the value is a letter
#include <stdlib.h> // EXIT_SUCCESS
#include <stdio.h> // puts(), printf(), scanf()
int main(void)
{
for(;;) { // for-ever ... endless loop since the user exits by answering
// 'n' or 'N' two times
puts("Would you like to convert a number today?\nPlease press Y or N:");
char input;
if (scanf(" %c", &input) != 1) // We reached EOF ... end of file
break; // that's improbable for stdin,
// but input could be redirected to
// read from a file instead.
if (input == 'y' || input == 'Y') {
puts("\nThank you!\nWhat number?");
int number;
scanf("%d", &number);
if (isalpha((char unsigned)number)) // *)
printf("\nYour Number \t ASCII letter\n%d\t %c\n\n", number, number);
else
puts("Sorry, but that's not the ASCII code of a letter :(\n");
}
else if (input == 'n' || input == 'N') {
puts("\nWould you like to convert a letter instead?\nPlease press Y or N:");
scanf(" %c", &input);
if (input == 'y' || input == 'Y') {
puts("\nGreat choice adventurer!\nWhat letter will it be today?");
char letter;
scanf(" %c", &letter);
if (isalpha(letter))
printf("\nYour letter \t ASCII code\n%d\t %c\n\n", letter, letter);
else
puts("Sorry, but that's not a letter :(\n");
}
else if (input == 'n' || input == 'N') {
puts("\nDifficult to please, I see...\n\nI suggest you move on with that attitude!\n");
puts("Bye bye then.");
return EXIT_SUCCESS;
}
}
else {
puts("Sorry I did not recognize your command... Please retry.");
puts("Press Y or N next time!\n");
}
}
}
*) isalpha() (and the other functions in <ctype.h>) expects a value that fits in a unsigned char or the value EOF. It has undefined behaviour for other values. Since we read user input into an int we cannot be sure that's the case so we have to cast the value to unsigned char before passing it to isalpha() (and friends).
Next time you ask a question please include your full code, including variable declarations, functions like test() and conversion() and #includes. But please, post an example that focuses on your problem at hand. All that dialog you included would not have been necessary.

Scanning character caused problem in devC

So my code does the following:
Ask what's the option
If option is 1: Scan some numbers
If option is 2: Print those numbers
After each option, ask if user wanted to continue choosing (Y/N)
This is my main code
while(yesnocheck==1)
{
printf("What's your option?: ");
scanf("%d",&b);
switch(b){
case 1:
printf("How many numbers?: ");
scanf(" %d",&n);
a=(struct sv*)malloc(n*sizeof(struct sv));
for(int i=0;i<n;i++)
scanf("%d",&((a+i)->num));
break;
case 2:
for(int i=0;i<n;i++)
printf("%d\n",(a+i)->num);
break;
}
yesnocheck==yesnochecker();
}
And this is the yesnochecker function:
int yesnochecker()
{
char yesorno;
printf("Do you want to continue? (Y/N)");
while(scanf("%s",&yesorno))
{
if(yesorno=='Y')
return 1;
if(yesorno='N')
return 0;
printf("*Wrong input. Please reenter (Y/N): ");
}
}
So on dev C++, my code won't run correctly. After it's done option 1, when I enter "Y" then choose option 2, case 2 will display some weird numbers. However it works well on online C compilers.
And then, when I change the char yesorno in yesnochecker() function to char yesorno[2] and treat it as a string, the code does work.
Can someone shed some light?
It is a bad idea to read a char c with scanf("%s", &c);. "%s" requires a buffer to store a string. The only string which fits into a char is an empty string (consisting only of a terminator '\0' – not very useful). Every string with 1 character requires 2 chars of storage – 1 for the character, 1 for the terminator ('\0'). Providing a char for storage is Undefined Behavior.
So, the first hint was to use the proper formatter instead – "%c".
This is better as it removes the Undefined Behavior. However, it doesn't solve another problem as the following sample shows:
#include <stdio.h>
int cont()
{
char c; do {
printf("Continue (y/n): ");
scanf("%c", &c);
printf("Input %c\n", c);
} while (c != 'y' && c != 'n');
return c == 'y';
}
int main()
{
int i = 0;
do {
printf("Loop iteration %d.\n", ++i);
} while (cont());
/* done */
return 0;
}
Output:
Loop iteration 1.
Continue (y/n): y↵
Input 'y'
Loop iteration 2.
Continue (y/n):
Input '
'
Continue (y/n): n↵
Input 'n'
Live Demo on ideone
WTH?
The scanf("%c") consumes one character from input. The other character (inserted for the ENTER key) stays in input buffer until next call of any input function.
Too bad, without ENTER it is hard to confirm input on console.
A possible solution is to read characters until the ENTER key is received (or input fails for any reasons). (And, btw., getc() or fgetc() can be used as well to read a single character.):
#include <stdio.h>
int cont()
{
int c;
do {
int d;
printf("Continue (y/n): ");
if ((c = fgetc(stdin)) < 0) {
fprintf(stderr, "Input failed!\n"); return 0;
}
printf("Input '%c'\n", c);
for (d = c; d != '\n';) {
if ((d = fgetc(stdin)) < 0) {
fprintf(stderr, "Input failed!\n"); return 0;
}
}
} while (c != 'y' && c != 'n');
return c == 'y';
}
int main()
{
int i = 0;
do {
printf("Loop iteration %d.\n", ++i);
} while (cont());
/* done */
return 0;
}
Output:
Loop iteration 1.
Continue (y/n): y↵
Input 'y'
Loop iteration 2.
Continue (y/n): Hello↵
Input 'H'
Continue (y/n): n↵
Input 'n'
Live Demo on ideone
Please, note, that I changed the type for the read character to int. This is because getc()/fgetc() return an int which is capable to store any of the 256 possible char values as well as -1 which is returned in case of failing.
However, it isn't any problem to compare an int with a character constant (e.g. 'y'). In C, the type of character constants is just int (SO: Type of character constant).

Change(?) user input character into another character c language

Writing a program for school to cause a user inputted character to identify a gas cylinder. Here is what i have written so far, but all inputs display ammonia.
#include <stdio.h>
int main()
{
char n;
printf("Please enter the first letter of color of gas cylinder\n");
scanf("%c", &n);
if(n = 'o' ){
printf("Ammonia\n");
}
else if(n = 'b'){
printf("Carbon Monoxide\n");
}
else if(n = 'y'){
printf("Hydrogen\n");
}
else if(n = 'g'){
printf("Oxygen\n");
}
else{
printf("Contents Unknown\n");
}
return 0;
}
You want to use ==—the equality operator—inside your if statements.
= is the assignment operator—it will set n to be 'o', so your first if statement always returns true ('o' is non-zero).

I'm trying to code a number guessing game

I got up to here, but I still need to use while loop somehow. "want to play again(y/n)" and "Illegal guess. Your guess must be between 1 and 200.Try again. Your guess?" don't seem to work. Please help me with the while/do-while loop and fix my two problems above. Thank you.
#include <stdio.h>
int main()
{
int i,number,guess,tries=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 tries 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;i<tries;i++) {
printf("\n\nYour guess? ");
scanf("%i",&guess);
if (guess==number) {
printf("**** CORRECT ****\n\nWant to play again(y/n) ");
scanf("%i",&answer);
if (answer=='y') {
return (i=0);
}
else (answer=='n'); {
printf("Goodbye, It was fun. Play again soon.");
}
}
else if (guess>number) {
printf("Too high!");
}
else if (guess<number) {
printf("Too low!");
}
else (guess>200); {
printf("Illegal guess. Your guess must be between 1 and 200.\nTry again. Your guess?");
}
}
printf("\n\nSorry, you ran out of tries.\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;
}
First, and most important, turn on warnings. You have several elementary mistakes in your code that would be caught with compiler warnings. They're unfortunately off by default. -Wall turns on the basic warnings. It's not "all" warnings, because this is C! -fsanitize=address -Wall -Wshadow -Wwrite-strings -Wextra -Wconversion -std=c99 -pedantic is a good set of warnings to work with.
You could put a loop around the loop, but that rapidly gets hard to maintain. Instead, put the game into a function and loop around that.
void do_game(int tries) {
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 tries.\n\n");
return;
}
Note how the game only has to concern itself with the game. No other logic or questions about playing another game. And it can immediately return when the game is over.
Then the rest of the program is pretty simple. Run the game in an infinite loop, break out of it when you're done.
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 tries 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;
}
There's a problem, and it's scanf. It's always scanf. scanf is such a problem, there's a whole FAQ for it.
scanf("%i") reads a single integer but not the following newline. That newline, and any other extra input, hangs around on stdin. A later scanf("%c", &answer); might then read that newline instead of their answer.
scanf("%i\n") does not solve the problem. That tells scanf to read an integer, then a newline, then look for another non-whitespace character. scanf is weird.
You're much better off reading the whole line with fgets and parsing it with sscanf. You can write a little utility function for that which gets into variadic arguments.
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);
}
Then use it just like scanf. It guarantees to read the whole line and not leave newlines or partial input on the buffer.
int guess;
printf("\n\nYour guess? ");
line_scanf("%i",&guess);
This is only a partial answer, but it can be a starting point. You really should have a reliable input function. Your scanf() won't do, even if you fix the obvious errors trying to get a character using %i, which is for integers. I won't go into details here, I wrote a document on this. (Basically, you will at least run into problems with unparsable input that scanf() will just leave unread.)
Here's an example how you could do reliable input for your usecase with comments along the way:
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}
Try to understand this function, read the manuals for functions you don't know or understand (google "man strtol" for example will find you a manual page for strtol()).
For reading your yes/no response, use fgets() as well, but of course this function will look different, like check if the input is only 1 character (the second one has to be '\n') and return this one character.
just because it's a bit of fun, here's a possible whole game working robustly:
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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;
}
}
}
This exercise is an exercise to ingrain in your mind why scanf is generally a bad choice for taking mixed user input! You can do it, but you must be very careful to account for any characters that remain in the input buffer (i.e. stdin) -- especially when taking character input... Why?
When you enter a value that is read by scanf, the '\n' will always remain in the input buffer (unless accounted for in your format string). Further, on a failed conversion -- all characters will remain in the input buffer. Further, the user can do something stupid like entering "4 is my guess" when prompted leaving is my guess\n for you to deal with.
Further, what if the user cancels input by pressing ctrl + d (or ctrl + z on windoze) generating a manual EOF? You must account for all possibilities for each and every input.
You must also use the correct format specifier to read input. You are not going to read 'y' or 'n' with %d or %i. When you want to read an int use %d when you want to read a char, use %c. You must also take into account that %c never skips leading whitespace.
(you beginning to understand why it's better to use fgets and then call sscanf for user input??)
How do you handle the characters that remain in the input buffer? Well generally you will use getchar() to read until you have read '\n' (generated by pressing Enter) or until EOF is encountered. You can make it easy on yourself by writing a short function like the following:
/* empty characters that remain in stdin */
void fflushstdin ()
{
for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
}
If you call fflushstdin after each input, you will always take care of any characters that remain. If you know chars remain from a prior input that have not been removed, then call it before taking input.
Don't use magic numbers in your code (e.g. 1, 5, 200), instead define any needed constants at the beginning of your code and use the constants in your code. Why? If they change, then you have a single readily accessible place to change them and you don't have to go picking though your code to find them. You can use a #define or an enum like the following:
enum {LOW = 1, TRIES = 5, HIGH = 200 };
The remainder of your problems are simply logic problems that you can work out. Incorporating the above, you can handle (what I think you are attempting to do) as follows:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
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 tries 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;
}
Example Use/Output
$ ./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 tries 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 tries, 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 tries, number was: 110
play again (y/n) n
Note, the above handles stupid user input (like onehundred) and adds number to the failure output to let the user know what he missed.
Look things over and let me know if you have further questions.
scanf("%i", ...) reads integers in base 10, not characters or strings.
You need to organize your loops. You have 2 main loops, one that runs while the user wants to keep playing, and another that runs while the a game is on.
You program in a nutshell:
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
}
You could start your loops within main a bit like this:
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');
There are probably as many ways to do this as there are numbers between 0 and 200. A good strategy is to start by writing comments in your C file that describe step by step what the program needs to do. Going through them one by one is much easier than having the program only in your head, especially when you are starting to code. It will get easier with time as you get used to juggle the concepts and basic blocks your mind.

Resources