From 7c1be49dde90311ac1415437e68ac8f6ea975230 Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Mon, 14 Feb 2022 01:55:13 +0200 Subject: [PATCH] new classes wip + parsing --- .vscode/launch.json | 57 ++++---- analysis_options.yaml | 2 +- lib/_utils/uuid.dart | 3 + lib/alignment.dart | 88 +++++++++++++ lib/bond.dart | 40 ++++++ lib/character_class.dart | 89 +++++++++++++ lib/dice.dart | 105 +++++++++++++++ lib/dungeon_world_data.dart | 12 ++ lib/gear_choice.dart | 44 +++++++ lib/gear_option.dart | 37 ++++++ lib/gear_selection.dart | 45 +++++++ lib/item.dart | 53 ++++++++ lib/models/repository.dart | 38 ++++++ lib/monster.dart | 67 ++++++++++ lib/move.dart | 88 +++++++++++++ lib/race.dart | 66 ++++++++++ lib/spell.dart | 66 ++++++++++ lib/tag.dart | 40 ++++++ pubspec.lock | 9 +- pubspec.yaml | 4 +- scripts/migrate/v2_to_v3.dart | 242 ++++++++++++++++++++++++++++++++++ test/dice_test.dart | 105 ++++++--------- 22 files changed, 1198 insertions(+), 102 deletions(-) create mode 100644 lib/_utils/uuid.dart create mode 100644 lib/alignment.dart create mode 100644 lib/bond.dart create mode 100644 lib/character_class.dart create mode 100644 lib/dice.dart create mode 100644 lib/gear_choice.dart create mode 100644 lib/gear_option.dart create mode 100644 lib/gear_selection.dart create mode 100644 lib/item.dart create mode 100644 lib/models/repository.dart create mode 100644 lib/monster.dart create mode 100644 lib/move.dart create mode 100644 lib/race.dart create mode 100644 lib/spell.dart create mode 100644 lib/tag.dart create mode 100644 scripts/migrate/v2_to_v3.dart diff --git a/.vscode/launch.json b/.vscode/launch.json index 07c3a14..1fac798 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,28 +4,35 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { - "name": "All Tests", - "type": "dart", - "request": "launch", - "program": "test/" - }, - { - "name": "Main", - "program": "bin/dw_data.dart", - "request": "launch", - "type": "dart" - }, - { - "name": "Example", - "program": "example/example.dart", - "request": "launch", - "type": "dart" - }, - { - "name": "Dump Dart", - "program": "scripts/dump/dump.dart", - "request": "launch", - "type": "dart" - }, ] -} + { + "name": "Dart - Current File", + "type": "dart", + "request": "launch", + "program": "${file}" + }, + { + "name": "All Tests", + "type": "dart", + "request": "launch", + "program": "test/" + }, + { + "name": "Main", + "program": "bin/dw_data.dart", + "request": "launch", + "type": "dart" + }, + { + "name": "Example", + "program": "example/example.dart", + "request": "launch", + "type": "dart" + }, + { + "name": "Dump Dart", + "program": "scripts/dump/dump.dart", + "request": "launch", + "type": "dart" + }, + ] +} \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 82c398e..ed70b56 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,7 +1,7 @@ # Defines a default set of lint rules enforced for # projects at Google. For details and rationale, # see https://github.com/dart-lang/pedantic#enabled-lints. -include: package:pedantic/analysis_options.yaml +include: package:lints/recommended.yaml analyzer: exclude: - example/*.dart diff --git a/lib/_utils/uuid.dart b/lib/_utils/uuid.dart new file mode 100644 index 0000000..b7dc749 --- /dev/null +++ b/lib/_utils/uuid.dart @@ -0,0 +1,3 @@ +import 'package:uuid/uuid.dart'; + +String uuid() => Uuid().v4(); diff --git a/lib/alignment.dart b/lib/alignment.dart new file mode 100644 index 0000000..0434ab0 --- /dev/null +++ b/lib/alignment.dart @@ -0,0 +1,88 @@ +import 'dart:convert'; + +class AlignmentValue { + AlignmentValue({ + required this.key, + required this.description, + }); + + final String key; + final String description; + + AlignmentValue copyWith({ + String? key, + String? description, + }) => + AlignmentValue( + key: key ?? this.key, + description: description ?? this.description, + ); + + factory AlignmentValue.fromRawJson(String str) => + AlignmentValue.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AlignmentValue.fromJson(Map json) => AlignmentValue( + key: json["key"], + description: json["description"], + ); + + Map toJson() => { + "key": key, + "description": description, + }; +} + +class AlignmentValues { + AlignmentValues({ + required this.good, + required this.evil, + required this.lawful, + required this.neutral, + required this.chaotic, + }); + + final String good; + final String evil; + final String lawful; + final String neutral; + final String chaotic; + + AlignmentValues copyWith({ + String? good, + String? evil, + String? lawful, + String? neutral, + String? chaotic, + }) => + AlignmentValues( + good: good ?? this.good, + evil: evil ?? this.evil, + lawful: lawful ?? this.lawful, + neutral: neutral ?? this.neutral, + chaotic: chaotic ?? this.chaotic, + ); + + factory AlignmentValues.fromRawJson(String str) => + AlignmentValues.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AlignmentValues.fromJson(Map json) => + AlignmentValues( + good: json["good"], + evil: json["evil"], + lawful: json["lawful"], + neutral: json["neutral"], + chaotic: json["chaotic"], + ); + + Map toJson() => { + "good": good, + "evil": evil, + "lawful": lawful, + "neutral": neutral, + "chaotic": chaotic, + }; +} diff --git a/lib/bond.dart b/lib/bond.dart new file mode 100644 index 0000000..a124912 --- /dev/null +++ b/lib/bond.dart @@ -0,0 +1,40 @@ +import 'dart:convert'; + +class Bond { + Bond({ + required this.key, + required this.description, + required this.completed, + }); + + final String key; + final String description; + final bool completed; + + Bond copyWith({ + String? key, + String? description, + bool? completed, + }) => + Bond( + key: key ?? this.key, + description: description ?? this.description, + completed: completed ?? this.completed, + ); + + factory Bond.fromRawJson(String str) => Bond.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Bond.fromJson(Map json) => Bond( + key: json["key"], + completed: json["completed"], + description: json["description"], + ); + + Map toJson() => { + "key": key, + "description": description, + "completed": completed, + }; +} diff --git a/lib/character_class.dart b/lib/character_class.dart new file mode 100644 index 0000000..8a54061 --- /dev/null +++ b/lib/character_class.dart @@ -0,0 +1,89 @@ +import 'dart:convert'; + +import 'alignment.dart'; +import 'dice.dart'; +import 'gear_choice.dart'; + +class CharacterClass { + CharacterClass({ + required this.meta, + required this.name, + required this.key, + required this.description, + required this.damageDice, + required this.load, + required this.hp, + required this.alignments, + required this.bonds, + required this.gearChoices, + }); + + final dynamic meta; + final String name; + final String key; + final String description; + final Dice damageDice; + final int load; + final int hp; + final AlignmentValues alignments; + final List bonds; + final List gearChoices; + + CharacterClass copyWith({ + dynamic meta, + String? name, + String? key, + String? description, + Dice? damageDice, + int? load, + int? hp, + AlignmentValues? alignments, + List? bonds, + List? gearChoices, + }) => + CharacterClass( + meta: meta ?? this.meta, + name: name ?? this.name, + key: key ?? this.key, + description: description ?? this.description, + damageDice: damageDice ?? this.damageDice, + load: load ?? this.load, + hp: hp ?? this.hp, + alignments: alignments ?? this.alignments, + bonds: bonds ?? this.bonds, + gearChoices: gearChoices ?? this.gearChoices, + ); + + factory CharacterClass.fromRawJson(String str) => + CharacterClass.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory CharacterClass.fromJson(Map json) => CharacterClass( + meta: json['_meta'], + name: json["name"], + key: json["key"], + description: json["description"], + damageDice: Dice.fromJson(json["damageDice"]), + load: json["load"], + hp: json["hp"], + alignments: AlignmentValues.fromJson(json["alignments"]), + bonds: List.from(json["bonds"].map((x) => x)), + gearChoices: List.from( + json["gearChoices"].map((x) => GearChoice.fromJson(x)), + ), + ); + + Map toJson() => { + "_meta": meta, + "name": name, + "key": key, + "description": description, + "damageDice": damageDice.toJson(), + "load": load, + "hp": hp, + "alignments": alignments.toJson(), + "bonds": List.from(bonds.map((x) => x)), + "gearChoices": List.from(gearChoices.map((x) => x.toJson())), + }; +} diff --git a/lib/dice.dart b/lib/dice.dart new file mode 100644 index 0000000..2a21805 --- /dev/null +++ b/lib/dice.dart @@ -0,0 +1,105 @@ +import 'dart:convert'; +import 'dart:math'; + +class Dice { + Dice({ + required this.amount, + required this.sides, + this.modifier, + }); + + final int amount; + final int sides; + final int? modifier; + + Dice copyWith({ + int? amount, + int? sides, + int? modifier, + }) => + Dice( + amount: amount ?? this.amount, + sides: sides ?? this.sides, + modifier: modifier ?? this.modifier, + ); + + factory Dice.fromRawJson(String str) => Dice.fromJson(json.decode(str)); + + static Dice d4 = Dice(amount: 1, sides: 4); + static Dice d6 = Dice(amount: 1, sides: 6); + static Dice d8 = Dice(amount: 1, sides: 8); + static Dice d10 = Dice(amount: 1, sides: 10); + static Dice d12 = Dice(amount: 1, sides: 12); + static Dice d20 = Dice(amount: 1, sides: 20); + static Dice d60 = Dice(amount: 1, sides: 60); + static Dice d100 = Dice(amount: 1, sides: 100); + + String toRawJson() => toJson(); + + factory Dice.fromJson(String json) { + var parts = json.split("d"); + var amount = int.tryParse(parts[0]); + int? sides; + int? modifier; + if (parts[1].contains(RegExp(r'[-+]'))) { + var idx = parts[1].indexOf(RegExp(r'[^0-9]')); + sides = int.tryParse(parts[1].substring(0, idx)); + modifier = int.tryParse(parts[1].substring(idx)); + } else { + sides = int.tryParse(parts[1]); + } + + if (sides == null || amount == null) { + throw Exception("Dice parsing failed"); + } + + return Dice( + amount: amount, + sides: sides, + modifier: modifier, + ); + } + + @override + String toString() => "${amount}d$sides$modifierWithSign"; + + String toJson() => toString(); + + String get modifierWithSign => modifier == null + ? "" + : modifier! > 0 + ? "+$modifier" + : "$modifier"; + + DiceResult roll() => DiceResult.roll(this); + + operator *(int amount) => copyWith(amount: this.amount * amount); + operator /(int amount) => copyWith(amount: this.amount ~/ amount); +} + +class DiceResult { + final Dice dice; + final List results; + + DiceResult({required this.dice, required this.results}); + + static List rollMany(List dice) { + return dice.map((d) { + var arr = []; + for (var i = 0; i < d.amount; i++) { + arr.add(Random().nextInt(d.sides)); + } + return DiceResult(dice: d, results: arr); + }).toList(); + } + + int get total => + results.reduce((all, cur) => all + cur) + (dice.modifier ?? 0); + + bool get didHitNaturalMax => indexOfNaturalMax >= 0; + int get indexOfNaturalMax => results.indexOf(dice.sides); + + static DiceResult roll(Dice dice) { + return rollMany([dice])[0]; + } +} diff --git a/lib/dungeon_world_data.dart b/lib/dungeon_world_data.dart index e69de29..461832e 100644 --- a/lib/dungeon_world_data.dart +++ b/lib/dungeon_world_data.dart @@ -0,0 +1,12 @@ +export 'alignment.dart'; +export 'bond.dart'; +export 'bond.dart'; +export 'character_class.dart'; +export 'dice.dart'; +export 'gear_choice.dart'; +export 'gear_selection.dart'; +export 'item.dart'; +export 'move.dart'; +export 'race.dart'; +export 'spell.dart'; +export 'tag.dart'; diff --git a/lib/gear_choice.dart b/lib/gear_choice.dart new file mode 100644 index 0000000..1e2cba6 --- /dev/null +++ b/lib/gear_choice.dart @@ -0,0 +1,44 @@ +import 'dart:convert'; + +import 'gear_selection.dart'; + +class GearChoice { + GearChoice({ + required this.key, + required this.description, + required this.selections, + }); + + final String key; + final String description; + final List selections; + + GearChoice copyWith({ + String? key, + String? description, + List? selections, + }) => + GearChoice( + key: key ?? this.key, + description: description ?? this.description, + selections: selections ?? this.selections, + ); + + factory GearChoice.fromRawJson(String str) => + GearChoice.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GearChoice.fromJson(Map json) => GearChoice( + key: json["key"], + description: json["description"], + selections: List.from( + json["selections"].map((x) => GearSelection.fromJson(x))), + ); + + Map toJson() => { + "key": key, + "description": description, + "selections": List.from(selections.map((x) => x.toJson())), + }; +} diff --git a/lib/gear_option.dart b/lib/gear_option.dart new file mode 100644 index 0000000..549a622 --- /dev/null +++ b/lib/gear_option.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; + +import 'item.dart'; + +class GearOption { + GearOption({ + required this.item, + required this.amount, + }); + + final Item item; + final double amount; + + GearOption copyWith({ + Item? item, + double? amount, + }) => + GearOption( + item: item ?? this.item, + amount: amount ?? this.amount, + ); + + factory GearOption.fromRawJson(String str) => + GearOption.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GearOption.fromJson(Map json) => GearOption( + item: json["item"], + amount: json["amount"], + ); + + Map toJson() => { + "item": item, + "amount": amount, + }; +} diff --git a/lib/gear_selection.dart b/lib/gear_selection.dart new file mode 100644 index 0000000..8685fd7 --- /dev/null +++ b/lib/gear_selection.dart @@ -0,0 +1,45 @@ +import 'dart:convert'; + +import 'gear_option.dart'; +import 'item.dart'; + +class GearSelection { + GearSelection({ + required this.description, + required this.items, + required this.gold, + }); + + final String description; + final List items; + final int gold; + + GearSelection copyWith({ + String? description, + List? items, + int? gold, + }) => + GearSelection( + description: description ?? this.description, + items: items ?? this.items, + gold: gold ?? this.gold, + ); + + factory GearSelection.fromRawJson(String str) => + GearSelection.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GearSelection.fromJson(Map json) => GearSelection( + description: json["description"], + items: List.from( + json["items"].map((x) => GearOption.fromJson(x))), + gold: json["gold"], + ); + + Map toJson() => { + "description": description, + "items": List.from(items.map((x) => x.toJson())), + "gold": gold, + }; +} diff --git a/lib/item.dart b/lib/item.dart new file mode 100644 index 0000000..a1f9efb --- /dev/null +++ b/lib/item.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; + +import 'tag.dart'; + +class Item { + Item({ + required this.meta, + required this.key, + required this.description, + required this.name, + required this.tags, + }); + + final dynamic meta; + final String key; + final String name; + final String description; + final List tags; + + Item copyWith({ + dynamic meta, + String? key, + String? name, + List? tags, + }) => + Item( + meta: meta ?? this.meta, + key: key ?? this.key, + name: name ?? this.name, + description: description ?? this.description, + tags: tags ?? this.tags, + ); + + factory Item.fromRawJson(String str) => Item.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Item.fromJson(Map json) => Item( + meta: json["_meta"], + key: json["key"], + name: json["name"], + description: json["description"], + tags: List.from(json["tags"].map((x) => Tag.fromJson(x))), + ); + + Map toJson() => { + "_meta": meta, + "key": key, + "name": name, + "description": description, + "tags": List.from(tags.map((x) => x.toJson())), + }; +} diff --git a/lib/models/repository.dart b/lib/models/repository.dart new file mode 100644 index 0000000..5a2e59e --- /dev/null +++ b/lib/models/repository.dart @@ -0,0 +1,38 @@ +class Repository {} + +class RepositoryItem { + final Map> _cache = {}; + String currentLocale; + + RepositoryItem({ + required this.currentLocale, + }); + + List get list { + _ensureLocale(currentLocale); + return _cache[currentLocale]!.values.toList(); + } + + Map get map { + _ensureLocale(currentLocale); + return _cache[currentLocale]!; + } + + void registerLocale(String locale) { + if (_cache[locale] != null) { + return; + } + _cache[locale] = {}; + } + + void addItems(String locale, Map items) { + _ensureLocale(locale); + _cache[locale]!.addAll(items); + } + + void _ensureLocale(String locale) { + if (_cache[locale] == null) { + throw Exception('Locale $locale does not exist.'); + } + } +} diff --git a/lib/monster.dart b/lib/monster.dart new file mode 100644 index 0000000..917b963 --- /dev/null +++ b/lib/monster.dart @@ -0,0 +1,67 @@ +import 'dart:convert'; + +import 'move.dart'; +import 'tag.dart'; + +class Monster { + Monster({ + required this.meta, + required this.key, + required this.name, + required this.description, + required this.instinct, + required this.tags, + required this.moves, + }); + + final dynamic meta; + final String key; + final String name; + final String description; + final String instinct; + final List tags; + final List moves; + + Monster copyWith({ + dynamic meta, + String? key, + String? name, + String? description, + String? instinct, + List? tags, + List? moves, + }) => + Monster( + meta: meta ?? this.meta, + key: key ?? this.key, + name: name ?? this.name, + description: description ?? this.description, + instinct: instinct ?? this.instinct, + tags: tags ?? this.tags, + moves: moves ?? this.moves, + ); + + factory Monster.fromRawJson(String str) => Monster.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Monster.fromJson(Map json) => Monster( + meta: json["_meta"], + key: json["key"], + name: json["name"], + description: json["description"], + instinct: json["instinct"], + tags: List.from(json["tags"].map((x) => Tag.fromJson(x))), + moves: json["moves"].map((x) => x), + ); + + Map toJson() => { + "_meta": meta, + "key": key, + "name": name, + "description": description, + "instinct": instinct, + "tags": List.from(tags.map((x) => x.toJson())), + "moves": moves, + }; +} diff --git a/lib/move.dart b/lib/move.dart new file mode 100644 index 0000000..ba9f9bb --- /dev/null +++ b/lib/move.dart @@ -0,0 +1,88 @@ +import 'dart:convert'; + +import 'dice.dart'; +import 'tag.dart'; + +enum MoveCategory { + starting, + basic, + advanced1, + advanced2, + other, +} + +class Move { + Move({ + required this.meta, + required this.key, + required this.name, + required this.description, + required this.explanation, + required this.dice, + required this.classKeys, + required this.tags, + required this.category, + }); + + final dynamic meta; + final String key; + final String name; + final String description; + final String explanation; + final List dice; + final List classKeys; + final List tags; + final MoveCategory category; + + Move copyWith({ + dynamic meta, + String? key, + String? name, + String? description, + String? explanation, + List? dice, + List? classKeys, + List? tags, + MoveCategory? category, + }) => + Move( + meta: meta ?? this.meta, + key: key ?? this.key, + name: name ?? this.name, + description: description ?? this.description, + explanation: explanation ?? this.explanation, + dice: dice ?? this.dice, + classKeys: classKeys ?? this.classKeys, + tags: tags ?? this.tags, + category: category ?? this.category, + ); + + factory Move.fromRawJson(String str) => Move.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Move.fromJson(Map json) => Move( + meta: json["_meta"], + key: json["key"], + name: json["name"], + description: json["description"], + explanation: json["explanation"], + dice: List.from(json["dice"].map((x) => x.toJson())), + classKeys: List.from(json["classKeys"].map((x) => x)), + tags: List.from(json["tags"].map((x) => Tag.fromJson(x))), + category: MoveCategory.values + .firstWhere((element) => element.name == json["category"]), + ); + + Map toJson() => { + "_meta": meta, + "key": key, + "name": name, + "description": description, + "explanation": explanation, + "dice": List.from(dice.map((x) => x.toJson())), + "classKeys": List.from(classKeys.map((x) => x)), + "tags": List.from(tags.map((x) => x.toJson())), + "category": category.name, + }; +} diff --git a/lib/race.dart b/lib/race.dart new file mode 100644 index 0000000..58b0e5e --- /dev/null +++ b/lib/race.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; + +import 'tag.dart'; + +class Race { + Race({ + required this.meta, + required this.key, + required this.name, + required this.description, + required this.explanation, + required this.classKeys, + required this.tags, + }); + + final dynamic meta; + final String key; + final String name; + final String description; + final String explanation; + final List classKeys; + final List tags; + + Race copyWith({ + dynamic meta, + String? key, + String? name, + String? description, + String? explanation, + List? classKeys, + List? tags, + }) => + Race( + meta: meta ?? this.meta, + key: key ?? this.key, + name: name ?? this.name, + description: description ?? this.description, + explanation: explanation ?? this.explanation, + classKeys: classKeys ?? this.classKeys, + tags: tags ?? this.tags, + ); + + factory Race.fromRawJson(String str) => Race.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Race.fromJson(Map json) => Race( + meta: json["_meta"], + key: json["key"], + name: json["name"], + description: json["description"], + explanation: json["explanation"], + classKeys: List.from(json["classKeys"].map((x) => x)), + tags: List.from(json["tags"].map((x) => Tag.fromJson(x))), + ); + + Map toJson() => { + "_meta": meta, + "key": key, + "name": name, + "description": description, + "explanation": explanation, + "classKeys": List.from(classKeys.map((x) => x)), + "tags": List.from(tags.map((x) => x.toJson())), + }; +} diff --git a/lib/spell.dart b/lib/spell.dart new file mode 100644 index 0000000..84db370 --- /dev/null +++ b/lib/spell.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; + +import 'tag.dart'; + +class Spell { + Spell({ + required this.meta, + required this.key, + required this.name, + required this.description, + required this.explanation, + required this.classKeys, + required this.tags, + }); + + final dynamic meta; + final String key; + final String name; + final String description; + final String explanation; + final List classKeys; + final List tags; + + Spell copyWith({ + dynamic meta, + String? key, + String? name, + String? description, + String? explanation, + List? classKeys, + List? tags, + }) => + Spell( + meta: meta ?? this.meta, + key: key ?? this.key, + name: name ?? this.name, + description: description ?? this.description, + explanation: explanation ?? this.explanation, + classKeys: classKeys ?? this.classKeys, + tags: tags ?? this.tags, + ); + + factory Spell.fromRawJson(String str) => Spell.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Spell.fromJson(Map json) => Spell( + meta: json["_meta"], + key: json["key"], + name: json["name"], + description: json["description"], + explanation: json["explanation"], + classKeys: List.from(json["classKeys"].map((x) => x)), + tags: List.from(json["tags"].map((x) => Tag.fromJson(x))), + ); + + Map toJson() => { + "_meta": meta, + "key": key, + "name": name, + "description": description, + "explanation": explanation, + "classKeys": List.from(classKeys.map((x) => x)), + "tags": List.from(tags.map((x) => x.toJson())), + }; +} diff --git a/lib/tag.dart b/lib/tag.dart new file mode 100644 index 0000000..1d53944 --- /dev/null +++ b/lib/tag.dart @@ -0,0 +1,40 @@ +import 'dart:convert'; + +class Tag { + Tag({ + required this.name, + required this.value, + this.description = "", + }); + + final String name; + final dynamic value; + final String description; + + Tag copyWith({ + String? name, + dynamic value, + String? description, + }) => + Tag( + name: name ?? this.name, + value: value ?? this.value, + description: description ?? this.description, + ); + + factory Tag.fromRawJson(String str) => Tag.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Tag.fromJson(Map json) => Tag( + name: json["name"], + value: json["value"], + description: json["description"] ?? "", + ); + + Map toJson() => { + "name": name, + "value": value, + "description": description, + }; +} diff --git a/pubspec.lock b/pubspec.lock index 0a6d1c1..9b07f7e 100755 --- a/pubspec.lock +++ b/pubspec.lock @@ -92,13 +92,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.2" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" glob: dependency: transitive description: @@ -135,7 +128,7 @@ packages: source: hosted version: "0.6.3" lints: - dependency: transitive + dependency: "direct dev" description: name: lints url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 272f50e..5fe78af 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,13 +8,13 @@ environment: sdk: ">=2.12.0 <3.0.0" dependencies: + collection: ^1.15.0-nullsafety.4 meta: ^1.1.0 quiver: ^3.0.1 uuid: ^3.0.4 - collection: ^1.15.0-nullsafety.4 dev_dependencies: - flutter_lints: ^1.0.4 + lints: ^1.0.1 dart_style: ^2.0.3 path: ^1.6.4 test: ^1.6.4 diff --git a/scripts/migrate/v2_to_v3.dart b/scripts/migrate/v2_to_v3.dart new file mode 100644 index 0000000..2e59809 --- /dev/null +++ b/scripts/migrate/v2_to_v3.dart @@ -0,0 +1,242 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:dungeon_world_data/_old/dw_data.dart' as old; +import 'package:dungeon_world_data/_utils/uuid.dart'; +import 'package:dungeon_world_data/dungeon_world_data.dart'; +import 'package:dungeon_world_data/gear_option.dart'; +import 'package:dungeon_world_data/monster.dart'; +import 'package:path/path.dart' as path; + +final _jsonOut = path.join( + path.dirname(Platform.script.path), + 'dump.json', +); + +Map strFixMap = { + "—": "-", + "–": "-", + "’": "'", + "“": "\"", + "”": "\"", +}; + +final json = >>{ + 'moves': [], + 'races': [], + 'classes': [], + 'spells': [], + 'items': [], + 'monsters': [], + 'tags': [], +}; + +final defaultTags = [ + Tag(name: "language", value: "EN"), + Tag(name: "source", value: "repo"), +]; + +main() async { + print("Starting..."); + // Basic moves + print("Adding ${old.dungeonWorld.basicMoves.length} basic moves"); + for (var move in old.dungeonWorld.basicMoves) { + json['moves']!.add(moveMapper(move, MoveCategory.basic).toJson()); + } + + for (var cls in old.dungeonWorld.classes) { + // Starting moves + print("Adding ${cls.startingMoves.length} starting moves"); + for (var move in cls.startingMoves) { + json['moves']!.add(moveMapper(move, MoveCategory.starting).toJson()); + } + + // Advanced Moves 1 + print("Adding ${cls.advancedMoves1.length} advanced1 moves"); + for (var move in cls.advancedMoves1) { + json['moves']!.add(moveMapper(move, MoveCategory.advanced1).toJson()); + } + + // Advanced Moves 1 + print("Adding ${cls.advancedMoves2.length} advanced2 moves"); + for (var move in cls.advancedMoves2) { + json['moves']!.add(moveMapper(move, MoveCategory.advanced2).toJson()); + } + + // Races + print("Adding ${cls.raceMoves.length} races"); + for (var race in cls.raceMoves) { + json['races']! + .add(raceMapper(race, cls.key ?? makeKey(cls.name)).toJson()); + } + + // Classes + json['classes']!.add(classMapper(cls).toJson()); + } + print("Total ${json['classes']!.length} classes"); + + // Spells + print("Adding ${old.dungeonWorld.spells.length} spells"); + for (var spell in old.dungeonWorld.spells) { + json['spells']!.add(spellMapper(spell).toJson()); + } + + print("Adding ${old.dungeonWorld.equipment.length} items"); + for (var equip in old.dungeonWorld.equipment) { + json['items']!.add(equipMapper(equip).toJson()); + } + + print("Adding ${old.dungeonWorld.monsters.length} monsters"); + for (var mon in old.dungeonWorld.monsters) { + json['monsters']!.add(monsterMapper(mon).toJson()); + } + + print("Adding ${old.dungeonWorld.tags.length} tags"); + for (var tag in old.dungeonWorld.tags) { + json['tags']!.add(tagMapper(tag).toJson()); + } + + // + // + + await File(_jsonOut).writeAsString(jsonEncode(json)); + print("Done"); +} + +String fix(String? str) { + if (str == null || str.trim().isEmpty) { + return ""; + } + for (var _fix in strFixMap.entries) { + str = str!.replaceAll(_fix.key, _fix.value); + } + return str!.trim(); +} + +String makeKey(String str) { + return fix(str) + .replaceAll(RegExp('["\']'), "") + .replaceAll(RegExp(r'[^a-z]+', caseSensitive: false), '_') + .toLowerCase(); +} + +Set guessDice(String str) { + var basicRollPattern = RegExp(r'\broll\+[a-z]{3}\b', caseSensitive: false); + var dicePattern = RegExp(r'\b\dd\d\b', caseSensitive: false); + var found = {}; + var basicRollMatches = basicRollPattern.allMatches(str); + for (var match in basicRollMatches) { + found.add(Dice.d6 * 2); + } + var diceMatches = dicePattern.allMatches(str); + for (var match in diceMatches) { + found.add(Dice.fromJson(match.input.substring(match.start, match.end))); + } + return found; +} + +Move moveMapper(old.Move move, MoveCategory category) => Move( + category: category, + classKeys: move.classes, + description: fix(move.description), + dice: guessDice(fix(move.description)).toList(), + explanation: fix(move.explanation ?? ""), + key: makeKey(move.name), + meta: null, + name: fix(move.name), + tags: defaultTags, + ); + +Race raceMapper(old.Move move, String classKey) => Race( + classKeys: [classKey], + description: fix(move.description), + explanation: fix(move.explanation ?? ""), + key: makeKey(move.name), + meta: null, + name: fix(move.name), + tags: defaultTags, + ); + +Spell spellMapper(old.Spell spell) => Spell( + classKeys: [], + description: fix(spell.description), + explanation: "", + key: makeKey(spell.name), + meta: null, + name: fix(spell.name), + tags: [...defaultTags, ...spell.tags.map((t) => tagMapper(t))], + ); + +Tag tagMapper(old.Tag t) => Tag.fromJson(t.toJSON().runtimeType == String + ? {"name": t.toJSON()} + : {"name": t.name, "value": t.value, "description": fix(t.description)}); + +Item equipMapper(old.Equipment equip) => Item( + key: makeKey(equip.name), + meta: null, + name: fix(equip.name), + description: fix(equip.description), + tags: equip.tags.map((t) => tagMapper(t)).toList(), + ); + +Monster monsterMapper(old.Monster move) => Monster( + instinct: fix(move.instinct), + description: fix(move.description), + key: makeKey(move.name), + meta: null, + name: fix(move.name), + tags: defaultTags, + moves: move.moves, + ); + +CharacterClass classMapper(old.PlayerClass cls) => CharacterClass( + alignments: AlignmentValues( + chaotic: fix(cls.alignments['chaotic']?.description ?? ""), + neutral: fix(cls.alignments['neutral']?.description ?? ""), + evil: fix(cls.alignments['evil']?.description ?? ""), + good: fix(cls.alignments['good']?.description ?? ""), + lawful: fix(cls.alignments['lawful']?.description ?? ""), + ), + bonds: cls.bonds.map(fix).toList(), + damageDice: Dice.fromJson(cls.damage.toString()), + description: fix(cls.description), + gearChoices: cls.gearChoices + .map( + (c) => GearChoice( + key: c.key ?? uuid(), + description: fix(c.label), + selections: c.gearOptions + .map( + (o) => GearSelection( + description: o.name + '(TODO: INCOMPLETE)', + items: [ + GearOption( + amount: o.name.contains(RegExp(r'[0-9]+')) + ? double.tryParse(RegExp(r'[0-9]+') + .firstMatch(o.name) + ?.group(0) ?? + "1.0") ?? + 1.0 + : 1.0, + item: Item( + key: makeKey(o.name), + meta: null, + name: fix(o.name), + description: fix(o.name), + tags: defaultTags, + ), + ), + ], + gold: 0, + ), + ) + .toList(), + ), + ) + .toList(), + hp: cls.baseHP.toInt(), + key: makeKey(cls.name), + load: cls.load.toInt(), + name: cls.name, + meta: null, + ); diff --git a/test/dice_test.dart b/test/dice_test.dart index f9df3a2..3494f14 100644 --- a/test/dice_test.dart +++ b/test/dice_test.dart @@ -1,75 +1,48 @@ -import 'package:dungeon_world_data/dw_data.dart'; +import 'package:dungeon_world_data/dice.dart'; import 'package:test/test.dart'; void main() { group('Dice', () { - test('Sides and amount', () { - var expected = Dice(6, 2); - expect(expected.sides, equals(6)); - expect(expected.amount, equals(2)); + group("Parse JSON", () { + test("No modifier", () { + var str = "1d6"; + var dice = Dice.fromJson(str); + expect(dice.amount, equals(1)); + expect(dice.sides, equals(6)); + expect(dice.modifier, equals(null)); + }); + test("With positive modifier", () { + var str = "3d8+3"; + var dice = Dice.fromJson(str); + expect(dice.amount, equals(3)); + expect(dice.sides, equals(8)); + expect(dice.modifier, equals(3)); + }); + test("With negative modifier", () { + var str = "2d20-4"; + var dice = Dice.fromJson(str); + expect(dice.amount, equals(2)); + expect(dice.sides, equals(20)); + expect(dice.modifier, equals(-4)); + }); }); - test('String representation', () { - var dice1 = Dice(10, 4); - var dice2 = Dice(6, 2, 3); - var dice3 = Dice(20, 2, -4); - expect(dice1.toString(), equals('4d10')); - expect(dice2.toString(), equals('2d6+3')); - expect(dice3.toString(), equals('2d20-4')); - }); - - test('Multiplication', () { - num amt = 10; - var dice = Dice.d12 * amt; - expect(dice.amount, amt); - }); - - test('Equality', () { - var compare = { - Dice(4): Dice.d4, - Dice(6): Dice.d6, - Dice(8): Dice.d8, - Dice(10): Dice.d10, - Dice(12): Dice.d12, - Dice(20): Dice.d20, - }; - for (var d in compare.keys) { - expect(d, equals(compare[d])); - expect(compare.values.where((el) => el == d).length, equals(1)); - } - }); - - test('Roll', () { - var d1 = Dice.d6; - var d2 = Dice.d12 * 2; - var d3 = Dice.d8 * 3; - var roll1 = d1.getRoll(); - var roll2 = d2.getRoll(); - var roll3 = d3.getRoll(); - - expect(roll1.results[0], greaterThanOrEqualTo(1)); - expect(roll1.results[0], lessThanOrEqualTo(6)); - - expect(roll2.results.length, equals(d2.amount)); - expect(roll2.results[0], greaterThanOrEqualTo(1)); - expect(roll2.results[0], lessThanOrEqualTo(12)); - expect(roll2.results[1], greaterThanOrEqualTo(1)); - expect(roll2.results[1], lessThanOrEqualTo(12)); - - expect(roll3.results.length, equals(d3.amount)); - expect(roll3.results[0], greaterThanOrEqualTo(1)); - expect(roll3.results[0], lessThanOrEqualTo(8)); - expect(roll3.results[1], greaterThanOrEqualTo(1)); - expect(roll3.results[1], lessThanOrEqualTo(8)); - expect(roll3.results[2], greaterThanOrEqualTo(1)); - expect(roll3.results[2], lessThanOrEqualTo(8)); - }); - - test('Roll', () { - var dice = Dice.d6; - var roll = dice.getRoll(); - expect(roll.results[0], greaterThanOrEqualTo(1)); - expect(roll.results[0], lessThanOrEqualTo(6)); + group("Dump JSON", () { + test("No modifier", () { + var str = "1d6"; + var dice = Dice(amount: 1, sides: 6); + expect(dice.toJson(), equals(str)); + }); + test("With positive modifier", () { + var str = "3d8+3"; + var dice = Dice(amount: 3, sides: 8, modifier: 3); + expect(dice.toJson(), equals(str)); + }); + test("With negative modifier", () { + var str = "2d20-4"; + var dice = Dice(amount: 2, sides: 20, modifier: -4); + expect(dice.toJson(), equals(str)); + }); }); }); }