gamefic 3.2.0 → 3.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac4f50caef9bd101bf90447e3ab289b05a29470e8f722ad391f7f0c9d8c4a921
4
- data.tar.gz: 926b7d40dc87036e3759a1a464d3db3c82187da4f829731d0d82b053ac5e52a4
3
+ metadata.gz: 85e6fc4d2b5c4a9abfe20cbbe17affdec1f1badb134e8cad88849a5305fb9bf0
4
+ data.tar.gz: 97a29823857d93fafa9e8a3bc4872e27a4a6da480f589535da6abf254eb5aa7a
5
5
  SHA512:
6
- metadata.gz: 6a4b426b4703a29717dbcc59bfee2e3f5d3b1b2ceeb6121741f52111e5fe2d8165e0946f8c3a085f1a3c4094edab11e2966f751dbc3cfc1541809ebc3a9ad970
7
- data.tar.gz: ba3f3e82bd772ae392ffb23b056b62de2c1b5bebb0e810fea81bd59449d3e64c8c848f748803ac36f155ad3a2891b0e42d76ca48e4e74239a776670f19fdcc0e
6
+ metadata.gz: 611c3b366e76a29cd2ffb95f906882a63ac9d73bfb6d1d7aaa505e4470f4526311704c2245285f908247b6248b086914d8a145e1c9b86577e197d8d77c707939
7
+ data.tar.gz: dc19cf51ab4edbc1b9f9708eb543c977527e3165a949d948782349e545d0a32fde42f8532c9071e0ddc86b521cc0d1cb778e8e88b1d7b0dcc575d547c5db1df1
@@ -22,7 +22,7 @@ jobs:
22
22
  runs-on: ubuntu-latest
23
23
  strategy:
24
24
  matrix:
25
- ruby-version: ['2.7', '3.0', '3.1']
25
+ ruby-version: ['2.7', '3.0', '3.1', '3.3']
26
26
 
27
27
  steps:
28
28
  - uses: actions/checkout@v3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## 3.3.0 - September 1, 2024
2
+ - Node#take
3
+ - Node#include?
4
+ - Reject non-string tokens in Text queries
5
+
6
+ ## 3.2.2 - July 21, 2024
7
+ - Describable#described?
8
+
9
+ ## 3.2.1 - July 1, 2024
10
+ - MultipleChoice accepts shortened text
11
+ - Return Proxy::Agent from attr_seed and make_seed
12
+ - MultipleChoice skips finish blocks for invalid input
13
+
1
14
  ## 3.2.0 - April 9, 2024
2
15
  - Bug fix for marshal of structs in Opal
3
16
  - Add last_input and last_prompt at start of take
data/Rakefile CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
3
+ require 'bundler/gem_tasks'
5
4
  require 'rspec/core/rake_task'
6
5
  require 'opal/rspec/rake_task'
7
6
 
@@ -14,7 +13,8 @@ end
14
13
  task :default => :spec
15
14
 
16
15
  Opal::RSpec::RakeTask.new(:opal) do |_, config|
17
- Opal.append_path File.expand_path('../lib', __FILE__)
16
+ Opal.append_path File.join(__dir__, 'lib')
17
+ config.default_path = 'spec'
18
18
  config.pattern = 'spec/**/*_spec.rb'
19
- config.requires = ['spec_helper']
19
+ config.requires = ['opal_helper']
20
20
  end
@@ -6,6 +6,7 @@ module Gamefic
6
6
  # a few shortcuts.
7
7
  #
8
8
  module Messaging
9
+ # @return [Messenger]
9
10
  def messenger
10
11
  @messenger ||= Messenger.new
11
12
  end
@@ -43,7 +43,7 @@ module Gamefic
43
43
  arguments = []
44
44
  response.queries.each_with_index do |query, idx|
45
45
  result = Scanner.send(method, query.select(actor), "#{remainder} #{expression.tokens[idx]}".strip)
46
- break unless validate_result_from_query(result, query)
46
+ break unless valid_result_from_query?(result, query)
47
47
 
48
48
  if query.ambiguous?
49
49
  arguments.push result.matched
@@ -60,7 +60,7 @@ module Gamefic
60
60
 
61
61
  # @param result [Scanner::Result]
62
62
  # @param query [Query::Base]
63
- def validate_result_from_query result, query
63
+ def valid_result_from_query? result, query
64
64
  return false if result.matched.empty?
65
65
 
