I need help regarding the function to show the top 5 scores in text file in C

Question

I am currently creating a really simple program for my beginners course in C programming and I am stuck. I want to be able to print the 5 best scores that are saved in a text file but right now it is only showing the first 5 lines not the best scored lines. The history part works okay and is printing everything but I cannot understand how to print the 5 best scores, maybe it has to do with sorting out the information in the extern text file? Sorry if this post is dumb but I need to ask somewhere (I am really basic at this).

Here is a part of the code that I think is not working properly, it is for codeblocks:

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

char name [40];     // Here every integer and character etc. is introduced as global.
int menu(char *name);
int startPractice(int choice);
int startTest(int choice, char *name);
char randomOperator();
int getRandomNumber(int min, int max);
int calc(int a, int b, char c);
int rand(void);
int showHistory();
int saveHistory(char *name, float average);
int showTop5();

struct gamer{
char *username;
float average;
};

int main()
{
printf("Hello and welcome to the program!\n"); // This is the introduction
printf("\nWhat is your name? ");
scanf("%s", &name);


printf("\nWelcome %s! \n\nWhat would you like to do? \n" , name);
menu(&name); // This line calls the main menu.

return 0;
}

int compare(const void *s1, const void *s2)
{
struct gamer *g1 = (struct gamer *)s1;
struct gamer *g2 = (struct gamer *)s2;/* same gender so sort by id */

return g1->average - g2->average;
}

int menu(char *name) {        // This is the main menu and whenever this function is called the program starts from here.
int qanswer;        // This variable is set so it can be used in this body, it is not a global integer.

printf("\n1. Do practices \n2. Do a test \n3. Quit \n4. Test history \n5. Top 5 \n");
scanf("%d", &qanswer);

switch (qanswer) {      // Switch cases for a more compressed code, here only one case will be activated and that is decided by the value of "qanswer".
    case 1:
        printf("\nNow, you can choose to do practices on: \n\n1. Additions\n2. Subtractions\n3. Addition and Subtractions: ");
        printf("\n\nEnter your choice: ");
        scanf("%d" , &qanswer);
        startPractice(qanswer);
        break;      //This breaks the code at any chosen case.
    case 2:
        printf("\nYou will be given 15 questions to solve as the test!\n\n");
        printf("\nYou can choose to do the test on: \n\n1. Additions\n2. Subtractions\n3. Addition and Subtractions: ");
        printf("\n\nEnter your choice: ");
        scanf("%d", &qanswer);
        startTest(qanswer, name);
        break;
    case 3:
        printf("\nExiting program... \n");
        exit(0); // Shuts down the program.
        break;
    case 4:
        printf("Test history\n");
        showHistory();
        break;
    case 5:
        printf("Top 5 test results:\n");
        showTop5();
        break;
    default:        // if the user enter anything except 1,2,3 this line of code will be returned.
        printf("Your input wasn't valid. Please try again...\n\n");
        menu(name);     // This calls the main menu again
}

return 0;
}

int startPractice(int choice) {     // If practice is chosen then this function will be called.

int a, b, answer1;
char c;

int i;
for (i = 1; i <= 10; i++) {     // The for loop runs this code until i=10 and increases by 1 each time. Therefore this code will run 10 times fully.
    a = getRandomNumber(1, 30);     // This calls the function for a random number.
    b = getRandomNumber(1, 30);
    if (choice == 1) {      // If choice is set to "1" c will be t to + otherwise -.
        c = '+';
    } else if (choice == 2) {
        c = '-';
    } else {
        c = randomOperator();
    }

    printf("%d. %d %c %d = ", i, a, c, b);
    scanf("%d", &answer1);

    while (answer1 != calc(a, b, c)){
        printf("Try again! ");
        scanf("%d", &answer1);
    }

    printf("Very good!\n\n");
}
printf("Practice is complete\n");
printf("\nNow what would you like to do?\n");
menu(name);

return 0;
}

