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.
- checksums.yaml +5 -5
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +13 -0
- data/.solargraph.yml +5 -0
- data/Gemfile +7 -0
- data/LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +10 -0
- data/gamefic.gemspec +27 -0
- data/lib/gamefic.rb +7 -6
- data/lib/gamefic/action.rb +38 -28
- data/lib/gamefic/active.rb +325 -280
- data/lib/gamefic/actor.rb +8 -5
- data/lib/gamefic/command.rb +9 -7
- data/lib/gamefic/core_ext/array.rb +24 -49
- data/lib/gamefic/core_ext/string.rb +25 -16
- data/lib/gamefic/describable.rb +21 -23
- data/lib/gamefic/element.rb +43 -31
- data/lib/gamefic/entity.rb +6 -12
- data/lib/gamefic/index.rb +121 -0
- data/lib/gamefic/{matchable.rb → keywords.rb} +52 -50
- data/lib/gamefic/messaging.rb +43 -44
- data/lib/gamefic/node.rb +14 -5
- data/lib/gamefic/plot.rb +69 -89
- data/lib/gamefic/plot/darkroom.rb +92 -264
- data/lib/gamefic/plot/host.rb +42 -48
- data/lib/gamefic/plot/snapshot.rb +5 -18
- data/lib/gamefic/query.rb +14 -18
- data/lib/gamefic/query/base.rb +30 -18
- data/lib/gamefic/query/children.rb +0 -0
- data/lib/gamefic/query/external.rb +18 -14
- data/lib/gamefic/query/family.rb +1 -7
- data/lib/gamefic/query/matches.rb +75 -67
- data/lib/gamefic/query/parent.rb +0 -0
- data/lib/gamefic/query/siblings.rb +0 -0
- data/lib/gamefic/query/text.rb +2 -1
- data/lib/gamefic/scene.rb +0 -2
- data/lib/gamefic/scene/activity.rb +24 -26
- data/lib/gamefic/scene/base.rb +64 -8
- data/lib/gamefic/scene/conclusion.rb +0 -2
- data/lib/gamefic/scene/custom.rb +0 -2
- data/lib/gamefic/scene/multiple_choice.rb +18 -3
- data/lib/gamefic/scene/multiple_scene.rb +29 -20
- data/lib/gamefic/scene/pause.rb +7 -2
- data/lib/gamefic/scene/yes_or_no.rb +21 -9
- data/lib/gamefic/scriptable.rb +87 -0
- data/lib/gamefic/serialize.rb +68 -0
- data/lib/gamefic/subplot.rb +29 -35
- data/lib/gamefic/syntax.rb +14 -13
- data/lib/gamefic/version.rb +3 -3
- data/lib/gamefic/world.rb +16 -0
- data/lib/gamefic/world/callbacks.rb +135 -0
- data/lib/gamefic/world/commands.rb +184 -0
- data/lib/gamefic/{plot → world}/entities.rb +30 -29
- data/lib/gamefic/{plot → world}/playbook.rb +255 -240
- data/lib/gamefic/world/players.rb +21 -0
- data/lib/gamefic/world/scenes.rb +226 -0
- metadata +41 -92
- data/bin/gamefic +0 -9
- data/lib/gamefic/engine.rb +0 -7
- data/lib/gamefic/engine/base.rb +0 -59
- data/lib/gamefic/engine/tty.rb +0 -24
- data/lib/gamefic/grammar.rb +0 -13
- data/lib/gamefic/grammar/conjugator.rb +0 -20
- data/lib/gamefic/grammar/gender.rb +0 -11
- data/lib/gamefic/grammar/person.rb +0 -10
- data/lib/gamefic/grammar/plural.rb +0 -13
- data/lib/gamefic/grammar/pronouns.rb +0 -106
- data/lib/gamefic/grammar/tense.rb +0 -6
- data/lib/gamefic/grammar/verb_set.rb +0 -43
- data/lib/gamefic/grammar/verbs.rb +0 -26
- data/lib/gamefic/grammar/word_adapter.rb +0 -49
- data/lib/gamefic/plot/articles.rb +0 -22
- data/lib/gamefic/plot/callbacks.rb +0 -126
- data/lib/gamefic/plot/commands.rb +0 -120
- data/lib/gamefic/plot/players.rb +0 -15
- data/lib/gamefic/plot/scenes.rb +0 -187
- data/lib/gamefic/plot/theater.rb +0 -73
- data/lib/gamefic/plot/you_mount.rb +0 -22
- data/lib/gamefic/script.rb +0 -13
- data/lib/gamefic/script/base.rb +0 -42
- data/lib/gamefic/script/file.rb +0 -14
- data/lib/gamefic/script/text.rb +0 -14
- data/lib/gamefic/shell.rb +0 -76
- data/lib/gamefic/source.rb +0 -14
- data/lib/gamefic/source/base.rb +0 -12
- data/lib/gamefic/source/file.rb +0 -23
- data/lib/gamefic/source/text.rb +0 -16
- data/lib/gamefic/tester.rb +0 -19
- data/lib/gamefic/text.rb +0 -8
- data/lib/gamefic/text/ansi.rb +0 -53
- data/lib/gamefic/text/html.rb +0 -68
- data/lib/gamefic/text/html/conversions.rb +0 -250
- data/lib/gamefic/text/html/entities.rb +0 -9
- data/lib/gamefic/tty.rb +0 -10
- data/lib/gamefic/user.rb +0 -7
- data/lib/gamefic/user/base.rb +0 -29
- data/lib/gamefic/user/tty.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 758b3f682dd5837841ce39281a53e6d8beada80ca0f0f0308f2b3909d12349b7
|
4
|
+
data.tar.gz: c330934f0a69040e96cf7c18936b93e5b5cf7580ba95a0ef46e633a79975f513
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f95a19e039ef00076f23f90902077ba78941b2b186bad9963222da21001148741800058c97c7a110662f660af3334ef0a4732046210946ce9a9c54a340d8d566
|
7
|
+
data.tar.gz: dfb08b610f13a75c5515bf95d41834db415bee8a3d196238f53385c6d2983a84dcd8906d048440c21601858f13c48a0f2359da21ea0596455ee7fac7ddd66acf
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -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
|
data/.solargraph.yml
ADDED
data/Gemfile
ADDED
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.
|
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/gamefic.gemspec
ADDED
@@ -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
|
data/lib/gamefic.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
-
require 'gamefic/
|
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'
|
data/lib/gamefic/action.rb
CHANGED
@@ -5,28 +5,36 @@ module Gamefic
|
|
5
5
|
end
|
6
6
|
|
7
7
|
class Action
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
@
|
17
|
+
@arguments = arguments
|
13
18
|
@executed = false
|
14
19
|
end
|
15
20
|
|
16
|
-
#
|
17
|
-
|
18
|
-
parameters
|
19
|
-
end
|
20
|
-
|
21
|
+
# Perform the action.
|
22
|
+
#
|
21
23
|
def execute
|
22
24
|
@executed = true
|
23
|
-
self.class.executor.call(@actor,
|
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
|
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
|
149
|
+
result.push accepted
|
136
150
|
else
|
137
|
-
return nil if
|
138
|
-
result.push
|
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
|
data/lib/gamefic/active.rb
CHANGED
@@ -1,280 +1,325 @@
|
|
1
|
-
module Gamefic
|
2
|
-
class NotConclusionError <
|
3
|
-
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
module Active
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
# @return [Gamefic::
|
13
|
-
attr_reader :
|
14
|
-
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
attr_reader :
|
19
|
-
|
20
|
-
#
|
21
|
-
#
|
22
|
-
|
23
|
-
# @return [
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
#
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
#
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
# @
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
#
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
end
|
128
|
-
|
129
|
-
#
|
130
|
-
# This
|
131
|
-
#
|
132
|
-
#
|
133
|
-
#
|
134
|
-
#
|
135
|
-
#
|
136
|
-
#
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
#
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
def
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
end
|
245
|
-
|
246
|
-
def
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
def
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
def
|
269
|
-
@
|
270
|
-
end
|
271
|
-
|
272
|
-
def
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
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
|