Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create mentoring.md for dnd-character #2342

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions tracks/typescript/exercises/dnd-character/dnd-character/mentoring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Mentoring

This exercise is meant to teach the relation of classes, instances, methods and properties.

## Reasonable Solution

The main point is about the use of static methods of the class and public properties of the instance. Without those, the test will fail.

There are different strategies for the `generateAbilityScore` method. One can use a loop and two variables to store the sum and the smallest, then return the sum minus the smallest throw. One can also use an Array of dices, which can be created in multiple ways, manually (`[dice(), dice(), ...]`), via the method shown above, `[...Array(4)].map(...)`, or `Array.from({ length: 4 }, () => ...)`. It can either be sorted and sliced or summed up and the smallest throw subtracted.

It is possible to set the values in the constructor by looping over an array with the property names, but that could be considered overengineering.

### Normal solution

```ts
export class DnDCharacter {
public strength : number;
public dexterity : number;
public constitution : number;
public intelligence : number;
public wisdom : number;
public charisma : number;
public hitpoints : number;

public constructor(
strength: number,
dexterity : number,
constitution : number,
intelligence : number,
wisdom : number,
charisma : number,
hitpoints : number
) {
this.strength = DnDCharacter.generateAbilityScore();
this.dexterity = DnDCharacter.generateAbilityScore();
this.constitution = DnDCharacter.generateAbilityScore();
this.intelligence = DnDCharacter.generateAbilityScore();
this.wisdom = DnDCharacter.generateAbilityScore();
this.charisma = DnDCharacter.generateAbilityScore();
this.hitpoints = 10 + DnDCharacter.getModifierFor(this.constitution);
}

public static generateAbilityScore(): number {
return [0, 0, 0, 0].map(() => Math.floor(Math.random() * 6 + 1))
.sort()
.slice(1)
.reduce((sum, dice) => sum + dice, 0);
}
public static getModifierFor(abilityValue: number): number {
return Math.floor((abilityValue - 10) / 2);
}
}
```

If the static methods are defined within the class, there is no way to use them in the instantiation outside of the constructor; to avoid separating the definition of the properties and filling them with values, one can create the functions outside of the class and use them directly for a very optimized solution:

### Optimized solution

```ts
const score = () => {
const dices = [0, 0, 0, 0].map(() => Math.floor(Math.random() * 6 + 1));
return dices.reduce((v, d) => v + d, -Math.min(...dices));
};
const mod = (value: number) => Math.floor((value - 10) / 2);

export class DnDCharacter {
public strength = score();
public dexterity = score();
public constitution = score();
public intelligence = score();
public wisdom = score();
public charisma = score();
public hitpoints = 10 + mod(this.constitution);
static generateAbilityScore = score;
static getModifierFor = mod;
}
```

### Prototypal inheritance

JS still supports prototypal inheritance, so instead of a class method, one could also use

```ts
export function DnDCharacter(this: {
strength: number,
dexterity: number,
constitution: number,
intelligence: number,
wisdom: number,
charisma: number,
hitpoints: number,
}) {
[this.strength, this.dexterity, this.constitution,
this.intelligence, this.wisdom, this.charisma] =
Array.from({ length: 6 }, DnDCharacter.generateAbilityScore);
this.hitpoints = 10 + DnDCharacter.getModifierFor(this.constitution);
}
DnDCharacter.generateAbilityScore = () => {
const dices = [...Array(4)].map(() => Math.floor(Math.random() * 6 + 1));
return dices.reduce((v, d) => v + d, -Math.min(...dices));
}
DnDCharacter.getModifierFor = (value: number) => Math.floor((value - 10) / 2);
```

but as the example shows, this also requires defining the properties before setting them.

## Typical mistakes

* Dice rolls miscalculated as `Math.floor(Math.random() * 6)` or `Math.floor(Math.random() * 5 + 1)`
* Removing not the smallest, but any dice
Loading