gamefic 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ module Gamefic
2
+ class Actor < Gamefic::Entity
3
+ include Gamefic::Active
4
+ end
5
+ end
@@ -1,16 +1,16 @@
1
1
  class String
2
- include Gamefic::Matchable
2
+ include Gamefic::Matchable
3
3
  # Capitalize the first letter without changing the rest of the string.
4
4
  # (String#capitalize makes the rest of the string lower-case.)
5
- def capitalize_first
6
- "#{self[0,1].upcase}#{self[1,self.length]}"
7
- end
8
- # @return [String]
9
- def cap_first
10
- self.capitalize_first
11
- end
12
- # @return [Array]
13
- def split_words
14
- self.gsub(/ +/, ' ').strip.split
15
- end
5
+ def capitalize_first
6
+ "#{self[0,1].upcase}#{self[1,self.length]}"
7
+ end
8
+ # @return [String]
9
+ def cap_first
10
+ self.capitalize_first
11
+ end
12
+ # @return [Array]
13
+ def split_words
14
+ self.gsub(/ +/, ' ').strip.split
15
+ end
16
16
  end
@@ -9,15 +9,24 @@ module Gamefic
9
9
  include Grammar::Person, Grammar::Plural
10
10
  include Matchable
11
11
 
12
+ # Get the name of the object.
13
+ # The name is usually presented without articles (e.g., "object" instead
14
+ # of "an object" or "the object" unless the article is part of a proper
15
+ # name (e.g., "The Ohio State University").
16
+ #
12
17
  # @return [String]
13
18
  attr_reader :name
14
19
 
15
20
  # @return [String]
16
21
  attr_reader :synonyms
17
22
 
23
+ # Get the object's indefinite article (usually "a" or "an").
24
+ #
18
25
  # @return [String]
19
26
  attr_reader :indefinite_article
20
27
 
28
+ # Get the object's definite article (usually "the").
29
+ #
21
30
  # @return [String]
22
31
  attr_reader :definite_article
23
32
 
@@ -41,22 +50,30 @@ module Gamefic
41
50
  # Get the name of the object with a definite article.
42
51
  # Note: proper-named objects never append an article, though an article
43
52
  # may be included in its proper name.
53
+ #
54
+ # @return [String]
44
55
  def definitely
45
56
  ((proper_named? or definite_article == '') ? '' : "#{definite_article} ") + name.to_s
46
57
  end
47
58
 
48
- # Get the definite article for this object.
59
+ # Get the definite article for this object (usually "the").
49
60
  #
50
61
  # @return [String]
51
62
  def definite_article
52
63
  @definite_article || "the"
53
64
  end
54
65
 
66
+ # Set the definite article.
67
+ #
68
+ # @param [String] article
55
69
  def definite_article= article
56
70
  @keywords = nil
57
71
  @definite_article = article
58
72
  end
59
73
 
74
+ # Set the indefinite article.
75
+ #
76
+ # @param [String] article
60
77
  def indefinite_article= article
61
78
  @keywords = nil
62
79
  @indefinite_article = article
@@ -0,0 +1,31 @@
1
+ module Gamefic
2
+ class Element
3
+ include Gamefic::Describable
4
+
5
+ def initialize(args = {})
6
+ self.class.default_attributes.merge(args).each { |key, value|
7
+ send "#{key}=", value
8
+ }
9
+ post_initialize
10
+ yield self if block_given?
11
+ end
12
+
13
+ def post_initialize
14
+ # raise NotImplementedError, "#{self.class} must implement post_initialize"
15
+ end
16
+
17
+ class << self
18
+ def set_default attrs = {}
19
+ default_attributes.merge! attrs
20
+ end
21
+
22
+ def default_attributes
23
+ @default_attributes ||= {}
24
+ end
25
+
26
+ def inherited subclass
27
+ subclass.set_default default_attributes
28
+ end
29
+ end
30
+ end
31
+ end
@@ -3,7 +3,10 @@ module Gamefic
3
3
  # Basic functionality for running a single-player game from a console.
