From fbdc253b60d64ad2f948c5d278b6c594e6d5e69b Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 13 Jan 2025 18:26:13 +0100 Subject: [PATCH] Add `dnd-character` exercise --- config.json | 10 ++ .../dnd-character/.docs/instructions.md | 32 +++++ .../dnd-character/.docs/introduction.md | 10 ++ .../practice/dnd-character/.meta/config.json | 19 +++ .../practice/dnd-character/.meta/example.clj | 19 +++ .../dnd-character/.meta/generator.clj | 19 +++ .../dnd-character/.meta/generator.tpl | 29 +++++ .../practice/dnd-character/.meta/tests.toml | 73 ++++++++++++ exercises/practice/dnd-character/deps.edn | 6 + exercises/practice/dnd-character/project.clj | 4 + .../dnd-character/src/dnd_character.clj | 19 +++ .../dnd-character/test/dnd_character_test.clj | 109 ++++++++++++++++++ 12 files changed, 349 insertions(+) create mode 100644 exercises/practice/dnd-character/.docs/instructions.md create mode 100644 exercises/practice/dnd-character/.docs/introduction.md create mode 100644 exercises/practice/dnd-character/.meta/config.json create mode 100644 exercises/practice/dnd-character/.meta/example.clj create mode 100644 exercises/practice/dnd-character/.meta/generator.clj create mode 100644 exercises/practice/dnd-character/.meta/generator.tpl create mode 100644 exercises/practice/dnd-character/.meta/tests.toml create mode 100644 exercises/practice/dnd-character/deps.edn create mode 100644 exercises/practice/dnd-character/project.clj create mode 100644 exercises/practice/dnd-character/src/dnd_character.clj create mode 100644 exercises/practice/dnd-character/test/dnd_character_test.clj diff --git a/config.json b/config.json index 24c366346..01031c468 100644 --- a/config.json +++ b/config.json @@ -1166,6 +1166,16 @@ "time" ] }, + { + "slug": "dnd-character", + "name": "D&D Character", + "uuid": "d6a7a17f-dd95-46cd-93a8-6b536b2ff4e4", + "practices": [], + "prerequisites": [ + "numbers" + ], + "difficulty": 4 + }, { "slug": "octal", "name": "Octal", diff --git a/exercises/practice/dnd-character/.docs/instructions.md b/exercises/practice/dnd-character/.docs/instructions.md new file mode 100644 index 000000000..e14e7949d --- /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 000000000..5301f6182 --- /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 diff --git a/exercises/practice/dnd-character/.meta/config.json b/exercises/practice/dnd-character/.meta/config.json new file mode 100644 index 000000000..3381c86ae --- /dev/null +++ b/exercises/practice/dnd-character/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "erikschierboom" + ], + "files": { + "solution": [ + "src/dnd_character.clj" + ], + "test": [ + "test/dnd_character_test.clj" + ], + "example": [ + ".meta/example.clj" + ] + }, + "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.clj b/exercises/practice/dnd-character/.meta/example.clj new file mode 100644 index 000000000..a4c3492a7 --- /dev/null +++ b/exercises/practice/dnd-character/.meta/example.clj @@ -0,0 +1,19 @@ +(ns dnd-character + (:require [clojure.math :refer [floor]])) + +(defn modifier [score] + (int (floor (/ (- score 10) 2)))) + +(defn- die [] (inc (rand-int 6))) + +(defn ability [] + (reduce + (rest (sort (repeatedly 4 die))))) + +(defrecord DndCharacter [strength dexterity charisma wisdom intelligence constitution hitpoints]) + +(defn character [] + (let [abilities (repeatedly 6 ability) + [strength dexterity charisma wisdom intelligence constitution] abilities + hitpoints (+ 10 (modifier constitution))] + (DndCharacter. strength dexterity charisma wisdom intelligence constitution hitpoints))) + \ No newline at end of file diff --git a/exercises/practice/dnd-character/.meta/generator.clj b/exercises/practice/dnd-character/.meta/generator.clj new file mode 100644 index 000000000..51344b6ce --- /dev/null +++ b/exercises/practice/dnd-character/.meta/generator.clj @@ -0,0 +1,19 @@ +(ns dnd-character-generator + (:require [hbs.helper :refer [safe-str]])) + +(def random-abilities [:strength :dexterity :charisma :wisdom :intelligence :constitution]) + +(defn- expand-character-test-case [test-case] + (map + (fn [ability] + (-> test-case + (update :path #(conj % (name ability))) + (assoc :ability (safe-str (str ability))))) random-abilities)) + +(defn- expand-test-case [test-case] + (if (= "character" (:property test-case)) + (expand-character-test-case test-case) + [test-case])) + +(defn transform [test-cases] + (mapcat #(expand-test-case %) test-cases)) diff --git a/exercises/practice/dnd-character/.meta/generator.tpl b/exercises/practice/dnd-character/.meta/generator.tpl new file mode 100644 index 000000000..43eb3e63f --- /dev/null +++ b/exercises/practice/dnd-character/.meta/generator.tpl @@ -0,0 +1,29 @@ +(ns dnd-character-test + (:require [clojure.test :refer [deftest testing is]] + dnd-character)) +{{#test_cases.modifier}} +(deftest modifier_test_{{idx}} + (testing {{description}} + (is (= {{expected}} (dnd-character/modifier {{input.score}}))))) +{{/test_cases.modifier}} +(deftest ability_test_1 + (testing "random ability is within range" + (is (<= 3 (dnd-character/ability) 18)))) + +(deftest ability_test_2 + (testing "ability is generated randomly" + (is (>= (count (set (repeatedly 100 #(dnd-character/ability)))) 5)))) +{{#test_cases.character}} +(deftest character_test_{{idx}} + (testing {{description}} + (is (<= 3 ({{ability}} (dnd-character/character)) 18)))) +{{/test_cases.character}} +(deftest character_test_7 + (testing "random character is valid - hitpoints" + (let [char (dnd-character/character) + expected (+ 10 (dnd-character/modifier (:constitution char)))] + (is (= expected (:hitpoints char)))))) + +(deftest character_test_8 + (testing "random character is generated randomly" + (is (>= (count (set (repeatedly 100 #(dnd-character/character)))) 5)))) diff --git a/exercises/practice/dnd-character/.meta/tests.toml b/exercises/practice/dnd-character/.meta/tests.toml new file mode 100644 index 000000000..44b3c20e0 --- /dev/null +++ b/exercises/practice/dnd-character/.meta/tests.toml @@ -0,0 +1,73 @@ +# 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" +include = false diff --git a/exercises/practice/dnd-character/deps.edn b/exercises/practice/dnd-character/deps.edn new file mode 100644 index 000000000..561c3e2da --- /dev/null +++ b/exercises/practice/dnd-character/deps.edn @@ -0,0 +1,6 @@ +{:aliases {:test {:extra-paths ["test"] + :extra-deps {io.github.cognitect-labs/test-runner + {:git/url "https://github.com/cognitect-labs/test-runner.git" + :sha "705ad25bbf0228b1c38d0244a36001c2987d7337"}} + :main-opts ["-m" "cognitect.test-runner"] + :exec-fn cognitect.test-runner.api/test}}} \ No newline at end of file diff --git a/exercises/practice/dnd-character/project.clj b/exercises/practice/dnd-character/project.clj new file mode 100644 index 000000000..f71894877 --- /dev/null +++ b/exercises/practice/dnd-character/project.clj @@ -0,0 +1,4 @@ +(defproject dnd-character "0.1.0-SNAPSHOT" + :description "dnd-character exercise." + :url "https://github.com/exercism/clojure/tree/main/exercises/practice/dnd-character" + :dependencies [[org.clojure/clojure "1.11.1"]]) diff --git a/exercises/practice/dnd-character/src/dnd_character.clj b/exercises/practice/dnd-character/src/dnd_character.clj new file mode 100644 index 000000000..c82ee0b29 --- /dev/null +++ b/exercises/practice/dnd-character/src/dnd_character.clj @@ -0,0 +1,19 @@ +(ns dnd-character) + +(defn modifier + "Calculate a score's modifier" + [score] + ;; function body + ) + +(defn ability + "Generate a random ability" + [] + ;; function body + ) + +(defn character + "Generate a random character" + [] + ;; function body + ) diff --git a/exercises/practice/dnd-character/test/dnd_character_test.clj b/exercises/practice/dnd-character/test/dnd_character_test.clj new file mode 100644 index 000000000..1b5e0d573 --- /dev/null +++ b/exercises/practice/dnd-character/test/dnd_character_test.clj @@ -0,0 +1,109 @@ +(ns dnd-character-test + (:require [clojure.test :refer [deftest testing is]] + dnd-character)) + +(deftest modifier_test_1 + (testing "ability modifier - ability modifier for score 3 is -4" + (is (= -4 (dnd-character/modifier 3))))) + +(deftest modifier_test_2 + (testing "ability modifier - ability modifier for score 4 is -3" + (is (= -3 (dnd-character/modifier 4))))) + +(deftest modifier_test_3 + (testing "ability modifier - ability modifier for score 5 is -3" + (is (= -3 (dnd-character/modifier 5))))) + +(deftest modifier_test_4 + (testing "ability modifier - ability modifier for score 6 is -2" + (is (= -2 (dnd-character/modifier 6))))) + +(deftest modifier_test_5 + (testing "ability modifier - ability modifier for score 7 is -2" + (is (= -2 (dnd-character/modifier 7))))) + +(deftest modifier_test_6 + (testing "ability modifier - ability modifier for score 8 is -1" + (is (= -1 (dnd-character/modifier 8))))) + +(deftest modifier_test_7 + (testing "ability modifier - ability modifier for score 9 is -1" + (is (= -1 (dnd-character/modifier 9))))) + +(deftest modifier_test_8 + (testing "ability modifier - ability modifier for score 10 is 0" + (is (= 0 (dnd-character/modifier 10))))) + +(deftest modifier_test_9 + (testing "ability modifier - ability modifier for score 11 is 0" + (is (= 0 (dnd-character/modifier 11))))) + +(deftest modifier_test_10 + (testing "ability modifier - ability modifier for score 12 is +1" + (is (= 1 (dnd-character/modifier 12))))) + +(deftest modifier_test_11 + (testing "ability modifier - ability modifier for score 13 is +1" + (is (= 1 (dnd-character/modifier 13))))) + +(deftest modifier_test_12 + (testing "ability modifier - ability modifier for score 14 is +2" + (is (= 2 (dnd-character/modifier 14))))) + +(deftest modifier_test_13 + (testing "ability modifier - ability modifier for score 15 is +2" + (is (= 2 (dnd-character/modifier 15))))) + +(deftest modifier_test_14 + (testing "ability modifier - ability modifier for score 16 is +3" + (is (= 3 (dnd-character/modifier 16))))) + +(deftest modifier_test_15 + (testing "ability modifier - ability modifier for score 17 is +3" + (is (= 3 (dnd-character/modifier 17))))) + +(deftest modifier_test_16 + (testing "ability modifier - ability modifier for score 18 is +4" + (is (= 4 (dnd-character/modifier 18))))) + +(deftest ability_test_1 + (testing "random ability is within range" + (is (<= 3 (dnd-character/ability) 18)))) + +(deftest ability_test_2 + (testing "ability is generated randomly" + (is (>= (count (set (repeatedly 100 #(dnd-character/ability)))) 5)))) + +(deftest character_test_1 + (testing "random character is valid - strength" + (is (<= 3 (:strength (dnd-character/character)) 18)))) + +(deftest character_test_2 + (testing "random character is valid - dexterity" + (is (<= 3 (:dexterity (dnd-character/character)) 18)))) + +(deftest character_test_3 + (testing "random character is valid - charisma" + (is (<= 3 (:charisma (dnd-character/character)) 18)))) + +(deftest character_test_4 + (testing "random character is valid - wisdom" + (is (<= 3 (:wisdom (dnd-character/character)) 18)))) + +(deftest character_test_5 + (testing "random character is valid - intelligence" + (is (<= 3 (:intelligence (dnd-character/character)) 18)))) + +(deftest character_test_6 + (testing "random character is valid - constitution" + (is (<= 3 (:constitution (dnd-character/character)) 18)))) + +(deftest character_test_7 + (testing "random character is valid - hitpoints" + (let [char (dnd-character/character) + expected (+ 10 (dnd-character/modifier (:constitution char)))] + (is (= expected (:hitpoints char)))))) + +(deftest character_test_8 + (testing "random character is generated randomly" + (is (>= (count (set (repeatedly 100 #(dnd-character/character)))) 5))))