Compare commits

..

2 commits

Author SHA1 Message Date
Bill Niblock
8233af348a Rakefile: Dummy task to make builds pass 2017-10-18 21:48:17 -04:00
Bill Niblock
26a6691ce4 Add minitest 2017-10-18 21:48:03 -04:00
8 changed files with 114 additions and 153 deletions

View file

@ -9,4 +9,5 @@ gem "yard"
gem "yard-ghpages" gem "yard-ghpages"
# Testing # Testing
gem "minitest"
gem "rubocop" gem "rubocop"

View file

@ -3,6 +3,7 @@ GEM
specs: specs:
ast (2.3.0) ast (2.3.0)
git (1.3.0) git (1.3.0)
minitest (5.10.1)
parser (2.4.0.0) parser (2.4.0.0)
ast (~> 2.2) ast (~> 2.2)
powerpack (0.1.1) powerpack (0.1.1)
@ -30,6 +31,7 @@ PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
minitest
rdoc rdoc
redcarpet redcarpet
rubocop rubocop

View file

@ -1,3 +1,9 @@
require 'yard-ghpages' require 'yard-ghpages'
task default: %w[truth]
task :truth do
puts 'True!'
end
Yard::GHPages::Tasks.install_tasks Yard::GHPages::Tasks.install_tasks

View file

@ -8,20 +8,19 @@ Dir[File.join(__dir__, 'sapling', '*.rb')].each { |file| require file }
# The main Sapling interface. # The main Sapling interface.
class Sapling < Thor class Sapling < Thor
desc 'read TREE', 'Load and traverse the TREE' 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.' puts 'Welcome to Sapling, a Dialogue Tree Utility.'
exit unless verify_tree(file) speaker = Dialogue::Speaker.new(YAML.load_file(tree), false)
tree = Gardner::Plot.new(YAML.load_file(file))
speaker = Dialogue::Speaker.new(tree, false)
speaker.conversation speaker.conversation
end end
desc 'edit TREE', 'Edit a new or existing TREE' desc 'edit TREE', 'Edit a new or existing TREE'
def edit(file = '') def edit(tree = '')
puts 'Welcome to Sapling, a Dialogue Tree Utility.' puts 'Welcome to Sapling, a Dialogue Tree Utility.'
if !tree.empty? if !tree.empty?
puts "Loading tree: #{file}" puts "Loading tree: #{tree}"
exit unless verify_tree(file) exit unless verify_tree(tree)
gardner = Planter::Spade.new(YAML.load_file(tree, false)) gardner = Planter::Spade.new(YAML.load_file(tree, false))
else else
puts 'Creating a new tree!' puts 'Creating a new tree!'

View file

