Kleines Beispielprojekt in C



  • Hallo Freunde der Sonne,

    Ich habe vor von Java auf C "umzusteigen", sofern man bei meiner allgemein geringen Erfahrung überhaupt von umsteigen sprechen kann. Auf jedenfall möchte ich jetzt C lernen und habe in die wichtigsten Themengebiete schon reingeschnuppert und ein kleines Rechenspiel entwickelt. Mich würde interessieren, wie Ihr als professionelle C Entwickler das Projekt realisiert hättet, also ob ihr mir da grobe Richtungsweisungen geben könntet. Insbesondere mit der calculate()-Funktion bin ich nicht zufrieden, da redundanter Code, sehe aber keinen besseren Lösungsansatz. Hier der Code:

    Kompiliert mit MinGW: gcc main.c

    main.c

    #include <stdio.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <time.h>
    #include "exercise.c"
    #include "player.c"
    
    void info()
    {
        printf("programmed by Henry Weinert\n");
        printf("\"0\" will close a menu or leave the game with saving\n\n");
    }
    
    int main()
    {
        srand(time(NULL));
    
        struct Player player;
        init(&player);
        printf("[4] save\n");
        printf("[5] info\n\n");
        while(1)
        {
            printf("[1] show character\n");
            printf("[2] calculate\n");
            printf("[3] attributes\n");
            printf("input: ");
            int input;
            scanf("%d", &input);
            printf("\n");
    
            if(input == 1)
            {
                show(&player);
            }
            else if(input == 2)
            {
                printf("number of exercises: ");
                scanf("%d", &input);
                printf("\n");
                for(int i = 0; i < input; i++) 
                {
                    calculate(&player);
                }
            }
            else if(input == 3)
            {
                attributes(&player);
            }
            else if(input == 4)
            {
                save(&player);
            }
            else if(input == 5)
            {
                info();
            }
            else if(input == 0)
            {
                save(&player);
                break;
            }
        }
    }
    

    player.c

    struct Player
    {
        // most important values
        int level;
        int experience;
        int experience_needed;
    
        char ranks[10][30]; 
        int rank;
        int rank_points;
        int rank_points_needed;
    
        int gold;
        int attribute_points;
    
        // attributes
        int intelligence;
        int wisdom;
        int dexterity;
        int intelligence_price;
        int wisdom_price;
        int dexterity_price;
    };
    
    void init(struct Player* player)
    {
        FILE* file = fopen("save.txt", "r");
        if(file)
        {
            char line[12];
            fgets(line, 12, file);
            player->level = atoi(line);
            fgets(line, 12, file);
            player->experience = atoi(line);
            fgets(line, 12, file);
            player->experience_needed = atoi(line);
            fgets(line, 12, file);
            player->rank = atoi(line);
            fgets(line, 12, file);
            player->rank_points = atoi(line);
            fgets(line, 12, file);
            player->rank_points_needed = atoi(line);
            fgets(line, 12, file);
            player->gold = atoi(line);
            fgets(line, 12, file);
            player->attribute_points = atoi(line);
            fgets(line, 12, file);
            player->intelligence = atoi(line);
            fgets(line, 12, file);
            player->wisdom = atoi(line);
            fgets(line, 12, file);
            player->dexterity = atoi(line);
            fgets(line, 12, file);
            player->intelligence_price = atoi(line);
            fgets(line, 12, file);
            player->wisdom_price = atoi(line);
            fgets(line, 12, file);
            player->dexterity_price = atoi(line);
        }
        else
        {
            player->level = 1;
            player->experience = 0;
            player->experience_needed = 10;
            player->rank = 1;
            player->rank_points = 0;
            player->rank_points_needed = 500;
            player->gold = 0;
            player->attribute_points = 0;
            player->intelligence = 1;
            player->wisdom = 1;
            player->dexterity = 1;
            player->intelligence_price = 10;
            player->wisdom_price = 10;
            player->dexterity_price = 10;
    
            file = fopen("save.txt", "w");
        }
        fclose(file);
    
        strcpy(player->ranks[0], "Pupil");
        strcpy(player->ranks[1], "Student");
        strcpy(player->ranks[2], "Bachelor");
        strcpy(player->ranks[3], "Master");
        strcpy(player->ranks[4], "Doctor");
        strcpy(player->ranks[5], "Professor");
        strcpy(player->ranks[6], "Researcher");
        strcpy(player->ranks[7], "Renowned Scientist");
        strcpy(player->ranks[8], "Nobel Prize Winner");
        strcpy(player->ranks[9], "Mathematician");
    }
    
    void save(struct Player* player)
    {
        FILE* file;
        file = fopen("save.txt", "w");
        fprintf(file, "%d\n", player->level);
        fprintf(file, "%d\n", player->experience);
        fprintf(file, "%d\n", player->experience_needed);
        fprintf(file, "%d\n", player->rank);
        fprintf(file, "%d\n", player->rank_points);
        fprintf(file, "%d\n", player->rank_points_needed);
        fprintf(file, "%d\n", player->gold);
        fprintf(file, "%d\n", player->attribute_points);
        fprintf(file, "%d\n", player->intelligence);
        fprintf(file, "%d\n", player->wisdom);
        fprintf(file, "%d\n", player->dexterity);
        fprintf(file, "%d\n", player->intelligence_price);
        fprintf(file, "%d\n", player->wisdom_price);
        fprintf(file, "%d\n", player->dexterity_price);
        fclose(file);
    }
    
    void show(struct Player* player)
    {
        printf("Level        %d\n", player->level);  
        printf("Rank         %s\n\n", player->ranks[player->rank - 1]);
        printf("Experience   %d / %d\n", player->experience, player->experience_needed);
        printf("Rank Points  %d / %d\n", player->rank_points, player->rank_points_needed);
        printf("Gold         %d\n", player->gold);
        printf("AP           %d\n\n", player->attribute_points);
    
        printf("Intelligence %d\n", player->intelligence);
        printf("Wisdom       %d\n", player->wisdom);
        printf("Dexterity    %d\n\n", player->dexterity);
    }
    
    void increase_level(struct Player* player)
    {
        while(player->experience >= player->experience_needed)
        {
            player->experience -= player->experience_needed;
            player->level++;
            player->attribute_points += 1;
            player->experience_needed = 5 * player->level * player->level + 5 * player->level;
            printf("\n");
            printf("----------\n");
            printf("-LEVEL UP-\n");
            printf("----------\n");
        }
    }
    
    void increase_rank(struct Player * player)
    {
        while(player->rank_points >= player->rank_points_needed && player->rank < 10)
        {
            player->rank_points -= player->rank_points_needed;
            player->rank++;
            player->rank_points_needed = 250 * player->rank * player->rank + 250 * player->rank;
            printf("\n");
            printf("---------\n");
            printf("-RANK UP-\n");
            printf("---------\n");
        }
    }
    
    void reward(struct Player* player, int multiplier)
    {
        int experience_reward = (player->intelligence * (1 + 0.1 * player->rank)) * multiplier;
        int rank_points_reward = (player->wisdom * (1 + 0.1 * player->rank)) * multiplier;
        int gold_reward = (player->dexterity * (1 + 0.1 * player->rank)) * multiplier;
    
        player->experience += experience_reward;
        player->rank_points += rank_points_reward;
        player->gold += gold_reward;
    
        printf("Exp  + %d\n", experience_reward);
        printf("RP   + %d\n", rank_points_reward);
        printf("Gold + %d\n", gold_reward);
    
        increase_level(player);
        increase_rank(player);
    
    }
    
    void calculate(struct Player* player)
    {
        int difficulty = (rand() % 4) + 1;
        int amount_of_reward = exercise(difficulty);
        if(amount_of_reward > 0)
        {
            reward(player, difficulty);
        }
        else 
        {
            printf("Wrong\n");
        }
        printf("\n");
    }
    
    void attributes(struct Player *player)
    {
        int input = 1;
        while(input != 0)
        {
            printf("Gold %d   Points %d\n", player->gold, player->attribute_points);
            printf("Intelligence   %d   Price %d\n", player->intelligence, player->intelligence_price);
            printf("Wisdom         %d   Price %d\n", player->wisdom, player->wisdom_price);
            printf("Dexterity      %d   Price %d\n", player->dexterity, player->dexterity_price);
            printf("input: ");
    
            scanf("%d", &input);
            printf("\n");
    
            if(input == 1)
            {
                if(player->attribute_points > 0)
                {
                    player->attribute_points--;
                    player->intelligence++;
                    player->intelligence_price = 5 * player->intelligence * player->intelligence + 5 * player->intelligence;
                    printf("Intelligence improved!\n");
                }
                else if(player->gold >= player->intelligence_price)
                {
                    player->gold -= player->intelligence_price;
                    player->intelligence++;
                    player->intelligence_price = 5 * player->intelligence * player->intelligence + 5 * player->intelligence;
                    printf("Intelligence improved!\n");
                }
                else
                {
                    printf("Not enough Gold and no attribute points!\n");
                }
                printf("\n");
            }
            else if(input == 2)
            {
                if(player->attribute_points > 0)
                {
                    player->attribute_points--;
                    player->wisdom++;
                    player->wisdom_price = 5 * player->wisdom * player->wisdom + 5 * player->wisdom;
                    printf("Wisdom improved!\n");
                }
                else if(player->gold >= player->wisdom_price)
                {
                    player->gold -= player->wisdom_price;
                    player->wisdom++;
                    player->wisdom_price = 5 * player->wisdom * player->wisdom + 5 * player->wisdom;
                    printf("Wisdom improved!\n");
                }
                else
                {
                    printf("Not enough Gold and no attribute points!\n");
                }
                printf("\n");
            }
            else if(input == 3)
            {
                if(player->attribute_points > 0)
                {
                    player->attribute_points--;
                    player->dexterity++;
                    player->dexterity_price = 5 * player->dexterity * player->dexterity + 5 * player->dexterity;
                    printf("Dexterity improved!\n");
                }
                else if(player->gold >= player->dexterity_price)
                {
                    player->gold -= player->dexterity_price;
                    player->dexterity++;
                    player->dexterity_price = 5 * player->dexterity * player->dexterity + 5 * player->dexterity;
                    printf("Dexterity improved!\n");
                }
                else
                {
                    printf("Not enough Gold and no attribute points!\n");
                }
                printf("\n");
            }
        }
    }
    

    exercise.c

    int exercise(int difficulty)
    {
        int number1, number2, result;
    
        if(difficulty == 1)
        {
            number1 = rand() % 21;
            number2 = rand() % 21;
            result = number1 + number2;
            printf("%d + %d = ", number1, number2);
        }
        else if(difficulty == 2)
        {
            number1 = rand() % 26;
            number2 = rand() % 21;
            result = number1 - number2;
            printf("%d - %d = ", number1, number2);
        }
        else if(difficulty == 3)
        {
            number1 = rand() % 16;
            number2 = rand() % 16;
            result = number1 * number2;
            printf("%d * %d = ", number1, number2);
        }
        else if(difficulty == 4)
        {
            number1 = rand() % 101; 
            number2 = (rand() % 20) + 1; // generating zero leads to crash
            result = number1 / number2;
            printf("%d / %d = ", number1, number2);
        }
    
        int guess;
        scanf("%d", &guess);
        printf("\n");
    
        if(guess == result)
        {
            return difficulty;
        }
        else
        {
            return 0;
        }
    }
    

  • Mod

    Ein grober Fehler: Niemals Sourcedateien (.c) via Include einbinden. Schreib Headerdateien für den Code, binde diese ein, dann übersetz alle Sourcedateien separat und linke sie zusammen. Dies ist der Standardvorgang, wie man in C den Quellcode in Module aufteilt, da findest du bestimmt auch genauere Anleitungen für.

    Ansonsten wirkt es auf den ersten Blick ganz ok. Schön objektorientiert, was viele Anfänger sonst gar nicht machen. Ich würde noch auf const-Korrektheit achten, beispielsweise wird der player in save nicht verändert und könnte damit auch const sein.

    An einigen Stellen scheinst du Funktionalität nicht genug aufzuteilen und die Bezeichner sind nicht so deutlich. Bei "attributes" kann ich mir vom namen her nicht erschließen, was es tun soll. Der Code von attributes scheint sowohl einen interaktiven Nutzerdialog zu führen (ist das wirklich die Aufgabe der player-Klasse?), als auch irgendwelche player-Logik durchzuführen. Das ist ein bisschen viel verschiedenes für eine einzige Funktion.

    Die Player-Klasse hat auch recht viele Einzelattribute (Attribute wie Klassenattribute, nicht die Playerattribute). Die könnte man in mehrere logische Untergruppen zusammenfassen.

    Ich habe jetzt nur die grobe Struktur betrachtet, weil es doch recht viel Code ist. Ich habe mir nicht im Details geguckt, was der Code genau macht und ob das gut ist oder nicht. Bei quer darüber lesen fielen mir jedenfalls keine groben Fehler auf.

    Insgesamt ein positiver Eindruck.



  • - *.c niemals per #include
    - main-return fehlt
    - switch/case statt if/else Kaskaden
    - FILE immer binär
    - konstante Stringlisten immer per const char *liste[]={"eins","zwei","drei"};
    - Variablendefinitionen immer am Blockanfang
    - FILE Handling immer mit Fehlerkontrolle
    - struct Initialisierung immer per C99 "Designated Initializers": http://www.drdobbs.com/the-new-c-declarations-initializations/184401377
    - const überall wo möglich, z.B. void save(const struct Player* player)
    - wenn schon scanf, dann richtig mit return-Auswertung und Rest-stdin Leerung
    - kein atoi
    - statt printf("\n"); besser puts("");
    - ...



  • Vielen Dank für die Hinweise!
    Hier nochmal ein paar kleine Fragen / Ergänzungen meinerseits:

    Schön objektorientiert, was viele Anfänger sonst gar nicht machen.

    Ja, ich hatte vorher fast nur in Java meine Skripte geschrieben, das hat hier abgefärbt.
    Warum ist Objektorientierung eigentlich die Programmierphilosophie schlechthin? Wenn man beispielsweise nach "objectoriented programming critizism" googlet, findet man teilweise sehr erfahrene Programmierer, die kübelweise Dreck über die OO-Philosophie ausschütten. Aber als Anfänger kann ich diese Diskussion leider nicht beurteilen.

    Niemals Sourcedateien (.c) via Include einbinden.

    Das liest man fast überall. Warum eigentlich nicht? Zumindest bei kleineren Projekten erschließt sich mir der Nutzen aus der besseren Vorgehensweise nicht.

    main-return fehlt

    Ist das nicht optional?

    switch/case statt if/else Kaskaden

    Habe ich in älteren projekten mal gemacht, aber irgendwie finde ich if-else-Konstruktionen schöner. Kann man das auch als Geschmackssache einstufen, oder hat switch-case auch praktische Vorteile?

    kein atoi
    

    Warum nicht? scheint wunderbar zu funktionieren.

    Ich danke für die vielen Hinweise von euch beiden, die restlichen habe ich soweit auf Anhieb verstanden!



  • Nerv nicht, nimm es als gottgegeben hin.



  • Bitte was? Erstmal mäßigst du dich im Ton. Außerdem bin ich hier um zu lernen, nicht um Gedichte aufsagen zu können. Das heißt ich muß die Dinge verstehen, sonst habe ich keinen Nutzen davon. Anwendung ohne Verständnis ist für einen Lernenden sinnlos.



  • Gewoehn dich dran.
    Sei froh, dass war noch Wutz in sanfter Form. Lustig wirds erst wenn er sich durch dich angegriffen fuehlt 🙂

    Faustregel: Beitraege von ihm durchlesen, keine Fragen stellen, einfach alles googlen und so tun als haette er dir nicht geantwortet.


  • Mod

    Dexter1997 schrieb:

    Niemals Sourcedateien (.c) via Include einbinden.

    Das liest man fast überall. Warum eigentlich nicht? Zumindest bei kleineren Projekten erschließt sich mir der Nutzen aus der besseren Vorgehensweise nicht.

    Weil du andernfalls doppelte Definitionen bekommst. Wenn du bei kleinen Projekten schluderst, dann lernst du es nie richtig und wirst niemals etwas größeres schreiben können.

    main-return fehlt

    Ist das nicht optional?

    In C++, aber nicht in C vor dem 1999er Standard.

    kein atoi

    Warum nicht? scheint wunderbar zu funktionieren.

    Weil atoi keine vernünftige Fehlerprüfung erlaubt. Wie unterscheidest du einen Fehler von einer eingegebenen "0"?


  • Mod

    SeppJ schrieb:

    Dexter1997 schrieb:

    Wutz schrieb:

    main-return fehlt

    Ist das nicht optional?

    In C++, aber nicht in C vor dem 1999er Standard.

    Andererseits nutzt Dexter schon C99-Features, Wutz empfiehlt auch struct Intialisierung per C99 "Designated Initializers". Ausserdem möchte er Variablendefinitionen am Blockanfang sehen (im von ihm geposteten Link wird das gerade nicht empfohlen). Wer weiss also schon, warum seine Empfehlungen so ausfallen, wie sie es tun. Die Version des C-Standards scheint jedenfalls nicht der Grund zu sein.



  • Statt deiner fgets und atoi Reihe kannst du auch fscanf(file,"%d",&player->level) nehemen.
    Wenn du den Rückgabewert von fscanf auswertest, kannst du sogar Fehler erkennen.


  • Mod

    camper schrieb:

    SeppJ schrieb:

    Dexter1997 schrieb:

    Wutz schrieb:

    main-return fehlt

    Ist das nicht optional?

    In C++, aber nicht in C vor dem 1999er Standard.

    Andererseits nutzt Dexter schon C99-Features, Wutz empfiehlt auch struct Intialisierung per C99 "Designated Initializers". Ausserdem möchte er Variablendefinitionen am Blockanfang sehen (im von ihm geposteten Link wird das gerade nicht empfohlen). Wer weiss also schon, warum seine Empfehlungen so ausfallen, wie sie es tun. Die Version des C-Standards scheint jedenfalls nicht der Grund zu sein.

    Die Initializer sind ein Feature, wohingegen implizite Rückgabewerte und lokale Definitionen die holde Reinheit der Sprache schänden, wie sie von den Altvorderen ersonnen wurde.



  • Dexter1997 schrieb:

    switch/case statt if/else Kaskaden

    Habe ich in älteren projekten mal gemacht, aber irgendwie finde ich if-else-Konstruktionen schöner. Kann man das auch als Geschmackssache einstufen, oder hat switch-case auch praktische Vorteile?

    ja der code wird übersichtlicher, ist weniger fehleranfällig, besser erweiterbar und (achtung evtl. gefährliches halbwissen) soweit ich weiß kann der compiler damit schnelleren maschinencode erzeugen, weil sich die switch-anweisung besser in sprungbefehle umwandeln lässt.

    kein atoi
    

    Warum nicht? scheint wunderbar zu funktionieren.

    dann gib mal eine zahl ein, die einen überlauf erzeugen würde, also bei int z.b. 5000000000. die dokumentation sagt dazu "undefined behaviour" und das ist (fast) immer schlecht.


Anmelden zum Antworten