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"
|
gem "yard-ghpages"
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
|
gem "minitest"
|
||||||
gem "rubocop"
|
gem "rubocop"
|
||||||
|
|
|
@ -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
|
||||||
|
|
6
Rakefile
6
Rakefile
|
@ -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
|
||||||
|
|
|
@ -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!'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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/*']
|
||||||
|
|
Loading…
Reference in a new issue