From cad9f95b42eea7845ca4741a0d0333d2b79be984 Mon Sep 17 00:00:00 2001 From: Ihnatus Date: Sat, 18 Mar 2023 22:57:25 +0300 Subject: [PATCH] Implement a function to calculate limits of uncertain effect value Mainly intended for AI to be used in unit production planning. See OSDN#47598. Signed-off-by: Ihnatus --- common/effects.c | 408 +++++++++++++++++++++++++++++++++++++++++++ common/effects.h | 13 +- common/multipliers.c | 3 +- common/multipliers.h | 12 +- common/player.c | 5 +- 5 files changed, 434 insertions(+), 7 deletions(-) diff --git a/common/effects.c b/common/effects.c index 502a3a5f91..2efd43a6a2 100644 --- a/common/effects.c +++ b/common/effects.c @@ -136,6 +136,20 @@ static struct { } reqs; } ruleset_cache; +/* A structure to cache effect calculation result */ +struct efv { + int *reqns; /* pointer on a position in an array of numbers of uncertain reqs*/ + int reqnn; /* number of uncertain requirements */ +}; + +static int shake_efvs_rec(struct efv *efvs, int *values, int n_efvs, + struct requirement *reqs, enum fc_tristate *rs, + int n_rs, bool tomax); +static void shake_efvs(int *values, struct requirement_vector *maybes, + int n_efvs, int total_reqs, int *miv, int *mav); +static enum fc_tristate efv_status(const struct efv *pefv, + const enum fc_tristate *rs); + /**********************************************************************//** Get a list of effects of this type. @@ -783,6 +797,400 @@ int get_target_bonus_effects(struct effect_list *plist, return bonus; } +/**********************************************************************//** + Consider pefv active, set statuses in rs[n_rs] accordingly. + It assumes that are_reqirements_contradictions() sees transitivity. +**************************************************************************/ +static inline void + efv_reqs_set(const struct efv *pefv, const struct requirement *reqs, + enum fc_tristate *rs, int n_rs) +{ + int reqnn = pefv->reqnn, *reqns = pefv->reqns; + + for (int i = 0; i < n_rs; i++) { + for (int j = 0; j < reqnn; j++) { + if (TRI_MAYBE == rs[i]) { + if (are_requirements_contradictions(&reqs[reqns[j]], &reqs[i])) { + rs[i] = TRI_NO; + } else if (req_implies_req(&reqs[reqns[j]], &reqs[i])) { + /* This sets also rs[reqns[j]] itself */ + rs[i] = TRI_YES; + } + } + } + } +} + +/**********************************************************************//** + The recursive (end of efvs[n_efvs] inner) part of shake_efvs() algorithm. + Returns the extremal value (maximal if tomax, minimal if !tomax) + with requirement statuses in rs[n_rs] set accordingly. + It's presumed that the effect for *efvs is uncertain at the start. + Worst performance expected if the effects are mutually incompatible + and have value signs opposite to the desired direction. + For big systems, precalculating requirement compatibility matrix may + accelerate the process, but yet no reasons are seen for it. +**************************************************************************/ +static int shake_efvs_rec(struct efv *efvs, int *values, int n_efvs, + struct requirement *reqs, enum fc_tristate *rs, + int n_rs, bool tomax) +{ + enum fc_tristate cache[n_rs]; + int myres = 0; /* Own value (if needed) + following certain effects */ + int en = 1; + enum fc_tristate mystatus; + int value = *values; + int free_ext = 0; /* What we may squeeze out of uncertain array tail */ + bool rightside = tomax ? value > 0 : value < 0; + + fc_assert_ret_val(n_efvs > 0 && n_rs > 0, 0); + /* Remember the status of the requirements before we do something. + * If we are not lucky, we'll start again from it */ + memcpy(cache, rs, sizeof(*rs) * n_rs); + /* Skip effects with already known statuses */ + while (en < n_efvs) { + enum fc_tristate status = efv_status(&efvs[en], rs); + + if (TRI_MAYBE == status) { + /* Recurse from here */ + break; + } else if (TRI_YES == status) { + myres += values[en]; + } + en++; + } /* while */ + if (en < n_efvs) { + /* Recurse from this effect */ + free_ext = shake_efvs_rec(&efvs[en], &values[en], n_efvs - en, reqs, rs, n_rs, tomax); + /* Got extremal value from following effects. + * Look if we can, or have to, add the value of this effect to it */ + mystatus = efv_status(efvs, rs); + } else { +#ifdef FC_NDEBUG + mystatus = TRI_MAYBE; +#else + mystatus = efv_status(efvs, rs); + fc_assert(TRI_MAYBE == mystatus); +#endif + } + if (en >= n_efvs || (rightside ? TRI_NO : TRI_YES) != mystatus) { + /* We are lucky: we don't need to recurse in again (this time) */ + if (rightside) { + myres += value; + if (TRI_MAYBE == mystatus) { + /* Set the requirements to turn the effect on + * for the outer recursion */ + efv_reqs_set(efvs, reqs, rs, n_rs); + } + } else if (TRI_MAYBE == mystatus) { + /* Switch one requirement off. If we switch off a wrong one, + * we'll come back here from outer recursion. + * FIXME: a space for optimization seems to be here */ + const int *reqns = efvs->reqns, reqnn = efvs->reqnn; + int i = 0; + + for (; i < reqnn; i++) { + if (TRI_MAYBE == rs[reqns[i]]) { + rs[reqns[i]] = TRI_NO; + break; + } + } + fc_assert(i < reqnn); + } + return myres + free_ext; + } else { + /* So, either our effect is contra the goal and spoils the result, + * or it's towards the goal and can not help it. Consider it. + * First, cache again what we have calculated for now + * and restore the outer values */ + if (rightside) { + int withval = value, wen = en; + + /* Operate on the cache */ + efv_reqs_set(efvs, reqs, cache, n_rs); + /* Skip effects that are determined then */ + while (wen < n_efvs) { + enum fc_tristate status = efv_status(&efvs[wen], cache); + + if (TRI_MAYBE == status) { + /* Recurse from here */ + break; + } else if (TRI_YES == status) { + withval += values[wen]; + } + wen++; + } + if (wen < n_efvs) { + withval += shake_efvs_rec(&efvs[wen], &values[wen], n_efvs - wen, reqs, cache, + n_rs, tomax); + } + if (withval > free_ext) { + memcpy(rs, cache, sizeof(*rs) * n_rs); + return myres + withval; + } else { + /* The previous one was better */ + return myres + free_ext; + } + } else { + /* Consider AT LEAST ONE of *efvs reqs is not fulfilled */ + enum fc_tristate cache2[n_rs]; + const int reqnn = efvs->reqnn, *reqns = efvs->reqns; + int myreqns[reqnn]; + int nrn = 0, bestval = free_ext; + + if (reqnn > 1) { + memcpy(cache2, cache, sizeof(*cache) * n_rs); + memcpy(myreqns, reqns, sizeof(*reqns) * reqnn); + } + do { + struct requirement req = reqs[myreqns[nrn]]; + struct requirement nreq = req; + int withoutval = 0, woen = en; + + nreq.present = !nreq.present; + for (int i = 0; i < n_rs; i++) { + if (TRI_MAYBE == cache[i]) { + if (are_requirements_contradictions(&nreq, &reqs[i])) { + /* This sets cache[reqns[nrn]] itself */ + cache[i] = TRI_NO; + } else if (are_requirements_contradictions(&req, &reqs[i])) { + cache[i] = TRI_YES; + } + } + } + /* Skip effects that are determined then */ + while (woen < n_efvs) { + enum fc_tristate status = efv_status(&efvs[woen], cache); + + if (TRI_MAYBE == status) { + /* Recurse from here */ + break; + } else if (TRI_YES == status) { + withoutval += values[woen]; + } + woen++; + } + if (woen < n_efvs) { + withoutval += shake_efvs_rec(&efvs[en], &values[en], n_efvs - en, reqs, cache, + n_rs, tomax) + value; + } + if (withoutval > bestval) { + /* Found a better solution */ + memcpy(rs, cache, sizeof(*rs) * n_rs); + bestval = withoutval; + } + + { + bool found_nrn = FALSE; + + /* We don't need to iterate over already got TRI_NO's */ + for (int i = nrn + 1; i < reqnn; i++) { + if (myreqns[i] >= 0) { + if (TRI_NO == cache[myreqns[i]]) { + myreqns[i] = -1; + } else if (!found_nrn) { + nrn = i; + found_nrn = TRUE; + } + } + } + if (!found_nrn) { + break; + } + memcpy(cache, cache2, sizeof(*cache) * n_rs); + } + } while (TRUE); + + return myres + bestval; + } + } +} + +/**********************************************************************//** + Calculate if with given considerations the effect is still uncertain, + or fulfilled or not +**************************************************************************/ +static enum fc_tristate efv_status(const struct efv *pefv, + const enum fc_tristate *rs) +{ + enum fc_tristate status = TRI_YES; + const int reqnn = pefv->reqnn, *reqns = pefv->reqns; + + for (int rnn = 0; rnn < reqnn; rnn++) { + enum fc_tristate reqst = rs[reqns[rnn]]; + + switch (reqst) { + case TRI_NO: + case TRI_MAYBE: + status = reqst; + break; + case TRI_YES: + /* OK */ + break; + default: + status = TRI_NO; + fc_assert(FALSE); + } + if (TRI_NO == status) { + break; + } + } + return status; +} + +/**********************************************************************//** + Take the efvs[n_efvs] array (n_efvs > 0) and add to miv and mav + the minimal and maximal possible sum of the effects. + Cap number of requirements total_reqs shall be calculated before run. +**************************************************************************/ +static void shake_efvs(int *values, struct requirement_vector *maybes, + int n_efvs, int total_reqs, int *miv, int *mav) +{ + struct requirement reqs[total_reqs]; + enum fc_tristate rs[total_reqs]; + int rn[total_reqs]; + struct efv efvs[n_efvs]; + int n_rs = 0, n_rn = 0; + + /* Build an array of unique requirements and cache their numbers */ + for (int i = 0; i < n_efvs; i++) { + efvs[i].reqns = &rn[n_rn]; + efvs[i].reqnn = requirement_vector_size(&maybes[i]); + requirement_vector_iterate (&maybes[i], mbr) { + int rnn = -1; + + for (int j = 0; j < n_rs; j++) { + if (are_requirements_equal(&reqs[j], mbr)) { + rnn = j; + break; + } + } + if (rnn < 0) { + rnn = n_rs; + reqs[n_rs] = *mbr; + rs[n_rs++] = TRI_MAYBE; + } + rn[n_rn++] = rnn; + } requirement_vector_iterate_end; + /* Don't need the vector any more */ + requirement_vector_free(&maybes[i]); + } + fc_assert(n_rn == total_reqs); + /* NOTE: May we need to save the "optimal" requirement set for something? + * With little overhead, we can as well get all the equal variants... */ + if (miv) { + *miv += shake_efvs_rec(efvs, values, n_efvs, reqs, rs, n_rs, FALSE); + if (mav) { + for (int i = 0; i < n_rs; i++) { + rs[i] = TRI_MAYBE; + } + } + } + if (mav) { + *mav += shake_efvs_rec(efvs, values, n_efvs, reqs, rs, n_rs, TRUE); + } +} + +/**********************************************************************//** + Sets the effect bonus limits of a given type for any target, + using tester callback with (data, n_data) data. + + Uses the current multiplier values if the player is present in context + and can set them, otherwise uses default multiplier values + + context gives the target (or targets) to evaluate requirements against + effect_type gives the effect type to be considered + + context may be NULL. This is equivalent to passing an empty context. +**************************************************************************/ +void get_target_effects_limits(int *minv, int *maxv, + const struct req_context *context, + const struct player *other_player, + enum effect_type effect_type, + req_tester_cb tester, + void *data, int n_data) +{ + int mi = 0, ma = 0; + int list_size = effect_list_size(get_effects(effect_type)); + struct requirement_vector maybes[list_size]; + int values[list_size]; + int n_shaken = 0; + int n_maybes = 0; + + fc_assert_ret(tester); + if (context == NULL) { + context = req_context_empty(); + } + + /* Loop over all effects of this type. */ + effect_list_iterate(get_effects(effect_type), peffect) { + int vlu = peffect->value; + enum fc_tristate fulf; + + if (!vlu) { + /* Nothing to do with it */ + continue; + } + /* We may have done it, but do it to be sure */ + requirement_vector_init(&maybes[n_shaken]); + fulf = tri_reqs_cb_active(context, other_player, &peffect->reqs, + &maybes[n_shaken], + tester, data, n_data); + + if (TRI_NO != fulf) { + const struct multiplier *pmul = peffect->multiplier; + + if (pmul) { + const struct player *plr = context->player; + + if (NULL != plr && multiplier_can_be_changed(pmul, plr)) { + vlu = vlu + * player_multiplier_effect_value(plr, pmul) + / 100; + } else { + vlu = vlu * multiplier_effect_value(pmul, pmul->def) / 100; + } + } + if (TRI_MAYBE == fulf) { + /* FIXME: caching effects compatibility on ruleset load + * would allow sometimes using a much simpler algorithm */ + if (0 == vlu) { + /* No point considering it (the struct is inited) */ + requirement_vector_free(&maybes[n_shaken]); + continue; + } else { + /* We might have to test compatibility of this effect */ + /* If we prepare some kind of a report, we would do: + * shaken[n_shaken].peffect = peffect; */ + values[n_shaken] = vlu; + n_maybes += requirement_vector_size(&maybes[n_shaken]); + n_shaken++; + } + } else { + /* A certain effect */ + mi += vlu; + ma += vlu; + } + } else { + /* The vector may have collected reqs we don't need */ + requirement_vector_free(&maybes[n_shaken]); + } + } effect_list_iterate_end; + + if (n_shaken > 0) { + /* Use the shaker on the efv array we've got */ + shake_efvs(values, maybes, n_shaken, n_maybes, + minv ? &mi : NULL, maxv ? &ma : NULL); + } + + if (minv) { + *minv = mi; + } + if (maxv) { + *maxv = ma; + } +} + /**********************************************************************//** Returns the effect bonus for the whole world. **************************************************************************/ diff --git a/common/effects.h b/common/effects.h index 33c14f3d23..dad701a9e3 100644 --- a/common/effects.h +++ b/common/effects.h @@ -472,15 +472,22 @@ int get_player_bonus_effects(struct effect_list *plist, const struct player *pplayer, enum effect_type effect_type); int get_city_bonus_effects(struct effect_list *plist, - const struct city *pcity, - const struct output_type *poutput, - enum effect_type effect_type); + const struct city *pcity, + const struct output_type *poutput, + enum effect_type effect_type); int get_target_bonus_effects(struct effect_list *plist, const struct req_context *context, const struct player *other_player, enum effect_type effect_type); +void get_target_effects_limits(int *minv, int *maxv, + const struct req_context *context, + const struct player *other_player, + enum effect_type effect_type, + req_tester_cb tester, + void *data, int n_data); + bool building_has_effect(const struct impr_type *pimprove, enum effect_type effect_type); int get_current_construction_bonus(const struct city *pcity, diff --git a/common/multipliers.c b/common/multipliers.c index 14b250134d..7581993ec4 100644 --- a/common/multipliers.c +++ b/common/multipliers.c @@ -134,7 +134,8 @@ struct multiplier *multiplier_by_rule_name(const char *name) /************************************************************************//** Can player change multiplier value ****************************************************************************/ -bool multiplier_can_be_changed(struct multiplier *pmul, struct player *pplayer) +bool multiplier_can_be_changed(const struct multiplier *pmul, + const struct player *pplayer) { int idx = multiplier_index(pmul); diff --git a/common/multipliers.h b/common/multipliers.h index d8f7c5fc5e..9a4e4cd0fe 100644 --- a/common/multipliers.h +++ b/common/multipliers.h @@ -56,7 +56,17 @@ const char *multiplier_name_translation(const struct multiplier *pmul); const char *multiplier_rule_name(const struct multiplier *pmul); struct multiplier *multiplier_by_rule_name(const char *name); -bool multiplier_can_be_changed(struct multiplier *pmul, struct player *pplayer); +bool multiplier_can_be_changed(const struct multiplier *pmul, + const struct player *pplayer); + +/************************************************************************//** + Convert multiplier value du from display units to effect percentage + it is not tested if du is within valid limits +****************************************************************************/ +inline int multiplier_effect_value(const struct multiplier *pmul, int du) +{ + return (du + pmul->offset) / pmul->factor; +} #define multipliers_iterate(_mul_) \ { \ diff --git a/common/player.c b/common/player.c index a96444015f..d347c650ef 100644 --- a/common/player.c +++ b/common/player.c @@ -1932,8 +1932,9 @@ int player_multiplier_value(const struct player *pplayer, int player_multiplier_effect_value(const struct player *pplayer, const struct multiplier *pmul) { - return (player_multiplier_value(pplayer, pmul) + pmul->offset) - * pmul->factor; + return + multiplier_effect_value(pmul, + player_multiplier_value(pplayer, pmul)); } /*******************************************************************//** -- 2.37.2