gamefic 0.2.0 → 0.6.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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/lib/gamefic/action.rb +87 -56
  3. data/lib/gamefic/ansi.rb +55 -0
  4. data/lib/gamefic/character.rb +130 -76
  5. data/lib/gamefic/command.rb +19 -0
  6. data/lib/gamefic/core_ext/array.rb +51 -40
  7. data/lib/gamefic/core_ext/string.rb +4 -0
  8. data/lib/gamefic/describable.rb +108 -46
  9. data/lib/gamefic/direction.rb +46 -0
  10. data/lib/gamefic/director/delegate.rb +91 -0
  11. data/lib/gamefic/director/order.rb +10 -0
  12. data/lib/gamefic/director/parser.rb +119 -0
  13. data/lib/gamefic/director.rb +16 -197
  14. data/lib/gamefic/engine/cgi.rb +221 -0
  15. data/lib/gamefic/engine/tty.rb +237 -0
  16. data/lib/gamefic/engine.rb +88 -67
  17. data/lib/gamefic/entity.rb +96 -69
  18. data/lib/gamefic/grammar/conjugator.rb +20 -0
  19. data/lib/gamefic/grammar/gender.rb +11 -0
  20. data/lib/gamefic/grammar/person.rb +10 -0
  21. data/lib/gamefic/grammar/plural.rb +13 -0
  22. data/lib/gamefic/grammar/pronouns.rb +60 -0
  23. data/lib/gamefic/grammar/tense.rb +6 -0
  24. data/lib/gamefic/grammar/verb_set.rb +43 -0
  25. data/lib/gamefic/grammar/verbs.rb +25 -0
  26. data/lib/gamefic/grammar/word_adapter.rb +36 -0
  27. data/lib/gamefic/grammar.rb +13 -0
  28. data/lib/gamefic/html.rb +53 -0
  29. data/lib/gamefic/keywords.rb +51 -33
  30. data/lib/gamefic/node.rb +65 -58
  31. data/lib/gamefic/plot/article_mount.rb +22 -0
  32. data/lib/gamefic/plot/command_mount.rb +88 -0
  33. data/lib/gamefic/plot/entity_mount.rb +45 -0
  34. data/lib/gamefic/plot/query_mount.rb +9 -0
  35. data/lib/gamefic/plot/scene_mount.rb +181 -0
  36. data/lib/gamefic/plot/you_mount.rb +22 -0
  37. data/lib/gamefic/plot.rb +296 -247
  38. data/lib/gamefic/query/ambiguous_children.rb +5 -0
  39. data/lib/gamefic/query/base.rb +265 -0
  40. data/lib/gamefic/query/children.rb +10 -0
  41. data/lib/gamefic/query/expression.rb +47 -0
  42. data/lib/gamefic/query/family.rb +10 -0
  43. data/lib/gamefic/query/many_children.rb +7 -0
  44. data/lib/gamefic/query/matches.rb +11 -0
  45. data/lib/gamefic/query/parent.rb +10 -0
  46. data/lib/gamefic/query/plural_children.rb +14 -0
  47. data/lib/gamefic/query/self.rb +10 -0
  48. data/lib/gamefic/query/siblings.rb +10 -0
  49. data/lib/gamefic/query/text.rb +43 -0
  50. data/lib/gamefic/query.rb +19 -203
  51. data/lib/gamefic/rule.rb +18 -0
  52. data/lib/gamefic/scene/active.rb +25 -0
  53. data/lib/gamefic/scene/concluded.rb +22 -0
  54. data/lib/gamefic/scene/multiplechoice.rb +74 -0
  55. data/lib/gamefic/scene/paused.rb +26 -0
  56. data/lib/gamefic/scene/yesorno.rb +43 -0
  57. data/lib/gamefic/scene.rb +125 -0
  58. data/lib/gamefic/script/base.rb +33 -0
  59. data/lib/gamefic/script/file.rb +14 -0
  60. data/lib/gamefic/script/text.rb +14 -0
  61. data/lib/gamefic/script.rb +9 -0
  62. data/lib/gamefic/serialized.rb +24 -0
  63. data/lib/gamefic/shell.rb +9 -247
  64. data/lib/gamefic/snapshots.rb +134 -0
  65. data/lib/gamefic/source/base.rb +12 -0
  66. data/lib/gamefic/source/file.rb +23 -0
  67. data/lib/gamefic/source/text.rb +16 -0
  68. data/lib/gamefic/source.rb +9 -0
  69. data/lib/gamefic/stage.rb +75 -0
  70. data/lib/gamefic/syntax.rb +106 -124
  71. data/lib/gamefic/tester.rb +20 -0
  72. data/lib/gamefic/version.rb +3 -0
  73. data/lib/gamefic.rb +18 -12
  74. metadata +102 -70
  75. data/lib/gamefic/base.rb +0 -10
  76. data/lib/gamefic/before.rb +0 -12
  77. data/lib/gamefic/import/basics/actions/close.rb +0 -16
  78. data/lib/gamefic/import/basics/actions/commands.rb +0 -3
  79. data/lib/gamefic/import/basics/actions/drop-in.rb +0 -17
  80. data/lib/gamefic/import/basics/actions/drop-on.rb +0 -16
  81. data/lib/gamefic/import/basics/actions/drop.rb +0 -30
  82. data/lib/gamefic/import/basics/actions/enter.rb +0 -16
  83. data/lib/gamefic/import/basics/actions/go.rb +0 -35
  84. data/lib/gamefic/import/basics/actions/inventory.rb +0 -8
  85. data/lib/gamefic/import/basics/actions/leave.rb +0 -29
  86. data/lib/gamefic/import/basics/actions/look-in-at.rb +0 -27
  87. data/lib/gamefic/import/basics/actions/look-under.rb +0 -3
  88. data/lib/gamefic/import/basics/actions/look.rb +0 -71
  89. data/lib/gamefic/import/basics/actions/nil.rb +0 -25
  90. data/lib/gamefic/import/basics/actions/open.rb +0 -23
  91. data/lib/gamefic/import/basics/actions/quit.rb +0 -3
  92. data/lib/gamefic/import/basics/actions/take.rb +0 -107
  93. data/lib/gamefic/import/basics/entities/container.rb +0 -8
  94. data/lib/gamefic/import/basics/entities/entity.rb +0 -11
  95. data/lib/gamefic/import/basics/entities/fixture.rb +0 -5
  96. data/lib/gamefic/import/basics/entities/item.rb +0 -5
  97. data/lib/gamefic/import/basics/entities/portal.rb +0 -40
  98. data/lib/gamefic/import/basics/entities/room.rb +0 -30
  99. data/lib/gamefic/import/basics/entities/scenery.rb +0 -5
  100. data/lib/gamefic/import/basics/entities/supporter.rb +0 -6
  101. data/lib/gamefic/import/basics/entities/thing.rb +0 -16
  102. data/lib/gamefic/import/basics/queries/reachable.rb +0 -38
  103. data/lib/gamefic/import/basics/queries/room.rb +0 -8
  104. data/lib/gamefic/import/basics/queries/visible.rb +0 -32
  105. data/lib/gamefic/import/basics/rules/has-enough-light.rb +0 -14
  106. data/lib/gamefic/import/basics.old/actions/container.rb +0 -112
  107. data/lib/gamefic/import/basics.old/actions/inventory.rb +0 -50
  108. data/lib/gamefic/import/basics.old/actions/look.rb +0 -53
  109. data/lib/gamefic/import/basics.old/actions/meta.rb +0 -6
  110. data/lib/gamefic/import/basics.old/actions/traversal.rb +0 -35
  111. data/lib/gamefic/import/basics.old/actions.rb +0 -1
  112. data/lib/gamefic/import/basics.old/entities/container.rb +0 -3
  113. data/lib/gamefic/import/basics.old/entities/fixture.rb +0 -3
  114. data/lib/gamefic/import/basics.old/entities/item.rb +0 -3
  115. data/lib/gamefic/import/basics.old/entities/portal.rb +0 -43
  116. data/lib/gamefic/import/basics.old/entities/room.rb +0 -27
  117. data/lib/gamefic/import/basics.old/entities/scenery.rb +0 -3
  118. data/lib/gamefic/import/basics.old/entities/supporter.rb +0 -3
  119. data/lib/gamefic/import/basics.old/entities.rb +0 -1
  120. data/lib/gamefic/import/basics.old/room_modes.rb +0 -48
  121. data/lib/gamefic/import/basics.rb +0 -6
  122. data/lib/gamefic/import/room_modes.rb +0 -48
  123. data/lib/gamefic/import/standard.rb +0 -1
  124. data/lib/gamefic/meta.rb +0 -12
  125. data/lib/gamefic/optionset.rb +0 -114
  126. data/lib/gamefic/requirement.rb +0 -14
  127. data/lib/gamefic/story.rb +0 -14
  128. data/lib/gamefic/thing.rb +0 -7
