4
\$\begingroup\$

I coded my first projects in Java but now I also wanted to try C. I've read something about pointers and the rest of my programming knowledge I've "imported" from Java. With this project I tried to take root in C, because I am generally new in programming and thats why I am interested in many languages.

This is the project I've reimplemented: Textual mathematician game

I want you to evaluate and criticise my code. Especially I am not satisfied with the attributes()-function, but I dont see a more elegant way to implement this function without using an integer list without a descriptive name for the specific attribute. Also it feels like i am writing sort of Javacode just in C syntax. Also the exercise()-function looks like it contains repititive code. Is there a better or a shorter way to implement this function?

I am interested in your thoughts!

I've used the MinGW-Compiler. The Program was compiled with the command: "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;
    }
}
\$\endgroup\$
2
\$\begingroup\$

Including .c files can be done, yet forming .h files with the global functions and variables and using separate .c compilations is more idiomatic.

#include "exercise.c"  // hmmmm
#include "player.c"

Small detail: Be more generous in buffer size to read an int. There really is no need to tightly control buffer input to 12: 10 characters + \n + \0. Consider that from time to time, the file will have unusual data. So either add more error checking and exit code on bad input and/or have a generous buffer. I suggest 2x expected worst case.

Additional error checks also suggested

What is the widest this could ever print?
Is 12 sufficient? Mouse over below for pedantic needs

fprintf(file, "%d\n", INT_MIN); --> "-2147483648\n" --> char[13] to read back 64-bit int would need char[22]

fprintf(file, "%d\n", player->gold);
...
// fgets(line, 12, file);
// player->gold = atoi(line);
char number[24];
if (fgets(number, sizeof number, file) == NULL) Handle_Error();
if (sscanf(number, "%d", &player->gold) != 1) Handle_Error();

OTOH, I do like to qualify input right up front. Suggest a helper function to read an int and check its range and handle various errors detections. Something like Read_float

#define GOLD_MIN
#define GOLD_MAX
...
if (read_int(file, &player->gold, GOLD_MIN, GOLD_MAX)) return FAIL

The whole alternate initialization could instead use a single assignment.

    struct Player player_default = { .level = 1, .experience = 0, .experience_needed = 10, 
         /* ... */} ;
    *player = player_default;
    // player->level = 1;
    // player->experience = 0;
    // player->experience_needed = 10;

number2 = rand() % 21; result = number1 / number2; --> might cause divide by 0

I like to avoid FP math when only int math is needed. Converting a double to an int results in truncation and not always the expect result once code uses values that are not converted exactly. (0.1 rarely forms an exact double)

// int experience_reward = (player->intelligence * (1 + 0.1 * player->rank)) * multiplier;
int experience_reward = 
  (player->intelligence * (10 + player->rank)) * multiplier / 10;

Highest level is not correct ;-)

// strcpy(player->ranks[9], "Mathematician");
strcpy(player->ranks[9], "Programmer");
\$\endgroup\$
  • \$\begingroup\$ Thank you for your answer! But why do you write Error_Handle() with capital letters? I heard that you should only use small letters, except your are writing some sort of class or in c, a struct. Thank you for your work! \$\endgroup\$ – Dexter Thorn Jul 4 '17 at 6:21
  • \$\begingroup\$ @HenryWeinert "only use small letters" is a style issue - not a universal axiom. Follow your group's coding standard. I did it for emphasis as I would have a set of functions to handle errors all beginning with something like Error_ \$\endgroup\$ – chux Jul 4 '17 at 15:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.