User Tools

Site Tools


pfk:c_programming

C Programming

C is the third language I decided to teach my son. The plan was simple; follow the theme of introducing components of the language one at a time while building interesting programs. In this case, I had already decided to introduce printf, fgets, rand and if – and using these four components we would immediately recreate the number guessing game in C.

C is the first language where there is no arbitrary limit to what you can write on modern computers. All modern computers can understand C code, or rather you can compile C code targeting virtually any modern computer system in use today.

Who is this tutorial for?

If you have completed the Shell Scripting tutorial, then you should be okay with this one. It's just that we aren't going to explain anything except Makefiles.

What will you learn?

As stated above, C is the first language without limits, and with power comes responsibility. Thus, all the code we show will be considered “safe”, i.e. we will show input using fgets and not scanf.

You will learn how to write simple, well-organized code.

And… you will learn to write amazing video games!

Hello Neo

I began by explaining that C was easy, like BASIC, but you needed to write a “starter program”, like in Assembly. So here I present the C starter program in place of language component #1 (output).

main.c

#include <stdio.h>

int main(void) {

    printf("Hello, Neo!\n");
    
    return(0);
}

Compile the program by typing “gcc main.c -o main” or “clang main.c -o main”.

This program illustrates the 'boilerplate' of #include and int main() in C. I explained to my son about include files, and about why int main(void) has a return 0 statement (because it will return int, hence int“ main() – int main() being the function declaration).

It may be interesting to point out some of the mistakes Neo originally made when I gave him this program to type out for his homework.

  • He forgot to put semicolons on some of the lines.
  • He forgot to put a newline inside the printf.
  • He forgot to put a 0 at the end of the return statement.
  • He forgot to include the closing bracket.

After I showed him how to fix each error from the compiler's list of errors, the program worked and he seemed pleased.

User Input

Following the same path we took in BASIC, the strategy was to introduce user input. We'll also introduce the IF/THEN/ELSE structure here.

filename 'input.c'

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

int main(void) {
    char str[80];

    printf("What is your name? ");

    fgets(str,80,stdin);

    if (strcmp(str,"Neo")==0) {
        printf("Hello, master.\n");
    } else {
        printf("Hi, %s.\n", str);
    }

    return 0;
}

A Bug by any other name

When we first wrote this program, Neo entered his name as “neo” and it didn’t work, because the function that read in the user's input kept the newline as a newline. We need to remove the newline by adding the following command after fgets:

str[strcspn(str,"\n")]=0;

