gamefic 1.7.0 → 2.0.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 (99) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +13 -0
  5. data/.solargraph.yml +5 -0
  6. data/Gemfile +7 -0
  7. data/LICENSE +20 -0
  8. data/README.md +28 -0
  9. data/Rakefile +10 -0
  10. data/gamefic.gemspec +27 -0
  11. data/lib/gamefic.rb +7 -6
  12. data/lib/gamefic/action.rb +38 -28
  13. data/lib/gamefic/active.rb +325 -280
  14. data/lib/gamefic/actor.rb +8 -5
  15. data/lib/gamefic/command.rb +9 -7
  16. data/lib/gamefic/core_ext/array.rb +24 -49
  17. data/lib/gamefic/core_ext/string.rb +25 -16
  18. data/lib/gamefic/describable.rb +21 -23
  19. data/lib/gamefic/element.rb +43 -31
  20. data/lib/gamefic/entity.rb +6 -12
  21. data/lib/gamefic/index.rb +121 -0
  22. data/lib/gamefic/{matchable.rb → keywords.rb} +52 -50
  23. data/lib/gamefic/messaging.rb +43 -44
  24. data/lib/gamefic/node.rb +14 -5
  25. data/lib/gamefic/plot.rb +69 -89
  26. data/lib/gamefic/plot/darkroom.rb +92 -264
  27. data/lib/gamefic/plot/host.rb +42 -48
  28. data/lib/gamefic/plot/snapshot.rb +5 -18
  29. data/lib/gamefic/query.rb +14 -18
  30. data/lib/gamefic/query/base.rb +30 -18
  31. data/lib/gamefic/query/children.rb +0 -0
  32. data/lib/gamefic/query/external.rb +18 -14
  33. data/lib/gamefic/query/family.rb +1 -7
  34. data/lib/gamefic/query/matches.rb +75 -67
  35. data/lib/gamefic/query/parent.rb +0 -0
  36. data/lib/gamefic/query/siblings.rb +0 -0
  37. data/lib/gamefic/query/text.rb +2 -1
  38. data/lib/gamefic/scene.rb +0 -2
  39. data/lib/gamefic/scene/activity.rb +24 -26
  40. data/lib/gamefic/scene/base.rb +64 -8
  41. data/lib/gamefic/scene/conclusion.rb +0 -2
  42. data/lib/gamefic/scene/custom.rb +0 -2
  43. data/lib/gamefic/scene/multiple_choice.rb +18 -3
  44. data/lib/gamefic/scene/multiple_scene.rb +29 -20
  45. data/lib/gamefic/scene/pause.rb +7 -2
  46. data/lib/gamefic/scene/yes_or_no.rb +21 -9
  47. data/lib/gamefic/scriptable.rb +87 -0
  48. data/lib/gamefic/serialize.rb +68 -0
  49. data/lib/gamefic/subplot.rb +29 -35
  50. data/lib/gamefic/syntax.rb +14 -13
  51. data/lib/gamefic/version.rb +3 -3
  52. data/lib/gamefic/world.rb +16 -0
  53. data/lib/gamefic/world/callbacks.rb +135 -0
  54. data/lib/gamefic/world/commands.rb +184 -0
  55. data/lib/gamefic/{plot → world}/entities.rb +30 -29
  56. data/lib/gamefic/{plot → world}/playbook.rb +255 -240
  57. data/lib/gamefic/world/players.rb +21 -0
  58. data/lib/gamefic/world/scenes.rb +226 -0
  59. metadata +41 -92
  60. data/bin/gamefic +0 -9
  61. data/lib/gamefic/engine.rb +0 -7
  62. data/lib/gamefic/engine/base.rb +0 -59
  63. data/lib/gamefic/engine/tty.rb +0 -24
  64. data/lib/gamefic/grammar.rb +0 -13
  65. data/lib/gamefic/grammar/conjugator.rb +0 -20
  66. data/lib/gamefic/grammar/gender.rb +0 -11
  67. data/lib/gamefic/grammar/person.rb +0 -10
  68. data/lib/gamefic/grammar/plural.rb +0 -13
  69. data/lib/gamefic/grammar/pronouns.rb +0 -106
  70. data/lib/gamefic/grammar/tense.rb +0 -6
  71. data/lib/gamefic/grammar/verb_set.rb +0 -43
  72. data/lib/gamefic/grammar/verbs.rb +0 -26
  73. data/lib/gamefic/grammar/word_adapter.rb +0 -49
  74. data/lib/gamefic/plot/articles.rb +0 -22
  75. data/lib/gamefic/plot/callbacks.rb +0 -126
  76. data/lib/gamefic/plot/commands.rb +0 -120
  77. data/lib/gamefic/plot/players.rb +0 -15
  78. data/lib/gamefic/plot/scenes.rb +0 -187
  79. data/lib/gamefic/plot/theater.rb +0 -73
  80. data/lib/gamefic/plot/you_mount.rb +0 -22
  81. data/lib/gamefic/script.rb +0 -13
  82. data/lib/gamefic/script/base.rb +0 -42
  83. data/lib/gamefic/script/file.rb +0 -14
  84. data/lib/gamefic/script/text.rb +0 -14
  85. data/lib/gamefic/shell.rb +0 -76
  86. data/lib/gamefic/source.rb +0 -14
  87. data/lib/gamefic/source/base.rb +0 -12
  88. data/lib/gamefic/source/file.rb +0 -23
  89. data/lib/gamefic/source/text.rb +0 -16
  90. data/lib/gamefic/tester.rb +0 -19
  91. data/lib/gamefic/text.rb +0 -8
  92. data/lib/gamefic/text/ansi.rb +0 -53
  93. data/lib/gamefic/text/html.rb +0 -68
  94. data/lib/gamefic/text/html/conversions.rb +0 -250
  95. data/lib/gamefic/text/html/entities.rb +0 -9
  96. data/lib/gamefic/tty.rb +0 -10
  97. data/lib/gamefic/user.rb +0 -7
  98. data/lib/gamefic/user/base.rb +0 -29
  99. data/lib/gamefic/user/tty.rb +0 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f3811a2f448c91712eaa8eec46000577c7d38f7a
