Skip to content

Commit bd28108

Browse files
authored
feat: add dnd-character exercise (#802)
1 parent 528881c commit bd28108

File tree

12 files changed

+18289
-0
lines changed

12 files changed

+18289
-0
lines changed

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,14 @@
11321132
"practices": [],
11331133
"prerequisites": [],
11341134
"difficulty": 7
1135+
},
1136+
{
1137+
"slug": "dnd-character",
1138+
"name": "D&D Character",
1139+
"uuid": "83e20a67-990e-4db7-b16a-48b4f516f893",
1140+
"practices": [],
1141+
"prerequisites": [],
1142+
"difficulty": 3
11351143
}
11361144
],
11371145
"foregone": [
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Instructions
2+
3+
For a game of [Dungeons & Dragons][dnd], each player starts by generating a character they can play with.
4+
This character has, among other things, six abilities; strength, dexterity, constitution, intelligence, wisdom and charisma.
5+
These six abilities have scores that are determined randomly.
6+
You do this by rolling four 6-sided dice and record the sum of the largest three dice.
7+
You do this six times, once for each ability.
8+
9+
Your character's initial hitpoints are 10 + your character's constitution modifier.
10+
You find your character's constitution modifier by subtracting 10 from your character's constitution, divide by 2 and round down.
11+
12+
Write a random character generator that follows the rules above.
13+
14+
For example, the six throws of four dice may look like:
15+
16+
- 5, 3, 1, 6: You discard the 1 and sum 5 + 3 + 6 = 14, which you assign to strength.
17+
- 3, 2, 5, 3: You discard the 2 and sum 3 + 5 + 3 = 11, which you assign to dexterity.
18+
- 1, 1, 1, 1: You discard the 1 and sum 1 + 1 + 1 = 3, which you assign to constitution.
19+
- 2, 1, 6, 6: You discard the 1 and sum 2 + 6 + 6 = 14, which you assign to intelligence.
20+
- 3, 5, 3, 4: You discard the 3 and sum 5 + 3 + 4 = 12, which you assign to wisdom.
21+
- 6, 6, 6, 6: You discard the 6 and sum 6 + 6 + 6 = 18, which you assign to charisma.
22+
23+
Because constitution is 3, the constitution modifier is -4 and the hitpoints are 6.
24+
25+
## Notes
26+
27+
Most programming languages feature (pseudo-)random generators, but few programming languages are designed to roll dice.
28+
One such language is [Troll][troll].
29+
30+
[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons
31+
[troll]: https://di.ku.dk/Ansatte/?pure=da%2Fpublications%2Ftroll-a-language-for-specifying-dicerolls(84a45ff0-068b-11df-825d-000ea68e967b)%2Fexport.html
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"authors": [
3+
"vaeng"
4+
],
5+
"files": {
6+
"solution": [
7+
"dnd_character.cpp",
8+
"dnd_character.h"
9+
],
10+
"test": [
11+
"dnd_character_test.cpp"
12+
],
13+
"example": [
14+
".meta/example.cpp",
15+
".meta/example.h"
16+
]
17+
},
18+
"blurb": "Randomly generate Dungeons & Dragons characters.",
19+
"source": "Simon Shine, Erik Schierboom",
20+
"source_url": "https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945"
21+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include "dnd_character.h"
2+
3+
#include <cmath>
4+
#include <cstdlib>
5+
6+
namespace dnd_character {
7+
int modifier(int score) {
8+
return std::floor((static_cast<double>(score) - 10) / 2);
9+
}
10+
11+
int dice_roll() { return 1 + std::rand() / ((RAND_MAX + 1u) / 6); }
12+
13+
// Throwing three dice is not the same as selecting a random number between 3
14+
// and 18.
15+
int ability() { return dice_roll() + dice_roll() + dice_roll(); }
16+
17+
} // namespace dnd_character
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#pragma once
2+
3+
namespace dnd_character {
4+
int modifier(int score);
5+
int ability();
6+
7+
struct Character {
8+
Character() {
9+
strength = ability();
10+
dexterity = ability();
11+
constitution = ability();
12+
intelligence = ability();
13+
wisdom = ability();
14+
charisma = ability();
15+
hitpoints = 10 + modifier(constitution);
16+
};
17+
int strength;
18+
int dexterity;
19+
int constitution;
20+
int intelligence;
21+
int wisdom;
22+
int charisma;
23+
int hitpoints;
24+
};
25+
26+
} // namespace dnd_character
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[1e9ae1dc-35bd-43ba-aa08-e4b94c20fa37]
13+
description = "ability modifier -> ability modifier for score 3 is -4"
14+
15+
[cc9bb24e-56b8-4e9e-989d-a0d1a29ebb9c]
16+
description = "ability modifier -> ability modifier for score 4 is -3"
17+
18+
[5b519fcd-6946-41ee-91fe-34b4f9808326]
19+
description = "ability modifier -> ability modifier for score 5 is -3"
20+
21+
[dc2913bd-6d7a-402e-b1e2-6d568b1cbe21]
22+
description = "ability modifier -> ability modifier for score 6 is -2"
23+
24+
[099440f5-0d66-4b1a-8a10-8f3a03cc499f]
25+
description = "ability modifier -> ability modifier for score 7 is -2"
26+
27+
[cfda6e5c-3489-42f0-b22b-4acb47084df0]
28+
description = "ability modifier -> ability modifier for score 8 is -1"
29+
30+
[c70f0507-fa7e-4228-8463-858bfbba1754]
31+
description = "ability modifier -> ability modifier for score 9 is -1"
32+
33+
[6f4e6c88-1cd9-46a0-92b8-db4a99b372f7]
34+
description = "ability modifier -> ability modifier for score 10 is 0"
35+
36+
[e00d9e5c-63c8-413f-879d-cd9be9697097]
37+
description = "ability modifier -> ability modifier for score 11 is 0"
38+
39+
[eea06f3c-8de0-45e7-9d9d-b8cab4179715]
40+
description = "ability modifier -> ability modifier for score 12 is +1"
41+
42+
[9c51f6be-db72-4af7-92ac-b293a02c0dcd]
43+
description = "ability modifier -> ability modifier for score 13 is +1"
44+
45+
[94053a5d-53b6-4efc-b669-a8b5098f7762]
46+
description = "ability modifier -> ability modifier for score 14 is +2"
47+
48+
[8c33e7ca-3f9f-4820-8ab3-65f2c9e2f0e2]
49+
description = "ability modifier -> ability modifier for score 15 is +2"
50+
51+
[c3ec871e-1791-44d0-b3cc-77e5fb4cd33d]
52+
description = "ability modifier -> ability modifier for score 16 is +3"
53+
54+
[3d053cee-2888-4616-b9fd-602a3b1efff4]
55+
description = "ability modifier -> ability modifier for score 17 is +3"
56+
57+
[bafd997a-e852-4e56-9f65-14b60261faee]
58+
description = "ability modifier -> ability modifier for score 18 is +4"
59+
60+
[4f28f19c-2e47-4453-a46a-c0d365259c14]
61+
description = "random ability is within range"
62+
63+
[385d7e72-864f-4e88-8279-81a7d75b04ad]
64+
description = "random character is valid"
65+
66+
[2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe]
67+
description = "each ability is only calculated once"
68+
include = false
69+
70+
[dca2b2ec-f729-4551-84b9-078876bb4808]
71+
description = "each ability is only calculated once"
72+
reimplements = "2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe"
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Get the exercise name from the current directory
2+
get_filename_component(exercise ${CMAKE_CURRENT_SOURCE_DIR} NAME)
3+
4+
# Basic CMake project
5+
cmake_minimum_required(VERSION 3.5.1)
6+
7+
# Name the project after the exercise
8+
project(${exercise} CXX)
9+
10+
# Get a source filename from the exercise name by replacing -'s with _'s
11+
string(REPLACE "-" "_" file ${exercise})
12+
13+
# Implementation could be only a header
14+
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${file}.cpp)
15+
set(exercise_cpp ${file}.cpp)
16+
else()
17+
set(exercise_cpp "")
18+
endif()
19+
20+
# Use the common Catch library?
21+
if(EXERCISM_COMMON_CATCH)
22+
# For Exercism track development only
23+
add_executable(${exercise} ${file}_test.cpp ${exercise_cpp} ${file}.h $<TARGET_OBJECTS:catchlib>)
24+
elseif(EXERCISM_TEST_SUITE)
25+
# The Exercism test suite is being run, the Docker image already
26+
# includes a pre-built version of Catch.
27+
find_package(Catch2 REQUIRED)
28+
add_executable(${exercise} ${file}_test.cpp ${exercise_cpp} ${file}.h)
29+
target_link_libraries(${exercise} PRIVATE Catch2::Catch2WithMain)
30+
# When Catch is installed system wide we need to include a different
31+
# header, we need this define to use the correct one.
32+
target_compile_definitions(${exercise} PRIVATE EXERCISM_TEST_SUITE)
33+
else()
34+
# Build executable from sources and headers
35+
add_executable(${exercise} ${file}_test.cpp ${exercise_cpp} ${file}.h test/tests-main.cpp)
36+
endif()
37+
38+
set_target_properties(${exercise} PROPERTIES
39+
CXX_STANDARD 17
40+
CXX_STANDARD_REQUIRED OFF
41+
CXX_EXTENSIONS OFF
42+
)
43+
44+
set(CMAKE_BUILD_TYPE Debug)
45+
46+
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(GNU|Clang)")
47+
set_target_properties(${exercise} PROPERTIES
48+
COMPILE_FLAGS "-Wall -Wextra -Wpedantic -Werror"
49+
)
50+
endif()
51+
52+
# Configure to run all the tests?
53+
if(${EXERCISM_RUN_ALL_TESTS})
54+
target_compile_definitions(${exercise} PRIVATE EXERCISM_RUN_ALL_TESTS)
55+
endif()
56+
57+
# Tell MSVC not to warn us about unchecked iterators in debug builds
58+
if(${MSVC})
59+
set_target_properties(${exercise} PROPERTIES
60+
COMPILE_DEFINITIONS_DEBUG _SCL_SECURE_NO_WARNINGS)
61+
endif()
62+
63+
# Run the tests on every build
64+
add_custom_target(test_${exercise} ALL DEPENDS ${exercise} COMMAND ${exercise})
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include "dnd_character.h"
2+
3+
namespace dnd_character {
4+
5+
} // namespace dnd_character
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#pragma once
2+
3+
namespace dnd_character {
4+
5+
} // namespace dnd_character
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#include "dnd_character.h"
2+
#ifdef EXERCISM_TEST_SUITE
3+
#include <catch2/catch.hpp>
4+
#else
5+
#include "test/catch.hpp"
6+
#endif
7+
8+
TEST_CASE("ability modifier for score 3 is -4", "[1e9ae1dc-35bd-43ba-aa08-e4b94c20fa37]") {
9+
REQUIRE(-4 == dnd_character::modifier(3));
10+
}
11+
12+
#if defined(EXERCISM_RUN_ALL_TESTS)
13+
14+
TEST_CASE("ability modifier for score 4 is -3", "[cc9bb24e-56b8-4e9e-989d-a0d1a29ebb9c]") {
15+
REQUIRE(-3 == dnd_character::modifier(4));
16+
}
17+
18+
TEST_CASE("ability modifier for score 5 is -3", "[5b519fcd-6946-41ee-91fe-34b4f9808326]") {
19+
REQUIRE(-3 == dnd_character::modifier(5));
20+
}
21+
22+
TEST_CASE("ability modifier for score 6 is -2", "[dc2913bd-6d7a-402e-b1e2-6d568b1cbe21]") {
23+
REQUIRE(-2 == dnd_character::modifier(6));
24+
}
25+
26+
TEST_CASE("ability modifier for score 7 is -2", "[099440f5-0d66-4b1a-8a10-8f3a03cc499f]") {
27+
REQUIRE(-2 == dnd_character::modifier(7));
28+
}
29+
30+
TEST_CASE("ability modifier for score 8 is -1", "[cfda6e5c-3489-42f0-b22b-4acb47084df0]") {
31+
REQUIRE(-1 == dnd_character::modifier(8));
32+
}
33+
34+
TEST_CASE("ability modifier for score 9 is -1", "[c70f0507-fa7e-4228-8463-858bfbba1754]") {
35+
REQUIRE(-1 == dnd_character::modifier(9));
36+
}
37+
38+
TEST_CASE("ability modifier for score 10 is 0", "[6f4e6c88-1cd9-46a0-92b8-db4a99b372f7]") {
39+
REQUIRE(0 == dnd_character::modifier(10));
40+
}
41+
42+
TEST_CASE("ability modifier for score 11 is 0", "[e00d9e5c-63c8-413f-879d-cd9be9697097]") {
43+
REQUIRE(0 == dnd_character::modifier(11));
44+
}
45+
46+
TEST_CASE("ability modifier for score 12 is +1", "[eea06f3c-8de0-45e7-9d9d-b8cab4179715]") {
47+
REQUIRE(1 == dnd_character::modifier(12));
48+
}
49+
50+
TEST_CASE("ability modifier for score 13 is +1", "[9c51f6be-db72-4af7-92ac-b293a02c0dcd]") {
51+
REQUIRE(1 == dnd_character::modifier(13));
52+
}
53+
54+
TEST_CASE("ability modifier for score 14 is +2", "[94053a5d-53b6-4efc-b669-a8b5098f7762]") {
55+
REQUIRE(2 == dnd_character::modifier(14));
56+
}
57+
58+
TEST_CASE("ability modifier for score 15 is +2", "[8c33e7ca-3f9f-4820-8ab3-65f2c9e2f0e2]") {
59+
REQUIRE(2 == dnd_character::modifier(15));
60+
}
61+
62+
TEST_CASE("ability modifier for score 16 is +3", "[c3ec871e-1791-44d0-b3cc-77e5fb4cd33d]") {
63+
REQUIRE(3 == dnd_character::modifier(16));
64+
}
65+
66+
TEST_CASE("ability modifier for score 17 is +3", "[3d053cee-2888-4616-b9fd-602a3b1efff4]") {
67+
REQUIRE(3 == dnd_character::modifier(17));
68+
}
69+
70+
TEST_CASE("ability modifier for score 18 is +4", "[bafd997a-e852-4e56-9f65-14b60261faee]") {
71+
REQUIRE(4 == dnd_character::modifier(18));
72+
}
73+
74+
TEST_CASE("random ability is within range", "[4f28f19c-2e47-4453-a46a-c0d365259c14]") {
75+
int result{dnd_character::ability()};
76+
//REQUIRE("score >= 3 && score <= 18" == dnd_character::ability());
77+
REQUIRE((result >= 3 && result <= 18));
78+
}
79+
80+
TEST_CASE("random character is valid", "[385d7e72-864f-4e88-8279-81a7d75b04ad]") {
81+
dnd_character::Character character;
82+
REQUIRE((character.strength >= 3 && character.strength <= 18));
83+
REQUIRE((character.dexterity >= 3 && character.dexterity <= 18));
84+
REQUIRE((character.constitution >= 3 && character.constitution <= 18));
85+
REQUIRE((character.intelligence >= 3 && character.intelligence <= 18));
86+
REQUIRE((character.wisdom >= 3 && character.wisdom <= 18));
87+
REQUIRE((character.charisma >= 3 && character.charisma <= 18));
88+
REQUIRE(character.hitpoints == 10 + dnd_character::modifier(character.constitution));
89+
}
90+
91+
TEST_CASE("each ability is only calculated once", "[dca2b2ec-f729-4551-84b9-078876bb4808]") {
92+
dnd_character::Character character;
93+
REQUIRE(character.strength == character.strength);
94+
REQUIRE(character.dexterity == character.dexterity);
95+
REQUIRE(character.constitution == character.constitution);
96+
REQUIRE(character.intelligence == character.intelligence);
97+
REQUIRE(character.wisdom == character.wisdom);
98+
REQUIRE(character.charisma == character.charisma);
99+
}
100+
101+
#endif

0 commit comments

Comments
 (0)