4
4
  #
5
5
  class Engine::Base
6
+ # @return [Class]
6
7
  attr_writer :user_class
8
+
9
+ # @return [Gamefic::Plot]
7
10
  attr_reader :plot
8
11
 
9
12
  def initialize(plot)
@@ -20,46 +23,36 @@ module Gamefic
20
23
  end
21
24
 
22
25
  def connect
23
- @character = @plot.make Character, name: 'yourself', synonyms: 'self myself you me', proper_named: true
24
- @user = user_class.new
25
- @character.connect @user
26
- @character
26
+ raise 'Plot did not specify a player class' if @plot.player_class.nil?
27
+ # @todo The plot itself can define name, etc.
28
+ character = @plot.make @plot.player_class, name: 'yourself', synonyms: 'self myself you me', proper_named: true
29
+ @user = user_class.new(self)
30
+ @user.connect character
31
+ character.connect @user
27
32
  end
28
33
 
29
34
  def run
30
35
  connect
31
- @plot.introduce @character
32
- @user.update @character.state
33
- turn until @character.concluded?
36
+ @plot.introduce @user.character
37
+ #@user.update @character.state
38
+ turn until @user.character.concluded?
39
+ @user.update
34
40
  #print @user.flush
35
41
  end
36
42
 
37
43
  def turn
38
44
  @plot.ready
39
- unless @character.state[:options].nil?
40
- list = '<ol class="multiple_choice">'
41
- @character.state[:options].each { |o|
42
- list += "<li><a href=\"#\" rel=\"gamefic\" data-command=\"#{o}\">#{o}</a></li>"
43
- }
44
- list += "</ol>"
45
- @character.tell list
46
- end
47
- #print @user.flush
48
- @user.update @character.state
49
- #@character.flush
50
- if @character.queue.empty?
45
+ @user.update
46
+ if @user.character.queue.empty?
51
47
  receive
52
48
  end
53
49
  @plot.update
54
- #print @user.flush
55
- @user.update @character.state
56
- #@character.flush
57
50
  end
58
51
 
59
52
  def receive
60
- print @character.scene.prompt + ' '
53
+ print @user.character.scene.prompt + ' '
61
54
  input = STDIN.gets
62
- @character.queue.push input unless input.nil?
55
+ @user.character.queue.push input unless input.nil?
63
56
  end
64
57
  end
65
58
 
@@ -4,39 +4,11 @@ require 'gamefic/messaging'
4
4
 
5
5
  module Gamefic
6
6
 
7
- class Entity
7
+ class Entity < Element
8
8
  include Node
9
- include Describable
10
9
  include Messaging
11
10
  include Grammar::WordAdapter
12
11
 
13
- attr_reader :session
14
-
15
- def initialize(args = {})
16
- pre_initialize
17
- args.each { |key, value|
18
- send "#{key}=", value
19
- }
20
- @session = Hash.new
21
- yield self if block_given?
22
- post_initialize
23
- end
24
-
25
- def uid
26
- if @uid == nil
27
- @uid = self.object_id.to_s
28
- end
29
- @uid
30
- end
31
-
32
- def pre_initialize
33
- # raise NotImplementedError, "#{self.class} must implement post_initialize"
34
- end
35
-
36
- def post_initialize
37
- # raise NotImplementedError, "#{self.class} must implement post_initialize"
38
- end
39
-
40
12
  # Execute the entity's on_update blocks.
41
13
  # This method is typically called by the Engine that manages game execution.
42
14
  # The base method does nothing. Subclasses can override it.
@@ -53,22 +25,32 @@ module Gamefic
53
25
  end
54
26
  super
55
27
  end
56
-
57
- # Get an extended property.
28
+
29
+ # A freeform property dictionary.
30
+ # Authors can use the session hash to assign custom properties to the
31
+ # entity. It can also be referenced directly using [] without the method
32
+ # name, e.g., entity.session[:my_value] or entity[:my_value].
58
33
  #
