Guest Post #2 – Nathan’s Magic Missile D&D spell in Python

Paul: Today we have a guest post by my friend Nathan Dibley. It's Magic Missile code in Python for all you D&D fans!

Nathan: Hi there. This is a D&D (5th edition) Magic Missile spell in Python, cast the spell with one die, or a die for each dart, depending on your persuasion.
 
Why? A wizard overheard some friends shooting-the-breeze about whether Magic Missile should be cast with one D4 and the result applied to each dart, or if a D4 should be rolled for each dart and result per die rolled applied to each dart. This gave the wizard an itch to scratch and so he wrote some Python code.
 
For those not familiar and left utterly confused, see a description of the spell here.
 
Disclaimer, this is code, not magic. Unlike magic, there may be bugs and silly things lurking!
 
The code consists of two Python scripts. The first (magic_missile.py) contains the main logic for the spell and the second (cast_magic_missile.py) is used to cast it.
 

You can find all the source code on GitHub here.

magic_missile.py

import random
 
class MagicMissile:
 
    def __init__(self, spell_slot_lvl, spell_mode):
        try:
            self.lvl = int(spell_slot_lvl)
        except:
            raise TypeError(“spell_slot_level should be an integer”)
        if spell_mode == “roll_die” or spell_mode == “roll_dice”:
            self.mode = spell_mode
        else:
            raise Exception(“spell_mode should be ‘roll_die’, or ‘roll_dice'”)
 
    def _dart_num(self):
        if self.lvl == 0:
            print(“You clearly have no magic ability,\
 and are utterly weak”)
            exit()
        elif self.lvl == 1:
            return 3
        else:
            bonus = self.lvl – 1
            return (3 + bonus)
 
    def _attack_damage(self):
        for x in range(1):
            return random.randint(1, 4)
 
    def _damage_roll_die(self):
        dart_num = self._dart_num()
        base_damage = self._attack_damage()
        damage_per_dart = (base_damage + 1)
        total_damage = damage_per_dart * dart_num
        return { “darts_fired”: dart_num,
                 “base_damage”: base_damage,
                 “damage_per_dart”: damage_per_dart,
                 “total_damage”: total_damage  }
 
    def _damage_roll_dice(self):
        dart_num = self._dart_num()
        base_damage_per_dart = {}
        total_damage_per_dart = {}
        for dart in range(dart_num):
            damage = self._attack_damage()
            base_damage_per_dart[“dart_{}”.format(dart + 1)]\
                = (damage)
            total_damage_per_dart[“dart_{}”.format(dart + 1)]\
                = (damage + 1)
        total_damage = sum(total_damage_per_dart.values())
        return { “darts_fired”: dart_num,
                 “base_damage_by_dart”: base_damage_per_dart,
                 “total_damage_by_dart”: total_damage_per_dart,
                 “total_damage_all_darts”: total_damage }
 
    def cast(self):
        if self.mode == “roll_die”:
            return self._damage_roll_die()
        elif self.mode == “roll_dice”:
            return self._damage_roll_dice()
 

cast_magic_missile.py

#!/bin/env python3.9
 
import sys
from magic_missile import MagicMissile
 
def usage():
     print(“Usage: cast_magic_missile.py “)
     print(”     spell-slot level must be an integer”)
     print(”     spell-mode either ‘roll_die’ or ‘roll_dice'”)
     print(”       roll_die, rolls a 1d4 once, and result applied to each dart”)
     print(”       roll_dice, rolls a 1d4 for each dart”)
     exit()
 
def main(argv):
    if len(argv) == 2:
        spell_slot_lvl = argv[0]
        spell_mode = argv[1]
        try:
            mm_spell = MagicMissile(spell_slot_lvl, spell_mode)
            for item, value in mm_spell.cast().items():
                print(“{}: {}”.format(item, value))
        except Exception as e:
            print(“Error casting MagicMissile, {}”.format(e))
            usage()
    else:
            usage()
 
if __name__ == ‘__main__’:
 
    main(sys.argv[1:])

Some examples of use

[[email protected] magic-missile]$ ./cast_magic_missile.py
Usage: cast_magic_missile.py
     spell-slot level must be an integer
     spell-mode either ‘roll_die’ or ‘roll_dice’
       roll_die, rolls a 1d4 once, and result applied to each dart
       roll_dice, rolls a 1d4 for each dart
[[email protected] magic-missile]$
 
[email protected] magic-missile]$ ./cast_magic_missile.py 0 roll_die
You clearly have no magic ability, and are utterly weak
[[email protected] magic-missile]$
 
[[email protected] magic-missile]$ ./cast_magic_missile.py 1 roll_die
darts_fired: 3
base_damage: 1
damage_per_dart: 2
total_damage: 6
[[email protected] magic-missile]$
 
[[email protected] magic-missile]$ ./cast_magic_missile.py 3 roll_dice
darts_fired: 5
base_damage_by_dart: {‘dart_1’: 4, ‘dart_2’: 2, ‘dart_3’: 3, ‘dart_4’: 2, ‘dart_5’: 4}
total_damage_by_dart: {‘dart_1’: 5, ‘dart_2’: 3, ‘dart_3’: 4, ‘dart_4’: 3, ‘dart_5’: 5}
total_damage_all_darts: 20
[[email protected] magic-missile]$
 
So there you have it. All ready for Saturday night’s D&D session!

What code have you written for fun? Let me know in the comments below. And if you need a Unix wizard, you can find Nathan here.

If you enjoyed this article, sign up for my newsletter!

1-2 emails per month with articles like this, Japan IT job news, articles on tech, life in Japan, and more.

Leave a comment