Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the logic of AI defensive tactics #8047

Merged
merged 19 commits into from
Dec 23, 2023

Conversation

oleg-derevenetz
Copy link
Collaborator

@oleg-derevenetz oleg-derevenetz commented Nov 16, 2023

close #8031

No rocket science, just a (hopefully) sane implementation of defensive tactics plus minor code deduplication.

master branch:

fheroes2.engine.version_.1.0.10.2023-11-19.22-47-29.mp4

Swordsmen do not actually cover the Rangers, they run out as soon as the Mummies come into range, allowing the Mummies to block the shooters and surround the infantry units, resulting in defeat.

This PR:

fheroes2.engine.version_.1.0.10.2023-11-19.22-44-45.mp4

Here the AI acts in the style of a human player: Rangers are safe under the protection of Swordsmen who stand on the defensive and do not allow the Mummies to surround the infantry units and approach the shooters, resulting in a victory with moderate losses.

Behavior in the situation from the @Branikolog's issue:

fheroes2.engine.version_.1.0.10.2023-11-19.23-18-57.mp4

There are no skipped turns, all units participate in the battle.

@oleg-derevenetz oleg-derevenetz marked this pull request as draft November 16, 2023 15:46
@oleg-derevenetz oleg-derevenetz added improvement New feature, request or improvement AI Artificial intelligence behaviour labels Nov 16, 2023
@oleg-derevenetz oleg-derevenetz added this to the 1.0.11 milestone Nov 16, 2023
@oleg-derevenetz oleg-derevenetz force-pushed the defensive-tactics-improvement branch 6 times, most recently from de16ef6 to d0ac7e6 Compare November 17, 2023 14:14
@oleg-derevenetz oleg-derevenetz marked this pull request as ready for review November 20, 2023 10:52
@Branikolog
Copy link
Collaborator

Branikolog commented Nov 22, 2023

Hello, @oleg-derevenetz !
I've made a few tests here.
Here's what I've observed:

2023-11-22.21-44-52.mp4

When placing griffins not really close to boars and mages AI decided to skip rocs. In this particular situation, I suppose, AI doesn't know how to block that free single cell near mages with 2-hex Roc. So why did he decide to skip?
But at the same time moved Golems and Mummies, which is good.

If I skip griffins - AI skips all of them: Rocs+Golems+Mummies.

2023-11-22.21-45-39.mp4

So I cannot say the PR fixes the issue entirely.

Speaking about your example from Battle mode.
I can see a few weak spots. (Probably, they are not related to your fix, but I just want to mention them.)

2023-11-22.21-49-37.mp4

Swordsmen prefer defending Archers, but there're just 3 of them. I'm not sure it is useful defending so tiny stack.

Even If I set higher swordsmen quantity - they still behave the same. I expect them attacking Mummies instead of defending useless stack of archers. Note, that with such behaviour they give the first strike to mummies, which brings more losses.

2023-11-22.22-08-03.mp4

I've also noticed that swordsmen don't attack mummies at all, while defending archers. I expect them fighting till death with nearest foes, while keeping the defensive position. Maybe they can skip attack, but only in a case they will be dead after mummies retaliate. Correct me, if I'm wrong, but currently AI cannot plan such things as not attacking to keep the line of defending troops unbroken. So when they are not attacking mummies standing right in front of them.

@oleg-derevenetz
Copy link
Collaborator Author

oleg-derevenetz commented Nov 22, 2023

Hi @Branikolog

When placing griffins not really close to boars and mages AI decided to skip rocs. In this particular situation, I suppose, AI doesn't know how to block that free single cell near mages with 2-hex Roc. So why did he decide to skip?
But at the same time moved Golems and Mummies, which is good.

When the defensive tactics is chosen, AI will only attack units located in its own half of the battlefield, because it is implied that if such a tactic is chosen, then the defending side has superiority in archers, and friendly units should not move too far - on the one hand, to protect the archers if necessary, and on the other - so as not to incur unnecessary losses while fighting surrounded by enemy melee units on the enemy side of the battlefield. Griffins in your video are placed just on the border of two halves of the battlefield, so everything is determined by the nearest cell to the attacker. For Rocks it's the cell with index 26, which is "on the other side", while for Mummies it's the cell 38, which is "on our side".

If I skip griffins - AI skips all of them: Rocs+Golems+Mummies.

And that's the right behavior.

So I cannot say the PR fixes the issue entirely.

I think the current behavior is more correct in the defensive tactics mode than just attacking enemy units headlong.

Swordsmen prefer defending Archers, but there're just 3 of them. I'm not sure it is useful defending so tiny stack.

This is a question of when to choose an attacking tactic, and when to choose a defensive one. In general, this is out of scope of this PR, but for general understanding, currently defensive tactics is chosen when player's side has the superiority in archers, but it doesn't have an overwhelming superiority in general army strength ("overwhelming" is x6 - x10 depending on the composition of the army and the type of the current unit - offensive tactics is more readily chosen for flying units). "Tiny stack" is a rather vague concept. Need some numbers :) Anyway, it's out of scope of this PR.

Correct me, if I'm wrong, but currently AI cannot plan such things as not attacking to keep the line of defending troops unbroken. So when they are not attacking mummies standing right in front of them.

It can do this, but this is not done deliberately for defensive tactics, so that the units protecting the archers will last as long as possible. With the current behavior, they will take damage just once per turn (considering the case when number of attackers is equal to the number of defending units), while with the more "agressive" behavior they will take damage at least twice per turn - normal damage from the attacker + retaliation damage. It is assumed that archers (and "free" units not covering the archers) will do all the job, because we have an advantage in them, and they will not be able to do their job if their guards are destroyed quickly. Even one or two additional turns with non-blocked archers can affect the outcome of the battle in this case, so the units covering the archers behave passively and do not get into trouble (not like Titus Pullo in the first scene of "Rome").

@Branikolog
Copy link
Collaborator

Branikolog commented Nov 23, 2023

Hi, @oleg-derevenetz

Griffins in your video are placed just on the border of two halves of the battlefield, so everything is determined by the nearest cell to the attacker. For Rocks it's the cell with index 26, which is "on the other side", while for Mummies it's the cell 38, which is "on our side".

