-5

I have a game for a class that made to show binary search. The code compiles and runs with no errors on my ide(atom) and the gcc compiler (gcc version 6.3.0 (MinGW.org GCC-6.3.0-1)). My professor is using Dev C++, and his compiler is (gcc 4.9.2). Unfortunately, his version will compile without an error but also fails when intaking the user's input from Normal Game. I am not skilled enough at debugging to figure out what is wrong. Any help is appreciated, and explanations would be incredible.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <ctype.h>



//--Menu--//
//user input for main menu
int userInput(){
  int var;
  scanf("%d", &var);
  return var;
}
//main menu for the user to decide what version they want to play
void menuMain(){
  printf("------------------------\n");
  printf("Menu\n");
  printf("1 - Normal Game\n");
  printf("2 - Efficency Mode\n");
  printf("3 - Quick Game\n");
  printf("4 - EXIT PROGRAM\n");
  printf("------------------------\n");
};
//good bye message
void menuGoodBye(){
  printf("---------------\n");
  printf("Good Bye!\n");
  printf("---------------\n");
}
//gets the users response and capitalizes it
char responseUser(int guess){
  char y;
  printf("---------------------------------------------\n");
  printf("Is it %d?\n",guess);
  printf("Please respond with Lower (L), Higher (H), or Correct (C).\n");
  scanf("%s",&y);
  y = toupper(y);
  return y;
}

//--Normal Game--//
//instructions for the user Normal Games
void instructions(int min,int max){
  printf("----------------------------------------------------------------------\n");
  printf("Pick a number from %d to %d and I will try and guess it.\n",min, 
  max);
  printf("If your number is lower, respond with a L.\n");
  printf("If your number is higher, respond with a H.\n");
  printf("If I guess correctly, respond with a C.\n");
  printf("----------------------------------------------------------------------\n");
}
//uses binary search for efficient gameplay
int efficentGuesser(int min, int max){
  return (max+min)/2;
}
//uses random numbers with updated minimums and maximums
int gameGuesser(int min, int max){
  int x;
  x=rand()%(max+1-min)+min;
  return x;
}
//the modular switch for the game
void gameSwitch(int (*method)(int, int),int min, int max){
  char response;
  int guess;
  do{
     guess = (*method)(min,max);
     response = responseUser(guess);
     switch (response) {
       case 'L':
         max= guess-1;
       break;
       case 'H':
         min= guess+1;
       break;
       case 'C':
       break;
       default:
       break;
     }
   }while(response != 'C');
}


//--Quick Game--//
//instructions for the quick game
void instructionsQuickGame(int min, int max){
  printf("----------------------------------------------------------------------\n");
  printf("Pick a number from %d to %d and I will try and guess it.\n",min, 
  max);
  printf("----------------------------------------------------------------------\n");
}
//search for a quick game
void quickGame(int (*method)(int, int),int x, int y){
  int input,guess,min,max, trys;
    input= userInput();

    clock_t begin = clock();
    min = x;
    max = y;

  do{
    guess=(*method)(min, max);
    if(input > guess){
      min = guess + 1;
    }else{
      max = guess - 1;
    }
    trys++;
  }while(input != guess);
  clock_t end = clock();

  printf("Wow,That took me %.8f seconds and %d trys!\n",(double)(end - 
  begin) / CLOCKS_PER_SEC,trys);

}

//--Modular Block--//
//basic building block for game, you can import functions modularly
void defaultGame(int (*method)(int, int), void (*s)(), void(*instructions) 
(int, int)){
  int min, max;
  min =0;
  max =100;
  (*instructions)(min,max);
  (*s)((*method),min,max);

}


//the actual code that runs
int main(){
  srand(time(0));
  int response;
  do{
    menuMain();
    response=userInput();
    switch (response){
      case 1:
        //defaultGame(method, what switch, what instructions)
        defaultGame(gameGuesser, gameSwitch, instructions);
      break;
      case 2:
        //defaultGame(method, what switch, what instructions)
        defaultGame(efficentGuesser, gameSwitch,instructions);
      break;
      case 3:
        //defaultGame(method, what switch, what instructions)
        defaultGame(efficentGuesser, quickGame, instructionsQuickGame);
      break;
      case 4:
        menuGoodBye();
      break;
      default:
        printf("Please Pick a number from the menu.\n");
      break;
    }
  }while(response != 4);

  return EXIT_SUCCESS;
}
bruno
  • 32,421
  • 7
  • 25
  • 37
