gamefic 1.6.0 → 1.7.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.
@@ -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