# Attack/Defence formula: ensure defender wins a certain percentage of the time

I have two simple stats for my attack formula, attack and defence.

I was reading through this answer which made it seem like using percentages for formula leads to a scaling nightmare, so I quite like using the damage = att * att / (att + def) formula.

However, I want there to be an advantage to defenders, but not for defenders to win every time. So I could have something like +5 bonus points to defenders like so

Attacker attack = 50
Defender defence = 55


which will put the fight in favour of the defender but they will win every time. How can I write a formula that will swing things in the way of the defender? Base stats I'm looking at is for defenders to win 55% of the time. Should there be a "crit chance" for defenders?

How does this then scale? If I wanted there to be a 2% increased win chance when equipping a level 1 weapon, how would I go about plotting this?

Below I've attempted to use random numbers and have managed to get the win rate to 55% at base values. But as I'm not a mathematician I can't figure out how to get this to scale when I introduce items that could increase attack or defence. Is there a graph that I can plot rather than having to guess values (as I trial and error'd until I got -/+0.7)?

import random

# Same as above, but use a coefficient to modify attack values
class BattleSimulationCoeff:
def __init__(self, attack_attacker, attack_defender, defence_attacker, defence_defender, attacker_attack_coeff_max, defender_attack_coeff_max, num_battles=200_000):
self.attack_attacker = attack_attacker
self.attack_defender = attack_defender
self.defence_attacker = defence_attacker
self.defence_defender = defence_defender
self.attacker_hp = 100
self.defender_hp = 100
self.attacker_attack_coeff_max = attacker_attack_coeff_max
self.defender_attack_coeff_max = defender_attack_coeff_max
# Run the simulation 200,000 times by default
self.num_battles = num_battles

# Use a coefficient to modify the attack value in favour of the defender
def attack_value(self, attack, defence, coeff):
return coeff * (attack + attack) / (attack + defence)

# Number of times you want to run the battle simulation
def run_battles(self):
attacker_wins = 0
defender_wins = 0

for _ in range(self.num_battles):
attacker_hp_simulation = self.attacker_hp
defender_hp_simulation = self.defender_hp

while attacker_hp_simulation > 0 and defender_hp_simulation > 0:
# Randomly distribute the value per attack
# Favour the defender by modifying the attacker's attack value to be a multiplication
# of a random number between 0 and self.attacker_attack_coeff_max
attacker_attack_coeff = random.uniform(0, self.attacker_attack_coeff_max)
# Favour the defender by modifying the attacker's attack value to be a random
# number between 0 and self.defender_attack_coeff_max
defender_attack_coeff = random.uniform(0, self.defender_attack_coeff_max)
# Attacker is attacking
defender_hp_simulation -= self.attack_value(self.attack_attacker, self.defence_defender, attacker_attack_coeff)
# Defender is attacking
attacker_hp_simulation -= self.attack_value(self.attack_defender, self.defence_attacker, defender_attack_coeff)

if attacker_hp_simulation < defender_hp_simulation:
defender_wins += 1
else:
attacker_wins += 1

attacker_win_rate = (attacker_wins / self.num_battles) * 100
defender_win_rate = (defender_wins / self.num_battles) * 100
print(f"Attacker won {attacker_wins} times and defender won {defender_wins} times.")
print(f"Attacker win chance: {attacker_win_rate}%")
print(f"Defender win chance: {defender_win_rate}%")
return defender_win_rate


I get 55% in favour of the defender running with:

bs = BattleSimulationCoeff(
50.0,
50.0,
50.0,
50.0,
attacker_attack_coeff_max=0.992,
defender_attack_coeff_max=1)
bs.run_battles()


I get 60% in favour of the defender with:

bs = BattleSimulationCoeff(
attack_attacker=50.0,
# +0.7 attack to defender
attack_defender=50.7,
defence_attacker=50.0,
defence_defender=50.0,
attacker_attack_coeff_max=0.992,
defender_attack_coeff_max=1,
num_battles=100_000)
bs.run_battles()


I get 50% with

bs = BattleSimulationCoeff(
# +0.7 attack to attacker
attack_attacker=50.7,
attack_defender=50.0,
defence_attacker=50.0,
defence_defender=50.0,
attacker_attack_coeff_max=0.992,
defender_attack_coeff_max=1,
num_battles=100_000)
bs.run_battles()


Changing each player's values but having them equal also gave me 55%

bs = BattleSimulationCoeff(
attack_attacker=65.7,
attack_defender=65.7,
defence_attacker=65.7,
defence_defender=65.7,
attacker_attack_coeff_max=0.992,
defender_attack_coeff_max=1,
num_battles=100_000)
bs.run_battles()

• Are you meaning that you don't use random numbers at all? May 27, 2022 at 11:43
• How does it look if instead of having your flat defence used each damage calcucaltion, it can fluctate in a range of X. So instead of always 55, it could be a range between 55 and 60. May 27, 2022 at 12:00
• Thanks for the comments both. I've edited the OP with a colab which I hope makes my attempts using random numbers clear. I'm not sure how to get the chance exactly to 55% although adding random numbers does make sense. May 27, 2022 at 14:06
• Hello, can you please paste your code directly in the post? Some browsers don't support colab(such as mine) : ) May 27, 2022 at 14:42
• Added the code! Thanks for taking a look May 28, 2022 at 10:50