I find it weird when melee 1-hex troops attack that griffins stack, but rocs preferred to skip. I think we should somehow make AI consistent in his decisions of attacking or not with all his troops.

and on the other - so as not to incur unnecessary losses while fighting surrounded by enemy melee units on the enemy side of the battlefield.

In the situation above griffins are still remain flying troops, that can move over the whole battlefield and AI shouldn't be scared attacking them.
Once he blinded minotaurs he still didn't want to attack neither griffins nor centaurs and just skipped.
Even if there're no griffins AI still prefers skipping:

2023-11-23.13-18-57.mp4

How do you think, should blinded melee troop be ignored in such situation and should AI behave as there're no melee troops on opponent's side?

If I skip griffins - AI skips all of them: Rocs+Golems+Mummies.

And that's the right behavior.

From my point of view a player should move such slow troops as mummies and golems closer to the defended shooting stack. I know it's not really related to the current PR. How do you think, should I open a separate issue for such cases?

So I cannot say the PR fixes the issue entirely.

I think the current behavior is more correct in the defensive tactics mode than just attacking enemy units headlong.

From my point of view AI could attack troops from opposite army in a case there're not much potential losses expected in melee combat on the opposite side of the battlefield. Or, as I mentioned above, he should make all his walking army a little more compact (gather all troops closer to the defended archers), so he can defend his archers more effectively. Maybe if there were fliers instead of mummies and golems, skipping could be the proper behaviour.
But anyway, AI attacking me looks much better, even if it's a little less optimal, that just skipping for a unknown reason. If Minotaurs were not blinded and all bottom troops were fliers this behaviour would look proper and correct. Now it looks like something is wrong and rather large stack of rocs just doing nothing, while it can bring significant damage to griffins or centaurs.

This is a question of when to choose an attacking tactic, and when to choose a defensive one. In general, this is out of scope of this PR, but for general understanding, currently defensive tactics is chosen when player's side has the superiority in archers, but it doesn't have an overwhelming superiority in general army strength ("overwhelming" is x6 - x10 depending on the composition of the army and the type of the current unit - offensive tactics is more readily chosen for flying units). "Tiny stack" is a rather vague concept. Need some numbers :) Anyway, it's out of scope of this PR.

It looks like I accidentally touched the edge values for this logic. :)
Unfortunately, since I don't know how exactly the code works, I cannot advise any numbers, except of saying: "Lower/higher."
I'm not sure anyone can propose the correct numbers and we just need to changing them with a little steps and testing it in different situations.
Should I create a separate issue for such defending of "tiny" archer stacks? :)

It can do this, but this is not done deliberately for defensive tactics, so that the units protecting the archers will last as long as possible. With the current behavior, they will take damage just once per turn (considering the case when number of attackers is equal to the number of defending units), while with the more "agressive" behavior they will take damage at least twice per turn - normal damage from the attacker + retaliation damage. It is assumed that archers (and "free" units not covering the archers) will do all the job, because we have an advantage in them, and they will not be able to do their job if their guards are destroyed quickly. Even one or two additional turns with non-blocked archers can affect the outcome of the battle in this case, so the units covering the archers behave passively and do not get into trouble (not like Titus Pullo in the first scene of "Rome").

Ideally I expect AI checking the overall strength of archers in such situation: if they are quite weak, then defending rows of swordsmen shouldn't suffer standing without an action. But this is rather tricky logic to adjust.
How do you think about making such logic, so small swordsmen stack won't attack if they will be eliminated from the retaliation? I mean if swordsmen attack and die - then just skip, allowing archers 1 extra shot. If swordsmen stack can survive retaliation - they should attack nearby enemy without opening a path for attackers to cover the archers.

if they will be eliminated from the retaliation? I mean if swordsmen attack and die - then just skip,

If you think it's just too risky, then what about making them skip their turn if they couldn't survive the potential retaliation AND the upcoming the attack of the troop? (Sure, there could be a situations, when swordsmen could be facing several stacks, but in this case they just die without dealing any damage suffering attacks from 2 and more enemy stacks.)
But if we have two and more stacks of swordsmen and only a single enemy stack, all swordsmen are going to be scared attacking, while 1 stack can attack, suffer retaliation, and the next stack of swordsmen will attack without retaliation. I believe than in average situation this brings more profit, than all 3 stacks of swordsmen would skip until enemy would break 1 and get the archers covered.

while with the more "agressive" behavior they will take damage at least twice per turn - normal damage from the attacker + retaliation damage.

Don't forget that swordsmen deal some damage reducing the attacking stack, which makes future enemy attacks weaker. (In the example from the video above swordsmen even have an advantage in speed, which allows them to attack first > eliminate some mummies before they retaliate and attack back).
Ideally, if we can create a formula, that considers archers strength (the speed they eliminate enemy stacks) +swordsmen strength (the damage swordsmen can deal on their own), and the potential losses of defending swordsmen it would be the perfect defending tactics, but for now I prefer AI to behave more aggressive, rather than skipping, as it looks like a bug in logic.

@oleg-derevenetz
Copy link
Collaborator Author

oleg-derevenetz commented Nov 23, 2023

Hi @Branikolog

I think we should somehow make AI consistent in his decisions of attacking or not with all his troops.

It is consistent. There is a clear logic: if the point from which it is advantageous to attack (the closest of those that gives the maximum potential damage) is on our side of the battlefield, then the unit will attack, otherwise it will not.

In the situation above griffins are still remain flying troops, that can move over the whole battlefield and AI shouldn't be scared attacking them.

If we have an advantage in shooting units, there is no point in putting flying units in a risky position, which will be outnumbered on the enemy side of the battlefield and will suffer unnecessary losses.

How do you think, should blinded melee troop be ignored in such situation and should AI behave as there're no melee troops on opponent's side?

I would say that this is an unnecessary risk, because the opponent may have Cure or Dispel Magic spells. He can heal the Minotaurs and they will attack our Rocks on the next turn, as they have an advantage in speed.

From my point of view a player should move such slow troops as mummies and golems closer to the defended shooting stack.

Currently they are moving closer to the shooters only if there is a real way to cover them. But in your video there is no way to do it because of obstacles. In theory, of course, they can try to snuggle up to other units covering the shooter, but in this case you can say that this means the risk of using mass damage spell by the attacker, therefore, this behavior does not apply. Free units will only attack the enemy as soon as he appears in their half of the battlefield.

