Skip to content

Commit edc283f

Browse files
committed
dice with late stat modifier
1 parent 7c1be49 commit edc283f

File tree

4 files changed

+231
-71
lines changed

4 files changed

+231
-71
lines changed

lib/dice.dart

+82-38
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,35 @@ class Dice {
55
Dice({
66
required this.amount,
77
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+
: "+");
1017

1118
final int amount;
1219
final int sides;
13-
final int? modifier;
20+
final int? modifierValue;
21+
final String? modifierStat;
22+
final String modifierSign;
1423

1524
Dice copyWith({
1625
int? amount,
1726
int? sides,
18-
int? modifier,
27+
int? modifierValue,
28+
String? modifierSign,
29+
String? modifierStat,
1930
}) =>
2031
Dice(
2132
amount: amount ?? this.amount,
2233
sides: sides ?? this.sides,
23-
modifier: modifier ?? this.modifier,
34+
modifierSign: modifierSign ?? this.modifierSign,
35+
modifierValue: modifierValue ?? this.modifierValue,
36+
modifierStat: modifierStat ?? this.modifierStat,
2437
);
2538

2639
factory Dice.fromRawJson(String str) => Dice.fromJson(json.decode(str));
@@ -33,21 +46,18 @@ class Dice {
3346
static Dice d20 = Dice(amount: 1, sides: 20);
3447
static Dice d60 = Dice(amount: 1, sides: 60);
3548
static Dice d100 = Dice(amount: 1, sides: 100);
49+
static final _dicePattern = RegExp(r'(\d+)d([0-9]+)(([+-])([0-9a-z]+))?', caseSensitive: false);
3650

3751
String toRawJson() => toJson();
3852

3953
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;
5161

5262
if (sides == null || amount == null) {
5363
throw Exception("Dice parsing failed");
@@ -56,50 +66,84 @@ class Dice {
5666
return Dice(
5767
amount: amount,
5868
sides: sides,
59-
modifier: modifier,
69+
modifierValue: modifierSign == '-' ? -(modifierValue ?? 0) : modifierValue,
70+
modifierSign: modifierSign,
71+
modifierStat: modifierStat,
6072
);
6173
}
6274

75+
Dice copyWithModifierValue(int statValue) =>
76+
copyWith(amount: amount, sides: sides, modifierValue: statValue);
77+
6378
@override
6479
String toString() => "${amount}d$sides$modifierWithSign";
6580

6681
String toJson() => toString();
6782

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);
7387

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;
75104

76105
operator *(int amount) => copyWith(amount: this.amount * amount);
77106
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+
}
78121
}
79122

80-
class DiceResult {
123+
class DiceRoll {
81124
final Dice dice;
82125
final List<int> results;
83126

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();
94129
}
95130

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);
98134

99135
bool get didHitNaturalMax => indexOfNaturalMax >= 0;
100136
int get indexOfNaturalMax => results.indexOf(dice.sides);
101137

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+
}
104148
}
105149
}

lib/models/repository.dart

+116-20
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,134 @@
1-
class Repository {}
1+
import '../character_class.dart';
2+
import '../alignment.dart';
3+
import '../item.dart';
4+
import '../monster.dart';
5+
import '../move.dart';
6+
import '../race.dart';
7+
import '../spell.dart';
8+
import '../tag.dart';
29

