diff --git a/config.json b/config.json index 9b4af2766..94caa6040 100644 --- a/config.json +++ b/config.json @@ -316,6 +316,14 @@ "strings" ] }, + { + "slug": "dnd-character", + "name": "D&D Character", + "uuid": "8fabd26a-9a2a-4afa-bcff-b5a89db7e4c3", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "prime-factors", "name": "Prime Factors", 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..8d243ab4a --- /dev/null +++ b/exercises/practice/dnd-character/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": ["khalidbelk"], + "files": { + "solution": [ + "dnd_character.ml" + ], + "test": [ + "test.ml" + ], + "example": [ + ".meta/example.ml" + ], + "editor": [ + "dnd_character.mli" + ] + }, + "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.ml b/exercises/practice/dnd-character/.meta/example.ml new file mode 100644 index 000000000..fe3db74eb --- /dev/null +++ b/exercises/practice/dnd-character/.meta/example.ml @@ -0,0 +1,37 @@ +type character = { + charisma : int; + constitution : int; + dexterity : int; + hitpoints : int; + intelligence : int; + strength : int; + wisdom : int; +} + +let roll_dice () = + let results = List.init 4 (fun _ -> 1 + Random.int 6) + in results |> List.sort (compare) |> List.tl + +let ability () = + let dice_result = roll_dice () in + dice_result |> List.fold_left (+) 0 + +let modifier ~score = + let res = (float_of_int(score) -. 10.0) /. 2.0 in + int_of_float(floor res) + +let generate_character () = + let cons = ability () in + let modifier_value = modifier ~score:cons in + let charisma = ability () in + let constitution = cons in + let dexterity = ability () in + let hitpoints = 10 + modifier_value in + let intelligence = ability () in + let strength = ability () in + let wisdom = ability () in + { charisma; constitution; dexterity; hitpoints; intelligence; strength; wisdom } + + +(* Init random seed *) +let () = Random.self_init () diff --git a/exercises/practice/dnd-character/.meta/tests.toml b/exercises/practice/dnd-character/.meta/tests.toml new file mode 100644 index 000000000..719043b25 --- /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" diff --git a/exercises/practice/dnd-character/Makefile b/exercises/practice/dnd-character/Makefile new file mode 100644 index 000000000..b71d6af2d --- /dev/null +++ b/exercises/practice/dnd-character/Makefile @@ -0,0 +1,9 @@ +default: clean test + +test: + dune runtest + +clean: + dune clean + +.PHONY: clean diff --git a/exercises/practice/dnd-character/dnd_character.ml b/exercises/practice/dnd-character/dnd_character.ml new file mode 100644 index 000000000..a566472f9 --- /dev/null +++ b/exercises/practice/dnd-character/dnd_character.ml @@ -0,0 +1,8 @@ +let ability () = + failwith "'ability' is missing" + +let modifier ~score = + failwith "'modifier' is missing" + +let generate_character () = + failwith "'generate_character' is missing" diff --git a/exercises/practice/dnd-character/dnd_character.mli b/exercises/practice/dnd-character/dnd_character.mli new file mode 100644 index 000000000..aeed64bc9 --- /dev/null +++ b/exercises/practice/dnd-character/dnd_character.mli @@ -0,0 +1,18 @@ +type character = { + charisma : int; + constitution : int; + dexterity : int; + hitpoints : int; + intelligence : int; + strength : int; + wisdom : int; +} + +(* Gives the random ability score obtained by rolling four 6-sided dice *) +val ability : unit -> int + +(* Given an ability score, returns the ability modifier *) +val modifier : score:int -> int + +(* Creates a new character *) +val generate_character : unit -> character diff --git a/exercises/practice/dnd-character/dune b/exercises/practice/dnd-character/dune new file mode 100644 index 000000000..28aeac0a3 --- /dev/null +++ b/exercises/practice/dnd-character/dune @@ -0,0 +1,16 @@ +(executable + (name test) + (libraries base ounit2)) + +(alias + (name runtest) + (deps (:x test.exe)) + (action (run %{x}))) + +(alias + (name buildtest) + (deps (:x test.exe))) + +(env + (dev + (flags (:standard -warn-error -A)))) \ No newline at end of file diff --git a/exercises/practice/dnd-character/dune-project b/exercises/practice/dnd-character/dune-project new file mode 100644 index 000000000..7655de077 --- /dev/null +++ b/exercises/practice/dnd-character/dune-project @@ -0,0 +1 @@ +(lang dune 1.1) diff --git a/exercises/practice/dnd-character/test.ml b/exercises/practice/dnd-character/test.ml new file mode 100644 index 000000000..4c579eedc --- /dev/null +++ b/exercises/practice/dnd-character/test.ml @@ -0,0 +1,110 @@ +open OUnit2 +open Dnd_character + +let ae exp got _test_ctxt = + assert_equal exp got ~printer:string_of_int + +let ae_bool exp got _test_ctxt = + assert_equal exp got ~printer:string_of_bool + +let tests = [ + "ability modifier for score 3 is -4" >:: + ae (-4) (modifier ~score:3); + + "ability modifier for score 4 is -3" >:: + ae (-3) (modifier ~score:4); + + "ability modifier for score 5 is -3" >:: + ae (-3) (modifier ~score: 5); + + "ability modifier for score 6 is -2" >:: + ae (-2) (modifier ~score: 6); + + "ability modifier for score 7 is -2" >:: + ae (-2) (modifier ~score: 7); + + "ability modifier for score 8 is -1" >:: + ae (-1) (modifier ~score: 8); + + "ability modifier for score 9 is -1" >:: + ae (-1) (modifier ~score: 9); + + "ability modifier for score 10 is 0" >:: + ae (0) (modifier ~score: 10); + + "ability modifier for score 11 is 0" >:: + ae (0) (modifier ~score: 11); + + "ability modifier for score 12 is +1" >:: + ae (1) (modifier ~score: 12); + + "ability modifier for score 13 is +1" >:: + ae (1) (modifier ~score: 13); + + "ability modifier for score 14 is +2" >:: + ae (2) (modifier ~score: 14); + + "ability modifier for score 15 is +2" >:: + ae (2) (modifier ~score: 15); + + "ability modifier for score 16 is +3" >:: + ae (3) (modifier ~score: 16); + + "ability modifier for score 17 is +3" >:: + ae (3) (modifier ~score: 17); + + "ability modifier for score 18 is +4" >:: + ae (4) (modifier ~score: 18); + + "random ability is within range" >:: (fun _test_ctxt -> + let value = ability () in + ae_bool true (value >= 3 && value <= 18) _test_ctxt; + ); + + "random character is valid" >:: (fun _test_ctxt -> + let c = generate_character () in + Printf.printf "Generated character: str=%d, dex=%d, con=%d, int=%d, wis=%d, cha=%d, hp=%d\n" + c.strength c.dexterity c.constitution c.intelligence c.wisdom c.charisma c.hitpoints; + ae_bool true (c.strength >= 3 && c.strength <= 18) _test_ctxt; + ae_bool true (c.dexterity >= 3 && c.dexterity <= 18) _test_ctxt; + ae_bool true (c.constitution >= 3 && c.constitution <= 18) _test_ctxt; + ae_bool true (c.intelligence >= 3 && c.intelligence <= 18) _test_ctxt; + ae_bool true (c.wisdom >= 3 && c.wisdom <= 18) _test_ctxt; + ae_bool true (c.charisma >= 3 && c.charisma <= 18) _test_ctxt; + ae_bool true (c.hitpoints == 10 + (modifier ~score:c.constitution) ) _test_ctxt; + ); + + "each ability is only calculated once" >:: (fun _test_ctxt -> + let c = generate_character () in + let strength1 = c.strength in + let strength2 = c.strength in + ae (strength1) (strength2) _test_ctxt; + + let dexterity1 = c.dexterity in + let dexterity2 = c.dexterity in + ae (dexterity1) (dexterity2) _test_ctxt; + + let constitution1 = c.constitution in + let constitution2 = c.constitution in + ae (constitution1) (constitution2) _test_ctxt; + + let intelligence1 = c.intelligence in + let intelligence2 = c.intelligence in + ae (intelligence1) (intelligence2) _test_ctxt; + + let wisdom1 = c.wisdom in + let wisdom2 = c.wisdom in + ae (wisdom1) (wisdom2) _test_ctxt; + + let charisma1 = c.charisma in + let charisma2 = c.charisma in + ae (charisma1) (charisma2) _test_ctxt; + + let hitpoints1 = c.hitpoints in + let hitpoints2 = c.hitpoints in + ae (hitpoints1) (hitpoints2) _test_ctxt; + ); +] + +let () = + run_test_tt_main ("D&D tests" >::: tests)