gamefic 0.2.0 → 0.6.0

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