@ -2,15 +2,16 @@ require_relative './gardner'
# Dialogue is the module for traversing an existing tree. # Dialogue is the module for traversing an existing tree.
module Dialogue module Dialogue
# Format and display the trunk # Format and display the trunk
# #
# @param trunk [Hash] The trunk hash # @param trunk [Hash] The trunk hash
# @param debug [Boolean] The status of showing debug information # @param debug [Boolean] The status of showing debug information
def self.display_trunk(trunk, debug=false) def self.display_trunk(trunk, debug=false)
40.times { print '-' } 40.times { print "-" }
puts "\n[ Trunk ]\n" if debug puts "\n[ Trunk ]\n" if debug
puts "\n#{trunk['trunk']}" puts "\n#{trunk["trunk"]}"
40.times { print '-' } 40.times { print "-" }
puts "\n" puts "\n"
end end
@ -21,9 +22,9 @@ module Dialogue
# @param debug [Boolean] Status of showing debug information # @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: #{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#{k}: #{v.keys[0]}"
puts "\t\t[ Goes to branch #{v.values[0]} ]\n" if debug puts "\t\t[ Goes to branch #{v.values[0]} ]\n" if debug
end end
@ -31,27 +32,30 @@ module Dialogue
# Speaker holds the functionality for going through a dialogue tree. # Speaker holds the functionality for going through a dialogue tree.
class Speaker class Speaker
# The tree, an instance of Gardner::Plot # The file, which should be a dialogue tree YAML file.
attr_reader :tree attr_accessor :file
# Status of verbose/debug mode. True = on; false = off. # Status of verbose/debug mode. True = on; false = off.
attr_reader :debug attr_accessor :debug
def initialize(tree, debug = false) def initialize(file="", debug=false)
@tree = tree @file = file
@debug = debug @debug = debug
end end
# Conversation handles navigating the tree, until the option to end is # Conversation handles navigating the tree, until the option to end is
# reached. # reached.
def conversation def conversation()
Dialogue.display_trunk(@tree.trunk, @debug) tree = Gardner.prune_trunk(@file)
Dialogue.display_trunk(tree[0], false)
branches = Gardner.prune_branches(tree[1])
next_branch = 1 next_branch = 1
until next_branch.zero? until next_branch == 0 do
next_branch = talk(@tree.branches[next_branch], next_branch) next_branch = talk(branches[next_branch], next_branch)
end end
puts "\n#{@tree.branches[0]['desc']}" puts "\n#{branches[0]["desc"]}"
exit exit
end end
@ -61,18 +65,25 @@ module Dialogue
# @param branch_no [Integer] The branch number # @param branch_no [Integer] The branch number
# @return [Integer] The number of the next branch # @return [Integer] The number of the next branch
def talk(branch, branch_no) 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) Dialogue.display_branch(branch, branch_no, @debug)
response = get_response(branch) response = get_response(branch)
unless response.zero? unless response == 0
puts "(Your choice: #{branch['options'][response].keys[0]})" puts "\n"
response = branch['options'][response].values[0].to_i 10.times { print "*" }
puts "\n(Your choice: #{branch["options"][response].keys[0]})"
response = branch["options"][response].values[0].to_i
end end
response return response
end end
# Get a response for the displayed branch # Get a response for the displayed branch
@ -80,32 +91,20 @@ module Dialogue
# @param branch [Hash] A branch data set # @param branch [Hash] A branch data set
# @return [Integer] the next branch # @return [Integer] the next branch
def get_response(branch) def get_response(branch)
valid_options = branch['options'].keys.join(', ') valid_options = branch["options"].keys.join(", ")
print "\n[#{valid_options}]> " print "\n[#{valid_options}]> "
STDOUT.flush STDOUT.flush
response = STDIN.gets.chomp response = STDIN.gets.chomp.to_i
until valid_options.include?(response) || response.to_i.zero? until branch["options"].keys.include?(response) or response == 0
print "[## Invalid options. Valid options are #{valid_options}," \ print "[## Invalid options. "
"or 0 to exit.\n[#{valid_options}]> " print "Valid options are #{valid_options}, or 0 to exit."
response = STDIN.gets.chomp print "\n[#{valid_options}]> "
response = STDIN.gets.chomp.to_i
end end
response.to_i return response
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
end end
end end
end end

View file

