1
\$\begingroup\$

I have recently made a small program to generate D&D (original edition) monster stat blocks in text form. I use this program myself to generate stats block so that I can then copy and paste them in the adventures I am writing.

The main reason for using a program is to have the program roll the random values.

Here is an example of usage:

monsterGenerator:monster("Kobold", 3).

Here is an example of output:

Kobold(3), AC 7, HD 1/2, HP (1, 4, 2), Att 1 weapon, Dmg (Club 1d3-1, Dagger 1d4-1, Dagger 1d4-1),
Save Normal Man, Morale 6, Treasure (2 cp, 12 cp, 21 cp), XP 5

Here is the code in question:

monsterGenerator.erl

-module(monsterGenerator).
-export([monster/2]).

monster(Name, Number) -> case Name of
                            "Bugbear" -> io:format("~s(~p), AC 5, HD 3+1, HP ~p, Att 1 weapon, Dmg ~s +1 ~n", 
                                                  [Name, Number, [monster_hp(3, 1) || _  <- lists:seq(1, Number)], 
                                                         [monsterWeapons:generate_weapon(large) || _ <- lists:seq(1, Number)]]),
                                        io:format("Save Normal Man, Morale 6, Treasure ~s ~s, XP 50 ~n",
                                                  [monsterTreasures:generate_treasure(p), monsterTreasures:generate_treasure(q)]);  

                            "Kobold" -> io:format("~s(~p), AC 7, HD 1/2, HP ~p, Att 1 weapon, Dmg ~s -1 ~n", 
                                                  [Name, Number, [monster_hp(0.5) || _  <- lists:seq(1, Number)], 
                                                         [monsterWeapons:generate_weapon(small) || _ <- lists:seq(1, Number)]]),
                                        io:format("Save Normal Man, Morale 6, Treasure ~s, XP 5 ~n",
                                                  [monsterTreasures:generate_treasure(p)]);         

                            "Skeleton" -> io:format("~s(~p), AC 7, HD 1, HP ~p, Att 1 weapon, Dmg Claws 1d2 ~n", 
                                                  [Name, Number, [monster_hp(1) || _  <- lists:seq(1, Number)]]), 
                                          io:format("Save F1, Morale 12, Treasure ~s, XP 10 ~n",
                                                  [monsterTreasures:generate_treasure(nil)]);       

                            "Goblin" -> io:format("~s(~p), AC 6, HD 1-1, HP ~p, Att 1 weapon, Dmg ~s ~n", 
                                                  [Name, Number, [monster_hp(1, -1) || _  <- lists:seq(1, Number)], 
                                                         [monsterWeapons:generate_weapon(small) || _ <- lists:seq(1, Number)]]),
                                        io:format("Save Normal Man, Morale 6, Treasure ~s, XP 5 ~n",
                                                  [[monsterTreasures:generate_treasure(r) || _ <- lists:seq(1, Number)]]);  

                            "Centaur" -> io:format("~s(~p), AC 5, HD 4, HP ~p, Att 2 hooves / 1 weapon, Dmg 1d6/1d6/by weapon ~s ~n", 
                                                  [Name, Number, [monster_hp(4) || _  <- lists:seq(1, Number)], 
                                                         [monsterWeapons:generate_weapon(medium) || _ <- lists:seq(1, Number)]]),
                                        io:format("Save Normal F4, Morale 8, Treasure ~s, XP 75 ~n",
                                                  [monsterTreasures:generate_treasure(nil)]);       

                            "Neanderthal" -> io:format("~s(~p), AC 8, HD 2, HP ~p, Att 1 weapon ~s ~n", 
                                                  [Name, Number, [monster_hp(2) || _  <- lists:seq(1, Number)], 
                                                         [monsterWeapons:generate_weapon(primitive) || _ <- lists:seq(1, Number)]]),
                                        io:format("Save Normal F2, Morale 8, Treasure ~s, XP 20 ~n",
                                                  [monsterTreasures:generate_treasure(nil)]);   

                            "Harpy" -> io:format("~s(~p), AC 7, HD 3, HP ~p, Att 3 2 claws / 1 weapon 1d4/1d4/ ~s ~n", 
                                                  [Name, Number, [monster_hp(3) || _  <- lists:seq(1, Number)], 
                                                         [monsterWeapons:generate_weapon(medium) || _ <- lists:seq(1, Number)]]),
                                        io:format("Save Normal F2, Morale 8, Treasure ~s, XP 20 ~n",
                                                  [monsterTreasures:generate_treasure(nil)]);           

                            _ ->        io:format("Unknown.~n")     
                         end.

monster_hp(0.5) -> random:uniform(round(4));
monster_hp(HitDice) -> lists:foldl(fun(X, Sum) -> X + Sum end, 0, [random:uniform(round(8)) || _  <- lists:seq(1, HitDice)]).
monster_hp(HitDice, Modifier) -> monster_hp(HitDice) + Modifier.

monsterTreasures.erl

-module(monsterTreasures).
-export([generate_treasure/1]).

