Compare commits
2 commits
Author | SHA1 | Date | |
---|---|---|---|
|
8233af348a | ||
|
26a6691ce4 |
8 changed files with 114 additions and 153 deletions
1
Gemfile
1
Gemfile
|
@ -9,4 +9,5 @@ gem "yard"
|
|||
gem "yard-ghpages"
|
||||
|
||||
# Testing
|
||||
gem "minitest"
|
||||
gem "rubocop"
|
||||
|
|
|
@ -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
|
||||
|
|
6
Rakefile
6
Rakefile
|
@ -1,3 +1,9 @@
|
|||
require 'yard-ghpages'
|
||||
|
||||
task default: %w[truth]
|
||||
|
||||
task :truth do
|
||||
puts 'True!'
|
||||
end
|
||||
|
||||
Yard::GHPages::Tasks.install_tasks
|
||||
|
|
|
@ -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!'
|
||||
|
|
|
@ -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 '-' }
|
||||
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
|
||||
|
||||
|
@ -21,9 +22,9 @@ module Dialogue
|
|||
# @param debug [Boolean] Status of showing debug information
|
||||
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
|
||||
|
|
|
@ -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
|
||||
# Parse the tree array into an array of numbered branches, and ordered leaves.
|
||||
#
|
||||
# @param tree [File] The dialogue tree file
|
||||
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
|
||||
# @param tree [Array] 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'])
|
||||
}
|
||||
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
|
||||
|
||||
return branches
|
||||
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)
|
||||
def self.prune_leaves(leaves)
|
||||
x = 1
|
||||
options = {}
|
||||
|
||||
return options if leaves.nil?
|
||||
|
||||
leaves.each do |l|
|
||||
options[x] = { l['text'] => l['branch'] }
|
||||
options[x] = { l["text"] => l["branch"] }
|
||||
x += 1
|
||||
end
|
||||
|
||||
options
|
||||
return options
|
||||
end
|
||||
|
||||
# Parse the trunk of the tree.
|
||||
#
|
||||
# @param tree [Hash] The entire tree
|
||||
# @return [Array] The trunk, and the remainder of the tree
|
||||
def prune_trunk
|
||||
@trunk = @tree.shift
|
||||
end
|
||||
end
|
||||
def self.prune_trunk(tree)
|
||||
trunk = tree.shift
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
return [trunk,tree]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
{"trunk" => "#{SKELE_TRUNK_TEXT}"},
|
||||
{"branch" => {
|
||||
"number" => 1,
|
||||
"text" => "#{SKELE_BRANCH_TEXT}",
|
||||
"leaf" => [{
|
||||
"text" => "#{SKELE_LEAF_TEXT}",
|
||||
"branch" => 1
|
||||
}]
|
||||
} }
|
||||
].freeze
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
|
|
|
@ -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/*']
|
||||
|
|
Loading…
Reference in a new issue