66
66
  result.matched.length == 1 || query.ambiguous?
@@ -112,10 +112,11 @@ module Gamefic
112
112
  # Does the object have a description?
113
113
  #
114
114
  # @return [Boolean]
115
- def description?
115
+ def described?
116
116
  @description.to_s != ''
117
117
  end
118
- alias has_description? description?
118
+ alias description? described?
119
+ alias has_description? described?
119
120
 
120
121
  # Get the object's description.
121
122
  #
data/lib/gamefic/node.rb CHANGED
@@ -42,6 +42,15 @@ module Gamefic
42
42
  parent&.add_child self
43
43
  end
44
44
 
45
+ # Add children to the node. Return all the node's children.
46
+ #
47
+ # @param children [Array<Node, Array<Node>>]
48
+ # @return [Array<Node>]
49
+ def take *children
50
+ children.flatten.each { |child| child.parent = self }
51
+ children
52
+ end
53
+
45
54
  # Determine if external objects can interact with this object's children.
46
55
  # For example, a game can designate that the contents of a bowl are
47
56
  # accessible, while the contents of a locked safe are not.
@@ -54,10 +63,14 @@ module Gamefic
54
63
  # True if this node is the other's parent.
55
64
  #
56
65
  # @param other [Node]
57
- def has?(other)
66
+ def include?(other)
58
67
  other.parent == self
59
68
  end
60
69
 
70
+ def adjacent?(other)
71
+ other.parent == parent
72
+ end
73
+
61
74
  protected
62
75
 
63
76
  def add_child(node)
data/lib/gamefic/plot.rb CHANGED
@@ -9,6 +9,8 @@ module Gamefic
9
9
  super
10
10
  subplots.each(&:ready)
11
11
  players.each(&:start_take)
12
+ subplots.each(&:conclude) if concluding?
13
+
12
14
  players.select(&:concluding?).each { |plyr| rulebook.run_player_conclude_blocks plyr }
13
15
  subplots.delete_if(&:concluding?)
14
16
  end
@@ -58,7 +58,8 @@ module Gamefic
58
58
  end
59
59
 
60
60
  def index_by_text
61
- options.find_index { |text| input.downcase == text.downcase }
61
+ matches = options.map.with_index { |text, idx| next idx if text.downcase.start_with?(input.downcase) }.compact
62
+ matches.first if matches.one?
62
63
  end
63
64
  end
64
65
  end
@@ -24,16 +24,24 @@ 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.
27
+ # Get a query result for a given subject and token.
28
+ #
29
+ # @note This method is retained as a convenience for authors. Narratives
30
+ # should use Composer to build commands, as it provides more precise
31
+ # matching of tokens to valid response arguments. Authors can use
32
+ # #query to find entities that match a token regardless of whether the
33
+ # result matches an available response.
34
+ #
35
+ # @example
36
+ # respond :reds do |actor|
37
+ # reds = available(ambiguous: true).query(actor, 'red').match
38
+ # actor.tell "The red things you can see here are #{reds.join_and}."
39
+ # end
32
40
  #
33
41
  # @param subject [Gamefic::Entity]
34
42
  # @param token [String]
35
43
  # @return [Result]
36
- def query(subject, token)
44
+ def query(_subject, _token)
37
45
  raise "#query not implemented for #{self.class}"
38
46
  end
39
47
 
@@ -42,7 +50,7 @@ module Gamefic
42
50
  #
43
51
  # @param subject [Entity]
44
52
  # @return [Array<Entity>]
45
- def select subject
53
+ def select _subject
46
54
  raise "#select not implemented for #{self.class}"
47
55
  end
48
56
 
@@ -88,12 +96,14 @@ module Gamefic
88
96
  depth
89
97
  end
90
98
 
99
+ # @param scan [Scanner::Result]
91
100
  def ambiguous_result scan
92
101
  return Result.new(nil, scan.token) if scan.matched.empty?
93
102
 
94
103
  Result.new(scan.matched, scan.remainder)
95
104
  end
96
105
 
106
+ # @param scan [Scanner::Result]
97
107
  def unambiguous_result scan
98
108
  return Result.new(nil, scan.token) unless scan.matched.one?
99
109
 
@@ -6,7 +6,6 @@ module Gamefic
6
6
  # relationship to the entity performing the query. For example,
7
7
  # Scope::Children would filter from an array of the entity's descendants.
8
8
  #
