## 010 combat

 ← step010 ↑ →

#### theory

I hope that working with different functions is no problem for you … you will need it now. The Goblin Dice Duel has this pretty displayed attack and devense variables …. it is time to use them for damage calculating. If you look at (computer) role playing games you will find plenty of rulesets for combat mechanics in games .. from the painfully simple to the extreme complex. Because role-playing games are far older than computer games, those rulesets often refer to dice throwing, using either 6-sided dice or 20 side dice among others.

Many computer games simply build up on those already etablished and tested rulesets, replacing the actual dice throwing with functions like the `random.randint()` function.

In this step i will introduce a slightly more complex combat mechanik ruleset than the pure damage “dice throwing” used until now:

#### ruleset

I will use those rules or instructions to calculate damage and combat:

1. The ruleset simulate melee combat, a alternating series of strikes and counterstrikes.
2. from both combatants, the computer selects per random one to strike first
3. The attacking does a strike against the defender
4. The attacker rolls a die ( `random.randint()` ) and adds his attack stat value.
5. The defender rolls a die and adds his defense stat value.
6. Both values are compared ( (attack + attackRoll) vs. (defense + defenseRoll) )
7. If the defender has a higher or equal value then the attacker, the attacker failed with his attack and the calculation is continues at counter-strike, where attacker and defender switch roles
8. If the defender has a lower value both values are substracted and the result is the damage:
1. `damage = (attack + attackRoll) - ( defense + defenseRoll)`
9. This damage is substracted from the defenders hitpoints.
10. If the defender has hitpoints left, he is able to counterstrike:
11. The counterstrike works like the strike, only that the roles of attacker and defender are switched.

Note that this ruleset says nothing specific about the dice to use for the attack/defense rolls and nothing about the range of the values for attack and defense (and hitpoints). It is up to you to figure out working ranges and fine-tune this model according to your needs. Do you want long, epic battles between monsters with tons of hitpoints ? Or short, brutal duels like in wild west shootings ? Should luck ( random ) play a bigger role than experience ( stats ) ?

#### strike function

This strike and that counterstrike function…. that is one and the same function with different arguments !

My generic strike function according to the rules above would looks like that:

```def strike(attA, defB, hpB, counter=False):
"""A strikes B. The function returns the new hpB and a text String with the combat report"""
striketext = "" # new text to append to the big text
if counter:
t = "counterattack"
else:
t = "attack"
rollAtt = random.randint(1,6)
rollDef = random.randint(1,6)
scoreA = attA + rollAtt
scoreD = defB + rollDef
if scoreA > scoreD:
striketext = "Sucessfull {} !  ({} > {})".format(t, scoreA,scoreD)
damage = scoreA - scoreD
hpB -= damage
striketext += "\n...doing {} damage.".format(damage)
else:
striketext = "The {} failed... ({} <= {})".format(t, scoreA, scoreD)
return hpB, striketext```

As you can see, the `strike()` function takes 3 or 4 arguments: attackStat of the attacker, defenseStat of the defender, hitpoints of the defender, and a boolean variable indication if this is a counterstrike.

The `strike()` function creates a local (remember the scope!) variable names `t` and some other local variables for the damage calculation. Finally, the `strike()` functon returns 2 (!) values: the (possible lowered) defenders hitpoints and a textstring.

#### calling strike and counterstrike

who calls a strike ? According to the ruleset, the computer decide by random if Stinky or Grunty is the “firststriker”. This -unfortunately- leads to some lines of identical code… at the moment.

``` while hitpointsStinky >0 and hitpointsGrunty > 0:
text+=output(combatround, hitpointsStinky, hitpointsGrunty)
combatround += 1
if random.randint(0,1) == 0:
text+="\nStinky strikes first: "
hitpointsGrunty, t = strike(attackStinky, defenseGrunty, hitpointsGrunty, False)
text+=t
if hitpointsGrunty > 0:
text+="\nCounterstrike of Grunty: "
hitpointsStinky, t = strike(attackGrunty, defenseStinky, hitpointsStinky, True)
text+=t
else:
text+="\nGrunty strikes first: "
hitpointsStinky, t = strike(attackGrunty, defenseStinky, hitpointsStinky, False)
text+=t
if hitpointsStinky>0:
text+="\nCounterstrike of Stinky: "
hitpointsGrunty, t = strike(attackStinky, defenseGrunty, hitpointsGrunty, True)
text+=t```

