gamefic 2.4.0 → 3.0.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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +41 -40
  3. data/.rspec-opal +2 -0
  4. data/.solargraph.yml +20 -3
  5. data/CHANGELOG.md +9 -0
  6. data/Rakefile +11 -1
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/gamefic.gemspec +5 -2
  10. data/lib/gamefic/action.rb +52 -183
  11. data/lib/gamefic/active/cue.rb +25 -0
  12. data/lib/gamefic/active/epic.rb +68 -0
  13. data/lib/gamefic/active/messaging.rb +43 -0
  14. data/lib/gamefic/active/take.rb +69 -0
  15. data/lib/gamefic/active.rb +95 -192
  16. data/lib/gamefic/actor.rb +2 -0
  17. data/lib/gamefic/block.rb +28 -0
  18. data/lib/gamefic/command.rb +16 -6
  19. data/lib/gamefic/core_ext/array.rb +4 -4
  20. data/lib/gamefic/core_ext/string.rb +10 -5
  21. data/lib/gamefic/describable.rb +39 -65
  22. data/lib/gamefic/dispatcher.rb +63 -32
  23. data/lib/gamefic/entity.rb +44 -19
  24. data/lib/gamefic/logging.rb +32 -0
  25. data/lib/gamefic/messenger.rb +66 -0
  26. data/lib/gamefic/narrative.rb +104 -0
  27. data/lib/gamefic/node.rb +44 -53
  28. data/lib/gamefic/plot.rb +60 -93
  29. data/lib/gamefic/props/default.rb +41 -0
  30. data/lib/gamefic/props/multiple_choice.rb +65 -0
  31. data/lib/gamefic/props/pause.rb +11 -0
  32. data/lib/gamefic/props/yes_or_no.rb +21 -0
  33. data/lib/gamefic/props.rb +10 -0
  34. data/lib/gamefic/query/base.rb +45 -126
  35. data/lib/gamefic/query/general.rb +46 -0
  36. data/lib/gamefic/query/result.rb +20 -0
  37. data/lib/gamefic/query/scoped.rb +41 -0
  38. data/lib/gamefic/query/text.rb +30 -31
  39. data/lib/gamefic/query.rb +7 -15
  40. data/lib/gamefic/response.rb +118 -0
  41. data/lib/gamefic/rulebook/calls.rb +90 -0
  42. data/lib/gamefic/rulebook/events.rb +79 -0
  43. data/lib/gamefic/rulebook/hooks.rb +57 -0
  44. data/lib/gamefic/rulebook/scenes.rb +68 -0
  45. data/lib/gamefic/rulebook.rb +139 -0
  46. data/lib/gamefic/scanner.rb +103 -0
  47. data/lib/gamefic/scene/activity.rb +9 -17
  48. data/lib/gamefic/scene/conclusion.rb +6 -5
  49. data/lib/gamefic/scene/default.rb +88 -0
  50. data/lib/gamefic/scene/multiple_choice.rb +14 -69
  51. data/lib/gamefic/scene/pause.rb +9 -13
  52. data/lib/gamefic/scene/yes_or_no.rb +6 -46
  53. data/lib/gamefic/scene.rb +11 -7
  54. data/lib/gamefic/scope/base.rb +44 -0
  55. data/lib/gamefic/scope/children.rb +16 -0
  56. data/lib/gamefic/scope/family.rb +20 -0
  57. data/lib/gamefic/scope/myself.rb +13 -0
  58. data/lib/gamefic/scope/parent.rb +13 -0
  59. data/lib/gamefic/scope/siblings.rb +14 -0
  60. data/lib/gamefic/scope.rb +8 -0
  61. data/lib/gamefic/scriptable/actions.rb +156 -0
  62. data/lib/gamefic/scriptable/entities.rb +76 -0
  63. data/lib/gamefic/scriptable/events.rb +65 -0
  64. data/lib/gamefic/scriptable/proxy.rb +55 -0
  65. data/lib/gamefic/scriptable/queries.rb +73 -0
  66. data/lib/gamefic/scriptable/scenes.rb +162 -0
  67. data/lib/gamefic/scriptable.rb +167 -73
  68. data/lib/gamefic/snapshot.rb +36 -0
  69. data/lib/gamefic/stage.rb +51 -0
  70. data/lib/gamefic/subplot.rb +51 -79
  71. data/lib/gamefic/syntax/template.rb +67 -0
  72. data/lib/gamefic/syntax.rb +102 -83
  73. data/lib/gamefic/vault.rb +50 -0
  74. data/lib/gamefic/version.rb +1 -1
  75. data/lib/gamefic.rb +26 -15
  76. data/spec-opal/spec_helper.rb +24 -0
  77. metadata +91 -29
  78. data/lib/gamefic/element.rb +0 -46
  79. data/lib/gamefic/keywords.rb +0 -52
  80. data/lib/gamefic/messaging.rb +0 -43
  81. data/lib/gamefic/plot/darkroom.rb +0 -120
  82. data/lib/gamefic/plot/host.rb +0 -42
  83. data/lib/gamefic/plot/snapshot.rb +0 -27
  84. data/lib/gamefic/query/children.rb +0 -9
  85. data/lib/gamefic/query/descendants.rb +0 -15
  86. data/lib/gamefic/query/external.rb +0 -39
  87. data/lib/gamefic/query/family.rb +0 -18
  88. data/lib/gamefic/query/itself.rb +0 -13
  89. data/lib/gamefic/query/matches.rb +0 -75
  90. data/lib/gamefic/query/parent.rb +0 -9
  91. data/lib/gamefic/query/siblings.rb +0 -13
  92. data/lib/gamefic/query/tree.rb +0 -17
  93. data/lib/gamefic/scene/base.rb +0 -142
  94. data/lib/gamefic/scene/multiple_scene.rb +0 -29
  95. data/lib/gamefic/serialize.rb +0 -196
  96. data/lib/gamefic/world/callbacks.rb +0 -135
  97. data/lib/gamefic/world/commands.rb +0 -181
  98. data/lib/gamefic/world/entities.rb +0 -98
  99. data/lib/gamefic/world/playbook.rb +0 -233
  100. data/lib/gamefic/world/players.rb +0 -37
  101. data/lib/gamefic/world/scenes.rb +0 -228
  102. data/lib/gamefic/world.rb +0 -18