3-
class RepositoryItem<T> {
4-
final Map<String, Map<String, T>> _cache = {};
5-
String currentLocale;
10+
class Repository {
11+
String _currentLocale;
12+
String get currentLocale => _currentLocale;
13+
final Set<String> _locales = {};
614

7-
RepositoryItem({
8-
required this.currentLocale,
9-
});
15+
final Map<String, RepositoryItem<AlignmentValue>> _alignments = {};
16+
final Map<String, RepositoryItem<CharacterClass>> _classes = {};
17+
final Map<String, RepositoryItem<Item>> _items = {};
18+
final Map<String, RepositoryItem<Monster>> _monsters = {};
19+
final Map<String, RepositoryItem<Race>> _races = {};
20+
final Map<String, RepositoryItem<Move>> _moves = {};
21+
final Map<String, RepositoryItem<Spell>> _spells = {};
22+
final Map<String, RepositoryItem<Tag>> _tags = {};
1023

11-
List<T> get list {
12-
_ensureLocale(currentLocale);
13-
return _cache[currentLocale]!.values.toList();
14-
}
24+
Repository({
25+
required String currentLocale,
26+
}) : _currentLocale = currentLocale;
1527

16-
Map<String, T> get map {
17-
_ensureLocale(currentLocale);
18-
return _cache[currentLocale]!;
19-
}
28+
RepositoryItem<AlignmentValue> get alignments => _withCurrentLocale(_alignments);
29+
RepositoryItem<CharacterClass> get classes => _withCurrentLocale(_classes);
30+
RepositoryItem<Item> get items => _withCurrentLocale(_items);
31+
RepositoryItem<Monster> get monsters => _withCurrentLocale(_monsters);
32+
RepositoryItem<Race> get races => _withCurrentLocale(_races);
33+
RepositoryItem<Move> get moves => _withCurrentLocale(_moves);
34+
RepositoryItem<Spell> get spells => _withCurrentLocale(_spells);
35+
RepositoryItem<Tag> get tags => _withCurrentLocale(_tags);
2036

2137
void registerLocale(String locale) {
22-
if (_cache[locale] != null) {
38+
if (localeExists(locale)) {
2339
return;
2440
}
25-
_cache[locale] = {};
41+
42+
for (var entry in _allRepositories.entries) {
43+
if (entry.value[locale] != null) {
44+
continue;
45+
}
46+
entry.value[locale] = RepositoryItem(locale: locale);
47+
}
48+
49+
_locales.add(locale);
2650
}
2751

28-
void addItems(String locale, Map<String, T> items) {
52+
void changeLocale(String locale) {
2953
_ensureLocale(locale);
30-
_cache[locale]!.addAll(items);
54+
_currentLocale = locale;
55+
}
56+
57+
void loadItems<T>(String locale, Map<String, T> items) {
58+
registerLocale(locale);
59+
switch (T) {
60+
case AlignmentValue:
61+
_alignments[locale]!.addItems(items.cast<String, AlignmentValue>());
62+
break;
63+
case CharacterClass:
64+
_classes[locale]!.addItems(items.cast<String, CharacterClass>());
65+
break;
66+
case Item:
67+
_items[locale]!.addItems(items.cast<String, Item>());
68+
break;
69+
case Monster:
70+
_monsters[locale]!.addItems(items.cast<String, Monster>());
71+
break;
72+
case Race:
73+
_races[locale]!.addItems(items.cast<String, Race>());
74+
break;
75+
case Move:
76+
_moves[locale]!.addItems(items.cast<String, Move>());
77+
break;
78+
case Spell:
79+
_spells[locale]!.addItems(items.cast<String, Spell>());
80+
break;
81+
case Tag:
82+
_tags[locale]!.addItems(items.cast<String, Tag>());
83+
break;
84+
default:
85+
throw Exception("Type $T not supported");
86+
}
3187
}
3288

89+
Map<String, Map<String, RepositoryItem<dynamic>>> get _allRepositories => {
90+
'alignments': _alignments,
91+
'classes': _classes,
92+
'items': _items,
93+
'monsters': _monsters,
94+
'races': _races,
95+
'moves': _moves,
96+
'spells': _spells,
97+
'tags': _tags,
98+
};
99+
33100
void _ensureLocale(String locale) {
34-
if (_cache[locale] == null) {
101+
if (!localeExists(locale)) {
35102
throw Exception('Locale $locale does not exist.');
36103
}
37104
}
105+
106+
bool localeExists(String locale) => _locales.contains(locale);
107+
108+
T _withCurrentLocale<T>(Map<String, T> iter) {
109+
_ensureLocale(currentLocale);
110+
if (iter.isEmpty || iter[currentLocale] == null) {
111+
throw Exception('Repository Item does not exist for locale $currentLocale');
112+
}
113+
return iter[currentLocale]!;
114+
}
115+
}
116+
117+
class RepositoryItem<T> {
118+
final Map<String, T> _cache = {};
119+
final String locale;
120+
121+
RepositoryItem({required this.locale});
122+
123+
List<T> get list {
124+
return _cache.values.toList();
125+
}
126+
127+
Map<String, T> get map {
128+
return _cache;
129+
}
130+
131+
void addItems(Map<String, T> items) {
132+
_cache!.addAll(items);
133+
}
38134
}

scripts/migrate/v2_to_v3.dart

+5-8
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ main() async {
6666
// Races
6767
print("Adding ${cls.raceMoves.length} races");
6868
for (var race in cls.raceMoves) {
69-
json['races']!
70-
.add(raceMapper(race, cls.key ?? makeKey(cls.name)).toJson());
69+
json['races']!.add(raceMapper(race, cls.key ?? makeKey(cls.name)).toJson());
7170
}
7271

7372
// Classes
@@ -121,12 +120,12 @@ String makeKey(String str) {
121120
}
122121

123122
Set<Dice> guessDice(String str) {
124-
var basicRollPattern = RegExp(r'\broll\+[a-z]{3}\b', caseSensitive: false);
123+
var basicRollPattern = RegExp(r'\broll([+-][a-z]+)\b', caseSensitive: false);
125124
var dicePattern = RegExp(r'\b\dd\d\b', caseSensitive: false);
126125
var found = <Dice>{};
127126
var basicRollMatches = basicRollPattern.allMatches(str);
128127
for (var match in basicRollMatches) {
129-
found.add(Dice.d6 * 2);
128+
found.add(Dice.fromJson('2d6' + match.group(1)!.toUpperCase()));
130129
}
131130
var diceMatches = dicePattern.allMatches(str);
132131
for (var match in diceMatches) {
@@ -212,10 +211,8 @@ CharacterClass classMapper(old.PlayerClass cls) => CharacterClass(
212211
items: [
213212
GearOption(
214213
amount: o.name.contains(RegExp(r'[0-9]+'))
215-
? double.tryParse(RegExp(r'[0-9]+')
216-
.firstMatch(o.name)
217-
?.group(0) ??
218-
"1.0") ??
214+
? double.tryParse(
215+
RegExp(r'[0-9]+').firstMatch(o.name)?.group(0) ?? "1.0") ??
219216
1.0
220217
: 1.0,
221218
item: Item(

0 commit comments

Comments
 (0)