# Hearthstone fatigue calculator

I made a simple Hearthstone fatigue calculator. For those who don't know, Hearthstone is a card game in which every turn you draw a card from your deck. Once you're out of cards, you start taking fatigue damage, getting 1 more damage each turn. The total damage dealt in X turns is known as the Triangular Number Sequence: 1 (1), 3 (1+2), 6 (1+2+3), 10 (1+2+3+4). For example, at turn 4 the fatigue damage has already dealt 10 dmg (1+2+3+4). This could be used for some decks that make you run out of cards, to calculate how much damage they need to kill the opponent or to just calculate how much damage X fatigue turns will do.

I just wanted to know how my code looks. I know it's a very simple program but I might be doing something wrong and I guess it's better to know as early as possible. Besides, code reviewing a long project is just too boring for everyone. I've been learning by myself and as nobody ever reads my code I might be doing terrible things without knowing it haha

def error():
print("Whoops, something went wrong. Please try again.")

def calc_mode(t, s):
d = 0
for i in range(s, t+s):
d = d + i
return d

def lethal_mode(d_input, s):
d_calc = 0
i = 1
t = 0
while d_calc < d_input:
d_calc = d_calc + i
i += 1
t += 1
return t

while True:  # Checks if the mode input is valid
try:
mode = int(input("\nMODES: "
"\n* '1' to get the turns needed for X damage "
"\n* '2' to get the damage dealt in X turns "
except ValueError:
error()
continue
else:
while True:
if mode == 1:  # Checks if the input after the mode is valid
while True:
try:
damageInput = int(input("\nPlease input the damage needed for lethal: "))
startInput = int(input("Starting at how much damage? (default is 1): "))
except ValueError:
error()
continue
else:

print("\n>> You will need %d fatigue turns for that sweet lethal"
% lethal_mode(damageInput, startInput))
continue

if mode == 2:
while True:
try:
turnsInput = int(input("\nPlease input how many fatigue turns: "))
startInput = int(input("Starting at how much damage? (default is 1): "))
except ValueError:
error()
continue
else:
print("\n>> It will deal %d damage" % calc_mode(turnsInput, startInput))
continue

else:
error()
break


What do you think overall? Thanks!

• What's your Python version? – Mast Jul 1 '18 at 20:14
• It's the 3.6.3 @Mast – Mario Ortiz Jul 1 '18 at 21:33

# The math

There is a useful formula for triangle numbers that vastly simplifies a lot of the code here:

$$\sum_{k=1}^n k = \frac{n(n+1)}{2}$$

So for instance, the first triangle number is $1$, the second $1 + 2 = 3$, the third $1 + 2 + 3 = 6$ and so fourth. Compare that to the Hearthstone fatigue counter.

# The interface

## The main function

In a lot of programming languages there is the concept of a main function. This is commonly emulated in Python with:

def main():
...

if __name__ == '__main__':
main()


See this answer for more details. In your case, you should probably have your main() consist of the top level while True statement i.e.:

def main():
while True:
...


## if statements

It is rather unfortunate that Python doesn't have switch-case statements which provide a very nice way to match against mode. In this case I would recommend usage of tuple (or dictionary) to handle the modes. To illustrate:

def needed():
while True:
try:
damageInput = int(input("\nPlease input the damage needed for lethal: "))
startInput = int(input("Starting at how much damage? (default is 1): "))
...

def dealt():
while True:
try:
turnsInput = int(input("\nPlease input how many fatigue turns: "))
startInput = int(input("Starting at how much damage? (default is 1): "))
...

MODES = (needed, dealt)

def main():
while True:
...
MODES[mode]()


## Default dictionary?

This takes a bit more work, but you probably want to invest some time into learning what defaultdict is. Default dictionaries give you the power of dictionaries in Python with the ability to avoid raising KeyErrors. You logic right now heavily relies on capturing bad input and handling the exceptions yourself. With a default dict, you:

1. don't need to convert anything to an integer, just use string keys. This prevents any sort of conversion error.
2. set the default value for the dictionary to error. In other words, MODES["SOME_GARBAGE_VALUE"]() will act like error().
3. Add another value to MODES which allows for an exit.

Dair mentioned already many important points, so I'll try not to overlap with him.

## Style

Have a look at PEP8 and some other style guides. There are tools to automatically check it: https://martin-thoma.com/python-style-guide/

## Names

Try to use descriptive variable and function names. Single letter variables are bad for two reasons:

1. It's hard to grep for them
2. They don't carry any meaning

The only reason for single letter variable names are indices (usually i). Often, indices are not needed but one can iterate over the thing directly.

## Doctests

I think the code is simple enough that you could easily add Doctests: https://docs.python.org/3/library/doctest.html

## Code complexity

The while loop looks as if its really could be improved. You could check is McCabe complexity as a guide if you should refactor it. For example, you could give the two modes their own function