532 lines
21 KiB
C
Executable File
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 {
|
|
natural count;
|
|
natural limit;
|
|
natural * completed;
|
|
natural * * requirement;
|
|
natural * type;
|
|
character * * class;
|
|
character * * description;
|
|
} challenge_structure;
|
|
|
|
static character * special_name [special_count] = {
|
|
"strength", "perception", "edurance", "charisma", "intelligence", "agility", "luck"
|
|
};
|
|
|
|
static boolean challenge_is_repeatable (challenge_structure * challenges, natural index) {
|
|
return ((challenges->type [index] == special_strength + 1) ||
|
|
(challenges->type [index] == special_edurance + 1) ||
|
|
(challenges->type [index] == special_agility + 1));
|
|
}
|
|
|
|
static boolean challenge_is_available (challenge_structure * challenges, natural index) {
|
|
return ((challenges->completed [index] == 0) || (challenge_is_repeatable (challenges, index) == true));
|
|
}
|
|
|
|
static boolean challenge_is_completable (natural * special, challenge_structure * challenges, natural index) {
|
|
if (challenge_is_available (challenges, index) == false) {
|
|
return (false);
|
|
}
|
|
|
|
for (natural check = 0; check < special_count; ++check) {
|
|
if (challenges->requirement [index] [check] > special [check]) {
|
|
return (false);
|
|
}
|
|
}
|
|
|
|
return (true);
|
|
}
|
|
|
|
static procedure render_challenge_list (terminal_structure * terminal, natural * special, challenge_structure * challenges, natural x, natural y) {
|
|
natural count = 0;
|
|
natural * array = allocate (challenges->count * sizeof (* array));
|
|
|
|
for (natural 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 (natural index = 0; index < count; ++index) {
|
|
terminal_render_character (terminal, '[', colour_grey, effect_bold, x, y + index + 2);
|
|
|
|
for (natural 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 procedure render_special_attributes (terminal_structure * terminal, natural * special, natural offset, natural selection) {
|
|
for (natural index = 0; index < special_count; ++index) {
|
|
natural effect = (selection == index) ? effect_bold : effect_normal;
|
|
natural length = attribute_maximum + 3;
|
|
|
|
character 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 procedure prompt_special_attributes (natural * special) {
|
|
terminal_structure * terminal = terminal_initialize ();
|
|
|
|
natural selection = 0;
|
|
boolean show_help = false;
|
|
|
|
character * 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:"
|
|
};
|
|
|
|
character * 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 (natural index = 0; index < special_count; ++index) {
|
|
special [index] = 5;
|
|
}
|
|
|
|
while (terminal->active == true) {
|
|
terminal_render_background (terminal, ' ', colour_white, effect_normal);
|
|
|
|
for (natural 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 (natural index = 0; index < array_length (help_messages); ++index) {
|
|
natural 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 (natural 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 procedure import_user_configuration (natural * special, natural challenge_count, natural * * daily_challenges, boolean * * completition) {
|
|
boolean special_defined [special_count] = { false };
|
|
|
|
boolean daily_challenges_defined = false;
|
|
boolean completition_defined = false;
|
|
|
|
script_structure * information = allocate (sizeof (* information));
|
|
script_data_structure * structure = script_open (configuration_format ("xhallenge.cfg"));
|
|
|
|
for (script_word_enumeration word = script_parser (structure); word != script_end; word = script_parser (structure)) {
|
|
if (word == script_marker) {
|
|
if (script_compare (structure, "challenges") == true) {
|
|
natural 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 (natural index = 0; index < challenges_per_day; ++index) {
|
|
script_failure (structure, (* daily_challenges) [index] >= challenge_count, "Invalid index.");
|
|
}
|
|
} else if (script_compare (structure, "completition") == true) {
|
|
natural 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 (natural 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 procedure export_user_configuration (natural * special, natural * daily_challenges, boolean * completition) {
|
|
character buffer [4096] = "";
|
|
|
|
for (natural index = 0; index < special_count; ++index) {
|
|
string_concatenate (buffer, format ("%s = %i\n", special_name [index], special [index]));
|
|
}
|
|
|
|
string_concatenate (buffer, "challenges = (");
|
|
for (natural 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 (natural 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 (natural limit) {
|
|
challenge_structure * challenges = allocate (sizeof (* challenges));
|
|
|
|
challenges->limit = limit;
|
|
|
|
return (challenges);
|
|
}
|
|
|
|
static challenge_structure * challenges_deinitialize (challenge_structure * challenges) {
|
|
for (natural 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 procedure import_challenges (challenge_structure * challenges) {
|
|
boolean completed_defined = false;
|
|
boolean requirement_defined = false;
|
|
boolean type_defined = false;
|
|
boolean class_defined = false;
|
|
boolean description_defined = false;
|
|
|
|
script_structure * information = allocate (sizeof (* information));
|
|
script_data_structure * structure = script_open (configuration_format ("xhallenge_list.cfg"));
|
|
|
|
for (script_word_enumeration 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) {
|
|
natural 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) {
|
|
natural 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 procedure export_challenges (challenge_structure * challenges) {
|
|
integer file = file_open (configuration_format ("xhallenge_list.cfg"), file_flag_edit | file_flag_truncate);
|
|
|
|
for (natural index = 0; index < challenges->count; ++index) {
|
|
file_echo (file, "[] ");
|
|
|
|
file_echo (file, format ("completed = %i ", challenges->completed [index]));
|
|
|
|
file_echo (file, "requirement = (");
|
|
for (natural 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 natural generate_challenge (natural * special, challenge_structure * challenges) {
|
|
natural index = random_natural (0, challenges->count - 1);
|
|
boolean valid = false;
|
|
|
|
while (valid == false) {
|
|
valid = true;
|
|
index = random_natural (0, challenges->count - 1);
|
|
|
|
if ((challenges->completed [index] > 0) && (challenges->type [index] != 1) && (challenges->type [index] != 3)) continue;
|
|
|
|
for (natural check = 0; check < special_count; ++check) {
|
|
if (challenges->requirement [index] [check] > special [check]) {
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (index);
|
|
}
|
|
|
|
static procedure generate_challenges (natural * special, challenge_structure * challenges, natural * * daily_challenges, boolean * * completition) {
|
|
(* daily_challenges) = allocate (challenges_per_day * sizeof (* * daily_challenges));
|
|
(* completition) = allocate (challenges_per_day * sizeof (* * completition));
|
|
|
|
for (natural index = 0; index < challenges_per_day; ++index) {
|
|
(* daily_challenges) [index] = generate_challenge (special, challenges);
|
|
}
|
|
}
|
|
|
|
static procedure render_challenges (natural * special, challenge_structure * challenges, natural * daily_challenges, boolean * completition) {
|
|
terminal_structure * terminal = terminal_initialize ();
|
|
|
|
natural selection = 0;
|
|
boolean show_help = false;
|
|
boolean show_list = false;
|
|
|
|
character * 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:"
|
|
};
|
|
|
|
character * 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 (natural index = 0; index < array_length (main_messages); ++index) {
|
|
terminal_render_string (terminal, main_messages [index], colour_white, effect_normal, 0, index);
|
|
}
|
|
|
|
for (natural index = 0; index < challenges_per_day; ++index) {
|
|
natural type = challenges->type [daily_challenges [index]];
|
|
character * class = challenges->class [daily_challenges [index]];
|
|
character * description = challenges->description [daily_challenges [index]];
|
|
|
|
natural effect = (selection == index) ? effect_bold : effect_normal;
|
|
|
|
natural offset = array_length (main_messages) + 2;
|
|
|
|
natural 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) {
|
|
natural offset = array_length (main_messages) + challenges_per_day + 3;
|
|
|
|
for (natural 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 (natural index = 0; index < challenges_per_day; ++index) {
|
|
daily_challenges [index] = generate_challenge (special, challenges);
|
|
completition [index] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
terminal = terminal_deinitialize (terminal);
|
|
}
|
|
|
|
integer main (integer argc, character * * argv) {
|
|
natural special [special_count] = { 0 };
|
|
|
|
natural * daily_challenges = null;
|
|
boolean * completition = null;
|
|
|
|
challenge_structure * challenges = challenges_initialize (1024);
|
|
|
|
random_integer_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);
|
|
}
|