int startTest(int choice, char *name) {
int a, b, answer1;
char c;
int counter = 0;
float average;
int i;
for (i = 1; i <= 15; i++) {
    a = getRandomNumber(1, 30);
    b = getRandomNumber(1, 30);
    if (choice == 1) {
        c = '+';
    } else if (choice == 2) {
        c = '-';
    } else {
        c = randomOperator();
    }

    printf("%d. %d %c %d = ", i, a, c, b);
    scanf("%d", &answer1);
    if (answer1 == calc(a, b, c)){
        counter++;
    }

    printf("\n\n");
}
printf("The test is complete\n");
average = (float) counter/(float)15;        // This calculates the average score as a float with the counter / 15.
printf("You scored %d out of 15 which is and average of %d %%\n", counter, (int)average*100);       // This line takes the value of average and multiply it by 100 to show the percentage.
saveHistory("Test", average);
printf("\nNow what would you like to do?\n");
menu(name); // This function calls the main menu again.

return 0;

}

int calc(int a, int b, char c) {        // This function is used to define the equation as (a + b) or (a - b).
switch(c) {
    case '+':
        return a+b;
    case '-':
        return a-b;
}
}

char randomOperator() {         //This code is used to decide if + or - is going to be used.
switch(getRandomNumber(0,1)) {      //Switch statement when you need two or more ways to return a value.
    case 0:
        return '+';     //If the random number is 0 return is + otherwise it is -.
    case 1:
        return '-';
}

return '+';
}

int getRandomNumber(int min, int max) {     // This simply decides a random value of min 0 and max 1.
return rand() % (max + 1 - min) + min;
}

int showHistory(){
FILE *f;
char c;
f=fopen("test.txt","rt");

if (f) {
    while((c=fgetc(f))!=EOF){
        printf("%c",c);
    }

    fclose(f);
    printf("\nWhat would you like to do now?\n");
} else {
    printf("There is no history file at the moment.");
}

menu(name);

return 0;
}

int saveHistory(char *name, float average){
FILE *f;
char c;
f=fopen("test.txt","a");

fprintf(f, "%s_%.2f\n", name, average);

fclose(f);
return 0;
}
int showTop5(){
//printf("\n");
FILE *f;
char c;
char line[256];
int count = 0;
f=fopen("test.txt","rt");

if (f) {
    while (fgets(line, sizeof(line), f)) {
        if (strlen(line) > 0) {
            count++;
        }
    }

    char delimiter[] = "_";
    char *ptr;

    fclose(f);

    f=fopen("test.txt","rt");

    struct gamer gamers[count];
    int i = 0;
    printf("\n");
    while (fgets(line, sizeof(line), f)) {
        ptr = strtok(line, delimiter);
        char n[40];
        strcpy(n, ptr);
        //printf("n: %s, ", n);
        ptr = strtok(NULL, delimiter);
        float avg = *(float *) ptr;
        //printf("points: %f\n", avg);
        gamers[i].average = avg;
        gamers[i].username = n;

        i++;
    }

    qsort(gamers, count, sizeof(struct gamer), compare);

    int j;
    for(j = 0; j < count; j++){
        if (j==5){
        break;
    }
        printf("Rank %d: %s with %.2f %% as average.\n", j+1, gamers[j].username, gamers[j]. average);
    }

    fclose(f);
} else {
    printf("There is no history file at the moment.");
}

menu(name);

return 0;
}

Show source
| sorting   | C   | return-value   2017-01-06 23:01 3 Answers

