gamefic 4.1.1 → 4.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/.github/workflows/rspec.yml +1 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +9 -0
- data/Rakefile +2 -1
- data/gamefic.gemspec +1 -0
- data/lib/gamefic/active/narratives.rb +2 -0
- data/lib/gamefic/binding.rb +2 -0
- data/lib/gamefic/chapter.rb +1 -0
- data/lib/gamefic/describable.rb +2 -2
- data/lib/gamefic/narrative.rb +2 -1
- data/lib/gamefic/narrator.rb +4 -4
- data/lib/gamefic/order.rb +3 -3
- data/lib/gamefic/props/multiple_choice.rb +32 -7
- data/lib/gamefic/props/multiple_partial.rb +1 -1
- data/lib/gamefic/query/base.rb +1 -1
- data/lib/gamefic/scanner.rb +1 -1
- data/lib/gamefic/scene/base.rb +2 -2
- data/lib/gamefic/scriptable/scenes.rb +11 -6
- data/lib/gamefic/scriptable/seeds.rb +1 -1
- data/lib/gamefic/scriptable/syntaxes.rb +9 -2
- data/lib/gamefic/scripting/hooks.rb +2 -0
- data/lib/gamefic/scripting/responses.rb +2 -0
- data/lib/gamefic/scripting/scenes.rb +2 -0
- data/lib/gamefic/scripting/seeds.rb +3 -1
- data/lib/gamefic/scripting/syntaxes.rb +2 -0
- data/lib/gamefic/scripting.rb +1 -1
- data/lib/gamefic/syntax.rb +46 -3
- data/lib/gamefic/version.rb +1 -1
- metadata +17 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 923f92309b7fcac0e2ed6aa4e691e2feea39521f15f19c7b7a560ddb7583b365
|
|
4
|
+
data.tar.gz: 858516d0d245300605612517d833e858b84b8a40473f6e9bf7d8670c5589cd77
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3d3aa37745380138eb41ed6bee89231e96b385a1c5b8fffc42eb90bb23e9fdcb5d9ea0466fe7f5e1b2d9d459609ec002c3f80d6d6ab324658cbdea4fce1aee2
|
|
7
|
+
data.tar.gz: 16a1c623c7fc6173f306f43ca080b668b72fdef3700469a6b421abcf88df04c405137f1458727754fce9706107502d7bc0f1dd4cf6c91115784e14de38a6af5e
|
data/.github/workflows/rspec.yml
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## 4.2.0 - November 27, 2025
|
|
2
|
+
- Syntaxes support variable (piped) words
|
|
3
|
+
- Props::MultipleChoice#index_of
|
|
4
|
+
|
|
5
|
+
## 4.1.2 - May 25, 2025
|
|
6
|
+
- Seed unreferenced entities
|
|
7
|
+
- Ensure seed uniqueness
|
|
8
|
+
- Add base64 dependency
|
|
9
|
+
|
|
1
10
|
## 4.1.1 - March 2, 2025
|
|
2
11
|
- Warn for duplicate chapters
|
|
3
12
|
- YARD documentation
|
data/Rakefile
CHANGED
|
@@ -15,7 +15,8 @@ task :default => :spec
|
|
|
15
15
|
|
|
16
16
|
Opal::RSpec::RakeTask.new(:opal) do |_, config|
|
|
17
17
|
Opal.append_path File.join(__dir__, 'lib')
|
|
18
|
+
Opal.append_path File.join(__dir__, 'spec', 'fixtures', 'modular')
|
|
18
19
|
config.default_path = 'spec'
|
|
19
20
|
config.pattern = 'spec/**/*_spec.rb'
|
|
20
|
-
config.requires = ['opal_helper']
|
|
21
|
+
config.requires = ['opal_helper', 'modular_test_script', 'modular_test_plot']
|
|
21
22
|
end
|
data/gamefic.gemspec
CHANGED
data/lib/gamefic/binding.rb
CHANGED
data/lib/gamefic/chapter.rb
CHANGED
data/lib/gamefic/describable.rb
CHANGED
|
@@ -142,10 +142,10 @@ module Gamefic
|
|
|
142
142
|
#
|
|
143
143
|
# @param text [String]
|
|
144
144
|
def description=(text)
|
|
145
|
-
@description = (text if text !=
|
|
145
|
+
@description = (text if text != format(Describable.default_description, name: definitely, Name: definitely.capitalize_first))
|
|
146
146
|
end
|
|
147
147
|
|
|
148
|
-
def synonyms=
|
|
148
|
+
def synonyms=(text)
|
|
149
149
|
@synonyms = text
|
|
150
150
|
end
|
|
151
151
|
|
data/lib/gamefic/narrative.rb
CHANGED
|
@@ -62,7 +62,7 @@ module Gamefic
|
|
|
62
62
|
#
|
|
63
63
|
# In the base Narrative class, this method runs all applicable player
|
|
64
64
|
# conclude blocks and the narrative's own conclude blocks.
|
|
65
|
-
#
|
|
65
|
+
#
|
|
66
66
|
# @return [void]
|
|
67
67
|
def turn
|
|
68
68
|
players.select(&:concluding?).each { |plyr| player_conclude_blocks.each { |blk| blk[plyr] } }
|
|
@@ -77,6 +77,7 @@ module Gamefic
|
|
|
77
77
|
# @param snapshot [String]
|
|
78
78
|
# @return [self]
|
|
79
79
|
def self.restore(snapshot)
|
|
80
|
+
# @sg-ignore
|
|
80
81
|
Marshal.load(snapshot)
|
|
81
82
|
end
|
|
82
83
|
|
data/lib/gamefic/narrator.rb
CHANGED
|
@@ -14,16 +14,16 @@ module Gamefic
|
|
|
14
14
|
|
|
15
15
|
# Cast a player character in the plot.
|
|
16
16
|
#
|
|
17
|
-
# @param character [Actor]
|
|
18
|
-
# @return [Actor]
|
|
17
|
+
# @param character [Actor, Active]
|
|
18
|
+
# @return [Actor, Active]
|
|
19
19
|
def cast(character = plot.introduce)
|
|
20
20
|
plot.cast character
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# Uncast a player character from the plot.
|
|
24
24
|
#
|
|
25
|
-
# @param character [Actor]
|
|
26
|
-
# @return [Actor]
|
|
25
|
+
# @param character [Actor, Active]
|
|
26
|
+
# @return [Actor, Active]
|
|
27
27
|
def uncast(character)
|
|
28
28
|
plot.uncast character
|
|
29
29
|
end
|
data/lib/gamefic/order.rb
CHANGED
|
@@ -20,9 +20,9 @@ module Gamefic
|
|
|
20
20
|
def to_actions
|
|
21
21
|
Action.sort(
|
|
22
22
|
actor.narratives
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
.responses_for(verb)
|
|
24
|
+
.map { |response| match_arguments(response) }
|
|
25
|
+
.compact
|
|
26
26
|
)
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -5,9 +5,6 @@ module Gamefic
|
|
|
5
5
|
# Props for MultipleChoice scenes.
|
|
6
6
|
#
|
|
7
7
|
class MultipleChoice < Default
|
|
8
|
-
# A message to send the player for an invalid choice. A formatting
|
|
9
|
-
# token named %<input>s can be used to inject the user input.
|
|
10
|
-
#
|
|
11
8
|
# @return [String]
|
|
12
9
|
attr_writer :invalid_message
|
|
13
10
|
|
|
@@ -18,6 +15,13 @@ module Gamefic
|
|
|
18
15
|
@options ||= []
|
|
19
16
|
end
|
|
20
17
|
|
|
18
|
+
# A message to send the player for an invalid choice. A formatting
|
|
19
|
+
# token named `%<input>s` can be used to inject the user input.
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
# props.invalid_message = '"%<input>s" is not a valid choice.'
|
|
23
|
+
#
|
|
24
|
+
# @return [String]
|
|
21
25
|
def invalid_message
|
|
22
26
|
@invalid_message ||= '"%<input>s" is not a valid choice.'
|
|
23
27
|
end
|
|
@@ -28,7 +32,7 @@ module Gamefic
|
|
|
28
32
|
def index
|
|
29
33
|
return nil unless input
|
|
30
34
|
|
|
31
|
-
@index ||=
|
|
35
|
+
@index ||= index_of(input)
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
# The one-based index of the selected option.
|
|
@@ -53,15 +57,36 @@ module Gamefic
|
|
|
53
57
|
!!index
|
|
54
58
|
end
|
|
55
59
|
|
|
60
|
+
# Get the index of an option using input criteria, e.g., a one-based
|
|
61
|
+
# number or the text of the option. The return value is the option's
|
|
62
|
+
# zero-based index or nil.
|
|
63
|
+
#
|
|
64
|
+
# @example
|
|
65
|
+
# props = Gamefic::Props::MultipleChoice.new
|
|
66
|
+
# props.options.push 'First choice', 'Second choice'
|
|
67
|
+
#
|
|
68
|
+
# props.index_of(1) # => 0
|
|
69
|
+
# props.index_of('Second choice') # => 1
|
|
70
|
+
#
|
|
71
|
+
# @param option [String, Integer]
|
|
72
|
+
# @return [Integer, nil]
|
|
73
|
+
def index_of(option)
|
|
74
|
+
index_by_number(option) || index_by_text(option)
|
|
75
|
+
end
|
|
76
|
+
|
|
56
77
|
private
|
|
57
78
|
|
|
58
|
-
|
|
59
|
-
|
|
79
|
+
# @param [String, Integer]
|
|
80
|
+
# @return [Integer, nil]
|
|
81
|
+
def index_by_number(input)
|
|
82
|
+
return input.to_i - 1 if input.to_s.match(/^\d+$/) && options[input.to_i - 1]
|
|
60
83
|
|
|
61
84
|
nil
|
|
62
85
|
end
|
|
63
86
|
|
|
64
|
-
|
|
87
|
+
# @param [String]
|
|
88
|
+
# @return [Integer, nil]
|
|
89
|
+
def index_by_text(input)
|
|
65
90
|
options.find_index { |opt| opt.casecmp?(input) }
|
|
66
91
|
end
|
|
67
92
|
end
|
|
@@ -7,7 +7,7 @@ module Gamefic
|
|
|
7
7
|
class MultiplePartial < MultipleChoice
|
|
8
8
|
private
|
|
9
9
|
|
|
10
|
-
def index_by_text
|
|
10
|
+
def index_by_text(input)
|
|
11
11
|
matches = options.map.with_index { |text, idx| next idx if text.downcase.start_with?(input.downcase) }.compact
|
|
12
12
|
matches.first if matches.one?
|
|
13
13
|
end
|
data/lib/gamefic/query/base.rb
CHANGED
|
@@ -77,7 +77,7 @@ module Gamefic
|
|
|
77
77
|
# gets sorted in descending order of their responses' overall precision,
|
|
78
78
|
# so the action with the highest precision gets attempted first.
|
|
79
79
|
#
|
|
80
|
-
# @return [Integer]
|
|
80
|
+
# @return [::Integer]
|
|
81
81
|
def precision
|
|
82
82
|
@precision ||= calculate_precision
|
|
83
83
|
end
|
data/lib/gamefic/scanner.rb
CHANGED
|
@@ -18,7 +18,7 @@ module Gamefic
|
|
|
18
18
|
# @param selection [Array<Entity>]
|
|
19
19
|
# @param token [String]
|
|
20
20
|
# @param use [Array<Class<Scanner::Base>>]
|
|
21
|
-
# @return [Result]
|
|
21
|
+
# @return [Result, nil]
|
|
22
22
|
def self.scan(selection, token, use = processors)
|
|
23
23
|
result = nil
|
|
24
24
|
use.each do |processor|
|
data/lib/gamefic/scene/base.rb
CHANGED
|
@@ -12,8 +12,8 @@ module Gamefic
|
|
|
12
12
|
attr_reader :actor, :narrative, :props, :context
|
|
13
13
|
|
|
14
14
|
# @param actor [Actor]
|
|
15
|
-
# @param narrative [Narrative]
|
|
16
|
-
# @param props [Props::Default]
|
|
15
|
+
# @param narrative [Narrative, nil]
|
|
16
|
+
# @param props [Props::Default, nil]
|
|
17
17
|
def initialize(actor, narrative = nil, props = nil, **context)
|
|
18
18
|
@actor = actor
|
|
19
19
|
@narrative = narrative
|
|
@@ -11,11 +11,13 @@ module Gamefic
|
|
|
11
11
|
# @return [Scene::Conclusion]
|
|
12
12
|
attr_reader :default_conclusion
|
|
13
13
|
|
|
14
|
+
# @param [Scene::Base]
|
|
14
15
|
def select_default_scene(klass)
|
|
15
16
|
scene_classes.add klass
|
|
16
17
|
@default_scene = klass
|
|
17
18
|
end
|
|
18
19
|
|
|
20
|
+
# @param [Scene::Conclusion]
|
|
19
21
|
def select_default_conclusion(klass)
|
|
20
22
|
scene_classes.add klass
|
|
21
23
|
@default_conclusion = klass
|
|
@@ -61,7 +63,8 @@ module Gamefic
|
|
|
61
63
|
#
|
|
62
64
|
# @yieldparam [Gamefic::Actor]
|
|
63
65
|
# @yieldparam [Props::Default]
|
|
64
|
-
# @
|
|
66
|
+
# @yieldreceiver [Object<self>]
|
|
67
|
+
# @return [void]
|
|
65
68
|
def introduction(&start)
|
|
66
69
|
introductions.push start
|
|
67
70
|
end
|
|
@@ -88,7 +91,7 @@ module Gamefic
|
|
|
88
91
|
# @yieldreceiver [Class<Scene::MultipleChoice>]
|
|
89
92
|
# @return [Class<Scene::MultipleChoice>]
|
|
90
93
|
def multiple_choice(name = nil, &block)
|
|
91
|
-
block Class.new(Scene::MultipleChoice, &block), name
|
|
94
|
+
self.block Class.new(Scene::MultipleChoice, &block), name
|
|
92
95
|
end
|
|
93
96
|
|
|
94
97
|
# Create a yes-or-no scene.
|
|
@@ -113,7 +116,7 @@ module Gamefic
|
|
|
113
116
|
# @param name [Symbol, nil]
|
|
114
117
|
# @return [Class<Scene::YesOrNo>]
|
|
115
118
|
def yes_or_no(name = nil, &block)
|
|
116
|
-
block Class.new(Scene::YesOrNo, &block), name
|
|
119
|
+
self.block Class.new(Scene::YesOrNo, &block), name
|
|
117
120
|
end
|
|
118
121
|
|
|
119
122
|
# Create an active choice scene.
|
|
@@ -121,7 +124,7 @@ module Gamefic
|
|
|
121
124
|
# @param name [Symbol, nil]
|
|
122
125
|
# @return [Class<Scene::ActiveChoice>]
|
|
123
126
|
def active_choice(name = nil, &block)
|
|
124
|
-
block Class.new(Scene::ActiveChoice, &block), name
|
|
127
|
+
self.block Class.new(Scene::ActiveChoice, &block), name
|
|
125
128
|
end
|
|
126
129
|
|
|
127
130
|
# Create a pause.
|
|
@@ -131,9 +134,10 @@ module Gamefic
|
|
|
131
134
|
# @param name [Symbol, nil]
|
|
132
135
|
# @yieldparam [Actor]
|
|
133
136
|
# @yieldparam [Props::Default]
|
|
137
|
+
# @yieldreceiver [Object<self>]
|
|
134
138
|
# @return [Class<Scene::Pause>]
|
|
135
139
|
def pause(name = nil, &block)
|
|
136
|
-
block(Class.new(Scene::Pause) do
|
|
140
|
+
self.block(Class.new(Scene::Pause) do
|
|
137
141
|
on_start(&block)
|
|
138
142
|
end, name)
|
|
139
143
|
end
|
|
@@ -150,9 +154,10 @@ module Gamefic
|
|
|
150
154
|
# @param name [Symbol, nil]
|
|
151
155
|
# @yieldparam [Actor]
|
|
152
156
|
# @yieldparam [Props::Default]
|
|
157
|
+
# @yieldreceiver [Object<self>]
|
|
153
158
|
# @return [Class<Scene::Conclusion>]
|
|
154
159
|
def conclusion(name = nil, &block)
|
|
155
|
-
block(Class.new(Scene::Conclusion) do
|
|
160
|
+
self.block(Class.new(Scene::Conclusion) do
|
|
156
161
|
on_start(&block)
|
|
157
162
|
end, name)
|
|
158
163
|
end
|
|
@@ -17,8 +17,15 @@ module Gamefic
|
|
|
17
17
|
# @param command [String] The format of the original command
|
|
18
18
|
# @param translation [String] The format of the translated command
|
|
19
19
|
# @return [Syntax] the Syntax object
|
|
20
|
-
def interpret
|
|
21
|
-
|
|
20
|
+
def interpret(command, translation)
|
|
21
|
+
parts = Syntax.split(command)
|
|
22
|
+
additions = if parts.first.include?('|')
|
|
23
|
+
parts.first.split('|').map { |verb| Syntax.new("#{verb} #{verb[1..].join(' ')}", translation) }
|
|
24
|
+
else
|
|
25
|
+
[Syntax.new(command, translation)]
|
|
26
|
+
end
|
|
27
|
+
syntaxes.concat additions
|
|
28
|
+
additions
|
|
22
29
|
end
|
|
23
30
|
|
|
24
31
|
def syntaxes
|
data/lib/gamefic/scripting.rb
CHANGED
data/lib/gamefic/syntax.rb
CHANGED
|
@@ -82,9 +82,10 @@ module Gamefic
|
|
|
82
82
|
#
|
|
83
83
|
# @param text [String]
|
|
84
84
|
# @return [Boolean]
|
|
85
|
-
def
|
|
85
|
+
def match?(text)
|
|
86
86
|
!!text.match(regexp)
|
|
87
87
|
end
|
|
88
|
+
alias accept? match?
|
|
88
89
|
|
|
89
90
|
# Get a signature that identifies the form of the Syntax.
|
|
90
91
|
# Signatures are used to compare Syntaxes to each other.
|
|
@@ -118,6 +119,48 @@ module Gamefic
|
|
|
118
119
|
string.start_with?(':') ? nil : string.to_sym
|
|
119
120
|
end
|
|
120
121
|
|
|
122
|
+
# Split a syntax template by words and expressions.
|
|
123
|
+
#
|
|
124
|
+
# @param template [String]
|
|
125
|
+
# @return [Array<String>]
|
|
126
|
+
def self.split(template)
|
|
127
|
+
parens = 0
|
|
128
|
+
result = template.normalize.chars.each.with_object(['']) do |char, result|
|
|
129
|
+
parens = parse_token(char, parens, result)
|
|
130
|
+
end
|
|
131
|
+
raise "Unbalanced parentheses in syntax '#{template}'" unless parens.zero?
|
|
132
|
+
|
|
133
|
+
result.pop if result.last.empty?
|
|
134
|
+
|
|
135
|
+
result
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
class << self
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
# @param char [String]
|
|
142
|
+
# @param parens [Integer]
|
|
143
|
+
# @param result [Array<String>]
|
|
144
|
+
def parse_token(char, parens, result)
|
|
145
|
+
case char
|
|
146
|
+
when /[()]/
|
|
147
|
+
parens += (char == '(' ? 1 : -1)
|
|
148
|
+
when /\s/
|
|
149
|
+
return parens if result.last.empty?
|
|
150
|
+
|
|
151
|
+
if parens.positive?
|
|
152
|
+
result.push(result.pop + char)
|
|
153
|
+
else
|
|
154
|
+
result.push ''
|
|
155
|
+
end
|
|
156
|
+
else
|
|
157
|
+
result.push(result.pop + char)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
parens
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
121
164
|
private
|
|
122
165
|
|
|
123
166
|
# @return [String]
|
|
@@ -142,9 +185,9 @@ module Gamefic
|
|
|
142
185
|
|
|
143
186
|
# @return [Array<String>]
|
|
144
187
|
def make_tokens
|
|
145
|
-
template.
|
|
188
|
+
Syntax.split(template).map.with_index do |word, idx|
|
|
189
|
+
next "(?:\\b#{word.gsub('|', '|\\b')})" if word.include?('|')
|
|
146
190
|
next word unless word.match?(PARAM_REGEXP)
|
|
147
|
-
|
|
148
191
|
next nil if idx.positive? && template.keywords[idx - 1].match?(PARAM_REGEXP)
|
|
149
192
|
|
|
150
193
|
'([\w\W\s\S]*?)'
|
data/lib/gamefic/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gamefic
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Fred Snyder
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
10
|
+
date: 2025-11-27 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: base64
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.1'
|
|
13
26
|
- !ruby/object:Gem::Dependency
|
|
14
27
|
name: yard-solargraph
|
|
15
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -226,7 +239,6 @@ homepage: https://gamefic.com
|
|
|
226
239
|
licenses:
|
|
227
240
|
- MIT
|
|
228
241
|
metadata: {}
|
|
229
|
-
post_install_message:
|
|
230
242
|
rdoc_options: []
|
|
231
243
|
require_paths:
|
|
232
244
|
- lib
|
|
@@ -241,8 +253,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
241
253
|
- !ruby/object:Gem::Version
|
|
242
254
|
version: '0'
|
|
243
255
|
requirements: []
|
|
244
|
-
rubygems_version: 3.
|
|
245
|
-
signing_key:
|
|
256
|
+
rubygems_version: 3.6.7
|
|
246
257
|
specification_version: 4
|
|
247
258
|
summary: Gamefic
|
|
248
259
|
test_files: []
|