gamefic 1.7.0 → 2.0.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 (99) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +13 -0
  5. data/.solargraph.yml +5 -0
  6. data/Gemfile +7 -0
  7. data/LICENSE +20 -0
  8. data/README.md +28 -0
  9. data/Rakefile +10 -0
  10. data/gamefic.gemspec +27 -0
  11. data/lib/gamefic.rb +7 -6
  12. data/lib/gamefic/action.rb +38 -28
  13. data/lib/gamefic/active.rb +325 -280
  14. data/lib/gamefic/actor.rb +8 -5
  15. data/lib/gamefic/command.rb +9 -7
  16. data/lib/gamefic/core_ext/array.rb +24 -49
  17. data/lib/gamefic/core_ext/string.rb +25 -16
  18. data/lib/gamefic/describable.rb +21 -23
  19. data/lib/gamefic/element.rb +43 -31
  20. data/lib/gamefic/entity.rb +6 -12
  21. data/lib/gamefic/index.rb +121 -0
  22. data/lib/gamefic/{matchable.rb → keywords.rb} +52 -50
  23. data/lib/gamefic/messaging.rb +43 -44
  24. data/lib/gamefic/node.rb +14 -5
  25. data/lib/gamefic/plot.rb +69 -89
  26. data/lib/gamefic/plot/darkroom.rb +92 -264
  27. data/lib/gamefic/plot/host.rb +42 -48
  28. data/lib/gamefic/plot/snapshot.rb +5 -18
  29. data/lib/gamefic/query.rb +14 -18
  30. data/lib/gamefic/query/base.rb +30 -18
  31. data/lib/gamefic/query/children.rb +0 -0
  32. data/lib/gamefic/query/external.rb +18 -14
  33. data/lib/gamefic/query/family.rb +1 -7
  34. data/lib/gamefic/query/matches.rb +75 -67
  35. data/lib/gamefic/query/parent.rb +0 -0
  36. data/lib/gamefic/query/siblings.rb +0 -0
  37. data/lib/gamefic/query/text.rb +2 -1
  38. data/lib/gamefic/scene.rb +0 -2
  39. data/lib/gamefic/scene/activity.rb +24 -26
  40. data/lib/gamefic/scene/base.rb +64 -8
  41. data/lib/gamefic/scene/conclusion.rb +0 -2
  42. data/lib/gamefic/scene/custom.rb +0 -2
  43. data/lib/gamefic/scene/multiple_choice.rb +18 -3
  44. data/lib/gamefic/scene/multiple_scene.rb +29 -20
  45. data/lib/gamefic/scene/pause.rb +7 -2
  46. data/lib/gamefic/scene/yes_or_no.rb +21 -9
  47. data/lib/gamefic/scriptable.rb +87 -0
  48. data/lib/gamefic/serialize.rb +68 -0
  49. data/lib/gamefic/subplot.rb +29 -35
  50. data/lib/gamefic/syntax.rb +14 -13
  51. data/lib/gamefic/version.rb +3 -3
  52. data/lib/gamefic/world.rb +16 -0
  53. data/lib/gamefic/world/callbacks.rb +135 -0
  54. data/lib/gamefic/world/commands.rb +184 -0
  55. data/lib/gamefic/{plot → world}/entities.rb +30 -29
  56. data/lib/gamefic/{plot → world}/playbook.rb +255 -240
  57. data/lib/gamefic/world/players.rb +21 -0
  58. data/lib/gamefic/world/scenes.rb +226 -0
  59. metadata +41 -92
  60. data/bin/gamefic +0 -9
  61. data/lib/gamefic/engine.rb +0 -7
  62. data/lib/gamefic/engine/base.rb +0 -59
  63. data/lib/gamefic/engine/tty.rb +0 -24
  64. data/lib/gamefic/grammar.rb +0 -13
  65. data/lib/gamefic/grammar/conjugator.rb +0 -20
  66. data/lib/gamefic/grammar/gender.rb +0 -11
  67. data/lib/gamefic/grammar/person.rb +0 -10
  68. data/lib/gamefic/grammar/plural.rb +0 -13
  69. data/lib/gamefic/grammar/pronouns.rb +0 -106
  70. data/lib/gamefic/grammar/tense.rb +0 -6
  71. data/lib/gamefic/grammar/verb_set.rb +0 -43
  72. data/lib/gamefic/grammar/verbs.rb +0 -26
  73. data/lib/gamefic/grammar/word_adapter.rb +0 -49
  74. data/lib/gamefic/plot/articles.rb +0 -22
  75. data/lib/gamefic/plot/callbacks.rb +0 -126
  76. data/lib/gamefic/plot/commands.rb +0 -120
  77. data/lib/gamefic/plot/players.rb +0 -15
  78. data/lib/gamefic/plot/scenes.rb +0 -187
  79. data/lib/gamefic/plot/theater.rb +0 -73
  80. data/lib/gamefic/plot/you_mount.rb +0 -22
  81. data/lib/gamefic/script.rb +0 -13
  82. data/lib/gamefic/script/base.rb +0 -42
  83. data/lib/gamefic/script/file.rb +0 -14
  84. data/lib/gamefic/script/text.rb +0 -14
  85. data/lib/gamefic/shell.rb +0 -76
  86. data/lib/gamefic/source.rb +0 -14
  87. data/lib/gamefic/source/base.rb +0 -12
  88. data/lib/gamefic/source/file.rb +0 -23
  89. data/lib/gamefic/source/text.rb +0 -16
  90. data/lib/gamefic/tester.rb +0 -19
  91. data/lib/gamefic/text.rb +0 -8
  92. data/lib/gamefic/text/ansi.rb +0 -53
  93. data/lib/gamefic/text/html.rb +0 -68
  94. data/lib/gamefic/text/html/conversions.rb +0 -250
  95. data/lib/gamefic/text/html/entities.rb +0 -9
  96. data/lib/gamefic/tty.rb +0 -10
  97. data/lib/gamefic/user.rb +0 -7
  98. data/lib/gamefic/user/base.rb +0 -29
  99. data/lib/gamefic/user/tty.rb +0 -38
