diff --git a/exercises/practice/dnd-character/.approaches/ability-method/content.md b/exercises/practice/dnd-character/.approaches/ability-method/content.md new file mode 100644 index 00000000000..43753f6522b --- /dev/null +++ b/exercises/practice/dnd-character/.approaches/ability-method/content.md @@ -0,0 +1,43 @@ +# Use the `ability()` Method to Generate and Return the Dice Rolls + + +```python +from random import sample + +class Character: + def __init__(self): + self.strength = self.ability() + self.dexterity = self.ability() + self.constitution = self.ability() + self.intelligence = self.ability() + self.wisdom = self.ability() + self.charisma = self.ability() + + self.hitpoints = 10 + modifier(self.constitution) + + def ability(self): + values = sample(range(1, 7), 4) + return sum(values) - min(values) + +def modifier(constitution): + return (constitution - 10)//2 +``` + + +This approach uses a single `ability()` method to calculate the dice rolls and return an ability value. +`ability()` is then called in `__init__()` to populate the listed-out character attributes. +`self.hitpoints` calls the stand-alone `modifier()` function, adding it to 10 for the character's hitpoints attribute. + +This approach is valid and passes all the tests. +However, it will trigger an analyzer comment about there being "too few public methods", since there are no methods for this class beyond the one that calculates attribute values. + + +The "too few" rule encourages you to think about the design of the class: is it worth the effort to create the class if it only holds attribute values for a character? +What other functionality should this class hold? +Should you separate dice rolls from ability values? +Is the class better as a [dataclass][dataclass], with dice roll as a utility function? + +None of these (_including the analyzer complaint about too few methods_) is a hard and fast rule or requirement - all are considerations for the class as you build out a larger program. + + +[dataclass]: https://docs.python.org/3/library/dataclasses.html diff --git a/exercises/practice/dnd-character/.approaches/ability-method/snippet.txt b/exercises/practice/dnd-character/.approaches/ability-method/snippet.txt new file mode 100644 index 00000000000..272a00c1ca0 --- /dev/null +++ b/exercises/practice/dnd-character/.approaches/ability-method/snippet.txt @@ -0,0 +1,8 @@ + ... + def __init__(self): + for ability_type in self.ABILITIES: + setattr(self, ability_type, self.ability()) + + def ability(self): + values = sample(range(1, 7), 4) + return sum(values) - min(values) \ No newline at end of file diff --git a/exercises/practice/dnd-character/.approaches/config.json b/exercises/practice/dnd-character/.approaches/config.json new file mode 100644 index 00000000000..49a5332f5ad --- /dev/null +++ b/exercises/practice/dnd-character/.approaches/config.json @@ -0,0 +1,37 @@ +{ + "introduction": { + "authors": ["colinleach", + "BethanyG"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "952835d1-e9d1-4dc3-b2c2-aad703e8db2e", + "slug": "ability-method", + "title": "Ability Method", + "blurb": "Use one method to generate dice rolls and return ability values.", + "authors": ["bethanyg"] + }, + { + "uuid": "5022c80c-8ca1-45e4-aa5f-d91bb3f53441", + "slug": "dice-roll-static-method", + "title": "Dice Roll Static Method", + "blurb": "Use a separate static method to conduct dice rolls.", + "authors": ["bethanyg"] + }, + { + "uuid": "98af6c1d-5ab4-476d-9041-30b8f55e13eb", + "slug": "stand-alone-dice-roll-function", + "title": "Stand-alone Dice Roll Function", + "blurb": "Create a dice roll function outside the Character class.", + "authors": ["bethanyg"] + }, + { + "uuid": "5dd9d9b0-bfa5-43b2-8e95-787425349fc4", + "slug": "loop-and-setattr-in-init", + "title": "Loop and setattr in init", + "blurb": "Use a tuple of attributes, a loop, and setattr in init to assign ability values.", + "authors": ["bethanyg"] + } + ] +} diff --git a/exercises/practice/dnd-character/.approaches/dice-roll-static-method/content.md b/exercises/practice/dnd-character/.approaches/dice-roll-static-method/content.md new file mode 100644 index 00000000000..ef358c60698 --- /dev/null +++ b/exercises/practice/dnd-character/.approaches/dice-roll-static-method/content.md @@ -0,0 +1,47 @@ +# Move Dice Rolls Into a Static Method Separate from the `ability` Method + + +```python +from math import floor +from random import choice + +class Character: + def __init__(self): + self.strength = Character.dice_rolls() + self.dexterity = Character.dice_rolls() + self.constitution = Character.dice_rolls() + self.intelligence = Character.dice_rolls() + self.wisdom = Character.dice_rolls() + self.charisma = Character.dice_rolls() + + self.hitpoints = 10 + modifier(self.constitution) + + def ability(self): + return choice([*vars(self).values()]) + + @staticmethod + def dice_rolls(): + values = sorted(choice(range(1,7)) for dice in range(4))[::-1] + return sum(values[:-1]) + +def modifier(constitution): + return floor((constitution - 10)/2) +``` + +This approach separates the `ability()` method from a [`static method`][staticmethod] that calculates dice rolls. +`ability()` returns the value of a randomly chosen character ability using [`random.choice`][random-choice] but does not roll dice or calculate values. +Instead, `dice_rolls()` handles the rolls/values using [`random.choice`][random-choice] for selection. + +The argument for this is that the logic/functionality of rolling dice 4 times and summing the top three values is not really related to a DnD character or their abilities - it is independent and likely useful across a wider scope than just the character class. +However, it might be tidier to include it in the character class, rather than "clutter" the program or module with an additional stand-alone function. +Declaring `dice_rolls()` as a static method allows other callers to use the function with or without instantiating a new `Character` object. +It also makes it cleaner to maintain, should the method or number of the dice rolls change. + +`dice_rolls()` is then called in `__init__()` to populate the listed-out character attributes. +Note that it needs to be called with the class name: `Character.dice_rolls()`. +`self.hitpoints` then calls the second stand-alone `modifier()` function, adding it to 10 for the character's `hitpoints` attribute. +`modifieer()` in this example uses [`math.floor`][math-floor] for calculating the `hitpoints` value. + +[math-floor]: https://docs.python.org/3/library/math.html#math.floor +[random-choice]: https://docs.python.org/3/library/random.html#random.choice +[staticmethod]: https://docs.python.org/3/library/functions.html#staticmethod diff --git a/exercises/practice/dnd-character/.approaches/dice-roll-static-method/snippet.txt b/exercises/practice/dnd-character/.approaches/dice-roll-static-method/snippet.txt new file mode 100644 index 00000000000..4ed53a4e01f --- /dev/null +++ b/exercises/practice/dnd-character/.approaches/dice-roll-static-method/snippet.txt @@ -0,0 +1,8 @@ + ... + def ability(self): + return choice([*vars(self).values()]) + + @staticmethod + def dice_roll(): + values = sample(range(1, 7), 4) + return sum(sorted(values, reverse=True)[:-1]) \ No newline at end of file diff --git a/exercises/practice/dnd-character/.approaches/introduction.md b/exercises/practice/dnd-character/.approaches/introduction.md new file mode 100644 index 00000000000..d7b7c7febc2 --- /dev/null +++ b/exercises/practice/dnd-character/.approaches/introduction.md @@ -0,0 +1,261 @@ +# Introduction + +The DnD Character exercise has two main purposes: + +1. Practice class-based programming, especially initialization of instance variables and the creation of methods. +2. Practice the use of pseudo-random numbers and methods in the Python [`random`][random] module. + +There are no complicated decisions to make about which algorithm to use, as the tests for this exercise constrain the implementation. +However, there is variation in how class and object variables are declared and initialized, how methods are declared, and which random functions are employed. + +These approaches will mostly explore these variations in Python syntax. + + +## General considerations + +Several items are specifically required and tested for: + +- A standalone (_outside the `Character` class_) function called `modifier()`. +- A `Character` class. +- Instance variables for 6 named abilities, plus one for hit points. +- An instance method called `ability()` to return ability values. + +Further methods are optional, but may be helpful to simulate random dice rolls and calculate ability scores. +Some methods (_such as `ability()`, or the optional `dice_roll()`_) may be better as [static methods][static-methods], since they do not necessarily require the class or object as a parameter but are still tied to the class logic/purpose. + + +### The `modifier()` function + +This stand-alone function modifies the characters constitution score by subtracting 10 from the total and dividing by 2. + +In Python 3.x, the division operators are `/` for floating point and `//` for integer or 'floor' division. +`modifier()` needs to use integer division, and return a result that does not have a decimal. +Function equivalents to `/` and `//` are [`operator.truediv(a, b)`][operator-trudiv] and [`operator.floordiv(a, b)`][operator-floordiv] (or [`math.floor(x)`][math-floor]). + +Integer division will always round "down": not to the nearest integer and not towards zero: + +```python +>>> 11 // 3 +3 +>>> -11 // 3 +-4 +``` + +Here are examples using both operators and functions imported from `math` and from `operator`: + +```python +def modifier(constitution): + return (constitution - 10)//2 + +# You can import the math module and use `floor()` +from math import floor + +def modifier(constitution): + return floor((constitution - 10)/2) + +# Another strategy is to use `floordiv()` from the operator module. +from operator import floordiv + +def modifier(constitution): + return floordiv((constitution - 10), 2) + +``` + +Using function equivalents in a solution will work, but they do create overhead due to module import and function calls. + + +### Dice rolls + +The instructions are to roll four 6-sided dice and record the sum of the largest three rolls. + +To simulate a roll of the dice, we need the [`random`][random] module which produces pseudo-random numbers. +The [`secrets`][secrets] module, which produces cryptographically strong random numbers is not needed here. + +Within `random`, there are various suitable methods available. + These include [`random.randint()`][randint] to produce an integer in a range, [`random.choice()`][choice] to select from a sequence of values, or even [`random.sample()`][random-sample] for multiple rolls 'sampled' from a distribution, group, or range of values. + + ````exercism/note + +`randint(lower, upper)` _**includes**_ the upper bound, in contrast to the built-in [`range(lower, upper)`][range], or Python's [slice notation][slice] which both _**exclude**_ the upper bound. + +[slice]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[range]: https://docs.python.org/3/library/stdtypes.html#range +```` + + +To roll four dice may need a loop or comprehension: + +```python +from random import randint, choice, sample + + # Choosing a pseudo-random number between 1 and 6 (inclusive) four different times. + def dice_rolls_randint(self): + rolls = [] + + for dice in rage(4): + rolls.append(randint(1, 6)) + + return rolls + + # Choosing from a range sequence between 1 and 7 (exclusive) four different times. + def dice_rolls_choice(self): + return [choice(range(1,7)) for dice in range(4)] + + # Choosing for different 'samples' (rolls) from a sample range + # between 1 and 7 (exclusive). This avoids a comprehension, + # since random.sample returns a list of unique values from the range. + def four_dice_rolls_sample(self): + return sample(range(1, 7), 4) + +``` + +Some community solutions begin with a call to `random.seed()` but (_at least in recent versions of Python_) calling this without a parameter has exactly the same effect as omitting it. +The value of this method is in debugging situations when it helps to have a reproducible sequence of results. +Then we would call it with a known seed such as `random.seed(42)`. + +After rolling four dice, we next need to sum the largest three scores, discarding the lowest. +Many programmers use `sorted()` and a slice for determining the three largest values: + + +```python +# The dice variable was generated as a 4-element list, as above. +sum(sorted(dice)[1:]) + +# The list can also be reverse sorted. +sum(sorted(dice, reverse=True)[:-1]) + +``` + +If slicing is hard to read or confusing, the built-ins `sum()` and `min()` can be used to subtract the smallest score from the total. +In this second case, `dice` can be any sequence, not just a list. +Because we are calculating a `min()` value, a generator expression cannot be used. + +```python +# The dice variable was generated as a 4-element sequence, as above. +sum(dice) - min(dice) + +``` + +This strategy might be considered more readable to some, since `min()` is very clear as to what is being calculated. +This is also more efficient when the input list is long and the order of values doesn't need to be preserved. + + +### The ability() Method + +The directions do not state how `ability()` should be implemented, but a look at [the tests][dnd-tests] indicate that any of the character's ability scores or a freshly generated score can be returned provided the score falls in the required range. +This means that the method can return a random ability value chosen from `vars(self).values()` **or** it can calculate and return a random value for use in an ability score. +Some solutions use this method as their "dice roll", and call it to populate abilities in `__init__`. +Other solutions separate out "dice rolls" or "ability score" logic into a separate method or function, leaving `ability()` as a method that returns a random choice from the character's already assigned abilities. + +Either strategy above will pass the tests, but separating out "dice roll" logic from a random character ability score can be both clearer and more reusable in scenarios where the same "dice roll" is used to calculate/populate some other metric. +Moving the "dice roll" or "ability score" out of the class altogether or making it a static method also lets it be called/used without instantiating the class and can make it clearer what is being calculated. + + +## Class initialization + +The various abilities need to be set just once for each character, which is most conveniently done in the class `__init__` method. + +The examples below assume that `modifier()` and `self.ability()` are implemented as discussed in the sections above. +The explicit approach is simple but rather verbose and repetitive. +It does have the advantage of declaring the abilities very explicitly, so that a reader can quickly determine how many and of what type the abilities are: + + +```python +class Character: + def __init__(self): + self.strength = self.ability() + self.dexterity = self.ability() + self.constitution = self.ability() + self.intelligence = self.ability() + self.wisdom = self.ability() + self.charisma = self.ability() + self.hitpoints = 10 + modifier(self.constitution) + + ... + + +# Using a dice_roll static method instead of self.ability() +class Character: + def __init__(self): + self.strength = Character.dice_roll() + self.dexterity = Character.dice_roll() + self.constitution = Character.dice_roll() + self.intelligence = Character.dice_roll() + self.wisdom = Character.dice_roll() + self.charisma = Character.dice_roll() + self.hitpoints = 10 + modifier(self.constitution) + + ... +``` + + +Alternatively, we could start from an iterable of ability names and loop over these using [`setattr()`][setattr] to write the values into the objects attribute dictionary. +This sacrifices a bit of readability/clarity for less verbosity and (somewhat) easier ability additions: + + +```python +# Setting a global ABILITIES constant. +# This enables other classes to "see" the abilities. +# This could be useful for a larger program that modifies +# or adds abilities outside the Character class. +ABILITIES = ('strength', 'dexterity', 'constitution', + 'intelligence', 'wisdom','charisma') + + +class Character: + def __init__(self): + + for ability_name in ABILITIES: + setattr(self, ability_name, self.ability()) + + self.hitpoints = modifier(self.constitution) + 10 + + +# Conversely, we can declare ABILITIES as a +# class attribute. This ensures that all objects made from +# the class share ABILITIES and they are only added or +# modified through the Character class. ABILITIES are not +# visible globally. +class Character: + + abilities = ('strength', 'dexterity', 'constitution', + 'intelligence', 'wisdom','charisma') + + def __init__(self): + + for ability_name in Character.abilities: + setattr(self, ability_name, self.ability()) + + self.hitpoints = modifier(self.constitution) + 10 +``` + +Listing out each ability vs looping through and using `setatter()` has identical results for the object. +Both calculate a score for an ability and write that ability + score into the object attribute dictionary when an object is instantiated from the class. + + +## Putting things together + +The four approaches below combine various options from the previous sections to show how a solution would work with them. +More variations are possible, but these cover most of the main decision differences. + +- [One `ability` method][approach-ability-method] +- [Dice roll static method][approach-dice-roll-static-method] +- [Dice roll stand-alone method][approach-stand-alone-dice-roll-function] +- [Loop and `setatter()` in `__init__`][approach-loop-and-setattr-in-init] + + +[approach-ability-method]: https://exercism.org/tracks/python/exercises/dnd-character/approaches/ability-method +[approach-dice-roll-static-method]: https://exercism.org/tracks/python/exercises/dnd-character/approaches/dice-roll-static-method +[approach-loop-and-setattr-in-init]: https://exercism.org/tracks/python/exercises/dnd-character/approaches/loop-and-setattr-in-init +[approach-stand-alone-dice-roll-function]: https://exercism.org/tracks/python/exercises/dnd-character/approaches/tand-alone-dice-roll-function +[choice]: https://docs.python.org/3/library/random.html#random.choice +[dnd-tests]: https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/dnd_character_test.py +[math-floor]: https://docs.python.org/3/library/math.html#math.floor +[operator-floordiv]: https://docs.python.org/3/library/operator.html#operator.floordiv +[operator-trudiv]: https://docs.python.org/3/library/operator.html#operator.truediv +[randint]: https://docs.python.org/3/library/random.html#random.randint +[random-sample]: https://docs.python.org/3/library/random.html#random.sample +[random]: https://exercism.org/tracks/python/concepts/random +[secrets]: https://docs.python.org/3/library/secrets.html +[setattr]: https://docs.python.org/3/library/functions.html#setattr +[static-methods]: https://www.digitalocean.com/community/tutorials/python-static-method diff --git a/exercises/practice/dnd-character/.approaches/loop-and-setattr-in-init/content.md b/exercises/practice/dnd-character/.approaches/loop-and-setattr-in-init/content.md new file mode 100644 index 00000000000..9db0033909f --- /dev/null +++ b/exercises/practice/dnd-character/.approaches/loop-and-setattr-in-init/content.md @@ -0,0 +1,84 @@ +# Loop Through an Abilities Tuple to Set Attribute Values in `__init__` + + +```python +from random import choice, sample + +class Character: + + abilities = ('strength', 'dexterity', 'constitution', 'intelligence', 'wisdom', 'charisma') + + def __init__(self): + for ability_type in Character.abilities: + setattr(self, ability_type, Character.dice_rolls()) + + self.hitpoints = 10 + modifier(self.constitution) + + def ability(self): + return choice([*vars(self).values()]) + + @staticmethod + def dice_rolls(): + values = sample(range(1, 7), 4) + return sum(values) - min(values) + +def modifier(constitution): + return (constitution - 10)//2 +``` + + +This approach uses a `tuple` to hold character attributes in a [`class variable`][class-variable] or `class attribute`. +Since this variable is common to all instances of the class, it can be looped through during object initialization to create instance variables and assign them values using [`setattr][setattr]. + +This strategy has several benefits: +1. The `__init__` is less verbose and the abilities are easier to maintain. +2. Organizationally, attributes remain with the class, making it clearer where they apply. +3. Attributes are inherited in any subclass and can be added to or overridden by them. +4. Changes to attributes are reflected in all new objets and subclasses automatically. + +Because `hitpoints` is calculated differently, it is assigned a value outside the `tuple` and `loop`. + +The remainder of the class body is the same as in the [dice roll static method][approach-dice-roll-static-method] approach (_as is the variant below_). + + +```python +from random import choice, sample + +CHARACTER_ABILITIES = ('strength', 'dexterity', 'constitution', + 'intelligence', 'wisdom', 'charisma') + +class Character: + + def __init__(self): + for ability_type in CHARACTER_ABILITIES: + setattr(self, ability_type, Character.dice_rolls()) + + self.hitpoints = 10 + modifier(self.constitution) + + def ability(self): + return choice([*vars(self).values()]) + + @staticmethod + def dice_rolls(): + values = sample(range(1, 7), 4) + return sum(values) - min(values) + +def modifier(constitution): + return (constitution - 10)//2 +``` + +Above, the character attributes are moved out of the class into a [`constant`][constant] at the module level. +The Character `__init__` method loops through them using `setattr` to create instance variables and assign them values, similar to the first example. +Again, because `hitpoints` is calculated differently, it is assigned a value outside the `tuple` and `loop`. +Making the character attributes a constant has the advantage of being visible to all other classes and functions defined in the module. + This could be easier if the attributes are being used by multiple classes beyond the Character class. +This also avoids having to reference the Character class when using or modifying the abilities and could help with clarity and maintenance in the larger program. +However, modifying the abilities in this context would also be visible at the module level, and could have wide or unintended consequences, so should be commented/documented carefully. + +The remainder of the class body is the same as in the [dice roll static method][approach-dice-roll-static-method] approach (_as is the variant below_). + + +[approach-dice-roll-static-method]: https://exercism.org/tracks/python/exercises/dnd-character/approaches/dice-roll-static-method +[class-variable]: https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables +[constant]: https://peps.python.org/pep-0008/#constants +[setattr]: https://docs.python.org/3/library/functions.html#setattr diff --git a/exercises/practice/dnd-character/.approaches/loop-and-setattr-in-init/snippet.txt b/exercises/practice/dnd-character/.approaches/loop-and-setattr-in-init/snippet.txt new file mode 100644 index 00000000000..353296a9576 --- /dev/null +++ b/exercises/practice/dnd-character/.approaches/loop-and-setattr-in-init/snippet.txt @@ -0,0 +1,8 @@ +class Character: + ABILITIES = ('strength', 'dexterity', 'constitution', 'intelligence', 'wisdom', 'charisma') + + def __init__(self): + for ability_type in self.ABILITIES: + setattr(self, ability_type, self.ability()) + self.hitpoints = 10 + modifier(self.constitution) + ... \ No newline at end of file diff --git a/exercises/practice/dnd-character/.approaches/stand-alone-dice-roll-function/content.md b/exercises/practice/dnd-character/.approaches/stand-alone-dice-roll-function/content.md new file mode 100644 index 00000000000..66d293a07d6 --- /dev/null +++ b/exercises/practice/dnd-character/.approaches/stand-alone-dice-roll-function/content.md @@ -0,0 +1,99 @@ +# Separate Dice Rolls into a Stand-Alone Dice Roll Function + + +```python +from random import choice, randint +from operator import floordiv + + +class Character: + def __init__(self): + self.strength = dice_rolls() + self.dexterity = dice_rolls() + self.constitution = dice_rolls() + self.intelligence = dice_rolls() + self.wisdom = dice_rolls() + self.charisma = dice_rolls() + + self.hitpoints = 10 + modifier(self.constitution) + + def ability(self): + return choice([*vars(self).values()]) + + +def dice_rolls(): + values = sorted(randint(1, 6) for item in range(4)) + return sum(values[1:]) + +def modifier(constitution): + return floordiv((constitution - 10), 2) +``` + + +This approach separates the `ability()` method from a stand-alone function that calculates dice rolls. +`ability()` returns the value of a randomly chosen character ability using [`random.choice`][randon-choice], but does not roll dice or calculate values. +Instead, `dice_rolls()` handles the rolls/values, using [`random.randint`][random-randint] to generate them. +The argument for this is that the logic/functionality of rolling dice 4 times and summing the top three values is not really related to a DnD character or their abilities - it is independent and likely useful across a wider scope than just the character class. +It also makes it cleaner to maintain, should the method or number of the dice rolls change. + +`dice_rolls()` is then called in `__init__()` to populate the listed-out character attributes. +`self.hitpoints` calls the second stand-alone `modifier()` function, adding it to 10 for the character's `hitpoints` attribute. +Note that `modifier()` uses the [`operator.floordiv`][operator-floordiv] method to trunkate the value. + +This approach is valid and passes all the tests. +However, it will trigger an analyzer comment about there being "too few public methods", since there are no methods for this class beyond `ability()`. + +The "too few" rule encourages you to think about the design of the class: is it worth the effort to create the class if it only holds attribute values for a character? +What other functionality should this class hold? +Should the `dice_roll()` function be outside or inside (_as a regular method or a static method_) the class? + +None of these (_including the analyzer complaint about too few methods_) is a hard and fast rule or requirement - all are considerations for the class as you build out a larger program. + +An alternative is to write a [dataclass][dataclass], although the design discussion and questions above remain the same: + + +```python +from random import choice, sample +from dataclasses import dataclass + +@dataclass +class Character: + + strength: int = 0 + dexterity: int = 0 + constitution: int = 0 + intelligence: int = 0 + wisdom: int = 0 + charisma: int = 0 + hitpoints: int = 0 + + def __post_init__(self): + for ability in vars(self): + setattr(self, ability, dice_rolls()) + + self.hitpoints = 10 + modifier(self.constitution) + + def ability(self): + return choice([*vars(self).values()]) + + +def dice_rolls(): + values = sample(range(1, 7), 4) + return sum(values) - min(values) + +def modifier(constitution): + return (constitution - 10)//2 +``` + + +Note that here there is a [`__post_init__`][post-init] method to assign ability values to the attributes, and that the attributes must start with a default value (_otherwise, they can't be assigned to in post-init_). +`hitpoints` has the same treatment as the other attributes, and requires assignment in post-init. + +`dice_rolls()` uses [`random.sample`][random-sample] for roll values here and `modifier()` uses the floor-division operator `//`. + +[dataclass]: https://docs.python.org/3/library/dataclasses.html +[operator-floordiv]: https://docs.python.org/3/library/operator.html#operator.floordiv +[post-init]: https://docs.python.org/3/library/dataclasses.html#post-init-processing +[random-randint]: https://docs.python.org/3/library/random.html#random.randint +[random-sample]: https://docs.python.org/3/library/random.html#random.randint +[randon-choice]: https://docs.python.org/3/library/random.html#random.choice diff --git a/exercises/practice/dnd-character/.approaches/stand-alone-dice-roll-function/snippet.txt b/exercises/practice/dnd-character/.approaches/stand-alone-dice-roll-function/snippet.txt new file mode 100644 index 00000000000..eaa4735de98 --- /dev/null +++ b/exercises/practice/dnd-character/.approaches/stand-alone-dice-roll-function/snippet.txt @@ -0,0 +1,8 @@ +class Character: + ... + def ability(self): + return choice([*vars(self).values()]) + +def dice_roll(): + values = sample(range(1, 7), 4) + return sum(sorted(values)[1:]) \ No newline at end of file