fable 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +30 -0
  3. data/.gitignore +57 -0
  4. data/.ruby-version +1 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +7 -0
  7. data/Gemfile.lock +30 -0
  8. data/LICENSE +21 -0
  9. data/README.md +2 -0
  10. data/Rakefile +10 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/bin/test +8 -0
  14. data/fable.gemspec +34 -0
  15. data/fable.sublime-project +8 -0
  16. data/lib/fable.rb +49 -0
  17. data/lib/fable/call_stack.rb +351 -0
  18. data/lib/fable/choice.rb +31 -0
  19. data/lib/fable/choice_point.rb +65 -0
  20. data/lib/fable/container.rb +218 -0
  21. data/lib/fable/control_command.rb +156 -0
  22. data/lib/fable/debug_metadata.rb +13 -0
  23. data/lib/fable/divert.rb +100 -0
  24. data/lib/fable/glue.rb +7 -0
  25. data/lib/fable/ink_list.rb +425 -0
  26. data/lib/fable/list_definition.rb +44 -0
  27. data/lib/fable/list_definitions_origin.rb +35 -0
  28. data/lib/fable/native_function_call.rb +324 -0
  29. data/lib/fable/native_function_operations.rb +149 -0
  30. data/lib/fable/observer.rb +205 -0
  31. data/lib/fable/path.rb +186 -0
  32. data/lib/fable/pointer.rb +42 -0
  33. data/lib/fable/profiler.rb +287 -0
  34. data/lib/fable/push_pop_type.rb +11 -0
  35. data/lib/fable/runtime_object.rb +159 -0
  36. data/lib/fable/search_result.rb +20 -0
  37. data/lib/fable/serializer.rb +560 -0
  38. data/lib/fable/state_patch.rb +47 -0
  39. data/lib/fable/story.rb +1447 -0
  40. data/lib/fable/story_state.rb +915 -0
  41. data/lib/fable/tag.rb +14 -0
  42. data/lib/fable/value.rb +334 -0
  43. data/lib/fable/variable_assignment.rb +20 -0
  44. data/lib/fable/variable_reference.rb +38 -0
  45. data/lib/fable/variables_state.rb +327 -0
  46. data/lib/fable/version.rb +3 -0
  47. data/lib/fable/void.rb +4 -0
  48. data/zork_mode.rb +23 -0
  49. metadata +149 -0
