@@ -5,22 +5,35 @@ class Dice {
5
5
Dice ({
6
6
required this .amount,
7
7
required this .sides,
8
- this .modifier,
9
- });
8
+ this .modifierValue,
9
+ this .modifierStat,
10
+ String ? modifierSign,
11
+ }) : modifierSign = modifierSign ??
12
+ (modifierValue != null
13
+ ? modifierValue >= 0
14
+ ? "+"
15
+ : "-"
16
+ : "+" );
10
17
11
18
final int amount;
12
19
final int sides;
13
- final int ? modifier;
20
+ final int ? modifierValue;
21
+ final String ? modifierStat;
22
+ final String modifierSign;
14
23
15
24
Dice copyWith ({
16
25
int ? amount,
17
26
int ? sides,
18
- int ? modifier,
27
+ int ? modifierValue,
28
+ String ? modifierSign,
29
+ String ? modifierStat,
19
30
}) =>
20
31
Dice (
21
32
amount: amount ?? this .amount,
22
33
sides: sides ?? this .sides,
23
- modifier: modifier ?? this .modifier,
34
+ modifierSign: modifierSign ?? this .modifierSign,
35
+ modifierValue: modifierValue ?? this .modifierValue,
36
+ modifierStat: modifierStat ?? this .modifierStat,
24
37
);
25
38
26
39
factory Dice .fromRawJson (String str) => Dice .fromJson (json.decode (str));
@@ -33,21 +46,18 @@ class Dice {
33
46
static Dice d20 = Dice (amount: 1 , sides: 20 );
34
47
static Dice d60 = Dice (amount: 1 , sides: 60 );
35
48
static Dice d100 = Dice (amount: 1 , sides: 100 );
49
+ static final _dicePattern = RegExp (r'(\d+)d([0-9]+)(([+-])([0-9a-z]+))?' , caseSensitive: false );
36
50
37
51
String toRawJson () => toJson ();
38
52
39
53
factory Dice .fromJson (String json) {
40
- var parts = json.split ("d" );
41
- var amount = int .tryParse (parts[0 ]);
42
- int ? sides;
43
- int ? modifier;
44
- if (parts[1 ].contains (RegExp (r'[-+]' ))) {
45
- var idx = parts[1 ].indexOf (RegExp (r'[^0-9]' ));
46
- sides = int .tryParse (parts[1 ].substring (0 , idx));
47
- modifier = int .tryParse (parts[1 ].substring (idx));
48
- } else {
49
- sides = int .tryParse (parts[1 ]);
50
- }
54
+ var matches = _diceMatches (json);
55
+ var amount = int .tryParse (matches[0 ]! );
56
+ var sides = int .tryParse (matches[1 ]! );
57
+ var modifierSign = matches[2 ];
58
+ var modifierValue = matches[3 ] != null ? int .tryParse (matches[3 ]! ) : null ;
59
+ var modifierStat =
60
+ matches[3 ] != null && modifierValue == null ? matches[3 ]! .toUpperCase () : null ;
51
61
52
62
if (sides == null || amount == null ) {
53
63
throw Exception ("Dice parsing failed" );
@@ -56,50 +66,84 @@ class Dice {
56
66
return Dice (
57
67
amount: amount,
58
68
sides: sides,
59
- modifier: modifier,
69
+ modifierValue: modifierSign == '-' ? - (modifierValue ?? 0 ) : modifierValue,
70
+ modifierSign: modifierSign,
71
+ modifierStat: modifierStat,
60
72
);
61
73
}
62
74
75
+ Dice copyWithModifierValue (int statValue) =>
76
+ copyWith (amount: amount, sides: sides, modifierValue: statValue);
77
+
63
78
@override
64
79
String toString () => "${amount }d$sides $modifierWithSign " ;
65
80
66
81
String toJson () => toString ();
67
82
68
- String get modifierWithSign => modifier == null
69
- ? ""
70
- : modifier! > 0
71
- ? "+$modifier "
72
- : "$modifier " ;
83
+ String get modifierWithSign =>
84
+ hasModifier ? "$modifierSign ${modifierValue ?.abs () ?? modifierStat }" : "" ;
85
+
86
+ bool get hasModifier => (modifierValue != null || modifierStat != null );
73
87
74
- DiceResult roll () => DiceResult .roll (this );
88
+ String get modifier => hasModifier ? modifierStat ?? modifierValue! .toString () : "" ;
89
+
90
+ DiceRoll roll () {
91
+ if (needsModifier) {
92
+ throw Exception ("Dice is being rolled without an actual modifier."
93
+ "Use `copyWithModifierValue`.\n "
94
+ "Expected modifier: $modifierWithSign " );
95
+ }
96
+ var arr = < int > [];
97
+ for (var i = 0 ; i < amount; i++ ) {
98
+ arr.add (Random ().nextInt (sides));
99
+ }
100
+ return DiceRoll (dice: this , results: arr);
101
+ }
102
+
103
+ bool get needsModifier => modifierStat != null && modifierValue == null ;
75
104
76
105
operator * (int amount) => copyWith (amount: this .amount * amount);
77
106
operator / (int amount) => copyWith (amount: this .amount ~ / amount);
107
+
108
+ static List <String ?> _diceMatches (String json) {
109
+ _assertDicePattern (json);
110
+ var m = _dicePattern.firstMatch (json)! ;
111
+ return m.groups ([1 , 2 , 4 , 5 ]);
112
+ }
113
+
114
+ static void _assertDicePattern (String dice) {
115
+ if (! _dicePattern.hasMatch (dice)) {
116
+ throw Exception ("Dice format is invalid, must be {amount}d{sides}([+-]{modifier})"
117
+ "(e.g. 1d20, 2d6+DEX, 1d8-3)\n "
118
+ "Received: $dice " );
119
+ }
120
+ }
78
121
}
79
122
80
- class DiceResult {
123
+ class DiceRoll {
81
124
final Dice dice;
82
125
final List <int > results;
83
126
84
- DiceResult ({required this .dice, required this .results});
85
-
86
- static List <DiceResult > rollMany (List <Dice > dice) {
87
- return dice.map ((d) {
88
- var arr = < int > [];
89
- for (var i = 0 ; i < d.amount; i++ ) {
90
- arr.add (Random ().nextInt (d.sides));
91
- }
92
- return DiceResult (dice: d, results: arr);
93
- }).toList ();
127
+ DiceRoll ({required this .dice, required this .results}) {
128
+ assertDiceModifier ();
94
129
}
95
130
96
- int get total =>
97
- results.reduce ((all, cur) => all + cur) + (dice.modifier ?? 0 );
131
+ static List <DiceRoll > rollMany (List <Dice > dice) => dice.map ((d) => roll (d)).toList ();
132
+
133
+ int get total => results.reduce ((all, cur) => all + cur) + (dice.modifierValue ?? 0 );
98
134
99
135
bool get didHitNaturalMax => indexOfNaturalMax >= 0 ;
100
136
int get indexOfNaturalMax => results.indexOf (dice.sides);
101
137
102
- static DiceResult roll (Dice dice) {
103
- return rollMany ([dice])[0 ];
138
+ static DiceRoll roll (Dice dice) {
139
+ return dice.roll ();
140
+ }
141
+
142
+ void assertDiceModifier () {
143
+ if (dice.needsModifier) {
144
+ throw Exception ("Dice is being rolled without an actual modifier."
145
+ "Use `dice.copyWithModifierValue(int modifierValue)`.\n "
146
+ "Expected modifier: ${dice .modifierWithSign }" );
147
+ }
104
148
}
105
149
}
0 commit comments