/* This code is hereby released to the public domain. ~aaaaaa123456789, 2015-03-30, last updated 2015-12-02 */ #include #include #include #define DEFAULT_HP 10 #define DEFAULT_POWER 10 #define DEFAULT_BONUS 0 #define DEFAULT_MIN_RANGE 50 #define DEFAULT_MAX_RANGE 150 #define ATTACK_ROUNDS 10 #define DEFAULT_MASTERY 0 // NOTE: don't even try using a C++ compiler here (unless it has a C mode). It will cry. A lot. struct player { char * name; unsigned power; int bonus; unsigned min; unsigned max; unsigned mastery; }; struct battle { char * location; unsigned HP; struct player player1; struct player player2; }; struct node { struct battle battle; struct node * next; }; struct range { unsigned min; unsigned max; }; unsigned randstate1 = 0, randstate2 = 0; // random number generator state int main(void); void init_RNG(void); unsigned get_RNG(void); char * get_line(void); unsigned get_number(unsigned); int get_signed_number(int); struct node * get_battle_set(void); struct node * get_battle(int *); void delete_last_battle(struct node **, struct node **); struct player get_player_info(const char *); void make_battles(FILE *, struct node *); void make_battle(FILE *, struct battle); struct range effective_range(struct player); unsigned roll(unsigned); // output routines void separator(FILE *); void header(FILE *, const char *); void player_info(FILE *, struct player, struct range); void player_separator(FILE *); void battle_header(FILE *, const char *, const char *); void attack(FILE *, unsigned, unsigned, unsigned, unsigned, unsigned); void battle_footer(FILE *); void footer(FILE *, const char *, const char *, const char *, unsigned, unsigned); int main (void) { srand(time(NULL)); init_RNG(); struct node * battles = get_battle_set(); if (!battles) return 0; puts(""); printf("%s", "File to write [default: output to console]: "); char * filename = get_line(); FILE * file; if (*filename) file = fopen(filename, "w"); else file = stdout; if (!file) { fprintf(stderr, "Error opening %s for writing\n", filename); return 1; } make_battles(file, battles); return 0; } void init_RNG (void) { unsigned char count; unsigned num, result = 0; for (count = 0; count < 4; count ++) { num = (rand() >> 5) & 255; result |= num << (8 * count); } randstate1 = randstate2; randstate2 = result; if (!(randstate1 && randstate2) || (randstate1 == 0x464fffff) || (randstate2 == 0x9068ffff)) init_RNG(); } unsigned get_RNG (void) { // George Marsaglia's MWC. Better than rand() for most cases. randstate2 = (36969 * (randstate2 & 65535) + (randstate2 >> 16)) & 0xffffffffu; randstate1 = (18000 * (randstate1 & 65535) + (randstate1 >> 16)) & 0xffffffffu; return ((randstate2 << 16) + randstate1) & 0xffffffffu; } char * get_line (void) { char * line = NULL; unsigned length = 0; int newchar; for (newchar = getchar(); (newchar != EOF) && (newchar != '\n'); newchar = getchar()) { line = realloc(line, ++ length); line[length - 1] = newchar; } line = realloc(line, length + 1); line[length] = 0; return line; } unsigned get_number (unsigned default_value) { char * line = NULL; char * end; unsigned number, attempted = 0; while (1) { if (attempted) { free(line); printf("%s", "That is not a valid number. Please enter a number: "); } else attempted = 1; line = get_line(); if (!(line && *line)) return default_value; number = strtoul(line, &end, 10); if (*end) continue; free(line); return number; } } int get_signed_number (int default_value) { char * line = NULL; char * end; int number, attempted = 0; while (1) { if (attempted) { free(line); printf("%s", "That is not a valid number. Please enter a number: "); } else attempted = 1; line = get_line(); if (!(line && *line)) return default_value; number = strtol(line, &end, 10); if (*end) continue; free(line); return number; } } struct node * get_battle_set (void) { int x = 0; struct node * result = get_battle(&x); struct node * current = result; struct node * new_node; while (1) { x = result != NULL; new_node = get_battle(&x); if (x) { if (!new_node) break; if (result) current = current -> next = new_node; else result = current = new_node; } else delete_last_battle(&result, ¤t); } return result; } struct node * get_battle (int * status) { if (*status) printf("%s", "Enter coordinates [: stop, delete: delete last]: "); else printf("%s", "Enter coordinates [empty input to stop]: "); char * coords = get_line(); if (!(coords && *coords)) { free(coords); *status = 1; return NULL; } if (!strcmp(coords, "delete")) { free(coords); if (!*status) { puts("Nothing to delete."); return get_battle(status); } *status = 0; return NULL; } struct battle battle = {.location = coords}; printf("Enter the HP of cell %s [default: %u]: ", coords, DEFAULT_HP); battle.HP = get_number(DEFAULT_HP); battle.player1 = get_player_info("Attacker"); battle.player2 = get_player_info("Defender"); puts(""); struct node * new = malloc(sizeof(struct node)); new -> battle = battle; new -> next = NULL; *status = 1; return new; } void delete_last_battle (struct node ** list, struct node ** pointer) { if (!*list) return; // you're drunk, go home if (*list == *pointer) { free(*list); *list = *pointer = NULL; return; } struct node * previous; for (previous = *list; previous -> next != *pointer; previous = previous -> next); *pointer = previous; free(previous -> next); previous -> next = NULL; } struct player get_player_info (const char * player) { printf("%s data:\n", player); printf("%s", "Name: "); char * name = get_line(); while (!(name && *name)) { free(name); printf("%s", "The name must not be blank. Name: "); name = get_line(); } struct player result = {.name = name}; printf("Base power [default: %u]: ", DEFAULT_POWER); result.power = get_number(DEFAULT_POWER); printf("Bonus %% [default: %d]: ", DEFAULT_BONUS); result.bonus = get_signed_number(DEFAULT_BONUS); printf("Min. range %% [default: %u]: ", DEFAULT_MIN_RANGE); result.min = get_number(DEFAULT_MIN_RANGE); printf("Max. range %% [default: %u]: ", DEFAULT_MAX_RANGE); result.max = get_number(DEFAULT_MAX_RANGE); printf("Mastery %% [default: %u]: ", DEFAULT_MASTERY); result.mastery = get_number(DEFAULT_MASTERY); return result; } void make_battles (FILE * output, struct node * battles) { make_battle(output, battles -> battle); for (battles = battles -> next; battles; battles = battles -> next) { separator(output); make_battle(output, battles -> battle); } } void make_battle (FILE * output, struct battle battle) { header(output, battle.location); struct range range1 = effective_range(battle.player1), range2 = effective_range(battle.player2); player_info(output, battle.player1, range1); player_separator(output); player_info(output, battle.player2, range2); battle_header(output, battle.player1.name, battle.player2.name); unsigned char attacks = 0, wins = 0; while ((attacks < ATTACK_ROUNDS) && (wins < battle.HP)) { unsigned roll1 = roll(range1.max - range1.min), roll2 = roll(range2.max - range2.min); unsigned char result = ((range1.min + roll1) > (range2.min + roll2)) ? 1 : ((range2.min + roll2) > (range1.min + roll1)) ? 2 : 0; attacks ++; if (result == 1) wins ++; attack(output, range1.min, roll1, range2.min, roll2, result); } battle_footer(output); footer(output, battle.player1.name, battle.player2.name, battle.location, wins, battle.HP - wins); } struct range effective_range (struct player player) { unsigned long long base = player.power * (100 + player.bonus); unsigned long long min = base * player.min, max = base * player.max; return (struct range) {.min = (min * 100 + (max - min) * player.mastery) / 1000000, .max = max / 10000}; } unsigned roll (unsigned max_roll) { if (!max_roll) return 0; unsigned limit = max_roll + 1; unsigned result = -1; while ((result + limit - (result % limit)) < result) result = get_RNG(); return result % limit; } // Output routines begin here. If the output format is to be changed, everything that should be changed starts here. void separator (FILE * output) { fputs("\n[hr]\n\n", output); } void header (FILE * output, const char * coordinate) { fprintf(output, "[b]Battle at %s[/b]\n", coordinate); fputs("[spoiler][center][b]", output); } void player_info (FILE * output, struct player player, struct range range) { fprintf(output, "%s's Power: %u\n", player.name, player.power); fprintf(output, "%s's Bonuses: %+d%%\n", player.name, player.bonus); fprintf(output, "%s's Mastery: %u%%\n", player.name, player.mastery); unsigned range_integer = player.min * 100 + (player.max - player.min) * player.mastery; unsigned char range_fraction = range_integer % 100; range_integer /= 100; if (range_fraction) fprintf(output, "%s's Range: %u.%02hhu%% - %u%%\n", player.name, range_integer, range_fraction, player.max); else fprintf(output, "%s's Range: %u%% - %u%%\n", player.name, range_integer, player.max); fprintf(output, "%s's Effective Range: %u - %u\n", player.name, range.min, range.max); } void player_separator (FILE * output) { fputs("\nVS\n\n", output); } void battle_header (FILE * output, const char * attacker, const char * defender) { fputs("\n[table=width: 400px; margin: auto]\n[tr]\n", output); fprintf(output, "[td]%s[/td]\n", attacker); fprintf(output, "[td]%s[/td]\n", defender); fputs("[td]Result[/td]\n", output); fputs("[/tr]\n", output); } void attack (FILE * output, unsigned min1, unsigned roll1, unsigned min2, unsigned roll2, unsigned result) { if (result > 2) return; fputs("[tr]\n", output); fprintf(output, "[td]%u + %u = %u[/td]\n", min1, roll1, min1 + roll1); fprintf(output, "[td]%u + %u = %u[/td]\n", min2, roll2, min2 + roll2); fprintf(output, "[td][color=%s]%s[/color][/td]\n", result[(const char * []) {"#000000", "#008000", "#ff0000"}], result[(const char * []) {"Draw", "Win", "Lose"}] ); fputs("[/tr]\n", output); } void battle_footer (FILE * output) { fputs("[/table]\n[/b][/center][/spoiler]\n\n", output); } void footer (FILE * output, const char * attacker, const char * defender, const char * coordinate, unsigned wins, unsigned remaining) { fputs("[b]", output); if (wins) { fprintf(output, "[color=#008000]%s wins %u %s.[/color]\n\n", attacker, wins, (wins == 1) ? "fight" : "fights"); fprintf(output, "%s loses %uHP.\n", defender, wins); } else fprintf(output, "[color=#ff0000]%s wins no fights.[/color]\n\n", attacker); if (remaining) fprintf(output, "%s has %uHP remaining.", coordinate, remaining); else fprintf(output, "%s falls to %s.", coordinate, attacker); fputs("[/b]\n", output); }