gamefic 1.6.0 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +16 -0
  5. data/.solargraph.yml +5 -0
  6. data/CHANGELOG.md +6 -0
  7. data/Gemfile +7 -0
  8. data/LICENSE +20 -0
  9. data/README.md +28 -0
  10. data/Rakefile +10 -0
  11. data/gamefic.gemspec +27 -0
  12. data/lib/gamefic.rb +11 -8
  13. data/lib/gamefic/action.rb +68 -58
  14. data/lib/gamefic/active.rb +331 -0
  15. data/lib/gamefic/actor.rb +8 -0
  16. data/lib/gamefic/command.rb +9 -7
  17. data/lib/gamefic/core_ext/array.rb +27 -49
  18. data/lib/gamefic/core_ext/string.rb +25 -16
  19. data/lib/gamefic/describable.rb +37 -22
  20. data/lib/gamefic/element.rb +47 -0
  21. data/lib/gamefic/entity.rb +24 -48
  22. data/lib/gamefic/{matchable.rb → keywords.rb} +52 -50
  23. data/lib/gamefic/messaging.rb +43 -45
  24. data/lib/gamefic/node.rb +14 -5
  25. data/lib/gamefic/plot.rb +73 -85
  26. data/lib/gamefic/plot/darkroom.rb +80 -0
  27. data/lib/gamefic/plot/host.rb +42 -46
  28. data/lib/gamefic/plot/snapshot.rb +14 -214
  29. data/lib/gamefic/query.rb +15 -17
  30. data/lib/gamefic/query/base.rb +51 -42
  31. data/lib/gamefic/query/children.rb +0 -0
  32. data/lib/gamefic/query/descendants.rb +2 -2
  33. data/lib/gamefic/query/external.rb +18 -0
  34. data/lib/gamefic/query/family.rb +3 -7
  35. data/lib/gamefic/query/matches.rb +75 -67
  36. data/lib/gamefic/query/parent.rb +0 -0
  37. data/lib/gamefic/query/siblings.rb +0 -0
  38. data/lib/gamefic/query/text.rb +12 -12
  39. data/lib/gamefic/query/tree.rb +17 -0
  40. data/lib/gamefic/scene.rb +1 -5
  41. data/lib/gamefic/scene/{active.rb → activity.rb} +4 -6
  42. data/lib/gamefic/scene/base.rb +77 -13
  43. data/lib/gamefic/scene/conclusion.rb +0 -2
  44. data/lib/gamefic/scene/custom.rb +0 -2
  45. data/lib/gamefic/scene/multiple_choice.rb +18 -16
  46. data/lib/gamefic/scene/multiple_scene.rb +29 -20
  47. data/lib/gamefic/scene/pause.rb +7 -2
  48. data/lib/gamefic/scene/yes_or_no.rb +21 -9
  49. data/lib/gamefic/scriptable.rb +88 -0
  50. data/lib/gamefic/serialize.rb +223 -0
  51. data/lib/gamefic/subplot.rb +47 -51
  52. data/lib/gamefic/syntax.rb +15 -13
  53. data/lib/gamefic/version.rb +3 -3
  54. data/lib/gamefic/world.rb +18 -0
  55. data/lib/gamefic/world/callbacks.rb +135 -0
  56. data/lib/gamefic/world/commands.rb +184 -0
  57. data/lib/gamefic/world/entities.rb +98 -0
  58. data/lib/gamefic/{plot → world}/playbook.rb +245 -236
  59. data/lib/gamefic/world/players.rb +37 -0
  60. data/lib/gamefic/world/scenes.rb +226 -0
  61. metadata +40 -108
  62. data/bin/gamefic +0 -9
  63. data/lib/gamefic/character.rb +0 -232
  64. data/lib/gamefic/character/state.rb +0 -12
  65. data/lib/gamefic/engine.rb +0 -7
  66. data/lib/gamefic/engine/base.rb +0 -66
  67. data/lib/gamefic/engine/tty.rb +0 -24
  68. data/lib/gamefic/grammar.rb +0 -13
  69. data/lib/gamefic/grammar/conjugator.rb +0 -20
  70. data/lib/gamefic/grammar/gender.rb +0 -11
  71. data/lib/gamefic/grammar/person.rb +0 -10
  72. data/lib/gamefic/grammar/plural.rb +0 -13
  73. data/lib/gamefic/grammar/pronouns.rb +0 -105
  74. data/lib/gamefic/grammar/tense.rb +0 -6
  75. data/lib/gamefic/grammar/verb_set.rb +0 -43
  76. data/lib/gamefic/grammar/verbs.rb +0 -26
  77. data/lib/gamefic/grammar/word_adapter.rb +0 -49
  78. data/lib/gamefic/plot/articles.rb +0 -22
  79. data/lib/gamefic/plot/callbacks.rb +0 -127
  80. data/lib/gamefic/plot/commands.rb +0 -121
  81. data/lib/gamefic/plot/entities.rb +0 -88
  82. data/lib/gamefic/plot/players.rb +0 -15
  83. data/lib/gamefic/plot/scenes.rb +0 -149
  84. data/lib/gamefic/plot/theater.rb +0 -73
  85. data/lib/gamefic/plot/you_mount.rb +0 -22
  86. data/lib/gamefic/script.rb +0 -13
  87. data/lib/gamefic/script/base.rb +0 -42
  88. data/lib/gamefic/script/file.rb +0 -14
  89. data/lib/gamefic/script/text.rb +0 -14
  90. data/lib/gamefic/shell.rb +0 -76
  91. data/lib/gamefic/source.rb +0 -14
  92. data/lib/gamefic/source/base.rb +0 -12
  93. data/lib/gamefic/source/file.rb +0 -23
  94. data/lib/gamefic/source/text.rb +0 -16
  95. data/lib/gamefic/tester.rb +0 -19
  96. data/lib/gamefic/text.rb +0 -8
  97. data/lib/gamefic/text/ansi.rb +0 -53
  98. data/lib/gamefic/text/html.rb +0 -68
  99. data/lib/gamefic/text/html/conversions.rb +0 -250
  100. data/lib/gamefic/text/html/entities.rb +0 -9
  101. data/lib/gamefic/tty.rb +0 -10
  102. data/lib/gamefic/user.rb +0 -8
  103. data/lib/gamefic/user/base.rb +0 -15
  104. data/lib/gamefic/user/buffer.rb +0 -32
  105. data/lib/gamefic/user/tty.rb +0 -54
