gamefic 1.7.0 → 2.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 (99) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +13 -0
  5. data/.solargraph.yml +5 -0
  6. data/Gemfile +7 -0
  7. data/LICENSE +20 -0
  8. data/README.md +28 -0
  9. data/Rakefile +10 -0
  10. data/gamefic.gemspec +27 -0
  11. data/lib/gamefic.rb +7 -6
  12. data/lib/gamefic/action.rb +38 -28
  13. data/lib/gamefic/active.rb +325 -280
  14. data/lib/gamefic/actor.rb +8 -5
  15. data/lib/gamefic/command.rb +9 -7
  16. data/lib/gamefic/core_ext/array.rb +24 -49
  17. data/lib/gamefic/core_ext/string.rb +25 -16
  18. data/lib/gamefic/describable.rb +21 -23
  19. data/lib/gamefic/element.rb +43 -31
  20. data/lib/gamefic/entity.rb +6 -12
  21. data/lib/gamefic/index.rb +121 -0
  22. data/lib/gamefic/{matchable.rb → keywords.rb} +52 -50
  23. data/lib/gamefic/messaging.rb +43 -44
  24. data/lib/gamefic/node.rb +14 -5
  25. data/lib/gamefic/plot.rb +69 -89
  26. data/lib/gamefic/plot/darkroom.rb +92 -264
  27. data/lib/gamefic/plot/host.rb +42 -48
  28. data/lib/gamefic/plot/snapshot.rb +5 -18
  29. data/lib/gamefic/query.rb +14 -18
  30. data/lib/gamefic/query/base.rb +30 -18
  31. data/lib/gamefic/query/children.rb +0 -0
  32. data/lib/gamefic/query/external.rb +18 -14
  33. data/lib/gamefic/query/family.rb +1 -7
  34. data/lib/gamefic/query/matches.rb +75 -67
  35. data/lib/gamefic/query/parent.rb +0 -0
  36. data/lib/gamefic/query/siblings.rb +0 -0
  37. data/lib/gamefic/query/text.rb +2 -1
  38. data/lib/gamefic/scene.rb +0 -2
  39. data/lib/gamefic/scene/activity.rb +24 -26
  40. data/lib/gamefic/scene/base.rb +64 -8
  41. data/lib/gamefic/scene/conclusion.rb +0 -2
  42. data/lib/gamefic/scene/custom.rb +0 -2
  43. data/lib/gamefic/scene/multiple_choice.rb +18 -3
  44. data/lib/gamefic/scene/multiple_scene.rb +29 -20
  45. data/lib/gamefic/scene/pause.rb +7 -2
  46. data/lib/gamefic/scene/yes_or_no.rb +21 -9
  47. data/lib/gamefic/scriptable.rb +87 -0
  48. data/lib/gamefic/serialize.rb +68 -0
  49. data/lib/gamefic/subplot.rb +29 -35
  50. data/lib/gamefic/syntax.rb +14 -13
  51. data/lib/gamefic/version.rb +3 -3
  52. data/lib/gamefic/world.rb +16 -0
  53. data/lib/gamefic/world/callbacks.rb +135 -0
  54. data/lib/gamefic/world/commands.rb +184 -0
  55. data/lib/gamefic/{plot → world}/entities.rb +30 -29
  56. data/lib/gamefic/{plot → world}/playbook.rb +255 -240
  57. data/lib/gamefic/world/players.rb +21 -0
  58. data/lib/gamefic/world/scenes.rb +226 -0
  59. metadata +41 -92
  60. data/bin/gamefic +0 -9
  61. data/lib/gamefic/engine.rb +0 -7
  62. data/lib/gamefic/engine/base.rb +0 -59
  63. data/lib/gamefic/engine/tty.rb +0 -24
  64. data/lib/gamefic/grammar.rb +0 -13
  65. data/lib/gamefic/grammar/conjugator.rb +0 -20
  66. data/lib/gamefic/grammar/gender.rb +0 -11
  67. data/lib/gamefic/grammar/person.rb +0 -10
  68. data/lib/gamefic/grammar/plural.rb +0 -13
  69. data/lib/gamefic/grammar/pronouns.rb +0 -106
  70. data/lib/gamefic/grammar/tense.rb +0 -6
  71. data/lib/gamefic/grammar/verb_set.rb +0 -43
  72. data/lib/gamefic/grammar/verbs.rb +0 -26
  73. data/lib/gamefic/grammar/word_adapter.rb +0 -49
  74. data/lib/gamefic/plot/articles.rb +0 -22
  75. data/lib/gamefic/plot/callbacks.rb +0 -126
  76. data/lib/gamefic/plot/commands.rb +0 -120
  77. data/lib/gamefic/plot/players.rb +0 -15
  78. data/lib/gamefic/plot/scenes.rb +0 -187
  79. data/lib/gamefic/plot/theater.rb +0 -73
  80. data/lib/gamefic/plot/you_mount.rb +0 -22
  81. data/lib/gamefic/script.rb +0 -13
  82. data/lib/gamefic/script/base.rb +0 -42
  83. data/lib/gamefic/script/file.rb +0 -14
  84. data/lib/gamefic/script/text.rb +0 -14
  85. data/lib/gamefic/shell.rb +0 -76
  86. data/lib/gamefic/source.rb +0 -14
  87. data/lib/gamefic/source/base.rb +0 -12
  88. data/lib/gamefic/source/file.rb +0 -23
  89. data/lib/gamefic/source/text.rb +0 -16
  90. data/lib/gamefic/tester.rb +0 -19
  91. data/lib/gamefic/text.rb +0 -8
  92. data/lib/gamefic/text/ansi.rb +0 -53
  93. data/lib/gamefic/text/html.rb +0 -68
  94. data/lib/gamefic/text/html/conversions.rb +0 -250
  95. data/lib/gamefic/text/html/entities.rb +0 -9
  96. data/lib/gamefic/tty.rb +0 -10
  97. data/lib/gamefic/user.rb +0 -7
  98. data/lib/gamefic/user/base.rb +0 -29
  99. data/lib/gamefic/user/tty.rb +0 -38
