gamefic 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/gamefic.rb +1 -2
  3. data/lib/gamefic/character.rb +31 -45
  4. data/lib/gamefic/director/delegate.rb +46 -24
  5. data/lib/gamefic/director/order.rb +7 -0
  6. data/lib/gamefic/director/parser.rb +11 -11
  7. data/lib/gamefic/engine/base.rb +2 -3
  8. data/lib/gamefic/entity.rb +8 -26
  9. data/lib/gamefic/plot.rb +38 -242
  10. data/lib/gamefic/plot/callbacks.rb +101 -0
  11. data/lib/gamefic/plot/command_mount.rb +70 -40
  12. data/lib/gamefic/plot/entities.rb +82 -0
  13. data/lib/gamefic/plot/host.rb +46 -0
  14. data/lib/gamefic/plot/playbook.rb +192 -0
  15. data/lib/gamefic/plot/players.rb +15 -0
  16. data/lib/gamefic/plot/scene_mount.rb +69 -31
  17. data/lib/gamefic/plot/snapshot.rb +20 -5
  18. data/lib/gamefic/scene/active.rb +8 -1
  19. data/lib/gamefic/scene/base.rb +4 -26
  20. data/lib/gamefic/scene/custom.rb +53 -3
  21. data/lib/gamefic/scene/multiple_choice.rb +1 -0
  22. data/lib/gamefic/scene/yes_or_no.rb +1 -1
  23. data/lib/gamefic/scene_data/multiple_scene.rb +0 -4
  24. data/lib/gamefic/shell.rb +0 -1
  25. data/lib/gamefic/source/file.rb +1 -1
  26. data/lib/gamefic/stage.rb +10 -2
  27. data/lib/gamefic/subplot.rb +70 -53
  28. data/lib/gamefic/syntax.rb +9 -2
  29. data/lib/gamefic/tester.rb +1 -1
  30. data/lib/gamefic/text.rb +8 -0
  31. data/lib/gamefic/{ansi.rb → text/ansi.rb} +12 -15
  32. data/lib/gamefic/text/html.rb +68 -0
  33. data/lib/gamefic/text/html/conversions.rb +250 -0
  34. data/lib/gamefic/text/html/entities.rb +9 -0
  35. data/lib/gamefic/tty.rb +2 -0
  36. data/lib/gamefic/user/tty.rb +2 -4
  37. data/lib/gamefic/version.rb +1 -1
  38. metadata +12 -8
  39. data/lib/gamefic/direction.rb +0 -46
  40. data/lib/gamefic/html.rb +0 -68
  41. data/lib/gamefic/html_to_ansi.rb +0 -185
  42. data/lib/gamefic/plot/entity_mount.rb +0 -45
  43. data/lib/gamefic/rule.rb +0 -18
@@ -14,6 +14,7 @@ module Gamefic
14
14
 
15
15
  def start actor
16
16
  data = start_data_for(actor)
17
+ data.options.clear
17
18
  do_start_block actor, data
18
19
  tell_options actor, data
19
20
  end
@@ -15,7 +15,7 @@ module Gamefic
15
15
  if data.yes? or data.no?
16
16
  this_scene = actor.scene
17
17
  do_finish_block actor, data
18
- actor.cue :active if (actor.scene == this_scene and actor.next_scene.nil?)
18
+ #actor.cue :active if (actor.scene == this_scene and actor.next_scene.nil?)
19
19
  else
20
20
  actor.tell data.invalid_message
21
21
  end
@@ -1,8 +1,6 @@
1
1
  module Gamefic
2
2
 
3
3
  class SceneData::MultipleScene < SceneData::MultipleChoice
4
- attr_accessor :selection
5
- attr_accessor :number
6
4
  def options
7
5
  scene_map.keys
8
6
  end
@@ -15,8 +13,6 @@ module Gamefic
15
13
  scene_map[choice]
16
14
  end
17
15
 
18
- private
19
-
20
16
  def scene_map
21
17
  @scene_map ||= {}
22
18
  end
data/lib/gamefic/shell.rb CHANGED
@@ -70,7 +70,6 @@ module Gamefic
70
70
  plot = Plot.new(Source::File.new(File.join(directory, 'scripts')))
71
71
  plot.script 'main'
72
72
  plot.metadata = YAML.load_file File.join(directory, 'metadata.yaml')
73
- puts ""
74
73
  Engine::Tty.start(plot)
75
74
  end
76
75
  end
@@ -16,7 +16,7 @@ module Gamefic
16
16
  end
17
17
  }