generate_treasure(Type) -> case Type of
                            p -> [integer_to_list(roll_dice(3, 8)), " cp"];
                            q -> [integer_to_list(roll_dice(3, 6)), " sp"];
                            r -> [integer_to_list(roll_dice(2, 6)), " ep"];
                            s -> [integer_to_list(roll_dice(2, 4)), " gp", conditional_treasure(5, 1, "gems")];
                            t -> [integer_to_list(roll_dice(1, 6)), " pp", conditional_treasure(5, 1, "gems")];
                            u -> [conditional_treasure(10, 100, "cp "), conditional_treasure(10, 100, "sp "),
                                  conditional_treasure(5, 100, "gp "), conditional_treasure(5, 2, "gems "),
                                  conditional_treasure(5, 4, "jewelry "), conditional_treasure(2, 1, "special treasure "),
                                  conditional_treasure(2, 1, "magical items ")];
                            v -> [conditional_treasure(10, 100, "sp "), conditional_treasure(5, 100, "ep "), 
                                  conditional_treasure(10, 100, "gp "), conditional_treasure(5, 100, "pp "),
                                  conditional_treasure(10, 2, "gems "), conditional_treasure(10, 4, "jewelry "),
                                  conditional_treasure(5, 1, "special treasure "), conditional_treasure(5, 1, "magical items ")];
                            _ -> ["No treasure"]
                          end.

conditional_treasure(Chance, Number, TreasureName) -> Result = random:uniform(100), 
                                                      if Result =< Chance -> 
                                                                  [integer_to_list(random:uniform(Number)), " ", TreasureName];
                                                         true -> ""
                                                      end.

roll_dice(Number, DiceType) -> lists:foldl(fun(_, Sum) -> random:uniform(DiceType) + Sum end, 0, lists:seq(1, Number

monsterWeapons.erl

-module(monsterWeapons).
-export([generate_weapon/1]).

generate_weapon(Type) when Type == small -> Weapons = ["Hand Axe 1d6 ", "Blackjack 1d2 ", "Torch 1d4 ", "Dagger 1d4 ",
                                                      "Short Sword 1d6 ", "Small club 1d2 ", "Sling 1d4 "],
                                                       pick_random_from_list(Weapons);

generate_weapon(Type) when Type == medium -> Weapons = ["Battle Axe 1d8 ", "Club 1d4 ", "War Hammer 1d6 ", "Mace 1d6 ",
                                                       "Staff 1d6 ", "Trident 1d6 ", "Normal Sword 1d8 ", "Whip 1d2 ",
                                                       "Bow, Short 1d6 ", "Crossbow, Lt 1d6 ", "Throwing Hammer 1d4 ",
                                                       "Shield, Sword 1d4 +2 "],
                                                       pick_random_from_list(Weapons);

generate_weapon(Type) when Type == primitive -> Weapons = ["Blackjack 1d2 ", "Sling 1d4 ", "Small club 1d2 ", 
                                                          "Staff 1d6 ", "Club 1d4 ", "Stone Mace 1d6 ",
                                                          "Stone Spear 1d6 "],
                                                         pick_random_from_list(Weapons);

generate_weapon(Type) when Type == large -> Weapons = ["Battle Axe 1d8 ", "Halberd 1d10 ", "Polearm 1d10 ", 
                                                      "Two-Handed Sword 1d10 ", "Normal Sword 1d8 ", "Club 1d4 "],
                                                      pick_random_from_list(Weapons).

pick_random_from_list(List) -> Index = random:uniform(length(List)),
                               lists:nth(Index, List).

I welcome review on any aspect of the code. Note that I am a novice with Erlang.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Erlang does pattern-matching, so anywhere you do

function_name(Arg) -> 
    case Arg of foo [foo code]
                bar [bar code]
                etc
    end.

or similar, you can instead do

function_name(foo) -> [foo code];
function_name(bar) -> [bar code];
etc.

This works for any data type, so you can apply it to each of your functions;

monster("Bugbear", Number) -> ...

generate_treasure(p) -> ...

generate_weapon(small) -> ...

A good fit for RPG stat blocks would be Erlang's records. ETS/DETS/Mnesia seems like a much better way in general to keep this data around than writing it all out in code. Specifically, you could store weapons as

-record(weapon, {name, damage}).

and monsters as

-record(monster, {name, ac, hit_dice, hp_mod, attacks, weapon_type, damage, save, type, morale, treasure_type, xp}).

Your monster two-liners are all very similar. You can probably abstract that out into a separate function, especially if you go the data route as I've suggested.

Something like

print_monster(#monster{name=Name, ac=AC, hit_dice=Hit, hp_mod=Mod, type=Type,
               attacks=Attacks, weapon_type=Weap, damage=Damage, 
               save=Save, morale=Morale, treasure_type=T, xp=XP}, 
          Number) ->
    io:format("~s(~p), AC ~s, HD ~s~s, HP ~p, Att ~s, Dmg ~s ~n",
          [Name, AC, Hit, Mod, [monster_hp(Hit, Mod) || _  <- lists:seq(1, Number)], Attacks, 
           [monsterWeapons:generate_weapon(Weap) || _ <- lists:seq(1, Number)]]),
    io:format("Save ~s ~a, Morale ~s, Treasure (~s), XP ~s ~n",
          [Save, Type, Morale, lists:map(monsterTreasures:generate_treasure/1, T)]).

You can pass a literal list to pick_random_from_list, so your generate_weapon clauses can be significantly simplified;

generate_weapon(small) -> 
    pick_random_from_list(["Hand Axe 1d6 ", "Blackjack 1d2 ", "Torch 1d4 ",
                           "Dagger 1d4 ", "Short Sword 1d6 ", "Small club 1d2 ", 
                           "Sling 1d4 "]);
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.