diff --git a/Gemfile b/Gemfile index f52d7ba..8d1ed40 100644 --- a/Gemfile +++ b/Gemfile @@ -9,4 +9,5 @@ gem "yard" gem "yard-ghpages" # Testing +gem "minitest" gem "rubocop" diff --git a/Gemfile.lock b/Gemfile.lock index bd564d0..6138e6f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,6 +3,7 @@ GEM specs: ast (2.3.0) git (1.3.0) + minitest (5.10.1) parser (2.4.0.0) ast (~> 2.2) powerpack (0.1.1) @@ -30,6 +31,7 @@ PLATFORMS ruby DEPENDENCIES + minitest rdoc redcarpet rubocop diff --git a/Rakefile b/Rakefile index 84a15c3..664ff4f 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,9 @@ require 'yard-ghpages' +task default: %w[truth] + +task :truth do + puts 'True!' +end + Yard::GHPages::Tasks.install_tasks diff --git a/lib/sapling.rb b/lib/sapling.rb index 901094b..2025207 100644 --- a/lib/sapling.rb +++ b/lib/sapling.rb @@ -8,20 +8,19 @@ Dir[File.join(__dir__, 'sapling', '*.rb')].each { |file| require file } # The main Sapling interface. class Sapling < Thor desc 'read TREE', 'Load and traverse the TREE' - def read(file) + def read(tree) + exit unless verify_tree(tree) puts 'Welcome to Sapling, a Dialogue Tree Utility.' - exit unless verify_tree(file) - tree = Gardner::Plot.new(YAML.load_file(file)) - speaker = Dialogue::Speaker.new(tree, false) + speaker = Dialogue::Speaker.new(YAML.load_file(tree), false) speaker.conversation end desc 'edit TREE', 'Edit a new or existing TREE' - def edit(file = '') + def edit(tree = '') puts 'Welcome to Sapling, a Dialogue Tree Utility.' if !tree.empty? - puts "Loading tree: #{file}" - exit unless verify_tree(file) + puts "Loading tree: #{tree}" + exit unless verify_tree(tree) gardner = Planter::Spade.new(YAML.load_file(tree, false)) else puts 'Creating a new tree!' diff --git a/lib/sapling/dialogue.rb b/lib/sapling/dialogue.rb index f981f52..5ccc010 100644 --- a/lib/sapling/dialogue.rb +++ b/lib/sapling/dialogue.rb @@ -2,15 +2,16 @@ require_relative './gardner' # Dialogue is the module for traversing an existing tree. module Dialogue + # Format and display the trunk # # @param trunk [Hash] The trunk hash # @param debug [Boolean] The status of showing debug information - def self.display_trunk(trunk, debug = false) - 40.times { print '-' } + def self.display_trunk(trunk, debug=false) + 40.times { print "-" } puts "\n[ Trunk ]\n" if debug - puts "\n#{trunk['trunk']}" - 40.times { print '-' } + puts "\n#{trunk["trunk"]}" + 40.times { print "-" } puts "\n" end @@ -19,11 +20,11 @@ module Dialogue # @param branch [Hash] A branch data set # @param branch_no [Integer] The branch number # @param debug [Boolean] Status of showing debug information - def self.display_branch(branch, branch_no, debug = false) + def self.display_branch(branch, branch_no, debug=false) puts "\n[ Branch: #{branch_no} ]" if debug - puts "\n#{branch['desc']}\n\n" + puts "\n#{branch["desc"]}\n\n" - branch['options'].each_pair do |k, v| + branch["options"].each_pair do |k,v| puts "\t#{k}: #{v.keys[0]}" puts "\t\t[ Goes to branch #{v.values[0]} ]\n" if debug end @@ -31,27 +32,30 @@ module Dialogue # Speaker holds the functionality for going through a dialogue tree. class Speaker - # The tree, an instance of Gardner::Plot - attr_reader :tree + # The file, which should be a dialogue tree YAML file. + attr_accessor :file # Status of verbose/debug mode. True = on; false = off. - attr_reader :debug + attr_accessor :debug - def initialize(tree, debug = false) - @tree = tree + def initialize(file="", debug=false) + @file = file @debug = debug end # Conversation handles navigating the tree, until the option to end is # reached. - def conversation - Dialogue.display_trunk(@tree.trunk, @debug) + def conversation() + tree = Gardner.prune_trunk(@file) + + Dialogue.display_trunk(tree[0], false) + branches = Gardner.prune_branches(tree[1]) next_branch = 1 - until next_branch.zero? - next_branch = talk(@tree.branches[next_branch], next_branch) + until next_branch == 0 do + next_branch = talk(branches[next_branch], next_branch) end - puts "\n#{@tree.branches[0]['desc']}" + puts "\n#{branches[0]["desc"]}" exit end @@ -61,18 +65,25 @@ module Dialogue # @param branch_no [Integer] The branch number # @return [Integer] The number of the next branch def talk(branch, branch_no) - return 0 if terminal?(branch) + # If there are no options on this branch, we assume it's a terminal + # branch. Return 0, and end the program. + if branch["options"].empty? + puts "\n#{branch["desc"]}\n\n" + return 0 + end Dialogue.display_branch(branch, branch_no, @debug) response = get_response(branch) - unless response.zero? - puts "(Your choice: #{branch['options'][response].keys[0]})" - response = branch['options'][response].values[0].to_i + unless response == 0 + puts "\n" + 10.times { print "*" } + puts "\n(Your choice: #{branch["options"][response].keys[0]})" + response = branch["options"][response].values[0].to_i end - response + return response end # Get a response for the displayed branch @@ -80,32 +91,20 @@ module Dialogue # @param branch [Hash] A branch data set # @return [Integer] the next branch def get_response(branch) - valid_options = branch['options'].keys.join(', ') + valid_options = branch["options"].keys.join(", ") print "\n[#{valid_options}]> " STDOUT.flush - response = STDIN.gets.chomp + response = STDIN.gets.chomp.to_i - until valid_options.include?(response) || response.to_i.zero? - print "[## Invalid options. Valid options are #{valid_options}," \ - "or 0 to exit.\n[#{valid_options}]> " - response = STDIN.gets.chomp + until branch["options"].keys.include?(response) or response == 0 + print "[## Invalid options. " + print "Valid options are #{valid_options}, or 0 to exit." + print "\n[#{valid_options}]> " + response = STDIN.gets.chomp.to_i end - response.to_i - end - - # Check if a branch is terminal - # - # @param branch [Hash] A branch data set - # @return [Boolean] true if the branch is terminal, false otherwise - def terminal?(branch) - if branch['options'].empty? - puts "\n#{branch['desc']}\n\n" - return true - end - - false + return response end end end diff --git a/lib/sapling/gardner.rb b/lib/sapling/gardner.rb index 1512076..e6184bf 100644 --- a/lib/sapling/gardner.rb +++ b/lib/sapling/gardner.rb @@ -2,95 +2,47 @@ require 'yaml' # Gardner is the module for working with a dialogue tree file module Gardner - # The Plot class handles a specific tree file. It provides functionality for - # parsing trunks and branches, and provides these as class attributes. - class Plot - # The trunk and branches instance variables - attr_reader :branches, :tree, :trunk - # Initialize a new Plot from a tree file - # - # @param tree [File] The dialogue tree file - def initialize(file) - @tree = file - prune_trunk - prune_branches + # Parse the tree array into an array of numbered branches, and ordered leaves. + # + # @param tree [Array] The dialogue tree + # @return [Array] An array of numbered branches, with numbered leaves + def self.prune_branches(tree) + branches = { 0 => { "desc" => "Thanks for using Sapling!" } } + tree.each do |b| + branches[b["branch"]["number"]] = { + "desc" => b["branch"]["text"], + "options" => prune_leaves(b["branch"]["leaf"]) } end - # Parse the tree array into an array of numbered branches, and ordered - # leaves. - # - # @param tree [File] The dialogue tree - # @return [Array] An array of numbered branches, with numbered leaves - def prune_branches - @branches = { 0 => { 'desc' => 'Thanks for using Sapling!' } } - @tree.each do |b| - @branches[b['branch']['number']] = { - 'desc' => b['branch']['text'], - 'options' => prune_leaves(b['branch']['leaf']) - } - end - end - - # Parse the leaves of a branch into a numbered hash of options. - # - # @param leaves [Array] The option of leaf hashes - # @return [Hash] A numbered hash of options - def prune_leaves(leaves) - x = 1 - options = {} - - return options if leaves.nil? - - leaves.each do |l| - options[x] = { l['text'] => l['branch'] } - x += 1 - end - - options - end - - # Parse the trunk of the tree. - # - # @return [Array] The trunk, and the remainder of the tree - def prune_trunk - @trunk = @tree.shift - end + return branches end - # Digiplot represents a Plot used for editing. The Digiplot functions exactly - # like a Plot, except with additional functionality for over-writing existing - # branches, leaves, and the trunk. - class Digiplot < Plot - # Duplicate the "old" trunk and branches, for restoration purposes - attr_reader :old_branches, :old_trunk + # Parse the leaves of a branch into a numbered hash of options. + # + # @param leaves [Array] The option of leaf hashes + # @return [Hash] A numbered hash of options + def self.prune_leaves(leaves) + x = 1 + options = {} - # Enable editing for the trunk - attr_writer :trunk + return options if leaves.nil? - # Initialize a Digiplot just like a Plot, but also copy the trunk and - # branches to "old" instance variables. - def initialize - super - @old_trunk = @trunk - @old_branches = @branches + leaves.each do |l| + options[x] = { l["text"] => l["branch"] } + x += 1 end - # Change a branch - # - # @param branch [Integer] the number of the branch to be edited - def branch=(branch, text) - @branches[branch]['desc'] = text - end + return options + end - # Change a leaf on a branch, grasshopper - # - # @param branch [Integer] the number of the branch to be edited - # @param leaf [Integer] the number of the leaf to be edited - # @param text [String] the new text for the leaf - # @param target [Integer] the branch number target for the leaf option - def leaf=(branch, leaf, text, target) - @branches[branch]['options'][leaf] = { text => target } - end + # Parse the trunk of the tree. + # + # @param tree [Hash] The entire tree + # @return [Array] The trunk, and the remainder of the tree + def self.prune_trunk(tree) + trunk = tree.shift + + return [trunk,tree] end end diff --git a/lib/sapling/utility.rb b/lib/sapling/utility.rb index fa5af53..576122a 100644 --- a/lib/sapling/utility.rb +++ b/lib/sapling/utility.rb @@ -7,32 +7,34 @@ # documentation. # The default trunk text of a new tree -SKELE_TRUNK_TEXT = 'Welcome to the Sapling Editor. For details, please see the -documentation!'.freeze +SKELE_TRUNK_TEXT = "Welcome to the Sapling Editor. For details, please see the +documentation!" # The default first-branch text of a new tree -SKELE_BRANCH_TEXT = 'The first branch is always shown by default. It should act -as the introduction to the story. From here, the user enters your world!'.freeze +SKELE_BRANCH_TEXT = "The first branch is always shown by default. It should act +as the introduction to the story. From here, the user enters your world!" # The default first-leaf text of the first branch of a new tree. The leaf points # to it's own branch. The only way out of the program is to either force-quit or # reply with option 0. -SKELE_LEAF_TEXT = 'Each branch can have any number of leaves, which represent +SKELE_LEAF_TEXT = "Each branch can have any number of leaves, which represent the options a user has on that branch. Each leaf points to another branch, or -can point to branch 0 to immediately exit.'.freeze +can point to branch 0 to immediately exit." # The final tree SKELETON_TREE = [ - { 'trunk' => SKELE_TRUNK_TEXT.to_s }, - { 'branch' => { - 'number' => 1, - 'text' => SKELE_BRANCH_TEXT.to_s, - 'leaf' => [{ - 'text' => SKELE_LEAF_TEXT.to_s, - 'branch' => 1 - }] - } } -].freeze + {"trunk" => "#{SKELE_TRUNK_TEXT}"}, + {"branch" => { + "number" => 1, + "text" => "#{SKELE_BRANCH_TEXT}", + "leaf" => [{ + "text" => "#{SKELE_LEAF_TEXT}", + "branch" => 1 + }] + } + } +] + # Verify that a file is a dialogue tree file. # @@ -42,13 +44,13 @@ def verify_tree(file) results = [] begin tree = YAML.load_file(file) - results << tree[0].keys.include?('trunk') - results << tree[1]['branch'].keys.include?('number') - results << tree[1]['branch'].keys.include?('text') - results << tree[1]['branch'].keys.include?('leaf') + results << tree[0].keys.include?("trunk") + results << tree[1]["branch"].keys.include?("number") + results << tree[1]["branch"].keys.include?("text") + results << tree[1]["branch"].keys.include?("leaf") rescue puts "Sorry chummer, I don't think this is a tree." - puts 'Verify your YAML file is formatted properly.' + puts "Verify your YAML file is formatted properly." results << false end diff --git a/sapling-dialogue.gemspec b/sapling-dialogue.gemspec index 3aebab3..99c9d8d 100644 --- a/sapling-dialogue.gemspec +++ b/sapling-dialogue.gemspec @@ -1,10 +1,10 @@ Gem::Specification.new do |s| s.name = 'sapling-dialogue' - s.version = '0.1.2' + s.version = '0.1.0' s.executables << 'sapling' - s.date = '2017-10-19' + s.date = '2017-10-14' s.summary = 'A Dialogue Tree Utility' - s.description = 'Create, edit, and traverse dialogue trees' + s.description = 'Create, edit, and traverse Dialogue trees' s.authors = ['Bill Niblock'] s.email = 'azulien@gmail.com' s.files = Dir['lib/**/*.rb'] + Dir['bin/*']