18
18
  }
19
- raise "Script #{path} not found"
19
+ raise LoadError.new("cannot load script -- #{path}")
20
20
  end
21
21
  end
22
22
 
data/lib/gamefic/stage.rb CHANGED
@@ -3,16 +3,17 @@ module Gamefic
3
3
  module Stage
4
4
  def stage *args, &block
5
5
  s = generate_stage
6
-
7
6
  if block.nil?
8
7
  s.module_eval(*args)
9
8
  else
10
9
  s.module_exec(*args, &block)
11
10
  end
12
11
  end
12
+
13
13
  private
14
+
14
15
  def generate_stage
15
- return @stage if !@stage.nil?
16
+ return @stage unless @stage.nil?
16
17
 
17
18
  exposed = self.class.exposed_methods.keys
18
19
  mounted = self.class.mounted_modules.keys
@@ -44,6 +45,7 @@ module Gamefic
44
45
 
45
46
  return @stage
46
47
  end
48
+
47
49
  module ClassMethods
48
50
  def mount *args
49
51
  args.each { |a|
@@ -51,22 +53,28 @@ module Gamefic
51
53
  mounted_modules[a] = nil
52
54
  }
53
55
  end
56
+
54
57
  def expose *args
55
58
  args.each { |a|
56
59
  exposed_methods[a] = nil
57
60
  }
58
61
  end
62
+
59
63
  def exposed_methods
60
64
  @@exposed_methods ||= from_superclass(:exposed_methods, {}).dup
61
65
  end
66
+
62
67
  def mounted_modules
63
68
  @@mounted_modules ||= from_superclass(:mounted_modules, {}).dup
64
69
  end
70
+
65
71
  private
72
+
66
73
  def from_superclass(m, default = nil)
67
74
  superclass.respond_to?(m) ? superclass.send(m) : default
68
75
  end
69
76
  end
77
+
70
78
  def self.included(base)
71
79
  base.extend(ClassMethods)
72
80
  end
@@ -1,82 +1,99 @@
1
+ require 'gamefic/plot'
2
+
1
3
  module Gamefic
2
4
 
3
5
  class Subplot
4
- module Element
5
- attr_reader :subplot
6
- end
7
- module Feature
8
- def subplots
9
- p_subplots.clone
10
- end
11
- private
12
- def p_subplots
13
- @p_subplots ||= []
14
- end
15
- end
16
- module Host
17
- # Get an array of all the current subplots.
18
- #
19
- # @return [Array<Subplot>]
20
- def subplots
21
- p_subplots.clone
22
- end
23
-
24
- # Start a new subplot based on the provided class.
25
- #
26
- # @param [Class] The class of the subplot to be created (Subplot by default)
27
- # @return [Subplot]
28
- def branch subplot_class, feature:nil, &block
29
- subplot = subplot_class.new(self, feature: feature, &block)
30
- p_subplots.push subplot
31
- subplot
6
+ include Stage
7
+
8
+ attr_reader :plot
9
+ attr_writer :denied_message
10
+
11
+ mount Plot::Entities
12
+ mount Plot::CommandMount
13
+ mount Plot::Callbacks
14
+ mount Plot::SceneMount
15
+ expose :plot, :conclude
16
+
17
+ class << self
18
+ def start_proc
19
+ @start_proc
32
20
  end
33
-
21
+
34
22
  private
35
- def p_subplots
36
- @p_subplots ||= []
23
+
24
+ def on_start &block
25
+ @start_proc = block
37
26
  end
38
27
  end
39
-
40
- attr_reader :plot, :entities, :players
41
-
42
- def initialize plot, feature:nil
28
+
29
+ def initialize plot, introduce: nil
43
30
  @plot = plot
44
- @entities = []
45
- @players = []
46
31
  @concluded = false
47
- post_initialize
48
- yield(self) if block_given?
49
- introduce feature unless feature.nil?
32
+ stage &self.class.start_proc unless self.class.start_proc.nil?
33
+ playbook.freeze
34
+ self.introduce introduce unless introduce.nil?
35
+ end
36
+
37
+ def default_scene
38
+ plot.default_scene
39
+ end
40
+
41
+ def default_conclusion
42
+ plot.default_conclusion
43
+ end
44
+
45
+ def playbook
46
+ @playbook ||= plot.playbook.dup
50
47
  end
51
- def post_initialize
48
+
49
+ # HACK: Always assume subplots are running for the sake of entity destruction
50
+ def running?
51
+ true
52
52
  end