@@ -1,18 +1,14 @@
1
- #require 'gamefic/keywords'
2
-
3
- module Gamefic
4
-
5
- module Query
6
- autoload :Base, 'gamefic/query/base'
7
- autoload :Children, 'gamefic/query/children'
8
- autoload :Descendants, 'gamefic/query/descendants'
9
- autoload :External, 'gamefic/query/external'
10
- autoload :Family, 'gamefic/query/family'
11
- autoload :Itself, 'gamefic/query/itself'
12
- autoload :Matches, 'gamefic/query/matches'
13
- autoload :Parent, 'gamefic/query/parent'
14
- autoload :Siblings, 'gamefic/query/siblings'
15
- autoload :Text, 'gamefic/query/text'
16
- end
17
-
18
- end
1
+ module Gamefic
2
+ module Query
3
+ autoload :Base, 'gamefic/query/base'
4
+ autoload :Children, 'gamefic/query/children'
5
+ autoload :Descendants, 'gamefic/query/descendants'
6
+ autoload :External, 'gamefic/query/external'
7
+ autoload :Family, 'gamefic/query/family'
8
+ autoload :Itself, 'gamefic/query/itself'
9
+ autoload :Matches, 'gamefic/query/matches'
10
+ autoload :Parent, 'gamefic/query/parent'
11
+ autoload :Siblings, 'gamefic/query/siblings'
12
+ autoload :Text, 'gamefic/query/text'
13
+ end
14
+ end
@@ -9,6 +9,13 @@ module Gamefic
9
9
  @arguments = args
10
10
  end
11
11
 
12
+ # Determine whether the query allows ambiguous entity references.
13
+ # If false, actions that use this query will only be valid if the token
14
+ # passed into it resolves to a single entity. If true, actions will
15
+ # accept an array of matching entities instead.
16
+ # Queries are not ambiguous by default (ambiguous? == false).
17
+ #
18
+ # @return [Boolean]
12
19
  def ambiguous?
13
20
  false
14
21
  end
@@ -36,8 +43,8 @@ module Gamefic
36
43
  return Matches.new(drill, token, '') unless drill.length != 1
37
44
  return Matches.new([], '', token)
38
45
  end
39
- result = available.select{ |e| e.match?(token) }
40
- result = available.select{ |e| e.match?(token, fuzzy: true) } if result.empty?
46
+ result = available.select{ |e| e.specified?(token) }
47
+ result = available.select{ |e| e.specified?(token, fuzzy: true) } if result.empty?
41
48
  result.keep_if{ |e| accept? e }
