From bcb29d16825ceaccf8a543b7a18dbd4757754cbe Mon Sep 17 00:00:00 2001 From: Ignitesss <58274732+Ignitesss@users.noreply.github.com> Date: Sun, 14 Nov 2021 22:02:00 +0300 Subject: [PATCH 1/2] Add resolver --- Gemfile.lock | 1 + README.md | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 151 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0b7f5b9..cc63e61 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,6 +17,7 @@ GEM simplecov-html (0.10.2) PLATFORMS + ruby x64-mingw32 x86_64-linux diff --git a/README.md b/README.md index 837c105..ff67b38 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,14 @@ # RuberDialog -Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/ruber_dialog`. To experiment with that code, run `bin/console` for an interactive prompt. +RuberDialog is a **dialog system** that helps game developers write dialogs in an intuitive way. -TODO: Delete this and the text above, and describe your gem +We provide new *domain specific language* for writing dialogs and tools for: +- configuration +- error checking +- running in terminal +- exporting in json +- Unity game engine integration ## Installation @@ -26,7 +31,149 @@ Or install it yourself as: ## Usage -TODO: Write usage instructions here +### Blocks +In our language, parsing consists of 3 *blocks*: +#### Character block: + Characters: + Gandalf + Frodo + Bilbo +#### Description block + {Greeting} + Description: There are items: ale, beer and + line breaks + Gandalf: Frodo, take the ring! +#### Responses block + [Yes, I will take it -> {To Mordor} | No, I won't take it -> {End} + +### Parsing +#### Character parsing + +**Parser::CharacterParser** (`lib/ruber_dialog/parser/character_parser.rb`) is a class for parsing single character string. + +Usage: + + s = "Gandalf" + parser = CharacterParser.new(forbidden_expressions: %w({ [ ] }), reserved_names: ["Description"] + character = parser.parse(s) # => Character("Gandalf") + errors = parser.validate(s) # => list of errors, [] in the example + +Methods: + + def initiate(forbidden_expressions: [], reserved_names: []) + + forbidden_expressions - [String/RegExpr], expressions that are not supposed to be inside character name + reserved_names - [String], reserved names such as "Description" + + def parse(content) - takes a string and returns Parser::Character + + def validate(content) - takes a string and returns [ValidationError] + +#### Character block parsing + +**Parser::CharacterBlockParser** (`lib/ruber_dialog/parser/character_block_parser.rb`) is main class for parsing characters. + +Usage: + + s = "Chars:\n + Gandalf\n + Frodo" + parser = CharacterBlockParser.new(block_name: "Chars:\n", reserved_names: ["Desc"]) + parser.parse(s) # => [Character("Gandalf"), Character("Frodo")] + +Methods: + + def initiate(starting_line: 1, block_name: "Characters:", reserved_names: ["Description"], + forbidden_expressions: %w({ [ ] }), separator: "\n") + + starting_line - Integer, line number where the blocks starts. + block_name - String, key word that helps to determine where the block starts + reserved_names - [String], reserved names such as "Description" + forbidden_expressions - [String/RegExpr], expressions that are not supposed to be inside character name + separator - String/RegExpr, used to separate characters + + def parse(content) - takes a string and returns array of Parser::Character + + def validate(content) - takes a string and returns Hash with Integer keys and [ValidationError] values + + def split_to_token_contents(character_content) - takes string, splits it to TokenContents + + def starting_line=(starting_line) - for setting up the line where block starts, used for error messages + +#### Abstractions +It is recommended to use **Parser::TokenParser** (`lib/ruber_dialog/parser/parser.rb`) for parsing simple objects in strings, such as a character in **Character Block** or a response in **Response Block**. +If you write your own token parser, consider inheriting it from TokenParser + +Here is an example of inheritance: + + class CharacterParser < TokenParser + def initialize(forbidden_expressions: [], reserved_names: []) + super(forbidden_expressions, reserved_names) + end + + def forbidden_expression_error(expression) + "Forbidden symbol '#{expression}'" + end + + def reserved_name_error(name) + "Use of reserved name (#{name}) as a character name is forbidden" + end + + protected :forbidden_expression_error, :reserved_name_error + + def parse(content) + raise ArgumentError unless content.is_a?(String) + + Character.new(content) + end + end +This class now is capable of: +- finding errors such as forbidden expressions and reserved names +- locating the line where the error occurred + + +For parsing **blocks**, there is an abstract class **Parser::BlockParser** (`lib/ruber_dialog_parser/block_parser.rb`), which is capable of validating content using encapsulated **Parser::TokenParser** inheritor repetitive +To use **BlockParser**, you have to implement **def** *split_to_token_contents(content)*, which is used to extract simple object strings that *TokenParser* could parse them + +### Nodes and dialogs +There are two classes of RuberDialog::DialogParts module. + +**Node** is a class containing NPC conversation and possibly the main character's responses (forks). +You can go to the next node if you have at least one response. Otherwise, it is considered that the node is final. +Every node has its name, list of NPC's lines, also they can have a list of the main characters' responses. + + +**Dialog** is not a dialogue. It is a class of all possible conversation forks in the game. +Every dialog consists of a starting node, a list of other nodes, a list of characters, and final nodes' names. + + +### Export to JSON + +There is a list of classes having **.to_json** method that converts the object to JSON: +- Character +- Line +- Response +- Node +- Dialog + +#### Examples of JSONs + +```ruby +# Character +{"name":"Character's name"} + +# Line +{"char_name":"Character's name", "phrase":"Character's phrase"} + +# Response +{"response":"Main character's phrase", "next_node":"Name of the next node"} + +# Node +{"name":"Node's name", "lines":[list_of_lines], "responses":[list_of_responses]} + +# Dialog +{"starting_node":starting_node, "nodes":[list_of_nodes], "characters":[list_of_characters], "final_nodes_names":[list_of_final_nodes_names]} +``` ## Development From fb9896267bb3e7d271d1def583950d5d58f521ac Mon Sep 17 00:00:00 2001 From: Ignitesss <58274732+Ignitesss@users.noreply.github.com> Date: Sun, 14 Nov 2021 22:08:46 +0300 Subject: [PATCH 2/2] Add right version of resolver --- lib/ruber_dialog/resolver.rb | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 lib/ruber_dialog/resolver.rb diff --git a/lib/ruber_dialog/resolver.rb b/lib/ruber_dialog/resolver.rb new file mode 100644 index 0000000..1317c58 --- /dev/null +++ b/lib/ruber_dialog/resolver.rb @@ -0,0 +1,39 @@ +module RuberDialog + module DialogParts + include Parser + # This class matches character names and responses to nodes + class Resolver + attr_accessor :nodes_map, :chars_map + + def initialize + @nodes_map = {} + @chars_map = {} + end + + # ensure that each character's name from chars list is in the names list + def validate_characters(names, chars) + chars.each do |char| + raise ValidationError unless names.include? char.name + + @chars_map[char.name] = char + end + end + + # map nodes to their names + def resolve_nodes(nodes) + nodes.each do |node| + @nodes_map[node.name] = node + end + end + + # ensure that every responce leads to a valid node + def validate_nodes + @nodes_map.each do |_, v| + v.each do |r| + raise ValidationError unless @nodes_map.include? r.next_node + end + end + end + end + end +end