4
- data.tar.gz: 6cb83cb93b14fd5c94401367e51828bad90a8960
2
+ SHA256:
3
+ metadata.gz: 758b3f682dd5837841ce39281a53e6d8beada80ca0f0f0308f2b3909d12349b7
4
+ data.tar.gz: c330934f0a69040e96cf7c18936b93e5b5cf7580ba95a0ef46e633a79975f513
5
5
  SHA512:
6
- metadata.gz: 12f2199a4d803e7a179b49e1d50f6e83571f21d601e67828546de918c65b3da0ee97790e66ee5f73f34b1a901d515284e3c8bd030ff0dc5d816678b7c9a0ba48
7
- data.tar.gz: 31118c91be341821662fe8624b85924b196c52f9b2bcd0c758f5af13fc9d1dceaa76f204d6f6f3d2e274ef049153b416dcd2648a207e4ad07ea02ea4b9b440c4
6
+ metadata.gz: f95a19e039ef00076f23f90902077ba78941b2b186bad9963222da21001148741800058c97c7a110662f660af3334ef0a4732046210946ce9a9c54a340d8d566
7
+ data.tar.gz: dfb08b610f13a75c5515bf95d41834db415bee8a3d196238f53385c6d2983a84dcd8906d048440c21601858f13c48a0f2359da21ea0596455ee7fac7ddd66acf
@@ -0,0 +1,12 @@
1
+ Gemfile.lock
2
+ .buildpath
3
+ .project
4
+ .settings
5
+ .DS_Store
6
+ coverage
7
+ examples/*/build
8
+ examples/*/release
9
+ .yardoc
10
+ doc
11
+ .vscode
12
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,13 @@
1
+ Metrics/LineLength:
2
+ Description: 'Limit lines to 80 characters.'
3
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
4
+ Enabled: false
5
+
6
+ Style/StringLiterals:
7
+ Description: 'Checks if uses of quotes match the configured preference.'
8
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals'
9
+ Enabled: false
10
+
11
+ Style/Documentation:
12
+ Description: 'Document classes and non-namespace modules.'
13
+ Enabled: false
@@ -0,0 +1,5 @@
1
+ include:
2
+ - lib/**/*.rb
3
+ exclude:
4
+ - spec/**/*
5
+ - test/**/*
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem "simplecov"
7
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Gamefic
2
+ Copyright (c) 2013 by Fred Snyder for Castwide Technologies
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ # Gamefic
2
+
3
+ **A Ruby Interactive Fiction Framework**
4
+
5
+ Gamefic is a system for developing and playing adventure games and interactive
6
+ fiction. This gem provides the core library for running game narratives.
7
+
8
+ Developers should refer to the [Gamefic SDK](https://github.com/castwide/gamefic-sdk)
9
+ for information about writing, building, and distributing Gamefic apps.
10
+
11
+ ## Development
12
+
13
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
14
+
15
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
16
+
17
+ ## Contributing
18
+
19
+ Bug reports and pull requests are welcome on GitHub at https://github.com/castwide/gamefic-sdk.
20
+
21
+ ## License
22
+
23
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
24
+
25
+ ## More Information
26
+
27
+ Go to [the official Gamefic website](http://gamefic.com) for games, news, and
28
+ more documentation.
@@ -0,0 +1,10 @@
1
+ require 'rake'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec) do |t|
5
+ t.pattern = Dir.glob('spec/**/*_spec.rb')
6
+ t.rspec_opts = '--format documentation'
7
+ # t.rspec_opts << ' more options'
8
+ #t.rcov = true
9
+ end
10
+ task :default => :spec
@@ -0,0 +1,27 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'gamefic/version'
4
+ require 'date'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'gamefic'
8
+ s.version = Gamefic::VERSION
9
+ s.date = Date.today.strftime("%Y-%m-%d")
10
+ s.summary = "Gamefic"
11
+ s.description = "An adventure game and interactive fiction framework"
12
+ s.authors = ["Fred Snyder"]
13
+ s.email = 'fsnyder@gamefic.com'
14
+ s.homepage = 'http://gamefic.com'
15
+ s.license = 'MIT'
16
+
17
+ s.files = Dir.chdir(File.expand_path('..', __FILE__)) do
18
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ end
20
+ s.require_paths = ['lib']
21
+
22
+ s.required_ruby_version = '>= 2.1.0'
23
+
24
+ s.add_development_dependency 'rake', '~> 12.3', '>= 12.3'
25
+ s.add_development_dependency 'rspec', '~> 3.5', '>= 3.5.0'
26
+ s.add_development_dependency 'simplecov', '~> 0.14'
27
+ end
@@ -1,9 +1,12 @@
1
- require 'gamefic/matchable'
1
+ require 'gamefic/version'
2
+
3
+ require 'gamefic/keywords'
2
4
  require 'gamefic/core_ext/array'
3
5
  require 'gamefic/core_ext/string'
4
6
 
5
- require 'gamefic/grammar'
6
7
  require 'gamefic/describable'
8
+ require 'gamefic/index'
9
+ require 'gamefic/serialize'
7
10
  require 'gamefic/element'
8
11
  require 'gamefic/entity'
9
12
  require 'gamefic/active'
@@ -12,9 +15,7 @@ require "gamefic/scene"
12
15
  require "gamefic/query"
13
16
  require "gamefic/action"
14
17
  require "gamefic/syntax"
18
+ require 'gamefic/world'
19
+ require 'gamefic/scriptable'
15
20
  require "gamefic/plot"
16
21
  require 'gamefic/subplot'
17
- require "gamefic/engine"
18
- require "gamefic/user"
19
-
20
- require 'gamefic/version'
@@ -5,28 +5,36 @@ module Gamefic
5
5
  end
6
6
 
7
7
  class Action
8
- attr_reader :parameters
9
-
10
- def initialize actor, parameters
8
+ # An array of objects on which the action will operate, e.g., an entity
9
+ # that is a direct object of a command.
10
+ #
11
+ # @return [Array<Object>]
12
+ attr_reader :arguments
13
+ alias parameters arguments
14
+
15
+ def initialize actor, arguments
11
16
  @actor = actor
12
- @parameters = parameters
17
+ @arguments = arguments
13
18
  @executed = false
14
19
  end
15
20
 
16
- # @todo Determine whether to call them parameters, arguments, or both.
17
- def arguments
18
- parameters
19
- end
20
-
21
+ # Perform the action.
22
+ #
21
23
  def execute
22
24
  @executed = true
23
- self.class.executor.call(@actor, *@parameters) unless self.class.executor.nil?
25
+ self.class.executor.call(@actor, *arguments) unless self.class.executor.nil?
24
26
  end
25
27
 
28
+ # True if the #execute method has been called for this action.
29
+ #
30
+ # @return [Boolean]
26
31
  def executed?
27
32
  @executed
28
33
  end
29
34
 
35
+ # The verb associated with this action.
36
+ #
37
+ # @return [Symbol] The symbol representing the verb
30
38
  def verb
31
39
  self.class.verb
32
40
  end
@@ -39,19 +47,17 @@ module Gamefic
39
47
  self.class.rank
40
48
  end
41
49
 
50
+ # True if the action is flagged as meta.
51
+ #
52
+ # @return [Boolean]
42
53
  def meta?
43
54
  self.class.meta?
44
55
  end
45
56
 
46
- def order_key
47
- self.class.order_key
48
- end
49
-
50
- def self.subclass verb, *q, meta: false, order_key: 0, &block
57
+ def self.subclass verb, *q, meta: false, &block
51
58
  act = Class.new(self) do
52
59
  self.verb = verb
53
60
  self.meta = meta
54
- self.order_key = order_key
55
61
  q.each { |q|
56
62
  add_query q
57
63
  }
@@ -87,21 +93,27 @@ module Gamefic
87
93
 
88
94
  def signature
89
95
  # @todo This is clearly unfinished
90
- "#{verb} #{queries.map{|m| m.signature}.join(',')}"
96
+ "#{verb} #{queries.map{|m| m.signature}.join(', ')}"
91
97
  end
92
98
 
99
+ # True if this action is not intended to be performed directly by a
100
+ # character.
101
+ # If the action is hidden, users should not be able to perform it with a
102
+ # direct command. By default, any action whose verb starts with an
103
+ # underscore is hidden.
104
+ #
105
+ # @return [Boolean]
93
106
  def hidden?
94
107
  verb.to_s.start_with?('_')
95
108
  end
96
109
 
110
+ # The proc to call when the action is executed
111
+ #
112
+ # @return [Proc]
97
113
  def executor
98
114
  @executor
99
115
  end
100
116
 
101
- def order_key
102
- @order_key ||= 0
103
- end
104
-
105
117
  def rank
106
118
  if @rank.nil?
107
119
  @rank = 0
@@ -131,11 +143,13 @@ module Gamefic
131
143
  return nil if tokens[i].nil? and matches.remaining == ''
132
144
  matches = p.resolve(actor, "#{matches.remaining} #{tokens[i]}".strip, continued: (i < queries.length - 1))
133
145
  return nil if matches.objects.empty?
146
+ accepted = matches.objects.select{|o| p.accept?(o)}
147
+ return nil if accepted.empty?
134
148
  if p.ambiguous?
135
- result.push matches.objects
149
+ result.push accepted
136
150
  else
137
- return nil if matches.objects.length > 1
138
- result.push matches.objects[0]
151
+ return nil if accepted.length != 1
152
+ result.push accepted.first
139
153
  end
140
154
  i += 1
141
155
  }