@@ -0,0 +1,221 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+
4
+ module Gamefic
5
+
6
+ class Entity
7
+ attr_reader :cgi_key
8
+ @@cgi_key_index = 0
9
+ alias_method :orig_post_initialize, :post_initialize
10
+ def post_initialize
11
+ orig_post_initialize
12
+ @cgi_key = "entity_#{@@cgi_key_index}"
13
+ @@cgi_key_index += 1
14
+ end
15
+ end
16
+
17
+ module Cgi
18
+
19
+ class Key
20
+ def initialize(key_value)
21
+ @value = key_value
22
+ end
23
+ def value
24
+ @value
25
+ end
26
+ end
27
+
28
+ class Engine < Gamefic::Engine
29
+ attr_reader :user
30
+ def initialize(plot, args = {})
31
+ super(plot)
32
+ @session_file = args[:session_file] || 'save.dat'
33
+ @message_format = args[:message_format] || :html
34
+ @response_format = args[:response_format] || :json
35
+ @new_game = args[:new_game] || false
36
+ @entity_keys = Hash.new
37
+ @introducing = true
38
+ end
39
+ def post_initialize
40
+ @user = Cgi::User.new @plot
41
+ end
42
+ def begin_session
43
+ # Initialize keys for all entities
44
+ @plot.entities.each { |e|
45
+ @entity_keys[e.cgi_key] = e
46
+ }
47
+ @entity_keys['yourself'] = @user.character
48
+ if !@new_game and @session_file != nil
49
+ if File.exist?(@session_file)
50
+ load @session_file
51
+ @introducing = false
52
+ end
53
+ end
54
+ end
55
+ def run
56
+ if @introducing == true
57
+ @plot.introduce @user.character
58
+ else
59
+ @plot.instance_variable_get(:@players).push @user.character
60
+ tick
61
+ end
62
+ response = Hash.new
63
+ response[:output] = @user.stream.flush
64
+ response[:prompt] = @user.character.state.prompt
65
+ response[:state] = @user.character.state.class.to_s.split('::').last
66
+ return JSON.generate(response)
67
+ end
68
+ def end_session
69
+ save @session_file
70
+ end
71
+ private
72
+ def load(filename)
73
+ x = File.open(filename, "r")
74
+ ser = x.read
75
+ x.close
76
+ data = Marshal.restore(ser)
77
+ data.each { |k, h|
78
+ if k == 'yourself'
79
+ entity = @entity_keys['yourself']
80
+ else
81
+ entity = @entity_keys[k]
82
+ end
83
+ h.each { |s, v|
84
+ if s == :session
85
+ entity.instance_variable_set(:@session, v)
86
+ elsif s == :state
87
+ entity.state = v
88
+ elsif s == :options
89
+ v.each { |opt|
90
+ entity.is opt
91
+ }
92
+ else
93
+ writer = "#{s.to_s[1..-1]}="
94
+ writer.untaint
95
+ if entity.respond_to?(writer)
96
+ if v.kind_of?(Key)
97
+ entity.send(writer, @entity_keys[v.value])
98
+ elsif v.kind_of?(CharacterState::Base)
99
+ v.instance_variable_set(:@character, entity)
100
+ entity.instance_variable_set(s, v)
101
+ elsif v.kind_of?(Array)
102
+ entity.send(writer, decode_array(v))
103
+ else
104
+ entity.send(writer, v)
105
+ end
106
+ end
107
+ end
108
+ }
109
+ }
110
+ @restored = true
111
+ end
112
+ def save(filename)
113
+ data = Hash.new
114
+ @plot.entities.each { |e|
115
+ data[e.cgi_key] = entity_hash(e)
116
+ }
117
+ f = File.new(filename, "w")
118
+ f.write Marshal.dump data
119
+ f.close
120
+ end
121
+ def entity_hash(e)
122
+ hash = Hash.new
123
+ e.instance_variables.each { |v|
124
+ next if v == :@state or v == :@option_array or v == :@update_procs
125
+ writer = "#{v.to_s[1..-1]}="
126
+ if e.respond_to?(writer)
127
+ value = e.instance_variable_get(v)
128
+ if value.kind_of?(String) or value.kind_of?(Numeric) or value.kind_of?(TrueClass) or value.kind_of?(FalseClass) or value.kind_of?(Entity) or value.kind_of?(Character) or value == nil or value.kind_of?(Array)
129
+ if value.kind_of?(Entity)
130
+ if value == @user.character
131
+ hash[v] = Key.new('yourself')
132
+ else
133
+ hash[v] = Key.new(value.cgi_key)
134
+ end
135
+ elsif value.kind_of?(CharacterState::Base)
136
+ value.instance_variable_set(:@character, nil)
137
+ hash[v] = value
138
+ elsif value.kind_of?(Array)
139
+ hash[v] = encode_array(value)
140
+ else
141
+ hash[v] = value
142
+ end
143
+ end
144
+ end
145
+ }
146
+ if e.respond_to?("state")
147
+ hash[:state] = e.state_name
148
+ end
149
+ if e.respond_to?("option_array")
150
+ hash[:options] = e.option_array
151
+ end
152
+ hash[:session] = Hash.new
153
+ e.session.each { |k, v|
154
+ if v.kind_of?(Entity)
155
+ hash[:session][k] = Key.new(v.cgi_key)
156
+ elsif v.kind_of?(Array)
157
+ hash[:session][k] = encode_array(v)
158
+ else
159
+ hash[:session][k] = v
160
+ end
161
+ }
162
+ hash
163
+ end
164
+ def encode_array(array)
165
+ result = Array.new
166
+ array.each { |item|
167
+ if item.kind_of?(Entity)
168
+ result.push Key.new(item.cgi_key)
169
+ else
170
+ result.push item
171
+ end
172
+ }
173
+ result
174
+ end
175
+ def decode_array(array)
176
+ result = Array.new
177
+ array.each { |item|
178
+ if item.kind_of?(Key)
179
+ result.push @entity_keys[item.value]
180
+ else
181
+ result.push item
182
+ end
183
+ }
184
+ result
185
+ end
186
+ end
187
+
188
+ class User < Gamefic::User
189
+ def post_initialize
190
+ @stream = Cgi::UserStream.new
191
+ @state = UserState.new self
192
+ #@character = @plot.make Character, :name => 'pWlayer'
193
+ end
194
+ end
195
+
196
+ class UserStream < Gamefic::UserStream
197
+ def initialize
198
+ super
199
+ @output = ''
200
+ end
201
+ def flush
202
+ buffer = "#{@output}"
203
+ @output.clear
204
+ buffer
205
+ end
206
+ def send(data)
207
+ #if data.start_with?(Ansi::NEL)
208
+ # data = "<p>#{data[Ansi::NEL.length..-1].rstrip}</p>"
209
+ #end
210
+ @output = "#{@output}#{data}"
211
+ end
212
+ def select(prompt)
213
+ # TODO: non-blocking read
214
+ line = STDIN.gets
215
+ @queue.push line
216
+ end
217
+ end
218
+
219
+ end
220
+
221
+ end
@@ -0,0 +1,237 @@
1
+ require 'gamefic/engine'
2
+ require 'rexml/document'
3
+ require 'gamefic/ansi'
4
+ require 'gamefic/html'
5
+
6
+ begin
7
+ require 'io/console'
8
+ rescue LoadError
9
+ puts "This version of Ruby does not support io/console. Text may not wrap correctly."
10
+ if RUBY_VERSION.split('.')[0].to_i < 2
11
+ puts "It is recommended that you upgrade to Ruby 2.0.0 or higher."
12
+ end
13
+ end
14
+
15
+ module Gamefic
16
+
17
+ module Tty
18
+ class Engine < Gamefic::Engine
19
+ def post_initialize
20
+ @user = Tty::User.new @plot
21
+ end
22
+ end
23
+ class User < Gamefic::User
24
+ def post_initialize
25
+ @stream = Tty::UserStream.new
26
+ @state = UserState.new self
27
+ end
28
+ def ansi=(val)
29
+ @stream.ansi = val
30
+ end
31
+ def ansi
32
+ @stream.ansi
33
+ end
34
+ def save filename, json
35
+ if json.nil?
36
+ @character.tell "Nothing to save."
37
+ end
38
+ if filename.nil?
39
+ stream.select "Enter the filename to save:"
40
+ filename = stream.queue.pop
41
+ end
42
+ if filename != ''
43
+ File.open(filename, 'w') do |f|
44
+ f.write json
45
+ end
46
+ @character.tell "Game saved."
47
+ end
48
+ end
49
+ def restore filename
50
+ if filename.nil?
51
+ stream.select "Enter the filename to restore:"
52
+ filename = stream.queue.pop
53
+ end
54
+ if filename != ''
55
+ if File.exists?(filename)
56
+ return File.read(filename)
57
+ else
58
+ @character.tell "File \"#{filename}\" not found."
59
+ end
60
+ end
61
+ end
62
+ end
63
+ class UserStream < Gamefic::UserStream
64
+ include Ansi
65
+ include Ansi::Code
66
+ def initialize
67
+ super
68
+ end
69
+ def size
70
+ begin
71
+ return STDOUT.winsize.reverse
72
+ rescue
73
+ return [nil,nil]
74
+ end
75
+ end
76
+ def flush
77
+ data = @buffer.clone
78
+ @buffer.clear
79
+ return if data.strip == ''
80
+ output = ''
81
+ begin
82
+ doc = Html.parse("<body>#{data}</body>")
83
+ format_recursively doc
84
+ texts = REXML::XPath.match(doc, './/text()')
85
+ output = texts.join('').gsub(/&apos;/, "'").gsub(/&quot;/, '"').gsub(/&lt;/, '<').gsub(/&gt;/, '>')
86
+ output += Ansi.graphics_mode(Attribute::NORMAL)
87
+ output = Html::decode(output)
88
+ rescue REXML::ParseException => e
89
+ puts e.inspect
90
+ output = Html.encode(data) + "\n\n"
91
+ end
92
+ output.gsub!(/(\n\n)+/, "\n\n")
93
+ width = size[0]
94
+ if width.nil?
95
+ output
96
+ else
97
+ "#{terminalize(output, width - 1)}"
98
+ end
99
+ end
100
+ def select(prompt)
101
+ super
102
+ print "\n"
103
+ end
104
+ def has_code?(fmt, code)
105
+ if fmt.kind_of?(Array)
106
+ return fmt.flatten.include?(code)
107
+ end
108
+ return fmt == code
109
+ end
110
+
111
+ private
112
+
113
+ def format_recursively(top, stack = nil)
114
+ ol_index = 1
115
+ stack ||= [Attribute::NORMAL]
116
+ top.elements.each { |element|
117
+ formats = [Attribute::NORMAL]
118
+ classes = element.attribute('class').to_s.split(' ')
119
+ if classes.include?('hint')
120
+ formats.push Foreground::YELLOW
121
+ end
122
+ case element.name
123
+ when 'strong', 'b'
124
+ formats.push Attribute::BOLD
125
+ when 'em', 'i', 'u'
126
+ formats.push Attribute::UNDERSCORE
127
+ when 'a'
128
+ if element.attributes['rel'].to_s == 'gamefic'
129
+ element.attributes['href'] = element.attributes['href'][5..-1]
130
+ formats.push [Attribute::UNDERSCORE, Extra::COMMAND]
131
+ else
132
+ formats.push [Attribute::UNDERSCORE, Extra::HREF]
133
+ end
134
+ when 'li'
135
+ if top.name == 'ol'
136
+ element.text = "#{ol_index}. #{element.text}"
137
+ ol_index += 1
138
+ else
139
+ element.text = "* #{element.text}"
140
+ end
141
+ formats.push [Extra::LINE]
142
+ when 'img'
143
+ formats.push [Extra::IGNORED]
144
+ when 'p', 'ol', 'ul'
145
+ formats.push Extra::BLOCK
146
+ when 'nav'
147
+ formats.push Extra::BLOCK
148
+ when 'h1', 'h2', 'h3', 'h4', 'h5'
149
+ formats.push [Attribute::BOLD, Extra::BLOCK, Extra::UPPERCASE]
150
+ when 'kbd'
151
+ formats.push [Foreground::GREEN]
152
+ end
153
+ if has_code?(stack, Extra::IGNORED)
154
+ element.parent.delete_element(element)
155
+ end
156
+ stack.push formats
157
+ if has_code?(stack, Extra::UPPERCASE)
158
+ element.texts.each { |text|
159
+ text.value.upcase!
160
+ }
161
+ end
162
+ element.texts.each { |text|
163
+ text.value = "#{Ansi.graphics_mode(*stack)}#{text.value}"
164
+ }
165
+ if has_code?(stack.last, Extra::IMAGE)
166
+ element.text = "#{element.attribute('alt') ? element.attribute('alt') : '[Image]'}"
167
+ end
168
+ format_recursively element, stack
169
+ if has_code?(stack.last, Extra::COMMAND)
170
+ if element.attribute('data-command').to_s != ''
171
+ tmp = stack.pop
172
+ element.add_text "#{Ansi.graphics_mode(*stack)}"
173
+ element.add_text " [#{element.attribute('data-command')}]"
174
+ stack.push tmp
175
+ element.add_text "#{Ansi.graphics_mode(*stack)}"
176
+ end
177
+ end
178
+ if has_code?(stack.last, Extra::BLOCK)
179
+ element.add_text("\n\n")
180
+ elsif has_code?(stack.last, Extra::LINE)
181
+ if !has_code?(stack[-2], Extra::BLOCK) || element != top.elements.to_a.last
182
+ element.add_text("\n")
183
+ end
184
+ end
185
+ if has_code?(stack.last, Extra::HREF)
186
+ if element.attribute('href').to_s != "#"
187
+ tmp = stack.pop
188
+ element.add_text "#{Ansi.graphics_mode(*stack)}"
189
+ element.add_text(" [#{element.attribute('href')}]")
190
+ stack.push tmp
191
+ element.add_text "#{Ansi.graphics_mode(*stack)}"
192
+ end
193
+ end
194
+ if has_code?(stack.last, Extra::IMAGE)
195
+ element.add_text(" [#{element.attribute('src')}]")
196
+ if !has_code?(stack, Extra::BLOCK)
197
+ element.add_text("\n\n")
198
+ end
199
+ end
200
+ stack.pop
201
+ }
202
+ end
203
+ def terminalize string, max_length
204
+ i = 0
205
+ output = ''
206
+ line_length = 0
207
+ while i < string.length
208
+ line_length += 1
209
+ char = string[i,1]
210
+ if char == "\e"
211
+ # Right now, graphics modes are the only supported ANSI sequences.
212
+ end_of_seq = string.index("m", i)
213
+ output += string[i..end_of_seq]
214
+ i = end_of_seq + 1
215
+ elsif char == " "
216
+ next_space = string.index(/[\s]/, i + 1)
217
+ if !next_space.nil? and line_length + (next_space - i) > max_length
218
+ output += "\n"
219
+ line_length = 0
220
+ else
221
+ output += char
222
+ end
223
+ i += 1
224
+ else
225
+ if char == "\n"
226
+ line_length = 0
227
+ end
228
+ output += char
229
+ i += 1
230
+ end
231
+ end
232
+ output #output.strip
233
+ end
234
+ end
235
+ end
236
+
237
+ end
@@ -1,86 +1,107 @@
1
1
  module Gamefic
