diff --git a/lib/gamedata/constants.txt b/lib/gamedata/constants.txt index 61afce970..aad12c482 100644 --- a/lib/gamedata/constants.txt +++ b/lib/gamedata/constants.txt @@ -208,3 +208,111 @@ player:start-gold:600 # Number of turns that 1% of player food capacity feeds them for player:food-value:100 + +#--------------------------------------------------------------------- +# Constants for critical calculations +# In general, those calculations compute a "power" of the critical +# by scaling the chance of a hit. The "power" is then converted to +# a chance of a critical (neglecting the armsman and marksman +# abilities) using (a * power) / (b * power + c) as the chance where +# a, b, and c are constants specified here. For more details, see +# critical_melee() and critical_shot() in player-attack.c. +#--------------------------------------------------------------------- + +# The numerator for the scale factor applied to the combined to-hit value to +# get the power of the critical +melee-critical:power-toh-scale-numerator:1 + +# The denominator for the scale factor applied to the combined to-hit value to +# get the power of the critical +melee-critical:power-toh-scale-denominator:1 + +# The scale factor for the critical's power in the numerator for the chance +# of the critical +melee-critical:chance-power-scale-numerator:1 + +# The scale factor for the critical's power in the denominator for the chance +# of the critical +melee-critical:chance-power-scale-denominator:1 + +# Value of an added term in the denominator for the chance of a critical +melee-critical:chance-add-denominator:240 + +# Players with the armsman ability have a chance, 1 / armsman-chance, +# of inflicting a critical on any melee hit against an obvious opponent. +# If they do not get a critical from that they still can get a critical +# the standard way. armsman-chance must be positive. +melee-critical:armsman-chance:6 + +# Define each of the levels for melee criticals. They are considered in the +# order they appear so it's convenient to put the least likely first. If no +# critical levels are defined, then there's no extra damage due to critical +# hits. +# Except for the last level, one over the first value is the probability that +# this level occurs when none of the prior levels have been selected; the last +# level will always be used if none of the prior levels have been selected. +# The first value must be positive. +# The second value is the number of dice to add for the critical. It must be +# non-negative. +# The third value is the name of the message, from list-messages.h, to use +# when a critical happens for that power level. +melee-critical-level:40:5:HIT_HI_GREAT +melee-critical-level:12:4:HIT_SUPERB +melee-critical-level:3:3:HIT_GREAT +melee-critical-level:1:2:HIT_GOOD + +# Players with the mana burn ability inflict mana-burn-dice additional +# dice of damage when a melee critical causes extra damage against a target +# with non-innate spells. That critical also impairs the target's spell +# casting. mana-burn-dice must be non-negative. +melee-critical:mana-burn-dice:1 + +# The numerator for the scale factor applied to the combined to-hit value to +# get the power of the critical with a launched missile +ranged-critical:power-launched-toh-scale-numerator:1 + +# The denominator for the scale factor applied to the combined to-hit value to +# get the power of the critical whith a launched missile +ranged-critical:power-launched-toh-scale-denominator:1 + +# The numerator for the scale factor applied to the combined to-hit value to +# get the power of the critical with a thrown missile; this and the +# denominator are currently set so thrown missiles get more criticals +ranged-critical:power-thrown-toh-scale-numerator:3 + +# The denominator for the scale factor applied to the combined to-hit value to +# get the power of the critical whith a thrown missile +ranged-critical:power-thrown-toh-scale-denominator:2 + +# The scale factor for the critical's power in the numerator for the chance +# of the critical +ranged-critical:chance-power-scale-numerator:1 + +# The scale factor for the critical's power in the denominator for the chance +# of the critical +ranged-critical:chance-power-scale-denominator:1 + +# Value of an added term in the denominator for the chance of a critical +ranged-critical:chance-add-denominator:360 + +# Players with the marksman ability have a chance, 1 / marksman-chance, +# of inflicting a critical on any ranged hit against a visible opponent. +# If they do not get a critical from that they still can get a critical +# the standard way. marksman-chance must be positive. +ranged-critical:marksman-chance:6 + +# Define each of the levels for ranged criticals. They are considered in the +# order they appear so it's convenient to put the least likely first. If no +# critical levels are defined, then there's no extra damage due to critical +# hits. +# Except for the last level, one over the first value is the probability that +# this level occurs when none of the prior levels have been selected; the last +# level will always be used if none of the prior levels have been selected. +# The first value must be positive. +# The second value is the number of dice to add for the critical. It must be +# non-negative. +# The third value is the name of the message, from list-messages.h, to use +# when a critical happens for that power level. +ranged-critical-level:50:3:HIT_SUPERB +ranged-critical-level:10:2:HIT_GREAT +ranged-critical-level:1:1:HIT_GOOD diff --git a/src/init.c b/src/init.c index 3dd9d7c62..89fabd8a8 100644 --- a/src/init.c +++ b/src/init.c @@ -35,6 +35,7 @@ #include "generate.h" #include "hint.h" #include "init.h" +#include "message.h" #include "mon-init.h" #include "mon-list.h" #include "mon-lore.h" @@ -728,6 +729,139 @@ static enum parser_error parse_constants_player(struct parser *p) { return PARSE_ERROR_NONE; } +static enum parser_error parse_constants_melee_critical(struct parser *p) +{ + struct angband_constants *z = parser_priv(p); + const char *label = parser_getsym(p, "label"); + int value = parser_getint(p, "value"); + + if (streq(label, "power-toh-scale-numerator")) { + z->m_crit_power_toh_scl_num = value; + } else if (streq(label, "power-toh-scale-denominator")) { + z->m_crit_power_toh_scl_den = value; + } else if (streq(label, "chance-power-scale-numerator")) { + z->m_crit_chance_power_scl_num = value; + } else if (streq(label, "chance-power-scale-denominator")) { + z->m_crit_chance_power_scl_den = value; + } else if (streq(label, "chance-add-denominator")) { + z->m_crit_chance_add_den = value; + } else if (streq(label, "armsman-chance")) { + if (value <= 0) { + return PARSE_ERROR_INVALID_VALUE; + } + z->m_armsman_chance = value; + } else if (streq(label, "mana-burn-dice")) { + if (value < 0) { + return PARSE_ERROR_INVALID_VALUE; + } + z->m_manaburn_dice = value; + } else { + return PARSE_ERROR_UNDEFINED_DIRECTIVE; + } + + return PARSE_ERROR_NONE; +} + +static enum parser_error parse_constants_melee_critical_level(struct parser *p) +{ + struct angband_constants *z = parser_priv(p); + struct critical_level *new_level; + unsigned int chance = parser_getuint(p, "chance"); + const char *msgt_str = parser_getstr(p, "msg"); + int msgt = message_lookup_by_name(msgt_str); + + if (chance == 0) { + return PARSE_ERROR_INVALID_VALUE; + } + if (msgt < 0) { + return PARSE_ERROR_INVALID_MESSAGE; + } + new_level = mem_alloc(sizeof(*new_level)); + new_level->next = NULL; + new_level->chance = chance; + new_level->added_dice = parser_getuint(p, "dice"); + new_level->msgt = msgt; + /* Add it to the end of the linked list. */ + if (z->m_crit_level_head) { + struct critical_level *cursor = z->m_crit_level_head; + + while (cursor->next) { + cursor = cursor->next; + } + cursor->next = new_level; + } else { + z->m_crit_level_head = new_level; + } + + return PARSE_ERROR_NONE; +} + +static enum parser_error parse_constants_ranged_critical(struct parser *p) +{ + struct angband_constants *z = parser_priv(p); + const char *label = parser_getsym(p, "label"); + int value = parser_getint(p, "value"); + + if (streq(label, "power-launched-toh-scale-numerator")) { + z->r_crit_power_launched_toh_scl_num = value; + } else if (streq(label, "power-launched-toh-scale-denominator")) { + z->r_crit_power_launched_toh_scl_den = value; + } else if (streq(label, "power-thrown-toh-scale-numerator")) { + z->r_crit_power_thrown_toh_scl_num = value; + } else if (streq(label, "power-thrown-toh-scale-denominator")) { + z->r_crit_power_thrown_toh_scl_den = value; + } else if (streq(label, "chance-power-scale-numerator")) { + z->r_crit_chance_power_scl_num = value; + } else if (streq(label, "chance-power-scale-denominator")) { + z->r_crit_chance_power_scl_den = value; + } else if (streq(label, "chance-add-denominator")) { + z->r_crit_chance_add_den = value; + } else if (streq(label, "marksman-chance")) { + if (value <= 0) { + return PARSE_ERROR_INVALID_VALUE; + } + z->r_marksman_chance = value; + } else { + return PARSE_ERROR_UNDEFINED_DIRECTIVE; + } + + return PARSE_ERROR_NONE; +} + +static enum parser_error parse_constants_ranged_critical_level(struct parser *p) +{ + struct angband_constants *z = parser_priv(p); + struct critical_level *new_level; + unsigned int chance = parser_getuint(p, "chance"); + const char *msgt_str = parser_getstr(p, "msg"); + int msgt = message_lookup_by_name(msgt_str); + + if (chance == 0) { + return PARSE_ERROR_INVALID_VALUE; + } + if (msgt < 0) { + return PARSE_ERROR_INVALID_MESSAGE; + } + new_level = mem_alloc(sizeof(*new_level)); + new_level->next = NULL; + new_level->chance = chance; + new_level->added_dice = parser_getuint(p, "dice"); + new_level->msgt = msgt; + /* Add it to the end of the linked list. */ + if (z->r_crit_level_head) { + struct critical_level *cursor = z->r_crit_level_head; + + while (cursor->next) { + cursor = cursor->next; + } + cursor->next = new_level; + } else { + z->r_crit_level_head = new_level; + } + + return PARSE_ERROR_NONE; +} + static struct parser *init_parse_constants(void) { struct angband_constants *z = mem_zalloc(sizeof *z); struct parser *p = parser_new(); @@ -742,6 +876,14 @@ static struct parser *init_parse_constants(void) { parser_reg(p, "store sym label int value", parse_constants_store); parser_reg(p, "obj-make sym label int value", parse_constants_obj_make); parser_reg(p, "player sym label int value", parse_constants_player); + parser_reg(p, "melee-critical sym label int value", + parse_constants_melee_critical); + parser_reg(p, "melee-critical-level uint chance uint dice str msg", + parse_constants_melee_critical_level); + parser_reg(p, "ranged-critical sym label int value", + parse_constants_ranged_critical); + parser_reg(p, "ranged-critical-level uint chance uint dice str msg", + parse_constants_ranged_critical_level); return p; } @@ -755,8 +897,20 @@ static errr finish_parse_constants(struct parser *p) { return 0; } +static void cleanup_critical_levels(struct critical_level *head) +{ + while (head) { + struct critical_level *target = head; + + head = head->next; + mem_free(target); + } +} + static void cleanup_constants(void) { + cleanup_critical_levels(z_info->m_crit_level_head); + cleanup_critical_levels(z_info->r_crit_level_head); mem_free(z_info); } diff --git a/src/init.h b/src/init.h index a53d9717e..898625c2d 100644 --- a/src/init.h +++ b/src/init.h @@ -16,9 +16,21 @@ #include "z-bitflag.h" #include "z-file.h" #include "z-rand.h" +#include "z-util.h" #include "datafile.h" #include "object.h" +/* Define a level of serverity for a critial hit */ +struct critical_level { + struct critical_level *next; + unsigned int chance; /* one in chance of this level unless + this is the last level; the rest + go to the next level */ + unsigned int added_dice; /* number of dice added for this + level */ + int msgt; /* mesage type to use for this level */ +}; + /** * Information about maximal indices of certain arrays. * @@ -126,6 +138,42 @@ struct angband_constants uint16_t max_range; /* Maximum missile and spell range */ uint16_t start_gold; /* Amount of gold the player starts with */ uint16_t food_value; /* Number of turns 1% of food lasts */ + + /* + * Constants for melee critical calculations; read from + * constants.txt + */ + int m_crit_power_toh_scl_num; + int m_crit_power_toh_scl_den; + int m_crit_chance_power_scl_num; + int m_crit_chance_power_scl_den; + int m_crit_chance_add_den; + int m_armsman_chance; + int m_manaburn_dice; + struct critical_level *m_crit_level_head; + + /* + * For object information, critical levels do not depend on the + * properties of the player or weapon so they can be summed over once + * after loading the constants file and stored here. + */ + struct my_rational m_max_added; + + /* + * Constants for ranged critical calculations; read from + * constants.txt + */ + int r_crit_power_launched_toh_scl_num; + int r_crit_power_launched_toh_scl_den; + int r_crit_power_thrown_toh_scl_num; + int r_crit_power_thrown_toh_scl_den; + int r_crit_chance_power_scl_num; + int r_crit_chance_power_scl_den; + int r_crit_chance_add_den; + int r_marksman_chance; + struct critical_level *r_crit_level_head; + /* See comment for m_max_added above. */ + struct my_rational r_max_added; }; struct init_module { diff --git a/src/obj-info.c b/src/obj-info.c index 6a0dffe56..f563ec76c 100644 --- a/src/obj-info.c +++ b/src/obj-info.c @@ -456,23 +456,130 @@ static bool describe_brands(textblock *tb, const struct object *obj) return true; } +/** + * Sum over the critical levels to get the expected number of + * dice added when a crtical happens. + */ +static struct my_rational sum_criticals(const struct critical_level *head) +{ + struct my_rational remaining_chance = my_rational_construct(1, 1); + struct my_rational added_dice = my_rational_construct(0, 1); + + while (head) { + /* The last level of criticals takes all the remainder. */ + struct my_rational level_added_dice = my_rational_construct( + head->added_dice, (head->next) ? head->chance : 1); + + level_added_dice = my_rational_product(&level_added_dice, + &remaining_chance); + added_dice = my_rational_sum(&added_dice, &level_added_dice); + if (head->next) { + struct my_rational pr_not_this = my_rational_construct( + head->chance - 1, head->chance); + + remaining_chance = my_rational_product( + &remaining_chance, &pr_not_this); + } + head = head->next; + } + + return added_dice; +} + /** * Account for criticals in the calculation of melee prowess for O-combat; * crit chance * average number of dice added * * Return value is 100x number of dice */ -static int calculate_melee_crits(struct player_state state, - const struct object *obj) +static unsigned int calculate_melee_crits(struct player_state *state, + const struct object *obj) { - int dice = 0; - int chance = BTH_PLUS_ADJ * (state.to_h + obj->known->to_h) + - state.skills[SKILL_TO_HIT_MELEE]; - chance = (100 * chance) / (chance + 240); - if (player_has(player, PF_ARMSMAN)) { - chance = 100 - (83 * (100 - chance)) / 100; + unsigned int dice; + + if (z_info->m_crit_level_head) { + /* + * Optimistically assume the target is visible (so + * can use chance_of_melee_hit_base() rather than + * chance_of_melee_hit()) and obvious (so armsman will be + * useful). Pessimistically assume that the target is not + * sleeping and is not subject ot the effects of the mana + * burn ability. Otherwise, these calculations must agree + * with those in player-attack.c's critical_melee(). + */ + struct player_state old_state = player->state; + struct my_rational chance; + int power, chance_std_num, chance_std_den; + unsigned int tr; + + if (z_info->m_max_added.n == 0) { + z_info->m_max_added = + sum_criticals(z_info->m_crit_level_head); + } + + player->state = *state; + power = chance_of_melee_hit_base(player, obj); + power = (power * z_info->m_crit_power_toh_scl_num) + / z_info->m_crit_power_toh_scl_den; + chance_std_num = power * z_info->m_crit_chance_power_scl_num; + chance_std_den = power * z_info->m_crit_chance_power_scl_den + + z_info->m_crit_chance_add_den; + if (chance_std_num > 0 && chance_std_den > 0) { + chance = my_rational_construct(chance_std_num, + chance_std_den); + } else { + chance = my_rational_construct(0, 1); + } + if (player_has(player, PF_ARMSMAN)) { + /* + * Get a critical if the standard calculation succeeds + * or the armsman ability triggers. + */ + if (z_info->m_armsman_chance > 0) { + struct my_rational chance_armsman = + my_rational_construct( + 1, z_info->m_armsman_chance); + struct my_rational chance_not_armsman = + my_rational_construct( + z_info->m_armsman_chance - 1, + z_info->m_armsman_chance); + + chance = my_rational_product(&chance, + &chance_not_armsman); + chance = my_rational_sum(&chance, + &chance_armsman); + } else { + chance = my_rational_construct(1, 1); + } + } + player->state = old_state; + if (chance.n < chance.d) { + /* + * Critical only happens some of the time. + * Scale by chance and 100. Round to the nearest + * integer. + */ + chance = my_rational_product(&chance, + &z_info->m_max_added); + dice = my_rational_to_uint(&chance, 100, &tr); + if (dice < UINT_MAX && tr >= chance.d / 2) { + ++dice; + } + } else { + /* + * Critical always happens. Scale by 100 and + * round to the nearest integer. + */ + dice = my_rational_to_uint( + &z_info->m_max_added, 100, &tr); + if (dice < UINT_MAX && tr >= z_info->m_max_added.d / 2) { + ++dice; + } + } + } else { + /* No critical levels defined so no additional damage. */ + dice = 0; } - dice = (537 * chance) / 240; return dice; } @@ -480,25 +587,101 @@ static int calculate_melee_crits(struct player_state state, /** * Missile crits follow the same approach as melee crits. */ -static int calculate_missile_crits(struct player_state state, - const struct object *obj, - const struct object *launcher) +static unsigned int calculate_missile_crits(struct player_state *state, + const struct object *obj, + const struct object *launcher) { - int dice = 0; - int bonus = state.to_h + obj->known->to_h - + (launcher ? launcher->known->to_h : 0); - int chance = BTH_PLUS_ADJ * bonus; - if (launcher) { - chance += state.skills[SKILL_TO_HIT_BOW]; + unsigned int dice; + + if (z_info->r_crit_level_head) { + /* + * Optimistically assume the target is obvious, that this + * is the first try, and that target's range incurs no + * penalty to hit. Thus, can use chance_of_missile_hit_base() + * rather than chance_of_missile_hit()). Also optimistically + * assume that the target is visible so that armsman will be + * useful. Pessimistically assume that the target is not + * sleeping. Otherwise, these calculations must agree with + * those in player-attack.c's critical_shot(). + */ + struct player_state old_state = player->state; + struct my_rational chance; + int power, chance_std_num, chance_std_den; + unsigned int tr; + + if (z_info->r_max_added.n == 0) { + z_info->r_max_added = + sum_criticals(z_info->r_crit_level_head); + } + + player->state = *state; + power = chance_of_missile_hit_base(player, obj, launcher); + if (launcher) { + power = (power * z_info->r_crit_power_launched_toh_scl_num) + / z_info->r_crit_power_launched_toh_scl_den; + } else { + power = (power * z_info->r_crit_power_thrown_toh_scl_num) + / z_info->r_crit_power_thrown_toh_scl_den; + } + chance_std_num = power * z_info->r_crit_chance_power_scl_num; + chance_std_den = power * z_info->r_crit_chance_power_scl_den + + z_info->r_crit_chance_add_den; + if (chance_std_num > 0 && chance_std_den > 0) { + chance = my_rational_construct(chance_std_num, + chance_std_den); + } else { + chance = my_rational_construct(0, 1); + } + if (player_has(player, PF_MARKSMAN)) { + /* + * Get a critical if the standard calculation succeeds + * or the marksman ability triggers. + */ + if (z_info->r_marksman_chance > 0) { + struct my_rational chance_marksman = + my_rational_construct( + 1, z_info->r_marksman_chance); + struct my_rational chance_not_marksman = + my_rational_construct( + z_info->r_marksman_chance - 1, + z_info->r_marksman_chance); + + chance = my_rational_product(&chance, + &chance_not_marksman); + chance = my_rational_sum(&chance, + &chance_marksman); + } else { + chance = my_rational_construct(1, 1); + } + } + player->state = old_state; + if (chance.n < chance.d) { + /* + * Critical only happens some of the time. + * Scale by chance and 100. Round to the nearest + * integer. + */ + chance = my_rational_product(&chance, + &z_info->r_max_added); + dice = my_rational_to_uint(&chance, 100, &tr); + if (dice < UINT_MAX && tr >= chance.d / 2) { + ++dice; + } + } else { + /* + * Critical always happens. Scale by 100 and + * round to the nearest integer. + */ + dice = my_rational_to_uint( + &z_info->r_max_added, 100, &tr); + if (dice < UINT_MAX && tr >= z_info->m_max_added.d / 2) { + ++dice; + } + } } else { - chance += state.skills[SKILL_TO_HIT_THROW]; - chance *= 3 / 2; - } - chance = (100 * chance) / (chance + 360); - if (player_has(player, PF_MARKSMAN)) { - chance = 100 - (83 * (100 - chance)) / 100; + /* No critical levels defined so no additional damage. */ + dice = 0; } - dice = (569 * chance) / 500; return dice; } @@ -762,15 +945,15 @@ static bool obj_known_damage(const struct object *obj, int *normal_damage, /* Get the number of additional dice from criticals (x100) */ if (weapon) { - dice += calculate_melee_crits(state, obj); + dice += calculate_melee_crits(&state, obj); old_blows = state.num_blows; } else if (ammo) { - dice += calculate_missile_crits(player->state, obj, bow); + dice += calculate_missile_crits(&player->state, obj, bow); } else { if (of_has(obj->known->flags, OF_PERFECT_BALANCE)) { dice *= 2; } - dice += calculate_missile_crits(player->state, obj, NULL); + dice += calculate_missile_crits(&player->state, obj, NULL); dice *= 2 + player->lev / 12; } diff --git a/src/player-attack.c b/src/player-attack.c index e954dce7e..5c934dcca 100644 --- a/src/player-attack.c +++ b/src/player-attack.c @@ -201,7 +201,7 @@ static int chance_of_melee_hit(const struct player *p, * \param missile The missile to launch * \param launcher The launcher to use (optional) */ -static int chance_of_missile_hit_base(const struct player *p, +int chance_of_missile_hit_base(const struct player *p, const struct object *missile, const struct object *launcher) { @@ -438,33 +438,54 @@ static int critical_shot(const struct player *p, const struct monster *mon, int sleeping_bonus, uint32_t *msg_type, bool *marksman, int tries) { - int power = chance_of_missile_hit(p, missile, launcher, mon, tries) - + sleeping_bonus; int add_dice = 0; + bool iscrit = false; - /* Thrown weapons get lots of critical hits. */ - if (!launcher) { - power = power * 3 / 2; + /* + * Marksman ability gives fixed chance for critical against visible + * opponents in addition to the standard critical chance. + */ + if (monster_is_visible(mon) && player_has(p, PF_MARKSMAN)) { + if (*marksman || one_in_(z_info->r_marksman_chance)) { + *marksman = true; + iscrit = true; + } } - /* Marksman Ability - 1/6 critical chance */ - if (monster_is_visible(mon) && player_has(p, PF_MARKSMAN) && one_in_(6)) { - *marksman = true; - } + /* Compute standard critical chance if marksman did not trigger. */ + if (!iscrit) { + int power = chance_of_missile_hit(p, missile, launcher, mon, + tries) + sleeping_bonus; + int chance_num, chance_den; - /* Test for critical hit - chance power / (power + 360) */ - if (randint1(power + 360) <= power || *marksman) { - /* Determine level of critical hit. */ - if (one_in_(50)) { - *msg_type = MSG_HIT_SUPERB; - add_dice = 3; - } else if (one_in_(10)) { - *msg_type = MSG_HIT_GREAT; - add_dice = 2; + /* Apply a rational scale factor. */ + if (launcher) { + power = (power * z_info->r_crit_power_launched_toh_scl_num) + / z_info->r_crit_power_launched_toh_scl_den; } else { - *msg_type = MSG_HIT_GOOD; - add_dice = 1; + power = (power * z_info->r_crit_power_thrown_toh_scl_num) + / z_info->r_crit_power_thrown_toh_scl_den; } + + /* + * Test for critical hit: chance is a * power / + * (b * power + c) + */ + chance_num = power * z_info->r_crit_chance_power_scl_num; + chance_den = power * z_info->r_crit_chance_power_scl_den + + z_info->r_crit_chance_add_den; + iscrit = randint1(chance_den) <= chance_num; + } + + /* Determine level of critical hit. */ + if (iscrit && z_info->r_crit_level_head) { + const struct critical_level *this_l = z_info->r_crit_level_head; + + while (this_l->next && !one_in_(this_l->chance)) { + this_l = this_l->next; + } + add_dice = this_l->added_dice; + *msg_type = this_l->msgt; } else { *msg_type = MSG_SHOOT_HIT; } @@ -481,30 +502,48 @@ static int critical_melee(const struct player *p, struct monster *mon, const struct object *obj, int sleeping_bonus, uint32_t *msg_type, bool *armsman) { - int power = chance_of_melee_hit(p, obj, mon) + sleeping_bonus; int add_dice = 0; + bool iscrit = false; - /* Armsman Ability - 1/6 critical chance */ - if (monster_is_obvious(mon) && player_has(p, PF_ARMSMAN) && one_in_(6)) { - *armsman = true; - } - - /* Test for critical hit - chance power / (power + 240) */ - if (randint1(power + 240) <= power || *armsman) { - /* Determine level of critical hit. */ - if (one_in_(40)) { - *msg_type = MSG_HIT_HI_GREAT; - add_dice = 5; - } else if (one_in_(12)) { - *msg_type = MSG_HIT_SUPERB; - add_dice = 4; - } else if (one_in_(3)) { - *msg_type = MSG_HIT_GREAT; - add_dice = 3; - } else { - *msg_type = MSG_HIT_GOOD; - add_dice = 2; + /* + * Armsman ability gives fixed chance for critical against obvious + * opponents in addition to the standard critical chance. + */ + if (monster_is_obvious(mon) && player_has(p, PF_ARMSMAN)) { + if (*armsman || one_in_(z_info->m_armsman_chance)) { + *armsman = true; + iscrit = true; + } + } + + /* Compute standard critical chance if armsman did not trigger. */ + if (!iscrit) { + int power = chance_of_melee_hit(p, obj, mon) + sleeping_bonus; + int chance_num, chance_den; + + /* Apply a rational scale factor. */ + power = (power * z_info->m_crit_power_toh_scl_num) + / z_info->m_crit_power_toh_scl_den; + + /* + * Test for critical hit: chance is a * power / + * (b * power + c) + */ + chance_num = power * z_info->m_crit_chance_power_scl_num; + chance_den = power * z_info->m_crit_chance_power_scl_den + + z_info->m_crit_chance_add_den; + iscrit = randint1(chance_den) <= chance_num; + } + + /* Determine level of critical hit. */ + if (iscrit && z_info->m_crit_level_head) { + const struct critical_level *this_l = z_info->m_crit_level_head; + + while (this_l->next && !one_in_(this_l->chance)) { + this_l = this_l->next; } + add_dice = this_l->added_dice; + *msg_type = this_l->msgt; } else { *msg_type = MSG_HIT; } @@ -514,7 +553,7 @@ static int critical_melee(const struct player *p, struct monster *mon, monster_has_non_innate_spells(mon)) { /* Impair monsters spellcasting and add to damage */ mflag_on(mon->mflag, MFLAG_LESS_SPELL); - add_dice++; + add_dice += z_info->m_manaburn_dice; msgt(MSG_HIT, "Mana Burn!"); } diff --git a/src/player-attack.h b/src/player-attack.h index f8dc96995..2eda3fc6b 100644 --- a/src/player-attack.h +++ b/src/player-attack.h @@ -70,6 +70,8 @@ extern int num_unarmed_blows; extern int breakage_chance(const struct object *obj, bool hit_target); +int chance_of_missile_hit_base(const struct player *p, + const struct object *missile, const struct object *launcher); int chance_of_melee_hit_base(const struct player *p, const struct object *weapon); extern bool test_hit(int to_hit, int ac); diff --git a/src/tests/parse/z-info.c b/src/tests/parse/z-info.c index f1d6202b1..2bbff9586 100644 --- a/src/tests/parse/z-info.c +++ b/src/tests/parse/z-info.c @@ -14,7 +14,8 @@ int setup_tests(void **state) { int teardown_tests(void *state) { struct angband_constants *z = parser_priv(state); - mem_free(z); + z_info = z; + constants_parser.cleanup(); parser_destroy(state); return 0; } @@ -40,6 +41,12 @@ static int test_negative(void *state) { eq(r, PARSE_ERROR_INVALID_VALUE); r = parser_parse(p, "player:max-sight:-1"); eq(r, PARSE_ERROR_INVALID_VALUE); + r = parser_parse(p, "melee-critical:armsman-chance:-1"); + eq(r, PARSE_ERROR_INVALID_VALUE); + r = parser_parse(p, "melee-critical:mana-burn-dice:-1"); + eq(r, PARSE_ERROR_INVALID_VALUE); + r = parser_parse(p, "ranged-critical:marksman-chance:-1"); + eq(r, PARSE_ERROR_INVALID_VALUE); ok; } @@ -64,6 +71,10 @@ static int test_baddirective(void *state) { eq(r, PARSE_ERROR_UNDEFINED_DIRECTIVE); r = parser_parse(p, "player:xyzzy:300"); eq(r, PARSE_ERROR_UNDEFINED_DIRECTIVE); + r = parser_parse(p, "melee-critical:xyzzy:300"); + eq(r, PARSE_ERROR_UNDEFINED_DIRECTIVE); + r = parser_parse(p, "ranged-critical:xyzzy:300"); + eq(r, PARSE_ERROR_UNDEFINED_DIRECTIVE); ok; } @@ -140,10 +151,140 @@ TEST_CONSTANT(max_range, "max-range", "player") TEST_CONSTANT(start_gold, "start-gold", "player") TEST_CONSTANT(food_value, "food-value", "player") +TEST_CONSTANT(m_crit_power_toh_scl_num, "power-toh-scale-numerator", "melee-critical") +TEST_CONSTANT(m_crit_power_toh_scl_den, "power-toh-scale-denominator", "melee-critical") +TEST_CONSTANT(m_crit_chance_power_scl_num, "chance-power-scale-numerator", "melee-critical") +TEST_CONSTANT(m_crit_chance_power_scl_den, "chance-power-scale-denominator", "melee-critical") +TEST_CONSTANT(m_crit_chance_add_den, "chance-add-denominator", "melee-critical") +TEST_CONSTANT(m_armsman_chance, "armsman-chance", "melee-critical") +TEST_CONSTANT(m_manaburn_dice, "mana-burn-dice", "melee-critical") + +TEST_CONSTANT(r_crit_power_launched_toh_scl_num, "power-launched-toh-scale-numerator", "ranged-critical") +TEST_CONSTANT(r_crit_power_launched_toh_scl_den, "power-launched-toh-scale-denominator", "ranged-critical") +TEST_CONSTANT(r_crit_power_thrown_toh_scl_num, "power-thrown-toh-scale-numerator", "ranged-critical") +TEST_CONSTANT(r_crit_power_thrown_toh_scl_den, "power-thrown-toh-scale-denominator", "ranged-critical") +TEST_CONSTANT(r_crit_chance_power_scl_num, "chance-power-scale-numerator", "ranged-critical") +TEST_CONSTANT(r_crit_chance_power_scl_den, "chance-power-scale-denominator", "ranged-critical") +TEST_CONSTANT(r_crit_chance_add_den, "chance-add-denominator", "ranged-critical") +TEST_CONSTANT(r_marksman_chance, "marksman-chance", "ranged-critical") + +static int test_bad_m_crit_level(void *s) { + struct parser *p = (struct parser*)s; + struct angband_constants *m = parser_priv(p); + enum parser_error r; + + null(m->m_crit_level_head); + /* Check invalid chance. */ + r = parser_parse(p, "melee-critical-level:0:5:HIT_HI_SUPERB"); + eq(r, PARSE_ERROR_INVALID_VALUE); + null(m->m_crit_level_head); + /* Check invalid message. */ + r = parser_parse(p, "melee-critical-level:40:5:XYZZY"); + eq(r, PARSE_ERROR_INVALID_MESSAGE); + null(m->m_crit_level_head); + /* Check invalid chance and invalid message. */ + r = parser_parse(p, "melee-critical-level:0:5:XYZZY"); + noteq(r, PARSE_ERROR_NONE); + null(m->m_crit_level_head); + ok; +} + +static int test_m_crit_level(void *s) { + struct parser *p = (struct parser*)s; + struct angband_constants *m = parser_priv(p); + enum parser_error r = parser_parse(p, + "melee-critical-level:40:5:HIT_HI_SUPERB"); + const struct critical_level *this_l, *last_l; + + eq(r, PARSE_ERROR_NONE); + notnull(m->m_crit_level_head); + for (this_l = m->m_crit_level_head; + this_l->next; + this_l = this_l->next) {} + eq(this_l->chance, 40); + eq(this_l->added_dice, 5); + eq(this_l->msgt, MSG_HIT_HI_SUPERB); + + r = parser_parse(p, "melee-critical-level:12:4:HIT_HI_GREAT"); + eq(r, PARSE_ERROR_NONE); + for (last_l = NULL, this_l = m->m_crit_level_head; + this_l->next; + last_l = this_l, this_l = this_l->next) {} + notnull(last_l); + eq(last_l->chance, 40); + eq(last_l->added_dice, 5); + eq(last_l->msgt, MSG_HIT_HI_SUPERB); + eq(this_l->chance, 12); + eq(this_l->added_dice, 4); + eq(this_l->msgt, MSG_HIT_HI_GREAT); + + ok; +} + +static int test_bad_r_crit_level(void *s) { + struct parser *p = (struct parser*)s; + struct angband_constants *m = parser_priv(p); + enum parser_error r; + + null(m->r_crit_level_head); + /* Check invalid chance. */ + r = parser_parse(p, "ranged-critical-level:0:3:HIT_SUPERB"); + eq(r, PARSE_ERROR_INVALID_VALUE); + null(m->r_crit_level_head); + /* Check invalid message. */ + r = parser_parse(p, "ranged-critical-level:50:3:XYZZY"); + eq(r, PARSE_ERROR_INVALID_MESSAGE); + null(m->r_crit_level_head); + /* Check invalid chance and invalid message. */ + r = parser_parse(p, "ranged-critical-level:0:3:XYZZY"); + noteq(r, PARSE_ERROR_NONE); + null(m->r_crit_level_head); + ok; +} + +static int test_r_crit_level(void *s) { + struct parser *p = (struct parser*)s; + struct angband_constants *m = parser_priv(p); + enum parser_error r = parser_parse(p, + "ranged-critical-level:50:3:HIT_SUPERB"); + const struct critical_level *this_l, *last_l; + + eq(r, PARSE_ERROR_NONE); + notnull(m->r_crit_level_head); + for (this_l = m->r_crit_level_head; + this_l->next; + this_l = this_l->next) {} + eq(this_l->chance, 50); + eq(this_l->added_dice, 3); + eq(this_l->msgt, MSG_HIT_SUPERB); + + r = parser_parse(p, "ranged-critical-level:10:2:HIT_GREAT"); + eq(r, PARSE_ERROR_NONE); + notnull(m->r_crit_level_head); + for (last_l = NULL, this_l = m->r_crit_level_head; + this_l->next; + last_l = this_l, this_l = this_l->next) {} + notnull(last_l); + eq(last_l->chance, 50); + eq(last_l->added_dice, 3); + eq(last_l->msgt, MSG_HIT_SUPERB); + eq(this_l->chance, 10); + eq(this_l->added_dice, 2); + eq(this_l->msgt, MSG_HIT_GREAT); + + ok; +} + +/* + * test_bad_m_crit_level() and test_bad_r_crit_level() have to be before + * test_m_crit_level() and test_r_crit_level(). + */ const char *suite_name = "parse/z-info"; struct test tests[] = { { "negative", test_negative }, { "baddirective", test_baddirective }, + { "bad_m_crit_level", test_bad_m_crit_level }, + { "bad_r_crit_level", test_bad_r_crit_level }, { "monsters_max", test_level_monster_max }, { "mon_chance", test_alloc_monster_chance }, { "monsters_min", test_level_monster_min }, @@ -196,5 +337,22 @@ struct test tests[] = { { "max_range", test_max_range }, { "start_gold", test_start_gold }, { "food_value", test_food_value }, + { "m_crit_power_toh_scl_num", test_m_crit_power_toh_scl_num }, + { "m_crit_power_toh_scl_den", test_m_crit_power_toh_scl_den }, + { "m_crit_chance_power_scl_num", test_m_crit_chance_power_scl_num }, + { "m_crit_chance_power_scl_den", test_m_crit_chance_power_scl_den }, + { "m_crit_chance_add_den", test_m_crit_chance_add_den }, + { "m_armsman_chance", test_m_armsman_chance }, + { "m_manaburn_dice", test_m_manaburn_dice }, + { "m_crit_level", test_m_crit_level }, + { "r_crit_power_launched_toh_scl_num", test_r_crit_power_launched_toh_scl_num }, + { "r_crit_power_launched_toh_scl_den", test_r_crit_power_launched_toh_scl_den }, + { "r_crit_power_thrown_toh_scl_num", test_r_crit_power_thrown_toh_scl_num }, + { "r_crit_power_thrown_toh_scl_den", test_r_crit_power_thrown_toh_scl_den }, + { "r_crit_chance_power_scl_num", test_r_crit_chance_power_scl_num }, + { "r_crit_chance_power_scl_den", test_r_crit_chance_power_scl_den }, + { "r_crit_chance_add_den", test_r_crit_chance_add_den }, + { "r_marksman_chance", test_r_marksman_chance }, + { "r_crit_level", test_r_crit_level }, { NULL, NULL } }; diff --git a/src/tests/z-util/rational.c b/src/tests/z-util/rational.c new file mode 100644 index 000000000..7d4796828 --- /dev/null +++ b/src/tests/z-util/rational.c @@ -0,0 +1,217 @@ +/* z-util/rational.c */ + +#include "unit-test.h" +#include "z-util.h" + +NOSETUP +NOTEARDOWN + +static int test_rational_construct(void *state) { + struct my_rational result = my_rational_construct(0, 1); + + eq(result.n, 0); + eq(result.d, 1); + result = my_rational_construct(1, 1); + eq(result.n, 1); + eq(result.d, 1); + result = my_rational_construct(105, 441); + eq(result.n, 5); + eq(result.d, 21); + ok; +} + +static int test_rational_to_uint(void *state) { + struct my_rational arg; + unsigned int result, remainder; + + arg = my_rational_construct(0, 1); + result = my_rational_to_uint(&arg, 0, NULL); + eq(result, 0); + result = my_rational_to_uint(&arg, 0, &remainder); + eq(result, 0); + eq(remainder, 0); + result = my_rational_to_uint(&arg, 1, NULL); + eq(result, 0); + result = my_rational_to_uint(&arg, 1, &remainder); + eq(result, 0); + eq(remainder, 0); + result = my_rational_to_uint(&arg, 100, NULL); + eq(result, 0); + result = my_rational_to_uint(&arg, 100, &remainder); + eq(result, 0); + eq(remainder, 0); + + arg = my_rational_construct(9, 5); + result = my_rational_to_uint(&arg, 1, NULL); + eq(result, 1); + result = my_rational_to_uint(&arg, 1, &remainder); + eq(result, 1); + eq(remainder, 4); + result = my_rational_to_uint(&arg, 4, NULL); + eq(result, 7); + result = my_rational_to_uint(&arg, 4, &remainder); + eq(result, 7); + eq(remainder, 1); + result = my_rational_to_uint(&arg, 17, NULL); + eq(result, 30); + result = my_rational_to_uint(&arg, 17, &remainder); + eq(result, 30); + eq(remainder, 3); + + arg = my_rational_construct(UINT_MAX - 5, 8); + result = my_rational_to_uint(&arg, 15, NULL); + eq(result, UINT_MAX); + result = my_rational_to_uint(&arg, 15, &remainder); + eq(result, UINT_MAX); + eq(remainder, 0); + arg = my_rational_construct(UINT_MAX - 7, UINT_MAX - 6); + result = my_rational_to_uint(&arg, 24, NULL); + eq(result, 23); + result = my_rational_to_uint(&arg, 24, &remainder); + eq(result, 23); + eq(remainder, UINT_MAX - 30); + arg = my_rational_construct(UINT_MAX, UINT_MAX - 1); + result = my_rational_to_uint(&arg, UINT_MAX, NULL); + eq(result, UINT_MAX); + result = my_rational_to_uint(&arg, UINT_MAX, &remainder); + eq(result, UINT_MAX); + eq(remainder, 0); + arg = my_rational_construct(3, 8); + result = my_rational_to_uint(&arg, UINT_MAX, NULL); + eq(result, (3U * (1U << (CHAR_BIT * sizeof(unsigned int) - 3)) - 1U)); + result = my_rational_to_uint(&arg, UINT_MAX, &remainder); + eq(result, (3U * (1U << (CHAR_BIT * sizeof(unsigned int) - 3)) - 1U)); + eq(remainder, 5); + + ok; +} + +static int test_rational_product(void *state) { + struct my_rational arg1, arg2, result; + + arg1 = my_rational_construct(0, 5); + arg2 = my_rational_construct(1, 1); + result = my_rational_product(&arg1, &arg2); + eq(result.n, 0); + result = my_rational_product(&arg2, &arg1); + eq(result.n, 0); + + arg1 = my_rational_construct(1, 9); + arg2 = my_rational_construct(2, 7); + result = my_rational_product(&arg1, &arg2); + eq(result.n, 2); + eq(result.d, 63); + result = my_rational_product(&arg2, &arg1); + eq(result.n, 2); + eq(result.d, 63); + + arg1 = my_rational_construct(39, 64); + arg2 = my_rational_construct(7, 13); + result = my_rational_product(&arg1, &arg2); + eq(result.n, 21); + eq(result.d, 64); + result = my_rational_product(&arg2, &arg1); + eq(result.n, 21); + eq(result.d, 64); + + arg1 = my_rational_construct(5, 4); + arg2 = my_rational_construct(6, 35); + result = my_rational_product(&arg1, &arg2); + eq(result.n, 3); + eq(result.d, 14); + result = my_rational_product(&arg2, &arg1); + eq(result.n, 3); + eq(result.d, 14); + + arg1 = my_rational_construct(UINT_MAX - 1, UINT_MAX); + arg2 = my_rational_construct(UINT_MAX - 1, UINT_MAX - 2); + result = my_rational_product(&arg1, &arg2); + eq(result.n, 1); + eq(result.d, 1); + result = my_rational_product(&arg2, &arg1); + eq(result.n, 1); + eq(result.d, 1); + + arg1 = my_rational_construct(1, UINT_MAX); + arg2 = my_rational_construct(1, UINT_MAX - 1); + result = my_rational_product(&arg1, &arg2); + eq(result.n, 0); + eq(result.d, 1); + + arg1 = my_rational_construct(UINT_MAX, 3); + arg2 = my_rational_construct(UINT_MAX - 1, 7); + result = my_rational_product(&arg1, &arg2); + eq(result.n, UINT_MAX); + eq(result.d, 1); + + ok; +} + +static int test_rational_sum(void *state) { + struct my_rational arg1, arg2, result; + + arg1 = my_rational_construct(0, 7); + arg2 = my_rational_construct(1, 1); + result = my_rational_sum(&arg1, &arg2); + eq(result.n, 1); + eq(result.d, 1); + result = my_rational_sum(&arg2, &arg1); + eq(result.n, 1); + eq(result.d, 1); + + arg1 = my_rational_construct(9, 17); + arg2 = my_rational_construct(3, 5); + result = my_rational_sum(&arg1, &arg2); + eq(result.n, 96); + eq(result.d, 85); + result = my_rational_sum(&arg2, &arg1); + eq(result.n, 96); + eq(result.d, 85); + + arg1 = my_rational_construct(3, 8); + arg2 = my_rational_construct(5, 4); + result = my_rational_sum(&arg1, &arg2); + eq(result.n, 13); + eq(result.d, 8); + result = my_rational_sum(&arg2, &arg1); + eq(result.n, 13); + eq(result.d, 8); + + arg1 = my_rational_construct(3, 14); + arg2 = my_rational_construct(7, 30); + result = my_rational_sum(&arg1, &arg2); + eq(result.n, 47); + eq(result.d, 105); + result = my_rational_sum(&arg2, &arg1); + eq(result.n, 47); + eq(result.d, 105); + + arg1 = my_rational_construct(UINT_MAX - 1, UINT_MAX); + arg2 = my_rational_construct(UINT_MAX - 2, UINT_MAX); + result = my_rational_sum(&arg1, &arg2); + eq(result.n, UINT_MAX - 2); + eq(result.d, UINT_MAX / 2); + + arg1 = my_rational_construct(1, UINT_MAX); + arg2 = my_rational_construct(1, UINT_MAX - 1); + result = my_rational_sum(&arg1, &arg2); + eq(result.n, 2); + eq(result.d, UINT_MAX); + + arg1 = my_rational_construct(UINT_MAX - 1, 1); + arg2 = my_rational_construct(17, 8); + result = my_rational_sum(&arg1, &arg2); + eq(result.n, UINT_MAX); + eq(result.d, 1); + + ok; +} + +const char *suite_name = "z-util/rational"; +struct test tests[] = { + { "rational_construct", test_rational_construct }, + { "rational_to_uint", test_rational_to_uint }, + { "rational_product", test_rational_product }, + { "rational_sum", test_rational_sum }, + { NULL, NULL } +}; diff --git a/src/tests/z-util/suite.mk b/src/tests/z-util/suite.mk index 2c6e41858..50e5adb81 100644 --- a/src/tests/z-util/suite.mk +++ b/src/tests/z-util/suite.mk @@ -1 +1,3 @@ -TESTPROGS += z-util/util +TESTPROGS += \ + z-util/rational \ + z-util/util diff --git a/src/z-util.c b/src/z-util.c index 4f3c4fa16..132b657b9 100644 --- a/src/z-util.c +++ b/src/z-util.c @@ -961,6 +961,769 @@ int variance(const int *nums, int size) return total / size; } +/** + * Return the greatest common divisor of the two arguments. + */ +unsigned int gcd(unsigned int a, unsigned int b) +{ + /* Use the division-based version of Euclid's algorithm. */ + while (b) { + unsigned int t = b; + + b = a % b; + a = t; + } + return a; +} + +/** + * Initialize a multiprecision integer from an unsigned int. + * + * \param r points to n uint16_t values to store the result. + * \param n is the number of digits in base 2^16 in r. + * \param a is the unsigned value to copy. + * \return true if a is greater than or equal to 2^(16*n) or false if a is + * less than 2^(16*n) (i.e. fits in r without overflow). + * + * Could use the GNU multiprecision library or something similar for this. Do + * this instead to avoid the extra library dependency. + */ +static bool ini_u16n(uint16_t *r, size_t n, unsigned int a) +{ + const unsigned int mask = (1U << 16) - 1; + size_t i = n; + + while (i > 0) { + --i; + r[i] = (uint16_t)(a & mask); + a >>= 16; + } + return a != 0; +} + +/** + * Set all digits of a multiprecision integer to zero. + * \param r points to n uint16_t digits. + * \param n is the number of digits in base 2^16. + * + * The rationale for this is the same as for ini_u16n. + */ +static void zer_u16n(uint16_t *r, size_t n) +{ + size_t i; + + for (i = 0; i < n; ++i) { + r[i] = 0; + } +} + +/** + * Extract the least significant CHAR_BIT * sizeof(unsigned int) bits of a + * multiprecision integer into an unsigned int. + * \param a points to the n uint16_t digits of the value. + * \param n is the number of digits in base 2^16. + * + * The rationale for this is the same as for ini_u16n. + */ +static unsigned int ext_u16n(const uint16_t *a, size_t n) +{ + unsigned int result = 0; + size_t i = n; + size_t rb = sizeof(unsigned int) * CHAR_BIT; + size_t shift = 0; + + while (1) { + if (i == 0 || rb == 0) { + break; + } + --i; + if (rb < 16) { + result += (a[i] & ((1U << rb) - 1)) << shift; + break; + } + result += (unsigned int)a[i] << shift; + rb -= 16; + shift += 16; + } + return result; +} + +/** + * Return the most significant nonzero bit in a multiprecision integer. + * \param a points to the n uint16_t digits of the value + * \param n is the number of digits in base 2^16. + * \return the 1-based index of the most significant nonzero bit (1 is + * the least significant bit) or zero if the value is zero. + */ +static size_t msb_u16n(const uint16_t *a, size_t n) +{ + size_t i = 0; + + while (1) { + if (i == n) { + return 0; + } + if (a[i]) { + unsigned int lo = 0, hi = 16; + + while (lo < hi - 1) { + /* + * Is a bit set in the upper part of the range? + */ + unsigned int half = (hi - lo + 1) / 2; + uint16_t mask = (((uint16_t)1 << half) - 1) + << (hi - half); + + if (a[i] & mask) { + lo = hi - half; + } else { + hi = hi - half; + } + } + return lo + 1 + 16 * ((n - 1) - i); + } + ++i; + } +} + +/** + * Compare the multiprecision integers a and b. + * + * \param a points to na uint16_t digits. + * \param b points to nb uint16_t digits. + * \param na is the number of digits in base 2^16 for a. + * \param nb is the number of digits in base 2^16 for b. + * \return 1 if a is greater than b, 0 if a equals b, and -1 if a is less + * than b. + * + * The rationale for this is the same as for ini_u16n. + */ +static int cmp_u16n(const uint16_t *a, const uint16_t *b, size_t na, size_t nb) +{ + size_t ia = 0, ib = 0; + + if (na >= nb) { + while (ia < na - nb) { + if (a[ia]) { + return 1; + } + ++ia; + } + } else { + while (ib < nb - na) { + if (b[ib]) { + return -1; + } + ++ib; + } + } + while (ia < na) { + assert(ib < nb); + if (a[ia] > b[ib]) { + return 1; + } + if (a[ia] < b[ib]) { + return -1; + } + ++ia; + ++ib; + } + return 0; +} + +/** + * Add b to a returning the result in a. + * + * \param a points to na uint16_t digits. + * \param b points to nb uint16_t digits. b can overlap with a if b's digits + * are from the end of a and end with the least significant digit of a. + * nb. Otherwise, a and b must not overlap. + * \param na is the number of digits in base 2^16 for a. + * \param nb is the number of digits in base 2^16 for b must be less than or + * equal to na. + * \return the amount of overflow in the result. + * + * The rationale for this is the same as for ini_u16n. + */ +static uint16_t addip_u16n(uint16_t *a, const uint16_t *b, size_t na, size_t nb) +{ + const uint32_t mask = ((uint32_t)1 << 16) - 1; + uint16_t carry = 0; + size_t ia = na, ib = nb; + + while (ib > 0) { + uint32_t t; + + assert(ia); + --ia; + --ib; + t = (uint32_t)a[ia] + (uint32_t)b[ib] + (uint32_t)carry; + a[ia] = t & mask; + carry = (uint16_t)(t >> 16); + } + while (ia > 0 && carry) { + uint32_t t; + + --ia; + t = (uint32_t)a[ia] + (uint32_t)carry; + a[ia] = t & mask; + carry = (uint16_t)(t >> 16); + } + return carry; +} + +/** + * Subtract b from a returning the result in a. + * + * \param a points to na uint16_t digits. + * \param b points to nb uint16_t digits. cmp_u16n(a, b) must be greater than + * or equal to zero. b can be the same as a if na equals nb. Otherwise, a and + * b must not overlap. + * \param na is the number of digits in base 2^16 for a. + * \param nb is the number of digits in base 2^16 for b. + * + * The rationale for this is the same as for ini_u16n. + */ +static void subip_u16n(uint16_t *a, const uint16_t *b, size_t na, size_t nb) +{ + bool carry = false; + size_t ia = na, ib = nb; + + while (ib > 0 && ia > 0) { + --ia; + --ib; + if (carry) { + if (a[ia] > b[ib]) { + a[ia] -= b[ib] + 1; + carry = false; + } else { + a[ia] += (uint16_t)65535 - b[ib]; + carry = true; + } + } else if (a[ia] >= b[ib]) { + a[ia] -= b[ib]; + } else { + a[ia] += (uint16_t)65535 - (b[ib] - 1); + carry = true; + } + } +#ifdef NDEBUG + while (ib > 0) { + --ib; + assert(!b[ib]); + } +#endif + while (ia > 0 && carry) { + --ia; + if (a[ia]) { + a[ia] -= 1; + carry = false; + } else { + a[ia] = 65535; + } + } + assert(!carry); +} + + +/** + * Multiply two multiprecision integers. Both have the most significant digit + * first. + * \param r points to nr uint16_t digits to store the result. Can not overlap + * with a or b. + * \param nr is the number of digits for r in base 2^16. + * \param a points to the na uint16_t digits for one of the values to multiply. + * \param b points to the nb uint16_t digits for one of the values to multiply. + * \param na is the number of digits for a in base 2^16. + * \param nb is the number of digits for b in base 2^16. + * \return true if the result is larger than can be stored in r or false if + * the result fits in r without overflow. + * + * The rationale for this is the same as for ini_u16n. + */ +static bool mul_u16n(uint16_t *r, const uint16_t *a, const uint16_t *b, + size_t nr, size_t na, size_t nb) +{ + const uint32_t mask = ((uint32_t)1 << 16) - 1; + size_t ia = na; + bool over = false; + + zer_u16n(r, nr); + while (ia > 0) { + size_t ib = nb; + + --ia; + while (ib > 0) { + size_t rir = (na - 1 - ia) + (nb - ib), ir; + uint32_t p, carry, t; + + if (rir >= nr) { + over = true; + break; + } + --ib; + ir = (nr - rir) - 1; + p = (uint32_t)a[ia] * (uint32_t)b[ib]; + carry = p >> 16; + t = (uint32_t)r[ir] + (p & mask); + r[ir] = t & mask; + carry += t >> 16; + while (carry > 0) { + if (ir == 0) { + over = true; + break; + } + --ir; + t = (uint32_t)r[ir] + carry; + r[ir] = t & mask; + carry = t >> 16; + } + } + } + return over; +} + +/** + * Divide two multiprecision integer. Both have the most significant digit + * first. + * \param q points to nq uint16_t digits to store the quotient. Can not + * overlap with r, w, or den. + * \param r points to n uint16_t digits to store the remainder. Can not + * overlap with q, r, w, num, or den. + * \param w points nd + 1 uint16_t digits of working space. Can not overlap + * with q, r, num, or den. + * \param num points to the n uint16_t digits of the integer to be divided. + * \param den points to the nd uint16_t digits of the divisor. + * \param nq is the number of base 2^16 digits in q. + * \param n is the number of base 2^16 digits in r and num. + * \param nd is the number of base 2^16 digits in den. + * \return zero if the divisor is greater than zero and the quotient could + * be stored without overflow, one if the divisor is greater than zero and + * only the least significant bits of the quotient could be stored, or two + * if the divisor is zero. If the divisor is zero, the contents or q and r + * are not modified. + * + * The rationale for this is the same as for ini_u16n. + */ +static int div_u16n(uint16_t *q, uint16_t *r, uint16_t *w, const uint16_t *num, + const uint16_t *den, size_t nq, size_t n, size_t nd) +{ + size_t msb_d = msb_u16n(den, nd), msb_r; + size_t nqu, ir, iqr, iqrb; + + if (msb_d == 0) { + return 2; + } + msb_r = msb_u16n(num, n); + if (msb_r > msb_d) { + nqu = ((msb_r - msb_d) + 15) / 16; + } else { + nqu = 1; + } + + for (ir = 0; ir < n; ++ir) { + r[ir] = num[ir]; + } + + for (iqr = MAX(nq, nqu), iqrb = MAX(nq, nqu) * 16; iqr > 0; + --iqr, iqrb -= 16) { + uint16_t lo; + uint32_t hi; + bool redo_w; + + if (msb_d + iqrb - 16 > msb_r) { + if (iqr <= nq) { + q[nq - iqr] = 0; + } + continue; + } + /* Use binary search to determine the digit in the quotient. */ + lo = 0; + hi = (uint32_t)1 << MIN(16, (msb_r - msb_d + 1 - (iqrb - 16))); + redo_w = true; + assert (lo < hi - 1); + while (1) { + uint16_t try = (uint16_t)((lo + hi) / 2); + bool over = mul_u16n(w, den, &try, nd + 1, nd, 1); + int c; + + if (over) { + assert(0); + } + c = cmp_u16n(r, w, n - (iqr - 1), nd + 1); + if (c < 0) { + hi = try; + redo_w = true; + } else { + lo = try; + redo_w = false; + } + if (!c || lo == hi - 1) { + if (redo_w) { + over = mul_u16n(w, den, &lo, nd + 1, + nd, 1); + assert(!over); + assert(cmp_u16n(r, w, n - (iqr - 1), + nd + 1) >= 0); + } + if (iqr <= nq) { + q[nq - iqr] = lo; + } + subip_u16n(r, w, n - (iqr - 1), nd + 1); + msb_r = msb_u16n(r, n); + break; + } + } + } + return (nqu > nq) ? 1 : 0; +} + +/** + * Construct a rational value. + */ +struct my_rational my_rational_construct(unsigned int numerator, + unsigned int denominator) +{ + struct my_rational result; + + assert(denominator > 0); + if (numerator == 0) { + /* Use 0 / 1 as the way to represent zero. */ + result.n = 0; + result.d = 1; + } else { + unsigned int g = gcd(numerator, denominator); + + result.n = numerator / g; + result.d = denominator / g; + } + return result; +} + +/** + * Scale a rational value and return the result. + * + * \param a points to the rational value to scale. + * \param scale is the scale factor to apply. + * \param remainder will, if not NULL, be dereferenced and set to numerator + * of the fraction (denominator is a->d) that is not included in the return + * value so the caller can use it for rounding or other purposes. + */ +unsigned int my_rational_to_uint(const struct my_rational *a, + unsigned int scale, unsigned int *remainder) +{ + unsigned int result, r, q, r2, t; + + if (!scale) { + if (remainder) { + *remainder = 0; + } + return 0; + } + result = a->n / a->d; + if (result > UINT_MAX / scale) { + if (remainder) { + *remainder = 0; + } + return UINT_MAX; + } + result *= scale; + r = a->n % a->d; + q = scale / a->d; + if (result > UINT_MAX - q * r) { + if (remainder) { + *remainder = 0; + } + return UINT_MAX; + } + result += q * r; + r2 = scale - q * a->d; + if (r && r2 > UINT_MAX / r) { + /* + * The product of the remainders overflows in the native + * arithmetic so use multiprecision integers. + */ +#define MP_DIGITS ((sizeof(unsigned int) + (sizeof(uint16_t) - 1)) / sizeof(uint16_t)) + uint16_t r_u16n[MP_DIGITS], r2_u16n[MP_DIGITS]; + uint16_t d_u16n[MP_DIGITS], p_u16n[2 * MP_DIGITS]; + uint16_t qr_u16n[MP_DIGITS], rr_u16n[2 * MP_DIGITS]; + uint16_t w_u16n[MP_DIGITS + 1]; + bool over; + + over = ini_u16n(r_u16n, MP_DIGITS, r); + if (over) { + assert(0); + } + over = ini_u16n(r2_u16n, MP_DIGITS, r2); + if (over) { + assert(0); + } + over = ini_u16n(d_u16n, MP_DIGITS, a->d); + if (over) { + assert(0); + } + over = mul_u16n(p_u16n, r_u16n, r2_u16n, 2 * MP_DIGITS, + MP_DIGITS, MP_DIGITS); + if (over) { + assert(0); + } + over = (div_u16n(qr_u16n, rr_u16n, w_u16n, p_u16n, d_u16n, + MP_DIGITS, 2 * MP_DIGITS, MP_DIGITS) != 0); + if (over) { + assert(0); + } + assert(msb_u16n(qr_u16n, MP_DIGITS) + <= sizeof(unsigned int) * CHAR_BIT); + q = ext_u16n(qr_u16n, MP_DIGITS); + if (result <= UINT_MAX - q) { + result += q; + if (remainder) { + assert(msb_u16n(rr_u16n, 2 * MP_DIGITS) + <= sizeof(unsigned int) * CHAR_BIT); + *remainder = ext_u16n(rr_u16n, 2 * MP_DIGITS); + assert(*remainder < a->d); + } + } else { + result = UINT_MAX; + if (remainder) { + *remainder = 0; + } + } + return result; +#undef MP_DIGITS + } + t = r * r2; + q = t / a->d; + if (result > UINT_MAX - q) { + if (remainder) { + *remainder = 0; + } + return UINT_MAX; + } + result += q; + if (remainder) { + *remainder = t - q * a->d; + } + return result; +} + +/** + * Multiply two rational values and return the result. + */ +struct my_rational my_rational_product(const struct my_rational *a, + const struct my_rational *b) +{ + unsigned int g1 = gcd(a->n, b->d); + unsigned int g2 = gcd(a->d, b->n); + unsigned int anr = a->n / g1; + unsigned int adr = a->d / g2; + unsigned int bnr = b->n / g2; + unsigned int bdr = b->d / g1; + struct my_rational result; + + if ((bnr && anr > UINT_MAX / bnr) || (adr > UINT_MAX / bdr)) { + /* Overflows in native arithmetic so approximate. */ +#define MP_DIGITS ((sizeof(unsigned int) + (sizeof(uint16_t) - 1)) / sizeof(uint16_t)) + uint16_t a_u16n[MP_DIGITS], b_u16n[MP_DIGITS]; + uint16_t n_u16n[2 * MP_DIGITS], d_u16n[2 * MP_DIGITS]; + uint16_t q_u16n[MP_DIGITS], r_u16n[3 * MP_DIGITS]; + uint16_t sr_u16n[3 * MP_DIGITS], w_u16n[2 * MP_DIGITS + 1]; + unsigned int rn, rd; + bool over; + + over = ini_u16n(a_u16n, MP_DIGITS, anr); + if (over) { + assert(0); + } + over = ini_u16n(b_u16n, MP_DIGITS, bnr); + if (over) { + assert(0); + } + over = mul_u16n(n_u16n, a_u16n, b_u16n, 2 * MP_DIGITS, + MP_DIGITS, MP_DIGITS); + if (over) { + assert(0); + } + over = ini_u16n(a_u16n, MP_DIGITS, adr); + if (over) { + assert(0); + } + over = ini_u16n(b_u16n, MP_DIGITS, bdr); + if (over) { + assert(0); + } + over = mul_u16n(d_u16n, a_u16n, b_u16n, 2 * MP_DIGITS, + MP_DIGITS, MP_DIGITS); + if (over) { + assert(0); + } + over = (div_u16n(q_u16n, r_u16n, w_u16n, n_u16n, d_u16n, + MP_DIGITS, 2 * MP_DIGITS, 2 * MP_DIGITS) != 0); + if (!over && msb_u16n(q_u16n, MP_DIGITS) + <= sizeof(unsigned int) * CHAR_BIT) { + unsigned int t; + + rn = ext_u16n(q_u16n, MP_DIGITS); + /* + * Use as large as possible of a denominator so the + * approximation is as accurate as possible. + */ + rd = (rn < UINT_MAX) ? UINT_MAX / (rn + 1) : 1; + rn *= rd; + over = ini_u16n(a_u16n, MP_DIGITS, rd); + if (over) { + assert(0); + } + over = mul_u16n(sr_u16n, r_u16n, a_u16n, 3 * MP_DIGITS, + 2 * MP_DIGITS, MP_DIGITS); + if (over) { + assert(0); + } + over = div_u16n(q_u16n, r_u16n, w_u16n, sr_u16n, d_u16n, + MP_DIGITS, 3 * MP_DIGITS, 2 * MP_DIGITS); + if (over) { + assert(0); + } + assert(msb_u16n(q_u16n, MP_DIGITS) + <= sizeof(unsigned int) * CHAR_BIT); + t = ext_u16n(q_u16n, MP_DIGITS); + assert(rn <= UINT_MAX - t); + rn += t; + /* Approximate rounding to the nearest. */ + if (msb_u16n(r_u16n, 3 * MP_DIGITS) + 1 + >= msb_u16n(d_u16n, 2 * MP_DIGITS)) { + assert(rn < UINT_MAX); + ++rn; + } + } else { + rn = UINT_MAX; + rd = 1; + } + return my_rational_construct(rn, rd); +#undef MP_DIGITS + } + result.n = anr * bnr; + result.d = adr * bdr; + return result; +} + +/** + * Add two rational values and return the result. + */ +struct my_rational my_rational_sum(const struct my_rational *a, + const struct my_rational *b) +{ + unsigned int g = gcd(a->d, b->d); + unsigned int adr = a->d / g, bdr = b->d / g; + unsigned int resn, resd; + + if (adr <= UINT_MAX / b->d + && a->n <= UINT_MAX / bdr + && b->n <= UINT_MAX / adr + && a->n * bdr <= UINT_MAX - b->n * adr) { + resn = a->n * bdr + b->n * adr; + resd = adr * b->d; + } else { + /* Overflows in native arithmetic so approximate. */ +#define MP_DIGITS ((sizeof(unsigned int) + (sizeof(uint16_t) - 1)) / sizeof(uint16_t)) + uint16_t a_u16n[MP_DIGITS], b_u16n[MP_DIGITS]; + uint16_t n_u16n[2 * MP_DIGITS + 1], d_u16n[2 * MP_DIGITS]; + uint16_t q_u16n[MP_DIGITS], r_u16n[2 * MP_DIGITS + 1]; + uint16_t sr_u16n[3 * MP_DIGITS + 1], rr_u16n[3 * MP_DIGITS + 1]; + uint16_t w_u16n[2 * MP_DIGITS + 1]; + bool over; + + over = ini_u16n(a_u16n, MP_DIGITS, a->n); + if (over) { + assert(0); + } + over = ini_u16n(b_u16n, MP_DIGITS, bdr); + if (over) { + assert(0); + } + over = mul_u16n(sr_u16n, a_u16n, b_u16n, 2 * MP_DIGITS, + MP_DIGITS, MP_DIGITS); + if (over) { + assert(0); + } + over = ini_u16n(a_u16n, MP_DIGITS, adr); + if (over) { + assert(0); + } + over = ini_u16n(b_u16n, MP_DIGITS, b->n); + if (over) { + assert(0); + } + over = mul_u16n(n_u16n + 1, a_u16n, b_u16n, 2 * MP_DIGITS, + MP_DIGITS, MP_DIGITS); + if (over) { + assert(0); + } + n_u16n[0] = addip_u16n(n_u16n + 1, sr_u16n, 2 * MP_DIGITS, + 2 * MP_DIGITS); + over = ini_u16n(b_u16n, MP_DIGITS, b->d); + if (over) { + assert(0); + } + over = mul_u16n(d_u16n, a_u16n, b_u16n, 2 * MP_DIGITS, + MP_DIGITS, MP_DIGITS); + if (over) { + assert(0); + } + over = (div_u16n(q_u16n, r_u16n, w_u16n, n_u16n, d_u16n, + MP_DIGITS, 2 * MP_DIGITS + 1, 2 * MP_DIGITS) != 0); + if (!over && msb_u16n(q_u16n, MP_DIGITS) + <= sizeof(unsigned int) * CHAR_BIT) { + unsigned int t; + + resn = ext_u16n(q_u16n, MP_DIGITS); + /* + * Use as large as a numerator as possible so the + * approximation is as accurate as possible. + */ + resd = (resn < UINT_MAX) ? UINT_MAX / (resn + 1) : 1; + resn *= resd; + over = ini_u16n(a_u16n, MP_DIGITS, resd); + if (over) { + assert(0); + } + over = mul_u16n(sr_u16n, r_u16n, a_u16n, + 3 * MP_DIGITS + 1, 2 * MP_DIGITS + 1, + MP_DIGITS); + if (over) { + assert(0); + } + over = (div_u16n(q_u16n, rr_u16n, w_u16n, sr_u16n, + d_u16n, MP_DIGITS, 3 * MP_DIGITS + 1, + 2 * MP_DIGITS) != 0); + if (over) { + assert(0); + } + assert(msb_u16n(q_u16n, MP_DIGITS) + <= sizeof(unsigned int) * CHAR_BIT); + t = ext_u16n(q_u16n, MP_DIGITS); + if (resn <= UINT_MAX - t) { + resn += t; + /* Approximate rounding to the nearest. */ + if (msb_u16n(rr_u16n, 3 * MP_DIGITS + 1) + 1 + >= msb_u16n(d_u16n, + 2 * MP_DIGITS) + && resn < UINT_MAX) { + ++resn; + } + } else { + assert(resd == 1); + resn = UINT_MAX; + } + } else { + resn = UINT_MAX; + resd = 1; + } +#undef MP_DIGITS + } + return my_rational_construct(resn, resd); +} + void sort(void *base, size_t nmemb, size_t smemb, int (*comp)(const void *, const void *)) { diff --git a/src/z-util.h b/src/z-util.h index 5ded3d504..fda961083 100644 --- a/src/z-util.h +++ b/src/z-util.h @@ -22,6 +22,9 @@ #include "h-basic.h" +struct my_rational { unsigned int n, d; }; /* numerator and denominator */ + + /** * ------------------------------------------------------------------------ * Available variables @@ -239,5 +242,14 @@ uint32_t djb2_hash(const char *str); */ int mean(const int *nums, int size); int variance(const int *nums, int size); +unsigned int gcd(unsigned int a, unsigned int b); +struct my_rational my_rational_construct(unsigned int numerator, + unsigned int denominator); +unsigned int my_rational_to_uint(const struct my_rational *a, + unsigned int scale, unsigned int *remainder); +struct my_rational my_rational_product(const struct my_rational *a, + const struct my_rational *b); +struct my_rational my_rational_sum(const struct my_rational *a, + const struct my_rational *b); #endif /* INCLUDED_Z_UTIL_H */