fable 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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