1
\$\begingroup\$

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()
\$\endgroup\$
5
  • \$\begingroup\$ Are you meaning that you don't use random numbers at all? \$\endgroup\$
    – Vaillancourt
    May 27, 2022 at 11:43
  • \$\begingroup\$ 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. \$\endgroup\$
    – Zibelas
    May 27, 2022 at 12:00
  • \$\begingroup\$ 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. \$\endgroup\$
    – Josh Laird
    May 27, 2022 at 14:06
  • \$\begingroup\$ Hello, can you please paste your code directly in the post? Some browsers don't support colab(such as mine) : ) \$\endgroup\$
    – Mangata
    May 27, 2022 at 14:42
  • \$\begingroup\$ Added the code! Thanks for taking a look \$\endgroup\$
    – Josh Laird
    May 28, 2022 at 10:50

0

You must log in to answer this question.

Browse other questions tagged .