gamefic 4.1.2 → 4.2.1
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 +9 -0
- data/lib/gamefic/binding.rb +28 -0
- data/lib/gamefic/describable.rb +2 -2
- data/lib/gamefic/narrative.rb +2 -1
- 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/scriptable/scenes.rb +0 -3
- 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 +2 -0
- 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 +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9800fd3de4b72b22f083edb20b4c9b74b58f306ce70266a1a9c2aa6b40b02d1e
|
|
4
|
+
data.tar.gz: 0ba0352e45dd1609d2070e915d6fcf3e41175fd7e52dafcdb4ed5151967241d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1350f7c939581714ba21108e8376faf4091905a5a85e15ec2d7b4fdc0e44de9c6057698117b04abcf763cf4b3cc74d807383c609c23814973cca58b0a318a59e
|
|
7
|
+
data.tar.gz: fc2464dbfc163a53742af0e286623a08f67c70472acabd8bfb0176aadfd18431b3f7c41a340264cbf79c9b493b8cda0fcf4b13b91be086cf9442ec9b971f3a4e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## 4.2.1 - February 1, 2026
|
|
2
|
+
- Correct Scenes#introduction documentation
|
|
3
|
+
- Fix parsing of piped verbs
|
|
4
|
+
- Binding documentation
|
|
5
|
+
|
|
6
|
+
## 4.2.0 - November 27, 2025
|
|
7
|
+
- Syntaxes support variable (piped) words
|
|
8
|
+
- Props::MultipleChoice#index_of
|
|
9
|
+
|
|
1
10
|
## 4.1.2 - May 25, 2025
|
|
2
11
|
- Seed unreferenced entities
|
|
3
12
|
- Ensure seed uniqueness
|
data/lib/gamefic/binding.rb
CHANGED
|
@@ -1,29 +1,54 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Gamefic
|
|
4
|
+
# A lightweight wrapper around a `Proc` that executes it in the context of a
|
|
5
|
+
# specific narrative.
|
|
6
|
+
#
|
|
7
|
+
# Gamefic stores most author-written blocks (responses, callbacks, etc.) as
|
|
8
|
+
# plain Ruby procs. Those blocks are intended to run within the context of
|
|
9
|
+
# their parent narrative so they can call the narrative's methods.
|
|
10
|
+
# Gamefic::Binding pairs the proc with its narrative and provides a safe way
|
|
11
|
+
# to execute it.
|
|
12
|
+
#
|
|
4
13
|
class Binding
|
|
5
14
|
class << self
|
|
15
|
+
# A map of objects and the stack of narratives in which they're currently
|
|
16
|
+
# involved.
|
|
17
|
+
#
|
|
6
18
|
def registry
|
|
7
19
|
@registry ||= {}
|
|
8
20
|
end
|
|
9
21
|
|
|
22
|
+
# Push a narrative to the top of an object's stack.
|
|
23
|
+
#
|
|
24
|
+
# @param object [Object]
|
|
25
|
+
# @param narrative [Narrative]
|
|
10
26
|
def push(object, narrative)
|
|
11
27
|
registry[object] ||= []
|
|
12
28
|
registry[object].push narrative
|
|
13
29
|
end
|
|
14
30
|
|
|
31
|
+
# Remove a narrative from the top of an object's stack.
|
|
32
|
+
#
|
|
33
|
+
# @param object [Object]
|
|
15
34
|
def pop(object)
|
|
16
35
|
registry[object].pop
|
|
17
36
|
registry.delete(object) if registry[object].empty?
|
|
18
37
|
end
|
|
19
38
|
|
|
39
|
+
# Fetch the narrative at the top of an object's stack.
|
|
40
|
+
#
|
|
41
|
+
# @param object [Object]
|
|
42
|
+
# @return [Narrative, nil]
|
|
20
43
|
def for(object)
|
|
21
44
|
registry.fetch(object, []).last
|
|
22
45
|
end
|
|
23
46
|
end
|
|
24
47
|
|
|
48
|
+
# @return [Narrative]
|
|
25
49
|
attr_reader :narrative
|
|
26
50
|
|
|
51
|
+
# @return [Proc]
|
|
27
52
|
attr_reader :code
|
|
28
53
|
|
|
29
54
|
# @param narrative [Narrative]
|
|
@@ -33,6 +58,9 @@ module Gamefic
|
|
|
33
58
|
@code = code
|
|
34
59
|
end
|
|
35
60
|
|
|
61
|
+
# Execute the binding's code within the context of the narrative.
|
|
62
|
+
#
|
|
63
|
+
# @return [void]
|
|
36
64
|
def call(*args)
|
|
37
65
|
args.each { |arg| Binding.push arg, @narrative }
|
|
38
66
|
@narrative.instance_exec(*args, &@code)
|
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/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
|
|
@@ -52,15 +52,12 @@ module Gamefic
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# Add a block to be executed when a player is added to the game.
|
|
55
|
-
# Each Plot should only have one introduction.
|
|
56
55
|
#
|
|
57
56
|
# @example Welcome the player to the game
|
|
58
57
|
# introduction do |actor|
|
|
59
58
|
# actor.tell "Welcome to the game!"
|
|
60
59
|
# end
|
|
61
60
|
#
|
|
62
|
-
# @raise [ArgumentError] if an introduction already exists
|
|
63
|
-
#
|
|
64
61
|
# @yieldparam [Gamefic::Actor]
|
|
65
62
|
# @yieldparam [Props::Default]
|
|
66
63
|
# @yieldreceiver [Object<self>]
|
|
@@ -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} #{parts[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,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gamefic
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.1
|
|
4
|
+
version: 4.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Fred Snyder
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 2026-02-01 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: base64
|
|
@@ -240,7 +239,6 @@ homepage: https://gamefic.com
|
|
|
240
239
|
licenses:
|
|
241
240
|
- MIT
|
|
242
241
|
metadata: {}
|
|
243
|
-
post_install_message:
|
|
244
242
|
rdoc_options: []
|
|
245
243
|
require_paths:
|
|
246
244
|
- lib
|
|
@@ -255,8 +253,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
255
253
|
- !ruby/object:Gem::Version
|
|
256
254
|
version: '0'
|
|
257
255
|
requirements: []
|
|
258
|
-
rubygems_version: 3.
|
|
259
|
-
signing_key:
|
|
256
|
+
rubygems_version: 3.6.7
|
|
260
257
|
specification_version: 4
|
|
261
258
|
summary: Gamefic
|
|
262
259
|
test_files: []
|