From my point of view AI could attack troops from opposite army in a case there're not much potential losses expected in melee combat on the opposite side of the battlefield.

Currently it will do it - if it has x6 - x10 advantage in the overall army power (regardless of the advantage in archers) AI will switch to the attacking tactics. Maybe these numbers can be reduced, but this PR does not relate to the choice of tactics, but to actions with the chosen tactics.

Now it looks like something is wrong and rather large stack of rocs just doing nothing, while it can bring significant damage to griffins or centaurs.

... and then get ambushed by the Minotaurs on the next turn if enemy has dispelling magic. I personally don't care much that "something may look wrong/like a bug" from the player's point of view as long as the behavior is backed up by clear logic.

Unfortunately, since I don't know how exactly the code works, I cannot advise any numbers, except of saying: "Lower/higher."
I'm not sure anyone can propose the correct numbers and we just need to changing them with a little steps and testing it in different situations.
Should I create a separate issue for such defending of "tiny" archer stacks? :)

This refers to the choice of tactics that is out of scope of this PR. We can set it up later if there are any specific ideas on this.

Ideally I expect AI checking the overall strength of archers in such situation: if they are quite weak, then defending rows of swordsmen shouldn't suffer standing without an action. But this is rather tricky logic to adjust.

Again, it's a matter of choosing tactics.

How do you think about making such logic, so small swordsmen stack won't attack if they will be eliminated from the retaliation?

This means looking just one step ahead. But in general if the goal is to hold out for as many turns as possible then it is more profitable NOT to attack. Consider the following case:

fheroes2.engine.version_.1.0.10.2023-11-23.11-49-44.mp4

Dwarves lasted 3 turns, attacking the Minotaurs uselessly, and did not kill a single one. With the "non-attacking" tactics:

fheroes2.engine.version_.1.0.10.2023-11-23.11-48-23.mp4

Dwarves lasted 6(!) turns. And every extra turn is an extra shot of archers covered by dwarves.

but for now I prefer AI to behave more aggressive, rather than skipping, as it looks like a bug in logic.

Again, don't worry too much about "how it looks like". Most players have no idea about the game mechanics, how their actions may turn out and what kind of behavior may be more profitable in the general case, taking into account current priorities (priorities "kill more enemies" and "hold out more turns" generally require different behavior). We need to do what is right in the general case, taking into account the chosen tactics, and not what the player "expects". From the point of view of the average player, the natural behavior of AI is to stupidly run at the nearest enemy unit, because it's what most AI implementations do :)

We can add the behavior "while covering shooters, attack only those units that are guaranteed to be unable to respond," but this improvement can be made a separate PR, because this also needs to be thought through - for example, should you attack a paralyzed unit or not, and if yes, in what cases? The thing is that from the "hold out more turns" point of view it is more profitable NOT to attack such a unit, and archers will not to attack it as well (unless there are no other targets).

@Branikolog
Copy link
Collaborator

Branikolog commented Nov 23, 2023

@oleg-derevenetz

Again, don't worry too much about "how it looks like". Most players have no idea about the game mechanics, how their actions may turn out and what kind of behavior may be more profitable in the general case, taking into account current priorities (priorities "kill more enemies" and "hold out more turns" generally require different behavior). We need to do what is right in the general case, taking into account the chosen tactics, and not what the player "expects". From the point of view of the average player, the natural behavior of AI is to stupidly run at the nearest enemy unit, because it's what most AI implementations do :)