Answers ( 3 )

  1. 2017-01-06 23:01

    So when i fixed the code that made the program crash (as was mentioned in the answers, thanks!) it seems like the problem with the sorting came from this line of code not treating the values correct, since I wanted to return it as percentage and it must have stopped reading after the decimal which made every user either scoring 0 or 1 if this makes sense. It works now when the return value is multiplied by 100.

    Started with this code:

    int compare(const void *s1, const void *s2)
    {
    struct gamer *g1 = (struct gamer *)s1;
    struct gamer *g2 = (struct gamer *)s2;/* same gender so sort by id */
    
    return g1->average - g2->average;
    }
    

    And this code seems to be the problemsolver.

    int compare(const void *s1, const void *s2)
    {
    struct Gamer *g1 = (struct Gamer *)s1;
    struct Gamer *g2 = (struct Gamer *)s2;/* same gender so sort by id */
    
    return (int)((g2->average - g1->average)*100);
    
  2. 2017-01-06 23:01

    This isnt going to work

     ptr = strtok(NULL, delimiter);
        float avg = *(float *) ptr;
    

    you are treating the score in the file as though it were the binary representation of a float. It is not, it is text. You need to do

       ptr = strtok(NULL, delimiter);
        float avg = atof(ptr);
    
  3. 2017-01-08 19:01

    After taking into account of previous remarks, it remains other problems before to manage correctly the sort of array of struct gamer by using the qsort() optimized function.

    Problem 1 - no allocated space to store the username into a struct gamer.

    1. The struct gamer is using a char *username;;
    2. When reading data from "test.txt" file, the user name is storing only the pointer to the buffer instead of the content gamers[i]->username = n;;
    3. Even if the read username is strcpy(n, ptr);, that storage is in the heap and be overwritten at the next reading operation;
    4. As @pm100 suggestes, the average is decoded as a binary data instead of a text value float avg = *(float *) ptr;

    In the showTop5() function use:

    while (fgets(line, sizeof(line), f)) {
        ptr = strtok(line, delimiter);
        char n[40];
        strcpy(n, ptr);
        ptr = strtok(NULL, delimiter);
        float avg = atof(ptr); // @pm100 proposed solution
        gamers[i].average = avg;
        gamers[i].username = (char *)malloc(sizeof(char)*(strlen(n)+1));
        strcpy(gamers[i].username,n); // allocate and copy the username
    
        i++;
    }
    

    Instead of

        while (fgets(line, sizeof(line), f)) {
            ptr = strtok(line, delimiter);
            char n[40];
            strcpy(n, ptr);
            //printf("n: %s, ", n);
            ptr = strtok(NULL, delimiter);
            float avg = *(float *) ptr;
            //printf("points: %f\n", avg);
            gamers[i].average = avg;
            gamers[i].username = n; // error no size allocated
    
            i++;
        }
    

    Problem 2 - When using qsort() the structure of data shall have a fixed-size length. Use an array of pointer struct gamer * to be sorted.

    1. If sizeof(struct gamer) is used to qsort(), the float average and the char *username will be sorted, but it is not optimized;
    2. After allocating the size to store username, it will be not easy to free the memory if the array struct gamer gamers[count]; is allocated from the heap;
    3. It the amount of gamers is large, storing in the heap could be a problem;

    Allocating the gamers[] array:

    struct gamer **gamers;
    int i;
    
    gamers = malloc(count*sizeof(struct gamer *));
    for (i = 0; i < count; i++) {
        gamers[i] = malloc(sizeof(struct gamer));
    }
    

    Manage the reading operation to the array:

    See the source code in the Problem 1

    Update the compare function of quicksort:

    int compare(const void *s1, const void *s2)
    {
        struct gamer *g1 = *((struct gamer **)s1); // instead of (struct gamer *)s1;
        struct gamer *g2 = *((struct gamer **)s2); // instead of (struct gamer *)s2;
        // as @BeginnerC suggests, reverse the comparison
        return ((int)((g2->average - g1->average)*100));
    }
    

    Perform the quicksort:

    qsort(gamers, count, sizeof(struct gamer *), compare);
    

    Print the Top 5 gamers:

    for(j = 0; j < count; j++){
        if (j==5){
            break;
        }
        printf("Rank %d: %s with %.2f %% as average.\n", j+1,
            gamers[j]->username, gamers[j]->average);
    }
    

    Free the allocated gamers[] array:

    for (i = 0; i < count; i++) {
        free (gamers[i]->username); // allocated to the username
        free (gamers[i]);
    }
    free(gamers);
    

    Note: in order to simplify the solution of Problem 1 and to have a fixed-size struct gamer, the field char *username could be replaced by char username[40]; (because char n[40]; before strcpy(n, ptr);.

◀ Go back