Please note the boolean operator `and` in the while loop. That means both conditions must be `True` or the python interpreter will go to the lines after the `while` loop. `And` is a python keyword, as well as `not` and `or`.

The `strike()` function is called so that the return values (both of them) are assigned to variables ( `hitpoints` and `t`.

Do you notice that the `strike()` as well as the `compareValues()` function both take a lot of arguments ? Not very comfortable, there must be a more elegant solution…. in the next chapter. Meanwhile, have a go at balancing the stats of Grunty and Stinky so that both have nearly the same chance of winning:

#### full source code

click reload on your browser if you see no source code here or visit Github.com

#### online

You can try out this example online..up to a point. The problem is that the online tutor website breaks a program if it loops more than 300 times. I changed the stats of Stinky and Grunty a bit for the online version so that you are less likely to run into errors. Try for yourself, use the “edit code” function to change the combat stats. Just click on the graphic:

You will notice that i deleted not only some now no longer necessary code lines but that i also removed several print lines. The output of the game is now very verbose:

#### output

``` --- Goblin Dice Duel ---

Stiny | vs. | Grunty
---------------+-----+-----------
hitpoints:  25 |  <  |  43
attack:      6 |  <  |  10
defense:     9 |  >  |   3

==== combat start ====
---combat round  0--- Stinky: 25 Grunty: 43
Grunty strikes first: Sucessfull attack !  (12 > 10)
...doing 2 damage.
Counterstrike of Stinky: Sucessfull counterattack !  (10 > 4)
...doing 6 damage.
---combat round  1--- Stinky: 23 Grunty: 37
Stinky strikes first: Sucessfull attack !  (10 > 9)
...doing 1 damage.
Counterstrike of Grunty: The counterattack failed... (13 <= 14)
---combat round  2--- Stinky: 23 Grunty: 36
Grunty strikes first: Sucessfull attack !  (14 > 10)
...doing 4 damage.
Counterstrike of Stinky: Sucessfull counterattack !  (12 > 5)
...doing 7 damage.
---combat round  3--- Stinky: 19 Grunty: 29
Stinky strikes first: The attack failed... (8 <= 9)
Counterstrike of Grunty: Sucessfull counterattack !  (16 > 11)
...doing 5 damage.
---combat round  4--- Stinky: 14 Grunty: 29
Stinky strikes first: Sucessfull attack !  (9 > 7)
...doing 2 damage.
Counterstrike of Grunty: Sucessfull counterattack !  (14 > 10)
...doing 4 damage.
---combat round  5--- Stinky: 10 Grunty: 27
Grunty strikes first: The attack failed... (13 <= 13)
Counterstrike of Stinky: Sucessfull counterattack !  (11 > 5)
...doing 6 damage.
---combat round  6--- Stinky: 10 Grunty: 21
Grunty strikes first: Sucessfull attack !  (16 > 15)
...doing 1 damage.
Counterstrike of Stinky: Sucessfull counterattack !  (8 > 6)
...doing 2 damage.
---combat round  7--- Stinky:  9 Grunty: 19
Stinky strikes first: Sucessfull attack !  (7 > 5)
...doing 2 damage.
Counterstrike of Grunty: Sucessfull counterattack !  (15 > 12)
...doing 3 damage.
---combat round  8--- Stinky:  6 Grunty: 17
Grunty strikes first: Sucessfull attack !  (16 > 14)
...doing 2 damage.
Counterstrike of Stinky: Sucessfull counterattack !  (8 > 4)
...doing 4 damage.
---combat round  9--- Stinky:  4 Grunty: 13
Grunty strikes first: Sucessfull attack !  (16 > 12)
...doing 4 damage.
---combat round 10--- Stinky:  0 Grunty: 13
Game Over
Grunty wins```

| | step010 | |