I agree, that average implementations are quite weak. :) We're both here to make fheroes2 implementation outstanding. ;)
But at the same time I expect AI behave in such manner, so I (as an experienced player) always know, what is happening and why. Currently, I find it really weird, when Rocs and other troops stand and do nothing, while they can at least come closer, but this issue for sure, is out of the scope of this PR. (But not out the #8031 ;) )

We can add the behavior "while covering shooters, attack only those units that are guaranteed to be unable to respond," but this improvement can be made a separate PR, because this also needs to be thought through - for example, should you attack a paralyzed unit or not, and if yes, in what cases? The thing is that from the "hold out more turns" point of view it is more profitable NOT to attack such a unit, and archers will not to attack it as well (unless there are no other targets).

Not responded attacks... Do you mean hydra's non-retaliatiative ability? Or just the troops, that were already retaliated this turn? (This is not going to happen too often, since to cover the rangers you need almost all stacks from your army and they are standing still without attacking.)

Sure, I don't want to force you making too huge PR so it would be impossible to test properly. Let's make improvements step by step.
But we definitely need a defined rule for choosing the most profitable behaviour (attacking or holding more turns) in this case.

How do you think about making such logic, so small swordsmen stack won't attack if they will be eliminated from the retaliation?

This means looking just one step ahead. But in general if the goal is to hold out for as many turns as possible then it is more profitable NOT to attack. Consider the following case:

Again, it's a matter of choosing tactics.

Indeed, but we need a clear logic to define which case is more proper for living as much as possible, and which brings more benefits when attacking.

Right now I see this logic as not optimal:

PR:

2023-11-23.20-16-22.mp4

AI lost 9 swordsmen holding their attacks.

Master:

2023-11-23.20-20-00.mp4

AI lost just 5 swordsmen. I believe I could personally reduce my losses even more targeting the most vulnerable mummies.

It goes without saying that covering 5 archers is not really worth it, considering how many swordsmen we have in army.

But I went even further and here's the next example:

2023-11-23.20-22-53.mp4

Doubled swordsmen stacks covering those 5 archers look ridiculous skipping attacks.

Here's the outcome from the master:

image

I think we should somehow make AI consistent in his decisions of attacking or not with all his troops.

It is consistent. There is a clear logic: if the point from which it is advantageous to attack (the closest of those that gives the maximum potential damage) is on our side of the battlefield, then the unit will attack, otherwise it will not.

I apologize if I understood you incorrectly. Griffins were placed on the edge of Rocs "safe" zone. But in the area of the safe zone of Golems and Mummies, right?

image

Is this why Golems and Mummies move towards griffins?

Attacking griffins

I assume they are willing to attack griffins. But Rocs are not, which is weird to me, as Rocs potentially appear on the same place as golems will and are going to be potentially attacked by minotaurs (potentially cured) (I'll come back later to this issue of the chances and risks with blind spell)

@oleg-derevenetz
Copy link
Collaborator Author

oleg-derevenetz commented Nov 23, 2023

Hi @Branikolog

Currently, I find it really weird, when Rocs and other troops stand and do nothing, while they can at least come closer

This needs to be formalized. "Come closer" is how much "closer"? Just stand all in a pile closely? Then some will interfere with others because some of them will need to perform extra steps to bypass others (and some of them may be blocked completely by surrounding friendly units), and this behavior increases the likelihood of the enemy using mass damage spells.

Do you mean hydra's non-retaliatiative ability? Or just the troops, that were already retaliated this turn? (This is not going to happen too often, since to cover the rangers you need almost all stacks from your army and they are standing still without attacking.)

Both. But, as you said, this is not going to happen too often, so I decided not to implement this.

Doubled swordsmen stacks covering those 5 archers look ridiculous skipping attacks.

I agree that AI should not use defensive tactics in cases like this. There should be some minimum threshold in archer's superiority, not just "I have 3 archers and you have none", or, maybe, x6-x10 advantage requirement is too high. This needs to be thought out separately.

Griffins were placed on the edge of Rocs "safe" zone. But in the edge of the safe zone of Golems and Mummies, right?

Not quite. Since battlefield has the hexagonal grid, grid cells on nearby rows are placed with offset. For Rocks, the closest cell near the Griffins is cell 26 (upper right one near the Griffins - on the same row where Rocks are placed) which is considered "enemy side", while for Mummies it's cell 38, to the right of the Griffins, which is "our side", since it has offset to the right.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Clang-Tidy found issue(s) with the introduced code (1/1)

src/fheroes2/ai/normal/ai_normal_battle.cpp Outdated Show resolved Hide resolved
@oleg-derevenetz
Copy link
Collaborator Author

oleg-derevenetz commented Nov 23, 2023

I apologize if I understood you incorrectly. Griffins were placed on the edge of Rocs "safe" zone. But in the area of the safe zone of Golems and Mummies, right?

OK, I streamlined the logic a bit. Now an enemy unit will be attacked if there is any way to attack it from a defended area, even if it means attacking not from the cell that is closest to the attacker:

fheroes2.engine.version_.1.0.10.2023-11-24.02-14-06.mp4

Defended area is the player's half of the battlefield (max 5 cells from the corresponding edge). After the Rocs moved to attack the Griffins, the passage to the Magi opened, and the Mummies with Golems began to approach them to protect them from all sides.

as Rocs potentially appear on the same place as golems will and are going to be potentially attacked by minotaurs (potentially cured)

Attempts to develop general behavior based on a particular case will not lead to anything good. Although the logic that with the chosen defensive tactics, units should not leave their half of the battlefield was made before me, I fully agree with it and understand why this was done. This is done in order to avoid unnecessary losses in the general case, because in the general case switching to this tactic means that the enemy has more melee units while you have more shooters. In the general case, trying to attack someone in the enemy half of the battlefield, you put your units under attack of enemy infantry, where they will be quickly outnumbered and surrounded (once again, I remind you that, logically, such tactics should be chosen if you have more shooters, but less melee units than the enemy, so sending melee units, which you already have less than the enemy, directly into a bunch of enemy infantry is not very smart). Please note once again that I am talking about actions with the chosen tactics, and not about the choice of tactics. The right choice of tactics is a separate, although of course related, problem, and there is a field for additional tuning.

I'll come back later to this issue of the chances and risks with blind spell

I suggest not wasting time discussing "chances and risks", but working out a general logic, if you believe that the current logic does not suit. Perhaps this should be done after re-evaluating the mechanism for choosing tactics - that is, when to choose defensive tactics, and when to choose offensive tactics, because choosing defensive tactics with a small number of archers is indeed not always justified. This can be done in a separate PR if you have any specific ideas. Perhaps after that, some of the questions about the current logic of the defensive tactics will disappear altogether, since this tactics just will not be applied in situations that are now considered controversial.

Please keep in mind that computers don't understand general ideas. Specific strict algorithms are needed, and preferences are given to simpler ones if they give results that are "good enough" in most cases, because the more complex the algorithm, the more difficult it is to implement, test and then maintain it (especially for people who came to the project recently and want to figure it out).

@ihhub
Copy link
Owner

ihhub commented Dec 7, 2023

Hi @Branikolog is busy with some personal issues so his feedback is delayed at the moment.

@ihhub
Copy link
Owner

ihhub commented Dec 14, 2023

Hi @Branikolog , please provide your feedback about this pull request.

@oleg-derevenetz
Copy link
Collaborator Author

Hi @ihhub the state of this PR is still uncertain, I believe, and it will not go to 1.0.11?

@ihhub
Copy link
Owner

ihhub commented Dec 21, 2023

Hi @ihhub the state of this PR is still uncertain, I believe, and it will not go to 1.0.11?

I will chase @Branikolog in person within (my) tomorrow and come back to you. I would like to put it to 1.0.11.

@ihhub ihhub added the high priority Very critical change needed immediately label Dec 21, 2023
@Branikolog
Copy link
Collaborator

Branikolog commented Dec 21, 2023

Hi, @oleg-derevenetz and @ihhub

First of all I want to apologize for such a late response to this PR.
For me it's really important to be sure that battles controlled by AI worse, as this would lower the overall AI efficiency in the match.
So I spent lots of time analysing current behaviour.

Testing the PR with enhanced battle mode was really a pleasure, by the way. :)

If a defensive tactic is chosen, but the stack cannot reach either friendly archers or any of the enemy units blocking them within the next 2 turns (and in your video these Swordsmen cannot reach their archers in 2 turns, they are missing one cell), BUT there is an enemy unit(s) within reach, then it will continue to use the attacking tactics (i.e. it will not try to retreat to cover the archers, because it's probably going to be late, but it will attack most dangerous reachable enemy stacks instead).

This logic is quite straightforward, but sometimes results of the battle really suffer from such approach:

image

image

image

While the common outcome is:
image

(in a case when all swordsmen defend archers)

I'm afraid the more variative scenarios we want AI to solve, the more we have to add into the existing AI logic.
I personally cannot describe whole effective behaviour in just a few logic blocks. 😞 There're so many things to consider so I think it's not possible to implement the whole logic of defending shooters in just 1 or 2 logic statemens.
But here're a few thoughts on the existing logic:

Defensive logic shines only in a case army has multiple melee stacks and all troops are close to the defended shooter. 👍
But in a case 1 troop is too far away - he stays and suffers much from all attacks focused on him. ➖

2023-12-17.12-00-33.mp4

In a case the defending army has unsufficient stacks, they start moving backwards allowing enemy to attack. ➖

2023-12-17.16-00-26.mp4

Trying to defend shooters with insufficient troop stack number makes no sense. It even makes worse, since followed enemy troops can reach shooters. Is it possible to implement some sort of counter, so AI would know how many troops are needed and are ready to cover shooters to initiate defensive tactics? I'm not asking to do it urgently, but with this issue AI would loose more shooters during battles, which is not good.

I don't see how and why we need to "fix" this. What exactly do you propose, with numbers? Increase the limit of turns from 2 to...? If you set the limit too high, then slow units, like Hydra or Iron Golems, will just crawl uselessly on the battlefield, trying to cover the archers, instead of attacking enemy units near them, and as a result, neither archers will be covered, nor damage to the enemy will be inflicted.

You're absolutely right. crawling back with a slow troop makes no sense.

Maybe defensive tactics should be initiated in the current way only if shooters could be covered within just 1 turn or all troops are faster than enemy, how do you think? Currently some troops are left alone during defending manuver and are surrounded by enemies (if they are too far away) which brings higher losses. I personally expect only faster troops to run back in a case troops are located not close enough (1+ turn to reach shooters).

I believe that if AI could consider troops' speed deciding to run back only if his troops are faster, than enemy, would reduce losses in average sutuation. If it's possible to implement for sure.
In a case there's at least one strong enough enemy troop that is faster than defending army, he can reach the shooter and spoil the whole maneuver. I believe turning to defensive tactics in the middle of the battle and rushing shooters while facing same or higher speed enemies is not a proper choice. (It's obvious on my video with golems) Ideally the mutual location of enemy and defending troops should be considered too, so defending troops could reach shooters faster, than enemies.

Uncontrolled turning into defensive tactics just allows enemy to attack defending troops on their way back to shooters, focus those 1-2 stacks, that decided not to protect shooters and also attack shooters in this case, as there'is not enough stacks to cover shooters.

So I propose to shorten the distance all troops can step back and defend shooters. I mean allow defensive tactics only if troops can reach shooters in 1 turn. This would bring benefit in 99% of cases. On longer distances the battle outcomes become not stable. For instance, if one stack gets one additional move due to morale triggered, it will die with the current logic. :)
Or we can think (now or in the future) about more complex logic. For instance: for fast-ultrafast troops allow 3 turns to come back, 2 turns for very slow-average speed stacks. Or set distance individually for each speed.
Or allow defensive behaviour for those troops, that are located on a certain part of the battlefield (as an idea).

