gamefic 3.4.0 → 3.6.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 +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +1 -1
- data/lib/gamefic/action.rb +1 -0
- data/lib/gamefic/active/epic.rb +1 -0
- data/lib/gamefic/active.rb +4 -0
- data/lib/gamefic/chapter.rb +25 -42
- data/lib/gamefic/command.rb +49 -1
- data/lib/gamefic/dispatcher.rb +8 -31
- data/lib/gamefic/entity.rb +26 -0
- data/lib/gamefic/expression.rb +3 -0
- data/lib/gamefic/narrative.rb +9 -5
- data/lib/gamefic/node.rb +3 -5
- data/lib/gamefic/plot.rb +5 -0
- data/lib/gamefic/proxy/base.rb +27 -0
- data/lib/gamefic/proxy/config.rb +16 -0
- data/lib/gamefic/proxy/pick.rb +11 -0
- data/lib/gamefic/proxy/plot_pick.rb +11 -0
- data/lib/gamefic/proxy.rb +79 -0
- data/lib/gamefic/query/abstract.rb +12 -0
- data/lib/gamefic/query/base.rb +62 -16
- data/lib/gamefic/query/general.rb +6 -15
- data/lib/gamefic/query/result.rb +4 -1
- data/lib/gamefic/query/scoped.rb +3 -21
- data/lib/gamefic/query/text.rb +17 -15
- data/lib/gamefic/query.rb +1 -0
- data/lib/gamefic/response.rb +75 -34
- data/lib/gamefic/scanner/base.rb +44 -0
- data/lib/gamefic/scanner/fuzzy.rb +17 -0
- data/lib/gamefic/scanner/fuzzy_nesting.rb +14 -0
- data/lib/gamefic/scanner/nesting.rb +39 -0
- data/lib/gamefic/scanner/result.rb +60 -0
- data/lib/gamefic/scanner/strict.rb +31 -0
- data/lib/gamefic/scanner.rb +33 -111
- data/lib/gamefic/scope/descendants.rb +16 -0
- data/lib/gamefic/scope/family.rb +31 -8
- data/lib/gamefic/scope.rb +1 -0
- data/lib/gamefic/scriptable/actions.rb +4 -23
- data/lib/gamefic/scriptable/entities.rb +32 -17
- data/lib/gamefic/scriptable/plot_proxies.rb +29 -0
- data/lib/gamefic/scriptable/proxies.rb +31 -0
- data/lib/gamefic/scriptable/queries.rb +27 -8
- data/lib/gamefic/scriptable/scenes.rb +7 -1
- data/lib/gamefic/scriptable.rb +78 -16
- data/lib/gamefic/stage.rb +2 -2
- data/lib/gamefic/subplot.rb +33 -1
- data/lib/gamefic/version.rb +1 -1
- data/lib/gamefic.rb +1 -1
- metadata +17 -4
- data/lib/gamefic/composer.rb +0 -70
- data/lib/gamefic/scriptable/proxy.rb +0 -69
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5c61efc096ecbacdd43431dd3f0b755120650258a4a3e5d724692957ee45f53
|
4
|
+
data.tar.gz: 499d740594a35e767c86fae7610a51dafa9ba647658af7b3ab46c61de4335501
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd430f5dfa970cacfe12915ff09284ef286b1116aa701ce5484472fdf880b68a5de91953182189c647e90c527653bde92db26016045ac5ea5304e660082979fe
|
7
|
+
data.tar.gz: 1726fc542b40c55443bc9b452df85d94fe0831fa19bc73e40076cd6768e92bc71e504fd2da7998ef65b913c21f8b3ccb7bd4f04329186b88e71eb9dc675b0b69
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
## 3.6.0 - October 6, 2024
|
2
|
+
- Normalized arguments accept strings
|
3
|
+
- Smarter picks and proxies
|
4
|
+
- Commands prefer strictness over precision
|
5
|
+
- Queries scan for ambiguity before filtering through arguments
|
6
|
+
- Abstract queries
|
7
|
+
- Command logging
|
8
|
+
|
9
|
+
## 3.5.0 - October 5, 2024
|
10
|
+
- Configurable scanners
|
11
|
+
- Refactored scanners and queries
|
12
|
+
- Allow assignment to nil instance variables in stage
|
13
|
+
- Lazy proxies
|
14
|
+
- Remove buggy index proxies
|
15
|
+
- Chapter inherits Narrative
|
16
|
+
- Plot attribute proxy for chapters and subplots
|
17
|
+
- Entity#leave
|
18
|
+
- Descendants query
|
19
|
+
- Persistent subplots
|
20
|
+
- Entity#broadcast
|
21
|
+
- Ascendants in family query
|
22
|
+
|
1
23
|
## 3.4.0 - September 10, 2024
|
2
24
|
- Chapters
|
3
25
|
- Subplots and chapters do not repeat plot scripts
|
data/README.md
CHANGED
@@ -16,7 +16,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
16
16
|
|
17
17
|
## Contributing
|
18
18
|
|
19
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/castwide/gamefic
|
19
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/castwide/gamefic.
|
20
20
|
|
21
21
|
## License
|
22
22
|
|
data/lib/gamefic/action.rb
CHANGED
data/lib/gamefic/active/epic.rb
CHANGED
data/lib/gamefic/active.rb
CHANGED
data/lib/gamefic/chapter.rb
CHANGED
@@ -6,17 +6,15 @@ module Gamefic
|
|
6
6
|
# adding the required instance variables, methods, and attributes to the
|
7
7
|
# plot.
|
8
8
|
#
|
9
|
-
# Chapters are similar to subplots with
|
10
|
-
# * Chapters persist for the duration of
|
9
|
+
# Chapters are similar to subplots with three important exceptions:
|
10
|
+
# * Chapters normally persist for the duration of a plot.
|
11
11
|
# * Players do not need to be introduced to a chapter.
|
12
|
-
# *
|
13
|
-
# * Using `make` to create an entity in a chapter adds it to the parent
|
14
|
-
# plot's entity list.
|
12
|
+
# * Chapters share their plot's entities, players, and rulebook.
|
15
13
|
#
|
16
14
|
# @example
|
17
15
|
# class MyChapter < Gamefic::Chapter
|
18
|
-
#
|
19
|
-
# @thing
|
16
|
+
# def thing
|
17
|
+
# @thing ||= make Gamefic::Entity, name: 'chapter thing'
|
20
18
|
# end
|
21
19
|
# end
|
22
20
|
#
|
@@ -25,64 +23,49 @@ module Gamefic
|
|
25
23
|
# end
|
26
24
|
#
|
27
25
|
# plot = MyPlot.new
|
28
|
-
# plot.entities
|
29
|
-
# plot.
|
26
|
+
# plot.entities #=> [<#Gamefic::Entity a chapter thing>]
|
27
|
+
# plot.thing # raises NoMethodError
|
28
|
+
# plot.chapters.first.thing #=> <#Gamefic::Entity a chapter thing>
|
30
29
|
#
|
31
|
-
class Chapter
|
32
|
-
extend Scriptable
|
33
|
-
|
34
|
-
include Scriptable::Actions
|
35
|
-
include Scriptable::Events
|
36
|
-
include Scriptable::Proxy
|
37
|
-
include Scriptable::Queries
|
38
|
-
include Scriptable::Scenes
|
30
|
+
class Chapter < Narrative
|
31
|
+
extend Scriptable::PlotProxies
|
39
32
|
|
40
33
|
# @return [Plot]
|
41
34
|
attr_reader :plot
|
42
35
|
|
43
36
|
# @param plot [Plot]
|
44
|
-
def initialize
|
37
|
+
def initialize(plot)
|
45
38
|
@plot = plot
|
46
|
-
|
47
|
-
|
48
|
-
def included_blocks
|
49
|
-
self.class.included_blocks - plot.included_blocks
|
50
|
-
end
|
51
|
-
|
52
|
-
def seed
|
53
|
-
included_blocks.select(&:seed?).each { |blk| Stage.run self, &blk.code }
|
39
|
+
# The plot is responsible for hydrating chapters
|
40
|
+
super(hydrate: false)
|
54
41
|
end
|
55
42
|
|
56
43
|
def script
|
57
44
|
included_blocks.select(&:script?).each { |blk| Stage.run self, &blk.code }
|
58
45
|
end
|
59
46
|
|
60
|
-
def
|
61
|
-
plot.
|
62
|
-
end
|
63
|
-
|
64
|
-
def make klass, **opts
|
65
|
-
plot.make klass, **opts
|
47
|
+
def included_blocks
|
48
|
+
self.class.included_blocks - plot.included_blocks
|
66
49
|
end
|
67
50
|
|
68
|
-
def
|
69
|
-
plot.
|
51
|
+
def rulebook
|
52
|
+
plot.rulebook
|
70
53
|
end
|
71
54
|
|
72
|
-
def
|
73
|
-
plot.
|
55
|
+
def entity_vault
|
56
|
+
plot.entity_vault
|
74
57
|
end
|
75
58
|
|
76
|
-
def
|
77
|
-
plot.
|
59
|
+
def player_vault
|
60
|
+
plot.player_vault
|
78
61
|
end
|
79
62
|
|
80
|
-
def
|
81
|
-
plot.
|
63
|
+
def subplots
|
64
|
+
plot.subplots
|
82
65
|
end
|
83
66
|
|
84
|
-
def
|
85
|
-
plot.
|
67
|
+
def branch(...)
|
68
|
+
plot.branch(...)
|
86
69
|
end
|
87
70
|
end
|
88
71
|
end
|
data/lib/gamefic/command.rb
CHANGED
@@ -10,11 +10,59 @@ module Gamefic
|
|
10
10
|
# @return [Array<Array<Entity>, Entity, String>]
|
11
11
|
attr_reader :arguments
|
12
12
|
|
13
|
+
# @return [Integer]
|
14
|
+
attr_reader :strictness
|
15
|
+
|
16
|
+
# @return [Integer]
|
17
|
+
attr_reader :precision
|
18
|
+
|
13
19
|
# @param verb [Symbol]
|
14
20
|
# @param arguments [Array<Array<Entity>, Entity, String>]
|
15
|
-
|
21
|
+
# @param strictness [Integer]
|
22
|
+
# @param precision [Integer]
|
23
|
+
#
|
24
|
+
# @todo Consider making strictness and precision required or providing
|
25
|
+
# another generator
|
26
|
+
def initialize verb, arguments, strictness = 0, precision = 0
|
16
27
|
@verb = verb
|
17
28
|
@arguments = arguments
|
29
|
+
@strictness = strictness
|
30
|
+
@precision = precision
|
31
|
+
end
|
32
|
+
|
33
|
+
def substantiality
|
34
|
+
@substantiality ||= arguments.that_are(Entity).length + (verb ? 1 : 0)
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect
|
38
|
+
"#<#{self.class} #{([verb] + arguments).map(&:inspect).join(', ')}>"
|
39
|
+
end
|
40
|
+
|
41
|
+
class << self
|
42
|
+
# Compose a command from input.
|
43
|
+
#
|
44
|
+
# @param actor [Actor]
|
45
|
+
# @param input [String]
|
46
|
+
# @return [Command]
|
47
|
+
def compose actor, input
|
48
|
+
expressions = Syntax.tokenize(input, actor.epic.syntaxes)
|
49
|
+
expressions.flat_map { |expression| expression_to_commands(actor, expression) }
|
50
|
+
.first || Command.new(nil, [])
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# @param actor [Actor]
|
56
|
+
# @param expression [Expression]
|
57
|
+
# @return [Array<Command>]
|
58
|
+
def expression_to_commands actor, expression
|
59
|
+
Gamefic.logger.info "Evaluating #{expression.inspect}"
|
60
|
+
actor.epic
|
61
|
+
.responses_for(expression.verb)
|
62
|
+
.map { |response| response.to_command(actor, expression) }
|
63
|
+
.compact
|
64
|
+
.sort_by.with_index { |result, idx| [-result.substantiality, -result.strictness, -result.precision, idx] }
|
65
|
+
end
|
18
66
|
end
|
19
67
|
end
|
20
68
|
end
|
data/lib/gamefic/dispatcher.rb
CHANGED
@@ -4,13 +4,15 @@ module Gamefic
|
|
4
4
|
# The action executor for character commands.
|
5
5
|
#
|
6
6
|
class Dispatcher
|
7
|
+
include Logging
|
8
|
+
|
7
9
|
# @param actor [Actor]
|
8
10
|
# @param command [Command]
|
9
11
|
def initialize actor, command
|
10
12
|
@actor = actor
|
11
13
|
@command = command
|
12
14
|
@executed = false
|
13
|
-
|
15
|
+
Gamefic.logger.info "Dispatching #{command.inspect}"
|
14
16
|
end
|
15
17
|
|
16
18
|
# Run the dispatcher.
|
@@ -23,11 +25,11 @@ module Gamefic
|
|
23
25
|
action = next_action
|
24
26
|
return unless action
|
25
27
|
|
26
|
-
|
28
|
+
actor.epic.rulebooks.flat_map { |rlbk| rlbk.run_before_actions action }
|
27
29
|
return if action.cancelled?
|
28
30
|
|
29
31
|
action.execute
|
30
|
-
|
32
|
+
actor.epic.rulebooks.flat_map { |rlbk| rlbk.run_after_actions action }
|
31
33
|
action
|
32
34
|
end
|
33
35
|
|
@@ -46,9 +48,9 @@ module Gamefic
|
|
46
48
|
# @param input [String]
|
47
49
|
# @return [Dispatcher]
|
48
50
|
def self.dispatch actor, input
|
49
|
-
expressions = Syntax.tokenize(input, actor.epic.syntaxes)
|
50
|
-
|
51
|
-
new(actor,
|
51
|
+
# expressions = Syntax.tokenize(input, actor.epic.syntaxes)
|
52
|
+
# new(actor, Command.compose(actor, expressions))
|
53
|
+
new(actor, Command.compose(actor, input))
|
52
54
|
end
|
53
55
|
|
54
56
|
# @param actor [Active]
|
@@ -82,31 +84,6 @@ module Gamefic
|
|
82
84
|
|
83
85
|
return Action.new(actor, @command.arguments, response) if response.accept?(actor, @command)
|
84
86
|
end
|
85
|
-
finalize
|
86
|
-
end
|
87
|
-
|
88
|
-
# @return [void]
|
89
|
-
def run_before_action_hooks action
|
90
|
-
actor.epic.rulebooks.flat_map { |rlbk| rlbk.run_before_actions action }
|
91
|
-
end
|
92
|
-
|
93
|
-
# @return [void]
|
94
|
-
def run_after_action_hooks action
|
95
|
-
actor.epic.rulebooks.flat_map { |rlbk| rlbk.run_after_actions action }
|
96
|
-
end
|
97
|
-
|
98
|
-
# If the dispatcher proceeds through all possible responses, it can fall
|
99
|
-
# back to a nil response as a catchall for commands that could not be
|
100
|
-
# completed.
|
101
|
-
#
|
102
|
-
# @return [Action, nil]
|
103
|
-
def finalize
|
104
|
-
return nil if @finalized
|
105
|
-
|
106
|
-
@finalized = true
|
107
|
-
@command = Command.new(nil, ["#{command.verb} #{command.arguments.join(' ').strip}"])
|
108
|
-
@responses = actor.epic.responses_for(nil)
|
109
|
-
next_action
|
110
87
|
end
|
111
88
|
end
|
112
89
|
end
|
data/lib/gamefic/entity.rb
CHANGED
@@ -55,6 +55,32 @@ module Gamefic
|
|
55
55
|
"#<#{self.class} #{name}>"
|
56
56
|
end
|
57
57
|
|
58
|
+
# Move this entity to its parent entity.
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# room = Gamefic::Entity.new(name: 'room')
|
62
|
+
# person = Gamefic::Entity.new(name: 'person', parent: room)
|
63
|
+
# thing = Gamefic::Entity.new(name: 'thing', parent: person)
|
64
|
+
#
|
65
|
+
# thing.parent #=> person
|
66
|
+
# thing.leave
|
67
|
+
# thing.parent #=> room
|
68
|
+
#
|
69
|
+
# @return [void]
|
70
|
+
def leave
|
71
|
+
self.parent = parent&.parent
|
72
|
+
end
|
73
|
+
|
74
|
+
# Tell a message to all of this entity's accessible descendants.
|
75
|
+
#
|
76
|
+
# @param message [String]
|
77
|
+
# @return [void]
|
78
|
+
def broadcast message
|
79
|
+
Query::Scoped.new(Scope::Descendants).select(self)
|
80
|
+
.that_are(Active, proc(&:acting?))
|
81
|
+
.each { |actor| actor.tell message }
|
82
|
+
end
|
83
|
+
|
58
84
|
class << self
|
59
85
|
# Set or update the default attributes for new instances.
|
60
86
|
#
|
data/lib/gamefic/expression.rb
CHANGED
data/lib/gamefic/narrative.rb
CHANGED
@@ -14,16 +14,18 @@ module Gamefic
|
|
14
14
|
include Scriptable::Actions
|
15
15
|
include Scriptable::Entities
|
16
16
|
include Scriptable::Events
|
17
|
-
include Scriptable::
|
17
|
+
include Scriptable::Proxies
|
18
18
|
include Scriptable::Queries
|
19
19
|
include Scriptable::Scenes
|
20
20
|
|
21
21
|
attr_reader :rulebook
|
22
22
|
|
23
|
-
def initialize
|
23
|
+
def initialize(hydrate: true)
|
24
|
+
return unless hydrate
|
25
|
+
|
24
26
|
seed
|
25
27
|
script
|
26
|
-
|
28
|
+
post_script
|
27
29
|
end
|
28
30
|
|
29
31
|
def seed
|
@@ -35,15 +37,17 @@ module Gamefic
|
|
35
37
|
included_blocks.select(&:script?).each { |blk| Stage.run self, &blk.code }
|
36
38
|
end
|
37
39
|
|
40
|
+
# @return [Array<Module>]
|
38
41
|
def included_blocks
|
39
42
|
self.class.included_blocks
|
40
43
|
end
|
41
44
|
|
42
|
-
def
|
45
|
+
def post_script
|
43
46
|
entity_vault.lock
|
44
47
|
rulebook.freeze
|
45
48
|
end
|
46
49
|
|
50
|
+
# @return [Array<Symbol>]
|
47
51
|
def scenes
|
48
52
|
rulebook.scenes.names
|
49
53
|
end
|
@@ -111,7 +115,7 @@ module Gamefic
|
|
111
115
|
|
112
116
|
def hydrate
|
113
117
|
script
|
114
|
-
|
118
|
+
post_script
|
115
119
|
end
|
116
120
|
|
117
121
|
def self.inherited klass
|
data/lib/gamefic/node.rb
CHANGED
@@ -88,11 +88,9 @@ module Gamefic
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def validate_parent(node)
|
91
|
-
raise NodeError,
|
92
|
-
|
93
|
-
raise NodeError, "
|
94
|
-
|
95
|
-
raise NodeError, 'Node cannot be a child of a descendant' if flatten.include?(node)
|
91
|
+
raise NodeError, "Parent of #{inspect} must be a Node, received #{node.inspect}" unless node.is_a?(Node) || node.nil?
|
92
|
+
raise NodeError, "#{inspect} cannot be its own parent" if node == self
|
93
|
+
raise NodeError, "#{inspect} cannot be a child of descendant #{node.inspect}" if flatten.include?(node)
|
96
94
|
end
|
97
95
|
end
|
98
96
|
end
|
data/lib/gamefic/plot.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
class Proxy
|
5
|
+
class Base
|
6
|
+
attr_reader :args
|
7
|
+
|
8
|
+
def initialize *args, raise: false
|
9
|
+
@args = args
|
10
|
+
@raise = raise
|
11
|
+
end
|
12
|
+
|
13
|
+
def raise?
|
14
|
+
@raise
|
15
|
+
end
|
16
|
+
|
17
|
+
def fetch narrative
|
18
|
+
result = select(narrative)
|
19
|
+
return result if result
|
20
|
+
raise "#{self.class} failed for #{args.inspect}" if raise?
|
21
|
+
end
|
22
|
+
|
23
|
+
def select narrative
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
# @todo Turn this into a module after the old proxies are completely deprecated
|
5
|
+
#
|
6
|
+
class Proxy
|
7
|
+
require 'gamefic/proxy/base'
|
8
|
+
require 'gamefic/proxy/config'
|
9
|
+
require 'gamefic/proxy/pick'
|
10
|
+
require 'gamefic/proxy/plot_pick'
|
11
|
+
|
12
|
+
TYPES = %i[attr ivar pick pick! plot_pick plot_pick! config].freeze
|
13
|
+
|
14
|
+
# @return [Symbol]
|
15
|
+
attr_reader :type
|
16
|
+
|
17
|
+
# @return [Symbol, Array<Symbol>, String, Integer]
|
18
|
+
attr_reader :key
|
19
|
+
|
20
|
+
# @param type [Symbol]
|
21
|
+
# @param key [Symbol, String, Array]
|
22
|
+
def initialize type, key
|
23
|
+
Gamefic.logger.debug "Using deprecated #{type} proxy"
|
24
|
+
@type = type
|
25
|
+
validate_type
|
26
|
+
@key = type == :config ? [key].compact : key
|
27
|
+
end
|
28
|
+
|
29
|
+
def fetch narrative
|
30
|
+
send(type, narrative) ||
|
31
|
+
raise(ArgumentError, "Unable to fetch entity from proxy agent symbol `#{key}`")
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](key)
|
35
|
+
raise ArgumentError, 'Invalid []' unless type == :config
|
36
|
+
|
37
|
+
@key.push key
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def attr narrative
|
44
|
+
Stage.run(narrative, [key].flatten) { |keys| keys.inject(self) { |obj, key| obj.send key } }
|
45
|
+
rescue NoMethodError
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def ivar narrative
|
50
|
+
narrative.instance_variable_get key
|
51
|
+
end
|
52
|
+
|
53
|
+
def pick narrative
|
54
|
+
narrative.pick *key
|
55
|
+
end
|
56
|
+
|
57
|
+
def pick! narrative
|
58
|
+
narrative.pick! *key
|
59
|
+
end
|
60
|
+
|
61
|
+
def plot_pick narrative
|
62
|
+
narrative.plot.pick *key
|
63
|
+
end
|
64
|
+
|
65
|
+
def plot_pick! narrative
|
66
|
+
narrative.plot.pick! *key
|
67
|
+
end
|
68
|
+
|
69
|
+
def config narrative
|
70
|
+
key.inject(narrative.config) { |hash, key| hash[key] }
|
71
|
+
end
|
72
|
+
|
73
|
+
def validate_type
|
74
|
+
return if TYPES.include?(type)
|
75
|
+
|
76
|
+
raise ArgumentError, "Invalid proxy type `#{type}` (must be #{TYPES.join_or})"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|