gamefic 3.0.0 → 3.2.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 +14 -1
- data/lib/gamefic/action.rb +9 -1
- data/lib/gamefic/active/epic.rb +8 -3
- data/lib/gamefic/active/messaging.rb +2 -0
- data/lib/gamefic/active/take.rb +3 -5
- data/lib/gamefic/active.rb +32 -13
- data/lib/gamefic/command.rb +4 -15
- data/lib/gamefic/composer.rb +70 -0
- data/lib/gamefic/dispatcher.rb +47 -40
- data/lib/gamefic/entity.rb +3 -5
- data/lib/gamefic/expression.rb +31 -0
- data/lib/gamefic/plot.rb +1 -1
- data/lib/gamefic/props/default.rb +10 -4
- data/lib/gamefic/props/output.rb +107 -0
- data/lib/gamefic/props/pause.rb +2 -0
- data/lib/gamefic/props.rb +1 -0
- data/lib/gamefic/query/base.rb +24 -0
- data/lib/gamefic/query/general.rb +4 -0
- data/lib/gamefic/query/scoped.rb +5 -0
- data/lib/gamefic/query/text.rb +16 -5
- data/lib/gamefic/response.rb +19 -25
- data/lib/gamefic/rulebook/events.rb +7 -7
- data/lib/gamefic/rulebook.rb +2 -2
- data/lib/gamefic/scanner.rb +54 -25
- data/lib/gamefic/scene/default.rb +3 -3
- data/lib/gamefic/scene/multiple_choice.rb +1 -1
- data/lib/gamefic/scriptable/entities.rb +8 -5
- data/lib/gamefic/scriptable/proxy.rb +13 -0
- data/lib/gamefic/scriptable/queries.rb +2 -2
- data/lib/gamefic/scriptable/scenes.rb +6 -6
- data/lib/gamefic/snapshot.rb +9 -1
- data/lib/gamefic/syntax.rb +4 -4
- data/lib/gamefic/vault.rb +2 -0
- data/lib/gamefic/version.rb +1 -1
- data/lib/gamefic.rb +2 -0
- metadata +5 -2
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
module Props
|
5
|
+
# A container for output sent to players with a hash interface for custom
|
6
|
+
# data.
|
7
|
+
#
|
8
|
+
class Output
|
9
|
+
READER_METHODS = %i[messages options queue scene prompt last_prompt last_input].freeze
|
10
|
+
WRITER_METHODS = %i[messages= prompt= last_prompt= last_input=].freeze
|
11
|
+
|
12
|
+
attr_reader :raw_data
|
13
|
+
|
14
|
+
def initialize **data
|
15
|
+
@raw_data = {
|
16
|
+
messages: '',
|
17
|
+
options: [],
|
18
|
+
queue: [],
|
19
|
+
scene: {},
|
20
|
+
prompt: ''
|
21
|
+
}
|
22
|
+
merge! data
|
23
|
+
end
|
24
|
+
|
25
|
+
# @!attribute [rw] messages
|
26
|
+
# A text message to be displayed at the start of a scene.
|
27
|
+
#
|
28
|
+
# @return [String]
|
29
|
+
|
30
|
+
# @!attribute [rw] options
|
31
|
+
# An array of options to be presented to the player, e.g., in a
|
32
|
+
# MultipleChoice scene.
|
33
|
+
#
|
34
|
+
# @return [Array<String>]
|
35
|
+
|
36
|
+
# @!attribute [rw] queue
|
37
|
+
# An array of commands waiting to be executed.
|
38
|
+
#
|
39
|
+
# @return [Array<String>]
|
40
|
+
|
41
|
+
# @!attribute [rw] scene
|
42
|
+
# A hash containing the scene's :name and :type.
|
43
|
+
#
|
44
|
+
# @return [Hash]
|
45
|
+
|
46
|
+
# @!attribute [rw] [prompt]
|
47
|
+
# The input prompt to be displayed to the player.
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
|
51
|
+
# @!attribute [rw] last_input
|
52
|
+
# The input received from the player in the previous scene.
|
53
|
+
#
|
54
|
+
# @return [String, nil]
|
55
|
+
|
56
|
+
# @!attribute [rw] last_prompt
|
57
|
+
# The input prompt from the previous scene.
|
58
|
+
#
|
59
|
+
# @return [String, nil]
|
60
|
+
|
61
|
+
# @param key [Symbol]
|
62
|
+
def [] key
|
63
|
+
raw_data[key]
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param key [Symbol]
|
67
|
+
# @param value [Object]
|
68
|
+
def []= key, value
|
69
|
+
raw_data[key] = value
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [Hash]
|
73
|
+
def to_hash
|
74
|
+
raw_data.dup
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_json _ = nil
|
78
|
+
raw_data.to_json
|
79
|
+
end
|
80
|
+
|
81
|
+
def merge! data
|
82
|
+
data.each { |key, val| self[key] = val }
|
83
|
+
end
|
84
|
+
|
85
|
+
def replace data
|
86
|
+
raw_data.replace data
|
87
|
+
end
|
88
|
+
|
89
|
+
def freeze
|
90
|
+
raw_data.freeze
|
91
|
+
super
|
92
|
+
end
|
93
|
+
|
94
|
+
def method_missing method, *args
|
95
|
+
return raw_data[method] if READER_METHODS.include?(method)
|
96
|
+
|
97
|
+
return raw_data[method.to_s[0..-2].to_sym] = args.first if WRITER_METHODS.include?(method)
|
98
|
+
|
99
|
+
super
|
100
|
+
end
|
101
|
+
|
102
|
+
def respond_to_missing?(method, _with_private = false)
|
103
|
+
READER_METHODS.include?(method) || WRITER_METHODS.include?(method)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/gamefic/props/pause.rb
CHANGED
data/lib/gamefic/props.rb
CHANGED
data/lib/gamefic/query/base.rb
CHANGED
@@ -24,6 +24,12 @@ module Gamefic
|
|
24
24
|
@ambiguous = ambiguous
|
25
25
|
end
|
26
26
|
|
27
|
+
# @deprecated Queries should only be used to select entities that are
|
28
|
+
# eligible to be response arguments. After a text command is tokenized
|
29
|
+
# into an array of expressions, the composer builds the command that
|
30
|
+
# the dispatcher uses to execute actions. The #accept? method verifies
|
31
|
+
# that the command's arguments match the response's queries.
|
32
|
+
#
|
27
33
|
# @param subject [Gamefic::Entity]
|
28
34
|
# @param token [String]
|
29
35
|
# @return [Result]
|
@@ -31,6 +37,24 @@ module Gamefic
|
|
31
37
|
raise "#query not implemented for #{self.class}"
|
32
38
|
end
|
33
39
|
|
40
|
+
# Get an array of entities that match the query from the context of the
|
41
|
+
# subject.
|
42
|
+
#
|
43
|
+
# @param subject [Entity]
|
44
|
+
# @return [Array<Entity>]
|
45
|
+
def select subject
|
46
|
+
raise "#select not implemented for #{self.class}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def accept?(subject, object)
|
50
|
+
available = select(subject)
|
51
|
+
if ambiguous?
|
52
|
+
object & available == object
|
53
|
+
else
|
54
|
+
available.include?(object)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
34
58
|
# @return [Integer]
|
35
59
|
def precision
|
36
60
|
@precision ||= calculate_precision
|
@@ -19,6 +19,10 @@ module Gamefic
|
|
19
19
|
@entities = entities
|
20
20
|
end
|
21
21
|
|
22
|
+
def select subject
|
23
|
+
available_entities(subject).that_are(*@arguments)
|
24
|
+
end
|
25
|
+
|
22
26
|
def query subject, token
|
23
27
|
filtered = available_entities(subject).that_are(*@arguments)
|
24
28
|
return Result.new(token, nil) if filtered.include?(token)
|
data/lib/gamefic/query/scoped.rb
CHANGED
data/lib/gamefic/query/text.rb
CHANGED
@@ -5,12 +5,17 @@ module Gamefic
|
|
5
5
|
# A special query that handles text instead of entities.
|
6
6
|
#
|
7
7
|
class Text
|
8
|
-
# @param argument [String, Regexp
|
9
|
-
def initialize argument =
|
8
|
+
# @param argument [String, Regexp]
|
9
|
+
def initialize argument = /.*/
|
10
10
|
@argument = argument
|
11
11
|
validate
|
12
12
|
end
|
13
13
|
|
14
|
+
# @return [String, Regexp]
|
15
|
+
def select(_subject)
|
16
|
+
@argument
|
17
|
+
end
|
18
|
+
|
14
19
|
def query _subject, token
|
15
20
|
if match? token
|
16
21
|
Result.new(token, '')
|
@@ -23,11 +28,17 @@ module Gamefic
|
|
23
28
|
0
|
24
29
|
end
|
25
30
|
|
31
|
+
def accept? _subject, argument
|
32
|
+
match? argument
|
33
|
+
end
|
34
|
+
|
35
|
+
def ambiguous?
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
26
39
|
private
|
27
40
|
|
28
41
|
def match? token
|
29
|
-
return true if @argument.nil?
|
30
|
-
|
31
42
|
case @argument
|
32
43
|
when Regexp
|
33
44
|
token =~ @argument
|
@@ -37,7 +48,7 @@ module Gamefic
|
|
37
48
|
end
|
38
49
|
|
39
50
|
def validate
|
40
|
-
return if @argument.
|
51
|
+
return if @argument.is_a?(String) || @argument.is_a?(Regexp)
|
41
52
|
|
42
53
|
raise ArgumentError, 'Invalid text query argument'
|
43
54
|
end
|
data/lib/gamefic/response.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Gamefic
|
4
4
|
# A proc to be executed in response to a command that matches its verb and
|
@@ -11,17 +11,17 @@ module Gamefic
|
|
11
11
|
# @return [Array<Query::Base>]
|
12
12
|
attr_reader :queries
|
13
13
|
|
14
|
-
# @return [
|
15
|
-
|
14
|
+
# @return [Narrative]
|
15
|
+
attr_reader :narrative
|
16
16
|
|
17
17
|
# @param verb [Symbol]
|
18
|
-
# @param
|
18
|
+
# @param narrative [Narrative]
|
19
19
|
# @param queries [Array<Query::Base>]
|
20
20
|
# @param meta [Boolean]
|
21
21
|
# @param block [Proc]
|
22
|
-
def initialize verb,
|
22
|
+
def initialize verb, narrative, *queries, meta: false, &block
|
23
23
|
@verb = verb
|
24
|
-
@
|
24
|
+
@narrative = narrative
|
25
25
|
@queries = map_queryable_objects(queries)
|
26
26
|
@meta = meta
|
27
27
|
@block = block
|
@@ -48,35 +48,29 @@ module Gamefic
|
|
48
48
|
#
|
49
49
|
# @param actor [Entity]
|
50
50
|
# @param command [Command]
|
51
|
-
# @param with_hooks [Boolean]
|
52
51
|
# @return [Action, nil]
|
53
52
|
def attempt actor, command
|
54
|
-
return nil
|
53
|
+
return nil unless accept?(actor, command)
|
55
54
|
|
56
|
-
|
57
|
-
|
58
|
-
remainder = ''
|
59
|
-
|
60
|
-
queries.each do |qd|
|
61
|
-
token = tokens.shift
|
62
|
-
txt = "#{remainder} #{token}".strip
|
63
|
-
return nil if txt.empty?
|
64
|
-
|
65
|
-
response = qd.query(actor, txt)
|
66
|
-
return nil if response.match.nil?
|
55
|
+
Action.new(actor, command.arguments, self)
|
56
|
+
end
|
67
57
|
|
68
|
-
|
58
|
+
# True if the Response can be executed for the given actor and command.
|
59
|
+
#
|
60
|
+
# @param actor [Active]
|
61
|
+
# @param command [Command]
|
62
|
+
def accept? actor, command
|
63
|
+
return false if command.verb != verb || command.arguments.length != queries.length
|
69
64
|
|
70
|
-
|
65
|
+
queries.each_with_index do |query, idx|
|
66
|
+
return false unless query.accept?(actor, command.arguments[idx])
|
71
67
|
end
|
72
68
|
|
73
|
-
|
74
|
-
|
75
|
-
Action.new(actor, result, self)
|
69
|
+
true
|
76
70
|
end
|
77
71
|
|
78
72
|
def execute *args
|
79
|
-
Stage.run(
|
73
|
+
Stage.run(narrative, *args, &@block)
|
80
74
|
end
|
81
75
|
|
82
76
|
def precision
|
@@ -34,16 +34,16 @@ module Gamefic
|
|
34
34
|
self
|
35
35
|
end
|
36
36
|
|
37
|
-
# @return [
|
37
|
+
# @return [void]
|
38
38
|
def on_ready &block
|
39
39
|
@ready_blocks.push block
|
40
40
|
end
|
41
41
|
|
42
42
|
# @yieldparam [Actor]
|
43
|
-
# @return [
|
43
|
+
# @return [void]
|
44
44
|
def on_player_ready &block
|
45
45
|
@ready_blocks.push(proc do
|
46
|
-
players.each { |plyr|
|
46
|
+
players.each { |plyr| instance_exec plyr, &block }
|
47
47
|
end)
|
48
48
|
end
|
49
49
|
|
@@ -53,24 +53,24 @@ module Gamefic
|
|
53
53
|
|
54
54
|
def on_player_update &block
|
55
55
|
@update_blocks.push(proc do
|
56
|
-
players.each { |plyr|
|
56
|
+
players.each { |plyr| instance_exec plyr, &block }
|
57
57
|
end)
|
58
58
|
end
|
59
59
|
|
60
|
-
# @return [
|
60
|
+
# @return [void]
|
61
61
|
def on_conclude &block
|
62
62
|
@conclude_blocks.push block
|
63
63
|
end
|
64
64
|
|
65
65
|
# @yieldparam [Actor]
|
66
|
-
# @return [
|
66
|
+
# @return [void]
|
67
67
|
def on_player_conclude &block
|
68
68
|
@player_conclude_blocks.push block
|
69
69
|
end
|
70
70
|
|
71
71
|
# @yieldparam [Actor]
|
72
72
|
# @yieldparam [Hash]
|
73
|
-
# @return [
|
73
|
+
# @return [void]
|
74
74
|
def on_player_output &block
|
75
75
|
@player_output_blocks.push block
|
76
76
|
end
|
data/lib/gamefic/rulebook.rb
CHANGED
@@ -116,11 +116,11 @@ module Gamefic
|
|
116
116
|
end
|
117
117
|
|
118
118
|
def run_player_conclude_blocks player
|
119
|
-
events.player_conclude_blocks.each { |blk| Stage.run(narrative
|
119
|
+
events.player_conclude_blocks.each { |blk| Stage.run(narrative, player, &blk) }
|
120
120
|
end
|
121
121
|
|
122
122
|
def run_player_output_blocks player, output
|
123
|
-
events.player_output_blocks.each { |blk| Stage.run(narrative
|
123
|
+
events.player_output_blocks.each { |blk| Stage.run(narrative, player, output, &blk) }
|
124
124
|
end
|
125
125
|
|
126
126
|
def empty?
|
data/lib/gamefic/scanner.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Gamefic
|
2
4
|
# A module for matching objects to tokens.
|
3
5
|
#
|
@@ -11,7 +13,7 @@ module Gamefic
|
|
11
13
|
class Result
|
12
14
|
# The scanned objects
|
13
15
|
#
|
14
|
-
# @return [Array<
|
16
|
+
# @return [Array<Entity>, String, Regexp]
|
15
17
|
attr_reader :scanned
|
16
18
|
|
17
19
|
# The scanned token
|
@@ -21,7 +23,7 @@ module Gamefic
|
|
21
23
|
|
22
24
|
# The matched objects
|
23
25
|
#
|
24
|
-
# @return [Array<
|
26
|
+
# @return [Array<Entity>, String]
|
25
27
|
attr_reader :matched
|
26
28
|
|
27
29
|
# The remaining (unmatched) portion of the token
|
@@ -39,36 +41,53 @@ module Gamefic
|
|
39
41
|
|
40
42
|
# Scan entities against a token.
|
41
43
|
#
|
42
|
-
# @param
|
44
|
+
# @param selection [Array<Entity>, String, Regexp]
|
43
45
|
# @param token [String]
|
44
46
|
# @return [Result]
|
45
|
-
def self.scan
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
47
|
+
def self.scan selection, token
|
48
|
+
strict_result = strict(selection, token)
|
49
|
+
strict_result.matched.empty? ? fuzzy(selection, token) : strict_result
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param selection [Array<Entity>, String, Regexp]
|
53
|
+
# @param token [String]
|
54
|
+
# @return [Result]
|
55
|
+
def self.strict selection, token
|
56
|
+
return Result.new(selection, token, '', token) unless selection.is_a?(Array)
|
57
|
+
|
58
|
+
scan_strict_or_fuzzy(selection, token, :select_strict)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param selection [Array<Entity>, String, Regexp]
|
62
|
+
# @param token [String]
|
63
|
+
# @return [Result]
|
64
|
+
def self.fuzzy selection, token
|
65
|
+
return scan_text(selection, token) unless selection.is_a?(Array)
|
66
|
+
|
67
|
+
scan_strict_or_fuzzy(selection, token, :select_fuzzy)
|
67
68
|
end
|
68
69
|
|
69
70
|
class << self
|
70
71
|
private
|
71
72
|
|
73
|
+
def scan_strict_or_fuzzy objects, token, method
|
74
|
+
if nested?(token) && objects.all?(&:children)
|
75
|
+
denest(objects, token)
|
76
|
+
else
|
77
|
+
words = token.keywords
|
78
|
+
available = objects.clone
|
79
|
+
filtered = []
|
80
|
+
words.each_with_index do |word, idx|
|
81
|
+
tested = send(method, available, word)
|
82
|
+
return Result.new(objects, token, filtered, words[idx..].join(' ')) if tested.empty?
|
83
|
+
|
84
|
+
filtered = tested
|
85
|
+
available = filtered
|
86
|
+
end
|
87
|
+
Result.new(objects, token, filtered, '')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
72
91
|
def select_strict available, word
|
73
92
|
available.select { |obj| obj.keywords.include?(word) }
|
74
93
|
end
|
@@ -81,6 +100,16 @@ module Gamefic
|
|
81
100
|
token.match(NEST_REGEXP)
|
82
101
|
end
|
83
102
|
|
103
|
+
def scan_text selection, token
|
104
|
+
case selection
|
105
|
+
when Regexp
|
106
|
+
return Result.new(selection, token, token, '') if token =~ selection
|
107
|
+
else
|
108
|
+
return Result.new(selection, token, selection, token[selection.length..]) if token.start_with?(selection)
|
109
|
+
end
|
110
|
+
Result.new(selection, token, '', token)
|
111
|
+
end
|
112
|
+
|
84
113
|
def denest(objects, token)
|
85
114
|
parts = token.split(NEST_REGEXP)
|
86
115
|
current = parts.pop
|
@@ -30,7 +30,7 @@ module Gamefic
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def new_props(**context)
|
33
|
-
self.class.props_class.new(
|
33
|
+
self.class.props_class.new(self, **context)
|
34
34
|
end
|
35
35
|
|
36
36
|
def on_start &block
|
@@ -45,8 +45,8 @@ module Gamefic
|
|
45
45
|
# @param props [Props::Default]
|
46
46
|
# @return [void]
|
47
47
|
def start actor, props
|
48
|
-
|
49
|
-
|
48
|
+
props.output[:scene] = to_hash
|
49
|
+
props.output[:prompt] = props.prompt
|
50
50
|
end
|
51
51
|
|
52
52
|
# @param actor [Gamefic::Actor]
|
@@ -55,7 +55,10 @@ module Gamefic
|
|
55
55
|
# @param description [String]
|
56
56
|
# @return [Gamefic::Entity, nil]
|
57
57
|
def pick description
|
58
|
-
|
58
|
+
result = Scanner.scan(entities, description)
|
59
|
+
return nil unless result.matched.one?
|
60
|
+
|
61
|
+
result.matched.first
|
59
62
|
end
|
60
63
|
|
61
64
|
# Same as #pick, but raise an error if a unique match could not be found.
|
@@ -63,13 +66,13 @@ module Gamefic
|
|
63
66
|
# @param description [String]
|
64
67
|
# @return [Gamefic::Entity, nil]
|
65
68
|
def pick! description
|
66
|
-
|
69
|
+
result = Scanner.scan(entities, description)
|
67
70
|
|
68
|
-
raise "no entity matching '#{description}'" if
|
71
|
+
raise "no entity matching '#{description}'" if result.matched.empty?
|
69
72
|
|
70
|
-
raise "multiple entities matching '#{description}': #{
|
73
|
+
raise "multiple entities matching '#{description}': #{result.matched.join_and}" unless result.matched.one?
|
71
74
|
|
72
|
-
|
75
|
+
result.matched.first
|
73
76
|
end
|
74
77
|
end
|
75
78
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Gamefic
|
2
4
|
module Scriptable
|
3
5
|
# Functions that provide proxies for referencing a narrative's entities
|
@@ -15,6 +17,15 @@ module Gamefic
|
|
15
17
|
end
|
16
18
|
|
17
19
|
def fetch container
|
20
|
+
result = safe_fetch(container)
|
21
|
+
raise ArgumentError, "Unable to fetch entity from proxy agent symbol `#{symbol}`" unless result
|
22
|
+
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def safe_fetch container
|
18
29
|
if symbol.to_s =~ /^\d+$/
|
19
30
|
Stage.run(container, symbol) { |sym| entities[sym] }
|
20
31
|
elsif symbol.to_s.start_with?('@')
|
@@ -22,6 +33,8 @@ module Gamefic
|
|
22
33
|
else
|
23
34
|
Stage.run(container, symbol) { |sym| send(sym) }
|
24
35
|
end
|
36
|
+
rescue NoMethodError
|
37
|
+
nil
|
25
38
|
end
|
26
39
|
end
|
27
40
|
|
@@ -63,9 +63,9 @@ module Gamefic
|
|
63
63
|
# any text it finds in the command. A successful query returns the
|
64
64
|
# corresponding text instead of an entity.
|
65
65
|
#
|
66
|
-
# @param arg [String,
|
66
|
+
# @param arg [String, Regexp] The string or regular expression to match
|
67
67
|
# @return [Query::Text]
|
68
|
-
def plaintext arg =
|
68
|
+
def plaintext arg = /.*/
|
69
69
|
Query::Text.new arg
|
70
70
|
end
|
71
71
|
end
|
@@ -32,8 +32,8 @@ module Gamefic
|
|
32
32
|
# @param block [Proc]
|
33
33
|
# @yieldparam [Scene]
|
34
34
|
# @return [Symbol]
|
35
|
-
def block name, klass = Scene::Default, on_start: nil, on_finish: nil, &
|
36
|
-
rulebook.scenes.add klass.new(name, rulebook.narrative, on_start: on_start, on_finish: on_finish, &
|
35
|
+
def block name, klass = Scene::Default, on_start: nil, on_finish: nil, &blk
|
36
|
+
rulebook.scenes.add klass.new(name, rulebook.narrative, on_start: on_start, on_finish: on_finish, &blk)
|
37
37
|
name
|
38
38
|
end
|
39
39
|
alias scene block
|
@@ -77,14 +77,14 @@ module Gamefic
|
|
77
77
|
# @yieldparam [Actor]
|
78
78
|
# @yieldparam [Props::MultipleChoice]
|
79
79
|
# @return [Symbol]
|
80
|
-
def multiple_choice name, choices = [], prompt = 'What is your choice?', &
|
80
|
+
def multiple_choice name, choices = [], prompt = 'What is your choice?', &blk
|
81
81
|
block name,
|
82
82
|
Scene::MultipleChoice,
|
83
83
|
on_start: proc { |_actor, props|
|
84
84
|
props.prompt = prompt
|
85
85
|
props.options.concat choices
|
86
86
|
},
|
87
|
-
on_finish:
|
87
|
+
on_finish: blk
|
88
88
|
end
|
89
89
|
|
90
90
|
# Create a yes-or-no scene.
|
@@ -105,13 +105,13 @@ module Gamefic
|
|
105
105
|
# @yieldparam [Actor]
|
106
106
|
# @yieldparam [Props::YesOrNo]
|
107
107
|
# @return [Symbol]
|
108
|
-
def yes_or_no name, prompt = 'Answer:', &
|
108
|
+
def yes_or_no name, prompt = 'Answer:', &blk
|
109
109
|
block name,
|
110
110
|
Scene::YesOrNo,
|
111
111
|
on_start: proc { |_actor, props|
|
112
112
|
props.prompt = prompt
|
113
113
|
},
|
114
|
-
on_finish:
|
114
|
+
on_finish: blk
|
115
115
|
end
|
116
116
|
|
117
117
|
# Create a scene that pauses the game.
|
data/lib/gamefic/snapshot.rb
CHANGED
@@ -27,10 +27,18 @@ module Gamefic
|
|
27
27
|
Marshal.load(binary).tap do |plot|
|
28
28
|
plot.hydrate
|
29
29
|
# @todo Opal marshal dumps are not idempotent
|
30
|
-
next if RUBY_ENGINE == 'opal' ||
|
30
|
+
next if RUBY_ENGINE == 'opal' || match?(plot, snapshot)
|
31
31
|
|
32
32
|
Logging.logger.warn "Scripts modified #{plot.class} data. Snapshot may not have restored properly"
|
33
33
|
end
|
34
34
|
end
|
35
|
+
|
36
|
+
# True if the plot's state matches the snapshot.
|
37
|
+
#
|
38
|
+
# @param plot [Plot]
|
39
|
+
# @param snapshot [String]
|
40
|
+
def self.match?(plot, snapshot)
|
41
|
+
save(plot) == snapshot
|
42
|
+
end
|
35
43
|
end
|
36
44
|
end
|