@@ -1,46 +1,42 @@
1
- require 'gamefic/subplot'
2
-
3
- module Gamefic
4
-
5
- module Plot::Host
6
- # Get an array of all the current subplots.
7
- #
8
- # @return [Array<Subplot>]
9
- def subplots
10
- p_subplots.clone
11
- end
12
-
13
- # Start a new subplot based on the provided class.
14
- #
15
- # @param subplot_class [Class] The class of the subplot to be created (Subplot by default)
16
- # @return [Subplot]
17
- def branch subplot_class = Gamefic::Subplot, introduce: nil, next_cue: nil, busy_cue: nil
18
- subplot = subplot_class.new(self, introduce: introduce, next_cue: next_cue, busy_cue: busy_cue)
19
- p_subplots.push subplot
20
- subplot
21
- end
22
-
23
- # Get the player's current subplot or nil if none exists.
24
- #
25
- # @return [Subplot]
26
- def subplot_for player
27
- subplots.each { |s|
28
- return s if s.players.include?(player)
29
- }
30
- nil
31
- end
32
-
33
- # Determine whether the player is involved in a subplot.
34
- #
35
- # @return [Boolean]
36
- def in_subplot? player
37
- !subplot_for(player).nil?
38
- end
39
-
40
- private
41
- def p_subplots
42
- @p_subplots ||= []
43
- end
44
- end
45
-
46
- end
1
+ require 'gamefic/subplot'
2
+
3
+ module Gamefic
4
+ # Methods for hosting and managing subplots.
5
+ #
6
+ module Plot::Host
7
+ # Get an array of all the current subplots.
8
+ #
9
+ # @return [Array<Subplot>]
10
+ def subplots
11
+ @subplots ||= []
12
+ end
13
+
14
+ # Start a new subplot based on the provided class.
15
+ #
16
+ # @param subplot_class [Class<Gamefic::Subplot>] The class of the subplot to be created (Subplot by default)
17
+ # @return [Gamefic::Subplot]
18
+ def branch subplot_class = Gamefic::Subplot, introduce: nil, next_cue: nil, **more
19
+ subplot = subplot_class.new(self, introduce: introduce, next_cue: next_cue, **more)
20
+ subplots.push subplot
21
+ subplot
22
+ end
23
+
24
+ # Get the player's current subplots.
25
+ #
26
+ # @return [Array<Subplot>]
27
+ def subplots_featuring player
28
+ result = []
29
+ subplots.each { |s|
30
+ result.push s if s.players.include?(player)
31
+ }
32
+ result
33
+ end
34
+
35
+ # Determine whether the player is involved in a subplot.
36
+ #
37
+ # @return [Boolean]
38
+ def in_subplot? player
39
+ !subplots_featuring(player).empty?
40
+ end
41
+ end
42
+ end
@@ -2,226 +2,26 @@ require 'json'
2
2
 
