Making a Dungeons and Dragons Barbarian Calculator with Python 3 (or how to over-engineer a solution to a nonexistent problem)

Tutorial hell - we’ve all been there. Follow the article - get to the desired result. What comes next? Well for me - Project #1 was cementing my knowledge in how while, and if loops interact.What better way to do that than my making a dice roller for the stereotypically dumb, but surprisingly complex Dungeons and Dragons 5e class of the Barbarian?While there is probably a pretty big cross over in the Venn diagram of (Knows/is learning Python) and (Plays DnD) lets first look at the  ruleset to understand what we need to accomplish with this dice roller before turning it into pseudo-code, and then making it in Python 3.This calculator will be used specifically for the combat aspect of Dungeons and Dragons. 

Combat flows like this:

  • Attacker checks to see if their attack roll is with either disadvantage (roll twice, pick lowest), advantage (roll twice, pick highest), or straight (roll once).
  • Attacker rolls a 20 sided die (d20), applies a set of bonuses and checks to see if that number is HIGHER than the defenders armour class.
  • If it is - the attacker will then roll weapon damage (this damage type depends on the weapon used), and add any applicable bonuses for the roll.Repeat until victory (or defeat).

Barbarians are different to most martial (fighting) classes, in that their bonuses are more likely to come into play during a fight. They have a way to always trigger advantage (attacking recklessly), and have an extra bonus to apply to their damage if raging (a finite action).The other information we need is their character level, their strength modifier (how strong they are), and their rage bonus.So! For a Barbarian: The ‘roll to hit’ maths is equal to dice roll + proficiency bonus + strength modifier, and needs to account for rolling with advantage, disadvantage, or as a straight roll.The ‘attack damage’ maths is equal to their weapon type + strength modifier + rage bonus if applicable.My first Dungeons and Dragons character ever was a half-elf Barbarian named Malachi with a noble background. We’ll use him (and DnDBeyond) to make sure our math is looking right. Malachi was level 3, had a strength modifier of +4, a proficiency bonus of +2, and a rage bonus of +2.In pseudo code our calculator will look something like this:We’ll need to import two modules to make our lives easier.The first module - math is imported so that we can round numbers up or down (Dungeons and Dragons only works in whole numbers).The second module is d20, this will handle our dice for us.With the modules imported, time to start at the top of our pseudo code - capturing the variables needed for later math (we’ll swing back around to defining our functions after this).With the while loop we can execute a set of statements as long as a condition is true. So we’ll use this for capturing the character info, repeating the loop if it’s incorrect, and breaking the loop if it is correct.Here’s my code as an example:Breaking this code down.A while loop will only execute when a condition is true, so we set the condition (charInfo) = True. Riveting. We want this looping as many times as the user requires to enter the information correctly. If we wanted to give them x chances, we could code this as: We want to give the user as many attempts as needed, so we’ll leave it as True, and break the loop if we tell it.Next step - Capture the info (pcLevel, PcStrMod, and rageBonus) - proficiency we can work out (and use math to raise up!). These are all numbers, and we’ll need them as numbers for later math so we capture them as integers. We validate the input through giving print statements, and check for a text (string, or str) input response from the user to make sure this is all correct - using .lower() to convert the input into all lower case. Once we have the users response there are three expected outcomes - either the data is correct, the data is incorrect or the input is not recognised.An if/elif/else statement covers these options nicely. The attack loop will look pretty similar to this - also using a while statement. In pseudo code:and for how that translated into Python 3:Going back to the pseudo code - I had originally had the order of check if raging, check if advantage/disadvantage, roll to hit, roll damage.I realised that this would be better serve pairing the modifiers (rage/no rage and advantage/disadvantage) with the dice rolls related to them (attack damage, and roll to hit respectfully).Let's start by creating our 'roll to hit' function, because we'll use this for all the weapons.Whats happening here? First step is finding if the user has advantage, disadvantage or if it is a normal role. Similar to getting the character info, we’ll capture this with a string input and use that for our if/elif/else loop.In Dungeons and Dragons if you get a natural twenty (die = 20), or a natural one (die = 1) the behaviour changes slightly. For results on the die 2-19 the behaviour is the change. As such, time for some basic maths to check if the randomised dice roll = 20, or 1 and if so to print a message to let you know.Repeat this for advantage/disadvantage/normal rolls, and leave an else statement for unexpected input and this piece of the puzzle is done. So far - so good. Finally we get to the weapon damage calculation, and where the bulk of the time in the program will be spent. This is where we find out if we are raging or not, and then calculate damage with the relevant bonuses. As raging/not raging applies different bonuses to the damage, it is easier to make two separate functions to call on. They act fundamentally the same so we can just explore one. Because we want to keep this loop going forever, we’re back to our old while tricks. We call upon our previously created rollToHit function, roll our weapon damage, and loop this until the combat is over and we choose to exit. Rage will typically last for an entire combat, so we don’t need to validate it every round - however we do need to check if each attack roll has advantage or otherwise.Repeat this and change the maths to create a function for greatsword damage without the rage bonus, and for any other barbarian fitting weapons and you’ll be slaying Dragons in no time.Altogether we've made some nice interchangeable pieces. When we run the focus of the program, the code is easy to read and follow - always the idea.All in all we are sitting round one hundred and fifty lines of code that could be drastically reduced and optimised further. (however, saving <10 seconds per dice roll means that spending time doing this becomes increasingly less beneficial). Putting it into practice looks something like this.We're using Malachi to make sure our math all checks out - and with a 25 for our first hit not even an Ancient Red Dragon is safe. Not bad for level three.Let Python do the math for you, so you can focus on things important to a barbarian - drinking ale, and finding a bigger monster to slay. 

Previous
Previous

SSH vs RDP with Ubuntu and Windows

Next
Next

Ad-free internet with a (headless) Raspberry Pi