ðŸŽ‰ Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io ðŸŽ‰

# jlacar's solution

## to D&D Character in the Java Track

Published at May 04 2019 · 0 comments
Instructions
Test suite
Solution

For a game of Dungeons & Dragons, 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.

## Setup

Go through the setup instructions for Java to install the necessary dependencies:

https://exercism.io/tracks/java/installation

# Running the tests

You can run all the tests for an exercise by entering the following in your terminal:

``````\$ gradle test
``````

Use `gradlew.bat` if you're on Windows

In the test suites all tests but the first have been skipped.

Once you get a test passing, you can enable the next one by removing the `@Ignore("Remove to run test")` annotation.

## Source

Simon Shine, Erik Schierboom https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945

## Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

### DnDCharacterTest.java

``````import org.junit.Ignore;
import org.junit.Test;

import static org.junit.Assert.*;

public class DnDCharacterTest {

private DnDCharacter dndCharacter = new DnDCharacter();

@Test
public void testAbilityModifierForScore3IsNegative4() {
assertEquals(-4, dndCharacter.modifier(3));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore4IsNegative3() {
assertEquals(-3, dndCharacter.modifier(4));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore5IsNegative3() {
assertEquals(-3, dndCharacter.modifier(5));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore6IsNegative2() {
assertEquals(-2, dndCharacter.modifier(6));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore7IsNegative2() {
assertEquals(-2, dndCharacter.modifier(7));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore8IsNegative1() {
assertEquals(-1, dndCharacter.modifier(8));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore9IsNegative1() {
assertEquals(-1, dndCharacter.modifier(9));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore10Is0() {
assertEquals(0, dndCharacter.modifier(10));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore11Is0() {
assertEquals(0, dndCharacter.modifier(11));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore12Is1() {
assertEquals(1, dndCharacter.modifier(12));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore13Is1() {
assertEquals(1, dndCharacter.modifier(13));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore14Is2() {
assertEquals(2, dndCharacter.modifier(14));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore15Is2() {
assertEquals(2, dndCharacter.modifier(15));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore16Is3() {
assertEquals(3, dndCharacter.modifier(16));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore17Is3() {
assertEquals(3, dndCharacter.modifier(17));
}

@Ignore("Remove to run test")
@Test
public void testAbilityModifierForScore18Is4() {
assertEquals(4, dndCharacter.modifier(18));
}

@Ignore("Remove to run test")
@Test
public void testRandomAbilityIsWithinRange() {
int score = dndCharacter.ability();
assertTrue(score > 2 && score < 19);
}

@Ignore("Remove to run test")
@Test
public void testRandomCharacterIsValid() {
for (int i = 0; i < 1000; i++) {
DnDCharacter character = new DnDCharacter();
assertTrue(character.getStrength() > 2 && character.getStrength() < 19);
assertTrue(character.getDexterity() > 2 && character.getDexterity() < 19);
assertTrue(character.getConstitution() > 2 && character.getConstitution() < 19);
assertTrue(character.getIntelligence() > 2 && character.getIntelligence() < 19);
assertTrue(character.getWisdom() > 2 && character.getWisdom() < 19);
assertTrue(character.getCharisma() > 2 && character.getCharisma() < 19);
assertEquals(character.getHitpoints(),
10 + character.modifier(character.getConstitution()));
}
}

@Ignore("Remove to run test")
@Test
public void testEachAbilityIsOnlyCalculatedOnce() {
assertEquals(dndCharacter.getStrength(), dndCharacter.getStrength());
}

}``````

### src/main/java/DnDCharacter.java

``````import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static java.util.stream.Collectors.summingInt;

class DnDCharacter {

private enum Ability {
STRENGTH, DEXTERITY, CONSTITUTION, INTELLIGENCE, WISDOM, CHARISMA;
}

private final Map<Ability, Integer> abilities;

private final Dice dice = new Dice();

DnDCharacter() {
abilities = new HashMap<>();
Arrays.stream(Ability.values()).forEach(ability ->
abilities.put(ability, sumOflargest(3, dice.roll(4)))
);
}

/** Returns the value of a random ability */
int ability() {
return abilities.get(Ability.values()[dice.roll() - 1]);
}

int modifier(int input) {
return Math.floorDiv(input - 10, 2);
}

int getStrength() {
return abilities.get(Ability.STRENGTH);
}

int getDexterity() {
return abilities.get(Ability.DEXTERITY);
}

int getConstitution() {
return abilities.get(Ability.CONSTITUTION);
}

int getIntelligence() {
return abilities.get(Ability.INTELLIGENCE);
}

int getWisdom() {
return abilities.get(Ability.WISDOM);
}

int getCharisma() {
return abilities.get(Ability.CHARISMA);
}

int getHitpoints() {
return 10 + modifier(getConstitution());
}

int sumOflargest(Integer n, Integer... rolls) {
Arrays.sort(rolls, Collections.reverseOrder());
return Arrays.stream(rolls)
.limit(n)
.collect(summingInt(Integer::intValue));
}

}

class Dice {
private static final int SIDES = 6;

private final Random rand = new Random();

int roll() {
return rand.nextInt(SIDES) + 1;
}

Integer[] roll(int times) {
return IntStream.range(0, times)
.map(n -> roll())
.boxed().toArray(Integer[]::new);
}
}``````

### src/test/java/DnDCharacterTest.java

``````import org.junit.Ignore;
import org.junit.Test;

import java.util.List;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;

public class DnDCharacterTest {

private DnDCharacter dndCharacter = new DnDCharacter();

@Test
public void testAbilityModifierForScore3IsNegative4() {
assertEquals(-4, dndCharacter.modifier(3));
}

@Test
public void testAbilityModifierForScore4IsNegative3() {
assertEquals(-3, dndCharacter.modifier(4));
}

@Test
public void testAbilityModifierForScore5IsNegative3() {
assertEquals(-3, dndCharacter.modifier(5));
}

@Test
public void testAbilityModifierForScore6IsNegative2() {
assertEquals(-2, dndCharacter.modifier(6));
}

@Test
public void testAbilityModifierForScore7IsNegative2() {
assertEquals(-2, dndCharacter.modifier(7));
}

@Test
public void testAbilityModifierForScore8IsNegative1() {
assertEquals(-1, dndCharacter.modifier(8));
}

@Test
public void testAbilityModifierForScore9IsNegative1() {
assertEquals(-1, dndCharacter.modifier(9));
}

@Test
public void testAbilityModifierForScore10Is0() {
assertEquals(0, dndCharacter.modifier(10));
}

@Test
public void testAbilityModifierForScore11Is0() {
assertEquals(0, dndCharacter.modifier(11));
}

@Test
public void testAbilityModifierForScore12Is1() {
assertEquals(1, dndCharacter.modifier(12));
}

@Test
public void testAbilityModifierForScore13Is1() {
assertEquals(1, dndCharacter.modifier(13));
}

@Test
public void testAbilityModifierForScore14Is2() {
assertEquals(2, dndCharacter.modifier(14));
}

@Test
public void testAbilityModifierForScore15Is2() {
assertEquals(2, dndCharacter.modifier(15));
}

@Test
public void testAbilityModifierForScore16Is3() {
assertEquals(3, dndCharacter.modifier(16));
}

@Test
public void testAbilityModifierForScore17Is3() {
assertEquals(3, dndCharacter.modifier(17));
}

@Test
public void testAbilityModifierForScore18Is4() {
assertEquals(4, dndCharacter.modifier(18));
}

@Test
public void testRandomAbilityIsWithinRange() {
for (int i = 0; i < 1000; i++) {
int score = dndCharacter.ability();
assertTrue(score > 2 && score < 19);
}
}

@Test
public void testRandomCharacterIsValid() {
for (int i = 0; i < 1000; i++) {
DnDCharacter character = new DnDCharacter();
assertTrue(character.getStrength() > 2 && character.getStrength() < 19);
assertTrue(character.getDexterity() > 2 && character.getDexterity() < 19);
assertTrue(character.getConstitution() > 2 && character.getConstitution() < 19);
assertTrue(character.getIntelligence() > 2 && character.getIntelligence() < 19);
assertTrue(character.getWisdom() > 2 && character.getWisdom() < 19);
assertTrue(character.getCharisma() > 2 && character.getCharisma() < 19);
assertEquals(character.getHitpoints(),
10 + character.modifier(character.getConstitution()));
}
}

@Test
public void testEachAbilityIsOnlyCalculatedOnce() {
assertEquals(dndCharacter.getStrength(), dndCharacter.getStrength());
}

@Test
public void sumOfLargest3of4_any_order() {
assertThat(dndCharacter.sumOflargest(3, new Integer[]{1, 3, 5, 6}), is(14));
assertThat(dndCharacter.sumOflargest(3, new Integer[]{5, 3, 1, 6}), is(14));
assertThat(dndCharacter.sumOflargest(3, new Integer[]{6, 3, 5, 1}), is(14));
}

@Test
public void sumOfLargest3of4_with_double_min_values() {
assertThat(dndCharacter.sumOflargest(3, new Integer[]{3, 2, 6, 2}), is(11));
assertThat(dndCharacter.sumOflargest(3, new Integer[]{2, 2, 6, 3}), is(11));
assertThat(dndCharacter.sumOflargest(3, new Integer[]{5, 4, 2, 2}), is(11));
}

@Test
public void sumOfLargest3of4_with_double_max_values() {
assertThat(dndCharacter.sumOflargest(3, new Integer[]{1, 2, 6, 6}), is(14));
assertThat(dndCharacter.sumOflargest(3, new Integer[]{6, 2, 6, 1}), is(14));
assertThat(dndCharacter.sumOflargest(3, new Integer[]{6, 6, 1, 2}), is(14));
}

@Test
public void sumOfLargest3of4_with_all_same_values() {
assertThat(dndCharacter.sumOflargest(3, new Integer[] {1, 1, 1, 1}), is(3));
assertThat(dndCharacter.sumOflargest(3, new Integer[] {6, 6, 6, 6}), is(18));
}

}``````