2
2
 
3
- class Engine
4
- def initialize(plot)
5
- @plot = plot
3
+ class Engine
4
+ def initialize(plot)
5
+ @plot = plot
6
6
  post_initialize
7
- end
7
+ end
8
8
  def post_initialize
9
9
  @user = User.new @plot
10
10
  end
11
- def run
11
+ def run
12
12
  @plot.introduce @user.character
13
- while @user.character.state.kind_of?(GameOverState) == false
14
- tick
15
- end
16
- end
17
- def tick
18
- proc {
19
- $SAFE = Gamefic.safe_level
20
- @user.stream.select @user.character.state.prompt
21
- @user.state.update
22
- @plot.update
23
- }.call
24
- end
25
- end
13
+ print @user.state.output
14
+ while @user.character.scene.state != "Concluded"
15
+ turn
16
+ end
17
+ print @user.state.output
18
+ end
19
+ def turn
20
+ @plot.ready
21
+ print @user.state.output
22
+ # HACK Exception for test scenes
23
+ if @user.character.scene.key != :test
24
+ if @user.character.scene.state != "Concluded"
25
+ @user.stream.select @user.character.scene.data.prompt
26
+ @user.state.input
27
+ end
28
+ end
29
+ @plot.update
30
+ print @user.state.output
31
+ end
32
+ end
26
33
 
