From 03e419b6175600640181404d786b056817650da9 Mon Sep 17 00:00:00 2001 From: Maartz Date: Tue, 1 Apr 2025 20:42:17 +0200 Subject: [PATCH 1/7] make docs --- .../dnd-character/.docs/instructions.md | 32 +++++++++++++++++++ .../dnd-character/.docs/introduction.md | 10 ++++++ 2 files changed, 42 insertions(+) create mode 100644 exercises/practice/dnd-character/.docs/instructions.md create mode 100644 exercises/practice/dnd-character/.docs/introduction.md diff --git a/exercises/practice/dnd-character/.docs/instructions.md b/exercises/practice/dnd-character/.docs/instructions.md new file mode 100644 index 00000000..e14e7949 --- /dev/null +++ b/exercises/practice/dnd-character/.docs/instructions.md @@ -0,0 +1,32 @@ +# Instructions + +For a game of [Dungeons & Dragons][dnd], each player starts by generating a character they can play with. +This character has, among other things, six abilities; strength, dexterity, constitution, intelligence, wisdom and charisma. +These six abilities have scores that are determined randomly. +You do this by rolling four 6-sided dice and recording the sum of the largest three dice. +You do this six times, once for each ability. + +Your character's initial hitpoints are 10 + your character's constitution modifier. +You find your character's constitution modifier by subtracting 10 from your character's constitution, divide by 2 and round down. + +Write a random character generator that follows the above rules. + +For example, the six throws of four dice may look like: + +- 5, 3, 1, 6: You discard the 1 and sum 5 + 3 + 6 = 14, which you assign to strength. +- 3, 2, 5, 3: You discard the 2 and sum 3 + 5 + 3 = 11, which you assign to dexterity. +- 1, 1, 1, 1: You discard the 1 and sum 1 + 1 + 1 = 3, which you assign to constitution. +- 2, 1, 6, 6: You discard the 1 and sum 2 + 6 + 6 = 14, which you assign to intelligence. +- 3, 5, 3, 4: You discard the 3 and sum 5 + 3 + 4 = 12, which you assign to wisdom. +- 6, 6, 6, 6: You discard the 6 and sum 6 + 6 + 6 = 18, which you assign to charisma. + +Because constitution is 3, the constitution modifier is -4 and the hitpoints are 6. + +~~~~exercism/note +Most programming languages feature (pseudo-)random generators, but few programming languages are designed to roll dice. +One such language is [Troll][troll]. + +[troll]: https://di.ku.dk/Ansatte/?pure=da%2Fpublications%2Ftroll-a-language-for-specifying-dicerolls(84a45ff0-068b-11df-825d-000ea68e967b)%2Fexport.html +~~~~ + +[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons diff --git a/exercises/practice/dnd-character/.docs/introduction.md b/exercises/practice/dnd-character/.docs/introduction.md new file mode 100644 index 00000000..5301f618 --- /dev/null +++ b/exercises/practice/dnd-character/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +After weeks of anticipation, you and your friends get together for your very first game of [Dungeons & Dragons][dnd] (D&D). +Since this is the first session of the game, each player has to generate a character to play with. +The character's abilities are determined by rolling 6-sided dice, but where _are_ the dice? +With a shock, you realize that your friends are waiting for _you_ to produce the dice; after all it was your idea to play D&D! +Panicking, you realize you forgot to bring the dice, which would mean no D&D game. +As you have some basic coding skills, you quickly come up with a solution: you'll write a program to simulate dice rolls. + +[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons From aad7c64a173bc7b73d45b48097d8582f3a2b8349 Mon Sep 17 00:00:00 2001 From: Maartz Date: Tue, 1 Apr 2025 20:42:33 +0200 Subject: [PATCH 2/7] make metadata --- .../practice/dnd-character/.meta/config.json | 20 ++++++ .../practice/dnd-character/.meta/example.erl | 45 ++++++++++++ .../practice/dnd-character/.meta/tests.toml | 72 +++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 exercises/practice/dnd-character/.meta/config.json create mode 100644 exercises/practice/dnd-character/.meta/example.erl create mode 100644 exercises/practice/dnd-character/.meta/tests.toml diff --git a/exercises/practice/dnd-character/.meta/config.json b/exercises/practice/dnd-character/.meta/config.json new file mode 100644 index 00000000..c22662a3 --- /dev/null +++ b/exercises/practice/dnd-character/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [""], + "contributors": [ + "Maartz" + ], + "files": { + "solution": [ + "src/dnd_character.erl" + ], + "test": [ + "test/dnd_character_tests.erl" + ], + "example": [ + ".meta/example.erl" + ] + }, + "blurb": "Randomly generate Dungeons & Dragons characters.", + "source": "Simon Shine, Erik Schierboom", + "source_url": "https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945" +} diff --git a/exercises/practice/dnd-character/.meta/example.erl b/exercises/practice/dnd-character/.meta/example.erl new file mode 100644 index 00000000..fa514520 --- /dev/null +++ b/exercises/practice/dnd-character/.meta/example.erl @@ -0,0 +1,45 @@ +-module(example). + +-export([modifier/1, ability/0, character/0]). + +-record(?MODULE, { + strength, + dexterity, + constitution, + intelligence, + wisdom, + charisma, + hitpoints +}). + +-export_type([character/0]). +-type character() :: #?MODULE{}. + +%% @doc Calculate the ability modifier for a given ability score +-spec modifier(pos_integer()) -> integer(). +modifier(Score) -> + (Score - 10) div 2. + +%% @doc Generate a random ability score +-spec ability() -> pos_integer(). +ability() -> + Rolls = [rand:uniform(6) || _ <- lists:seq(1, 4)], + + SortedRolls = lists:sort(Rolls), + [_ | HighestThree] = SortedRolls, + lists:sum(HighestThree). + +%% @doc Generate a complete character with random ability scores +-spec character() -> character(). +character() -> + Constitution = ability(), + + #?MODULE{ + strength = ability(), + dexterity = ability(), + constitution = Constitution, + intelligence = ability(), + wisdom = ability(), + charisma = ability(), + hitpoints = 10 + modifier(Constitution) + }. diff --git a/exercises/practice/dnd-character/.meta/tests.toml b/exercises/practice/dnd-character/.meta/tests.toml new file mode 100644 index 00000000..719043b2 --- /dev/null +++ b/exercises/practice/dnd-character/.meta/tests.toml @@ -0,0 +1,72 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[1e9ae1dc-35bd-43ba-aa08-e4b94c20fa37] +description = "ability modifier -> ability modifier for score 3 is -4" + +[cc9bb24e-56b8-4e9e-989d-a0d1a29ebb9c] +description = "ability modifier -> ability modifier for score 4 is -3" + +[5b519fcd-6946-41ee-91fe-34b4f9808326] +description = "ability modifier -> ability modifier for score 5 is -3" + +[dc2913bd-6d7a-402e-b1e2-6d568b1cbe21] +description = "ability modifier -> ability modifier for score 6 is -2" + +[099440f5-0d66-4b1a-8a10-8f3a03cc499f] +description = "ability modifier -> ability modifier for score 7 is -2" + +[cfda6e5c-3489-42f0-b22b-4acb47084df0] +description = "ability modifier -> ability modifier for score 8 is -1" + +[c70f0507-fa7e-4228-8463-858bfbba1754] +description = "ability modifier -> ability modifier for score 9 is -1" + +[6f4e6c88-1cd9-46a0-92b8-db4a99b372f7] +description = "ability modifier -> ability modifier for score 10 is 0" + +[e00d9e5c-63c8-413f-879d-cd9be9697097] +description = "ability modifier -> ability modifier for score 11 is 0" + +[eea06f3c-8de0-45e7-9d9d-b8cab4179715] +description = "ability modifier -> ability modifier for score 12 is +1" + +[9c51f6be-db72-4af7-92ac-b293a02c0dcd] +description = "ability modifier -> ability modifier for score 13 is +1" + +[94053a5d-53b6-4efc-b669-a8b5098f7762] +description = "ability modifier -> ability modifier for score 14 is +2" + +[8c33e7ca-3f9f-4820-8ab3-65f2c9e2f0e2] +description = "ability modifier -> ability modifier for score 15 is +2" + +[c3ec871e-1791-44d0-b3cc-77e5fb4cd33d] +description = "ability modifier -> ability modifier for score 16 is +3" + +[3d053cee-2888-4616-b9fd-602a3b1efff4] +description = "ability modifier -> ability modifier for score 17 is +3" + +[bafd997a-e852-4e56-9f65-14b60261faee] +description = "ability modifier -> ability modifier for score 18 is +4" + +[4f28f19c-2e47-4453-a46a-c0d365259c14] +description = "random ability is within range" + +[385d7e72-864f-4e88-8279-81a7d75b04ad] +description = "random character is valid" + +[2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe] +description = "each ability is only calculated once" +include = false + +[dca2b2ec-f729-4551-84b9-078876bb4808] +description = "each ability is only calculated once" +reimplements = "2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe" From 19e950c119df7844ab5f2a1e7a3e81b94a87828f Mon Sep 17 00:00:00 2001 From: Maartz Date: Tue, 1 Apr 2025 20:42:41 +0200 Subject: [PATCH 3/7] update global conf --- config.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/config.json b/config.json index 3175db29..ad1b4fc9 100644 --- a/config.json +++ b/config.json @@ -1021,6 +1021,14 @@ "practices": [], "prerequisites": [], "difficulty": 5 + }, + { + "slug": "dnd-character", + "name": "D&D Character", + "uuid": "4fc39dbe-4515-40d6-a59a-2098887b2a76", + "practices": [], + "prerequisites": [], + "difficulty": 1 } ], "foregone": [ From 421b96c4340cd7698449078287b47bdfdf265cab Mon Sep 17 00:00:00 2001 From: Maartz Date: Tue, 1 Apr 2025 20:42:46 +0200 Subject: [PATCH 4/7] make the tests --- .../test/dnd_character_tests.erl | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 exercises/practice/dnd-character/test/dnd_character_tests.erl diff --git a/exercises/practice/dnd-character/test/dnd_character_tests.erl b/exercises/practice/dnd-character/test/dnd_character_tests.erl new file mode 100644 index 00000000..c1fefeff --- /dev/null +++ b/exercises/practice/dnd-character/test/dnd_character_tests.erl @@ -0,0 +1,107 @@ +-module(dnd_character_tests). + +-include_lib("erl_exercism/include/exercism.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +%% Ability modifier tests + +'1_ability_modifier_for_score_3_is_minus_4_test_'() -> + {"ability modifier for score 3 is -4", + ?_assertEqual(-4, dnd_character:modifier(3))}. + +'2_ability_modifier_for_score_4_is_minus_3_test_'() -> + {"ability modifier for score 4 is -3", + ?_assertEqual(-3, dnd_character:modifier(4))}. + +'3_ability_modifier_for_score_5_is_minus_3_test_'() -> + {"ability modifier for score 5 is -3", + ?_assertEqual(-3, dnd_character:modifier(5))}. + +'4_ability_modifier_for_score_6_is_minus_2_test_'() -> + {"ability modifier for score 6 is -2", + ?_assertEqual(-2, dnd_character:modifier(6))}. + +'5_ability_modifier_for_score_7_is_minus_2_test_'() -> + {"ability modifier for score 7 is -2", + ?_assertEqual(-2, dnd_character:modifier(7))}. + +'6_ability_modifier_for_score_8_is_minus_1_test_'() -> + {"ability modifier for score 8 is -1", + ?_assertEqual(-1, dnd_character:modifier(8))}. + +'7_ability_modifier_for_score_9_is_minus_1_test_'() -> + {"ability modifier for score 9 is -1", + ?_assertEqual(-1, dnd_character:modifier(9))}. + +'8_ability_modifier_for_score_10_is_0_test_'() -> + {"ability modifier for score 10 is 0", + ?_assertEqual(0, dnd_character:modifier(10))}. + +'9_ability_modifier_for_score_11_is_0_test_'() -> + {"ability modifier for score 11 is 0", + ?_assertEqual(0, dnd_character:modifier(11))}. + +'10_ability_modifier_for_score_12_is_plus_1_test_'() -> + {"ability modifier for score 12 is +1", + ?_assertEqual(1, dnd_character:modifier(12))}. + +'11_ability_modifier_for_score_13_is_plus_1_test_'() -> + {"ability modifier for score 13 is +1", + ?_assertEqual(1, dnd_character:modifier(13))}. + +'12_ability_modifier_for_score_14_is_plus_2_test_'() -> + {"ability modifier for score 14 is +2", + ?_assertEqual(2, dnd_character:modifier(14))}. + +'13_ability_modifier_for_score_15_is_plus_2_test_'() -> + {"ability modifier for score 15 is +2", + ?_assertEqual(2, dnd_character:modifier(15))}. + +'14_ability_modifier_for_score_16_is_plus_3_test_'() -> + {"ability modifier for score 16 is +3", + ?_assertEqual(3, dnd_character:modifier(16))}. + +'15_ability_modifier_for_score_17_is_plus_3_test_'() -> + {"ability modifier for score 17 is +3", + ?_assertEqual(3, dnd_character:modifier(17))}. + +'16_ability_modifier_for_score_18_is_plus_4_test_'() -> + {"ability modifier for score 18 is +4", + ?_assertEqual(4, dnd_character:modifier(18))}. + +%% Random ability tests + +'17_random_ability_is_within_range_test_'() -> + {"random ability is within range", + fun() -> + Results = [dnd_character:ability() || _ <- lists:seq(1, 200)], + [?assert(Result >= 3 andalso Result =< 18) || Result <- Results], + ok + end}. + +%% Random character tests + +'18_random_character_has_valid_values_test_'() -> + {"random character has each ability within range and valid hitpoints", + fun() -> + Characters = [dnd_character:character() || _ <- lists:seq(1, 20)], + + lists:foreach(fun(Character) -> + #{strength := Strength, + dexterity := Dexterity, + constitution := Constitution, + intelligence := Intelligence, + wisdom := Wisdom, + charisma := Charisma, + hitpoints := Hitpoints} = Character, + + ?assert(Strength >= 3 andalso Strength =< 18), + ?assert(Dexterity >= 3 andalso Dexterity =< 18), + ?assert(Constitution >= 3 andalso Constitution =< 18), + ?assert(Intelligence >= 3 andalso Intelligence =< 18), + ?assert(Wisdom >= 3 andalso Wisdom =< 18), + ?assert(Charisma >= 3 andalso Charisma =< 18), + ?assertEqual(10 + dnd_character:modifier(Constitution), Hitpoints) + end, Characters), + ok + end}. From d0b115e7e3695bf132de5b8b1c86555c688a5433 Mon Sep 17 00:00:00 2001 From: Maartz Date: Tue, 1 Apr 2025 20:44:44 +0200 Subject: [PATCH 5/7] stub --- exercises/practice/dnd-character/src/dnd_character.erl | 1 + 1 file changed, 1 insertion(+) create mode 100644 exercises/practice/dnd-character/src/dnd_character.erl diff --git a/exercises/practice/dnd-character/src/dnd_character.erl b/exercises/practice/dnd-character/src/dnd_character.erl new file mode 100644 index 00000000..7f3d246e --- /dev/null +++ b/exercises/practice/dnd-character/src/dnd_character.erl @@ -0,0 +1 @@ +-module(dnd_character). From d5d03ee1bf1aaecc06fc2a3a072fd4035cb7645c Mon Sep 17 00:00:00 2001 From: Maartz Date: Tue, 1 Apr 2025 20:50:05 +0200 Subject: [PATCH 6/7] configlet lint of course --- exercises/practice/dnd-character/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/dnd-character/.meta/config.json b/exercises/practice/dnd-character/.meta/config.json index c22662a3..9b896bcd 100644 --- a/exercises/practice/dnd-character/.meta/config.json +++ b/exercises/practice/dnd-character/.meta/config.json @@ -1,5 +1,5 @@ { - "authors": [""], + "authors": [], "contributors": [ "Maartz" ], From 6b8f24ec2b89f1938a7b7a3e559a35e42ba09580 Mon Sep 17 00:00:00 2001 From: Maartz Date: Tue, 1 Apr 2025 21:15:08 +0200 Subject: [PATCH 7/7] switch from record to map --- .../practice/dnd-character/.meta/example.erl | 41 +++++++------------ .../test/dnd_character_tests.erl | 11 +++-- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/exercises/practice/dnd-character/.meta/example.erl b/exercises/practice/dnd-character/.meta/example.erl index fa514520..4a142570 100644 --- a/exercises/practice/dnd-character/.meta/example.erl +++ b/exercises/practice/dnd-character/.meta/example.erl @@ -1,45 +1,34 @@ -module(example). - -export([modifier/1, ability/0, character/0]). --record(?MODULE, { - strength, - dexterity, - constitution, - intelligence, - wisdom, - charisma, - hitpoints -}). - --export_type([character/0]). --type character() :: #?MODULE{}. - %% @doc Calculate the ability modifier for a given ability score -spec modifier(pos_integer()) -> integer(). modifier(Score) -> - (Score - 10) div 2. + Diff = Score - 10, + case Diff < 0 andalso Diff rem 2 /= 0 of + true -> (Diff div 2) - 1; + false -> Diff div 2 + end. %% @doc Generate a random ability score -spec ability() -> pos_integer(). ability() -> Rolls = [rand:uniform(6) || _ <- lists:seq(1, 4)], - SortedRolls = lists:sort(Rolls), [_ | HighestThree] = SortedRolls, lists:sum(HighestThree). %% @doc Generate a complete character with random ability scores --spec character() -> character(). +-spec character() -> map(). character() -> Constitution = ability(), - - #?MODULE{ - strength = ability(), - dexterity = ability(), - constitution = Constitution, - intelligence = ability(), - wisdom = ability(), - charisma = ability(), - hitpoints = 10 + modifier(Constitution) + #{ + strength => ability(), + dexterity => ability(), + constitution => Constitution, + intelligence => ability(), + wisdom => ability(), + charisma => ability(), + hitpoints => 10 + modifier(Constitution) }. + diff --git a/exercises/practice/dnd-character/test/dnd_character_tests.erl b/exercises/practice/dnd-character/test/dnd_character_tests.erl index c1fefeff..1df03dd0 100644 --- a/exercises/practice/dnd-character/test/dnd_character_tests.erl +++ b/exercises/practice/dnd-character/test/dnd_character_tests.erl @@ -85,16 +85,18 @@ {"random character has each ability within range and valid hitpoints", fun() -> Characters = [dnd_character:character() || _ <- lists:seq(1, 20)], - + lists:foreach(fun(Character) -> - #{strength := Strength, + #{ + strength := Strength, dexterity := Dexterity, constitution := Constitution, intelligence := Intelligence, wisdom := Wisdom, charisma := Charisma, - hitpoints := Hitpoints} = Character, - + hitpoints := Hitpoints + } = Character, + ?assert(Strength >= 3 andalso Strength =< 18), ?assert(Dexterity >= 3 andalso Dexterity =< 18), ?assert(Constitution >= 3 andalso Constitution =< 18), @@ -105,3 +107,4 @@ end, Characters), ok end}. +