Randy G.
  • 111
  • 7
  • 6
    `scanf("%s",&y);` is undefined behavior as `y` is a single `char`. Could be other issues, but this one is first I saw. – Eugene Sh. Jan 11 '19 at 16:51
  • Probably `scanf("%s",&y);` -> `scanf("%c", &y);` – Jabberwocky Jan 11 '19 at 16:53
  • This does not compile at all. – klutt Jan 11 '19 at 16:53
  • 1
    Next time, you should only paste a minimum portion of code snippet. – RandomEli Jan 11 '19 at 16:57
  • @Broman That's the issue for me. I cannot logistically test all gcc so I narrowed it down to the two that I know I use and the one my professor uses. Will you check your gcc for me. CMD gcc -v – Randy G. Jan 11 '19 at 16:57
  • `prog.c:24:2: error: ISO C does not allow extra ‘;’ outside of a function` – melpomene Jan 11 '19 at 16:59
  • @LingboTang noted. The reason I posted the code as a whole is it compiles on my end, and my professors end, but I can't find any errors until I use it using gcc 4.9.2. Even with those "errors" the code still compiles, and I have no idea why. – Randy G. Jan 11 '19 at 16:59
  • 1
    `y = toupper(y);` has undefined behavior if `y` is negative, which is a possibility since `y` is a `char`. – melpomene Jan 11 '19 at 17:03
  • It's called `run-time error`, `scan("%s", &y)` will cause `segmentation fault` when you input, because it will only scan your input as a string, not a char. Also, different version of compiler will support different behaviors. For ex, I recall that before g++ 4.8.x, g++ does not support regex quite well if my memory is correct. – RandomEli Jan 11 '19 at 17:04
  • `(max+min)/2` suffers from a potential integer overflow issue (signed integer overflow has undefined behavior in C). `min + (max - min) / 2` would be safer. – melpomene Jan 11 '19 at 17:04
  • `(*` `)` is redundant in `(*method)(min,max)`. You can just do `method(min, max)`. – melpomene Jan 11 '19 at 17:05
  • 3
    "I am not skilled enough at debugging to figure out what is wrong." Tip: Enable all compliers warnings: This code has "warning: ISO C does not allow extra ';' outside of a function [-Wpedantic]", "warning: conversion to 'char' from 'int' may alter its value [-Wconversion]", "warning: conversion to 'unsigned int' from 'time_t {aka long int}' may alter its value [-Wconversion]", This is a _good_ practice to narrow your coding mistakes. Your compiler may provide different feedback. Its faster than posting on SO. – chux - Reinstate Monica Jan 11 '19 at 18:54
  • @melpomene How is `max+min` safer than `max - min`? Both suffer from `int` OF over the `int` range. I suppose `max - min` is better if no negative values are used. – chux - Reinstate Monica Jan 11 '19 at 18:58
  • 1
    @Jabberwocky Rather than [suggest](https://stackoverflow.com/questions/54150809/why-does-this-code-work-on-gcc-6-3-0-and-not-4-9-2#comment95132756_54150809) `scanf("%c", &y);`, perhaps `scanf(" %c", &y);` (with a space) for the usual reasons. – chux - Reinstate Monica Jan 11 '19 at 19:03
  • @chux I think you've got that backwards. Also, I'm assuming non-negative values. – melpomene Jan 11 '19 at 19:05
  • @melpomene With only positive `int`, `a-b` will not OF - which on review, makes sense with this code. In _general_, `a-b` can readily OF as with `INT_MAX - -1`. – chux - Reinstate Monica Jan 11 '19 at 19:08
  • I get lot's of errors with gcc 8.2.0 – klutt Jan 11 '19 at 21:08

2 Answers2

2

Undefined Behavior (UB)

char y; ... scanf("%s",&y); is undefined behavior as "%s" directs scanf() to form a string of at least one non-white-space character plus an appended null character. char y is too small for that. @Eugene Sh.

Consider the following instead.

scanf(" %c",&y);

Other problems exists too, yet the above is a big one.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
0

The userInput() and responseUser() functions are defective; you need to fix them.

The fatal error in responseUser() is (as chux already mentioned in another answer) is to use the %s scan conversion specifier to a single character, char y.

Both functions ignore any and all issues with the input. They do not check if the conversion was successful (the return value from scanf()), and simply assume it was.


Since standard input is line-buffered, it is best to read the input line by line. (That is, when user types something, it is only passed to the program when they press Enter.) There are at least three ways to do this (fgets(), an fgetc() loop, and scanf()), but since OP is using scanf(), let's see how it is best used.

The "trick" is using conversion pattern " %1[^\n]%*[^\n]". This actually consists of three parts. The leading space tells scanf() to skip all whitespace, including newlines. The %1[^\n] is a proper conversion, an one-character string that is not a newline (\n). The target is an array of at least two characters, since one is taken by the character itself, and the other is needed for the end-of-string nul character (\0). The %*[^\n] is a skipped conversion, meaning it does not actually convert anything, but will skip any non-newline characters. Because it is skipped, and no literals or non-skipped conversions follow in the pattern, it is also optional.

The idea of that conversion pattern is that the space in front consumes all whitespace, including any newlines or empty lines left in the input buffer. Then, we convert an one-character string, and skip everything else on that line (but not the newline at the end of the line, which is consumed by the next one as part of skipping whitespace).

Here is an example function that returns the first character, converted to uppercase, of the input line; or EOF if an error occurs (for example, user presses Ctrl+D at the beginning of a line to end standard input):

int  input(void)
{
    char  first[2];

    if (scanf(" %1[^\n]%*[^\n]", first) == 1)
        return toupper( (unsigned char)(first[0]) );

    return EOF;
}

Note that toupper() takes a character code cast to unsigned char. (The reason for that boils down to the fact that the C standard does not say whether char is a signed type or not, but the <ctype.h> functions, including isalpha() and such, are defined as taking an unsigned char character code.)

Consider the following small main game menu program as an example:

#include <stdlib.h>
#include <locale.h>
#include <stdio.h>
#include <ctype.h>

int  input(void)
{
    char  first[2];
    if (scanf(" %1[^\n]%*[^\n]", first) == 1)
        return toupper( (unsigned char)(first[0]) );
    else
        return EOF;
}

void normal_game(void) { printf("<normal game played>\n"); }
void efficiency_game(void) { printf("<efficiency game played>\n"); }
void quick_game(void) { printf("<quick game played>\n"); }

int main(void)
{

    if (!setlocale(LC_ALL, ""))
        fprintf(stderr, "Warning: Your C library does not support your current locale.\n");

    while (1) {
        printf("------------------------\n");
        printf("Menu\n");
        printf("1 - Normal Game\n");
        printf("2 - Efficency Mode\n");
        printf("3 - Quick Game\n");
        printf("4 - EXIT PROGRAM\n");
        printf("------------------------\n");

        switch (input()) {

        case '1':
            normal_game();
            break;

        case '2':
            efficiency_game();
            break;

        case '3':
            quick_game();
            break;

        case '4':
            exit(EXIT_SUCCESS);

        case EOF:
            printf("No more input; exiting.\n");
            exit(EXIT_SUCCESS);

        default:
            printf("That is not a valid choice!\n");
        }
    }
}

If you compile (enabling warnings; I use -Wall -O2 with all versions of GCC) and run the above, you can experiment with how it responds if you type incorrect choices, or press Ctrl+D at the beginning of a line.

Note that this does limit the input to a single character.


The responseUser() function is now simple to implement:

int  responseUser(int guess)
{
    printf("---------------------------------------------\n");
    printf("Is it %d (C), higher (H), or lower (L)?\n", guess);

    while(1) {
        switch (input()) {
        case 'C': return  0;
        case 'H': return +1;
        case 'L': return -1;
        case EOF:
            printf("No more input, so aborting.\n");
            exit(EXIT_SUCCESS);
        default:
            printf("Please input C, H, or L.\n");
        }
    }
}

Note that we could also use case 'C': case 'c': ... above, to accept both lower and upper case letters, and drop the toupper() from the input() function.


Let's say you extend the game by allowing the user to first set the range of integers. So, we'll want to read numbers as input. The problem with scanf() in this case is that if the user types something other than a number, it is left in the buffer. Repeating the scanf() call will just retry converting the same input in the buffer, and will fail. (We can clear the input buffer by consuming input until a newline or an EOF is encountered, but that tends to be fragile, leading to behaviour where the program seems to "ignore" input lines, really being one line behind in processing input.)

To avoid that, it is best to use the fgets() approach instead. Fortunately, that approach allows us to accept the input in more forms than one. For example:

#define  MAX_LINE_LEN  200

int choose_range(int *min_to, int *max_to)
{
    char  buffer[MAX_LINE_LEN], *line;
    int   min, max;
    char  dummy;

    printf("What is the range of integers?\n");

    while (1) {
        /* Read next input line. */
        line = fgets(buffer, sizeof buffer, stdin);
        if (!line)
            return -1; /* No more input; no range specified. */

        if (sscanf(line, " %d %d %c", &min, &max, &dummy) == 2 ||
            sscanf(line, " %d %*s %d %c", &min, &max, &dummy) == 2 ||
            sscanf(line, " %*s %d %*s %d %c", &min, &max, &dummy) == 2) {
            if (min <= max) {
                *min_to = min;
                *max_to = max;
            } else {
                *min_to = max;
                *max_to = min;
            }
            return 0; /* A range was specified! */
        }

        printf("Sorry, I don't understand that.\n");
        printf("Please state the range as minimum to maximum.\n");
    }
}

Here, we use buffer array as a buffer to buffer a whole input line. The fgets() function returns a pointer to it, if there is more input. (If there is no more input, or a read error occurs, it returns NULL.)

If we do read a line, we try the conversion pattern " %d %d %c" first. The %c converts a single character, and is used as a sentinel test: If the entire pattern converts, the result is 3, but there was at least one extra non-whitespace character following the second integer. If sscanf() returns two, it means there was just the two integers (possibly followed by whitespace, say a newline), and that's what we want.

If that pattern didn't work, we try " %d %*s %d %c" next. This is similar, except now there is a skipped conversion, %*s, between the two integers. It can be any sequence of non-whitespace characters, for example to or ... The idea is that this will match input like 5 to 15 and 1 .. 100.

If that did not work either, we try " %*s %d %*s %d %c". I'm sure you already know what it is for: to match input like from 5 to 15 or between 1 and 100, but ignoring the words, only converting the integers.

The function itself returns 0 if a range was specified, and nonzero otherwise. You can use it like this:

    int  minimum_guess, maximum_guess;

    if (choose_range(&minimum_guess, &maximum_guess)) {
        printf("Oh okay, no need to be rude! Bye!\n");
        exit(EXIT_FAILURE);
    } else
    if (minimum_guess == maximum_guess) {
        printf("I'm not *that* stupid, you know. Bye!\n");
        exit(EXIT_FAILURE);
    }

    printf("Okay, I shall guess what number you are thinking of,\n");
    printf("between %d and %d, including those numbers.\n",
           minimum_guess, maximum_guess);

There is also a potential problem with how OP is trying to time a game.

The clock() function returns the amount of CPU time elapsed, in units of CLOCKS_PER_SEC per second. (That is, (double)(stopped - started) / (double)CLOCKS_PER_SEC returns the number of seconds of CPU time between started = clock() and stopped = clock().)

The problem is that it is CPU time, not real world time. It is only the duration during which the program did actual computation, and does not include waiting for user input.

Furthermore, it can have a rather low resolution. (On most systems, CLOCKS_PER_SEC is one million, but the value clock() returns increments in large steps. That is, if you call it repeatedly in a loop and print the values, on some systems it prints the same value a lot of times, then jumps to a much bigger value and stays there for a long time, and so on; often the precision is just a hundredth of a second or so.)

If we wanted to measure wall clock time at high precision, we can use clock_gettime(CLOCK_REALTIME) on most systems, and some Windows kludge on Windows:

#define _POSIX_C_SOURCE  200809L
#include <time.h>

static struct timespec  mark;

static inline void wallclock_mark(void)
{
    clock_gettime(CLOCK_REALTIME, &mark);
}

static inline double wallclock_elapsed(void)
{
    struct timespec  now;
    clock_gettime(CLOCK_REALTIME, &now);
    return (double)(now.tv_sec - mark.tv_sec)
         + (double)(now.tv_nsec - mark.tv_nsec) / 1000000000.0;
}

(If you disagree with "kludge", just look at the code needed.)

With the above code (including the additional code you need from that other answer linked to, if you use Windows and your C library does not support POSIX.1 clock_gettime()), you can measure wall clock taken using e.g.

double  seconds;

wallclock_mark();
/* Do something */
seconds = wallclock_elapsed();

With that, we can easily measure the wall clock duration of each game.

However, if we wanted to split that into how much time is taken for computation, and how much time was taken to wait/process user input, it becomes problematic, because those things can happen at the same time in real life. If we wanted to do that, we'd better switch to e.g. Ncurses, so we can receive each keypress when they occur.

In short, the clock() use is not necessarily wrong; it is just an issue of what one is trying to measure, and conveying that correctly to the user as well.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86