This will set str[#] to zero, making it a zero terminated string. Which # is #? The one which equals a \n. Therefore the above will change the newline at the end of the string into a zero, and it will work.

The next time he played it he entered his name as 'neo', and it worked! But I asked him, what happens if you type 'Neo' instead? It didn't work.

I then explained to him how to do an OR comparison like this:

if ( (strcmp(str,"Neo")==0) || (strcmp(str,"neo")==0) ) …

After we made this change he entered his name as “Neo” and it worked! So I asked him, what if he typed 'NEO instead? It didn't work. I explained to him that in this case we would need to use the tolower function to check every character in the string and make it lowercase before we compared it. This would be better than testing for every possible combination of characters with IF statements. he seemed to understand, and we added the following code:

for(int i = 0; i < 80; i++) {
    str[i] = tolower(str[i]);
}

Final Listing

final source code for 'input.c'

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

int main(void) {
    char str[80];

    printf("What is your name? ");

    fgets(str,80,stdin);
    str[strcspn(str,"\n")]=0;

    for(int i = 0; i < 80; i++) {
        str[i] = tolower(str[i]);
    }

    if (strcmp(str,"Neo")==0) {
        printf("Hello, master.\n");
    } else {
        printf("Hi, %s.\n", str);
    }

    return 0;
}

In his version of this program, which I asked him to type in from scratch (from a printout), Neo made several mistakes. In addition to repeating many of the same mistakes from the hello.c program, he also accidentally wrote /n instead of \n for newline, and he attempted to write code such as:

    if (strcmp(str=="Neo") {
    …
    printf("Hello, ", char, "%s!");

However after these mistakes were fixed the program worked and I counted it as his progress.

Random Numbers

I introduced the concept of random numbers by modifying the starter program:

rand() example

	
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
	int r = 0;

	printf("Hello, Neo!");

	srand(time(0));
	r = (rand() % 100) + 1;

	printf("Here’s a random number: %d\n", r);

	return(0);
}

I explained that as always, we had two parts to random numbers: One, we had to initialize the random number generator, and two, we had to generate a random number within our range using a random number generator. In comparison to the way things were done in Assembly or BASIC, this was closer to the way things worked in Assembly since we were given a large range which we had to tame. The number range was large enough that modulus was an acceptable solution (for our purposes) so that’s what I showed him. It was also somewhat convenient in that it represented a third way of using a random source to come up with an answer in our desired range.

Makefiles

At about this time I had to explain makefiles to him because we were about to have multiple files in a single project. I explained to him each line had three components:

Makefile structure

target_filename : dependancy1 dependancy2 …
	{command}

For example, a Makefile to make hello.c might look like this:

hello:  hello.c hello.h
	cc hello.c -o hello

What this does is, if hello.c or hello.h get changed, or the file target 'hello' doesn't exist, it will re-make the target file (in this case, 'hello').

The makefile we began using during the next section was just like this:

Makefile for 'decnum' project

# Makefile for decnum project
decnum : decnum.o ../mystdlib/mystdlib.o
	cc decnum.o ../mystdlib/mystdlib.o -o decnum

decnum.o : decnum.c
	cc -c decnum.c -o decnum.o

mystdlib.o : ../mystdlib/mystdlib.c ../mystdlib/mystdlib.h
	cc -c ../mystdlib/mystdlib.c -o ../mystdlib/mystdlib.o

clean :
	rm -f decnum decnum.o ../mystdlib/mystdlib.o

With the above we can “make decnum” and “make clean”.

Functions and Header Files

Neo already knew about functions and header files because we needed to include several header files so that various functions would work. But now I set about to teach him how to make his own header file and put functions in a separate file. We created two files: mystdlib.h and mystdlib.c. Inside, I explained to him, we needed to do two important things:

  1. Put a function definition in the header file.
  2. Put the code for the function in the code file.

I began by explaining the math behind random numbers to him, since he was a little hazy. We came up with the following functions to help us:

#include "mystdlib.h"

int mymax(int a, int b) {
    if (a > b) return a;
    return b;
}

int mymin(int a, int b) {
    if (a < b) return a;
    return b;
}

Inside mystdlib.h, I explained, we needed to have function definitions:

mystdlib.h

int mymax(int a, int b);
int mymin(int a, int b);

That was enough, I told him, to be able to use those functions the .c file.

Next we wrote a function to find random numbers. The first version had two functions that looked like this:

int myrand(int a, int b) {
    int min = mymin(a,b);
    int max = mymax(a,b);
    int diff = max – min;

    int r = (rand() % (diff+1)) + min;
    return r;
}

One of his first questions was, why diff+1? The reason is, if we used diff, and happened to roll a number that evenely divided into diff, the modulus would be zero – since we needed an inclusive range we just add 1 to diff; this will cut off the range at one higher than diff (which would become zero). This gives us an inclusive range of between 0 and diff. When we add min to these, we get the original range of numbers between a and b inclusive.

We also made a myrand_init(void) function that called srand().

I then pointed out that we could write this function in a slightly different way, and we re-wrote it to look like this:

int myrand_init = 0;
int myrand(int a, int b) {
    if (myrand_init = 0) {
        srand(time(0));
        myrand_init = 1;
    }

    int min = b;
    int diff = abs(a-b);
    
    if (a < b) min = a;
    int r = (rand() % (diff+1)) + min;
    return r;
}

My motivation for rewriting the function was to explain the differences moreso than improve on the function. I explained how absolute value worked to create a difference. I explained the logic of the if statement was better than the function way of doing it (since it was easier to understand), and I explained how the program would initialize the myrand_init variable to zero so that the first time it was run it would srand() itself and you wouldn’t need an initialization routine. These features seemed to make sense to him.

int vs double

To test my son’s knowledge I asked him to write some functions to do addition, subtraction, multiplication and division. This was to be a test of his understanding. Here’s what he came up with:

addnum.c

#include <stdio.h>
#include "mystdlib.h"

int main(void) {
    int a = 5;
    int b = 2;
    int addans = add2numbers(a,b);
    int subans = sub2numbers(a,b);
    int mulans = mul2numbers(a,b);
    int divans = div2numbers(a,b);

    printf("Add: %d\n", addans);
    printf("Sub: %d\n", subans);
    printf("Mul: %d\n", mulans);
    printf("Div: %d\n\n", divans);

    return 0;
}

The Makefile we used is shown at the end of section 3.3; the header file will be left as an exercise for the reader.

There were still a few mistakes. He forgot newlines again and made some other minor errors, but was mainly correct in his thinking over the program flow. The one annoying thing he did was to use variable names like e, g, a, x without any rhyme or reason. I had to remind him that in C, choosing a proper variable name can help you remember what the variable represents.

I then pointed out to him, that 5 divided by 2 isn’t really 2, and that we should include a modulus function. We did that by adding the following code in the appropriate place:

    int modans = mod2numbers(a,b);
    ...
    printf("Div: %d with remainder %d\n", divans, modans);

This seemed like child’s play to him so I told him, but wait, I meant to have the program print 2.5 – not “two and one remainder”. I told him to research how to do this on his own on the Internet.

About twenty minutes later he came up with the following program:

divnum.c

#include <stdlib.h>
#include <stdio.h>
#include "../mystdlib/mystdlib.h"

int main(void) {
    int a = 5;
    int b = 2;

    double num = div2numbersf(a,b);

    printf("Here: %f\n", num);
    return 0;
}

During this time we had moved mystdlib into it’s own directory, hence the different include path.

As part of this program, the following function was added to mystdlib:

double div2numbersf(double a, double b) {
    return a/b;
}

The important thing to note here is the use of double (and %f in the main program). We could also use float, but it’s my habit to use double whenever we need a float, just in case we would need the extra capacity.

The Number Guessing Game

The previous chapters have introduced all of the basics of a number guessing game, in the same manner we wrote one in BASIC and then in Assembly. Since my son had so much experience writing such code in other languages, I told him that I wanted him to write it entirely on his own this time. I did give him two hints: He needed to learn the atoi() function, and if he ran into trouble, he could use the “goto” statement to write the program using the same structure as his BASIC program. This is what he came up with:

numguess.c

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

int main(void) {
    printf("Hello, Guys!\n\n");

    int c = 0;
    int g = 0;
    srand (time(0));
    int n = rand() % 10 + 1;
    char str[10];

loop:
    printf("Guess a number: ");
    fgets(str,10,stdin);
    str[strcspn(str,"\n")]=0;

    g = atoi(str);

    if (g == n) {
        printf("You got the right number!\n");
        return 0;
    }

    if (g > n) {
        printf("Your number is too big.\n");
    } else {
        printf("Your number is too small.\n");
    }

    goto loop;
}

This program is slightly modified. In the original, there were some bugs and cosmetic issues:

  • He forgot to use newline several times,
  • He had a newline after “Guess the number: ”,
  • He had put the final goto into the else block with the “too small” message, and
  • He did not have a return statement after the user won.

Adding a Guess Counter

After we went through the bugs in his program, I suggested he alter his coding style a bit. I made the following changes; one, initialize all variables at the start of the function before using them or doing anything else:

int main(void) {
    int c = 0;
    int g = 0;
    int n = 0;
    char str[10];

    srand (time(0));
    n = rand() % 10 + 1;
    printf("Hello, Guys!\n\n");
    ...

Second, we changed g to “guess” and “n” to “the_number”. I pointed out that he didn’t use the variable c and we removed it. Then we added a guess counter; we added int guesses_left = 7; in the variables section, and the following code to the main loop:

loop:
    printf(“You have %d guesses left.”, guesses_left);
    printf("Guess a number: ");

    ...

    guesses_left = guesses_left -1;
    if (guesses_left < 1) {
        printf(“You have on more guesses left! Game over.”);
        return 0;
    }

goto loop;

I did also explain to him that you could write something like “if (–guesses_left < 1)”, but we chose to leave the program as-is for now.

GOTO vs. Proper Looping

A lot of people think GOTO is evil because it can be used to create convoluted code that is difficult to follow. This isn’t a good argument because ultimately all code executes in Assembly language and relies on JUMP and BRANCH calls (which are essentially GOTO statements). Any program can be poorly designed; the use of GOTO does not mean a program is poorly designed. In this case for example, the use of GOTO acts in the place of a while (true) loop. I explained to my son that you could replace “loop:” and “goto loop” with a while(true) {} block. Ex:

    while (true) {
        printf(“You have %d guesses left.”, guesses_left);
        printf("Guess a number: ");

	...

        guesses_left = guesses_left -1;
        if (guesses_left < 1) {
            printf(“You have on more guesses left! Game over.”);
            return 0;
        }
    }

From a conceptual standpoint, the use of GOTO actually makes more sense here than while (true); it is therefore a mistake to blindly discourage the use of GOTO. The use of while here has a condition that is always true; it therefore operates as a goto command and a label; there is zero functional difference. Secondly, this is not necessarily obvious from the use of while(), since while() takes a condition. The use of while() would be better served if the game loop exited upon some condition. If we used a statement such as “while (playing=1) {}”, and then set playing to zero when we wanted to exit, then the use of while could be justified. However even in such a case it is almost a kludge in order to justify the use of while. It also adds complexity to the program because you are discouraged from exiting the game during the main loop, and must find a way to structure your if statements so as to provide a consistent experience.

In any case, the use of GOTO is extremely rare in modern C programs. Nevertheless it is there if you need it. We will leave this discussion there and say that it is your choice how you want to write programs. It can not be a bad thing to use goto, that would be like saying it is a bad thing to program in BASIC or Assembly language. Each language has it’s own unique use case, and each programmer has his own unique style of programming.

Zombie Nights

At around this time, I told my son, I want you to make a game. Any game. Any game you want, in C. It took him about a week to come up with something good. Most of the time he spent slacking and I knew from experience that he didn't have a good idea. Once he did have a good idea, we talked about it briefly (hold that thought) and he began coding. A day or two later he came up with this:

Survive Three Nights Game v1

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

int main(void) {
    int food = 0;
    int money = 0;
    int ifDark = 0;
    int hunger = 10;
    char name[40];
    char option[20];

    printf("Name: ");
    fgets(name,40,stdin);
    name[strcspn(name, "\n")]=0;

options:
    if (ifDark > 20){
        printf("\n");
        printf("It's getting dark.\n");
        printf("So you should sleep now.\n");
        printf("Time: ");
        for (int i = 0; i < 50; ++i){
            for (int i = 0; i < 100; ++i){}
                printf("#");
        }
        goto options;
    }

    if (hunger < 3) {
        printf("Warnings: Your hunger is too low.\n");
    }

    printf("\n");

    if (hunger < 1){
        printf("Oh no! You ran out of hunger! The end!\n");
        return 0;
    }

    printf("%s's money: %d\n", name, money);
    printf("Food: %d\n", food);
    printf("Hunger: %d\n", hunger);
    printf("\n");
    printf("Do you want to //eat// ?\n");
    printf("Go in the //store// ?\n");
    printf("Or go to //work// ?\n");
    printf("Do: ");

    fgets(option,20,stdin);
    option[strcspn(option, "\n")]=0;

    if (strcmp(option, "work")==0){
        printf("\n");
        printf("You go to work at your favorite restaurant\n");
        hunger--;
        money=money+10;
        ifDark++;
        goto options;
    }

    if (strcmp(option, "store")==0){
        printf("\n");
        printf("You enter the store.\n");
        hunger--;
        ifDark++;
        goto store;
    }

    if (strcmp(option, "eat")==0 && food > 0){
        printf("\n");
        printf("You eat your food.\n");
        food--;
        hunger++;
        ifDark++;
        goto options;
    }

    if (strcmp(option, "exit")==0){
        printf("\n");
        printf("Exiting ...\n");
        return 0;
    }

    printf("Something is wrong. Maybe you didn't type that properly.\n");
    goto options;

store:
    printf("\n");
    printf("What do you want to buy (bread/steak, or leave)? ");

    char buy[20];
    fgets(buy,20,stdin);
    buy[strcspn(option, "\n")]=0;

    if(strcmp(option, "bread")==0 && (money > 4)){
        printf("\n");
        printf("You pay for your loaf of bread.\n");
        money=money-5;
        food++;
        ifDark++;
        goto store;
    }

    if (strcmp(option, "steak")==0 && money > 9){
        printf("\n");
        printf("You pay for your yummy steak.\n");
	money=money-10;
        food=food+2;
        hunger++;
        ifDark++;
	goto store;
    }

    if (strcmp(option,"leave")==0) {
        goto options;
    }

    printf("\n");
    printf("Something's wrong. Ether you don't have any money for that or you didnt type it properly.\n");
    ifDark++;
}

For a first attempt at an original game in C, this is pretty good. There are of course some glaring issues which we will go through now. The plan is to first make the game work as-is then try to see if we can rewrite parts of it to make it better.

Analysis

Line 18 to 29: This is an infinite loop. What Neo forgot to do was add an ifDark=0 inside the loop. We added one just before the “goto options;” statement.

Line 54 to 84: The logic here didn't make sense to me so I stopped the player from getting hungry while eating, and we increased the hunger per food to 5.

Line 93 to 95: buy is not used in the if statements below. For the sake of convenience I removed the char buy[20] and just used the reference from iasmw

Line 97 to 118: Some more balance changes were made here.

Line 123: The program ends prematurely so we added a “goto store;” in front of line 122.

Zombie Nights v2

After bugfixing his code, my son added a few new features to his game. It was at this point he told me he was “done”, so I had a look at refactoring his program. First, here is the complete listing of his original code:

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

int main(void) {
    int nights = 3;
    int food = 0;
    int money = 0;
    int ifDark = 0;
    int hunger = 10;
    int havegun = 0;
    int havesword = 0;
    int bullets = 0;
    char name[40];
    char option[20];
    int zombies = 1;
    printf("Name: ");
    fgets(name,40,stdin);
    name[strcspn(name, "\n")]=0;

    srand (time(0));
    zombies = rand() % 5 + 1;

options:
    if (nights == 0) {
        printf("You survived the zombie invasion! Yay!\n");
        printf("Good work! The End.\n\n");
        return 0;
    } else {
        printf("\n\n\n *** You must survive for %d more nights! ***\n\n", nights);
    }

    if (ifDark > 20){
    printf("\n");
    printf("It's getting dark.\n");
    printf("The zombies are coming to your house!\n");
    goto zombie;
    }

    if (hunger < 3){
        printf("Warnings: Your hunger is too low.\n");
    }

    printf("\n");

    if (hunger < 1){
        printf("Oh no! You ran out of hunger! The end!\n");
        return 0;
    }

    if (havegun > 0){
        printf("Gun: Yes\n");
    } else {
        printf("Gun: No\n");
    }

    if (havesword > 0){
        printf("Sword: Yes\n");
    } else {
        printf("Sword: No\n");
    }

    printf("Money: %d\n", money);
    printf("Food: %d\n", food);
    printf("Hunger: %d\n", hunger);
    printf("\n");
    printf("Do you want to //eat// ?\n");
    printf("Go in the //store// ?\n");
    printf("Or go to //work// ?\n");
    printf("Do: ");

    fgets(option,20,stdin);
    option[strcspn(option, "\n")]=0;

    if (strcmp(option, "work")==0){
        printf("\n");
        printf("You go to work at your favorite restaurant\n");
        hunger--;
        money=money+10;
        ifDark++;
        goto options;
    }

    if (strcmp(option, "store")==0){
        printf("\n");
        printf("You enter the store.\n");
        hunger--;
        ifDark++;
        goto store;
    }

    if (strcmp(option, "eat")==0 && food > 0){
        printf("\n");
        printf("You eat your food.\n");
        food--;
        hunger=hunger+5;
        ifDark++;
        goto options;
    }

    if (strcmp(option, "exit")==0){
        printf("\n");
        printf("Exiting ...\n");
        return 0;
    }

    printf("Something is wrong. Maybe you didn't type that properly.\n");
    goto options;

store:
    printf("\n");
    printf("What do you want to buy (bread, steak, sword, gun, bullet or leave)? ");

    fgets(option,20,stdin);
    option[strcspn(option, "\n")]=0;

    if(strcmp(option, "bread")==0 && (money > 4)){
        printf("\n");
        printf("You pay for your loaf of bread.\n");
        money=money-3;
        food++;
        goto store;
    }

    if (strcmp(option, "steak")==0 && money > 9){
        printf("\n");
        printf("You pay for your yummy steak.\n");
        money=money-10;
        food=food+4;
        goto store;
    }

    if (strcmp(option, "gun")==0 && money > 29){
        printf("\n");
        printf("You pay for your gun.\n");
        money=money-30;
	havegun=havegun+1;
	goto store;
    }

    if (strcmp(option, "sword")==0 && money > 9){
        printf("\n");
        printf("You pay for your sword.\n");
        money=money-10;
        havesword=havesword+1;
        goto store;
    }

    if (strcmp(option, "bullet")==0 && money > 29){
        printf("\n");
        printf("You pay for your bullets.\n");
        money=money-1;
        bullets++;
        goto store;
    }

    if (strcmp(option,"leave")==0) {
        goto options;
    }

    printf("\n");
    printf("Something's wrong. Ether you don't have any money for that or you didnt type it properly.\n");
    goto store;

zombie:
    printf("\n");
    printf("\n");
    printf("Zombies left: %d\n", zombies);
    printf("What wepon do you want to use (sword, gun)? ");

    fgets(option,20,stdin);
    option[strcspn(option, "\n")]=0;

    if (strcmp(option, "gun")==0 && bullets > zombies){
        printf("\n");
        printf("You kill the zombie(s) without getting hurt.\n");
        bullets=bullets-zombies;
        hunger++;
        ifDark = 0;
        nights--;
        goto options;
    }
    else if (strcmp(option, "sword")==0) {
        printf("\n");
        printf("You killed a zombie.\n");
        zombies=zombies-1;
        havesword=0;
        
        if (bullets > zombies || bullets == zombies) {
            bullets=bullets-zombies;
            printf("You killed the rest of the zombies with your gun.\n");
            ifDark = 0;
            nights--;
            goto options;
        } else {
            printf("You could not kill the zombies and the zombies ate your brain.\n");
        }
    }

    printf("The end.\n");
}

Refactoring the Program

1. Try replacing GOTOs with while(1), continue and break.

The first idea is that any time someone is calling GOTO, we check to see if it is part of a loop which can be replaced by for, or while.

while (true)
    if (nights == 0) { ...
    if (ifDark > 20){ ...
    if (hunger < 3){ ...
    if (hunger < 1) { ...

    if (havegun > 0){ ...
    if (havesword > 0) { ...
    printf("Money: %d\n", money); ...
    printf("Do: ");

    fgets(option,20,stdin);
    option[strcspn(option, "\n")]=0;

    if (strcmp(option, "work")==0){
        ...
        continue;
    }

    if (strcmp(option, "store")==0) {
        ....
        continue;
    }

    if (strcmp(option, "eat")==0 && food > 0){
        ...
        continue;
    }

    ....
}

exit(0);

2. Try to move goto sections into functions

Since the options section looks like a main loop, we can call 'store:' and then 'zombie:' as functions instead, then 'continue' in the main loop once the function returns.

Again inside store(), enclose the function in a while(true) loop, then replace all instances of “goto store” with “continue”. “goto options” will be replaced with “break;”. When the loop is broken, the function will return, and the next line in the main loop is “continue”, i.e. the program flow will effectively “goto options;”.

Other things we needed to do in this step were to move the variables into global scope so that the functions could access them. This is not ideal, but it is a temporary step we took while refactoring the program.

Moving variables into global scope temporarily

...
#include <time.h>

void store(void);
void zombie(void);
#define true 1

    int nights = 3;
    int ifDark = 0;
    int hunger = 10;
    int havesword = 0;
    int havegun = 0;
    int bullets = 0;
    char option[20];
    int zombies = 1;
    int food = 0;
    int money = 0;

int main(void) {
    ...

With the above changes made, I also corrected a few spelling mistakes and made the game a bit harder (by increasing dark more often). The result is as follows; an amazing game, written by Neo, age 11, in C!

Zombie Nights Final

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

void store(void);
void zombie(void);

#define true 1
int nights = 3;
int ifDark = 0;
int hunger = 10;
int havesword = 0;
int havegun = 0;
int bullets = 0;
char option[20];
int zombies = 1;
int food = 0;
int money = 0;

int main(void) {
    char name[40];
    printf("Name: ");
    fgets(name, 40, stdin);
    name[strcspn(name, "\n")] = 0;

    srand(time(0));
    zombies = rand() % 5 + 1;

    while (true) {
        if (nights == 0) {
            printf("You survived the zombie invasion! Yay!\n");
            printf("Good work! The End.\n\n");
            return 0;
        } else {
            printf("\n\n\n *** You must survive for %d more nights! ***\n\n", nights);
        }

        if (ifDark > 20) {
            printf("\n");
            printf("It's getting dark.\n");
            printf("The zombies are coming to your house!\n");
            zombie();
            continue;
        }

        if (hunger < 3) {
            printf("Warnings: Your hunger is too low.\n");
        }

        printf("\n");

        if (hunger < 1) {
            printf("Oh no! You ran out of hunger! The end!\n");
            return 0;
        }

        if (havegun > 0) {
            printf("Gun: Yes\n");
        } else {
            printf("Gun: No\n");
        }

        if (havesword > 0) {
            printf("Sword: Yes\n");
        } else {
            printf("Sword: No\n");
        }

        printf("Money: %d\n", money);
        printf("Food: %d\n", food);
        printf("Hunger: %d\n", hunger);
        printf("\n");
        printf("Do you want to //eat// ?\n");
        printf("Go in the //store// ?\n");
        printf("Or go to //work// ?\n");
        printf("Do: ");

        fgets(option, 20, stdin);
        option[strcspn(option, "\n")] = 0;

        if (strcmp(option, "work") == 0) {
            printf("\n");
            printf("You go to work at your favorite restaurant\n");
            hunger--;
            money = money + 10;
            ifDark++;
            continue;
        }

        if (strcmp(option, "store") == 0) {
            printf("\n");
            printf("You enter the store.\n");
            hunger--;
            ifDark++;
            store();
            continue;
        }

        if (strcmp(option, "eat") == 0 && food > 0) {
            printf("\n");
            printf("You eat your food.\n");
            food--;
            hunger = hunger + 5;
            ifDark++;
            continue;
        }

        if (strcmp(option, "exit") == 0) {
            break;
        }

        printf("Something is wrong. Maybe you didn't type that properly.\n");
        // continue;	// omitted
    }

    // fallthrough -- reached on 'break;' above.
    printf("\n");
    printf("Exiting ...\n");
    return 0;
}

void store(void) {
    while (true) {

        printf("\n");
        printf("What do you want to buy (bread, steak, sword, gun, bullet or leave)? ");

        fgets(option, 20, stdin);
        option[strcspn(option, "\n")] = 0;

        if (strcmp(option, "bread") == 0 && (money > 4)) {
            printf("\n");
            printf("You pay for your loaf of bread.\n");
            money = money - 3;
            food++;
            continue;
        }

        if (strcmp(option, "steak") == 0 && money > 9) {
            printf("\n");
            printf("You pay for your yummy steak.\n");
            money = money - 10;
            food = food + 4;
            continue;
        }

        if (strcmp(option, "gun") == 0 && money > 29) {
            printf("\n");
            printf("You pay for your gun.\n");
            money = money - 30;
            havegun = havegun + 1;
            continue;
        }

        if (strcmp(option, "sword") == 0 && money > 9) {
            printf("\n");
            printf("You pay for your sword.\n");
            money = money - 10;
            havesword = havesword + 1;
            continue;
        }

        if (strcmp(option, "bullet") == 0 && money > 29) {
            printf("\n");
            printf("You pay for your bullets.\n");
            money = money - 1;
            bullets++;
            continue;
        }

        if (strcmp(option, "leave") == 0) {
            break;
        }

        printf("\n");
        printf("Something's wrong. Ether you don't have any money for that or you "
               "didnt type it properly.\n");
        // continue; not necessary at end of loop
    }

    return; // exit function, return control to main loop
}

void zombie(void) {
    while (true) {
        printf("\n");
        printf("\n");
        printf("Zombies left: %d\n", zombies);
        printf("What wepon do you want to use (sword, gun)? ");

        fgets(option, 20, stdin);
        option[strcspn(option, "\n")] = 0;

        if (strcmp(option, "gun") == 0 && bullets > zombies) {
            printf("\n");
            printf("You kill the zombie(s) without getting hurt.\n");
            bullets = bullets - zombies;
            hunger++;
            ifDark = 0;
            nights--;
            break;
        } else if (strcmp(option, "sword") == 0) {
            printf("\n");
            printf("You killed a zombie.\n");
            zombies = zombies - 1;
            havesword = 0;

            if (bullets > zombies || bullets == zombies) {
                bullets = bullets - zombies;
                printf("You killed the rest of the zombies with your gun.\n");
                ifDark = 0;
                nights--;
                break;
            } else {
                printf("You could not kill the zombies and the zombies ate your brain.\n");
                exit(0);
            }
        }
    } // while

    // fallthrough on break; above
    return; // return control to main program
}

I went through all of these changes with my son and I explained that it was better to write code this way than another way; although, it would be better if we didn't have so many globally scoped variables, so if you wanted to fix the program up a bit more you could bring them back into scope in main and maybe try passing them as function parameters. To do this efficiently you would need to be able to return multiple values at once; thus the next section will be about structs (which allow you to pass and return data as a package).

Structures

The major benefit of C over the above two, besides having proper looping and better function handling, is the idea of a struct. What is a struct? First, recall that you can have a pointer, which acts as an array:

Define as:

char	str[80];

Use as:

printf(“String here: %s.\n”, str);

What’s going on here is that str is being used, not as a variable in and of itself, but as a pointer to the 80 byte address space reserved by str. That’s why a string function must return pointer to char:

Copy one string to another:

char *strcpy (char *dest, char *src);

Example:

    char str[80];
    char str2[80];

    fgets(str, 80, stdin);
    str2 = strcpy(str2, str);		/* copies str into str2 */

Above, the function strcpy returns a char pointer, which replaces the str2[80] declaration earlier.

Another example (this time from <time.h>) is the asctime() function:

Usage:

char *asctime(const struct tm *timeptr);

This function returns a pointer to a string which represents the day and time of the structure timeptr. So, this needs to return a string for you, and that is done by passing you a string pointer. How do you know it’s a string pointer? Because it’s of size char, and if you add 1 to it, it will point to the next memory location. This “pointer” logic is exactly the same as indirect addressing on the 6502 – instead of loading the value of a memory location (i.e. a variable) you are loading from the memory location stored in the variable (indirect addressing).

But wait a moment; the function takes a “struct tm *” as an argument. What’s that?

By way of example let’s discuss the functions localtime() and gmtime(). In localtime(), the value of a timer is broken up into the structure tm and expressed in the local time zone; gmtime() is the same except it expresses it in GMT and not the local time zone.

So what’s a struct tm? Looking at the man page for localtime() we see the following data structure:

struct tm {
   int tm_sec;         /* seconds,  range 0 to 59          */
   int tm_min;         /* minutes, range 0 to 59           */
   int tm_hour;        /* hours, range 0 to 23             */
   int tm_mday;        /* day of the month, range 1 to 31  */
   int tm_mon;         /* month, range 0 to 11             */
   int tm_year;        /* The number of years since 1900   */
   int tm_wday;        /* day of the week, range 0 to 6    */
   int tm_yday;        /* day in the year, range 0 to 365  */
   int tm_isdst;       /* daylight saving time             */   
};

This is the big one, folks. What do we see here? We see, in one variable, an entire set of data. So whereas you type char str[80] and then you can access it via char[i], or the equivalent, (char+i)*, the reason why this can be a string is because each unit I is one “char” in size. So it conveniently can represent a string. You see, strings don’t really exist in C. We pretend that an array of characters is a string and traverse it char by char until we find a zero. We then treat that as a string. You see, behind the scenes, there are a lot of for-loops going over “strings” character by character.

In the same way, once you define the struct as above, you can do this:

tm varname;

Now, “varname” is a pointer, not to a char as in char *str, but to a structure of other variables. You could even do “tm varname[10]” if you wanted to have an array of such structs. Then, when you did a varname[i], “i” would point not to the next char or byte, but to the start of the next struct in the array.

The way you access this is by doing something like;

    time_t rawtime;
    struct tm *info;
    char buffer[80];

    time( &rawtime );

    info = localtime( &rawtime );
    printf("Current local time and date: %s", asctime(info));

You can also modify individual parts of the struct like this:

info->tm_mon = 1;	/* Change the month to 0 (January). */

This gives you the ability to hold the “time”, including month, date, year, and everything else you need, in one convenient variable.

Why are structures useful?

When I first learned C, I found struct to be the most useless part of the entire language. However, after many years of programming in C++, Java, and so on, I came to understand the value of struct. As it turns out, struct can be used to implement object oriented programming; and in this sense, C++ does not really add anything new to C from a functional standpoint other than some minor types of protection against snooping; but really nothing that impacts your average developer. I wouldn’t say there’s no reason to use C++. If your goal is to do object oriented programming, C++ has a myriad of quality-of-life improvements that make it a much better language for that than C. However, structs themselves are capable of doing most of what you can do in C++. For example structs can contain function pointers – pointers to functions. This means that you can have a struct which represents an object type, and the functions that are attached to that struct can do different things based on the type of object, which is not known at compile time. A great example is to write a fruit stand app where you have a “struct fruit”, and in that you have a variable called “price” and a function called “peel”. You would use it like this:

struct fruit {
	float price;
	void (*func_ptr)(int) = &func;
};

You would define true not defined in cthe variable like this:

fruit a_fruit;

You would define its type as follows (for an orange):

	a_fruit->price = 0.75;
	a_fruit->(*func_ptr) &orange_func;

By setting the address of the function pointer to the orange function, you can now have a whole list or array of “fruit”s, each of which acts differently based on their type; and each of which remembers it’s own price for sale.

The uses are myriad; You could add a struct tm to an event log, you could create a car struct with information about cars, maybe even in a video game, and give each car it’s own steering speed, acceleration and braking speeds.

Of course, you don’t need structs to do any of these things. It’s just a means of organizing similar information.

For me, I never really found a use for structs in C. Maybe that’s because I didn’t really spend a lot of time with C and I started using C++ very quickly. In any case, it is my duty to inform you of this important language feature.

The incredible array of C libraries

One of the most useful “features” of C is it’s unique combination of raw power, age, and use. As a result, C has probably the largest codebase in the world. There are so many libraries of functions available for C that you will often spend more time learning about a library you want to use than actually writing your program. For example, in stdlib.c, you have 28 standard functions including:

  • int atoi(const char *str)
    • Converts the string pointed to, by the argument str to an integer (type int).
  • long int strtol(const char *str, char **endptr, int base)
    • Converts the string pointed to, by the argument str to a long integer (type long int).
  • void free(void *ptr)
    • Deallocates memory previously allocated by a call to calloc, malloc, or realloc.
  • void *malloc(size_t size)
    • Allocates the requested memory and returns a pointer to it.
  • int atexit(void (*func)(void))
    • Causes the specified function func to be called when the program terminates normally.
  • void exit(int status)
    • Causes the program to terminate normally.
  • char *getenv(const char *name)
    • Searches for the environment string pointed to by name and returns the associated value to the string.
  • int system(const char *string)
    • The command specified by string is passed to the host environment to be executed by the command processor.
  • void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
    • Sorts an array.
  • int abs(int x)
    • Returns the absolute value of x.

These functions can be considered vital to C programming, but they aren’t really a part of C itself, technically. They’re part of the standard library. Even exit()! The meaning is that commands (staments) in C can be created by You. You can make your own C langauge by writing enough functions to make it look like your own kind of language. If you really wanted to, you could create a function called PRINT, but you would still need to use brackets i.e. () to hold the string! Some other important C libraries you should look into are of course, stdio.h, time.h, math.h, string.h and others. Your knowledge of these libraries will define how good you are at C.

Welcome to C

At this point you, the reader, should have an excellent knowledge of C. My advice from this point forward is to keep learning! Identify what it is you want to program and then research the C libraries you need to approach writing that program. Don't stop experimenting with small, ineteresting programs – even if only once in a while. Also, you may wish to learn more about C++ at this stage; even if you just use “C with Classes”, it's a bit easier to type out than using structs.

Also, don't forget to write out proper header files for your .c files. We didn't do this in Zombie Nights Final (above), but you should always do it in any program of size.

Please enjoy your time with C into the future!

pfk/c_programming.txt · Last modified: 2019/05/18 08:24 by serena