0

I have an assignment I'm trying to finish. I'm new to C, and things aren't working as well as I'd hoped.

Our assignment is to write a small program to take input from a user through the command line, asking their name and then to take a guess at the magic number. We have a .txt document that has three names with associated numbers. Its three lines, on each line has a name, followed by a space, then the number. If the user's name doesn't equal one of those three, the magic number defaults to 12345.

After each guess, the program should say whether the number is too high or too low, and then quit (yeah, I don't know why either).

I've been debugging by printf statements, but can't figure out why it won't go into the comparison part of the code.

Any help or hints is greatly appreciated!

#include <stdio.h>
#include <string.h>

typedef struct {
    char list_name[20];
    int list_num;
}list;

    char user_name[20];
    int user_magic_num;


main(){

    FILE *fp;
    int i;
    list answers;

    //
    fp = fopen("answers.txt", "rb");
    if(fp == NULL)
        {
      perror("Error opening file");
      return(-1);
        }

    printf("\n Hi, what's your name? \n \n");
    scanf("%s",user_name);
    printf("\n Hello, %s! \n \n What's the magic number? \n \n",user_name);
    scanf("%d", &user_magic_num);
    printf("You guessed %d.\n\n", user_magic_num);

    for (i=0; i<=4;i++)
    {
        fgets(answers.list_name, 20, fp);
        puts(answers.list_name);
        if (strcmp(answers.list_name,user_name)==0)
        {
            printf("entered first if statement");
            if(user_magic_num==answers.list_num)
                 {
                printf("You guess correctly! %d is the magic number!", 
answers.list_num);
                break;
            }
            else if(user_magic_num>answers.list_num)
            {
                printf("Too high.");
            }
            else if(user_magic_num<answers.list_num)
            {
                printf("Too low");
            }
        }
        else
        {
            user_magic_num = 12345;
        }
    }
  return 0;
}

**************************************EDIT: #include #include

typedef struct list{
char list_name[20];
int list_num;
}list;

char user_name[20];
int user_magic_num;


main(){

FILE *fp;
int i;
list answers;

//
fp = fopen("answers.txt", "rb");
if(fp == NULL)
    {
  perror("Error opening file");
  return(-1);
    }

printf("\n Hi, what's your name? \n \n");
scanf("%s",user_name);
printf("\n Hello, %s! \n \n What's the magic number? \n \n",user_name);
scanf("%d", &user_magic_num);
printf("You guessed %d.\n\n", user_magic_num);

 while (fscanf(fp, "%s %d",answers.list_name, &answers.list_num) != EOF)
{
    //printf("%s\n", answers.list_name);
    if(strcmp(user_name, answers.list_name)==0)
    {
        if (user_magic_num == answers.list_num)
        {
            printf("\nYes! %d is the magic number!\n", user_magic_num);
            break;
        }
        else if (user_magic_num > answers.list_num)
        {
            printf("\nNope. Too high.\n");
            break;
        }
        else
        {
            printf("\nNope. Too low.\n");
            break;
        }
    }
    else
    {
        answers.list_num = 12345;
        printf("\nUser not recognized. Default magic number set.\n");
        if (user_magic_num == answers.list_num)
        {
            printf("Yes! %d is the magic number!", user_magic_num);
            break;
        }
        else if (user_magic_num > answers.list_num)
        {
            printf("\nNope. Too high.\n");
            break;
        }
        else
        {
            printf("\nNope. Too low.\n");
            break;
        }
    }
}

return 0; }

I just did this. It works now, but only for the first name in the txt file. The text file looks like this:

Bob 123
Mike 23
Rachel 345

But it only works if I type in Bob and then 123. But if I try Mike or Rachel, it treats it like on unknown and resets the magic number to 12345. I'm assuming this is something with fscanf that I'm doing wrong.

3 Answers3

0

There are some error in your loop block,First read the name and magic_number separately. then you have given extra else if check your last else if statement.

        else if(user_magic_num<answers.list_num)
         {
             printf("Too low");
         }

should be

     else 
         printf("Too low");

Some of modification in for loop:

    for (i=0; i<2;i++)
    {
      fscanf(fp,"%s %d",answers.list_name,&answers.list_num);
      puts(answers.list_name);
      if (strcmp(answers.list_name,user_name)==0)
      {
         if(user_magic_num==answers.list_num)
              {
              printf("You guess correctly! %d is the magic number!\n", 
              answers.list_num);
             break;
          }
         else if(user_magic_num>answers.list_num)
         {
             printf("Too high.\n");
         }
         else 
         {
             printf("Too low\n");
         }
      }
      else
        user_magic_num = 12345; 
   }
krpra
  • 476
  • 1
  • 4
  • 19
  • If the `user_name` does not match with first entry name in the file then the `user_magic_num` will be assigned to 12345 but the requirement is - If the user's name doesn't equal one of those three, the magic number defaults to 12345 – H.S. Oct 13 '17 at 03:44
  • The for loop`for (i=0; i<2;i++)` will run only two iterations but from the description of the problem - the text document has 3 names associated with number. – H.S. Oct 13 '17 at 03:48
  • @H.S. that was a typo – krpra Oct 13 '17 at 03:56
0

You can do:

#include <stdio.h>
#include <string.h>

typedef struct {
    char list_name[20];
    int list_num;
}list;

char user_name[20];
int user_magic_num;

int main(){

    FILE *fp;
    int i;
    list answers;
    int matched = 0;

    fp = fopen("answers.txt", "rb");
    if(fp == NULL)
    {
      perror("Error opening file");
      return -1;
    }

    printf("\n Hi, what's your name? \n \n");
    scanf("%s",user_name);
    printf("\n Hello, %s! \n \n What's the magic number? \n \n",user_name);
    scanf("%d", &user_magic_num);
    printf("You guessed %d.\n\n", user_magic_num);

    /*
     * i<3 - as per your description, your text document suppose to 
     * 3 names associated with numbers.
     * You can change it as per your requirement.
     */
    for (i=0; i<3; i++)
    {
        fscanf (fp, "%s %d", answers.list_name, &answers.list_num);
        if(feof(fp))
        {
           puts("EOF");     
           break;
        }

        if (strcmp(answers.list_name,user_name)==0)
        {
            if(user_magic_num==answers.list_num)
            {
                printf("You guess correctly! %d is the magic number!\n", answers.list_num);
        matched = 1;
                break;
            }
            else if(user_magic_num>answers.list_num)
            {
                printf("Too high.\n");
            }
            else 
            {
                printf("Too low.\n");
            }
        }
    }
    if (!matched)
    {
        user_magic_num = 12345;
        printf ("Not matched\n");
        /*
         * Considering not matched as error and returning -1
         */
        return -1;
    }
  return 0;
}
H.S.
  • 9,215
  • 2
  • 10
  • 25
  • Thanks, this is actually almost similar to what I just did (although yours is much more elegant). I used fscanf and strcmp. its working as it should now, thank you! – Michael Brown Oct 13 '17 at 03:54
  • although using fscanf in this way seems to only work with the first name on the list. anything else I enter as a user name treats it like it isn't on the list, even if I use a name from the list that is further down. I edited my post to show the most recent version. Yours works pretty much perfectly though, but I can't see why mine doesn't. – Michael Brown Oct 13 '17 at 04:09
0

Mike, there are a number of subtle issues to address, all a normal part of learning C. First a few generalities:

char user_name[20];   /* avoid global variables unless absolutely */
int user_magic_num;   /* required (that is almost never) */

Declare your variables in main and pass them as parameters to any functions as required. Declaring globals raises the risk of name-collisions and variable shadowing that can be very difficult to debug as your code grows.

main(){

main is a function of type int. It has two valid declarations recognized by the C standard:

int main (void)                    /* passes no arguments to main */

and

int main (int argc, char *argv[])  /* passes argc arguments in argv - use them */

(You will also see the equivalent int main (int argc, char **argv))

For a further discussion on main see: C11 Standard §5.1.2.2.1 Program startup (draft n1570). See also: What should main() return in C and C++?

Next:

  return(-1);   /* don't return negative values to the shell */

C provides two defined returns for main,

EXIT_SUCCESS     /* 0 */
EXIT_FAILURE     /* 1 */

(while it is shell dependent, returning negative values should be avoided)

Validate ALL input - period. Especially if it comes from a user (for all you know a cat could be stepping on the keyboard). All input functions have a return. At minimum, you must validate the return to know whether or not input has been successfully received. Failure to do so is an invitation for Undefined Behavior (that's a really bad thing...) Also, if you require input values within a certain range, then validate that the input is within that range.

Now to your problems. All line-oriented input functions (e.g. fgets and POSIX getline) read up to and including the trailing '\n'. It is included in the buffer they fill. Using fgets is the proper and recommended way to get lines of input (including user input). While the scanf family has its place, it is full or pitfalls, just waiting to trap new C-programmers. Read a line at a time into a buffer, and parse the values you need from the buffer. (using sscanf for that purpose is fine)

Your comparisons were failing because you didn't recognize your use of fgets was reading the entire line (or at least up to 19 chars) into answers.list_name, e.g.

fgets(answers.list_name, 20, fp);

Don't be stingy on buffers. Sure make them reasonable, but I'd rather be 256-chars to big, than 1-char too short... (Below I simply declare a 128-byte buffer to read each line of the file into.)

Logic. A quick logic diagram of how you will get input, what tests are needed on each variable, when to ask for additional input, how to respond to input, etc.. sketched out on an 8.5x11 with a pencil can save you hours of staring at the screen hoping inspiration strikes. (it rarely does when your just sitting at the screen guessing at changes and repeatedly recompiling -- only to have it fail again). Clear logic and a clear road-map of what you need to accomplish will save you time.

Putting the above together, and grabbing the trusty 8.5x11 and a pencil, it appears you were attempting to:

  1. take a user name as user input

  2. open and read through your file to match the user name, and read a magic number (error if name not found)

  3. take a magic number as user input

  4. prompt the user for a maximum of 5 guesses to guess the number

  5. providing too high and too low as hints for the next guess

Unanswered were, what to do if the name isn't found?

Below is a stab at what I took your intent to be. Look through the code, understand each line, if you don't -- then ask. note: the program expects the filename to read as the first argument to the program. Make use of that ability rather than hard-coding a path/filename in your code.

Also note the use of constants below. Never hardcode values (ironically called magic-numbers) in your code. Defining a constant or two up top provides a single convenient place where changes can be made if needed -- without having to pick through all variable and loop declarations to make needed changes.

(note: for scanf field width limits, you are just stuck -- those must be hard coded, which is another reason fgets (or getline) are preferred)

(note2: I intentionally left one line from your code commented with // and a question for you to answer)

#include <stdio.h>
#include <string.h>

#define MAXC 20         /* define constants when you need one */
#define MAX_TRIES 5
#define BUFSZ 128

typedef struct {
    char list_name[MAXC];
    int list_num;
} list;

int main (int argc, char **argv) {

    int i = 0,                  /* always good to initialize variables */
        name_found = 0,
        user_magic_num = 0;
    char user_name[MAXC] = "",
        buf[BUFSZ] = "";
    size_t len = 0;
    list answers = { "", 0 };
    FILE *fp = NULL;

    if (argc != 2) {    /* validate argument given on command-line */
        fprintf (stderr, "error: insufficient input.\n"
                         "usage: %s filename\n", argv[0]);
        return 1;
    }

    /* open / validate file open for reading */
    if (!(fp = fopen (argv[1], "r"))) {
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    printf ("\n Hi, what's your name?: ");
    if (scanf ("%19s", user_name) != 1) {       /* validate ALL input */
        fprintf (stderr, "error: invalid input or EOF - user_name.\n");
        return 1;
    }
    while (fgets (buf, BUFSZ, fp)) { /* read each line from file into buf */

        /* fgets reads and *includes* the trailing '\n' in buf, remove it */
        len = strlen (buf);                 /* get length */
        if (len && buf[len - 1] == '\n')    /* check trailing '\n' */
            buf[--len] = 0;                 /* overwite with nul-char */
        else {
            /* handle error - string too long */
        }

        /* separate buffer into answers.list_name and answers.list_num */
        if (sscanf (buf, "%19s %d", answers.list_name, &answers.list_num) != 2) {
            fprintf (stderr, "error: failed to parse name and num from line.\n");
            return 1;
        }

        if (strcmp (answers.list_name, user_name) == 0) {
            name_found = 1;                     /* flag - name found in file  */
            break;
        }
    }
    fclose (fp);         /* close file - you are done with it */

    if (!name_found) {
        fprintf (stderr, "error: name '%s' not found in file.\n", user_name);
        return 1;
    }

    printf ("\n Hello, %s! \n \n What's the magic number?: ",user_name);

    if (scanf ("%d", &user_magic_num) != 1) {   /* ditto */
        fprintf (stderr, "error: invalid input or EOF - user_magic_num.\n");
        return 1;
    }
    printf (" You guessed: %d\n\n", user_magic_num);

    for (i = 0; i < MAX_TRIES; i++)     /* avoid <= (unless required) */
    {
        if (user_magic_num == answers.list_num) {
            printf ("You guessed correctly! %d is the magic number!\n",
                    answers.list_num);
            return 0;
        }
        else if (user_magic_num > answers.list_num)
            printf ("Too high.\n\n");
        else
            printf ("Too low.\n\n");

        printf (" What's your next guess %s?: ", user_name);
        if (scanf ("%d", &user_magic_num) != 1) {
            fprintf (stderr, "error: invalid input - user_magic_num\n");
            return 1;
        }
        putchar ('\n');
    }
    fprintf (stderr, "Oops: tries exceed max (%d)\n", MAX_TRIES);

    return 0;
}

Example Use/Output

$ ./bin/magicnum2 dat/magicnum.txt

 Hi, what's your name?: Mike

 Hello, Mike!

 What's the magic number?: 100
 You guessed: 100

Too high.

 What's your next guess Mike?: 10

Too low.

 What's your next guess Mike?: 20

Too low.

 What's your next guess Mike?: 25

Too high.

 What's your next guess Mike?: 23

You guessed correctly! 23 is the magic number!

What if the user_name is not in the file?

$ ./bin/magicnum2 dat/magicnum.txt

 Hi, what's your name?: Frank
error: name 'Frank' not found in file.

Let me know if I failed to glean your intent from the question and I'm happy to help further.

Lastly, don't forget to enable compiler warnings each time you compile. That means at minimum -Wall -Wextra for gcc or /W3 (or /W4) for cl.exe (VS on windoze). For fuller warnings you can add -pedantic with gcc or use /Wall for cl.exe. Do not accept code until it compiles cleanly, without warning. You can learn more C just by reading and understanding what the compiler is telling you than you can from most tutorials...

David C. Rankin
  • 69,681
  • 6
  • 44
  • 72