On Sun, 2006-03-12 at 11:09 -0600, Bruno Wolff III wrote:
> On Fri, Mar 10, 2006 at 22:13:58 -0500,
> "john w. bjerk" <[EMAIL PROTECTED]> wrote:
> >
> > Reason:
> > Since currently non-obvious how healing/curing will be apportioned in
> > any but the simplest scenarios, and a number of our coders consider
> > that writing an optimal healing distribution algorithm is quite
> > difficult, and hasn't yet been done:
> >
> > Idea:
> > Healers/Curers should do a fixed amount of healing per adjacent unit,
> > no matter how many units are adjacent. Healing/Curing could stack,
> > but the Healing cap of 8HP(+2 resting) per turn per unit would not be
> > changed. The effect on poison would be unchanged.
>
> Are you going to address team healing at the same time? That is also not
> very clean in the way that works currently. But if you go to a no cap on
> healers (other than there being 6 adjacent spaces), it would be very simple
> to heal allied units at the same time.
Hi all,
I've implemented (not committed) an optimal healing algo. Patch below.
Fun problem. It's O(N^2 * P) where N is the number of healers, and P is
the average patients per healer. Healing is divided on single HP
granularity, currently on "most damaged first" priority.
Anyway, I like having a limit on how much healing a single healer can
perform: it just makes sense, and adds something to the game by forcing
user to triage units.
I agree with Bruno that allied healing is a PITA: it would be far
simpler with this new code, and more obvious, if we change healing to
happen at start of *healer's* turn, and have healer prefer own units
over allies.
Cheers,
Rusty.
Index: actions.cpp
===================================================================
--- actions.cpp (revision 10450)
+++ actions.cpp (working copy)
@@ -1366,6 +1366,245 @@
namespace {
+struct patient
+{
+ patient(const unit &u);
+
+ const unit &u;
+
+ // How much have we been healed already?
+ unsigned int healed;
+
+ // How much healing would we like?
+ unsigned int need;
+
+ // Was our poison stopped?
+ bool poison_stopped;
+
+ // Was our poison cured?
+ bool poison_cured;
+
+ // What priority is this patient? Higher gets healed first.
+ unsigned int priority() const;
+
+ // Treat poison: we do this instead of healing.
+ void patient::treat_poison(bool cure);
+
+ // Heal by some hitpoints (<= need).
+ void heal(unsigned int hp);
+};
+
+patient::patient(const unit &un)
+ : u(un), healed(0), poison_stopped(false), poison_cured(false)
+{
+ int needed = u.max_hitpoints() - u.hitpoints();
+
+ // You can do this with wierd WML, I think.
+ if (needed < 0)
+ need = 0;
+ else
+ need = needed;
+}
+
+void patient::heal(unsigned int hp)
+{
+ wassert(hp <= need);
+ healed += hp;
+ need -= hp;
+}
+
+void patient::treat_poison(bool cure)
+{
+ poison_stopped = true;
+ if (cure)
+ poison_cured = true;
+}
+
+// We use this function to rank patients. We could use
+// least hp, rather than most damage, for example.
+unsigned int patient::priority(void) const
+{
+ return need;
+}
+
+struct healer
+{
+ healer(const gamemap::location& loc, const unit &un);
+
+ // Gather patients.
+ void find_patients(std::map<gamemap::location,patient *> &patient_map,
+ const gamemap &map);
+
+ const gamemap::location& loc;
+ const unit &u;
+
+ std::list<patient *> patients;
+
+ unsigned int healing_left;
+
+ // Did we do any healing? (for graphics)
+ bool did_healing;
+
+ // Ratio of how much healing we can do to how much is needed.
+ double capacity_ratio() const;
+
+ // Perform healing on one patient.
+ void do_heal();
+
+ void treat_poison();
+
+ // Consider your patients (they might have changed).
+ // Return false if there's nothing more this healer can do.
+ void assess_patients();
+
+ bool healer::finished() const;
+
+ // Will this unit heal for us?
+ static bool will_heal(const gamemap::location& loc, const unit &un,
+ unsigned int side, const
unit_map& units,
+ const gamemap& map, const
std::vector<team>& teams);
+
+private:
+ bool can_help_unit(const gamemap::location& loc, const unit &un, const
gamemap& map);
+};
+
+bool healer::will_heal(const gamemap::location& loc,
+ const unit &un,
+ unsigned int side,
+ const unit_map& units,
+ const gamemap& map,
+ const std::vector<team>& teams)
+{
+ if (!un.type().heals() || un.incapacitated())
+ return false;
+
+ if (un.side() == side)
+ return true;
+
+ if (teams[un.side()-1].is_enemy(side))
+ return false;
+
+ // Allied healers only heal us if they have no adjacent, healable, own
units.
+ // FIXME: This would be much simplified if they did healing at start of
*healer* turn.
+ gamemap::location adjacent[6];
+ get_adjacent_tiles(loc, adjacent);
+ for (unsigned int n = 0; n != 6U; ++n) {
+ const unit_map::const_iterator i = units.find(adjacent[n]);
+ if (i != units.end()) {
+ if (!map.gives_healing(i->first)
+ && i->second.healable()
+ && i->second.side() == un.side()
+ && (i->second.poisoned() ||
i->second.hitpoints() < i->second.max_hitpoints()))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+healer::healer(const gamemap::location& location, const unit &un)
+ : loc(location), u(un), healing_left(u.type().max_unit_healing()),
did_healing(false)
+{
+}
+
+bool healer::can_help_unit(const gamemap::location& location, const unit &un,
const gamemap& map)
+{
+ // Village will do all the healing: we can add nothing.
+ if (map.gives_healing(location))
+ return false;
+
+ return un.poisoned() || un.hitpoints() < un.max_hitpoints();
+}
+
+void healer::find_patients(std::map<gamemap::location,patient *> &patient_map,
+ const gamemap &map)
+{
+ // Put all units on this side who we can into the map.
+ gamemap::location adjacent[6];
+ get_adjacent_tiles(loc, adjacent);
+
+ for (unsigned int n = 0; n != 6U; ++n) {
+ std::map<gamemap::location,patient *>::iterator p;
+ p = patient_map.find(adjacent[n]);
+ if (p != patient_map.end() && can_help_unit(p->first,
p->second->u, map)) {
+ LOG_NG << "Healer '" << u.name() << "' adding patient
'"
+ <<
patient_map.find(adjacent[n])->second->u.name() << "'\n";
+
patients.push_back(patient_map.find(adjacent[n])->second);
+ }
+ }
+}
+
+double healer::capacity_ratio() const
+{
+ unsigned int demand = 0;
+
+ // What's the most we could offer each unit?
+ for (std::list<patient *>::const_iterator i = patients.begin(); i !=
patients.end(); ++i) {
+ demand += minimum((*i)->need, u.type().heals() - (*i)->healed);
+ }
+
+ return healing_left / (double)demand;
+}
+
+void healer::do_heal()
+{
+ unsigned int prio = 0;
+ std::list<patient*>::iterator p;
+
+ // Find highest priority patient.
+ for (std::list<patient*>::iterator i = patients.begin(); i !=
patients.end(); ++i) {
+ if ((*i)->priority() >= prio) {
+ prio = (*i)->priority();
+ p = i;
+ }
+ }
+ LOG_NG << "Healer '" << u.name() << "' healing patient '"
+ << (*p)->u.name() << "'\n";
+
+ (*p)->heal(1);
+ healing_left--;
+ did_healing = true;
+}
+
+// We do this first up: we can stop/cure poison, but can't heal any more.
+void healer::treat_poison()
+{
+ for (std::list<patient*>::iterator i = patients.begin(); i !=
patients.end(); ) {
+ if ((*i)->u.poisoned()) {
+ LOG_NG << "Healer '" << u.name() << "' treating
poisoned patient '"
+ << (*i)->u.name() << "'\n";
+ (*i)->treat_poison(u.type().heals() >=
game_config::cure_amount);
+ i = patients.erase(i);
+ } else
+ i++;
+ }
+}
+
+// Trivially optimizable: only reconsider healers with patient which actually
changed.
+void healer::assess_patients()
+{
+ // Discard patients which have no need, or whom we can't help.
+ // In practice, there can only be 1 at the moment.
+ std::list<patient*>::iterator i;
+ for (i = patients.begin(); i != patients.end(); ) {
+ // FIXME: make u.type().heals() unsigned.
+ if ((*i)->need == 0
+ || (*i)->healed >= unsigned(u.type().heals())) {
+ LOG_NG << "Healer '" << u.name() << "' done with
patient '"
+ << (*i)->u.name() << "' need:" <<
lexical_cast<std::string>((*i)->need)
+ << " healed:" <<
lexical_cast<std::string>((*i)->healed) << "\n";
+ i = patients.erase(i);
+ } else
+ i++;
+ }
+}
+
+bool healer::finished() const
+{
+ return healing_left == 0 || patients.empty();
+}
+
+#if 0
//function which returns true iff the unit at 'loc' will heal a unit from side
'side'
//on this turn.
//
@@ -1424,9 +1663,11 @@
//there's no-one of higher priority nearby, so the ally will heal
return true;
}
+#endif
}
+#if 0
void calculate_healing(display& disp, const gamestatus& status, const gamemap&
map,
std::map<gamemap::location,unit>& units, int side,
const std::vector<team>& teams, bool
update_display)
@@ -1706,7 +1947,129 @@
}
}
}
+#else
+void calculate_healing(display& disp, const gamestatus& status, const gamemap&
map,
+ std::map<gamemap::location,unit>& units, unsigned int
side,
+ const std::vector<team>& teams, bool
update_display)
+{
+ std::map<gamemap::location,unit>::iterator i;
+ std::map<gamemap::location,patient *> patient_map, village_people;
+ std::map<gamemap::location,patient *>::iterator p;
+ std::list<healer> healers, done_healers;
+ std::list<healer>::iterator h;
+ // All units on this side are potential patients.
+ for (i = units.begin(); i != units.end(); ++i) {
+ if (i->second.side() == side && i->second.healable()) {
+ patient *p = new patient(i->second);
+ patient_map.insert(std::pair<gamemap::location,patient
*>(i->first, p));
+
+ // Track units in villages (ignored by healers)
+ if (map.gives_healing(i->first))
+
village_people.insert(std::pair<gamemap::location,patient *>
+
(i->first, p));
+ }
+
+ // Is this a healer which will help us?
+ if (healer::will_heal(i->first, i->second, side, units, map,
teams))
+ healers.push_back(healer(i->first, i->second));
+ }
+
+ // Now we have complete patient map, have healers find their patients.
+ for (h = healers.begin(); h != healers.end(); h++)
+ h->find_patients(patient_map, map);
+
+ // Deal with the Village People first!
+ for (p = village_people.begin(); p != village_people.end(); p++) {
+ if (p->second->u.poisoned()) {
+ LOG_NG << "Village curing poison '" <<
p->second->u.name() << "'\n";
+ p->second->treat_poison(true);
+ } else {
+ LOG_NG << "Village healing '" << p->second->u.name()
<< "'\n";
+ p->second->heal(game_config::cure_amount);
+ }
+ }
+
+ // Cure/delay poison first.
+ for (h = healers.begin(); h != healers.end(); ) {
+ h->treat_poison();
+
+ // This also weeds out any healers who had no patients to begin
with.
+ if (h->finished()) {
+ done_healers.push_back(*h);
+ h = healers.erase(h);
+ } else
+ h++;
+ }
+
+ while (!healers.empty()) {
+ double best_capacity_ratio = -1;
+ std::list<healer>::iterator best;
+
+ // Find least stressed healer.
+ for (h = healers.begin(); h != healers.end(); h++) {
+ if (h->capacity_ratio() > best_capacity_ratio) {
+ best = h;
+ best_capacity_ratio = h->capacity_ratio();
+ }
+ }
+
+ LOG_NG << "Best healer chosen was '" << best->u.name() << "'\n";
+ best->do_heal();
+
+ // Weed out any healers now spent (could be others, if they
+ // share same patient as this healer).
+ for (h = healers.begin(); h != healers.end(); ) {
+ h->assess_patients();
+ if (h->finished()) {
+ LOG_NG << "Healer '" << h->u.name() << "' is
finished\n";
+ done_healers.push_back(*h);
+ h = healers.erase(h);
+ } else
+ h++;
+ }
+ }
+
+ // Apply bonus to rested units.
+ for (p = patient_map.begin(); p != patient_map.end(); p++) {
+ if (p->second->u.is_resting()) {
+ // FIXME: BUG compatible, only give resting bonus if
poison untreated.
+ if (!p->second->u.poisoned() ||
!p->second->poison_stopped) {
+ p->second->healed +=
minimum<int>(game_config::rest_heal_amount,
+
p->second->need);
+ }
+ }
+ }
+
+ // FIXME: Draw results.
+ // Now, apply the results.
+ for (p = patient_map.begin(); p != patient_map.end(); p++) {
+ unit &u = units.find(p->first)->second;
+
+ if (p->second->u.poisoned()) {
+ if (p->second->poison_cured)
+ u.remove_flag("poisoned");
+ else if (!p->second->poison_stopped) {
+ int damage = minimum<int>(u.hitpoints() - 1,
game_config::cure_amount);
+ // FIXME: BUG compatible: can get resting bonus
when poisoned.
+ u.gets_hit(damage - p->second->healed);
+ }
+ // No healer should have wasted effort on this.
+ // FIXME: rest_heal_amount should be unsigned.
+ wassert(p->second->healed <=
unsigned(game_config::rest_heal_amount));
+ } else {
+ if (p->second->healed)
+ u.heal(p->second->healed);
+ }
+
+ // Reset all units' resting state while we're looping anyway.
+ u.set_resting(true);
+
+ delete(p->second);
+ }
+}
+#endif
+
unit get_advanced_unit(const game_data& info,
std::map<gamemap::location,unit>& units,
const gamemap::location& loc, const std::string& advance_to)
Index: actions.hpp
===================================================================
--- actions.hpp (revision 10450)
+++ actions.hpp (working copy)
@@ -123,7 +123,7 @@
//calculates healing for all units for the given side. Should be called
//at the beginning of a side's turn.
void calculate_healing(display& disp, const gamestatus& status, const gamemap&
map,
- std::map<gamemap::location,unit>& units, int side,
+ std::map<gamemap::location,unit>& units, unsigned int
side,
const std::vector<team>& teams, bool
update_display);
//function which, given the location of a unit that is advancing, and the
--
ccontrol: http://ozlabs.org/~rusty/ccontrol
_______________________________________________
Wesnoth-dev mailing list
[email protected]
https://mail.gna.org/listinfo/wesnoth-dev