59
- # @param key [Symbol] The property's name.
34
+ # @return [Hash]
35
+ def session
36
+ @session ||= {}
37
+ end
38
+
39
+ # Get a custom property.
40
+ #
41
+ # @param key [Symbol] The property's name
42
+ # @return The value of the property
60
43
  def [](key)
61
44
  session[key]
62
45
  end
63
46
 
64
- # Set an extended property.
47
+ # Set a custom property.
65
48
  #
66
- # @param key [Symbol] The property's name.
67
- # @param value The value to set.
49
+ # @param key [Symbol] The property's name
50
+ # @param value The value to set
68
51
  def []=(key, value)
69
52
  session[key] = value
70
53
  end
71
-
72
54
  end
73
55
 
74
56
  end
@@ -5,7 +5,7 @@ module Gamefic::Grammar
5
5
  attr_writer :gender
6
6
  def gender
7
7
  # Supported values are "male", "female", "other", and "neutral"
8
- @gender ||= (self.kind_of?(Character) ? "other" : "neutral")
8
+ @gender ||= "neutral"
9
9
  end
10
10
  end
11
11
  end
@@ -53,7 +53,7 @@ module Gamefic::Grammar
53
53
  #
54
54
  # @return [String]
55
55
  def Poss
56
- obj.cap_first
56
+ poss.cap_first
57
57
  end
58
58
 
59
59
  # Get the capitalized reflexive pronoun
@@ -88,7 +88,7 @@ module Gamefic::Grammar
88
88
  if @sets.nil?
89
89
  @sets = {}
90
90
  @sets["1:singular"] = ["I", "me", "my", "myself"]
91
- @sets["2"] = ["you", "you", "your", "yourself"]
91
+ @sets["2:singular"] = ["you", "you", "your", "yourself"]
92
92
  @sets["3:singular:male"] = ["he", "him", "his", "himself"]
93
93
  @sets["3:singular:female"] = ["she", "her", "her", "herself"]
94
94
  # "other" refers to a person or living being that is neither
@@ -97,6 +97,7 @@ module Gamefic::Grammar
97
97
  @sets["3:singular:other"] = ["they", "them", "their", "themselves"]
98
98
  @sets["3:singular:neutral"] = ["it", "it", "its", "itself"]
99
99
  @sets["1:plural"] = ["we", "us", "our", "ourselves"]
100
+ @sets["2:plural"] = ["you", "you", "your", "yourselves"]
100
101
  @sets["3:plural"] = ["they", "them", "their", "themselves"]
101
102
  end
102
103
  @sets
@@ -11,7 +11,6 @@ module Gamefic
11
11
  # compatibility with Opal.
12
12
  message = message.gsub(/[ \t\r]*\n[ \t\r]*\n[ \t\r]*/, '</p><p>')
13
13
  message = message.gsub(/[ \t]*\n[ \t]*/, ' ')
14
- #user.send message
15
14
  p_set_messages messages + message
16
15
  end
17
16
 
@@ -1,5 +1,3 @@
1
- # TODO: JSON support is currently experimental.
2
- #require 'gamefic/entityloader'
3
1
  require 'gamefic/tester'
4
2
  require 'gamefic/source'
5
3
  require 'gamefic/script'
@@ -14,6 +12,7 @@ module Gamefic
14
12
  autoload :Articles, 'gamefic/plot/articles'
15
13
  autoload :YouMount, 'gamefic/plot/you_mount'
16
14
  autoload :Snapshot, 'gamefic/plot/snapshot'
15
+ autoload :Darkroom, 'gamefic/plot/darkroom'
17
16
  autoload :Host, 'gamefic/plot/host'
18
17
  autoload :Players, 'gamefic/plot/players'
19
18
  autoload :Playbook, 'gamefic/plot/playbook'
