backlinks to this page:
Github:
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:
I will use those rules or instructions to calculate damage and combat:
random.randint()
) and adds his attack stat value. damage = (attack + attackRoll) - ( defense + defenseRoll)
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 ) ?
I'm sure your well-trained eye for Pattern_recognition and your stellar i'm-to-lazy-to-code-anything- twice brain has it already detected after reading the paragraph above:
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.
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:
click reload on your browser if you see no source code here or visit Github.com
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:
--- 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