From 0778e6e7f732faa486576dc9b88bb9e8a8a61ff8 Mon Sep 17 00:00:00 2001 From: Ihnatus Date: Mon, 7 Nov 2022 22:51:25 +0300 Subject: [PATCH] AI: Improve want for defenders Look at ability of a unit to fortify and to get any bonus out of it. Also, consider then that "CityStatus" requirements may have different source values from "OwnedByOriginal". Most of the patch consists of a generic mechanism to get minimal and maximal effect values in given context considering which requirements of individual effects are contradictory and which imply each other. See OSDN#41781 Signed-off-by: Ihnatus --- ai/default/aitech.c | 49 ++++- ai/default/daieffects.c | 6 +- common/effects.c | 399 ++++++++++++++++++++++++++++++++++++++++ common/effects.h | 13 +- 4 files changed, 461 insertions(+), 6 deletions(-) diff --git a/ai/default/aitech.c b/ai/default/aitech.c index 4f1119ea6e..b9b1543171 100644 --- a/ai/default/aitech.c +++ b/ai/default/aitech.c @@ -23,6 +23,7 @@ /* common */ #include "game.h" #include "government.h" +#include "movement.h" /* is_native_tile() */ #include "player.h" #include "research.h" #include "tech.h" @@ -399,20 +400,62 @@ struct unit_type *dai_wants_defender_against(struct ai_type *ait, struct tile *ptile = city_tile(pcity); int def_values[U_LAST]; int att_idx = utype_index(att); + int fd_min, fd_max; int defbonus = 100 + get_unittype_bonus(pplayer, ptile, att, NULL, EFT_DEFEND_BONUS); unit_type_iterate(deftype) { int mp_pct = deftype->cache.defense_mp_bonuses_pct[att_idx] + 100; int scramble = deftype->cache.scramble_coeff[att_idx]; - int div_bonus_pct = 100 + combat_bonus_against(att->bonuses, deftype, - CBONUS_DEFENSE_DIVIDER_PCT) + int div_bonus_pct = 100 + + combat_bonus_against(att->bonuses, deftype, + CBONUS_DEFENSE_DIVIDER_PCT) + 100 * combat_bonus_against(att->bonuses, deftype, CBONUS_DEFENSE_DIVIDER); + /* Presumed to be popular unit-ranged fortifying requirements */ + struct requirement ureqs[3] = { + { + .source = {.kind = VUT_UNITSTATE, + .value.unit_state = USP_LIVABLE_TILE}, + .range = REQ_RANGE_LOCAL, + .present = TRUE + }, + { + .source = {.kind = VUT_UNITSTATE, + .value.unit_state = USP_NATIVE_TILE}, + .range = REQ_RANGE_LOCAL, + .present = is_native_tile(deftype, ptile) + }, + }; + int ureqs_n = ARRAY_SIZE(ureqs) - 1; /* Number of common requirements */ /* Either the unit uses city defense bonus, or scrambles with its own one */ int def = deftype->defense_strength * (scramble ? scramble : defbonus * mp_pct) / div_bonus_pct; + /* Look if the unit type may ever fortify (not catched elsewhere) */ + /* NOTE: utype_can_do_act_if_tgt_citytile() does not cover activities */ + /* FIXME: needs generalization */ + if (!utype_can_do_action(deftype, ACTION_FORTIFY)) { + ureqs[ureqs_n++] = (struct requirement) { + .source = {.kind = VUT_ACTIVITY, + .value.activity = ACTIVITY_FORTIFIED}, + .range = REQ_RANGE_LOCAL, + .present = FALSE + }; + } + /* FIXME: city tile extras defense is ignored since it is not present + * in most rulesets and may be too local in others. Maybe we need it? */ + get_target_effects_limits(&fd_min, &fd_max, + &(const struct req_context) { + .player = city_owner(pcity), + .city = pcity, + .tile = ptile, + .unittype = deftype, + }, NULL, EFT_FORTIFY_DEFENSE_BONUS, + &default_tester_cb, + ureqs, ureqs_n); + /* The unit is usually fortified but may ever be not (WAG) */ + def = def * (300 + 2 * fd_max + fd_min) / 300; def_values[utype_index(deftype)] = def; if (can_city_build_unit_now(pcity, deftype)) { @@ -426,6 +469,8 @@ struct unit_type *dai_wants_defender_against(struct ai_type *ait, unit_type_iterate(deftype) { if (def_values[utype_index(deftype)] > best_avl_def && !can_city_build_unit_now(pcity, deftype) + /* FIXME: following function does not look at unit reqs, + * do we need a more specific AI (or non-AI) function? */ && can_city_build_unit_later(pcity, deftype)) { /* It would be better than current best. Consider researching tech */ const struct impr_type *building; diff --git a/ai/default/daieffects.c b/ai/default/daieffects.c index 640dfe5dd3..c4b8593f96 100644 --- a/ai/default/daieffects.c +++ b/ai/default/daieffects.c @@ -775,7 +775,11 @@ bool dai_can_requirement_be_met_in_city(const struct requirement *preq, return FALSE; case VUT_CITYSTATUS: - /* FIXME: update */ + if (CITYS_OWNED_BY_ORIGINAL != preq->source.value.citystatus) { + /* We currently are not smart enough to turn it on but can wait + * until it ceases */ + return !preq->present; + } if (pcity == NULL) { return preq->present; } diff --git a/common/effects.c b/common/effects.c index bb91c1ff6b..a690adf873 100644 --- a/common/effects.c +++ b/common/effects.c @@ -129,6 +129,22 @@ static struct { } ruleset_cache; +/* A structure of cache effect calculation result */ +struct efv { + int value; + struct requirement_vector maybes; + int *reqns; + int reqnn; +}; + +static int shake_efvs_rec(struct efv *efvs, int n_efvs, + struct requirement *reqs, enum fc_tristate *rs, + int n_rs, bool tomax); +static void shake_efvs(struct efv *efvs, 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. **************************************************************************/ @@ -680,6 +696,389 @@ bool is_building_replaced(const struct city *pcity, return TRUE; } +/**********************************************************************//** + 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 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 free_ext = 0; /* What we may squeeze out of uncertain array tail */ + bool rightside = tomax ? efvs->value > 0 : efvs->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 += efvs[en].value; + } + en++; + } /* while */ + if (en < n_efvs) { + /* Recurse from this effect */ + free_ext = shake_efvs_rec(&efvs[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 += efvs->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 = efvs->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 += efvs[wen].value; + } + wen++; + } + if (wen < n_efvs) { + withval += shake_efvs_rec(&efvs[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 += efvs[woen].value; + } + woen++; + } + if (woen < n_efvs) { + withoutval += shake_efvs_rec(&efvs[en], n_efvs - en, reqs, cache, + n_rs, tomax) + efvs->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(struct efv *efvs, 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]; + 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(&efvs[i].maybes); + requirement_vector_iterate (&efvs[i].maybes, 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(&efvs[i].maybes); + } + 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, 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, n_efvs, reqs, rs, n_rs, TRUE); + } +} + +/**********************************************************************//** + Sets the effect bonus limits of a given type for any target, + using tester callback with (data, data_sz) data. + + 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 data_sz) +{ + int mi = 0, ma = 0; + int list_size = effect_list_size(get_effects(effect_type)); + struct efv shaken[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(&shaken[n_shaken].maybes); + fulf = tri_reqs_cb_active(context, other_player, &peffect->reqs, + &shaken[n_shaken].maybes, + tester, data, data_sz); + + if (TRI_NO != fulf) { + /* Using multiplier only if we know the player */ + /* FIXME: if we want to consider some other multipliers setting, + * we have to modify the ingame player's structures */ + if (context->player && peffect->multiplier) { + vlu = vlu + * player_multiplier_effect_value(context->player, + peffect->multiplier) / 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(&shaken[n_shaken].maybes); + 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; */ + shaken[n_shaken].value = vlu; + n_maybes += requirement_vector_size(&shaken[n_shaken].maybes); + n_shaken++; + } + } else { + /* A certain effect */ + mi += vlu; + ma += vlu; + } + } else { + /* The vector may have collected reqs we don't need */ + requirement_vector_free(&shaken[n_shaken].maybes); + } + } effect_list_iterate_end; + + if (n_shaken > 0) { + /* Use the shaker on the efv array we've got */ + shake_efvs(shaken, n_shaken, n_maybes, + minv ? &mi : NULL, maxv ? &ma : NULL); + } + + if (minv) { + *minv = mi; + } + if (maxv) { + *maxv = ma; + } +} + /**********************************************************************//** Returns the effect bonus of a given type for any target. diff --git a/common/effects.h b/common/effects.h index 3372826cf7..3f85c29888 100644 --- a/common/effects.h +++ b/common/effects.h @@ -460,15 +460,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 data_sz); + bool building_has_effect(const struct impr_type *pimprove, enum effect_type effect_type); int get_current_construction_bonus(const struct city *pcity, -- 2.34.1