27
- class User
28
- attr_reader :state, :character, :story
29
- def initialize(plot)
30
- @plot = plot
31
- @character = Character.new @plot, :name => 'yourself', :synonyms => 'self myself you me'
34
+ class User
35
+ attr_reader :state, :character, :story
36
+ def initialize(plot)
37
+ @plot = plot
38
+ @character = Character.new @plot, :name => 'yourself', :synonyms => 'self myself you me', :proper_named => true
32
39
  @character.connect self
33
40
  post_initialize
34
- end
41
+ end
35
42
  def post_initialize
36
- @stream = UserStream.new
37
- @state = UserState.new self
43
+ @stream = UserStream.new
44
+ @state = UserState.new self
45
+ end
46
+ def stream
47
+ @stream ||= UserStream.new
38
48
  end
39
- def stream
40
- @stream ||= UserStream.new
41
- end
42
49
  def state
43
50
  @state ||= UserState.new(self)
44
51
  end
45
- def state=(state_class)
46
- @state = state_class.new self
47
- end
48
- def refresh
49
- # Nothing to do
50
- end
51
- def quit
52
- #exit
53
- end
54
- end
55
-
56
- class UserStream
57
- def initialize
58
- @queue = Array.new
59
- end
60
- def send(data)
61
- puts data
62
- end
63
- def select(prompt)
64
- print prompt
65
- line = STDIN.gets
52
+ def state=(state_class)
53
+ @state = state_class.new self
54
+ end
55
+ def refresh
56
+ # Nothing to do
57
+ end
58
+ def quit
59
+ #exit
60
+ end
61
+ end
62
+
63
+ class UserStream
64
+ def initialize
65
+ @queue = Array.new
66
+ @buffer = ''
67
+ end
68
+ def flush
69
+ tmp = @buffer.clone
70
+ #@buffer.clear
71
+ @buffer = ''
72
+ tmp
73
+ end
74
+ def send(data)
75
+ # Quick and dirty HTML sanitization
76
+ #data.gsub!(/<[a-z]+[^>]*>/i, "")
77
+ #data.gsub!(/<\/[^>]*>/, "")
78
+ @buffer += data
79
+ end
80
+ def select(prompt)
81
+ print "#{prompt} "
82
+ STDOUT.flush
83
+ line = STDIN.gets
66
84
  @queue.push line.strip
67
- end
68
- def recv
69
- @queue.shift
70
- end
71
- end
72
-
73
- class UserState
74
- attr_reader :user
75
- def initialize(user)
76
- @user = user
77
- end
78
- def update
79
- line = @user.stream.recv
80
- if line != nil
85
+ end
86
+ def recv
87
+ @queue.shift
88
+ end
89
+ end
90
+
91
+ class UserState
92
+ attr_reader :user
93
+ def initialize(user)
94
+ @user = user
95
+ end
96
+ def input
97
+ line = @user.stream.recv
98
+ if line != nil
81
99
  @user.character.queue.push line
82
- end
83
- end
84
- end
100
+ end
101
+ end
102
+ def output
103
+ @user.stream.flush
104
+ end
105
+ end
85
106
 
86
107
  end