@@ -21,6 +20,7 @@ module Gamefic
21
20
  autoload :Theater, 'gamefic/plot/theater'
22
21
 
23
22
  attr_reader :commands, :imported_scripts, :source
23
+
24
24
  # TODO: Metadata could use better protection
25
25
  attr_accessor :metadata
26
26
  include Theater
@@ -36,6 +36,11 @@ module Gamefic
36
36
  post_initialize
37
37
  end
38
38
 
39
+ def player_class cls = nil
40
+ @player_class = cls unless cls.nil?
41
+ @player_class
42
+ end
43
+
39
44
  # @return [Gamefic::Plot::Playbook]
40
45
  def playbook
41
46
  @playbook ||= Gamefic::Plot::Playbook.new
@@ -68,6 +73,8 @@ module Gamefic
68
73
  def ready
69
74
  playbook.freeze
70
75
  @running = true
76
+ # Call the initial state to make sure it's set
77
+ initial_state
71
78
  call_ready
72
79
  call_player_ready
73
80
  p_subplots.each { |s| s.ready }
@@ -78,7 +85,10 @@ module Gamefic
78
85
  def update
79
86
  entities.each { |e| e.flush }
80
87
  call_before_player_update
81
- p_players.each { |p| p.scene.update }
88
+ p_players.each { |p|
89
+ p.performed nil
90
+ p.scene.update
91
+ }
82
92
  p_entities.each { |e| e.update }
83
93
  call_player_update
84
94
  call_update
@@ -117,7 +127,7 @@ module Gamefic
117
127
  else
118
128
  false
119
129
  end
120
- end
130
+ end
121
131
  end
122
132
 
123
133
  end
@@ -30,7 +30,7 @@ module Gamefic
30
30
  # player[:turns] += 1
31
31
  # end
32
32
  #
33
- # @yieldparam [Character]
33
+ # @yieldparam [Gamefic::Performance]
34
34
  def on_player_ready &block
35
35
  p_player_ready_procs.push block
36
36
  end
@@ -98,7 +98,6 @@ module Gamefic
98
98
  #
99
99
  def call_player_update
100
100
  p_players.each { |player|
101
- #player.performed nil
102
101
  p_player_update_procs.each { |block| block.call player }
103
102
  }
104
103
  end
@@ -103,11 +103,10 @@ module Gamefic
103
103
  end
104
104
 
105
105
  # Get an Array of available verbs.
106
- # If the to_s parameter is true, convert Symbols to Strings.
107
106
  #
108
- # @return [Array<Symbol|String>]
109
- def verbs to_s: false
110
- to_s ? playbook.verbs.map { |v| v.to_s } : playbook.verbs
107
+ # @return [Array<String>]
108
+ def verbs
109
+ playbook.verbs.map { |v| v.to_s }.reject{ |v| v.start_with?('_') }
111
110
  end
112
111
 
113
112
  # Get an Array of all Actions defined in the Plot.
