diff --git a/config.json b/config.json index 3ad25420c..e1a1d56f5 100644 --- a/config.json +++ b/config.json @@ -1515,6 +1515,15 @@ "practices": [], "prerequisites": [], "difficulty": 1 + }, + { + "slug": "dnd-character", + "name": "D&D Character", + "uuid": "8f52127a-ad81-46a2-b734-a76117138aae", + "practices": [], + "prerequisites": [], + "difficulty": 1, + "topics":["structs", "randomness"] } ], "foregone": [ diff --git a/exercises/practice/dnd-character/.docs/instructions.md b/exercises/practice/dnd-character/.docs/instructions.md new file mode 100644 index 000000000..b0a603591 --- /dev/null +++ b/exercises/practice/dnd-character/.docs/instructions.md @@ -0,0 +1,31 @@ +# 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 record 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 rules above. + +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. + +## Notes + +Most programming languages feature (pseudo-)random generators, but few programming languages are designed to roll dice. +One such language is [Troll][troll]. + +[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons +[troll]: https://di.ku.dk/Ansatte/?pure=da%2Fpublications%2Ftroll-a-language-for-specifying-dicerolls(84a45ff0-068b-11df-825d-000ea68e967b)%2Fexport.html diff --git a/exercises/practice/dnd-character/.gitignore b/exercises/practice/dnd-character/.gitignore new file mode 100644 index 000000000..e24ae5981 --- /dev/null +++ b/exercises/practice/dnd-character/.gitignore @@ -0,0 +1,8 @@ +# Generated by Cargo +# Will have compiled files and executables +/target/ +**/*.rs.bk + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/exercises/practice/dnd-character/.meta/config.json b/exercises/practice/dnd-character/.meta/config.json new file mode 100644 index 000000000..592afd784 --- /dev/null +++ b/exercises/practice/dnd-character/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "dem4ron" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/dnd-character.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "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.rs b/exercises/practice/dnd-character/.meta/example.rs new file mode 100644 index 000000000..6c1ce8909 --- /dev/null +++ b/exercises/practice/dnd-character/.meta/example.rs @@ -0,0 +1,45 @@ +use rand::prelude::*; + +pub fn modifier(ability_score: u8) -> f32 { + ((ability_score as i8 - 10) as f32 / 2.0).floor() +} + +pub struct Character { + pub strength: u8, + pub dexterity: u8, + pub constitution: u8, + pub intelligence: u8, + pub wisdom: u8, + pub charisma: u8, + pub hitpoints: u8, +} + +impl Character { + pub fn new() -> Self { + let constitution = Character::calculate_ability_score(Character::roll_four_dice()); + Character { + strength: Character::calculate_ability_score(Character::roll_four_dice()), + dexterity: Character::calculate_ability_score(Character::roll_four_dice()), + constitution, + intelligence: Character::calculate_ability_score(Character::roll_four_dice()), + wisdom: Character::calculate_ability_score(Character::roll_four_dice()), + charisma: Character::calculate_ability_score(Character::roll_four_dice()), + hitpoints: 10 + modifier(constitution) as u8, + } + } + + pub fn roll_four_dice() -> [u8; 4] { + let mut rng = thread_rng(); + let mut rolls = [0; 4]; + for i in 0..4 { + rolls[i] = rng.gen_range(1..=4) + } + rolls + } + + pub fn calculate_ability_score(ability_dice_rolls: [u8; 4]) -> u8 { + let mut ability_dice_rolls = ability_dice_rolls; + ability_dice_rolls.sort(); + ability_dice_rolls[1..].iter().sum() + } +} diff --git a/exercises/practice/dnd-character/.meta/tests.toml b/exercises/practice/dnd-character/.meta/tests.toml new file mode 100644 index 000000000..2b04dd335 --- /dev/null +++ b/exercises/practice/dnd-character/.meta/tests.toml @@ -0,0 +1,67 @@ +# 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" diff --git a/exercises/practice/dnd-character/Cargo.toml b/exercises/practice/dnd-character/Cargo.toml new file mode 100644 index 000000000..cd42fb7c2 --- /dev/null +++ b/exercises/practice/dnd-character/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "dnd_character" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.5" diff --git a/exercises/practice/dnd-character/src/lib.rs b/exercises/practice/dnd-character/src/lib.rs new file mode 100644 index 000000000..e3bd995ac --- /dev/null +++ b/exercises/practice/dnd-character/src/lib.rs @@ -0,0 +1,27 @@ +pub fn modifier(_ability_score: u8) -> f32 { + unimplemented!("Implement a utility function that calculates the ability modifier for a given ability score") +} + +pub struct Character { + pub strength: u8, + pub dexterity: u8, + pub constitution: u8, + pub intelligence: u8, + pub wisdom: u8, + pub charisma: u8, + pub hitpoints: u8, +} + +impl Character { + pub fn new() -> Self { + unimplemented!("Create a function that generates a new random character with the following ability scores and hitpoints") + } + + pub fn roll_four_dice() -> [u8; 4] { + unimplemented!("Create a utility function that rolls four dice") + } + + pub fn calculate_ability_score(_ability_dice_rolls: [u8; 4]) -> u8 { + unimplemented!("Calculate the ability score from the given rolled dice") + } +} diff --git a/exercises/practice/dnd-character/tests/dnd-character.rs b/exercises/practice/dnd-character/tests/dnd-character.rs new file mode 100644 index 000000000..9248c1df6 --- /dev/null +++ b/exercises/practice/dnd-character/tests/dnd-character.rs @@ -0,0 +1,183 @@ +use dnd_character::*; + +#[test] +fn test_ability_modifier_for_score_3_is_4() { + let input = 3; + let expected: f32 = -4.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_4_is_3() { + let input = 4; + let expected: f32 = -3.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_5_is_3() { + let input = 5; + let expected: f32 = -3.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_6_is_2() { + let input = 6; + let expected: f32 = -2.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_7_is_2() { + let input = 7; + let expected: f32 = -2.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_8_is_1() { + let input = 8; + let expected: f32 = -1.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_9_is_1() { + let input = 9; + let expected: f32 = -1.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_10_is_0() { + let input = 10; + let expected: f32 = 0.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_11_is_0() { + let input = 11; + let expected: f32 = 0.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_12_is_1() { + let input = 12; + let expected: f32 = 1.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_13_is_1() { + let input = 13; + let expected: f32 = 1.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_14_is_2() { + let input = 14; + let expected: f32 = 2.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_15_is_2() { + let input = 15; + let expected: f32 = 2.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_16_is_3() { + let input = 16; + let expected: f32 = 3.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_17_is_3() { + let input = 17; + let expected: f32 = 3.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_ability_modifier_for_score_18_is_4() { + let input = 18; + let expected: f32 = 4.0; + assert_eq!(modifier(input), expected); +} + +#[test] +#[ignore] +fn test_random_ability_is_within_range() { + let score = Character::calculate_ability_score(Character::roll_four_dice()); + assert!((3..=18).contains(&score)); +} + +#[test] +#[ignore] +fn test_random_character_is_valid() { + let character = Character::new(); + assert!((3..=18).contains(&character.strength)); + assert!((3..=18).contains(&character.dexterity)); + assert!((3..=18).contains(&character.constitution)); + assert!((3..=18).contains(&character.intelligence)); + assert!((3..=18).contains(&character.wisdom)); + assert!((3..=18).contains(&character.charisma)); + assert_eq!( + character.hitpoints, + 10 + modifier(character.constitution) as u8 + ); +} + +#[test] +#[ignore] +fn test_dice_are_rolled_for_each_stat() { + // there is a low chance that this might be equal + let character = Character::new(); + assert_ne!( + ( + character.strength, + character.dexterity, + character.constitution, + character.intelligence, + character.wisdom, + character.charisma + ), + ( + character.strength, + character.strength, + character.strength, + character.strength, + character.strength, + character.strength + ) + ); +} + +#[test] +#[ignore] +fn test_each_ability_is_only_calculated_once() { + let character = Character::new(); + assert_eq!(character.strength, character.strength); +}