3
3
  module Gamefic
4
4
  module Plot::Snapshot
5
-
6
- # Take a snapshot of the plot's current state.
7
- # The snapshot is a hash with two keys: entities and subplots.
5
+ # Save the current game state as a data hash.
6
+ # See Gamefic::Plot::Darkroom for more information about
7
+ # the data format.
8
8
  #
9
9
  # @return [Hash]
10
10
  def save
11
- store = get_entity_hash
12
- if @initial_state.nil?
13
- @initial_state = store
14
- store = []
15
- @initial_state.length.times do
16
- store.push {}
17
- end
18
- else
19
- store = reduce(store)
20
- end
21
- return {
22
- entities: store,
23
- subplots: save_subplots,
24
- metadata: metadata
25
- }
11
+ Gamefic::Plot::Darkroom.new(self).save
26
12
  end
27
-
28
- # Restore the plot to the state of the provided snapshot.
13
+
14
+ # Restore the game state from a snapshot.
29
15
  #
30
- # @param snapshot [Hash]
31
- def restore snapshot
32
- restore_initial_state
33
- internal_restore snapshot[:entities]
34
- restore_subplots snapshot[:subplots]
35
- end
36
-
37
- private
38
-
39
- # Restore the plot to the state of its first snapshot.
16
+ # If `snapshot` is a string, parse it as a JSON object.
40
17
  #
