Skip to content

Commit 768b3c5

Browse files
committed
[D&D Character] draft approaches
1 parent d8b3979 commit 768b3c5

2 files changed

Lines changed: 143 additions & 0 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"introduction": {
3+
"authors": ["colinleach",
4+
"BethanyG"],
5+
"contributors": []
6+
}
7+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Introduction
2+
3+
This exercise has two main purposes:
4+
5+
- To practice class-based programming, especially initialization of instance variables.
6+
- To practice random numbers.
7+
8+
There are no complicated decisions to make about which algorithm to use, as the tests constrain the implementation.
9+
10+
This document will mostly explore some variations in Python syntax.
11+
12+
## General considerations
13+
14+
Several items are specifically required by the tests:
15+
16+
- A standalone function called `modifier()`.
17+
- A `Character` class.
18+
- Instance variables for 6 named abilities, plus one for hitpoints.
19+
- An instance method called `ability()` to handle dice throws.
20+
21+
Further methods are optional, but may be helpful with random dice rolls.
22+
23+
## The `modifier()` function
24+
25+
This function needs integer division:
26+
27+
```python
28+
def modifier(constitution):
29+
return (constitution - 10) // 2
30+
```
31+
32+
In Python 3.x, the division operators are `/` for floating point and `//` for integer division (ignore any old Python 2.x documentation you may find).
33+
34+
Integer division will always round *down*: not to the nearest integer and not towards zero:
35+
36+
```python
37+
>>> 11 // 3
38+
3
39+
>>> -11 // 3
40+
-4
41+
```
42+
43+
Using `math.floor()` here will work, but slightly misses the point of what is being practised, as well as forcing an unnecessary import.
44+
45+
## Dice rolls
46+
47+
The instructions are to roll four 6-sided dice and record the sum of the largest three dice.
48+
49+
To roll a die we need the [`random`][random] module.
50+
51+
There are various suitable options available, including `random.randint()` and `random.choice()` for a single throw or `random.sample()` for multiple rolls.
52+
Note that `randint(lower, upper)` *includes* the upper bound, in contrast to `range(lower, uppper + 1)`.
53+
54+
To roll four dice may need a list comprehension:
55+
56+
```python
57+
def dice_rolls_1(self):
58+
return [random.randint(1, 6) for _ in range(4)]
59+
60+
def dice_rolls_2(self):
61+
return [random.choice(range(1,7)) for _ in range(4)]
62+
63+
def four_dice_rolls(self):
64+
return random.sample(range(1, 7), 4)
65+
```
66+
67+
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.
68+
The value of this method is in debugging situations when it helps to have a reproducible sequence of results.
69+
Then we would call it with a known seed such as `random.seed(42)`.
70+
71+
After rolling four dice, next we need to sum the largest three scores, discarding the lowest.
72+
73+
Most Python programmers use a list sort and slicing for this:
74+
75+
```python
76+
# the dice variable was generated as a 4-element list, as above
77+
sum(sorted(dice)[1:4])
78+
```
79+
80+
In some ways simpler, we could use the built-in `sum()` and `min()` functions to subtract the smallest score from the total:
81+
82+
```python
83+
# the dice variable was generated as a 4-element enumerable, as above
84+
sum(dice) - min(dice)
85+
```
86+
87+
In this second case, `dice` can be any enumerable, not just a list.
88+
89+
## Class initialization
90+
91+
The various abilities need to be set just once for each character, which is most conveniently done in the class initializer.
92+
93+
The examples below assume that `modifier()` and `self.ability()` are implemented as dicussed above
94+
95+
The explicit approach is simple but rather verbose and repetitive:
96+
97+
```python
98+
class Character:
99+
def __init__(self):
100+
self.strength = self.ability()
101+
self.dexterity = self.ability()
102+
self.constitution = self.ability()
103+
self.intelligence = self.ability()
104+
self.wisdom = self.ability()
105+
self.charisma = self.ability()
106+
self.hitpoints = 10 + modifier(self.constitution)
107+
```
108+
109+
Alternatively, we could start from a tuple of ability names then loop over these using [`setattr()`][setattr]:
110+
111+
```python
112+
ABILITIES = (
113+
'strength',
114+
'dexterity',
115+
'constitution',
116+
'intelligence',
117+
'wisdom',
118+
'charisma'
119+
)
120+
121+
class Character:
122+
def __init__(self):
123+
for ability_name in ABILITIES:
124+
setattr(self, ability_name, self.ability())
125+
self.hitpoints = modifier(self.constitution) + 10
126+
```
127+
128+
The key to this is that these two expressions have identical results:
129+
130+
```python
131+
setattr(self, 'strength`, self.ability())
132+
self.strength = self.ability()
133+
```
134+
135+
[random]: https://exercism.org/tracks/python/concepts/random
136+
[setattr]: https://docs.python.org/3/library/functions.html#setattr

0 commit comments

Comments
 (0)