diff --git a/mvkroller.py b/mvkroller.py index e383d49..71d6d2d 100644 --- a/mvkroller.py +++ b/mvkroller.py @@ -74,7 +74,7 @@ def parse_dice(dicestr: str): return dicecounts -def roll_dice(dicecounts, cheat=False): +def roll_dice(dicecounts): """Returns a dictionary of dieSize => rolls[]""" dicerolls = { 20: [], @@ -88,11 +88,7 @@ def roll_dice(dicecounts, cheat=False): try: for size, num in dicecounts.items(): if num > 0: - # pylint: disable=unused-variable - if cheat: - dicerolls[size] = [size for idx in range(0, num)] - else: - dicerolls[size] = [random.randint(1, size) for idx in range(0, num)] + dicerolls[size] = [random.randint(1, size) for idx in range(0, num)] except Exception as exc: raise RollError("Exception while rolling dice.") from exc @@ -136,16 +132,71 @@ def adv_disadv(advantage, disadvantage, dicecounts, dicerolls): except Exception as exc: raise RollError("Coding error calculating advantage or disadvantage.") from exc + return answer, dicerolls[20] + + +def calc_action(fortunedicerolls, characterdicerolls): + """Compute the action total, using up to one d20 and the highest character die roll.""" + try: + action_dice = fortunedicerolls + characterdicerolls + action_dice.sort(reverse=True) + action_dice = action_dice[:2] + answer = f"**Action Total: {str(sum(action_dice))}** {str(action_dice)}\n" + except Exception as exc: + raise RollError( + "Coding error flattening dice rolls and creating total." + ) from exc return answer +def calc_impact(fortunedicerolls, characterdicerolls): + """Calculate the impact total""" + try: + # die results of 10 or higher on a d10 or 12 give two impact. It doesn't happen on a d20. + fortuneimpact = 1 if fortunedicerolls[0] >= 4 else 0 + doublecharacterimpact = sum(2 for p in characterdicerolls if p >= 10) + characterimpact = sum(1 for p in characterdicerolls if 4 <= p < 10) + impact = fortuneimpact + doublecharacterimpact + characterimpact + impact = max(impact, 1) + answer = f"**Impact: {impact}** " + answer += ( + f"(fortune={fortuneimpact} 2x={doublecharacterimpact} 1x={characterimpact})" + ) + except Exception as exc: + raise RollError("Coding error calculating Impact") from exc + return answer + + +def crit_fumble(fortunedicerolls, characterdicerolls): + """Check if we had a critical fumble. If so, add output and discard lowest non-1 die""" + answer = "" + newdicerolls = characterdicerolls + if fortunedicerolls[0] == 1: + answer += "**Critical Fumble**\n" + characterdicerolls.sort() + newdicerolls = [] + scratched = False + for i in characterdicerolls: + if scratched: + newdicerolls.append(i) + elif i == 1: + newdicerolls.append(i) + else: + scratched = True + answer += f"*Scratched {i}*\n" + # no append because scratching this die + + answer += "**Gain 1 inspiration point**\n" + answer += f"New character dice: {newdicerolls}\n" + return answer, newdicerolls + + def mvkroll(dicestr: str): """Implementation of dice roller that applies MvK rules.""" logger.debug("Roll %s", {dicestr}) answer = "" - cheat = False advantage = False disadvantage = False @@ -154,9 +205,6 @@ def mvkroll(dicestr: str): elif re.search(r"advantage", dicestr, flags=re.IGNORECASE): advantage = True - if re.search(r"cheat", dicestr, flags=re.IGNORECASE): - cheat = True - dicecounts = parse_dice(dicestr) # advantage and disadvantage need _at least_ 2d20 @@ -169,7 +217,7 @@ def mvkroll(dicestr: str): dicecounts[20] = 1 answer += "_No advantage/disadvantage, setting 1d20_\n" - dicerolls = roll_dice(dicecounts, cheat) + dicerolls = roll_dice(dicecounts) # the d20 is called the "Fortune Die" fortunedicerolls = dicerolls[20] @@ -187,35 +235,21 @@ def mvkroll(dicestr: str): if len(characterdicerolls) + len(fortunedicerolls) < 1: raise RollError("Not enough dice to roll") - answer += adv_disadv(advantage, disadvantage, dicecounts, dicerolls) + adv_disadv_answer, fortunedicerolls = adv_disadv( + advantage, disadvantage, dicecounts, dicerolls + ) + answer += adv_disadv_answer answer += print_dice(dicerolls) - # Compute the action total, using up to one d20 and the highest character die roll. - try: - action_dice = fortunedicerolls[:1] + characterdicerolls[:1] - answer += f"**Action Total: {str(sum(action_dice))}** {str(action_dice)}\n" - except Exception as exc: - raise RollError( - "Coding error flattening dice rolls and creating total." - ) from exc + fumble_answer, characterdicerolls = crit_fumble( + fortunedicerolls, characterdicerolls + ) + answer += fumble_answer - try: - # die results of 10 or higher on a d10 or 12 give two impact. It doesn't happen on a d20. - fortuneimpact = 1 if fortunedicerolls[0] >= 4 else 0 - doublecharacterimpact = sum(2 for p in characterdicerolls if p >= 10) - characterimpact = sum(1 for p in characterdicerolls if 4 <= p < 10) - impact = fortuneimpact + doublecharacterimpact + characterimpact - impact = max(impact, 1) - answer += f"**Impact: {impact}** " - answer += ( - f"(fortune={fortuneimpact} 2x={doublecharacterimpact} 1x={characterimpact})" - ) - except Exception as exc: - raise RollError("Coding error calculating Impact") from exc + answer += calc_action(fortunedicerolls, characterdicerolls) - if cheat: - answer = "\n# Cheating" + answer + "\n# Cheating" + answer += calc_impact(fortunedicerolls, characterdicerolls) return answer @@ -226,10 +260,9 @@ def plainroll(dicestr: str): logger.debug("Roll %s", {dicestr}) answer = "" - cheat = False dicecounts = parse_dice(dicestr) - dicerolls = roll_dice(dicecounts, cheat) + dicerolls = roll_dice(dicecounts) answer += print_dice(dicerolls)