41
- def restore_initial_state
42
- p_entities[@initial_state.length..-1].each { |e|
43
- e.parent = nil
44
- }
45
- p_entities.slice! @initial_state.length..-1
46
- internal_restore @initial_state
47
- end
48
-
49
- def get_entity_hash
50
- store = []
51
- entities.each { |e|
52
- hash = {}
53
- e.instance_variables.each { |k|
54
- next if k == :@children
55
- v = e.instance_variable_get(k)
56
- hash[k] = v
57
- }
58
- hash[:class] = e.class.to_s
59
- store.push serialize_object(hash)
60
- }
61
- store
62
- end
63
-
64
- def internal_restore snapshot
65
- index = 0
66
- snapshot.each { |hash|
67
- if entities[index].nil?
68
- cls = Kernel.const_get(hash[:class])
69
- p_entities[index] = make cls
70
- end
71
- internal_restore_hash hash, index
72
- index += 1
73
- }
74
- nil
75
- end
76
-
77
- def internal_restore_hash hash, index
78
- hash.each { |k, v|
79
- if k == :scene
80
- entities[index].cue v.to_sym
81
- elsif (k != :class)
82
- entities[index].instance_variable_set(k, unserialize(v))
83
- end
84
- }
85
- nil
86
- end
87
-
88
- def reduce entities
89
- reduced = []
90
- index = 0
91
- entities.each { |e|
92
- r = {}
93
- e.each_pair { |k, v|
94
- if index >= @initial_state.length or @initial_state[index][k] != v
95
- r[k] = v
96
- end
97
- }
98
- reduced.push r
99
- index += 1
100
- }
101
- reduced
102
- end
103
-
104
- def can_serialize? obj
105
- return true if (obj == true or obj == false or obj.nil?)
106
- allowed = [String, Fixnum, Float, Numeric, Entity, Direction, Hash, Array, Symbol]
107
- allowed.each { |a|
108
- return true if obj.kind_of?(a)
109
- }
110
- false
111
- end
112
-
113
- def serialize_object obj
114
- return nil if obj.nil?
115
- return false if obj == false
116
- if obj.kind_of?(Hash)
117
- return serialize_hash obj
118
- elsif obj.kind_of?(Array)
119
- return serialize_array obj
120
- else
121
- if obj.kind_of?(Entity)
122
- return "#<EIN_#{p_entities.index(obj)}>"
123
- elsif obj.kind_of?(Direction)
124
- return "#<DIR_#{obj.name}>"
125
- end
126
- end
127
- return obj
128
- end
129
-
130
- def serialize_hash obj
131
- hash = {}
132
- obj.each_pair { |k, v|
133
- if can_serialize?(k) and can_serialize?(v)
134
- hash[serialize_object(k)] = serialize_object(v)
135
- end
136
- }
137
- return hash
138
- end
139
-
140
- def serialize_array obj
141
- arr = []
142
- obj.each_index { |i|
143
- if can_serialize?(obj[i])
144
- arr[i] = serialize_object(obj[i])
145
- else
146
- raise "Bad array in snapshot"
147
- end
148
- }
149
- return arr
150
- end
151
-
152
- def unserialize obj
153
- if obj.kind_of?(Hash)
154
- unserialize_hash obj
155
- elsif obj.kind_of?(Array)
156
- unserialize_array obj
157
- elsif obj.to_s.match(/^#<EIN_[0-9]+>$/)
158
- i = obj[6..-2].to_i
159
- p_entities[i]
160
- elsif obj.to_s.match(/^#<DIR_[a-z]+>$/)
161
- Direction.find(obj[6..-2])
162
- else
163
- obj
164
- end
165
- end
166
-
167
- def unserialize_hash obj
168
- hash = {}
169
- obj.each_pair { |k, v|
170
- hash[unserialize(k)] = unserialize(v)
171
- }
172
- hash
173
- end
174
-
175
- def unserialize_array obj
176
- arr = []
177
- obj.each_index { |i|
178
- arr[i] = unserialize(obj[i])
179
- }
180
- arr
181
- end
182
-
183
- def save_subplots
184
- # TODO: Subplot snapshots are temporarily disabled.
185
- return []
186
- arr = []
187
- subplots.each { |s|
188
- hash = {}
189
- hash[:class] = s.class.to_s
190
- s.instance_variables.each { |k|
191
- v = s.instance_variable_get(k)
192
- if can_serialize?(v)
193
- hash[k] = serialize_object(v)
194
- end
195
- }
196
- arr.push hash
197
- }
198
- arr
199
- end
200
-
201
- def restore_subplots arr
202
- # TODO: Subplot snapshots are temporarily disabled.
203
- return
204
- players.each { |p|
205
- p.send(:p_subplots).clear
206
- }
207
- p_subplots.clear
208
- arr.each { |hash|
209
- cls = Kernel.const_get(hash[:class])
210
- subplot = cls.new self
211
- hash.each { |k, v|
212
- if k != :class
213
- subplot.instance_variable_set(k, unserialize(v))
214
- end
215
- }
216
- subplot.players.each { |p|
217
- p.send(:p_subplots).push subplot
218
- }
219
- subplot.entities.each { |e|
220
- e.extend Subplot::Element
221
- e.instance_variable_set(:@subplot, subplot)
222
- }
223
- p_subplots.push subplot
224
- }
18
+ # @note The string conversion is performed as a convenience for web apps.
19
+ #
20
+ # @param snapshot [Hash, String]
21
+ # @return [void]
22
+ def restore snapshot
23
+ snapshot = JSON.parse(snapshot) if snapshot.is_a?(String)
24
+ Gamefic::Plot::Darkroom.new(self).restore(snapshot)
225
25
  end
226
26
  end
227
27
  end
@@ -1,17 +1,15 @@
1
- #require 'gamefic/keywords'
2
-
3
- module Gamefic
4
-
5
- module Query
6
- autoload :Base, 'gamefic/query/base'
7
- autoload :Children, 'gamefic/query/children'
8
- autoload :Descendants, 'gamefic/query/descendants'
9
- autoload :Family, 'gamefic/query/family'
10
- autoload :Itself, 'gamefic/query/itself'
11
- autoload :Matches, 'gamefic/query/matches'
12
- autoload :Parent, 'gamefic/query/parent'
13
- autoload :Siblings, 'gamefic/query/siblings'
14
- autoload :Text, 'gamefic/query/text'
15
- end
16
-
17
- end
1
+ module Gamefic
2
+ module Query
3
+ autoload :Base, 'gamefic/query/base'
4
+ autoload :Children, 'gamefic/query/children'
5
+ autoload :Descendants, 'gamefic/query/descendants'
6
+ autoload :External, 'gamefic/query/external'
7
+ autoload :Family, 'gamefic/query/family'
8
+ autoload :Tree, 'gamefic/query/tree'
9
+ autoload :Itself, 'gamefic/query/itself'
10
+ autoload :Matches, 'gamefic/query/matches'
11
+ autoload :Parent, 'gamefic/query/parent'
12
+ autoload :Siblings, 'gamefic/query/siblings'
13
+ autoload :Text, 'gamefic/query/text'
14
+ end
15
+ end
@@ -9,6 +9,13 @@ module Gamefic
9
9
  @arguments = args
10
10
  end
11
11
 
12
+ # Determine whether the query allows ambiguous entity references.
13
+ # If false, actions that use this query will only be valid if the token
14
+ # passed into it resolves to a single entity. If true, actions will
15
+ # accept an array of matching entities instead.
16
+ # Queries are not ambiguous by default (ambiguous? == false).
17
+ #
18
+ # @return [Boolean]
12
19
  def ambiguous?
13
20
  false
14
21
  end
@@ -21,9 +28,10 @@ module Gamefic
21
28
  []
22
29
  end
23
30
 
24
- # Get an array of objects that exist in the subject's context and match
25
- # the provided token.
31
+ # Get a collection of objects that exist in the subject's context and
32
+ # match the provided token. The result is provided as a Matches object.
26
33
  #
34
+ # @return [Gamefic::Query::Matches]
27
35
  def resolve(subject, token, continued: false)
28
36
  available = context_from(subject)
29
37
  return Matches.new([], '', token) if available.empty?
@@ -35,8 +43,8 @@ module Gamefic
35
43
  return Matches.new(drill, token, '') unless drill.length != 1
36
44
  return Matches.new([], '', token)
37
45
  end
38
- result = available.select{ |e| e.match?(token) }
39
- result = available.select{ |e| e.match?(token, fuzzy: true) } if result.empty?
46
+ result = available.select{ |e| e.specified?(token) }
47
+ result = available.select{ |e| e.specified?(token, fuzzy: true) } if result.empty?
40
48
  result.keep_if{ |e| accept? e }
41
49
  Matches.new(result, (result.empty? ? '' : token), (result.empty? ? token : ''))
42
50
  end
@@ -47,17 +55,18 @@ module Gamefic
47
55
  result.include?(object)
48
56
  end
49
57
 
58
+ # A ranking of how precise the query's arguments are.
59
+ #
60
+ # Query precision is a factor in calculating Action#rank.
61
+ #
62
+ # @return [Integer]
50
63
  def precision
51
64
  if @precision.nil?
52
65
  @precision = 1
53
66
  arguments.each { |a|
54
- if a.kind_of?(Symbol) or a.kind_of?(Regexp)
55
- @precision += 1
56
- elsif a.kind_of?(Class)
57
- @precision += (count_superclasses(a) * 100)
58
- elsif a.kind_of?(Module)
59
- @precision += 10
60
- elsif a.kind_of?(Object)
67
+ if a.is_a?(Class)
68
+ @precision += 100
69
+ elsif a.is_a?(Gamefic::Entity)
61
70
  @precision += 1000
62
71
  end
63
72
  }
@@ -65,60 +74,61 @@ module Gamefic
65
74
  end
66
75
  @precision
67
76
  end
68
-
69
- def rank
70
- precision
71
- end
77
+ alias rank precision
72
78
 
73
79
  def signature
74
- "#{self.class.to_s.downcase}(#{@arguments.join(',')})"
80
+ "#{self.class.to_s.split('::').last.downcase}(#{simplify_arguments.join(', ')})"
75
81
  end
76
82
 
83
+ # Determine whether the specified entity passes the query's arguments.
84
+ #
85
+ # @return [Boolean]
77
86
  def accept?(entity)
78
87
  result = true
79
- arguments.each { |a|
80
- if a.kind_of?(Symbol)
81
- result = (entity.send(a) != false)
82
- elsif a.kind_of?(Regexp)
83
- result = (!entity.to_s.match(a).nil?)
84
- elsif a.is_a?(Module) or a.is_a?(Class)
85
- result = (entity.is_a?(a))
88
+ arguments.each do |a|
89
+ result = if a.is_a?(Symbol)
90
+ (entity.send(a) != false)
91
+ elsif a.is_a?(Regexp)
92
+ !entity.to_s.match(a).nil?
93
+ elsif a.is_a?(Module) || a.is_a?(Class)
94
+ entity.is_a?(a)
86
95
  else
87
- result = (entity == a)
96
+ (entity == a)
88
97
  end
89
98
  break if result == false
90
- }
99
+ end
91
100
  result
92
101
  end
93
102
 
94
103
  protected
95
-
96
- # Return an array of the entity's children. If the child is neighborly,
104
+
105
+ # Return an array of the entity's children. If the child is accessible,
97
106
  # recursively append its children.
98
107
  # The result will NOT include the original entity itself.
99
108
  #
100
109
  # @return [Array<Object>]
101
110
  def subquery_accessible entity
111
+ return [] if entity.nil?
102
112
  result = []
103
113
  if entity.accessible?
104
- entity.children.each { |c|
114
+ entity.children.each do |c|
105
115
  result.push c
106
116
  result.concat subquery_accessible(c)
107
- }
117
+ end
108
118
  end
109
119
  result
110
120
  end
111
121
 
112
122
  private
113
123
 
114
- def count_superclasses cls
115
- s = cls.superclass
116
- c = 1
117
- until s.nil? or s == Object or s == BasicObject
118
- c += 1
119
- s = s.superclass
124
+ def simplify_arguments
125
+ arguments.map do |a|
126
+ if a.is_a?(Class) || a.is_a?(Object)
127
+ a.to_s.split('::').last.downcase
128
+ else
129
+ a.to_s.downcase
130
+ end
120
131
  end
121
- c
122
132
  end
123
133
 
124
134
  def nested?(token)
@@ -128,13 +138,12 @@ module Gamefic
128
138
  def denest(objects, token)
129
139
  parts = token.split(NEST_REGEXP)
130
140
  current = parts.pop
131
- last_result = objects.select{ |e| e.match?(current) }
132
- last_result = objects.select{ |e| e.match?(current, fuzzy: true) } if last_result.empty?
133
- result = last_result
134
- while parts.length > 0
141
+ last_result = objects.select { |e| e.specified?(current) }
142
+ last_result = objects.select { |e| e.specified?(current, fuzzy: true) } if last_result.empty?
143
+ until parts.empty?
135
144
  current = "#{parts.last} #{current}"
136
- result = last_result.select{ |e| e.match?(current) }
137
- result = last_result.select{ |e| e.match?(current, fuzzy: true) } if result.empty?
145
+ result = last_result.select { |e| e.specified?(current) }
146
+ result = last_result.select { |e| e.specified?(current, fuzzy: true) } if result.empty?
138
147
  break if result.empty?
139
148
  parts.pop
140
149
  last_result = result