5
\$\begingroup\$

I wrote this a few months ago as my first "serious" C program (I've used JS for a few years). It's a simple resource collecting and trading game (based off one I made in HTML/JS), that takes input in the form of some simple commands.

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

struct trade_like {
    int8_t buy_item;
    int8_t sell_item;
    uint64_t buy_amount;
    uint64_t sell_amount;
    int16_t timer;
};

struct upgrade {
    int8_t thread;
    int8_t id;
};

void shorten_number(char *dest, uint64_t number) {
    if (number >= 1000000000000) {
        sprintf(dest, "%.2fT", fmin((double) number / 1000000000000.0, 999.99));
    } else if (number >= 1000000000) {
        sprintf(dest, "%.2fB", fmin((double) number / 1000000000.0, 999.99));
    } else if (number >= 1000000) {
        sprintf(dest, "%.2fM", fmin((double) number / 1000000.0, 999.99));
    } else if (number >= 1000) {
        sprintf(dest, "%.2fK", fmin((double) number / 1000.0, 999.99));
    } else {
        sprintf(dest, "%d", number);
    }

    return;
}

void print_resources(uint64_t resources[], struct trade_like trades[], struct trade_like deals[], struct upgrade upgrades[]) {
    char amounts[9][10];
    char trade_list[5][48];
    char deal_list[3][48];
    char upgrade_list[9][36];

    char names[9][8] = {"Wood", "Stone", "Coal", "Iron", "Copper", "Silver", "Gold", "Diamond", "Emerald"};
    char upper_names[9][8] = {"WOOD", "STONE", "COAL", "IRON", "COPPER", "SILVER", "GOLD", "DIAMOND", "EMERALD"};
    char unlock_costs[8][2][16] = {
        {"Wood (200)", "Coal (40)"},
        {"Stone (400)", "Coal (80)"},
        {"Stone (200)", "Iron (100)"},
        {"Iron (300)", "Copper (50)"},
        {"Stone (1.00K)", "Iron (400)"},
        {"Copper (500)", "Coal (300)"},
        {"Iron (1.00K)", "Silver (400)"},
        {"Diamond (200)", "Silver (100)"}
    };
    char multiplier_levels[7][8] = {"+10%", "+25%", "+75%", "DOUBLE", "LOTS OF", "TONS OF"};
    int16_t base_cost[7] = {200, 500, 1500, 4000, 10000, 20000};

    memset(trade_list, '\0', sizeof(trade_list[0][0]) * 240);
    memset(deal_list, '\0', sizeof(deal_list[0][0]) * 144);
    memset(upgrade_list, '\0', sizeof(upgrade_list[0][0]) * 324);

    printf("\033[F\033[A\033[A\033[A\033[A\033[A\033[A\033[A\033[A\033[A\033[A\033[A");

    for (int8_t i = 0; i < 9; i++) {
        shorten_number(amounts[i], resources[i]);
    }

    for (int8_t i = 0; i < 5; i++) {
        if (trades[i].timer >= 0) {
            char sell_string[10];
            char buy_string[10];

            shorten_number(sell_string, trades[i].sell_amount);
            shorten_number(buy_string, trades[i].buy_amount);

            sprintf(trade_list[i], "[%d] %s (%s) -> %s (%s)", i + 1, names[trades[i].sell_item], sell_string, names[trades[i].buy_item], buy_string);
        }
    }

    for (int8_t i = 0; i < 3; i++) {
        if (deals[i].timer >= 0) {
            char sell_string[10];
            char buy_string[10];

            shorten_number(sell_string, deals[i].sell_amount);
            shorten_number(buy_string, deals[i].buy_amount);

            sprintf(deal_list[i], "[%d] %s (%s) -> %s (%s)", i + 1, names[deals[i].sell_item], sell_string, names[deals[i].buy_item], buy_string);
        }
    }

    if (upgrades[0].thread != -1) {
        sprintf(upgrade_list[0], "MINE %s", upper_names[upgrades[0].id + 1]);
        sprintf(upgrade_list[1], "   %s", unlock_costs[upgrades[0].id][0]);
        sprintf(upgrade_list[2], "   %s", unlock_costs[upgrades[0].id][1]);
    }

    if (upgrades[1].thread != -1) {
        sprintf(upgrade_list[4], "%s %s", multiplier_levels[(int8_t) ((double) upgrades[1].id / 9.0)], upper_names[upgrades[1].id % 9]);

        char cost_string[10];

        shorten_number(cost_string, (double) base_cost[(int8_t) upgrades[1].id / 9] * (1.0 - 0.1 * (upgrades[1].id % 9)));

        sprintf(upgrade_list[5], "   %s (%s)", names[upgrades[1].id % 9], cost_string);
    }

    if (upgrades[2].thread == 2) {
        sprintf(upgrade_list[7], "AUTO-%s", upper_names[upgrades[2].id]);
        if (upgrades[2].id == 4) {
            strcpy(upgrade_list[8], "   Gold (200)");
        }
        sprintf(upgrade_list[8], "   %s (200)", names[upgrades[2].id + 2]);
    } else if (upgrades[2].thread == 3) {
        sprintf(upgrade_list[7], "%s/%s DEALS", upper_names[upgrades[2].id * 2], upper_names[upgrades[2].id * 2 + 1]);

        char cost_string[10];

        shorten_number(cost_string, 1750 - 375 * upgrades[2].id);

        sprintf(upgrade_list[8], "   %s (%s)", names[upgrades[2].id * 2 + 1], cost_string);
    }

    printf(
        "  Wood:     %-9s%-44s%-36s\n  Stone:    %-9s%-44s%-36s\n  Coal:     %-9s%-44s%-36s\n  Iron:     %-9s%-44s%-36s\n  Copper:   %-9s%-44s%-36s\n  Silver:   %-9s%-44s%-36s\n  Gold:     %-9s%-44s%-36s\n  Diamond:  %-9s%-44s%-36s\n  Emerald:  %-9s%-44s%-36s\n\n\n\n >>                         \b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b",
        amounts[0], trade_list[0], upgrade_list[0], amounts[1], trade_list[1], upgrade_list[1], amounts[2], trade_list[2], upgrade_list[2],
        amounts[3], trade_list[3], upgrade_list[3], amounts[4], trade_list[4], upgrade_list[4], amounts[5], " ", upgrade_list[5],
        amounts[6], deal_list[0], upgrade_list[6], amounts[7], deal_list[1], upgrade_list[7], amounts[8], deal_list[2], upgrade_list[8]
    );

    return;
}

int main(int argc, char** argv) {
    int8_t unlocked = 0;
    uint64_t resources[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
    double multipliers[9] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
    int8_t auto_level = 0;

    int16_t item_costs[9] = {1, 2, 5, 10, 25, 40, 80, 200, 875};

    char names[9][8] = {"Wood", "Stone", "Coal", "Iron", "Copper", "Silver", "Gold", "Diamond", "Emerald"};

    struct trade_like trades[5];
    struct trade_like deals[3];

    int8_t trade_tick = 0;
    int8_t deal_tick = 0;
    int8_t unlocked_deals = 0;

    for (int8_t i = 0; i < 5; i++) {
        trades[i].timer = -1;
        
        if (i < 3) {
            deals[i].timer = -1;
        }
    }

    struct upgrade upgrades[3];

    upgrades[0].thread = 0;
    upgrades[0].id = 0;

    upgrades[0].thread = -1;
    upgrades[1].thread = -1;
    upgrades[2].thread = -1;

    int64_t turn_count = 0;

    char words_redo[4][24];

    srand(time(NULL));

    strcpy(words_redo[0], "collect");
    strcpy(words_redo[1], "wood");
    memset(words_redo[2], '\0', sizeof(words_redo[2][0]) * 48);

    printf("MERCHANT 2.0.0 [TERMINAL EDITION]\n\n\n\n\n\n\n\n\n\n\n\n  Enter 'collect wood' to begin playing!\n\n\n\033[A");

    char input[24];

    while (strncmp(input, "quit\n", 5) != 0 && strncmp(input, "exit\n", 5) != 0 && strncmp(input, "q\n", 2) != 0 && strncmp(input, "x\n", 2) != 0) {
        print_resources(resources, trades, deals, upgrades);

        fgets(input, 24, stdin);

        printf("\033[F\033[A\033[A                                                  \b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");

        char words[4][24];
        int8_t word_count = 0;
        int8_t within_word = 0;

        memset(words[0], '\0', sizeof(words[0][0]) * 96);

        for (int8_t i = 0; i < 24; i++) {
            if (input[i] == '\n' || input[i] == '\0') {
                break;
            } else if (input[i] >= 'a' && input[i] <= 'z' || input[i] >= 'A' && input[i] <= 'Z' || input[i] >= '0' && input[i] <= '9' || input[i] == '-' || input[i] == '_') {
                words[word_count][within_word++] = input[i];
            } else {
                if (within_word != 0) {
                    word_count++;
                    within_word = 0;

                    if (word_count == 4) {
                        break;
                    }
                }
            }
        }

        if (strncmp(words[0], "redo\0", 5) == 0 || strncmp(words[0], "r\0", 2) == 0 || words[0][0] == '\0') {
            strcpy(words[0], words_redo[0]);
            strcpy(words[1], words_redo[1]);
            strcpy(words[2], words_redo[2]);
            strcpy(words[3], words_redo[3]);
        }

        if (strncmp(words[0], "collect\0", 8) == 0 || strncmp(words[0], "col\0", 4) == 0 || strncmp(words[0], "c\0", 2) == 0) {
            int16_t collect_amount;

            if (words[1][0] == '\0' || words[2][0] != '\0') {
                printf("ERR: Expected one argument (resource name)\n\n");
                continue;
            }

            if (strncmp(words[1], "wood\0", 5) == 0 || strncmp(words[1], "w\0", 2) == 0) {
                if (unlocked < 0) {
                    printf("ERR: You haven't unlocked this resource\n\n");
                    continue;
                }

                collect_amount = (int16_t) (floorf(multipliers[0]) + (rand() < RAND_MAX * (multipliers[0] - floorf(multipliers[0]))));

                resources[0] += collect_amount;
                printf("Collected %d wood", collect_amount);
            } else if (strncmp(words[1], "stone\0", 6) == 0 || strncmp(words[1], "s\0", 2) == 0) {
                if (unlocked < 1) {
                    printf("ERR: You haven't unlocked this resource\n\n");
                    continue;
                }

                collect_amount = (int16_t) (floorf(multipliers[1]) + (rand() < RAND_MAX * (multipliers[1] - floorf(multipliers[1]))));

                resources[1] += collect_amount;
                printf("Collected %d stone", collect_amount);
            } else if (strncmp(words[1], "coal\0", 5) == 0 || strncmp(words[1], "c\0", 2) == 0) {
                if (unlocked < 2) {
                    printf("ERR: You haven't unlocked this resource\n\n");
                    continue;
                }

                collect_amount = (int16_t) (floorf(multipliers[2]) + (rand() < RAND_MAX * (multipliers[2] - floorf(multipliers[2]))));

                resources[2] += collect_amount;
                printf("Collected %d coal", collect_amount);
            } else if (strncmp(words[1], "iron\0", 5) == 0 || strncmp(words[1], "i\0", 2) == 0) {
                if (unlocked < 3) {
                    printf("ERR: You haven't unlocked this resource\n\n");
                    continue;
                }

                collect_amount = (int16_t) (floorf(multipliers[3]) + (rand() < RAND_MAX * (multipliers[3] - floorf(multipliers[3]))));

                resources[3] += collect_amount;
                printf("Collected %d iron", collect_amount);
            } else if (strncmp(words[1], "copper\0", 7) == 0 || strncmp(words[1], "cp\0", 3) == 0 || strncmp(words[1], "cr\0", 3) == 0) {
                if (unlocked < 4) {
                    printf("ERR: You haven't unlocked this resource\n\n");
                    continue;
                }

                collect_amount = (int16_t) (floorf(multipliers[4]) + (rand() < RAND_MAX * (multipliers[4] - floorf(multipliers[4]))));

                resources[4] += collect_amount;
                printf("Collected %d copper", collect_amount);
            } else if (strncmp(words[1], "silver\0", 7) == 0 || strncmp(words[1], "si\0", 3) == 0 || strncmp(words[1], "sv\0", 3) == 0) {
                if (unlocked < 5) {
                    printf("ERR: You haven't unlocked this resource\n\n");
                    continue;
                }

                collect_amount = (int16_t) (floorf(multipliers[5]) + (rand() < RAND_MAX * (multipliers[5] - floorf(multipliers[5]))));

                resources[5] += collect_amount;
                printf("Collected %d silver", collect_amount);
            } else if (strncmp(words[1], "gold\0", 5) == 0 || strncmp(words[1], "g\0", 2) == 0) {
                if (unlocked < 6) {
                    printf("ERR: You haven't unlocked this resource\n\n");
                    continue;
                }

                collect_amount = (int16_t) (floorf(multipliers[6]) + (rand() < RAND_MAX * (multipliers[6] - floorf(multipliers[6]))));

                resources[6] += collect_amount;
                printf("Collected %d gold", collect_amount);
            } else if (strncmp(words[1], "diamond\0", 8) == 0 || strncmp(words[1], "d\0", 2) == 0) {
                if (unlocked < 7) {
                    printf("ERR: You haven't unlocked this resource\n\n");
                    continue;
                }

                collect_amount = (int16_t) (floorf(multipliers[7]) + (rand() < RAND_MAX * (multipliers[7] - floorf(multipliers[7]))));

                resources[7] += collect_amount;
                printf("Collected %d diamond", collect_amount);
            } else if (strncmp(words[1], "emerald\0", 8) == 0 || strncmp(words[1], "e\0", 2) == 0 || strncmp(words[1], "m\0", 2) == 0) {
                if (unlocked < 8) {
                    printf("ERR: You haven't unlocked this resource\n\n");
                    continue;
                }

                collect_amount = (int16_t) (floorf(multipliers[8]) + (rand() < RAND_MAX * (multipliers[8] - floorf(multipliers[8]))));

                resources[8] += collect_amount;
                printf("Collected %d emerald", collect_amount);
            } else {
                printf("ERR: Unknown resource\n\n");
                continue;
            }
        } else if (strncmp(words[0], "trade\0", 6) == 0 || strncmp(words[0], "tr\0", 3) == 0 || strncmp(words[0], "t\0", 2) == 0) {
            if (words[1][0] == '\0' || words[2][0] != '\0') {
                printf("ERR: Expected one argument (trade id)\n\n");
                continue;
            }
            
            if (words[1][1] != '\0' || words[1][0] < '1' || words[1][0] > '5') {
                printf("ERR: Invalid trade id\n\n");
                continue;
            }

            int8_t trade = words[1][0] - 49;

            if (trades[trade].timer == -1) {
                printf("ERR: There is no trade with that id\n\n");
                continue;
            } else if (resources[trades[trade].sell_item] < trades[trade].sell_amount) {
                printf("ERR: You can't afford that trade\n\n");
                continue;
            } else {
                resources[trades[trade].buy_item] += trades[trade].buy_amount;
                resources[trades[trade].sell_item] -= trades[trade].sell_amount;

                trades[trade].timer = fmax(trades[trade].timer - (int16_t) ((trades[trade].buy_amount / resources[trades[trade].buy_item]) * 4), -1);

                printf("Trade #1: %s (%d) -> %s (%d)", names[trades[trade].sell_item], trades[trade].sell_amount, names[trades[trade].buy_item], trades[trade].buy_amount);
            }
        } else if (strncmp(words[0], "deal\0", 5) == 0 || strncmp(words[0], "d\0", 2) == 0) {
            if (words[1][0] == '\0' || words[2][0] != '\0') {
                printf("ERR: Expected one argument (deal id)\n\n");
                continue;
            }
            
            if (words[1][1] != '\0' || words[1][0] < '1' || words[1][0] > '3') {
                printf("ERR: Invalid deal id\n\n");
                continue;
            }

            int8_t deal = words[1][0] - 49;

            if (deals[deal].timer == -1) {
                printf("ERR: There is no deal with that id\n\n");
                continue;
            } else if (resources[deals[deal].sell_item] < deals[deal].sell_amount) {
                printf("ERR: You can't afford that deal\n\n");
                continue;
            } else {
                resources[deals[deal].buy_item] += deals[deal].buy_amount;
                resources[deals[deal].sell_item] -= deals[deal].sell_amount;

                deals[deal].timer = -1;

                printf("Deal #1: %s (%d) -> %s (%d)", names[deals[deal].sell_item], deals[deal].sell_amount, names[deals[deal].buy_item], deals[deal].buy_amount);
            }
        } else if (strncmp(words[0], "unlock\0", 7) == 0 || strncmp(words[0], "unl\0", 4) == 0 || strncmp(words[0], "u\0", 2) == 0) {
            if (words[1][0] == '\0' || words[2][0] != '\0') {
                printf("ERR: Expected one argument (resource id)\n\n");
                continue;
            }

            if (strncmp(words[1], "wood\0", 5) == 0 || strncmp(words[1], "w\0", 2) == 0) {
                printf("ERR: You have already unlocked this resource\n\n");
                continue;
            } else if (strncmp(words[1], "stone\0", 6) == 0 || strncmp(words[1], "s\0", 2) == 0) {
                if (unlocked >= 1) {
                    printf("ERR: You have already unlocked this resource\n\n");
                    continue;
                }

                if (resources[0] < 200 || resources[2] < 40) {
                    printf("ERR: You cannot afford to unlock this resource\n\n");
                    continue;
                }

                resources[0] -= 200;
                resources[2] -= 40;

                unlocked++;

                printf("Unlocked stone");
            } else if (strncmp(words[1], "coal\0", 5) == 0 || strncmp(words[1], "c\0", 2) == 0) {
                if (unlocked >= 2) {
                    printf("ERR: You have already unlocked this resource\n\n");
                    continue;
                }

                if (unlocked < 1) {
                    printf("ERR: You cannot unlock this resource yet\n\n");
                    continue;
                }

                if (resources[1] < 400 || resources[2] < 80) {
                    printf("ERR: You cannot afford to unlock this resource\n\n");
                    continue;
                }

                resources[1] -= 400;
                resources[2] -= 80;

                unlocked++;

                printf("Unlocked coal");
            } else if (strncmp(words[1], "iron\0", 5) == 0 || strncmp(words[1], "i\0", 2) == 0) {
                if (unlocked >= 3) {
                    printf("ERR: You have already unlocked this resource\n\n");
                    continue;
                }

                if (unlocked < 2) {
                    printf("ERR: You cannot unlock this resource yet\n\n");
                    continue;
                }

                if (resources[1] < 200 || resources[3] < 100) {
                    printf("ERR: You cannot afford to unlock this resource\n\n");
                    continue;
                }

                resources[1] -= 200;
                resources[3] -= 100;

                unlocked++;

                printf("Unlocked iron");
            } else if (strncmp(words[1], "copper\0", 7) == 0 || strncmp(words[1], "cp\0", 3) == 0 || strncmp(words[1], "cr\0", 3) == 0) {
                if (unlocked >= 4) {
                    printf("ERR: You have already unlocked this resource\n\n");
                    continue;
                }

                if (unlocked < 3) {
                    printf("ERR: You cannot unlock this resource yet\n\n");
                    continue;
                }

                if (resources[3] < 300 || resources[4] < 50) {
                    printf("ERR: You cannot afford to unlock this resource\n\n");
                    continue;
                }

                resources[3] -= 300;
                resources[4] -= 50;

                unlocked++;

                printf("Unlocked copper");
            } else if (strncmp(words[1], "silver\0", 7) == 0 || strncmp(words[1], "si\0", 3) == 0 || strncmp(words[1], "sv\0", 3) == 0) {
                if (unlocked >= 5) {
                    printf("ERR: You have already unlocked this resource\n\n");
                    continue;
                }

                if (unlocked < 4) {
                    printf("ERR: You cannot unlock this resource yet\n\n");
                    continue;
                }

                if (resources[1] < 1000 || resources[3] < 400) {
                    printf("ERR: You cannot afford to unlock this resource\n\n");
                    continue;
                }

                resources[1] -= 1000;
                resources[3] -= 400;

                unlocked++;

                printf("Unlocked silver");
            } else if (strncmp(words[1], "gold\0", 5) == 0 || strncmp(words[1], "g\0", 2) == 0) {
                if (unlocked >= 6) {
                    printf("ERR: You have already unlocked this resource\n\n");
                    continue;
                }

                if (unlocked < 5) {
                    printf("ERR: You cannot unlock this resource yet\n\n");
                    continue;
                }

                if (resources[4] < 500 || resources[2] < 300) {
                    printf("ERR: You cannot afford to unlock this resource\n\n");
                    continue;
                }

                resources[4] -= 500;
                resources[2] -= 300;

                unlocked++;

                printf("Unlocked gold");
            } else if (strncmp(words[1], "diamond\0", 8) == 0 || strncmp(words[1], "d\0", 2) == 0) {
                if (unlocked >= 7) {
                    printf("ERR: You have already unlocked this resource\n\n");
                    continue;
                }

                if (unlocked < 6) {
                    printf("ERR: You cannot unlock this resource yet\n\n");
                    continue;
                }

                if (resources[3] < 1000 || resources[5] < 400) {
                    printf("ERR: You cannot afford to unlock this resource\n\n");
                    continue;
                }

                resources[3] -= 1000;
                resources[5] -= 400;

                unlocked++;

                printf("Unlocked diamond");
            } else if (strncmp(words[1], "emerald\0", 8) == 0 || strncmp(words[1], "e\0", 2) == 0 || strncmp(words[1], "m\0", 2) == 0) {
                if (unlocked == 8) {
                    printf("ERR: You have already unlocked this resource\n\n");
                    continue;
                }

                if (unlocked < 7) {
                    printf("ERR: You cannot unlock this resource yet\n\n");
                    continue;
                }

                if (resources[7] < 200 || resources[5] < 100) {
                    printf("ERR: You cannot afford to unlock this resource\n\n");
                    continue;
                }

                resources[7] -= 200;
                resources[5] -= 100;

                unlocked++;

                printf("Unlocked emerald");
            } else {
                printf("ERR: Unknown resource\n\n");
                continue;
            }

            upgrades[0].thread = -1;
        } else if (strncmp(words[0], "multiplier\0", 11) == 0 || strncmp(words[0], "multiply\0", 9) == 0 || strncmp(words[0], "multi\0", 6) == 0 || strncmp(words[0], "mul\0", 4) == 0 || strncmp(words[0], "m\0", 2) == 0) {
            if (words[1][0] == '\0' || words[2][0] != '\0') {
                printf("ERR: Expected one argument (resource name)\n\n");
                continue;
            }

            int8_t resource;

            if (strncmp(words[1], "wood\0", 5) == 0 || strncmp(words[1], "w\0", 2) == 0) {
                resource = 0;
            } else if (strncmp(words[1], "stone\0", 6) == 0 || strncmp(words[1], "s\0", 2) == 0) {
                resource = 1;
            } else if (strncmp(words[1], "coal\0", 5) == 0 || strncmp(words[1], "c\0", 2) == 0) {
                resource = 2;
            } else if (strncmp(words[1], "iron\0", 5) == 0 || strncmp(words[1], "i\0", 2) == 0) {
                resource = 3;
            } else if (strncmp(words[1], "copper\0", 7) == 0 || strncmp(words[1], "cp\0", 3) == 0 || strncmp(words[1], "cr\0", 3) == 0) {
                resource = 4;
            } else if (strncmp(words[1], "silver\0", 7) == 0 || strncmp(words[1], "si\0", 3) == 0 || strncmp(words[1], "sv\0", 3) == 0) {
                resource = 5;
            } else if (strncmp(words[1], "gold\0", 5) == 0 || strncmp(words[1], "g\0", 2) == 0) {
                resource = 6;
            } else if (strncmp(words[1], "diamond\0", 8) == 0 || strncmp(words[1], "d\0", 2) == 0) {
                resource = 7;
            } else if (strncmp(words[1], "emerald\0", 8) == 0 || strncmp(words[1], "e\0", 2) == 0 || strncmp(words[1], "m\0", 2) == 0) {
                resource = 8;
            } else {
                printf("ERR: Unknown resource\n\n");
                continue;
            }

            if (multipliers[resource] == 1.0) {
                if (resources[resource] < 200 - 20 * resource) {
                    printf("ERR: You cannot afford this upgrade\n\n");
                    continue;
                }

                resources[resource] -= 200 - 20 * resource;
                multipliers[resource] = 1.1;
            } else if (multipliers[resource] == 1.1) {
                if (resources[resource] < 500 - 50 * resource) {
                    printf("ERR: You cannot afford this upgrade\n\n");
                    continue;
                }

                resources[resource] -= 500 - 50 * resource;
                multipliers[resource] = 1.25;
            } else if (multipliers[resource] == 1.25) {
                if (resources[resource] < 1500 - 150 * resource) {
                    printf("ERR: You cannot afford this upgrade\n\n");
                    continue;
                }

                resources[resource] -= 1500 - 150 * resource;
                multipliers[resource] = 1.75;
            } else if (multipliers[resource] == 1.75) {
                if (resources[resource] < 4000 - 40 * resource) {
                    printf("ERR: You cannot afford this upgrade\n\n");
                    continue;
                }

                resources[resource] -= 4000 - 400 * resource;
                multipliers[resource] = 2.0;
            } else if (multipliers[resource] == 2.0) {
                if (resources[resource] < 10000 - 1000 * resource) {
                    printf("ERR: You cannot afford this upgrade\n\n");
                    continue;
                }

                resources[resource] -= 10000 - 1000 * resource;
                multipliers[resource] = 5.0;
            } else if (multipliers[resource] == 5.0) {
                if (resources[resource] < 20000 - 2000 * resource) {
                    printf("ERR: You cannot afford this upgrade\n\n");
                    continue;
                }

                resources[resource] -= 20000 - 2000 * resource;
                multipliers[resource] = 10.0;
            } else {
                printf("ERR: This resource is already at max multiplier\n\n");
                continue;
            }

            upgrades[1].thread = -1;
        } else if (strncmp(words[0], "autocollect\0", 12) == 0 || strncmp(words[0], "autocol\0", 8) == 0 || strncmp(words[0], "auto\0", 5) == 0 || strncmp(words[0], "a\0", 2) == 0) {
            if (words[1][0] == '\0' || words[2][0] != '\0') {
                printf("ERR: Expected one argument (resource)\n\n");
                continue;
            }

            int8_t level;

            if (strncmp(words[1], "wood\0", 5) == 0 || strncmp(words[1], "w\0", 2) == 0) {
                level = 1;
            } else if (strncmp(words[1], "stone\0", 6) == 0 || strncmp(words[1], "s\0", 2) == 0) {
                level = 2;
            } else if (strncmp(words[1], "coal\0", 5) == 0 || strncmp(words[1], "c\0", 2) == 0) {
                level = 3;
            } else if (strncmp(words[1], "iron\0", 5) == 0 || strncmp(words[1], "i\0", 2) == 0) {
                level = 4;
            } else if (strncmp(words[1], "metal\0", 6) == 0 || strncmp(words[1], "m\0", 2) == 0) {
                level = 5;
            } else {
                printf("ERR: Unknown or non-automatable resource\n\n");
                continue;
            }

            if (auto_level > level) {
                printf("ERR: You have already automated this resource\n\n");
                continue;
            }

            if (auto_level < level - 1) {
                printf("ERR: You cannot automate this resource yet\n\n");
                continue;
            }

            if (resources[level + 1] < 200) {
                printf("ERR: You cannot afford to automate this resource\n\n");
                continue;
            }

            auto_level = level;
            resources[level + 1] -= 200;

            upgrades[2].thread = -1;
        } else if (strncmp(words[0], "unlock-deal\0", 12) == 0 || strncmp(words[0], "unlock_deal\0", 12) == 0 || strncmp(words[0], "unl-d\0", 6) == 0 || strncmp(words[0], "unl_d\0", 6) == 0 || strncmp(words[0], "ud\0", 3) == 0 || strncmp(words[0], "l\0", 2) == 0) {
            if (words[1][0] == '\0' || words[2][0] != '\0') {
                printf("ERR: Expected one argument (resource)\n\n");
                continue;
            }

            int8_t deal;

            if (strncmp(words[1], "wood\0", 5) == 0 || strncmp(words[1], "w\0", 2) == 0) {
                deal = 2;
            } else if (strncmp(words[1], "stone\0", 6) == 0 || strncmp(words[1], "s\0", 2) == 0) {
                deal = 2;
            } else if (strncmp(words[1], "coal\0", 5) == 0 || strncmp(words[1], "c\0", 2) == 0) {
                deal = 4;
            } else if (strncmp(words[1], "iron\0", 5) == 0 || strncmp(words[1], "i\0", 2) == 0) {
                deal = 4;
            } else if (strncmp(words[1], "copper\0", 7) == 0 || strncmp(words[1], "cp\0", 3) == 0 || strncmp(words[1], "cr\0", 3) == 0) {
                deal = 6;
            } else if (strncmp(words[1], "silver\0", 7) == 0 || strncmp(words[1], "si\0", 3) == 0 || strncmp(words[1], "sv\0", 3) == 0) {
                deal = 6;
            } else if (strncmp(words[1], "gold\0", 5) == 0 || strncmp(words[1], "g\0", 2) == 0) {
                deal = 8;
            } else if (strncmp(words[1], "diamond\0", 8) == 0 || strncmp(words[1], "d\0", 2) == 0) {
                deal = 8;
            } else {
                printf("ERR: Unknown resource\n\n");
                continue;
            }

            if (deal < unlocked_deals) {
                printf("You have already unlocked these deals\n\n");
                continue;
            }

            if (unlocked_deals < deal - 2) {
                printf("You cannot unlock these deals yet\n\n");
                continue;
            }

            if (resources[deal - 1] < 2125 - (deal / 2) * 375) {
                printf("You cannot afford to unlock these deals\n\n");
                continue;
            }

            resources[deal - 1] -= 2125 - (deal / 2) * 375;
            unlocked_deals = deal;

            upgrades[2].thread = -1;
        } else {
            printf("ERR: Unknown command\n\n");
            continue;
        }

        strcpy(words_redo[0], words[0]);
        strcpy(words_redo[1], words[1]);
        strcpy(words_redo[2], words[2]);
        strcpy(words_redo[3], words[3]);

        printf("\n\n");

        if (auto_level == 1) {
            if (rand() < (double) RAND_MAX * 0.1) {
                resources[0]++;
            }
        } else if (auto_level == 2) {
            if (rand() < (double) RAND_MAX * 0.2) {
                resources[0]++;
            }

            if (rand() < (double) RAND_MAX * 0.1) {
                resources[1]++;
            }
        } else if (auto_level == 3) {
            if (rand() < (double) RAND_MAX * 0.35) {
                resources[0]++;
            }

            if (rand() < (double) RAND_MAX * 0.2) {
                resources[1]++;
            }

            if (rand() < (double) RAND_MAX * 0.1) {
                resources[2]++;
            }
        } else if (auto_level == 4) {
            if (rand() < (double) RAND_MAX * 0.5) {
                resources[0]++;
            }

            if (rand() < (double) RAND_MAX * 0.35) {
                resources[1]++;
            }

            if (rand() < (double) RAND_MAX * 0.2) {
                resources[2]++;
            }

            if (rand() < (double) RAND_MAX * 0.1) {
                resources[3]++;
            }
        } else if (auto_level == 5) {
            if (rand() < (double) RAND_MAX * 0.75) {
                resources[0]++;
            }

            if (rand() < (double) RAND_MAX * 0.5) {
                resources[1]++;
            }

            if (rand() < (double) RAND_MAX * 0.35) {
                resources[2]++;
            }

            if (rand() < (double) RAND_MAX * 0.2) {
                resources[3]++;
            }

            if (rand() < (double) RAND_MAX * 0.1) {
                resources[4]++;
            }

            if (rand() < (double) RAND_MAX * 0.05) {
                resources[5]++;
            }

            if (rand() < (double) RAND_MAX * 0.01) {
                resources[6]++;
            }
        }

        if (trade_tick-- < 0) {
            for (int8_t i = 0; i < 5; i++) {
                if (trades[i].timer >= 0)
                    trades[i].timer--;
            }

            if (rand() < RAND_MAX * 0.15) {
                int8_t trade = -1;

                for (int8_t i = 0; i < 5; i++) {
                    if (trades[i].timer == -1) {
                        trade = i;
                        break;
                    }
                }

                if (trade != -1) {
                    trades[trade].sell_item = (int8_t) ((rand() / (double) RAND_MAX) * (unlocked + 1));
                    trades[trade].sell_amount = (uint64_t) (resources[trades[trade].sell_item] * ((rand() / (double) RAND_MAX) + 0.25) + (uint64_t) (rand() / (double) RAND_MAX) * 40 * multipliers[trades[trade].sell_item]);
                
                    do {
                        trades[trade].buy_item = (int8_t) ((rand() / (double) RAND_MAX) * (fmin(unlocked, 8) + 1));

                        double random = rand() / (double) RAND_MAX;

                        if (random < 0.02 && unlocked <= 5) {
                            trades[trade].buy_item += 3;
                        } else if (random < 0.2 && unlocked <= 6) {
                            trades[trade].buy_item += 2;
                        } else if (random < 0.6 && unlocked <= 7) {
                            trades[trade].buy_item += 1;
                        }
                    } while (trades[trade].buy_item == trades[trade].sell_item);

                    trades[trade].buy_amount = (uint64_t) (((trades[trade].sell_amount * item_costs[trades[trade].sell_item]) * ((rand() / (double) RAND_MAX) * 0.6 + 0.75)) / item_costs[trades[trade].buy_item]);
                    trades[trade].timer = (int16_t) ((rand() / (double) RAND_MAX) * 8 + 16);
                }
            }

            trade_tick = (int8_t) ((rand() / (double) RAND_MAX) * 5) + 4;
        }

        if (unlocked_deals != 0 && deal_tick-- < 0) {
            for (int8_t i = 0; i < 3; i++) {
                if (deals[i].timer >= 0)
                    deals[i].timer--;
            }

            if (rand() < RAND_MAX * 0.35) {
                int8_t deal = -1;

                for (int8_t i = 0; i < 3; i++) {
                    if (deals[i].timer == -1) {
                        deal = i;
                        break;
                    }
                }

                if (deal != -1) {
                    deals[deal].sell_item = (int8_t) ((rand() / (double) RAND_MAX) * unlocked_deals);
                    deals[deal].sell_amount = (uint64_t) (resources[deals[deal].sell_item] * ((rand() / (double) RAND_MAX) * 0.5 + 0.1) + (rand() / (double) RAND_MAX) * 20 * multipliers[deals[deal].sell_item]);
                
                    do {
                        deals[deal].buy_item = (int8_t) ((rand() / (double) RAND_MAX) * unlocked_deals);
                    } while (deals[deal].buy_item == deals[deal].sell_item);

                    deals[deal].buy_amount = (uint64_t) (((deals[deal].sell_amount * item_costs[deals[deal].sell_item]) * ((rand() / (double) RAND_MAX) + 0.6)) / item_costs[deals[deal].buy_item]);
                    deals[deal].timer = (int16_t) ((rand() / (double) RAND_MAX) * 3 + 8);
                }
            }

            deal_tick = (int8_t) ((rand() / (double) RAND_MAX) * 2) + 2;
        }

        if (upgrades[0].thread == -1) {
            if (unlocked == 0) {
                upgrades[0].thread = 0;
                upgrades[0].id = 0;
            } else if (unlocked == 1 && resources[2]) {
                upgrades[0].thread = 0;
                upgrades[0].id = 1;
            } else if (unlocked == 2 && resources[3]) {
                upgrades[0].thread = 0;
                upgrades[0].id = 2;
            } else if (unlocked == 3 && resources[4]) {
                upgrades[0].thread = 0;
                upgrades[0].id = 3;
            } else if (unlocked == 4 && resources[5]) {
                upgrades[0].thread = 0;
                upgrades[0].id = 4;
            } else if (unlocked == 5 && resources[6]) {
                upgrades[0].thread = 0;
                upgrades[0].id = 5;
            } else if (unlocked == 6 && resources[7]) {
                upgrades[0].thread = 0;
                upgrades[0].id = 6;
            } else if (unlocked == 7 && resources[8]) {
                upgrades[0].thread = 0;
                upgrades[0].id = 7;
            }
        }

        if (upgrades[1].thread == -1) {
            for (int8_t i = 0; i < 9; i++) {
                if (multipliers[i] == 5.0 && resources[i] > 2000) {
                    upgrades[1].thread = 1;
                    upgrades[1].id = i + 45;

                    break;
                }
            }

            for (int8_t i = 0; i < 9; i++) {
                if (multipliers[i] == 2.0 && resources[i] > 1000) {
                    upgrades[1].thread = 1;
                    upgrades[1].id = i + 36;

                    break;
                }
            }

            for (int8_t i = 0; i < 9; i++) {
                if (multipliers[i] == 1.75 && resources[i] > 400) {
                    upgrades[1].thread = 1;
                    upgrades[1].id = i + 27;

                    break;
                }
            }

            for (int8_t i = 0; i < 9; i++) {
                if (multipliers[i] == 1.25 && resources[i] > 150) {
                    upgrades[1].thread = 1;
                    upgrades[1].id = i + 18;

                    break;
                }
            }

            for (int8_t i = 0; i < 9; i++) {
                if (multipliers[i] == 1.1 && resources[i] > 50) {
                    upgrades[1].thread = 1;
                    upgrades[1].id = i + 9;

                    break;
                }
            }

            for (int8_t i = 0; i < 9; i++) {
                if (multipliers[i] == 1.0 && resources[i] > 20) {
                    upgrades[1].thread = 1;
                    upgrades[1].id = i;

                    break;
                }
            }
        }

        if (upgrades[2].thread == -1) {
            if (auto_level == 0 && unlocked >= 1 && resources[2]) {
                upgrades[2].thread = 2;
                upgrades[2].id = 0;
            } else if (auto_level == 1 && unlocked >= 2 && resources[3]) {
                upgrades[2].thread = 2;
                upgrades[2].id = 1;
            } else if (auto_level == 2 && unlocked >= 3 && resources[4]) {
                upgrades[2].thread = 2;
                upgrades[2].id = 2;
            } else if (auto_level == 3 && unlocked >= 4 && resources[5]) {
                upgrades[2].thread = 2;
                upgrades[2].id = 3;
            } else if (auto_level == 4 && unlocked >= 6) {
                upgrades[2].thread = 2;
                upgrades[2].id = 4;
            } else if (unlocked_deals == 0 && unlocked >= 1) {
                upgrades[2].thread = 3;
                upgrades[2].id = 0;
            } else if (unlocked_deals == 2 && unlocked >= 3) {
                upgrades[2].thread = 3;
                upgrades[2].id = 1;
            } else if (unlocked_deals == 4 && unlocked >= 5) {
                upgrades[2].thread = 3;
                upgrades[2].id = 2;
            } else if (unlocked_deals == 6 && unlocked >= 7) {
                upgrades[2].thread = 3;
                upgrades[2].id = 3;
            }
        }

        turn_count++;
    }

    printf("\033[F\033[A  Finished game after %d turn(s)\n\n", turn_count);

    return 0;
}

(Compiled on my machine (Bash, Linux VM) with gcc -g -lm -o merchant merchant.c)

Some things I was wondering about:

  • Is there a way to get rid of all those else ifs? Maybe a switch statement or for loop?
  • Is there a better way to overwrite all of the program's previous output than just moving the cursor to overwrite it?
  • Is my code readable?

First post on code review, if there's something I forgot to include let me know!

\$\endgroup\$
5
  • \$\begingroup\$ (In case of any confusion thread in upgrade doesn't refer to threads in the computer sense, it's just the word I used for "upgrade path/type") \$\endgroup\$ – Redwolf Programs Nov 18 '20 at 3:31
  • 1
    \$\begingroup\$ Perfect first question, don't worry about it. \$\endgroup\$ – Aryan Parekh Nov 18 '20 at 5:40
  • \$\begingroup\$ OT: running the posted code through a compiler results in LOTs of warnings. When compiling, always enable the warnings, then fix those warnings. ( for gcc, at a minimum use: -Wall -Wextra -Wconversion -pedantic -std=gnu11 ) Note: other compilers use different options to produce the same results. \$\endgroup\$ – user3629249 Nov 20 '20 at 13:16
  • \$\begingroup\$ OT: the posted code fails to prompt the user as to what the user should input at the >> prompt \$\endgroup\$ – user3629249 Nov 20 '20 at 13:20
  • 1
    \$\begingroup\$ PS look up for the ncurses.h / curses.h header-file, it's a library for terminal "GUI" applications, very handy. \$\endgroup\$ – paladin Nov 20 '20 at 16:53
2
\$\begingroup\$

You may use a switch, but be informed that using break; to break the loop would not work (Instead you would have to use a goto to break the loop.); same applies to a nested for-loop (With additional regards to continue, which would also have to be used in a goto way.), so summa summarum, using if, else if, else is the best way in your case.

Moving the cursor is the way how to do in a terminal application, totally fine.

Your code is readable, but I would suggest to outsource some code to functions, especially all this redundant checking.

PS I would probably separate the input code from the output code. Meaning, I would write a function only for user inputs, one function for processing those inputs and another function for outputs.

\$\endgroup\$
2
\$\begingroup\$

Is there a way to get rid of all those else ifs? Maybe a switch statement or for loop?

Certainly.

With if (strncmp(words[0], "redo\0", .... consider a table of pairs of string and function.

// Something like 
for (i=0; i<table_size; i++) {
  if (my_compare(words[0], table[i].s)) {
    table[i].f(state_data, words, ....);  // do whatever per helper function
    break;
  }
}
if (i == table_size) my_handle_no_match();

With shorten_number():

To get a correct answer is tricky mixing 64-bit integer math with double.

Example: (Note original code provides errant results with large values and near 999.995+*10^3x)

// Reduce to "1.00x" to "999.99x"
void shorten_number(char *dest, uint64_t number) {
  if (number < 1000) {
    sprintf(dest, "%d", (int) number);
    return;
  }
  static const char suffix[] = " KMBTqQ"; // or maybe metric " kMGTPEZY"
  int suffix_index = 0;
  double scale = 1.0;

  while (number/scale >= 999.995) {
    scale *= 1000.0;
    suffix_index++;
  }
  sprintf(dest, "%.2f%c", number / scale, suffix[suffix_index]);
}

Is there a better way to overwrite all of the program's previous output than just moving the cursor to overwrite it?

C has no standard way to overwrite previous output. Any solution is specific to the terminal type.

Rather than printf("\033[F\033[A\033[A ..., consider making a helper function erase_line() that does the same, but with this abstraction allows to more easily identify and maintain implementation specific code.

Same applies for any place an ANSI escape sequence is used - abstract it.


Is my code readable?

Mostly yes.

I find the 4 space indent excessive over 2, yet such style issues are best decided by your group's style guide.

main() is long. Perhaps break it up.

Too many naked magic numbers.


Zero data cleanly

Rather than potentially miscalculated code

// memset(trade_list, '\0', sizeof(trade_list[0][0]) * 240);
memset(trade_list, 0, sizeof trade_list);

or initialize with zero

// char trade_list[5][48];
char trade_list[5][48] = { 0 };

Wrong format

uint64_t number
// at this point number < 1000, but still type uint64_t
// sprintf(dest, "%d", number);
sprintf(dest, "%d", (int) number);
// or 
sprintf(dest, "%" PRIu64, number);

This hints at a serious weakness in OP's efforts: not fully enabling all warnings as a good well enabled compiler will warn about this. This is the most important feedback: enable all warnings and let your compiler give your rapid feedback.

\$\endgroup\$

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.