53
- def make *args
54
- e = plot.make(*args)
55
- e.extend Gamefic::Subplot::Element
56
- e.instance_variable_set(:@subplot, self)
57
- entities.push e
58
- e
53
+
54
+ def denied_message
55
+ @denied_message ||= 'You are already involved in another subplot.'
59
56
  end
57
+
60
58
  def introduce player
61
- player.send(:p_subplots).push self
62
- players.push player
59
+ if plot.subbed?(player)
60
+ player.tell denied_message
61
+ else
62
+ super
63
+ end
63
64
  end
65
+
64
66
  def exeunt player
65
- player.send(:p_subplots).delete self
66
- players.delete player
67
+ player.playbook = plot.playbook
68
+ p_players.delete player
67
69
  end
70
+
68
71
  def conclude
69
- concluded = true
72
+ @concluded = true
70
73
  entities.each { |e|
71
- e.destroy
74
+ destroy e
72
75
  }
73
76
  players.each { |p|
74
77
  exeunt p
75
78
  }
76
79
  end
80
+
77
81
  def concluded?
78
82
  @concluded
79
83
  end
84
+
85
+ def ready
86
+ conclude if players.empty?
87
+ return if concluded?
88
+ playbook.freeze
89
+ call_ready
90
+ call_player_ready
91
+ end
92
+
93
+ def update
94
+ call_player_update
95
+ call_update
96
+ end
80
97
  end
81
98
 
82
99
  end
@@ -60,7 +60,10 @@ module Gamefic
60
60
  m = text.match(@regexp)
61
61
  return nil if m.nil?
62
62
  arguments = []
63
- @replace.to_s.split_words.each { |r|
63
+ # HACK: Skip the first word if the verb is not nil.
64
+ # This is ugly.
65
+ b = @verb.nil? ? 0 : 1
66
+ @replace.to_s.split_words[b..-1].each { |r|
64
67
  if r.match(/^\{\$[0-9]+\}$/)
65
68
  arguments.push m[r[2..-2].to_i]
66
69
  else
@@ -70,6 +73,10 @@ module Gamefic
70
73
  Command.new @verb, arguments
71
74
  end
72
75
 
76
+ def accept? text
77
+ !text.match(@regexp).nil?
78
+ end
79
+
73
80
  # Get a signature that identifies the form of the Syntax.
74
81
  # Signatures are used to compare Syntaxes to each other.
75
82
  #
@@ -106,7 +113,7 @@ module Gamefic
106
113
  }
107
114
  cb <=> ca
108
115
  }
109
- matches
116
+ matches
110
117
  end
111
118
  end
112
119
 
@@ -9,7 +9,7 @@ module Gamefic
9
9
  end
10
10
  def run_test name, actor
11
11
  queue = []
12
- actor.plot.stage actor, queue, &test_procs[name]
12
+ stage actor, queue, &test_procs[name]
13
13
  actor.queue.push *queue
14
14
  actor[:test_queue_length] = queue.length
15
15
  actor[:test_queue_scene] = actor.scene
@@ -0,0 +1,8 @@
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,11 +1,8 @@
1
1
  module Gamefic
2
2
 
3
- # Constants for ANSI codes, plus Extras for custom formatting.
4
- module Ansi
3
+ # Constants for ANSI codes, plus ExtraCodes for custom formatting.
4
+ module Text::Ansi
5
5
  module Code
6
- class Nonstandard < String
7
-
8
- end
9
6
  module Attribute
10
7
  NORMAL = 0
11
8
  BOLD = 1
@@ -35,19 +32,19 @@ module Gamefic
35
32
  WHITE = 47
36
33
  end
37
34
  module Extra
38
- BLOCK = Nonstandard.new("block")
39
- PRE = Nonstandard.new("pre")
40
- HREF = Nonstandard.new("href")
41
- IMAGE = Nonstandard.new("image")
42
- SRC = Nonstandard.new("src")
43
- UPPERCASE = Nonstandard.new("uppercase")
44
- COMMAND = Nonstandard.new("command")
45
- IGNORED = Nonstandard.new("ignored")
46
- LINE = Nonstandard.new("line")
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
47
44
  end
48
45
  end
49
46
  def self.graphics_mode(*settings)
50
- ansi = settings.flatten.that_are_not(Code::Nonstandard)
47
+ ansi = settings.flatten.that_are(Fixnum)
51
48
  return '' if ansi.length == 0
52
49
  "\e[#{ansi.join(';')}m"
53
50
  end
@@ -0,0 +1,68 @@
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
@@ -0,0 +1,250 @@
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