@@ -0,0 +1,31 @@
1
+ module Fable
2
+ class Choice < RuntimeObject
3
+ # The main text presented to the player for this choice
4
+ attr_accessor :text
5
+
6
+
7
+ # Get the path to the original choice point - where was this choice
8
+ # defined in the story?
9
+ attr_accessor :source_path
10
+
11
+ # The original index into current_choices list on the Story when
12
+ # this choice was generated, for convenience
13
+ attr_accessor :index
14
+
15
+ attr_accessor :target_path, :thread_at_generation, :original_thread_index,
16
+ :invisible_default
17
+
18
+ alias_method :invisible_default?, :invisible_default
19
+
20
+ # The target path that the story should be diverted to
21
+ # if the choice is chosen
22
+ def path_string_on_choice
23
+ return self.target_path.to_s
24
+ end
25
+
26
+ def path_string_on_choice=(value)
27
+ self.target_path = Path.new(value)
28
+ value
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,65 @@
1
+ module Fable
2
+ class ChoicePoint < RuntimeObject
3
+ attr_accessor :has_condition,
4
+ :has_start_content, :has_choice_only_content,
5
+ :invisible_default,
6
+ :once_only, :path_on_choice
7
+
8
+ alias_method :invisible_default?, :invisible_default
9
+ alias_method :has_start_content?, :has_start_content
10
+ alias_method :has_condition?, :has_condition
11
+ alias_method :has_choice_only_content?, :has_choice_only_content
12
+ alias_method :once_only?, :once_only
13
+
14
+ # The ChoicePoint represents the point within the Story
15
+ # where a Choice instance gets generated. The distinction
16
+ # is made because the text of the choice can be dynamically
17
+ # generated
18
+ def path_on_choice
19
+ # Resolve any relative pahts to global ones as we come across them
20
+ if !@path_on_choice.nil? && @path_on_choice.relative?
21
+ choice_target_object = choice_target
22
+ if !choice_target_object.nil?
23
+ @path_on_choice = choice_target_object.path
24
+ end
25
+ end
26
+
27
+ return @path_on_choice
28
+ end
29
+
30
+ def path_on_choice=(value)
31
+ @path_on_choice = value
32
+ end
33
+
34
+ def choice_target
35
+ self.resolve_path(@path_on_choice).container
36
+ end
37
+
38
+ def path_string_on_choice
39
+ compact_path_string(path_on_choice)
40
+ end
41
+
42
+ def path_string_on_choice=(value)
43
+ self.path_on_choice = Path.new(value)
44
+ end
45
+
46
+ def flags=(flag)
47
+ self.has_condition = (flag & 1) > 0
48
+ self.has_start_content = (flag & 2) > 0
49
+ self.has_choice_only_content = (flag & 4) > 0
50
+ self.invisible_default = (flag & 8) > 0
51
+ self.once_only = (flag & 16) > 0
52
+ end
53
+
54
+ def to_s
55
+ target_line_number = debug_line_number_of_path(path_on_choice)
56
+ target_string = path_on_choice.to_s
57
+
58
+ if !target_line_number.nil?
59
+ target_string = " line #{target_line_number} (#{target_string})"
60
+ end
61
+
62
+ return "Choice: -> #{target_string}"
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,218 @@
1
+ module Fable
2
+ class Container < RuntimeObject
3
+ attr_accessor :bit_flags, :name, :content, :named_content,
4
+ :visits_should_be_counted, :turn_index_should_be_counted,
5
+ :counting_at_start_only, :path_to_first_leaf_content
6
+
7
+ def initialize(flags)
8
+ super()
9
+ self.bit_flags = flags
10
+ self.content = []
11
+ self.named_content = {}
12
+ self.process_bit_flags
13
+ end
14
+
15
+ def add_content(content_to_add)
16
+ if content_to_add.is_a?(Enumerable)
17
+ content_to_add.each{|individual_items| add_content(individual_items) }
18
+ return
19
+ end
20
+
21
+ debugger if content_to_add.nil?
22
+
23
+ if !content_to_add.parent.nil?
24
+ raise Error, "content is already in #{content_to_add.parent}"
25
+ end
26
+
27
+ content_to_add.parent = self
28
+ content << content_to_add
29
+
30
+ try_adding_to_named_content(content_to_add)
31
+ end
32
+
33
+ def insert_content_at(content_to_add, index)
34
+ if !content_to_add.parent.nil?
35
+ raise Error, "content is already in #{content_to_add.parent}"
36
+ end
37
+
38
+ content_to_add.parent = self
39
+ content.insert(index, content_to_add)
40
+ try_adding_to_named_content(content_to_add)
41
+ end
42
+
43
+ def try_adding_to_named_content(content_to_add)
44
+ if content_to_add.respond_to?(:valid_name?) && content_to_add.valid_name?
45
+ add_to_named_content(content_to_add)
46
+ end
47
+ end
48
+
49
+ def add_to_named_content(content_to_add)
50
+ content_to_add.parent = self
51
+ named_content[content_to_add.name] = content_to_add
52
+ end
53
+
54
+ def add_contents_of_container(other_container)
55
+ content += other_container.content
56
+ other_container.content.each do |content_to_add|
57
+ content_to_add.parent = self
58
+ try_adding_to_named_content(content_to_add)
59
+ end
60
+ end
61
+
62
+ def content_with_path_component(component)
63
+ if component.is_index?
64
+ return content[component.index]
65
+ elsif component.is_parent?
66
+ return self.parent
67
+ else
68
+ return named_content[component.name]
69
+ end
70
+ end
71
+
72
+ def content_at_path(path, options= { partial_path_start: 0, partial_path_length: -1 })
73
+ partial_path_length = options[:partial_path_length]
74
+ partial_path_start = options[:partial_path_start]
75
+
76
+ if partial_path_length == -1
77
+ partial_path_length = path.length
78
+ end
79
+
80
+ result = SearchResult.new
81
+ result.approximate = false
82
+
83
+ current_container = self
84
+ current_object = self
85
+
86
+ partial_path_end = (partial_path_length - 1)
87
+
88
+ (partial_path_start..partial_path_end).each do |i|
89
+ component = path.components[i]
90
+
91
+ # Path component was wrong type
92
+ if current_container.nil?
93
+ result.approximate = true
94
+ break
95
+ end
96
+
97
+ found_object = current_container.content_with_path_component(component)
98
+
99
+ # Couldn't resolve entire path?
100
+ if found_object.nil?
101
+ result.approximate = true
102
+ break
103
+ end
104
+
105
+ current_object = found_object
106
+ if found_object.is_a?(Container)
107
+ current_container = found_object
108
+ else
109
+ current_container = nil
110
+ end
111
+ end
112
+
113
+ result.object = current_object
114
+ return result
115
+ end
116
+
117
+ def build_string_of_hierarchy(io, indentation, pointed_object)
118
+ io << indentation_string(indentation)
119
+ io << "["
120
+
121
+ if self.valid_name?
122
+ io << " (#{self.name})"
123
+ end
124
+
125
+ if self == pointed_object
126
+ io << " <---"
127
+ end
128
+
129
+ io << "\n"
130
+
131
+ indentation += 1
132
+
133
+
134
+ content.each_with_index do |object, index|
135
+ if object.is_a?(Container)
136
+ object.build_string_of_hierarchy(io, indentation, pointed_object)
137
+ else
138
+ io << indentation_string(indentation)
139
+ if object.is_a?(StringValue)
140
+ io << "\"#{object.to_s.gsub("\n", "\\n")}\""
141
+ else
142
+ io << object.to_s
143
+ end
144
+ end
145
+
146
+ if index != (content.size - 1)
147
+ io << ","
148
+ end
149
+
150
+ if !object.is_a?(Container) && object == pointed_object
151
+ io << " <---"
152
+ end
153
+
154
+ io << "\n"
155
+ end
156
+
157
+ only_named_content = named_content.reject{|name, item| content.include?(item) }
158
+
159
+ if only_named_content.any?
160
+ io << indentation_string(indentation)
161
+ io << "-- named: --\n"
162
+
163
+ only_named_content.each do |key, container|
164
+ container.build_string_of_hierarchy(io, indentation, pointed_object)
165
+ io << "\n"
166
+ end
167
+ end
168
+
169
+ indentation -= 1
170
+
171
+ io << indentation_string(indentation)
172
+ io << "]"
173
+ end
174
+
175
+ def path_to_first_leaf_content
176
+ @path_to_first_leaf_content ||= path.path_by_appending_path(internal_path_to_first_lead_content)
177
+ end
178
+
179
+ def valid_name?
180
+ !name.to_s.empty?
181
+ end
182
+
183
+ alias_method :visits_should_be_counted?, :visits_should_be_counted
184
+ alias_method :turn_index_should_be_counted?, :turn_index_should_be_counted
185
+ alias_method :counting_at_start_only?, :counting_at_start_only
186
+
187
+ def process_bit_flags
188
+ if has_bit_flags?
189
+ self.visits_should_be_counted = (bit_flags & 1) > 0
190
+ self.turn_index_should_be_counted = (bit_flags & 2) > 0
191
+ self.counting_at_start_only = (bit_flags & 4) > 0
192
+ else
193
+ self.visits_should_be_counted = false
194
+ self.turn_index_should_be_counted = false
195
+ self.counting_at_start_only = false
196
+ end
197
+ end
198
+
199
+ def has_bit_flags?
200
+ !self.bit_flags.nil?
201
+ end
202
+
203
+ protected
204
+
205
+ def internal_path_to_first_lead_content
206
+ components = []
207
+ container = self
208
+ while !container.nil?
209
+ if container.content.size > 0
210
+ components << Path::Component.new(0)
211
+ container = container.content.first
212
+ end
213
+ end
214
+
215
+ return Path.new(components)
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,156 @@
1
+ module Fable
2
+ class ControlCommand < RuntimeObject
3
+ COMMANDS = {
4
+ NOT_SET: -1,
5
+ EVALUATION_START: "ev",
6
+ EVALUATION_OUTPUT: "out",
7
+ EVALUATION_END: "/ev",
8
+ DUPLICATE_TOPMOST: "du",
9
+ POP_EVALUATED_VALUE: "pop",
10
+ POP_FUNCTION: "~ret",
11
+ POP_TUNNEL: "->->",
12
+ BEGIN_STRING_EVALUATION_MODE: "str",
13
+ END_STRING_EVALUATION_MODE: "/str",
14
+ NOOP: "nop",
15
+ PUSH_CHOICE_COUNT: "choiceCnt",
16
+ TURNS: "turn",
17
+ TURNS_SINCE: "turns",
18
+ READ_COUNT: "readc",
19
+ RANDOM: "rnd",
20
+ SEED_RANDOM: "srnd",
21
+ VISIT_INDEX: "visit",
22
+ SEQUENCE_SHUFFLE_INDEX: "seq",
23
+ START_THREAD: "thread",
24
+ DONE: "done",
25
+ STORY_END: "end",
26
+ LIST_FROM_INT: "listInt",
27
+ LIST_RANGE: "range",
28
+ LIST_RANDOM: "lrnd",
29
+
30
+ GLUE: "<>",
31
+ }.freeze
32
+
33
+ LOOKUP = COMMANDS.invert.freeze
34
+
35
+ attr_accessor :command_type
36
+
37
+ def self.is_control_command?(value)
38
+ LOOKUP.has_key?(value)
39
+ end
40
+
41
+ def self.get_control_command(value)
42
+ raise ArgumentError if !is_control_command?(value)
43
+ self.new(value)
44
+ end
45
+
46
+ def initialize(command_symbol)
47
+ super()
48
+ self.command_type = LOOKUP[command_symbol]
49
+ end
50
+
51
+ def self.is_instance_of?(object, command_type)
52
+ object.is_a?(self) && object.command_type == command_type
53
+ end
54
+
55
+ def to_s
56
+ command_type.to_s
57
+ end
58
+
59
+ def self.evaluation_start
60
+ self.new(COMMANDS[:EVALUATION_START])
61
+ end
62
+
63
+ def self.evaluation_output
64
+ self.new(COMMANDS[:EVALUATION_OUTPUT])
65
+ end
66
+
67
+ def self.evaluation_end
68
+ self.new(COMMANDS[:EVALUATION_END])
69
+ end
70
+
71
+ def self.duplicate_topmost
72
+ self.new(COMMANDS[:DUPLICATE_TOPMOST])
73
+ end
74
+
75
+ def self.pop_evaluated_value
76
+ self.new(COMMANDS[:POP_EVALUATED_VALUE])
77
+ end
78
+
79
+ def self.pop_function
80
+ self.new(COMMANDS[:POP_FUNCTION])
81
+ end
82
+
83
+ def self.pop_tunnel
84
+ self.new(COMMANDS[:POP_TUNNEL])
85
+ end
86
+
87
+ def self.begin_string_evaluation_mode
88
+ self.new(COMMANDS[:BEGIN_STRING_EVALUATION_MODE])
89
+ end
90
+
91
+ def self.end_string_evaluation_mode
92
+ self.new(COMMANDS[:END_STRING_EVALUATION_MODE])
93
+ end
94
+
95
+ def self.noop
96
+ self.new(COMMANDS[:NOOP])
97
+ end
98
+
99
+ def self.push_choice_count
100
+ self.new(COMMANDS[:PUSH_CHOICE_COUNT])
101
+ end
102
+
103
+ def self.turns
104
+ self.new(COMMANDS[:TURNS])
105
+ end
106
+
107
+ def self.turns_since
108
+ self.new(COMMANDS[:TURNS_SINCE])
109
+ end
110
+
111
+ def self.read_count
112
+ self.new(COMMANDS[:READ_COUNT])
113
+ end
114
+
115
+ def self.random
116
+ self.new(COMMANDS[:RANDOM])
117
+ end
118
+
119
+ def self.seed_random
120
+ self.new(COMMANDS[:SEED_RANDOM])
121
+ end
122
+
123
+ def self.visit_index
124
+ self.new(COMMANDS[:VISIT_INDEX])
125
+ end
126
+
127
+ def self.sequence_shuffle_index
128
+ self.new(COMMANDS[:SEQUENCE_SHUFFLE_INDEX])
129
+ end
130
+
131
+ def self.start_thread
132
+ self.new(COMMANDS[:START_THREAD])
133
+ end
134
+
135
+ def self.done
136
+ self.new(COMMANDS[:DONE])
137
+ end
138
+
139
+ def self.story_end
140
+ self.new(COMMANDS[:STORY_END])
141
+ end
142
+
143
+ def self.list_from_int
144
+ self.new(COMMANDS[:LIST_FROM_INT])
145
+ end
146
+
147
+ def self.list_range
148
+ self.new(COMMANDS[:LIST_RANGE])
149
+ end
150
+
151
+ def self.list_random
152
+ self.new(COMMANDS[:LIST_RANDOM])
153
+ end
154
+
155
+ end
156
+ end