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.