9
- # @return [Class<Gamefic::Scope::Base>]
10
9
  class Scoped < Base
11
10
  attr_reader :scope
12
11
 
@@ -39,6 +39,8 @@ module Gamefic
39
39
  private
40
40
 
41
41
  def match? token
42
+ return false unless token.is_a?(String)
43
+
42
44
  case @argument
43
45
  when Regexp
44
46
  token =~ @argument
@@ -53,7 +53,7 @@ module Gamefic
53
53
  # @param props [Props::Default]
54
54
  # @return [void]
55
55
  def finish actor, props
56
- props.input = actor.queue.shift
56
+ props.input = actor.queue.shift&.strip
57
57
  end
58
58
 
59
59
  def run_start_blocks actor, props
@@ -20,6 +20,12 @@ module Gamefic
20
20
  actor.tell format(props.invalid_message, input: props.input)
21
21
  actor.recue
22
22
  end
23
+
24
+ def run_finish_blocks actor, props
25
+ return unless props.index
26
+
27
+ super
28
+ end
23
29
  end
24
30
  end
25
31
  end
@@ -45,6 +45,7 @@ module Gamefic
45
45
  # proxy(:@instance_variable_name)
46
46
  #
47
47
  # @param symbol [Symbol]
48
+ # @return [Agent]
48
49
  def proxy symbol
49
50
  Agent.new(symbol)
50
51
  end
@@ -38,6 +38,12 @@ module Gamefic
38
38
  end
39
39
  alias scene block
40
40
 
41
+ def preface name, klass = Scene::Activity, &start
42
+ rulebook.scenes.add klass.new(name, rulebook.narrative, on_start: start)
43
+ name
44
+ end
45
+ alias precursor preface
46
+
41
47
  # Add a block to be executed when a player is added to the game.
42
48
  # Each Plot should only have one introduction.
43
49
  #
@@ -55,7 +61,7 @@ module Gamefic
55
61
  rulebook.scenes
56
62
  .introduction Scene::Default.new nil,
57
63
  rulebook.narrative,
58
- on_start: proc { |actor, _props| instance_exec(actor, &start) }
64
+ on_start: proc { |actor, _props| Stage.run(self, actor, &start) }
59
65
  end
60
66
 
61
67
  # Create a multiple-choice scene.
@@ -104,7 +104,7 @@ module Gamefic
104
104
  def make_seed klass, **opts
105
105
  @count ||= 0
106
106
  seed { make(klass, **opts) }
107
- @count.tap { @count += 1 }
107
+ Proxy::Agent.new(@count.tap { @count += 1 })
108
108
  end
109
109
 
110
110
  # Seed an entity with an attribute method.
@@ -124,7 +124,7 @@ module Gamefic
124
124
  instance_variable_set("@#{name}", make(klass, **opts))
125
125
  self.class.define_method(name) { instance_variable_get("@#{name}") }
126
126
  end
127
- @count.tap { @count += 1 }
127
+ Proxy::Agent.new(@count.tap { @count += 1 })
128
128
  end
129
129
 
130
130
  if RUBY_ENGINE == 'opal'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gamefic
4
- VERSION = '3.2.0'
4
+ VERSION = '3.3.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gamefic
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-09 00:00:00.000000000 Z
11
+ date: 2024-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opal
@@ -195,7 +195,6 @@ files:
195
195
  - lib/gamefic/syntax/template.rb
196
196
  - lib/gamefic/vault.rb
197
197
  - lib/gamefic/version.rb
198
- - spec-opal/spec_helper.rb
199
198
  homepage: https://gamefic.com
200
199
  licenses:
201
200
  - MIT
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'gamefic'
4
- require 'ostruct'
5
-
6
- RSpec.configure do |config|
7
- # Run specs in random order to surface order dependencies. If you find an
8
- # order dependency and want to debug it, you can fix the order by providing
9
- # the seed, which is printed after each run.
10
- # --seed 1234
11
- # config.order = :random
12
-
13
- # Seed global randomization in this process using the `--seed` CLI option.
14
- # Setting this allows you to use `--seed` to deterministically reproduce
15
- # test failures related to randomization by passing the same `--seed` value
16
- # as the one that triggered the failure.
17
- # Kernel.srand config.seed
18
-
19
- config.after :each do
20
- Gamefic::Narrative.blocks.clear
21
- Gamefic::Plot.blocks.clear
22
- Gamefic::Subplot.blocks.clear
23
- end
24
- end