@@ -151,10 +165,6 @@ module Gamefic
151
165
  def meta= bool
152
166
  @meta = bool
153
167
  end
154
-
155
- def order_key= num
156
- @order_key = num
157
- end
158
168
  end
159
169
  end
160
170
  end
@@ -1,280 +1,325 @@
1
- module Gamefic
2
- class NotConclusionError < Exception
3
- end
4
-
5
- # The Active module gives entities the ability to perform actions and
6
- # participate in scenes.
7
- #
8
- module Active
9
- # @return [Gamefic::Action]
10
- attr_reader :last_action
11
-
12
- # @return [Gamefic::User::Base]
13
- attr_reader :user
14
-
15
- # @return [Gamefic::Scene::Base]
16
- attr_reader :scene
17
-
18
- attr_reader :next_scene
19
-
20
- # @return [Gamefic::Plot::Playbook]
21
- #attr_accessor :playbook
22
-
23
- # @return [Array<Gamefic::Plot::Playbook>]
24
- def playbooks
25
- @playbooks ||= []
26
- end
27
-
28
- def connect user
29
- @user = user
30
- end
31
-
32
- # An array of actions waiting to be performed.
33
- #
34
- # @return [Array<String>]
35
- def queue
36
- @queue ||= []
37
- end
38
-
39
- # A hash of values representing the state of a performing entity.
40
- #
41
- # @return [Hash]
42
- def state
43
- @state = {}
44
- @state.merge! scene.state unless scene.nil?
45
- @state[:output] = messages
46
- @state
47
- end
48
-
49
- # Send a message to the entity.
50
- # This method will automatically wrap the message in HTML paragraphs.
51
- # To send a message without paragraph formatting, use #stream instead.
52
- #
53
- # @param message [String]
54
- def tell(message)
55
- if buffer_stack > 0
56
- append_buffer message
57
- else
58
- super
59
- end
60
- end
61
-
62
- # Send a message to the Character as raw text.
63
- # Unlike #tell, this method will not wrap the message in HTML paragraphs.
64
- #
65
- # @param message [String]
66
- def stream(message)
67
- if buffer_stack > 0
68
- append_buffer message
69
- else
70
- super
71
- end
72
- end
73
-
74
- # Perform a command.
75
- # The command can be specified as a String or a verb with a list of
76
- # parameters. Either form should yield the same result, but the
77
- # verb/parameter form can yield better performance since it bypasses the
78
- # parser.
79
- #
80
- # The command will be executed immediately regardless of the entity's
81
- # state.
82
- #
83
- # @example Send a command as a string
84
- # character.perform "take the key"
85
- #
86
- # @example Send a command as a verb with parameters
87
- # character.perform :take, @key
88
- #
89
- # @return [Gamefic::Action]
90
- def perform(*command)
91
- actions = []
92
- playbooks.reverse.each { |p| actions.concat p.dispatch(self, *command) }
93
- execute_stack actions
94
- end
95
-
96
- # Quietly perform a command.
97
- # This method executes the command exactly as #perform does, except it
98
- # buffers the resulting output instead of sending it to the user.
99
- #
100
- # @return [String] The output that resulted from performing the command.
101
- def quietly(*command)
102
- if buffer_stack == 0
103
- clear_buffer
104
- end
105
- set_buffer_stack buffer_stack + 1
106
- self.perform *command
107
- set_buffer_stack buffer_stack - 1
108
- buffer
109
- end
110
-
111
- # Perform an action.
112
- # This is functionally identical to the `perform` method, except the
113
- # action must be declared as a verb with a list of parameters. Use
114
- # `perform` if you need to parse a string as a command.
115
- #
116
- # The command will be executed immediately regardless of the entity's
117
- # state.
118
- #
119
- # @example
120
- # character.execute :take, @key
121
- #
122
- # @return [Gamefic::Action]
123
- def execute(verb, *params, quietly: false)
124
- actions = []
125
- playbooks.reverse.each { |p| actions.concat p.dispatch_from_params(self, verb, params) }
126
- execute_stack actions, quietly: quietly
127
- end
128
-
129
- # Proceed to the next Action in the current stack.
130
- # This method is typically used in Action blocks to cascade through
131
- # multiple implementations of the same verb.
132
- #
133
- # @example Proceed through two implementations of a verb
134
- # introduction do |actor|
135
- # actor[:has_eaten] = false # Initial value
136
- # end
137
- # respond :eat do |actor|
138
- # actor.tell "You eat something."
139
- # actor[:has_eaten] = true
140
- # end
141
- # respond :eat do |actor|
142
- # # This version will be executed first because it was implemented last
143
- # if actor[:has_eaten]
144
- # actor.tell "You already ate."
145
- # else
146
- # actor.proceed # Execute the previous implementation
147
- # end
148
- # end
149
- #
150
- def proceed quietly: false
151
- #Director::Delegate.proceed_for self
152
- return if performance_stack.empty?
153
- a = performance_stack.last.shift
154
- unless a.nil?
155
- if quietly
156
- if @buffer_stack == 0
157
- @buffer = ""
158
- end
159
- @buffer_stack += 1
160
- end
161
- a.execute
162
- if quietly
163
- @buffer_stack -= 1
164
- @buffer
165
- end
166
- end
167
- end
168
-
169
- # Immediately start a new scene for the character.
170
- # Use #prepare if you want to declare a scene to be started at the
171
- # beginning of the next turn.
172
- #
173
- def cue new_scene
174
- @next_scene = nil
175
- if new_scene.nil?
176
- @scene = nil
177
- else
178
- @scene = new_scene.new(self)
179
- @scene.start
180
- end
181
- end
182
-
183
- # Prepare a scene to be started for this character at the beginning of the
184
- # next turn.
185
- #
186
- def prepare s
187
- @next_scene = s
188
- end
189
-
190
- # Return true if the character is expected to be in the specified scene on
191
- # the next turn.
192
- #
193
- # @return [Boolean]
194
- def will_cue? scene
195
- (@scene.class == scene and @next_scene.nil?) or @next_scene == scene
196
- end
197
-
198
- # Cue a conclusion. This method works like #cue, except it will raise a
199
- # NotConclusionError if the scene is not a Scene::Conclusion.
200
- #
201
- def conclude scene
202
- raise NotConclusionError unless scene <= Scene::Conclusion
203
- cue scene
204
- end
205
-
206
- # True if the character is in a conclusion.
207
- #
208
- # @return [Boolean]
209
- def concluded?
210
- !scene.nil? and scene.kind_of?(Scene::Conclusion)
211
- end
212
-
213
- def performed order
214
- order.freeze
215
- @last_action = order
216
- end
217
-
218
- def accessible?
219
- false
220
- end
221
-
222
- def inspect
223
- to_s
224
- end
225
-
226
- private
227
-
228
- def execute_stack actions, quietly: false
229
- return nil if actions.empty?
230
- a = actions.first
231
- okay = true
232
- unless a.meta?
233
- playbooks.reverse.each do |playbook|
234
- okay = validate_playbook playbook, a
235
- break unless okay
236
- end
237
- end
238
- if okay
239
- performance_stack.push actions
240
- proceed quietly: quietly
241
- performance_stack.pop
242
- end
243
- a
244
- end
245
-
246
- def validate_playbook playbook, action
247
- okay = true
248
- playbook.validators.each { |v|
249
- result = v.call(self, action.verb, action.parameters)
250
- okay = (result != false)
251
- break unless okay
252
- }
253
- okay
254
- end
255
-
256
- def buffer_stack
257
- @buffer_stack ||= 0
258
- end
259
-
260
- def set_buffer_stack num
261
- @buffer_stack = num
262
- end
263
-
264
- def buffer
265
- @buffer ||= ''
266
- end
267
-
268
- def append_buffer str
269
- @buffer += str
270
- end
271
-
272
- def clear_buffer
273
- @buffer = '' unless @buffer.empty?
274
- end
275
-
276
- def performance_stack
277
- @performance_stack ||= []
278
- end
279
- end
280
- end
1
+ module Gamefic
2
+ class NotConclusionError < RuntimeError; end
3
+
4
+ # The Active module gives entities the ability to perform actions and
5
+ # participate in scenes. The Actor class, for example, is an Entity
6
+ # subclass that includes this module.
7
+ #
8
+ module Active
9
+ # The last action executed by the entity, as reported by the
10
+ # Active#performed method.
11
+ #
12
+ # @return [Gamefic::Action]
13
+ attr_reader :last_action
14
+
15
+ # The scene in which the entity is currently participating.
16
+ #
17
+ # @return [Gamefic::Scene::Base]
18
+ attr_reader :scene
19
+
20
+ # The scene class that will be cued for this entity on the next turn.
21
+ # Usually set with the #prepare method.
22
+ #
23
+ # @return [Class<Gamefic::Scene::Base>]
24
+ attr_reader :next_scene
25
+
26
+ attr_reader :next_options
27
+
28
+ # The prompt for the previous scene.
29
+ #
30
+ # @return [String]
31
+ attr_accessor :last_prompt
32
+
33
+ # The input for the previous scene.
34
+ #
35
+ # @return [String]
36
+ attr_accessor :last_input
37
+
38
+ # The playbooks that will be used to perform commands.
39
+ #
40
+ # @return [Array<Gamefic::World::Playbook>]
41
+ def playbooks
42
+ @playbooks ||= []
43
+ end
44
+
45
+ def syntaxes
46
+ playbooks.map(&:syntaxes).flatten
47
+ end
48
+
49
+ # An array of actions waiting to be performed.
50
+ #
51
+ # @return [Array<String>]
52
+ def queue
53
+ @queue ||= []
54
+ end
55
+
56
+ # A hash of values representing the state of a performing entity.
57
+ #
58
+ # @return [Hash{Symbol => Object}]
59
+ def state
60
+ @state ||= {}
61
+ end
62
+
63
+ def output
64
+ @output ||= {}
65
+ end
66
+
67
+ # Send a message to the entity.
68
+ # This method will automatically wrap the message in HTML paragraphs.
69
+ # To send a message without paragraph formatting, use #stream instead.
70
+ #
71
+ # @param message [String]
72
+ def tell(message)
73
+ if buffer_stack > 0
74
+ append_buffer format(message)
75
+ else
76
+ super
77
+ end
78
+ end
79
+
80
+ # Send a message to the Character as raw text.
81
+ # Unlike #tell, this method will not wrap the message in HTML paragraphs.
82
+ #
83
+ # @param message [String]
84
+ def stream(message)
85
+ if buffer_stack > 0
86
+ append_buffer message
87
+ else
88
+ super
89
+ end
90
+ end
91
+
92
+ # Perform a command.
93
+ # The command can be specified as a String or a verb with a list of
94
+ # parameters. Either form should yield the same result, but the
95
+ # verb/parameter form can yield better performance since it bypasses the
96
+ # parser.
97
+ #
98
+ # The command will be executed immediately regardless of the entity's
99
+ # state.
100
+ #
101
+ # @example Send a command as a string
102
+ # character.perform "take the key"
103
+ #
104
+ # @example Send a command as a verb with parameters
105
+ # character.perform :take, @key
106
+ #
107
+ # @return [Gamefic::Action]
108
+ def perform(*command)
109
+ actions = []
110
+ playbooks.reverse.each { |p| actions.concat p.dispatch(self, *command) }
111
+ execute_stack actions
112
+ end
113
+
114
+ # Quietly perform a command.
115
+ # This method executes the command exactly as #perform does, except it
116
+ # buffers the resulting output instead of sending it to the user.
117
+ #
118
+ # @return [String] The output that resulted from performing the command.
119
+ def quietly(*command)
120
+ if buffer_stack == 0
121
+ clear_buffer
122
+ end
123
+ set_buffer_stack buffer_stack + 1
124
+ self.perform *command
125
+ set_buffer_stack buffer_stack - 1
126
+ buffer
127
+ end
128
+
129
+ # Perform an action.
130
+ # This is functionally identical to the `perform` method, except the
131
+ # action must be declared as a verb with a list of parameters. Use
132
+ # `perform` if you need to parse a string as a command.
133
+ #
134
+ # The command will be executed immediately regardless of the entity's
135
+ # state.
136
+ #
137
+ # @example
138
+ # character.execute :take, @key
139
+ #
140
+ # @return [Gamefic::Action]
141
+ def execute(verb, *params, quietly: false)
142
+ actions = []
143
+ playbooks.reverse.each { |p| actions.concat p.dispatch_from_params(self, verb, params) }
144
+ execute_stack actions, quietly: quietly
145
+ end
146
+
147
+ # Proceed to the next Action in the current stack.
148
+ # This method is typically used in Action blocks to cascade through
149
+ # multiple implementations of the same verb.
150
+ #
151
+ # @example Proceed through two implementations of a verb
152
+ # introduction do |actor|
153
+ # actor[:has_eaten] = false # Initial value
154
+ # end
155
+ # respond :eat do |actor|
156
+ # actor.tell "You eat something."
157
+ # actor[:has_eaten] = true
158
+ # end
159
+ # respond :eat do |actor|
160
+ # # This version will be executed first because it was implemented last
161
+ # if actor[:has_eaten]
162
+ # actor.tell "You already ate."
163
+ # else
164
+ # actor.proceed # Execute the previous implementation
165
+ # end
166
+ # end
167
+ #
168
+ def proceed quietly: false
169
+ return if performance_stack.empty?
170
+ a = performance_stack.last.shift
171
+ unless a.nil?
172
+ if quietly
173
+ if buffer_stack == 0
174
+ @buffer = ""
175
+ end
176
+ set_buffer_stack(buffer_stack + 1)
177
+ end
178
+ a.execute
179
+ if quietly
180
+ set_buffer_stack(buffer_stack - 1)
181
+ @buffer
182
+ end
183
+ end
184
+ end
185
+
186
+ # Immediately start a new scene for the character.
187
+ # Use #prepare if you want to declare a scene to be started at the
188
+ # beginning of the next turn.
189
+ #
190
+ # @param new_scene [Class]
191
+ def cue new_scene, **options
192
+ @next_scene = nil
193
+ if new_scene.nil?
194
+ @scene = nil
195
+ else
196
+ @scene = new_scene.new(self, **options)
197
+ @scene.start
198
+ end
199
+ end
200
+
201
+ # Prepare a scene to be started for this character at the beginning of the
202
+ # next turn. As opposed to #cue, a prepared scene will not start until the
203
+ # current scene finishes.
204
+ #
205
+ # @param new_scene [Class]
206
+ def prepare new_scene, **options
207
+ @next_scene = new_scene
208
+ @next_options = options
209
+ end
210
+
211
+ # Return true if the character is expected to be in the specified scene on
212
+ # the next turn.
213
+ #
214
+ # @return [Boolean]
215
+ def will_cue? scene
216
+ (@scene.class == scene and @next_scene.nil?) || @next_scene == scene
217
+ end
218
+
219
+ # Cue a conclusion. This method works like #cue, except it will raise a
220
+ # NotConclusionError if the scene is not a Scene::Conclusion.
221
+ #
222
+ def conclude scene
223
+ raise NotConclusionError unless scene <= Scene::Conclusion
224
+ cue scene
225
+ end
226
+
227
+ # True if the character is in a conclusion.
228
+ #
229
+ # @return [Boolean]
230
+ def concluded?
231
+ !scene.nil? && scene.kind_of?(Scene::Conclusion)
232
+ end
233
+
234
+ # Record the last action the entity executed. This method is typically
235
+ # called when the entity performs an action in response to user input.
236
+ #
237
+ def performed action
238
+ action.freeze
239
+ @last_action = action
240
+ end
241
+
242
+ def accessible?
243
+ false
244
+ end
245
+
246
+ def inspect
247
+ to_s
248
+ end
249
+
250
+ # Track the entity's performance of a scene.
251
+ #
252
+ def entered scene
253
+ klass = (scene.kind_of?(Gamefic::Scene::Base) ? scene.class : scene)
254
+ entered_scenes.push klass unless entered_scenes.include?(klass)
255
+ end
256
+
257
+ # Determine whether the entity has performed the specified scene.
258
+ #
259
+ # @return [Boolean]
260
+ def entered? scene
261
+ klass = (scene.kind_of?(Gamefic::Scene::Base) ? scene.class : scene)
262
+ entered_scenes.include?(klass)
263
+ end
264
+
265
+ private
266
+
267
+ # @return [Array<Gamefic::Scene::Base>]
268
+ def entered_scenes
269
+ @entered_scenes ||= []
270
+ end
271
+
272
+ def execute_stack actions, quietly: false
273
+ return nil if actions.empty?
274
+ a = actions.first
275
+ okay = true
276
+ unless a.meta?
277
+ playbooks.reverse.each do |playbook|
278
+ okay = validate_playbook playbook, a
279
+ break unless okay
280
+ end
281
+ end
282
+ if okay
283
+ performance_stack.push actions
284
+ proceed quietly: quietly
285
+ performance_stack.pop
286
+ end
287
+ a
288
+ end
289
+
290
+ def validate_playbook playbook, action
291
+ okay = true
292
+ playbook.validators.each { |v|
293
+ result = v.call(self, action.verb, action.parameters)
294
+ okay = (result != false)
295
+ break unless okay
296
+ }
297
+ okay
298
+ end
299
+
300
+ def buffer_stack
301
+ @buffer_stack ||= 0
302
+ end
303
+
304
+ def set_buffer_stack num
305
+ @buffer_stack = num
306
+ end
307
+
308
+ # @return [String]
309
+ def buffer
310
+ @buffer ||= ''
311
+ end
312
+
313
+ def append_buffer str
314
+ @buffer += str
315
+ end
316
+
317
+ def clear_buffer
318
+ @buffer = ''
319
+ end
320
+
321
+ def performance_stack
322
+ @performance_stack ||= []
323
+ end
324
+ end
325
+ end