One more small remark:

I think turning into attacking state could be done when the army threat is not so overwhelming:

2023-12-16.16-32-44.mp4

Opposite army is significantly stronger, I have just a few mummies but AI continues defending archers. I don't know the exact value, but I suggest rising it for a few %. But it's just my personal feeling. How do you think, @oleg-derevenetz ? Anyway, this could be done later.

When testing battles, please do not limit yourself to AI vs AI battles only.

Sure, but improving logic in AI vs AI battles would make the enemy heroes more effective on the adventure map. They would get less losses in simple battles against neutral monsters allowing to perform better against human and other players without wasting too much troops on early stages of the game. So this PR is supposed to make AI controlled heroes perform better agains neutral monsters -> make AI a better rival for other players on the map. :)

A couple of curious cases I found while testing your PR. Not sure what's going on here, but if everthing goes according to the proposed logic - I apologise for bothering.

2023-12-17.12-47-05.mp4

2 of 3 swordsmen decided to run back (to protect rangers probably from flier). I find it not really effective, since rangers don't have much chances to survive here, especially considering the fact, that swordsmen need 2 turns to reach them. But why only two stacks of swordsmen decided to run back? I suppose one of them later decided to proceed attacking, because rangers became too weak after the losses. And the second stack decided to attack flier just because Gargoyles were in attacking range and swordsmen considered them as a proper target. Correct me, if I'm wrong.
Anyway, the whole situation with running back looks kind of strange.

2023-12-19.15-19-35.mp4

There is no space around shooters for 1 stack of swordsmen. So they just stayed still until minotaurs came closer. But minotaurs were definitely in attack radius of swordsmen earlier. So swordsmen just skipped 1 turn, while they could attack. I expect them to continu attacking or coming closer to the rest of defending stacks.

@oleg-derevenetz
Copy link
Collaborator Author

oleg-derevenetz commented Dec 22, 2023

This logic is quite straightforward, but sometimes results of the battle really suffer from such approach:

In your video, Swordsmen are moving forward to attack the Mummies because of that 15% limit in strength (proposed by you by the way :)). Archers' strength in your setup is 163.8 and total army strength is 1109, so archers' strength is 14.77% of total army strength which is less than 15%. If we add just one archer to the group of 12 archers (and turn it to the group of 13 archers) we will see the results like the following:

result

because in this case Swordsmen initially move to defend archers instead of trying to attack during the first few turns, and I cannot break to the archers even when controlling the Mummies manually because archers are properly defended. As you can see, there is another corner case appeared due to this 15% archer's strength threshold.

Trying to defend shooters with insufficient troop stack number makes no sense. It even makes worse, since followed enemy troops can reach shooters.

They will always try to reach shooters - especially if they are properly controlled by the human player (AI-controlled units are too stupid for this at the moment, so it is not indicative). And it always makes sense to protect the shooters by placing the units protecting them close to the shooters, because at least this does not allow the enemy to completely surround the shooters, and slows down the elimination of the shooters, and if the units protecting the shooters are powerful enough, then the shooters can even shoot a couple of times, because fast, but relatively weak blocking stack (e.g. some weak flying unit like Sprites) will be quickly eliminated. Going on the attack in this sense may help against a (currently) blunt enemy AI, that may be relatively easily distracted from shooters, but the human player will not be tricked by this, he will just bypass the nearby attacking units and surround the shooters.