@@ -1,120 +0,0 @@
1
- module Gamefic
2
- # Create and restore plot snapshots.
3
- #
4
- class Plot
5
- class Darkroom
6
- # @return [Plot]
7
- attr_reader :plot
8
-
9
- # @param plot [Plot]
10
- def initialize plot
11
- @plot = plot
12
- end
13
-
14
- # Create a snapshot of the plot.
15
- #
16
- # @return [Hash]
17
- def save
18
- {
19
- 'program' => {}, # @todo Metadata for version control, etc.
20
- 'index' => index.map { |obj| serialize_indexed(obj) }
21
- }
22
- end
23
-
24
- # Restore a snapshot.
25
- #
26
- # @param snapshot [Hash]
27
- def restore snapshot
28
- # @todo Use `program` for verification
29
-
30
- plot.subplots.each(&:conclude)
31
- plot.subplots.clear
32
-
33
- index = plot.static + plot.players
34
- snapshot['index'].each_with_index do |obj, idx|
35
- next if index[idx]
36
- elematch = obj['class'].match(/^#<ELE_([\d]+)>$/)
37
- if elematch
38
- klass = index[elematch[1].to_i]
39
- else
40
- klass = Gamefic::Serialize.string_to_constant(obj['class'])
41
- end
42
- index.push klass.allocate
43
- end
44
-
45
- snapshot['index'].each_with_index do |obj, idx|
46
- if index[idx].class.to_s != obj['class']
47
- STDERR.puts "MISMATCH: #{index[idx].class} is not #{obj['class']}"
48
- STDERR.puts obj.inspect
49
- end
50
- obj['ivars'].each_pair do |k, v|
51
- next if k == '@subplots'
52
- uns = v.from_serial(index)
53
- next if uns == "#<UNKNOWN>"
54
- index[idx].instance_variable_set(k, uns)
55
- end
56
- if index[idx].is_a?(Gamefic::Subplot)
57
- index[idx].extend Gamefic::Scriptable
58
- index[idx].instance_variable_set(:@theater, nil)
59
- index[idx].send(:run_scripts)
60
- index[idx].players.each do |pl|
61
- pl.playbooks.push index[idx].playbook unless pl.playbooks.include?(index[idx].playbook)
62
- end
63
- index[idx].instance_variable_set(:@static, [index[idx]] + index[idx].scene_classes + index[idx].entities)
64
- plot.subplots.push index[idx]
65
- end
66
- end
67
- end
68
-
69
- private
70
-
71
- def index
72
- @index ||= begin
73
- populate_full_index_from(plot)
74
- Set.new(plot.static + plot.players).merge(full_index).to_a
75
- end
76
- end
77
-
78
- def full_index
79
- @full_index ||= Set.new
80
- end
81
-
82
- def populate_full_index_from(object)
83
- return if full_index.include?(object)
84
- if object.is_a?(Array) || object.is_a?(Set)
85
- object.each { |ele| populate_full_index_from(ele) }
86
- elsif object.is_a?(Hash)
87
- object.each_pair do |k, v|
88
- populate_full_index_from(k)
89
- populate_full_index_from(v)
90
- end
91
- else
92
- if object.is_a?(Gamefic::Serialize)
93
- full_index.add object unless object.is_a?(Module) && object.name
94
- object.instance_variables.each do |v|
95
- next if object.class.excluded_from_serial.include?(v)
96
- populate_full_index_from(object.instance_variable_get(v))
97
- end
98
- else
99
- object.instance_variables.each do |v|
100
- populate_full_index_from(object.instance_variable_get(v))
101
- end
102
- end
103
- end
104
- end
105
-
106
- def serialize_indexed object
107
- if object.is_a?(Gamefic::Serialize)
108
- # Serialized objects in the index should be a full serialization.
109
- # Serialize#to_serial rturns a reference to the indexed object.
110
- {
111
- 'class' => object.class.to_s,
112
- 'ivars' => object.serialize_instance_variables(index)
113
- }
114
- else
115
- object.to_serial(index)
116
- end
117
- end
118
- end
119
- end
120
- end
@@ -1,42 +0,0 @@
1
- require 'gamefic/subplot'
2
-
3
- module Gamefic
4
- # Methods for hosting and managing subplots.
5
- #
6
- module Plot::Host
7
- # Get an array of all the current subplots.
8
- #
9
- # @return [Array<Subplot>]
10
- def subplots
11
- @subplots ||= []
12
- end
13
-
14
- # Start a new subplot based on the provided class.
15
- #
16
- # @param subplot_class [Class<Gamefic::Subplot>] The class of the subplot to be created (Subplot by default)
17
- # @return [Gamefic::Subplot]
18
- def branch subplot_class = Gamefic::Subplot, introduce: nil, next_cue: nil, **more
19
- subplot = subplot_class.new(self, introduce: introduce, next_cue: next_cue, **more)
20
- subplots.push subplot
21
- subplot
22
- end
23
-
24
- # Get the player's current subplots.
25
- #
26
- # @return [Array<Subplot>]
27
- def subplots_featuring player
28
- result = []
29
- subplots.each { |s|
30
- result.push s if s.players.include?(player)
31
- }
32
- result
33
- end
34
-
35
- # Determine whether the player is involved in a subplot.
36
- #
37
- # @return [Boolean]
38
- def in_subplot? player
39
- !subplots_featuring(player).empty?
40
- end
41
- end
42
- end
@@ -1,27 +0,0 @@
1
- require 'json'
2
-
3
- module Gamefic
4
- module Plot::Snapshot
5
- # Save the current game state as a data hash.
6
- # See Gamefic::Plot::Darkroom for more information about
7
- # the data format.
8
- #
9
- # @return [Hash]
10
- def save
11
- Gamefic::Plot::Darkroom.new(self).save
12
- end
13
-
14
- # Restore the game state from a snapshot.
15
- #
16
- # If `snapshot` is a string, parse it as a JSON object.
17
- #
18
- # @note The string conversion is performed as a convenience for web apps.
19
- #
20
- # @param snapshot [Hash, String]
21
- # @return [void]
22
- def restore snapshot
23
- snapshot = JSON.parse(snapshot) if snapshot.is_a?(String)
24
- Gamefic::Plot::Darkroom.new(self).restore(snapshot)
25
- end
26
- end
27
- end
@@ -1,9 +0,0 @@
1
- module Gamefic
2
- module Query
3
- class Children < Base
4
- def context_from(subject)
5
- subject.children
6
- end
7
- end
8
- end
9
- end
@@ -1,15 +0,0 @@
1
- module Gamefic
2
- module Query
3
- class Descendants < Children
4
- def context_from(subject)
5
- result = []
6
- children = super
7
- result.concat children
8
- children.each do |c|
9
- result.concat subquery_accessible(c)
10
- end
11
- result
12
- end
13
- end
14
- end
15
- end
@@ -1,39 +0,0 @@
1
- module Gamefic
2
- module Query
3
- class External < Base
4
- # @param container [Plot, Subplot, Array]
5
- def initialize container, *args
6
- super(*args)
7
- @container = container
8
- end
9
-
10
- def context_from subject
11
- Set.new
12
- .merge(container_entities)
13
- .merge(container_subplots_for(@container, subject))
14
- .to_a
15
- end
16
-
17
- private
18
-
19
- # @return [Array<Entity>]
20
- def container_entities
21
- if @container.is_a?(World::Entities)
22
- @container.entities
23
- elsif @container.is_a?(Enumerable)
24
- @container
25
- else
26
- raise ArgumentError, "Unable to derive entities from #{@container}"
27
- end
28
- end
29
-
30
- # @return [Array<Entity>]
31
- def container_subplots_for container, subject
32
- return [] unless container.is_a?(Plot::Host)
33
- container.subplots_featuring(subject).flat_map do |subplot|
34
- subplot.entities + container_subplots_for(subplot, subject)
35
- end
36
- end
37
- end
38
- end
39
- end
@@ -1,18 +0,0 @@
1
- module Gamefic
2
- module Query
3
- # Query to retrieve the subject's siblings and all accessible descendants.
4
- #
5
- class Family < Base
6
- def context_from(subject)
7
- result = []
8
- result.concat subquery_accessible(subject.parent)
9
- result.delete subject
10
- subject.children.each { |c|
11
- result.push c
12
- result.concat subquery_accessible(c)
13
- }
14
- result
15
- end
16
- end
17
- end
18
- end
@@ -1,13 +0,0 @@
1
- module Gamefic
2
- module Query
3
- class Itself < Base
4
- def context_from(subject)
5
- [subject]
6
- end
7
-
8
- def include?(subject, object)
9
- return false unless accept?(object) and subject == object
10
- end
11
- end
12
- end
13
- end
@@ -1,75 +0,0 @@
1
- module Gamefic
2
- module Query
3
- class Matches
4
- # The resolved tokens
5
- # @return [Array<Object>]
6
- attr_reader :objects
7
-
8
- # The matching string
9
- # @return [String]
10
- attr_reader :matching
11
-
12
- # The remaining (unmatched) string
13
- # @return [String]
14
- attr_reader :remaining
15
-
16
- def initialize objects, matching, remaining
17
- @objects = objects
18
- @matching = matching
19
- @remaining = remaining
20
- end
21
-
22
- def self.execute objects, description, continued: false
23
- if continued
24
- match_with_remainder objects, description
25
- else
26
- match_without_remainder objects, description
27
- end
28
- end
29
-
30
- class << self
31
- private
32
-
33
- def match_without_remainder objects, description
34
- matches = objects.select{ |e| e.specified?(description) }
35
- if matches.empty?
36
- matching = ''
37
- remaining = description
38
- else
39
- matching = description
40
- remaining = ''
41
- end
42
- Matches.new(matches, matching, remaining)
43
- end
44
-
45
- def match_with_remainder objects, description
46
- matching_objects = objects
47
- matching_text = []
48
- words = description.split(Keywords::SPLIT_REGEXP)
49
- i = 0
50
- words.each { |w|
51
- cursor = inner_match matching_objects, words, matching_text, i, w
52
- break if cursor.empty? or (cursor & matching_objects).empty?
53
- matching_objects = (cursor & matching_objects)
54
- i += 1
55
- }
56
- objects = matching_objects
57
- matching = matching_text.uniq.join(' ')
58
- remaining = words[i..-1].join(' ')
59
- Matches.new(objects, matching, remaining)
60
- end
61
-
62
- def inner_match matching_objects, words, matching_text, i, w
63
- cursor = []
64
- matching_objects.each { |o|
65
- if o.specified?(words[0..i].join(' '), fuzzy: true)
66
- cursor.push o
67
- matching_text.push w
68
- end
69
- }
70
- cursor
71
- end
72
- end
73
- end
74
- end
75
- end
@@ -1,9 +0,0 @@
1
- module Gamefic
2
- module Query
3
- class Parent < Base
4
- def context_from(subject)
5
- subject.parent.nil? ? [] : [subject.parent]
6
- end
7
- end
8
- end
9
- end
@@ -1,13 +0,0 @@
1
- module Gamefic
2
- module Query
3
- class Siblings < Base
4
- def context_from(subject)
5
- result = []
6
- unless subject.parent.nil?
7
- result.concat(subject.parent.children - [subject])
8
- end
9
- result
10
- end
11
- end
12
- end
13
- end
@@ -1,17 +0,0 @@
1
- module Gamefic
2
- module Query
3
- # Query to retrieve all of the subject's ancestors, siblings, and descendants.
4
- #
5
- class Tree < Family
6
- def context_from(subject)
7
- result = super
8
- parent = subject.parent
9
- until parent.nil?
10
- result.unshift parent
11
- parent = parent.parent
12
- end
13
- result
14
- end
15
- end
16
- end
17
- end
@@ -1,142 +0,0 @@
1
- module Gamefic
2
- # An abstract class for building different types of scenes. It can be
3
- # extended either through concrete subclasses or by creating anonymous
4
- # subclasses through a scene helper method like
5
- # `Gamefic::World::Scenes#custom`.
6
- #
7
- class Scene::Base
8
- include Gamefic::Serialize
9
- extend Gamefic::Serialize
10
-
11
- # The scene's primary actor.
12
- #
13
- # @return [Gamefic::Actor]
14
- attr_reader :actor
15
-
16
- # A human-readable string identifying the type of scene.
17
- #
18
- # @return [String]
19
- attr_writer :type
20
-
21
- # The text to display when requesting input.
22
- #
23
- # @return [String]
24
- attr_writer :prompt
25
-
26
- # The input received from the actor.
27
- #
28
- # @return [String]
29
- attr_reader :input
30
-
31
- # @return [Hash{Symbol => Object}]
32
- attr_reader :data
33
-
34
- def initialize actor, **data
35
- @actor = actor
36
- @data = data
37
- post_initialize
38
- end
39
-
40
- # A shortcut for the #data hash.
41
- #
42
- # @param key [Symbol]
43
- # @return [Object]
44
- def [] key
45
- data[key]
46
- end
47
-
48
- def post_initialize
49
- end
50
-
51
- # Set a proc to be executed at the end of the scene.
52
- #
53
- def on_finish &block
54
- @finish_block = block
55
- end
56
-
57
- # Update the scene.
58
- #
59
- def update
60
- @input = actor.queue.shift
61
- finish
62
- end
63
-
64
- # Start the scene.
65
- #
66
- def start
67
- self.class.start_block.call @actor, self unless self.class.start_block.nil?
68
- @actor.entered self if tracked?
69
- end
70
-
71
- # Finish the scene.
72
- #
73
- def finish
74
- @finish_block.call @actor, self unless @finish_block.nil?
75
- @finished = true
76
- end
77
-
78
- # Determine whether the scene's execution is finished.
79
- #
80
- # @return [Boolean]
81
- def finished?
82
- @finished ||= false
83
- end
84
-
85
- # Get a hash that describes the current state of the scene.
86
- #
87
- # @return [Hash]
88
- def state
89
- {
90
- scene: type, prompt: prompt
91
- }
92
- end
93
-
94
- # @yieldparam [Class<Gamefic::Actor>]
95
- # @return [Class<Gamefic::Scene::Base>]
96
- def self.subclass &block
97
- c = Class.new(self) do
98
- on_start &block
99
- end
100
- c
101
- end
102
-
103
- # Get the prompt to be displayed to the user when accepting input.
104
- #
105
- # @return [String] The text to be displayed.
106
- def prompt
107
- @prompt ||= '>'
108
- end
109
-
110
- # Get a String that describes the type of scene.
111
- #
112
- # @return [String]
113
- def type
114
- @type ||= 'Scene'
115
- end
116
-
117
- # @yieldparam [Class<Gamefic::Scene::Base>]
118
- def self.on_start &block
119
- @start_block = block
120
- end
121
-
122
- def tracked?
123
- self.class.tracked?
124
- end
125
-
126
- def tracked= bool
127
- self.class.tracked = bool
128
- end
129
-
130
- class << self
131
- attr_writer :tracked
132
-
133
- def start_block
134
- @start_block
135
- end
136
-
137
- def tracked?
138
- @tracked ||= false
139
- end
140
- end
141
- end
142
- end
@@ -1,29 +0,0 @@
1
- module Gamefic
2
- class Scene::MultipleScene < Scene::MultipleChoice
3
- def option_map
4
- @option_map ||= {}
5
- end
6
-
7
- # @param option [String]
8
- # @param scene [Class<Gamefic::Scene::Base>]
9
- def map option, scene
10
- options.push option
11
- option_map[option] = scene
12
- end
13
-
14
- def finish
15
- get_choice
16
- unless selection.nil?
17
- actor.prepare option_map[selection]
18
- end
19
- end
20
-
21
- def state
22
- entered = {}
23
- option_map.each_pair do |k, v|
24
- entered[k] = actor.entered?(v)
25
- end
26
- super.merge entered: entered
27
- end
28
- end
29
- end