@ -2,95 +2,47 @@ require 'yaml'
# Gardner is the module for working with a dialogue tree file # Gardner is the module for working with a dialogue tree file
module Gardner 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 # Parse the tree array into an array of numbered branches, and ordered leaves.
# #
# @param tree [File] The dialogue tree file # @param tree [Array] The dialogue tree
def initialize(file)
@tree = file
prune_trunk
prune_branches
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 # @return [Array] An array of numbered branches, with numbered leaves
def prune_branches def self.prune_branches(tree)
@branches = { 0 => { 'desc' => 'Thanks for using Sapling!' } } branches = { 0 => { "desc" => "Thanks for using Sapling!" } }
@tree.each do |b| tree.each do |b|
@branches[b['branch']['number']] = { branches[b["branch"]["number"]] = {
'desc' => b['branch']['text'], "desc" => b["branch"]["text"],
'options' => prune_leaves(b['branch']['leaf']) "options" => prune_leaves(b["branch"]["leaf"]) }
}
end end
return branches
end end
# Parse the leaves of a branch into a numbered hash of options. # Parse the leaves of a branch into a numbered hash of options.
# #
# @param leaves [Array] The option of leaf hashes # @param leaves [Array] The option of leaf hashes
# @return [Hash] A numbered hash of options # @return [Hash] A numbered hash of options
def prune_leaves(leaves) def self.prune_leaves(leaves)
x = 1 x = 1
options = {} options = {}
return options if leaves.nil? return options if leaves.nil?
leaves.each do |l| leaves.each do |l|
options[x] = { l['text'] => l['branch'] } options[x] = { l["text"] => l["branch"] }
x += 1 x += 1
end end
options return options
end end
# Parse the trunk of the tree. # Parse the trunk of the tree.
# #
# @param tree [Hash] The entire tree
# @return [Array] The trunk, and the remainder of the tree # @return [Array] The trunk, and the remainder of the tree
def prune_trunk def self.prune_trunk(tree)
@trunk = @tree.shift trunk = tree.shift
end
end
# Digiplot represents a Plot used for editing. The Digiplot functions exactly return [trunk,tree]
# 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
# Enable editing for the trunk
attr_writer :trunk
# 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
end
# Change a branch
#
# @param branch [Integer] the number of the branch to be edited
def branch=(branch, text)
@branches[branch]['desc'] = text
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
end end
end end

View file

@ -7,32 +7,34 @@
# documentation. # documentation.
# The default trunk text of a new tree # The default trunk text of a new tree
SKELE_TRUNK_TEXT = 'Welcome to the Sapling Editor. For details, please see the SKELE_TRUNK_TEXT = "Welcome to the Sapling Editor. For details, please see the
documentation!'.freeze documentation!"
# The default first-branch text of a new tree # The default first-branch text of a new tree
SKELE_BRANCH_TEXT = 'The first branch is always shown by default. It should act 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 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 # 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 # to it's own branch. The only way out of the program is to either force-quit or
# reply with option 0. # 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 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 # The final tree
SKELETON_TREE = [ SKELETON_TREE = [
{ 'trunk' => SKELE_TRUNK_TEXT.to_s }, {"trunk" => "#{SKELE_TRUNK_TEXT}"},
{ 'branch' => { {"branch" => {
'number' => 1, "number" => 1,
'text' => SKELE_BRANCH_TEXT.to_s, "text" => "#{SKELE_BRANCH_TEXT}",
'leaf' => [{ "leaf" => [{
'text' => SKELE_LEAF_TEXT.to_s, "text" => "#{SKELE_LEAF_TEXT}",
'branch' => 1 "branch" => 1
}] }]
} } }
].freeze }
]
# Verify that a file is a dialogue tree file. # Verify that a file is a dialogue tree file.
# #
@ -42,13 +44,13 @@ def verify_tree(file)
results = [] results = []
begin begin
tree = YAML.load_file(file) tree = YAML.load_file(file)
results << tree[0].keys.include?('trunk') results << tree[0].keys.include?("trunk")
results << tree[1]['branch'].keys.include?('number') results << tree[1]["branch"].keys.include?("number")
results << tree[1]['branch'].keys.include?('text') results << tree[1]["branch"].keys.include?("text")
results << tree[1]['branch'].keys.include?('leaf') results << tree[1]["branch"].keys.include?("leaf")
rescue rescue
puts "Sorry chummer, I don't think this is a tree." 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 results << false
end end

View file

@ -1,10 +1,10 @@
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = 'sapling-dialogue' s.name = 'sapling-dialogue'
s.version = '0.1.2' s.version = '0.1.0'
s.executables << 'sapling' s.executables << 'sapling'
s.date = '2017-10-19' s.date = '2017-10-14'
s.summary = 'A Dialogue Tree Utility' 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.authors = ['Bill Niblock']
s.email = 'azulien@gmail.com' s.email = 'azulien@gmail.com'
s.files = Dir['lib/**/*.rb'] + Dir['bin/*'] s.files = Dir['lib/**/*.rb'] + Dir['bin/*']