gamefic 2.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +41 -0
  3. data/.rspec-opal +2 -0
  4. data/.rubocop.yml +4 -1
  5. data/.solargraph.yml +20 -3
  6. data/CHANGELOG.md +15 -0
  7. data/Gemfile +0 -4
  8. data/Rakefile +11 -1
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/gamefic.gemspec +5 -2
  12. data/lib/gamefic/action.rb +53 -173
  13. data/lib/gamefic/active/cue.rb +25 -0
  14. data/lib/gamefic/active/epic.rb +68 -0
  15. data/lib/gamefic/active/messaging.rb +43 -0
  16. data/lib/gamefic/active/take.rb +69 -0
  17. data/lib/gamefic/active.rb +97 -192
  18. data/lib/gamefic/actor.rb +2 -0
  19. data/lib/gamefic/block.rb +28 -0
  20. data/lib/gamefic/command.rb +16 -6
  21. data/lib/gamefic/core_ext/array.rb +4 -4
  22. data/lib/gamefic/core_ext/string.rb +10 -5
  23. data/lib/gamefic/describable.rb +39 -65
  24. data/lib/gamefic/dispatcher.rb +67 -29
  25. data/lib/gamefic/entity.rb +44 -19
  26. data/lib/gamefic/logging.rb +32 -0
  27. data/lib/gamefic/messenger.rb +66 -0
  28. data/lib/gamefic/narrative.rb +104 -0
  29. data/lib/gamefic/node.rb +44 -53
  30. data/lib/gamefic/plot.rb +60 -93
  31. data/lib/gamefic/props/default.rb +41 -0
  32. data/lib/gamefic/props/multiple_choice.rb +65 -0
  33. data/lib/gamefic/props/pause.rb +11 -0
  34. data/lib/gamefic/props/yes_or_no.rb +21 -0
  35. data/lib/gamefic/props.rb +10 -0
  36. data/lib/gamefic/query/base.rb +45 -126
  37. data/lib/gamefic/query/general.rb +46 -0
  38. data/lib/gamefic/query/result.rb +20 -0
  39. data/lib/gamefic/query/scoped.rb +41 -0
  40. data/lib/gamefic/query/text.rb +30 -31
  41. data/lib/gamefic/query.rb +7 -15
  42. data/lib/gamefic/response.rb +118 -0
  43. data/lib/gamefic/rulebook/calls.rb +90 -0
  44. data/lib/gamefic/rulebook/events.rb +79 -0
  45. data/lib/gamefic/rulebook/hooks.rb +57 -0
  46. data/lib/gamefic/rulebook/scenes.rb +68 -0
  47. data/lib/gamefic/rulebook.rb +139 -0
  48. data/lib/gamefic/scanner.rb +103 -0
  49. data/lib/gamefic/scene/activity.rb +9 -17
  50. data/lib/gamefic/scene/conclusion.rb +6 -5
  51. data/lib/gamefic/scene/default.rb +88 -0
  52. data/lib/gamefic/scene/multiple_choice.rb +14 -69
  53. data/lib/gamefic/scene/pause.rb +9 -13
  54. data/lib/gamefic/scene/yes_or_no.rb +6 -46
  55. data/lib/gamefic/scene.rb +11 -7
  56. data/lib/gamefic/scope/base.rb +44 -0
  57. data/lib/gamefic/scope/children.rb +16 -0
  58. data/lib/gamefic/scope/family.rb +20 -0
  59. data/lib/gamefic/scope/myself.rb +13 -0
  60. data/lib/gamefic/scope/parent.rb +13 -0
  61. data/lib/gamefic/scope/siblings.rb +14 -0
  62. data/lib/gamefic/scope.rb +8 -0
  63. data/lib/gamefic/scriptable/actions.rb +156 -0
  64. data/lib/gamefic/scriptable/entities.rb +76 -0
  65. data/lib/gamefic/scriptable/events.rb +65 -0
  66. data/lib/gamefic/scriptable/proxy.rb +55 -0
  67. data/lib/gamefic/scriptable/queries.rb +73 -0
  68. data/lib/gamefic/scriptable/scenes.rb +162 -0
  69. data/lib/gamefic/scriptable.rb +167 -68
  70. data/lib/gamefic/snapshot.rb +36 -0
  71. data/lib/gamefic/stage.rb +51 -0
  72. data/lib/gamefic/subplot.rb +51 -79
  73. data/lib/gamefic/syntax/template.rb +67 -0
  74. data/lib/gamefic/syntax.rb +102 -83
  75. data/lib/gamefic/vault.rb +50 -0
  76. data/lib/gamefic/version.rb +3 -1
  77. data/lib/gamefic.rb +26 -15
  78. data/spec-opal/spec_helper.rb +24 -0
  79. metadata +92 -29
  80. data/lib/gamefic/element.rb +0 -46
  81. data/lib/gamefic/keywords.rb +0 -52
  82. data/lib/gamefic/messaging.rb +0 -43
  83. data/lib/gamefic/plot/darkroom.rb +0 -120
  84. data/lib/gamefic/plot/host.rb +0 -42
  85. data/lib/gamefic/plot/snapshot.rb +0 -27
  86. data/lib/gamefic/query/children.rb +0 -9
  87. data/lib/gamefic/query/descendants.rb +0 -15
  88. data/lib/gamefic/query/external.rb +0 -39
  89. data/lib/gamefic/query/family.rb +0 -18
  90. data/lib/gamefic/query/itself.rb +0 -13
  91. data/lib/gamefic/query/matches.rb +0 -75
  92. data/lib/gamefic/query/parent.rb +0 -9
  93. data/lib/gamefic/query/siblings.rb +0 -13
  94. data/lib/gamefic/query/tree.rb +0 -17
  95. data/lib/gamefic/scene/base.rb +0 -142
  96. data/lib/gamefic/scene/multiple_scene.rb +0 -29
  97. data/lib/gamefic/serialize.rb +0 -196
  98. data/lib/gamefic/world/callbacks.rb +0 -135
  99. data/lib/gamefic/world/commands.rb +0 -173
  100. data/lib/gamefic/world/entities.rb +0 -98
  101. data/lib/gamefic/world/playbook.rb +0 -225
  102. data/lib/gamefic/world/players.rb +0 -37
  103. data/lib/gamefic/world/scenes.rb +0 -226
  104. 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