@@ -1,14 +0,0 @@
1
- module Gamefic
2
-
3
- class Script::File < Script::Base
4
- attr_reader :path, :absolute_path
5
- def initialize filename, path
6
- @absolute_path = filename.gsub(/\/+/, '/')
7
- @path = path
8
- end
9
- def read
10
- File.read(@absolute_path)
11
- end
12
- end
13
-
14
- end
@@ -1,14 +0,0 @@
1
- module Gamefic
2
-
3
- class Script::Text < Script::Base
4
- attr_reader :path, :absolute_path
5
- def initialize path, code, absolute_path = nil
6
- @path = path
7
- @code = code
8
- @absolute_path = absolute_path || path
9
- end
10
- def read
11
- @code
12
- end
13
- end
14
- end
@@ -1,76 +0,0 @@
1
- require 'thor'
2
- require 'gamefic/engine/tty'
3
- require 'zip'
4
- require 'tmpdir'
5
- require 'yaml'
6
-
7
- module Gamefic
8
- class Shell < Thor
9
- map %w[--version -v] => :version
10
-
11
- desc "--version, -v", "Print the version"
12
- def version
13
- puts "gamefic #{Gamefic::VERSION}"
14
- end
15
-
16
- desc 'play FILE_NAME', 'Execute a compiled (.gfic) game'
17
- option :verbose, type: :boolean, aliases: :v, desc: "Don't suppress Ruby exceptions"
18
- def play(file)
19
- Dir.mktmpdir 'gamefic_' do |dir|
20
- puts 'Loading...'
21
- decompress file, dir
22
- run_game(dir)
23
- end
24
- rescue Zip::Error => e
25
- puts "'#{file}' does not appear to be a valid Gamefic file."
26
- show_exception(e) if options[:verbose]
27
- rescue StandardError => e
28
- puts "An error occurred: #{e.message}"
29
- show_exception(e) if options[:verbose]
30
- end
31
-
32
- desc 'info FILE_NAME', 'Print information about a (.gfic) game'
33
- option :verbose, type: :boolean, aliases: :v, desc: "Don't suppress Ruby exceptions"
34
- def info(file)
35
- Dir.mktmpdir 'gamefic_' do |dir|
36
- decompress file, dir
37
- metadata = YAML.load_file File.join(dir, 'metadata.yaml')
38
- metadata.each { |k, v|
39
- puts "#{k}: #{v}"
40
- }
41
- end
42
- rescue StandardError, Zip::Error => e
43
- puts "'#{file}' does not appear to be a valid Gamefic file."
44
- show_exception(e) if options[:verbose]
45
- end
46
-
47
- # Custom error message for invalid command or filename
48
- def method_missing(symbol, *args)
49
- raise UndefinedCommandError, "Could not find command or file named \"#{symbol}\"."
50
- end
51
-
52
- private
53
-
54
- def show_exception(exception)
55
- puts exception.inspect
56
- puts exception.backtrace.join("\n")
57
- end
58
-
59
- def decompress(zipfile, destination)
60
- Zip::File.open(zipfile) do |z|
61
- z.each do |entry|
62
- FileUtils.mkdir_p File.join(destination, File.dirname(entry.name))
63
- full_path = File.join(destination, entry.name)
64
- entry.extract full_path unless File.exist?(full_path)
65
- end
66
- end
67
- end
68
-
69
- def run_game(directory)
70
- plot = Plot.new(Source::File.new(File.join(directory, 'scripts')))
71
- plot.script 'main'
72
- plot.metadata = YAML.load_file File.join(directory, 'metadata.yaml')
73
- Engine::Tty.start(plot)
74
- end
75
- end
76
- end
@@ -1,14 +0,0 @@
1
- module Gamefic
2
-
3
- # Plots use Source classes to fetch scripts to be executed. The most common
4
- # type of Source class is Source::File, which searches for scripts in
5
- # a predefined list of directories on the filesystem, similar to the way
6
- # that Kernel#require works.
7
- #
8
- module Source
9
- autoload :Base, 'gamefic/source/base'
10
- autoload :File, 'gamefic/source/file'
11
- autoload :Text, 'gamefic/source/text'
12
- end
13
-
14
- end
@@ -1,12 +0,0 @@
1
- module Gamefic
2
-
3
- class Source::Base
4
- def initialize
5
- raise "#initialize must be defined in subclasses"
6
- end
7
- def export path
8
- raise "#export must be defined in subclasses"
9
- end
10
- end
11
-
12
- end
@@ -1,23 +0,0 @@
1
- module Gamefic
2
-
3
- class Source::File < Source::Base
4
- @@extensions = ['.plot.rb', '.plot', '.rb']
5
- attr_reader :directories
6
- attr_accessor :main_dir
7
- def initialize(*directories)
8
- @directories = directories || []
9
- end
10
- def export path
11
- @directories.each { |directory|
12
- @@extensions.each { |ext|
13
- abs_file = File.join(directory, path + ext)
14
- if File.file?(abs_file)
15
- return Script::File.new(abs_file, path)
16
- end
17
- }
18
- }
19
- raise LoadError.new("cannot load script -- #{path}")
20
- end
21
- end
22
-
23
- end
@@ -1,16 +0,0 @@
1
- module Gamefic
2
-
3
- class Source::Text < Source::Base
4
- def initialize scripts = {}
5
- @scripts = scripts
6
- end
7
- def export path
8
- if @scripts.has_key?(path)
9
- Script::Text.new(path, @scripts[path])
10
- else
11
- raise "Script #{path} not found"
12
- end
13
- end
14
- end
15
-
16
- end
@@ -1,19 +0,0 @@
1
- module Gamefic
2
-
3
- module Tester
4
- def test_procs
5
- @test_procs ||= Hash.new
6
- end
7
- def on_test name = :me, &block
8
- test_procs[name] = block
9
- end
10
- def run_test name, actor
11
- queue = []
12
- stage actor, queue, &test_procs[name]
13
- actor.queue.push *queue
14
- actor[:test_queue_length] = queue.length
15
- actor[:test_queue_scene] = actor.scene
16
- end
17
- end
18
-
19
- end
@@ -1,8 +0,0 @@
1
- module Gamefic
2
-
3
- module Text
4
- autoload :Ansi, 'gamefic/text/ansi'
5
- autoload :Html, 'gamefic/text/html'
6
- end
7
-
8
- end
@@ -1,53 +0,0 @@
1
- module Gamefic
2
-
3
- # Constants for ANSI codes, plus ExtraCodes for custom formatting.
4
- module Text::Ansi
5
- module Code
6
- module Attribute
7
- NORMAL = 0
8
- BOLD = 1
9
- UNDERSCORE = 4
10
- BLINK = 5
11
- REVERSE = 7
12
- CONCEALED = 8
13
- end
14
- module Foreground
15
- BLACK = 30
16
- RED = 31
17
- GREEN = 32
18
- YELLOW = 33
19
- BLUE = 34
20
- MAGENTA = 35
21
- CYAN = 36
22
- WHITE = 37
23
- end
24
- module Background
25
- BLACK = 40
26
- RED = 41
27
- GREEN = 42
28
- YELLOW = 43
29
- BLUE = 44
30
- MAGENTA = 45
31
- CYAN = 46
32
- WHITE = 47
33
- end
34
- module Extra
35
- BLOCK = :block
36
- PRE = :pre
37
- HREF = :href
38
- IMAGE = :image
39
- SRC = :src
40
- UPPERCASE = :uppercase
41
- COMMAND = :command
42
- IGNORED = :ignored
43
- LINE = :line
44
- end
45
- end
46
- def self.graphics_mode(*settings)
47
- ansi = settings.flatten.that_are(Fixnum)
48
- return '' if ansi.length == 0
49
- "\e[#{ansi.join(';')}m"
50
- end
51
- end
52
-
53
- end
@@ -1,68 +0,0 @@
1
- require 'rexml/document'
2
- require 'gamefic/text/html/entities'
3
-
4
- module Gamefic
5
- module Text
6
- module Html
7
- autoload :Conversions, 'gamefic/text/html/conversions'
8
-
9
- # Convert ampersands to &amp;
10
- #
11
- # @param text [String]
12
- # @return [String]
13
- def self.fix_ampersands(text)
14
- codes = []
15
- ENTITIES.keys.each { |e|
16
- codes.push e[1..-1]
17
- }
18
- piped = codes.join('|')
19
- re = Regexp.new("&(?!(#{piped}))")
20
- text.gsub(re, '&amp;\1')
21
- end
22
-
23
- # Encode a String with HTML entities
24
- #
25
- # @param text [String]
26
- # @return [String]
27
- def self.encode(text)
28
- encoded = text
29
- ENTITIES.each { |k, v|
30
- encoded = encoded.gsub(v, k)
31
- }
32
- encoded
33
- end
34
-
35
- # Decode a String's HTML entities
36
- #
37
- # @param text [String]
38
- # @return [String]
39
- def self.decode(text)
40
- ENTITIES.each { |k, v|
41
- text = text.gsub(k, v)
42
- }
43
- text
44
- end
45
-
46
- # Parse a String into an XML document
47
- #
48
- # @param code [String]
49
- # @return [REXML::Document]
50
- def self.parse(code)
51
- code = fix_ampersands(code).strip
52
- last = nil
53
- begin
54
- doc = REXML::Document.new code
55
- rescue REXML::ParseException => e
56
- # Convert invalid < characters to &lt;
57
- if e.source.buffer != last and e.source.buffer[0,1] == '<'
58
- code = code[0,(code.length - e.source.buffer.length)] + '&lt;' + e.source.buffer[1..-1]
59
- last = e.source.buffer
60
- retry
61
- end
62
- raise e
63
- end
64
- end
65
- end
66
- end
67
-
68
- end
@@ -1,250 +0,0 @@
1
- require 'gamefic/text/ansi'
2
- require 'gamefic/text/html'
3
- require 'io/console'
4
-
5
- module Gamefic
6
-
7
- module Text::Html::Conversions
8
- include Gamefic::Text
9
-
10
- def self.html_to_ansi text, wrap: true, width: nil
11
- return '' if text.strip == ''
12
- output = ''
13
- begin
14
- doc = Html.parse("<body>#{text.gsub(/\r/, '').strip}</body>")
15
- output = AnsiFormatter.new.format(doc) + Ansi.graphics_mode(Ansi::Code::Attribute::NORMAL)
16
- output = Html.decode(output)
17
- rescue REXML::ParseException => e
18
- output = text.strip
19
- end
20
- calc_width = width || size[0]
21
- if calc_width.nil? or !wrap
22
- output
23
- else
24
- terminalize(output, calc_width - 1)
25
- end
26
- end
27
-
28
- def self.html_to_text text, wrap: true, width: nil
29
- text = html_to_ansi text, wrap: wrap, width: width
30
- text.gsub(/\e\[([;\d]+)?m/, '').gsub(/\n +\n/, "\n\n")
31
- end
32
-
33
- class AnsiNode
34
- include Gamefic::Text::Ansi::Code
35
- attr_accessor :parent
36
-
37
- def render
38
- end
39
-
40
- def in_block?
41
- p = parent
42
- while !p.nil?
43
- return true if p.kind_of?(BlockNode)
44
- p = p.parent
45
- end
46
- false
47
- end
48
- end
49
- private_constant :AnsiNode
50
-
51
- class TextNode < AnsiNode
52
- @@prev_format = nil
53
- attr_reader :text, :format
54
- def initialize text, format
55
- @text = text
56
- @format = format
57
- end
58
- def render
59
- return @text if format.include?(Extra::PRE)
60
- index = parent.children.index(self)
61
- if index > 0
62
- prev = parent.children[index - 1]
63
- if prev.kind_of?(TextNode) and prev.format == format
64
- if prev.text.match(/ $/)
65
- return @text.lstrip
66
- else
67
- return @text
68
- end
69
- end
70
- end
71
- if @@prev_format == format
72
- @text
73
- else
74
- @prev_format = format
75
- Gamefic::Text::Ansi.graphics_mode(*format) + @text
76
- end
77
- end
78
- end
79
- private_constant :TextNode
80
-
81
- class ElementNode < AnsiNode
82
- def children
83
- @children ||= []
84
- end
85
- def append child
86
- children.push child
87
- child.parent = self
88
- end
89
- end
90
- private_constant :ElementNode
91
-
92
- class BlockNode < ElementNode
93
- def render
94
- output = ''
95
- children.each { |c|
96
- output += c.render
97
- }
98
- output = "\n" + output.strip unless in_block?
99
- output
100
- end
101
- end
102
- private_constant :BlockNode
103
-
104
- class InlineNode < ElementNode
105
- def render
106
- output = ''
107
- children.each { |c|
108
- output += c.render
109
- output += "\n" if c.kind_of?(BlockNode)
110
- }
111
- output
112
- end
113
- end
114
- private_constant :InlineNode
115
-
116
- class BreakNode < ElementNode
117
- def render
118
- output = ''
119
- children.each { |c|
120
- output += c.render
121
- }
122
- output + "\n"
123
- end
124
- end
125
- private_constant :BreakNode
126
-
127
- class AnsiFormatter
128
- include Gamefic::Text::Ansi::Code
129
- def format document
130
- @document = document
131
- @ansi_root = InlineNode.new
132
- @list_index = []
133
- format_recursively @document.root, @ansi_root, [Attribute::NORMAL]
134
- output = @ansi_root.render
135
- output += (@ansi_root.children.last.kind_of?(BlockNode) ? "\n" : "")
136
- end
137
-
138
- def format_recursively element, ansi_node, stack
139
- if element.is_a?(REXML::Text)
140
- append_text element, ansi_node, stack
141
- else
142
- current = []
143
- case element.name
144
- when 'b', 'strong', 'em'
145
- current.push Attribute::BOLD
146
- format_children element, ansi_node, stack + current
147
- when 'i', 'u'
148
- current.push Attribute::UNDERSCORE
149
- format_children element, ansi_node, stack + current
150
- when 'h1', 'h2', 'h3', 'h4', 'h5'
151
- current.push Attribute::BOLD, Extra::UPPERCASE
152
- format_paragraph element, ansi_node, stack + current
153
- when 'p'
154
- format_paragraph element, ansi_node, stack
155
- when 'ol', 'ul'
156
- @list_index.push 0
157
- format_paragraph element, ansi_node, stack
158
- @list_index.pop
159
- when 'li'
160
- format_list_item element, ansi_node, stack
161
- when 'pre'
162
- current.push Extra::PRE
163
- format_children element, ansi_node, stack + current
164
- when 'br'
165
- ansi_node.append TextNode.new("\n", stack + [Extra::PRE])
166
- else
167
- format_children element, ansi_node, stack
168
- end
169
- end
170
- end
171
-
172
- def append_text element, ansi_node, stack
173
- text = element.to_s
174
- text.gsub!(/[\s]+/, ' ') unless stack.include?(Extra::PRE)
175
- text.upcase! if stack.include?(Extra::UPPERCASE)
176
- ansi_node.append TextNode.new(text, stack)
177
- end
178
-
179
- def format_children element, node, stack
180
- element.each { |e|
181
- format_recursively e, node, stack
182
- }
183
- end
184
-
185
- def format_paragraph element, node, stack
186
- paragraph = BlockNode.new
187
- node.append paragraph
188
- format_children element, paragraph, stack
189
- end
190
-
191
- def format_list_item element, node, stack
192
- i = 0
193
- unless @list_index.empty?
194
- @list_index[-1] = @list_index[-1] + 1
195
- i = @list_index[-1]
196
- end
197
- b = BreakNode.new
198
- node.append b
199
- if element.parent.name == 'ol'
200
- b.append TextNode.new("#{i}. ", stack)
201
- else
202
- b.append TextNode.new("* ", stack)
203
- end
204
- format_children element, b, stack
205
- end
206
- end
207
- private_constant :AnsiFormatter
208
-
209
- def self.terminalize string, max_length
210
- i = 0
211
- output = ''
212
- line_length = 0
213
- while i < string.length
214
- line_length += 1
215
- char = string[i,1]
216
- if char == "\e"
217
- # Right now, graphics modes are the only supported ANSI sequences.
218
- end_of_seq = string.index("m", i)
219
- output += string[i..end_of_seq]
220
- i = end_of_seq + 1
221
- elsif char == " "
222
- next_space = string.index(/[\s]/, i + 1)
223
- if !next_space.nil? and line_length + (next_space - i) > max_length
224
- output += "\n"
225
- line_length = 0
226
- else
227
- output += char
228
- end
229
- i += 1
230
- else
231
- if char == "\n"
232
- line_length = 0
233
- end
234
- output += char
235
- i += 1
236
- end
237
- end
238
- output
239
- end
240
-
241
- def self.size
242
- begin
243
- return STDOUT.winsize.reverse
244
- rescue
245
- return [nil,nil]
246
- end
247
- end
248
- end
249
-
250
- end