Continuing from my comments, the key takeaways from your code are (1) you cannot learn C by guessing at syntax, compiling, over and over again, (2) validate ALL input to your program by checking the return of all input functions and validating the value you receive, (3) enable compiler warnings and then read, understand and correct each warning before attempting to compile again, and (4) do not accept code until it compiles cleanly, without warning.
Since your code includes #define _CRT_SECURE_NO_WARNINGS
, it is apparent you are on windows using cl.exe
(either from cmd.exe
or from VS-Code). For learning basic programming, close VS-Code, open the Command Line provided by your VS (or SDK) install, and don't worry about using the IDE again, until you have mastered compiling from the command line and understand your compiler options. See cl.exe C/C++ Compiler Options, or type cl /?
at the command prompt.
From the command line, your basic compile string should be similar to:
cl.exe /nologo /W3 /Ox /Tc mysource.c
(/W3
enable most warnings, /Ox
enable all optimizations)
I find it helpful to not clutter my c-source directory with .obj
and .exe
files so I create two additional directories /obj
and /bin
for the object and executable files. You then use the /Fo
and /Fe
options to tell the compiler to put the object files and exe files in the proper directories, e.g.
cl /nologo /W3 /Ox /Foobj/mysource /Febin/mysource /Tc mysource.c
That will put mysource.obj
in the obj
directory and mysource.exe
in the bin
directory.
You must have the logic for your code clear in your head before you sit behind the keyboard and start pecking away. (See: (1) above). The easiest way to keep it straight by drawing a simple logic diagram for your code and identify what values you will handle in main()
and then what will be handled in each function()
. You don't need anything fancy, an 8.5x11 sheet of paper and pencil will do. After you have a clear road map for what each part of your code will do, then sit down and start pecking away.
Putting that logic to test, you can rework your code so it makes a lot more sense than it currently does, e.g.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>
#define MAXGUESSES 5
void Instructions();
int PlayGuess (char solution);
char GetLetter();
int CompareLetters (char guess, char solution);
int main (void)
{
int i = 0,
numgames = 0;
char solution;
FILE *inp = fopen ("letterList.txt", "r");
if (inp == NULL) {
fprintf (stderr, "error: file open failed 'letterList.txt'.\n");
return 1;
}
Instructions(); /* give instructions */
/* get number of games the user wants to play */
printf("Please enter the number of games you want to play: ");
if (scanf ("%d", &numgames) != 1) {
fprintf (stderr, "error: invalid input - numgames.\n");
return 1;
}
putchar ('\n');
for (i = 0; i < numgames; i++)
{
/* get letter to guess from file */
if (fscanf (inp, " %c", &solution) == EOF || solution < ' '
|| '~' < solution) {
fprintf (stderr, "error: invalid character - solution.\n");
return 1;
}
printf (" ==> Game %d <==\n\n", i + 1);
PlayGuess (solution);
printf("The letter was '%c'!\n\n", solution);
}
fclose (inp);
return 0; /* main() is type int and returns a value */
}
void Instructions()
{
printf ("Welcome to Letter Guess\n"
"To begin you will enter the number of games you want "
"to play (1 – 4 games)\n"
"You have 5 chances to guess each letter\n"
"Let's begin\n\n");
}
int PlayGuess (char solution)
{
int numGuesses = 0;
char guess;
while (numGuesses < MAXGUESSES)
{
guess = GetLetter();
if (CompareLetters (guess, solution))
return 1;
numGuesses = numGuesses + 1;
}
printf ("You have run out of guesses\n");
return 0;
}
/* get a letter and validate it is good */
char GetLetter()
{
char guess = 0,
tmp;
printf ("Enter a guess: ");
if (scanf (" %c", &tmp) != EOF && ' ' <= tmp && tmp <= '~')
guess = tmp;
return guess;
}
/* compare the guess and the solution
* return a 1 if they are the same
* message based on before or after alphabetically
* return a 0 if the guess and answer are not the same
*/
int CompareLetters(char guess, char solution)
{
if (guess == solution) /* answer is correct */
{
printf ("Thats it!\n\n");
return 1;
}
if (guess < solution)
printf ("The letter you are trying to guess comes after '%c'\n",
guess);
else
printf ("The letter you are trying to guess comes before '%c'\n",
guess);
printf ("Try again\n\n");
return 0;
}
Example Compile String for cl.exe
(VS)
>cl /nologo /W3 /Ox /Foobj/guessletter /Febin/guessletter /Tc guessletter.c
Example Use/Output
> bin\guessletter.exe
Welcome to Letter Guess
To begin you will enter the number of games you want to play (1 – 4 games)
You have 5 chances to guess each letter
Let's begin
Please enter the number of games you want to play: 2
==> Game 1 <==
Enter a guess: k
The letter you are trying to guess comes before 'k'
Try again
Enter a guess: c
The letter you are trying to guess comes after 'c'
Try again
Enter a guess: d
Thats it!
The letter was 'd'!
==> Game 2 <==
Enter a guess: e
The letter you are trying to guess comes after 'e'
Try again
Enter a guess: g
The letter you are trying to guess comes before 'g'
Try again
Enter a guess: f
Thats it!
The letter was 'f'!
Look things over and think about how to approach programming in C. It is an exact language. It is up to you to account for all characters in all input buffers as well as your memory use. If you don't know what parameters a library function takes, or what type and value it will return, or how to use it, look it up. The man pages are available at, e.g. msdn fscanf, fwscanf or scanf(3): input format conversion)
Let me know if you have further questions.
Accepting Input In Any Case, Converting to Lowercase
To accept input in any case and convert the value to lowercase so that guess
is always lowercase in your code, you need to change only one-line:
/* get a letter and validate it is good
* (convert letter to lowercase)
*/
char GetLetter()
{
char guess = 0,
tmp;
printf ("Enter a guess: ");
if (scanf (" %c", &tmp) != EOF && ' ' <= tmp && tmp <= '~')
guess = tolower (tmp);
return guess;
}
note: For ASCII characters, the 6th-bit is the 'case bit', if it is 1
, the character is lowercase, 0
uppercase. tolower
can simply be written as:
unsigned c_tolower (unsigned c)
{
if ('A' <= c && c <= 'Z')
c ^= (1 << 5);
return c;
}