@@ -0,0 +1,264 @@
1
+ module Gamefic
2
+ # Create and restore plot snapshots.
3
+ #
4
+ class Plot::Darkroom
5
+ attr_reader :plot
6
+
7
+ def initialize plot
8
+ @plot = plot
9
+ end
10
+
11
+ # Create a snapshot of the plot.
12
+ #
13
+ # @return [Hash]
14
+ def save
15
+ result = { entities: [], players: [], subplots: [], instance_variables: {} }
16
+ entity_store.clear
17
+ player_store.clear
18
+ entity_store.concat plot.entities
19
+ player_store.concat plot.players
20
+ plot.subplots.each { |s| entity_store.concat s.entities }
21
+ entity_store.uniq!
22
+ entity_store.each do |e|
23
+ result[:entities].push hash_entity(e)
24
+ end
25
+ player_store.each do |p|
26
+ result[:players].push hash_entity(p)
27
+ end
28
+ plot.theater.instance_variables.each { |i|
29
+ v = plot.theater.instance_variable_get(i)
30
+ result[:instance_variables][i] = serialize(v) if can_serialize?(v)
31
+ }
32
+ plot.subplots.each { |s|
33
+ result[:subplots].push hash_subplot(s)
34
+ }
35
+ result
36
+ end
37
+
38
+ # Restore a snapshot.
39
+ #
40
+ def restore snapshot
41
+ entity_store.clear
42
+ player_store.clear
43
+ plot.subplots.each { |s| s.conclude }
44
+ plot.entities[plot.initial_state[:entities].length..-1].each { |e| plot.destroy e }
45
+ entity_store.concat plot.entities[0..plot.initial_state[:entities].length-1]
46
+ entity_store.uniq!
47
+ player_store.concat plot.players
48
+ i = 0
49
+ snapshot[:entities].each { |h|
50
+ if entity_store[i].nil?
51
+ e = plot.stage do
52
+ cls = self.const_get(h[:class])
53
+ make cls
54
+ end
55
+ entity_store.push e
56
+ end
57
+ i += 1
58
+ }
59
+ snapshot[:subplots].each { |s|
60
+ sp = plot.stage do
61
+ cls = const_get(s[:class])
62
+ branch cls
63
+ end
64
+ # @todo Assuming one player
65
+ sp.introduce player_store[0] unless player_store.empty?
66
+ rebuild_subplot sp, s
67
+ }
68
+ i = 0
69
+ snapshot[:entities].each { |h|
70
+ rebuild1 entity_store[i], h
71
+ i += 1
72
+ }
73
+ i = 0
74
+ snapshot[:players].each { |p|
75
+ rebuild1 player_store[i], p
76
+ i += 1
77
+ }
78
+ i = 0
79
+ snapshot[:entities].each { |h|
80
+ rebuild2 entity_store[i], h
81
+ i += 1
82
+ }
83
+ i = 0
84
+ snapshot[:players].each { |h|
85
+ rebuild2 player_store[i], h
86
+ i += 1
87
+ }
88
+ snapshot[:instance_variables].each_pair { |k, v|
89
+ plot.theater.instance_variable_set(k, unserialize(v))
90
+ }
91
+ end
92
+
93
+ private
94
+
95
+ def hash_blacklist
96
+ [:@parent, :@children, :@last_action, :@scene, :@next_scene, :@playbook, :@performance_stack, :@buffer_stack, :@messages, :@state]
97
+ end
98
+
99
+ def can_serialize? v
100
+ return true if v.kind_of?(String) or v.kind_of?(Numeric) or v.kind_of?(Symbol) or v.kind_of?(Gamefic::Entity) or is_scene_class?(v) or v == true or v == false or v.nil?
101
+ if v.kind_of?(Array)
102
+ v.each do |e|
103
+ result = can_serialize?(e)
104
+ return false if result == false
105
+ end
106
+ true
107
+ elsif v.kind_of?(Hash)
108
+ v.each_pair do |k, v|
109
+ result = can_serialize?(k)
110
+ return false if result == false
111
+ result = can_serialize?(v)
112
+ return false if result == false
113
+ end
114
+ true
115
+ else
116
+ false
117
+ end
118
+ end
119
+
120
+ def is_scene_class?(v)
121
+ if v.kind_of?(Class)
122
+ s = v
123
+ until s.nil?
124
+ return true if s == Gamefic::Scene::Base
125
+ s = s.superclass
126
+ end
127
+ false
128
+ else
129
+ false
130
+ end
131
+ end
132
+
133
+ def serialize v
134
+ if v.kind_of?(Array)
135
+ result = []
136
+ v.each do |e|
137
+ result.push serialize(e)
138
+ end
139
+ result
140
+ elsif v.kind_of?(Hash)
141
+ result = {}
142
+ v.each_pair do |k, v|
143
+ result[serialize(k)] = serialize(v)
144
+ end
145
+ result
146
+ elsif is_scene_class?(v)
147
+ i = plot.scene_classes.index(v)
148
+ "#<SIN_#{i}>"
149
+ elsif v.kind_of?(Gamefic::Entity)
150
+ i = entity_store.index(v)
151
+ if i.nil?
152
+ i = player_store.index(v)
153
+ if i.nil?
154
+ raise "#{v} not found in plot"
155
+ nil
156
+ else
157
+ "#<PIN_#{i}>"
158
+ end
159
+ else
160
+ "#<EIN_#{i}>"
161
+ end
162
+ else
163
+ v
164
+ end
165
+ end
166
+
167
+ def unserialize v
168
+ if v.kind_of?(Array)
169
+ result = []
170
+ v.each do |e|
171
+ result.push unserialize(e)
172
+ end
173
+ result
174
+ elsif v.kind_of?(Hash)
175
+ result = {}
176
+ v.each_pair do |k, v|
177
+ result[unserialize(k)] = unserialize(v)
178
+ end
179
+ result
180
+ elsif v.kind_of?(String)
181
+ if m = v.match(/#<SIN_([0-9]+)>/)
182
+ plot.scene_classes[m[1].to_i]
183
+ elsif m = v.match(/#<EIN_([0-9]+)>/)
184
+ entity_store[m[1].to_i]
185
+ elsif m = v.match(/#<PIN_([0-9]+)>/)
186
+ player_store[m[1].to_i]
187
+ else
188
+ v
189
+ end
190
+ else
191
+ v
192
+ end
193
+ end
194
+
195
+ def rebuild1 e, h
196
+ h.each_pair do |k, v|
197
+ if k.to_s.start_with?('@')
198
+ e.instance_variable_set(k, unserialize(v))
199
+ end
200
+ end
201
+ end
202
+
203
+ def rebuild2 e, h
204
+ h.each_pair do |k, v|
205
+ if k.to_s != 'class' and !k.to_s.start_with?('@')
206
+ e.send("#{k}=", unserialize(v))
207
+ end
208
+ end
209
+ end
210
+
211
+ def hash_subplot s
212
+ result = { entities: [], instance_variables: {}, theater_instance_variables: {} }
213
+ s.instance_variables.each { |i|
214
+ v = s.instance_variable_get(i)
215
+ result[:instance_variables][i] = serialize(v) if can_serialize?(v)
216
+ }
217
+ s.theater.instance_variables.each { |i|
218
+ v = s.theater.instance_variable_get(i)
219
+ result[:theater_instance_variables][i] = serialize(v) if can_serialize?(v)
220
+ }
221
+ s.entities.each { |s|
222
+ result[:entities].push serialize(s)
223
+ }
224
+ result[:class] = s.class.to_s.split('::').last
225
+ result
226
+ end
227
+
228
+ def rebuild_subplot s, h
229
+ s.entities.each { |e|
230
+ s.destroy e
231
+ }
232
+ h[:instance_variables].each_pair { |k, v|
233
+ s.instance_variable_set(k, unserialize(v))
234
+ }
235
+ h[:theater_instance_variables].each_pair { |k, v|
236
+ s.theater.instance_variable_set(k, unserialize(v))
237
+ }
238
+ i = 0
239
+ h[:entities].each { |e|
240
+ s.add_entity unserialize(e)
241
+ i += 1
242
+ }
243
+ end
244
+
245
+ def entity_store
246
+ @entity_store ||= []
247
+ end
248
+
249
+ def player_store
250
+ @player_store ||= []
251
+ end
252
+
253
+ def hash_entity e
254
+ h = {}
255
+ e.instance_variables.each { |i|
256
+ v = e.instance_variable_get(i)
257
+ h[i] = serialize(v) unless hash_blacklist.include?(i) or !can_serialize?(v)
258
+ }
259
+ h[:class] = e.class.to_s.split('::').last
260
+ h[:parent] = serialize(e.parent)
261
+ h
262
+ end
263
+ end
264
+ end