/*
This code is hereby released to the public domain.
~aaaaaa123456789, 2015-03-30, last updated 2015-12-02
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#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 [<empty input>: 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);
}
Comments