Currently some troops are left alone during defending manuver and are surrounded by enemies (if they are too far away) which brings higher losses.

In your video these units just should not go on the offense just so that after the loss of a pair of swordsmen, the strength of the archers would become more than 15% of the total strength of the army and they would have been forced to go into defensive mode. This is just another example of typical corner case. As I demonstrated above, if we add just one archer to our army, then Swordsmen just act correctly from the very beginning (by covering the archers) and suffer minimal losses. In the master branch, there is no that 15% threshold and Swordsmen will go on the defensive from the very beginning, even with 12 archers.

Maybe defensive tactics should be initiated in the current way only if shooters could be covered within just 1 turn

1 turn is not enough. For example, in such (very typical) case:

fheroes2.engine.version_.1.0.10.2023-12-22.05-09-08.mp4

not all Swordsmen manage to cover archers in one turn, but that doesn't mean it's not worth doing.

I believe turning to defensive tactics in the middle of the battle and rushing shooters while facing same or higher speed enemies is not a proper choice.

OK, let's stick to the following logic for now: if melee unit is already in the enemy half of the battlefield, it will just always choose the offensive tactics and therefore will never retreat to defend the archers. Such logic was already present, it was just that the protection of archers was a higher priority, now it is not the case.

But why only two stacks of swordsmen decided to run back?

That's because during the turn of the first Swordsmen stack, one Swordsmen died from the retaliation attack. Relative strength of Archers is now above the 15% of total strength, so defending Archers is now a priority, so other two stacks are moved to protect them.

There is no space around shooters for 1 stack of swordsmen. So they just stayed still until minotaurs came closer. But minotaurs were definitely in attack radius of swordsmen earlier. So swordsmen just skipped 1 turn, while they could attack. I expect them to continu attacking or coming closer to the rest of defending stacks.

When defensive tactics is chosen, "free" units (who are not busy covering the archers) will attack targets only from their half of the battlefield (to not move too far away from friendly archers or not to leave the castle walls in case of defending a castle). There is nothing new in this regard in comparison with the master branch:

fheroes2.engine.version_.1.0.10.2023-12-22.04-34-45.mp4

In this video from the master branch 27 Swordsmen are standing exactly in the middle of the battlefield, but do not attack the Minotaurs because they are on the "enemy half" of the battlefield.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Clang-Tidy found issue(s) with the introduced code (1/1)

src/fheroes2/ai/normal/ai_normal_battle.cpp Outdated Show resolved Hide resolved
@Branikolog
Copy link
Collaborator

Branikolog commented Dec 22, 2023

@oleg-derevenetz

In your video, Swordsmen are moving forward to attack the Mummies because of that 15% limit in strength (proposed by you by the way :)). Archers' strength in your setup is 163.8 and total army strength is 1109, so archers' strength is 14.77% of total army strength which is less than 15%. If we add just one archer to the group of 12 archers (and turn it to the group of 13 archers) we will see the results like the following:

The problem here is not in the % :)
In the battle of swordsmen+1ranger vs mummies there're two cases, when outcome is good (we lost about 6-8 swordsmen): when swordsmen initially protect shooters and when all stacks step back to protect shooters. But there's one case, when one swordsmen stack had advanced too much (usually because of high morale triggered) and left alone against all mummies stacks, while other stacks run away. In this case e have much more losses: 13-15 of 30 swordsmen, while in two cases mentioned before the losses are ~8. It's 45% vs 25% of lost swordsmen (I even didn't count shooters losses which occur when swordsmen partially go back for defending.).

In your video these units just should not go on the offense just so that after the loss of a pair of swordsmen, the strength of the archers would become more than 15% of the total strength of the army and they would have been forced to go into defensive mode. This is just another example of typical corner case. As I demonstrated above, if we add just one archer to our army, then Swordsmen just act correctly from the very beginning (by covering the archers) and suffer minimal losses. In the master branch, there is no that 15% threshold and Swordsmen will go on the defensive from the very beginning, even with 12 archers.

There would always be situations, when melee troops eliminated partially and the overall army strength becomes weak enough to initiate defending tactics. The problem here is not in choosing right % of shooters strength to initiate this tactics, but in the fact, that AI tend to leave 1 stack sacrificing against the whole enemy army which is not optimal in most cases. I cannot tell it's a corner case. You can set up the same battle conditions (with leadership preferably) and just try to make 10 instant battles, where 2 would be with much higher losses.

They will always try to reach shooters - especially if they are properly controlled by the human player (AI-controlled units are too stupid for this at the moment, so it is not indicative). And it always makes sense to protect the shooters by placing the units protecting them close to the shooters, because at least this does not allow the enemy to completely surround the shooters, and slows down the elimination of the shooters, and if the units protecting the shooters are powerful enough, then the shooters can even shoot a couple of times, because fast, but relatively weak blocking stack (e.g. some weak flying unit like Sprites) will be quickly eliminated.

Sure, but defending the shooters is effective only if they are under a threat. In a case our melee troops fighting enemy melee troops in some distant area of battlefield, and enemy troops are slow enough not to reach shooters in a single turn - enemy troops have to just walk without attacking, which gives a huge advantage to our army and therefore is unlikely behaviour in many situations. So I consider initiating a defensive tactics in such situation only if our melee troops are fast enough to step back without being attack by enemies during the maneuver.

In the master branch, there is no that 15% threshold and Swordsmen will go on the defensive from the very beginning, even with 12 archers.

Indeed and this is a huge improvement! 🙏

OK, let's stick to the following logic for now: if melee unit is already in the enemy half of the battlefield, it will just always choose the offensive tactics and therefore will never retreat to defend the archers. Such logic was already present, it was just that the protection of archers was a higher priority, now it is not the case.

👍

That's because during the turn of the first Swordsmen stack, one Swordsmen died from the retaliation attack. Relative strength of Archers is now above the 15% of total strength, so defending Archers is now a priority, so other two stacks are moved to protect them.

Got it!
So always being offensive on enemy side of the battlefield would solve the problem I believe.

When defensive tactics is chosen, "free" units (who are not busy covering the archers) will attack targets only from their half of the battlefield (to not move too far away from friendly archers or not to leave the castle walls in case of defending a castle). There is nothing new in this regard in comparison with the master branch:

Maybe we should allow attack within movement range in such situations? I suppose it's out of the scope of this PR, right?

Made some tests. Well... Sometimes the results still not really good...
image

But such outcomes appear not so often.

So, troops are not turning into defensive if they are on the enemy side, am I correct?

2023-12-22.15-08-56.mp4

If I eliminate a significant part of swordsmen, they are still not defending nearby shooters.

@oleg-derevenetz
Copy link
Collaborator Author

oleg-derevenetz commented Dec 22, 2023

@Branikolog

The problem here is not in the % :)

The problem here is that offensive tactics were initially chosen, when in fact defensive tactics should have been chosen initially. And the offensive tactics were chosen because the strength ratio between the archers and the entire army as a whole was slightly below 15%. When another coefficient of such kind is introduced, there is always a risk of some boundary value that will cause the logic to be unstable, like in this case: we lose just a single swordsman and the logic changes.

The problem here is not in choosing right % of shooters strength to initiate this tactics, but in the fact, that AI tend to leave 1 stack sacrificing against the whole enemy army which is not optimal in most cases. I cannot tell it's a corner case. You can set up the same battle conditions (with leadership preferably) and just try to make 10 instant battles, where 2 would be with much higher losses.

Again, what exactly do you suggest? Whatever the limit on the number of turns for which it is necessary to reach the archers to cover them, there will always be someone who will not keep up with it - for example, due to the morale event. In the previous comment, for instance, you suggested limiting the number of turns to reach archers to one. You do understand that in this case there will be those who will have time to retreat and those who will not have time, right? And the same thing will happen in the case of two turns limit, three turns limit, and so on. A restriction that enables defensive tactics only for units in our half of the battlefield also does not solve this problem 100%, because it may happen that some units will be in our half of the battlefield (and they will retreat to cover the archers), and some will be in the enemy's half (and they will not retreat).

Made some tests. Well... Sometimes the results still not really good...

They can't always be good (see above).

So, troops are not turning into defensive if they are on the enemy side, am I correct?

Yes.

If I eliminate a significant part of swordsmen, they are still not defending nearby shooters.

In your video defending hero has attack spell which counts as "shooter strength" as well, so yes, enemy here is considered as having the superiority in shooters, and if enemy has superiority in shooters, then defensive tactics will not be chosen.

@oleg-derevenetz
Copy link
Collaborator Author

oleg-derevenetz commented Dec 22, 2023

Maybe we should allow attack within movement range in such situations? I suppose it's out of the scope of this PR, right?

Yes, first of all, it's out of scope of this PR, and second, if we allow attack the enemy targets on the enemy half of the battlefield within the movement range, units may get too far away from their archers and fail to help them when the units covering them are attacked or even completely destroyed. Not to mention that they won't be able to choose defensive tactics while on the opposite side of the battlefield - due to the latest changes in this PR. But they still can attack enemies on their own half of the battlefield, of course.

@oleg-derevenetz
Copy link
Collaborator Author

oleg-derevenetz commented Dec 22, 2023

Current implementation of the AI defensive tactics in the master branch does not defend archers at all. I have no idea how anything can be "worse" than this:

fheroes2.engine.version_.1.0.10.2023-12-22.22-18-09.mp4

And I should note that this is not some corner case like that "special" arrangement "on the edge" in which the death of one unit changes tactics, no. In the master branch AI always behaves like that. It just regularly leaves its archers without cover. This may work against AI because AI will not run without attacking as I did, but will be distracted by a swordsman running forward. But I just ran by, even though my mummies couldn't reach the archers in 1 turn, and this tactic actually works great. Archers are blocked, no damage is done.

The same army deals with these 50 Mummies just fine with proper management. This is AI management from this PR:

fheroes2.engine.version_.1.0.10.2023-12-22.22-23-23.mp4

and this is my manual management "in the style of this PR":

fheroes2.engine.version_.1.0.10.2023-12-22.22-15-46.mp4

Manual management gave slightly better results, but it's just a pure luck (Mummies just did a little less damage overall due to the randomness of this process).

@Branikolog
Copy link
Collaborator

@oleg-derevenetz

In your video defending hero has attack spell which counts as "shooter strength" as well, so yes, enemy here is considered as having the superiority in shooters, and if enemy has superiority in shooters, then defensive tactics will not be chosen.

Oh, forgot about this AI logic. My apologies!

A restriction that enables defensive tactics only for units in our half of the battlefield also does not solve this problem 100%, because it may happen that some units will be in our half of the battlefield (and they will retreat to cover the archers), and some will be in the enemy's half (and they will not retreat).

You're right. But after some tests I came up with a conclusion, that it works better in average: such logic reduces the chance of initiating defensive tactics and pretty fast swordsmen tend to continue their attacks all together on the second half on a battlefield. I mean cases, when a single stack was left alone against the whole enemy army occur not so often now.

To reduce the chance of leaving a single troop on the second half of a battlefield some additional rules could be implemented later or some day. :)
For instance, if >=50% of melee troops turned into shooter-defensive state, all troops, that faster than enemy should also run away, even if they need more than 2 turns to reach shooters or if they are located on the second half of the battlefield.

From my observations:
Sometimes slow troops tend to stay on the first half of the battlefield guarding shooters, while there's completely no threat for them. (I think we still need to have some sort of logic "Attack all" and "Defend all" to prevent some troops from chilling near shooters or sacrificing 1 stack on the second part of the battlefield, while others are coming back).
Sometimes during a fight on that edge case of equal strength of shooters and melee troops, AI decides to protect shooters placing one stack right near them, while there're lots of enemies nearby wasting that stack for nothing. But this maneuver occurs not really often.

For now, as a starting point we can merge this PR.
Sorry for such a long response. I really appreciate your work on this improtant logic.

@oleg-derevenetz
Copy link
Collaborator Author

oleg-derevenetz commented Dec 22, 2023

Sometimes slow troops tend to stay on the first half of the battlefield guarding shooters, while there's completely no threat for them.

It's probably not that they're "tend to stay", they simply do not have time to get anywhere until, due to the losses of faster units, the shooters' strength begins to exceed those 15% of the total army strength, so they go on the defensive. Regarding "no threat", hm... since they are slow, it makes no sense for them to attack the other half of the battlefield, because the faster enemy units will simply bypass them (again, I'm talking about proper human-controlled behavior, not AI behavior that doesn't like "run without attacking" and often suffers unnecessary losses because of this) and block/destroy the archers first, and then these slow units.