42
49
  Matches.new(result, (result.empty? ? '' : token), (result.empty? ? token : ''))
43
50
  end
@@ -52,15 +59,6 @@ module Gamefic
52
59
  if @precision.nil?
53
60
  @precision = 1
54
61
  arguments.each { |a|
55
- #if a.kind_of?(Symbol) or a.kind_of?(Regexp)
56
- # @precision += 1
57
- #elsif a.kind_of?(Class)
58
- # @precision += (count_superclasses(a) * 100)
59
- #elsif a.kind_of?(Module)
60
- # @precision += 10
61
- #elsif a.kind_of?(Object)
62
- # @precision += 1000
63
- #end
64
62
  if a.kind_of?(Class)
65
63
  @precision += 100
66
64
  elsif a.kind_of?(Gamefic::Entity)
@@ -77,9 +75,12 @@ module Gamefic
77
75
  end
78
76
 
79
77
  def signature
80
- "#{self.class.to_s.downcase}(#{@arguments.join(',')})"
78
+ "#{self.class.to_s.split('::').last.downcase}(#{simplify_arguments.join(', ')})"
81
79
  end
82
80
 
81
+ # Determine whether the specified entity passes the query's arguments.
82
+ #
83
+ # @return [Boolean]
83
84
  def accept?(entity)
84
85
  result = true
85
86
  arguments.each { |a|
@@ -98,13 +99,14 @@ module Gamefic
98
99
  end
99
100
 
100
101
  protected
101
-
102
- # Return an array of the entity's children. If the child is neighborly,
102
+
103
+ # Return an array of the entity's children. If the child is accessible,
103
104
  # recursively append its children.
104
105
  # The result will NOT include the original entity itself.
105
106
  #
106
107
  # @return [Array<Object>]
107
108
  def subquery_accessible entity
109
+ return [] if entity.nil?
108
110
  result = []
109
111
  if entity.accessible?
110
112
  entity.children.each { |c|
@@ -117,6 +119,16 @@ module Gamefic
117
119
 
118
120
  private
119
121
 
122
+ def simplify_arguments
123
+ arguments.map do |a|
124
+ if a.kind_of?(Class) or a.kind_of?(Object)
125
+ a.to_s.split('::').last.downcase
126
+ else
127
+ a.to_s.downcase
128
+ end
129
+ end
130
+ end
131
+
120
132
  def nested?(token)
121
133
  !token.match(NEST_REGEXP).nil?
122
134
  end
@@ -124,13 +136,13 @@ module Gamefic
124
136
  def denest(objects, token)
125
137
  parts = token.split(NEST_REGEXP)
126
138
  current = parts.pop
127
- last_result = objects.select{ |e| e.match?(current) }
128
- last_result = objects.select{ |e| e.match?(current, fuzzy: true) } if last_result.empty?
139
+ last_result = objects.select{ |e| e.specified?(current) }
140
+ last_result = objects.select{ |e| e.specified?(current, fuzzy: true) } if last_result.empty?
129
141
  result = last_result
130
142
  while parts.length > 0
131
143
  current = "#{parts.last} #{current}"
132
- result = last_result.select{ |e| e.match?(current) }
133
- result = last_result.select{ |e| e.match?(current, fuzzy: true) } if result.empty?
144
+ result = last_result.select{ |e| e.specified?(current) }
145
+ result = last_result.select{ |e| e.specified?(current, fuzzy: true) } if result.empty?
134
146
  break if result.empty?
135
147
  parts.pop
136
148
  last_result = result
File without changes
@@ -1,14 +1,18 @@
1
- module Gamefic
2
- module Query
3
- class External < Base
4
- def initialize objects, *args
5
- super(*args)
6
- @objects = objects
7
- end
8
-
9
- def context_from subject
10
- @objects
11
- end
12
- end
13
- end
14
- end
1
+ module Gamefic
2
+ module Query
3
+ class External < Base
4
+ def initialize objects, *args
5
+ super(*args)
6
+ @objects = objects
7
+ end
8
+
9
+ def context_from subject
10
+ @objects
11
+ end
12
+
13
+ def accept?(entity)
14
+ @objects.include?(entity) && super(entity)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -3,13 +3,7 @@ module Gamefic
3
3
  class Family < Base
4
4
  def context_from(subject)
5
5
  result = []
6
- top = subject.parent
7
- unless top.nil?
8
- #until top.parent.nil?
9
- # top = top.parent
10
- #end
11
- result.concat subquery_accessible(top)
12
- end
6
+ result.concat subquery_accessible(subject.parent)
13
7
  result.delete subject
14
8
  subject.children.each { |c|
15
9
  result.push c
@@ -1,67 +1,75 @@
1
- module Gamefic
2
- module Query
3
- class Matches
4
- attr_accessor :objects, :matching, :remaining
5
-
6
- def initialize objects, matching, remaining
7
- @objects = objects
8
- @matching = matching
9
- @remaining = remaining
10
- end
11
-
12
- def self.execute objects, description, continued: false
13
- if continued
14
- match_with_remainder objects, description
15
- else
16
- match_without_remainder objects, description
17
- end
18
- end
19
-
20
- class << self
21
- private
22
-
23
- def match_without_remainder objects, description
24
- matches = objects.select{ |e| e.match?(description) }
25
- if matches.empty?
26
- matching = ''
27
- remaining = description
28
- else
29
- matching = description
30
- remaining = ''
31
- end
32
- Matches.new(matches, matching, remaining)
33
- end
34
-
35
- def match_with_remainder objects, description
36
- matching_objects = objects
37
- matching_text = []
38
- words = description.split(Matchable::SPLIT_REGEXP)
39
- i = 0
40
- #cursor = []
41
- words.each { |w|
42
- cursor = inner_match matching_objects, words, matching_text, i, w
43
- break if cursor.empty? or (cursor & matching_objects).empty?
44
- matching_objects = (cursor & matching_objects)
45
- i += 1
46
- }
47
- objects = matching_objects
48
- matching = matching_text.uniq.join(' ')
49
- remaining = words[i..-1].join(' ')
50
- m = Matches.new(objects, matching, remaining)
51
- m
52
- end
53
-
54
- def inner_match matching_objects, words, matching_text, i, w
55
- cursor = []
56
- matching_objects.each { |o|
57
- if o.match?(words[0..i].join(' '), fuzzy: true)
58
- cursor.push o
59
- matching_text.push w
60
- end
61
- }
62
- cursor
63
- end
64
- end
65
- end
66
- end
67
- end
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
File without changes
File without changes
@@ -10,7 +10,8 @@ module Gamefic
10
10
  super
11
11
  end
12
12
  def resolve(subject, token, continued: false)
13
- parts = token.split(Matchable::SPLIT_REGEXP)
13
+ return Matches.new([], '', token) unless accept?(token)
14
+ parts = token.split(Keywords::SPLIT_REGEXP)
14
15
  cursor = []
15
16
  matches = []
16
17
  i = 0
@@ -1,5 +1,4 @@
1
1
  module Gamefic
2
-
3
2
  module Scene
4
3
  autoload :Base, 'gamefic/scene/base'
5
4
  autoload :Custom, 'gamefic/scene/custom'
@@ -10,5 +9,4 @@ module Gamefic
10
9
  autoload :MultipleScene, 'gamefic/scene/multiple_scene'
11
10
  autoload :YesOrNo, 'gamefic/scene/yes_or_no'
12
11
  end
13
-
14
12
  end
@@ -1,26 +1,24 @@
1
- module Gamefic
2
-
3
- # Active Scenes handle the default command prompt, where input is parsed
4
- # into an Action performed by the Character. This is the default scene in
5
- # a Plot.
6
- #
7
- class Scene::Activity < Scene::Base
8
- def post_initialize
9
- self.type = 'Activity'
10
- end
11
-
12
- def finish
13
- super
14
- o = nil
15
- o = actor.perform input.strip unless input.to_s.strip.empty?
16
- actor.performed o
17
- end
18
-
19
- class << self
20
- def type
21
- 'Activity'
22
- end
23
- end
24
- end
25
-
26
- end
1
+ module Gamefic
2
+ # Active Scenes handle the default command prompt, where input is parsed
3
+ # into an Action performed by the Character. This is the default scene in
4
+ # a Plot.
5
+ #
6
+ class Scene::Activity < Scene::Base
7
+ def post_initialize
8
+ self.type = 'Activity'
9
+ end
10
+
11
+ def finish
12
+ super
13
+ o = nil
14
+ o = actor.perform input.strip unless input.to_s.strip.empty?
15
+ actor.performed o
16
+ end
17
+
18
+ class << self
19
+ def type
20
+ 'Activity'
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,61 +1,100 @@
1
1
  module Gamefic
2
-
3
2
  # The Base Scene is not intended for instantiation. Other Scene classes
4
3
  # should inherit from it.
5
4
  #
6
5
  class Scene::Base
6
+ # The scene's primary actor.
7
+ #
8
+ # @return [Gamefic::Actor]
7
9
  attr_reader :actor
10
+
11
+ # A human-readable string identifying the type of scene.
12
+ #
13
+ # @return [String]
8
14
  attr_writer :type
15
+
16
+ # The text to display when requesting input.
17
+ #
18
+ # @return [String]
9
19
  attr_writer :prompt
20
+
21
+ # The input received from the actor.
22
+ #
23
+ # @return [String]
10
24
  attr_reader :input
11
25
 
12
- def initialize actor
26
+ # @return [Hash{Symbol => Object}]
27
+ attr_reader :data
28
+
29
+ def initialize actor, **data
13
30
  @actor = actor
31
+ @data = data
14
32
  post_initialize
15
33
  end
16
34
 
35
+ # A shortcut for the #data hash.
36
+ #
37
+ # @param key [Symbol]
38
+ # @return [Object]
39
+ def [] key
40
+ data[key]
41
+ end
42
+
17
43
  def post_initialize
18
44
  end
19
45
 
46
+ # Set a proc to be executed at the end of the scene.
47
+ #
20
48
  def on_finish &block
21
49
  @finish_block = block
22
50
  end
23
51
 
52
+ # Update the scene.
53
+ #
24
54
  def update
25
55
  @input = actor.queue.shift
26
56
  finish
27
57
  end
28
58
 
59
+ # Start the scene.
60
+ #
29
61
  def start
30
62
  self.class.start_block.call @actor, self unless self.class.start_block.nil?
63
+ @actor.entered self if tracked?
31
64
  end
32
65
 
66
+ # Finish the scene.
67
+ #
33
68
  def finish
34
69
  @finish_block.call @actor, self unless @finish_block.nil?
35
70
  @finished = true
36
71
  end
37
72
 
73
+ # Determine whether the scene's execution is finished.
74
+ #
75
+ # @return [Boolean]
38
76
  def finished?
39
77
  @finished ||= false
40
78
  end
41
79
 
42
- def flush
43
- @state.clear
44
- end
45
-
80
+ # Get a hash that describes the current state of the scene.
81
+ #
82
+ # @return [Hash]
46
83
  def state
47
84
  {
48
85
  scene: type, prompt: prompt
49
86
  }
50
87
  end
51
88
 
89
+ # @yieldparam [Class<Gamefic::Actor>]
90
+ # @return [Class<Gamefic::Scene::Base>]
52
91
  def self.subclass &block
53
92
  c = Class.new(self) do
54
93
  on_start &block
55
94
  end
56
95
  c
57
96
  end
58
-
97
+
59
98
  # Get the prompt to be displayed to the user when accepting input.
60
99
  #
61
100
  # @return [String] The text to be displayed.
@@ -63,19 +102,36 @@ module Gamefic
63
102
  @prompt ||= '>'
64
103
  end
65
104
 
105
+ # Get a String that describes the type of scene.
106
+ #
107
+ # @return [String]
66
108
  def type
67
109
  @type ||= 'Scene'
68
110
  end
69
111
 
112
+ # @yieldparam [Class<Gamefic::Scene::Base>]
70
113
  def self.on_start &block
71
114
  @start_block = block
72
115
  end
73
116
 
117
+ def tracked?
118
+ self.class.tracked?
119
+ end
120
+
121
+ def tracked= bool
122
+ self.class.tracked = bool
123
+ end
124
+
74
125
  class << self
126
+ attr_writer :tracked
127
+
75
128
  def start_block
76
129
  @start_block
77
130
  end
131
+
132
+ def tracked?
133
+ @tracked ||= false
134
+ end
78
135
  end
79
136
  end
80
-
81
137
  end