xolatilization/xhallenge.c
2025-04-14 19:49:49 +02:00

532 lines
21 KiB
C
Executable File

#define use_fatal_failure
#include <xolatile/xtandard.h>
#include <xolatile/xcript.h>
#include <xolatile/xerminal.h>
#define challenges_per_day (10)
#define attribute_minimum (1)
#define attribute_maximum (10)
typedef enum {
special_strength, special_perception, special_edurance, special_charisma,
special_intelligence, special_agility, special_luck,
special_count
} special_enumeration;
typedef struct {
uint count;
uint limit;
uint * completed;
uint * * requirement;
uint * type;
char * * class;
char * * description;
} challenge_structure;
static char * special_name [special_count] = {
"strength", "perception", "edurance", "charisma", "intelligence", "agility", "luck"
};
static bool challenge_is_repeatable (challenge_structure * challenges, uint index) {
return ((challenges->type [index] == special_strength + 1) ||
(challenges->type [index] == special_edurance + 1) ||
(challenges->type [index] == special_agility + 1));
}
static bool challenge_is_available (challenge_structure * challenges, uint index) {
return ((challenges->completed [index] == 0) || (challenge_is_repeatable (challenges, index) == true));
}
static bool challenge_is_completable (uint * special, challenge_structure * challenges, uint index) {
if (challenge_is_available (challenges, index) == false) {
return (false);
}
for (uint check = 0; check < special_count; ++check) {
if (challenges->requirement [index] [check] > special [check]) {
return (false);
}
}
return (true);
}
static void render_challenge_list (terminal_structure * terminal, uint * special, challenge_structure * challenges, uint x, uint y) {
uint count = 0;
uint * array = allocate (challenges->count * sizeof (* array));
for (uint index = 0; index < challenges->count; ++index) {
if (challenge_is_completable (special, challenges, index) == true) {
++count;
array [count - 1] = index;
}
}
terminal_render_format (terminal, "Count of unlocked challenges: /2/B%i", x, y + 0, count);
terminal_render_format (terminal, "Count of locked challenges: /1/B%i", x, y + 1, challenges->count - count);
for (uint index = 0; index < count; ++index) {
terminal_render_character (terminal, '[', colour_grey, effect_bold, x, y + index + 2);
for (uint value = 0; value < special_count; ++value) {
terminal_render_number (terminal, challenges->requirement [array [index]] [value], value + 1, effect_bold, x + 3 * value + 2, y + index + 2);
}
terminal_render_character (terminal, ']', colour_grey, effect_bold, x + special_count * 3 + 1, y + index + 2);
terminal_render_format (terminal, "/7/B%s/0/B --- /-%s", x + special_count * 3 + 3, y + index + 2,
challenges->class [array [index]],
challenges->description [array [index]]);
}
array = deallocate (array);
}
static void render_special_attributes (terminal_structure * terminal, uint * special, uint offset, uint selection) {
for (uint index = 0; index < special_count; ++index) {
uint effect = (selection == index) ? effect_bold : effect_normal;
uint length = attribute_maximum + 3;
char name [32] = "";
string_copy_limit (name, special_name [index], sizeof (name));
terminal_render_fill_bar (terminal, special [index], 10, '+', index + 1, effect, 0, index + offset);
terminal_render_string (terminal, capitalize (name), index + 1, effect, length, index + offset);
}
}
static void prompt_special_attributes (uint * special) {
terminal_structure * terminal = terminal_initialize ();
uint selection = 0;
bool show_help = false;
char * main_messages [] = {
"Press H or Tab to toggle help.",
"Press Q or Escape to use default settings.",
"Press S or Enter to save changes.",
"",
"Choose your SPECIAL attributes:"
};
char * help_messages [] = {
"Show help - H or Tab",
"Use default settings - Q or Escape",
"Save and quit - S or Enter",
"Move up - J, Up arrow key or KP8",
"Move down - K, Down arrow key or KP2",
"Increase attribute - P, Right arrow key or KP6",
"Decrease attribute - N, Left arrow key or KP4"
};
for (uint index = 0; index < special_count; ++index) {
special [index] = 5;
}
while (terminal->active == true) {
terminal_render_background (terminal, ' ', colour_white, effect_normal);
for (uint index = 0; index < array_length (main_messages); ++index) {
terminal_render_string (terminal, main_messages [index], colour_white, effect_normal, 0, index);
}
render_special_attributes (terminal, special, array_length (main_messages) + 1, selection);
if (show_help == true) {
for (uint index = 0; index < array_length (help_messages); ++index) {
uint offset = array_length (main_messages) + special_count + 2;
terminal_render_string (terminal, help_messages [index], colour_white, effect_normal, 0, index + offset);
}
}
terminal_synchronize (terminal);
if ((terminal->signal [signal_tabulator] == true) || (terminal->signal [signal_h] == true)) {
show_help = ! show_help;
} else if ((terminal->signal [signal_escape] == true) || (terminal->signal [signal_q] == true)) {
for (uint index = 0; index < special_count; ++index) {
special [index] = 5;
} break;
} else if ((terminal->signal [signal_return] == true) || (terminal->signal [signal_s] == true)) {
break;
} else if ((terminal->signal [signal_arrow_up] == true) || (terminal->signal [signal_j] == true)) {
selection = (selection - 1 + special_count) % special_count;
} else if ((terminal->signal [signal_arrow_down] == true) || (terminal->signal [signal_k] == true)) {
selection = (selection + 1) % special_count;
} else if ((terminal->signal [signal_arrow_left] == true) || (terminal->signal [signal_h] == true)) {
--special [selection];
special [selection] = (special [selection] < 1) ? 1 : special [selection];
} else if ((terminal->signal [signal_arrow_right] == true) || (terminal->signal [signal_l] == true)) {
++special [selection];
special [selection] = (special [selection] > 10) ? 10 : special [selection];
}
}
terminal = terminal_deinitialize (terminal);
}
static void import_user_configuration (uint * special, uint challenge_count, uint * * daily_challenges, bool * * completition) {
bool special_defined [special_count] = { false };
bool daily_challenges_defined = false;
bool completition_defined = false;
script_information * information = allocate (sizeof (* information));
script_structure * structure = script_open (configuration_format ("xhallenge.cfg"));
for (script_word_type word = script_parser (structure); word != script_end; word = script_parser (structure)) {
if (word == script_marker) {
if (script_compare (structure, "challenges") == true) {
uint check = 0;
script_failure (structure, daily_challenges_defined == true, "Challenge array was already defined.");
(* daily_challenges) = script_expect_ordered_array (information, structure, & check);
daily_challenges_defined = true;
script_failure (structure, check != challenges_per_day, "Ordered array 'daily_challenges' is incomplete.");
for (uint index = 0; index < challenges_per_day; ++index) {
script_failure (structure, (* daily_challenges) [index] >= challenge_count, "Invalid index.");
}
} else if (script_compare (structure, "completition") == true) {
uint check = 0;
script_failure (structure, completition_defined == true, "Completition array was already defined.");
(* completition) = script_expect_ordered_array (information, structure, & check);
completition_defined = true;
script_failure (structure, check != challenges_per_day, "Ordered array 'completition' is incomplete.");
} else for (uint index = 0; index < special_count; ++index) {
if (script_compare (structure, special_name [index]) == true) {
script_failure (structure, special_defined [index] == true, "Attribute was already defined.");
special [index] = script_expect_number (structure);
script_failure (structure, special [index] > attribute_maximum, "Attribute exceeded maximum value.");
script_failure (structure, special [index] < attribute_minimum, "Attribute exceeded minimum value.");
special_defined [index] = true;
}
}
} else if ((word == script_end) || (word == script_comment)) {
continue;
} else {
script_failure (structure, true, "Expected 'marker = number' in configuration script.");
}
}
structure = script_close (structure);
information = deallocate (information);
}
static void export_user_configuration (uint * special, uint * daily_challenges, bool * completition) {
char buffer [4096] = "";
for (uint index = 0; index < special_count; ++index) {
string_concatenate (buffer, format ("%s = %i\n", special_name [index], special [index]));
}
string_concatenate (buffer, "challenges = (");
for (uint index = 0; index < challenges_per_day; ++index) {
string_concatenate (buffer, number_to_string (daily_challenges [index]));
if (index < challenges_per_day - 1) {
string_concatenate (buffer, ", ");
}
}
string_concatenate (buffer, ")\n");
string_concatenate (buffer, "completition = (");
for (uint index = 0; index < challenges_per_day; ++index) {
string_concatenate (buffer, number_to_string (completition [index]));
if (index < challenges_per_day - 1) {
string_concatenate (buffer, ", ");
}
}
string_concatenate (buffer, ")\n");
configuration_export ("xhallenge.cfg", buffer);
}
static challenge_structure * challenges_initialize (uint limit) {
challenge_structure * challenges = allocate (sizeof (* challenges));
challenges->limit = limit;
return (challenges);
}
static challenge_structure * challenges_deinitialize (challenge_structure * challenges) {
for (uint index = 0; index < challenges->count; ++index) {
challenges->requirement [index] = deallocate (challenges->requirement [index]);
challenges->description [index] = deallocate (challenges->description [index]);
challenges->class [index] = deallocate (challenges->class [index]);
}
challenges->completed = deallocate (challenges->completed);
challenges->requirement = deallocate (challenges->requirement);
challenges->type = deallocate (challenges->type);
challenges->class = deallocate (challenges->class);
challenges->description = deallocate (challenges->description);
return (deallocate (challenges));
}
static void import_challenges (challenge_structure * challenges) {
bool completed_defined = false;
bool requirement_defined = false;
bool type_defined = false;
bool class_defined = false;
bool description_defined = false;
script_information * information = allocate (sizeof (* information));
script_structure * structure = script_open (configuration_format ("xhallenge_list.cfg"));
for (script_word_type word = script_parser (structure); word != script_end; word = script_parser (structure)) {
if (word == script_header) {
if (challenges->count > 0) {
script_warning (structure,
(completed_defined == false) ||
(requirement_defined == false) ||
(type_defined == false) ||
(class_defined == false) ||
(description_defined == false),
"Some fields were left uninitialized and default to zero.");
}
++challenges->count;
challenges->completed = reallocate (challenges->completed, challenges->count * sizeof (* challenges->completed));
challenges->requirement = reallocate (challenges->requirement, challenges->count * sizeof (* challenges->requirement));
challenges->type = reallocate (challenges->type, challenges->count * sizeof (* challenges->type));
challenges->class = reallocate (challenges->class, challenges->count * sizeof (* challenges->class));
challenges->description = reallocate (challenges->description, challenges->count * sizeof (* challenges->description));
completed_defined = false;
requirement_defined = false;
type_defined = false;
class_defined = false;
description_defined = false;
} else if (word == script_marker) {
uint current = challenges->count - 1;
if (script_compare (structure, "completed") == true) {
script_failure (structure, completed_defined == true, "Marker 'completed' already defined.");
challenges->completed [current] = script_expect_number (structure);
completed_defined = true;
} else if (script_compare (structure, "requirement") == true) {
uint check = 0;
script_failure (structure, requirement_defined == true, "Marker 'requirement' already defined.");
challenges->requirement [current] = script_expect_ordered_array (information, structure, & check);
requirement_defined = true;
script_failure (structure, check != special_count, "Ordered array doesn't have enough elements.");
} else if (script_compare (structure, "type") == true) {
script_failure (structure, type_defined == true, "Marker 'type' already defined.");
challenges->type [current] = script_expect_number (structure);
type_defined = true;
} else if (script_compare (structure, "class") == true) {
script_failure (structure, class_defined == true, "Marker 'class' already defined.");
challenges->class [current] = script_expect_string (structure);
class_defined = true;
} else if (script_compare (structure, "description") == true) {
script_failure (structure, description_defined == true, "Marker 'description' already defined.");
challenges->description [current] = script_expect_string (structure);
description_defined = true;
} else {
script_failure (structure, true, "Expected name, faction, statistic or ability.");
}
} else if ((word == script_end) || (word == script_comment)) {
continue;
} else {
script_failure (structure, true, "Expected 'marker = number/string/ordered_array' in configuration script.");
}
}
structure = script_close (structure);
information = deallocate (information);
}
static void export_challenges (challenge_structure * challenges) {
int file = file_open (configuration_format ("xhallenge_list.cfg"), file_flag_edit | file_flag_truncate);
for (uint index = 0; index < challenges->count; ++index) {
file_echo (file, "[] ");
file_echo (file, format ("completed = %i ", challenges->completed [index]));
file_echo (file, "requirement = (");
for (uint subindex = 0; subindex < special_count; ++subindex) {
file_echo (file, number_to_string (challenges->requirement [index] [subindex]));
if (subindex < special_count - 1) {
file_echo (file, ", ");
}
}
file_echo (file, ") ");
file_echo (file, format ("type = %i ", challenges->type [index]));
file_echo (file, format ("class = \"%s\" ", challenges->class [index]));
file_echo (file, format ("description = \"%s\"\n", challenges->description [index]));
}
file = file_close (file);
}
static uint generate_challenge (uint * special, challenge_structure * challenges) {
uint index = urandomize (0, challenges->count - 1);
bool valid = false;
while (valid == false) {
valid = true;
index = urandomize (0, challenges->count - 1);
if ((challenges->completed [index] > 0) && (challenges->type [index] != 1) && (challenges->type [index] != 3)) continue;
for (uint check = 0; check < special_count; ++check) {
if (challenges->requirement [index] [check] > special [check]) {
valid = false;
break;
}
}
}
return (index);
}
static void generate_challenges (uint * special, challenge_structure * challenges, uint * * daily_challenges, bool * * completition) {
(* daily_challenges) = allocate (challenges_per_day * sizeof (* * daily_challenges));
(* completition) = allocate (challenges_per_day * sizeof (* * completition));
for (uint index = 0; index < challenges_per_day; ++index) {
(* daily_challenges) [index] = generate_challenge (special, challenges);
}
}
static void render_challenges (uint * special, challenge_structure * challenges, uint * daily_challenges, bool * completition) {
terminal_structure * terminal = terminal_initialize ();
uint selection = 0;
bool show_help = false;
bool show_list = false;
char * main_messages [] = {
"Press H or Tab to toggle help.",
"Press Q or Escape to quit the program without saving changes.",
"Press S or Enter to save changes and quit the program.",
"",
"Your daily challenges:"
};
char * help_messages [] = {
"Show help - H or Tab",
"Quit - Q or Escape",
"Save and quit - S or Enter",
"Move up - J, Up arrow key or KP8",
"Move down - K, Down arrow key or KP2",
"(Un)Mark challenge - M or Space",
"Change challenge - C or Backspace",
"Reset challenges - R",
"List challenges - L"
};
while (terminal->active == true) {
terminal_render_background (terminal, ' ', colour_white, effect_normal);
for (uint index = 0; index < array_length (main_messages); ++index) {
terminal_render_string (terminal, main_messages [index], colour_white, effect_normal, 0, index);
}
for (uint index = 0; index < challenges_per_day; ++index) {
uint type = challenges->type [daily_challenges [index]];
char * class = challenges->class [daily_challenges [index]];
char * description = challenges->description [daily_challenges [index]];
uint effect = (selection == index) ? effect_bold : effect_normal;
uint offset = array_length (main_messages) + 2;
uint alignment = string_length (class) + 4;
terminal_render_toggle (terminal, completition [index], 0, index + offset);
terminal_render_string (terminal, class, type, effect, 4, index + offset);
terminal_render_string (terminal, " --- ", colour_grey, effect_bold, alignment + 0, index + offset);
terminal_render_string (terminal, description, colour_white, effect, alignment + 5, index + offset);
}
if (show_list == true) {
render_challenge_list (terminal, special, challenges, 80, 0);
}
if (show_help == true) {
uint offset = array_length (main_messages) + challenges_per_day + 3;
for (uint index = 0; index < array_length (help_messages); ++index) {
terminal_render_string (terminal, help_messages [index], colour_white, effect_normal, 0, index + offset);
}
render_special_attributes (terminal, special, offset + array_length (help_messages) + 1, special_count);
}
terminal_synchronize (terminal);
if ((terminal->signal [signal_tabulator] == true) || (terminal->signal [signal_h] == true)) {
show_help = ! show_help;
} else if (/*(terminal->signal [signal_tabulator] == true) || */(terminal->signal [signal_l] == true)) {
show_list = ! show_list;
} else if ((terminal->signal [signal_return] == true) || (terminal->signal [signal_s] == true)) {
export_user_configuration (special, daily_challenges, completition);
break;
} else if ((terminal->signal [signal_escape] == true) || (terminal->signal [signal_q] == true)) {
break;
} else if ((terminal->signal [signal_arrow_up] == true) || (terminal->signal [signal_j] == true)) {
selection = (selection - 1 + challenges_per_day) % challenges_per_day;
} else if ((terminal->signal [signal_arrow_down] == true) || (terminal->signal [signal_k] == true)) {
selection = (selection + 1) % challenges_per_day;
} else if ((terminal->signal [signal_space] == true) || (terminal->signal [signal_m] == true)) {
completition [selection] = ! completition [selection];
challenges->completed [daily_challenges [selection]] += (completition [selection] == true) ? 1 : -1;
} else if ((terminal->signal [signal_backspace] == true) || (terminal->signal [signal_c] == true)) {
daily_challenges [selection] = generate_challenge (special, challenges);
completition [selection] = false;
} else if (/*(terminal->signal [signal_backspace] == true) || */(terminal->signal [signal_r] == true)) {
for (uint index = 0; index < challenges_per_day; ++index) {
daily_challenges [index] = generate_challenge (special, challenges);
completition [index] = false;
}
}
}
terminal = terminal_deinitialize (terminal);
}
int main (int argc, char * * argv) {
uint special [special_count] = { 0 };
uint * daily_challenges = null;
bool * completition = null;
challenge_structure * challenges = challenges_initialize (1024);
randomize_seed_by_time ();
if (argc == 2) {
if (string_compare (argv [1], "-r") == true) {
configuration_remove ("xhallenge.cfg");
}
}
import_challenges (challenges);
if (configuration_exists ("xhallenge.cfg") == true) {
import_user_configuration (special, challenges->count, & daily_challenges, & completition);
} else {
prompt_special_attributes (special);
generate_challenges (special, challenges, & daily_challenges, & completition);
}
render_challenges (special, challenges, daily_challenges, completition);
export_challenges (challenges);
challenges = challenges_deinitialize (challenges);
daily_challenges = deallocate (daily_challenges);
completition = deallocate (completition);
return (log_success);
}