I'll bite, being as I do C every day.
1. You're not checking the return value of scanf, which means that when it fails, you don't know that. From here:
Return value
Number of receiving arguments successfully assigned (which may be zero in case a matching failure occurred before the first receiving argument was assigned), or EOF if input failure occurs before the first receiving argument was assigned.
I don't use scanf at all, but if I recall correctly the argument just remains unassigned. You need to check the return value, and if it's anything but 1, print an error.
2. Rand doesn't return 1 to 32767 but rather 1 to RAND_MAX, again grabbed from cppreference. RAND_MAX is implementation defined. In general C random numbers suck, and if you want to do anything even remotely cool with them you either need to use C++ (which has a somewhat involved random number facility that is powerful enough to do basically anything) or bring in a library for it.
The % operator is remainder. That is: 9%5 = 4 because 9 divided by 5 is 1 remainder 4. If you take any number and do number % x, you'll get numbers between 0 and x-1. Some examples:
0%5 = 0
1%5 = 1
2%5 = 2
3%5 = 3
4%5 = 4
5%5 = 0
6%5 = 1
And so on. Note that 0%5 and 5%5 both equal 0, because there's no remainder.
So, to get numbers between 1 and 9 the formula is (rand()%9)+1. We derive this because rand()%9 gives numbers between 0 and 8, which is 1 too small on both ends. I don't know if the parens are optional, but with rarely used C operators it's usually best to be explicit about it because "oops precedence" surprises abound and it's not worth consulting a precedence table when you work with them.
Your resources on C random numbers don't go beyond this because going beyond this isn't built into C, and they don't explain in detail because modulus/remainder operator is a bit fiddly. Also anything more complicated with random numbers beyond exactly this formula and "get me a number between 0 and 1", in general, needs a treatment of probability distributions.
3. & is the addressof operator. But before getting to that, you can just pass NULL to time, and avoid the variable entirely. NULL comes from (among many others) stdlib.h. That is, time(NULL) does what you want (and you don't need the & there. It is somewhat best to think of NULL as magic at your level, but if you're curious NULL is just a way of writing 0 that you use when you're dealing with pointers to make it clear that you're dealing with pointers and if you want every C programmer ever to eviscerate you you can also do time(0)).
I don't know if you've done pointers yet, so this might be confusing for the time being. If you know what they are, skip this paragraph. In C there's ints, floats, and etc. as you're familiar with, but also pointer to int, float, etc. which are references to an int. If you want you can pass variables to a function by saying that the function takes a pointer, then passing the address of the variable in memory to the function, and the function can write to that variable by dereferencing the pointer, as C calls it. Examples below.
The & operator in C has two uses. If it's between variables--a binary operator--like x & y, it's bitwise and, which you won'd tneed to use for anything for a long time yet. If it's unary, that is before a variable like you put - before negative nuymbers, it's the addressof operator, which takes a variable and produces a pointer to that variable.
The opposite is unary *, which converts a pointer into a not-pointer...if it's on the left of = you're saying "write to what this pointer is pointed to", but on the right, "read this pointer".
This makes more sense with examples. So, some examples, with comments:
int x = 0; /* Normal int. */
int *p = NULL; /* A pointer to an int, currently pointing at NULL, i.e. nothing. */
x = 5; /* You know this. */
p = &x; /* p now points at x. */
*p = 6; /* Set x to 6, but do it via p. */
int y = *p; /* y = 6, but instead of just using x, we read what p is pointing to. Since p is pointing at x, we get x's value. */
y += 1; /* Normal increment. Nothing happens to x. */
p = &y; /* Now p points at y. */
x = *p; /* Now x = 7, like above with y. */
What time does is something like the following:
time_t now = now(); /* Not real, just magically gets now in some magic way. */
if(parameter != NULL)
*parameter = now;
return now;
So if you just pass NULL, it won't also write to the variable. There is probably a very good historical reason as to why it does both, but you will probably never actually use the write-to-a-variable functionality.
As for 4, some comments:
Your code is the first time I have seen puts used in practice, and I had to look up what it does. It is much more typical to use printf("A line of text\n"), and you pay no extra cost in using printf that way unless you start using format strings.
You can use a while loop instead of a do loop. Do loops are kinda scary, because they are always guaranteed to run once. It's not bad that they always run once, but when I see a do loop my first immediate question is "Why does this need to run once? What don't I understand?" By the nature of C getting loops wrong can cause weird hard to debug crashes, so it's always an interesting question. Additionally, the while loop puts the condition at the top, which makes it immediately obvious that the loop ends when there's no more guesses.
But you can actually do one better. Observe (not exactly plug and play with yours, but will get the concept):
for(int i = 1; i <= maxGuesses; i++) {
int currentGuess;
/* get guess. */
if (currentGuess == goal) {
printf("Yay!\n");
return 0;
}
printf("%i guesses remaining\n", maxGuesses - i);
}
printf("Sorry, you lose.\n");
Which both makes it immediately clear what the loop is for, and makes sure that the maximum number of times the loop can run is clear to anyone used to reading C, since that's (almost) the idiomatic way to make a loop run up to a certain number of times.
I like one true brace style. That is:
Instead of what you do where the brace is always on the second line. In sighted programming land, this is the typical style, but additionally for us you don't have to always read the left brace with the screen reader.
Additionally to that, for one-line if statements, I (and many sighted people) do it as:
if(condition)
printf("Yay\n");
else
printf("No!\n");
Which avoids the noisy braces. If you're sighted or have indentation indication on, the indentation indicates which statement goes with which if statement. In general I don't suggest dropping braces for loops, and even though you can put it on the same line as the if statement if you want, putting it on the same line makes skimming harder because you can't just find out what the long if condition does without reading the whole condition first with the screen reader. Plus, putting it on the same line is a really long line which is bad if you're collaborating with sighted colleagues since it can be wider than their screen.
Initialized guessed. In general, always initialize variables. You don't have to if you have the right compiler warnings on and have configured compilation to treat warnings as errors, as modern compilers will give you notice that you used something without initializing. But I doubt you've done either, and in general you won't want to do either for other reasons at the stage you're at, so you will avoid much weird behavior by initializing numbers to 0 and pointers to NULL.
I could possibly go on , but overall this is actually pretty good code.
EDIT: my example loop needed return 0, not break, because if you break you'll tell them that they failed.
My BlogTwitter: @ajhicks1992