@Branikolog
Copy link
Collaborator

@oleg-derevenetz

It's probably not that they're "tend to stay", they simply do not have time to get anywhere until, due to the losses of faster units, the shooters' strength begins to exceed those 15% of the total army strength, so they go on the defensive. Regarding "no threat", hm... since they are slow, it makes no sense for them to attack the other half of the battlefield, because the faster enemy units will simply bypass them (again, I'm talking about proper human-controlled behavior, not AI behavior that doesn't like "run without attacking" and often suffers unnecessary losses because of this) and block/destroy the archers first, and then these slow units.

Sure, it is definitely a corner case, which cannot be handled by simple logic. And yes, in a case enemy has faster troops, he can bypass slow golems standing near rangers.
I hope some day we can work on such corner cases ad teach AI maneuvering to rush enemy from a certain angle not to be bypassed, or leave shooters and come to help the rest of army fighting far away from defended shooters, or step forward to meet the approaching enemies, in a case it's just single stack which cannot cover shooters well.

@oleg-derevenetz
Copy link
Collaborator Author

oleg-derevenetz commented Dec 22, 2023

leave shooters and come to help the rest of army fighting far away from defended shooters

They can do it now as well. If enemy melee units will suffer losses in such a way that enemy army will have an excess of archers and a lack of units to cover them (more specifically, more than 2/3 of the enemy army strength will be made up of archers) AI will go offensive, even if it has superiority in archers. And of course it will go offensive with a large overall advantage of his army over the enemy.

or step forward to meet the approaching enemies

That's what AI in master branch currently do :) In fact, I consider such behavior harmful. Maybe it looks "cool", but in fact such "step forward" brings nothing but additional problems, because it just leaves an extra cell near friendly archers free for an extra enemy unit, but gives no real benefit.

@Branikolog
Copy link
Collaborator

@oleg-derevenetz

That's what AI in master branch currently do :) In fact, I consider such behavior harmful. Maybe it looks "cool", but in fact such "step forward" brings nothing but additional problems, because it just leaves an extra cell near friendly archers free for an extra enemy unit, but gives no real benefit.

Sure, if we have a sufficient number of stacks to cover shooters, then keeping the position is the best tactics.

But if there's only one stack to cover shooters, I don't see any reasons to stay near in such situation:

image

They can do it now as well. If enemy melee units will suffer losses in such a way that enemy army will have an excess of archers and a lack of units to cover them (more specifically, more than 2/3 of the enemy army strength will be made up of archers) AI will go offensive, even if it has superiority in archers. And of course it will go offensive with a large overall advantage of his army over the enemy.

image

In this situation Golems just stayed for a 4-5 turns until swordsmen were eliminated. Mummies barely had a chance to reach rangers, so involving golems into a battle from a certain angle would help to minimize the losses.

But the first thing I wish to be added after this PR will be merged is attacking of defending troops while keeping the position, as currently they only can retaliate or switch to attacking state, when enemies are almost finished.

@oleg-derevenetz
Copy link
Collaborator Author

oleg-derevenetz commented Dec 22, 2023

But if there's only one stack to cover shooters, I don't see any reasons to stay near in such situation:

But why actually run forward here? First, the golems occupy a cell near the shooters, which otherwise would simply be occupied by the enemy. Secondly, by occupying this particular cell, golems may force enemy golems to go one cell further in order to reach the archers, which can result in an extra turn, during which the shooters can have time to make an extra shot.

Mummies barely had a chance to reach rangers

:))) There is no problem for Mummies to reach Rangers in this case at all. All that prevents them is a dumb red AI that allows archers to freely shoot a crowd of mummies from afar. A human player would definitely try to block the shooters, especially if the blue AI would not cover them. Consider:

fheroes2.engine.version_.1.0.10.2023-12-23.02-32-06.mp4

I was not able to 100% reproduce the setup due to the obstacle, but I think you get the idea.

But the first thing I wish to be added after this PR will be merged is attacking of defending troops while keeping the position, as currently they only can retaliate

Without attacking, they will last more turns (maybe even twice as long). I have already demonstrated this effect in one of the comments here before. This is the whole point of defensive tactics: do not get into trouble, do not incur unnecessary losses, let the shooters do all the work.

Here are two videos for comparison where the attacker is guaranteed to win. In the first video, I control the army manually and force the Swordsmen to attack:

fheroes2.engine.version_.1.0.10.2023-12-23.01-29-49.mp4

All Mummies are dead, 12 Swordsmen are lost. In the next video, I let the AI perform the defense (no attacks, just retaliation):

fheroes2.engine.version_.1.0.10.2023-12-23.01-31-53.mp4

All Mummies are dead and... 12 Swordsmen are lost, just like in the first video. Then why introduce another coefficient that needs to be carefully selected and take into account various corner cases - to avoid a situation in which defenders simply won't be able to hold out long enough if they attack - just like in this case:

fheroes2.engine.version_.1.0.10.2023-12-23.01-46-26.mp4

? BTW, here is another video when AI uses "non-attacking" defensive tactics, with the same initial setup and much better results:

fheroes2.engine.version_.1.0.10.2023-12-23.01-44-47.mp4

Why, Mr. Anderson? Why, why, why? So that people won't complain about "unnatural behavior" or "bug"? Honestly, I don't care much about that. I don't think this is a top priority task. IMHO what really worth to be thought out (in regard to the further improvement of the defensive tactics I mean) is more fast (in fewer turns) and maybe more optimal (with fewer units) cover for archers.

@ihhub ihhub merged commit cbd5147 into ihhub:master Dec 23, 2023
20 checks passed
@ihhub
Copy link
Owner

ihhub commented Dec 23, 2023

@oleg-derevenetz , thank you so much for these changes and patience with @Branikolog !

@oleg-derevenetz oleg-derevenetz deleted the defensive-tactics-improvement branch December 23, 2023 07:18
Kazhuu pushed a commit to Kazhuu/fheroes2 that referenced this pull request Jan 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
AI Artificial intelligence behaviour high priority Very critical change needed immediately improvement New feature, request or improvement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Combat, AI skips turns of several troops in a row
3 participants