gamefic 1.6.0 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +16 -0
  5. data/.solargraph.yml +5 -0
  6. data/CHANGELOG.md +6 -0
  7. data/Gemfile +7 -0
  8. data/LICENSE +20 -0
  9. data/README.md +28 -0
  10. data/Rakefile +10 -0
  11. data/gamefic.gemspec +27 -0
  12. data/lib/gamefic.rb +11 -8
  13. data/lib/gamefic/action.rb +68 -58
  14. data/lib/gamefic/active.rb +331 -0
  15. data/lib/gamefic/actor.rb +8 -0
  16. data/lib/gamefic/command.rb +9 -7
  17. data/lib/gamefic/core_ext/array.rb +27 -49
  18. data/lib/gamefic/core_ext/string.rb +25 -16
  19. data/lib/gamefic/describable.rb +37 -22
  20. data/lib/gamefic/element.rb +47 -0
  21. data/lib/gamefic/entity.rb +24 -48
  22. data/lib/gamefic/{matchable.rb → keywords.rb} +52 -50
  23. data/lib/gamefic/messaging.rb +43 -45
  24. data/lib/gamefic/node.rb +14 -5
  25. data/lib/gamefic/plot.rb +73 -85
  26. data/lib/gamefic/plot/darkroom.rb +80 -0
  27. data/lib/gamefic/plot/host.rb +42 -46
  28. data/lib/gamefic/plot/snapshot.rb +14 -214
  29. data/lib/gamefic/query.rb +15 -17
  30. data/lib/gamefic/query/base.rb +51 -42
  31. data/lib/gamefic/query/children.rb +0 -0
  32. data/lib/gamefic/query/descendants.rb +2 -2
  33. data/lib/gamefic/query/external.rb +18 -0
  34. data/lib/gamefic/query/family.rb +3 -7
  35. data/lib/gamefic/query/matches.rb +75 -67
  36. data/lib/gamefic/query/parent.rb +0 -0
  37. data/lib/gamefic/query/siblings.rb +0 -0
  38. data/lib/gamefic/query/text.rb +12 -12
  39. data/lib/gamefic/query/tree.rb +17 -0
  40. data/lib/gamefic/scene.rb +1 -5
  41. data/lib/gamefic/scene/{active.rb → activity.rb} +4 -6
  42. data/lib/gamefic/scene/base.rb +77 -13
  43. data/lib/gamefic/scene/conclusion.rb +0 -2
  44. data/lib/gamefic/scene/custom.rb +0 -2
  45. data/lib/gamefic/scene/multiple_choice.rb +18 -16
  46. data/lib/gamefic/scene/multiple_scene.rb +29 -20
  47. data/lib/gamefic/scene/pause.rb +7 -2
  48. data/lib/gamefic/scene/yes_or_no.rb +21 -9
  49. data/lib/gamefic/scriptable.rb +88 -0
  50. data/lib/gamefic/serialize.rb +223 -0
  51. data/lib/gamefic/subplot.rb +47 -51
  52. data/lib/gamefic/syntax.rb +15 -13
  53. data/lib/gamefic/version.rb +3 -3
  54. data/lib/gamefic/world.rb +18 -0
  55. data/lib/gamefic/world/callbacks.rb +135 -0
  56. data/lib/gamefic/world/commands.rb +184 -0
  57. data/lib/gamefic/world/entities.rb +98 -0
  58. data/lib/gamefic/{plot → world}/playbook.rb +245 -236
  59. data/lib/gamefic/world/players.rb +37 -0
  60. data/lib/gamefic/world/scenes.rb +226 -0
  61. metadata +40 -108
  62. data/bin/gamefic +0 -9
  63. data/lib/gamefic/character.rb +0 -232
  64. data/lib/gamefic/character/state.rb +0 -12
  65. data/lib/gamefic/engine.rb +0 -7
  66. data/lib/gamefic/engine/base.rb +0 -66
  67. data/lib/gamefic/engine/tty.rb +0 -24
  68. data/lib/gamefic/grammar.rb +0 -13
  69. data/lib/gamefic/grammar/conjugator.rb +0 -20
  70. data/lib/gamefic/grammar/gender.rb +0 -11
  71. data/lib/gamefic/grammar/person.rb +0 -10
  72. data/lib/gamefic/grammar/plural.rb +0 -13
  73. data/lib/gamefic/grammar/pronouns.rb +0 -105
  74. data/lib/gamefic/grammar/tense.rb +0 -6
  75. data/lib/gamefic/grammar/verb_set.rb +0 -43
  76. data/lib/gamefic/grammar/verbs.rb +0 -26
  77. data/lib/gamefic/grammar/word_adapter.rb +0 -49
  78. data/lib/gamefic/plot/articles.rb +0 -22
  79. data/lib/gamefic/plot/callbacks.rb +0 -127
  80. data/lib/gamefic/plot/commands.rb +0 -121
  81. data/lib/gamefic/plot/entities.rb +0 -88
  82. data/lib/gamefic/plot/players.rb +0 -15
  83. data/lib/gamefic/plot/scenes.rb +0 -149
  84. data/lib/gamefic/plot/theater.rb +0 -73
  85. data/lib/gamefic/plot/you_mount.rb +0 -22
  86. data/lib/gamefic/script.rb +0 -13
  87. data/lib/gamefic/script/base.rb +0 -42
  88. data/lib/gamefic/script/file.rb +0 -14
  89. data/lib/gamefic/script/text.rb +0 -14
  90. data/lib/gamefic/shell.rb +0 -76
  91. data/lib/gamefic/source.rb +0 -14
  92. data/lib/gamefic/source/base.rb +0 -12
  93. data/lib/gamefic/source/file.rb +0 -23
  94. data/lib/gamefic/source/text.rb +0 -16
  95. data/lib/gamefic/tester.rb +0 -19
  96. data/lib/gamefic/text.rb +0 -8
  97. data/lib/gamefic/text/ansi.rb +0 -53
  98. data/lib/gamefic/text/html.rb +0 -68
  99. data/lib/gamefic/text/html/conversions.rb +0 -250
  100. data/lib/gamefic/text/html/entities.rb +0 -9
  101. data/lib/gamefic/tty.rb +0 -10
  102. data/lib/gamefic/user.rb +0 -8
  103. data/lib/gamefic/user/base.rb +0 -15
  104. data/lib/gamefic/user/buffer.rb +0 -32
  105. data/lib/gamefic/user/tty.rb +0 -54
File without changes
@@ -5,9 +5,9 @@ module Gamefic
5
5
  result = []
6
6
  children = super
7
7
  result.concat children
8
- children.each { |c|
8
+ children.each do |c|
9
9
  result.concat subquery_accessible(c)
10
- }
10
+ end
11
11
  result
12
12
  end
13
13
  end
@@ -0,0 +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
+
13
+ def accept?(entity)
14
+ @objects.include?(entity) && super(entity)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,15 +1,11 @@
1
1
  module Gamefic
2
2
  module Query
3
+ # Query to retrieve the subject's siblings and all accessible descendants.
4
+ #
3
5
  class Family < Base
4
6
  def context_from(subject)
5
7
  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
8
+ result.concat subquery_accessible(subject.parent)
13
9
  result.delete subject
14
10
  subject.children.each { |c|
15
11
  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
@@ -2,15 +2,17 @@ module Gamefic
2
2
  module Query
3
3
  class Text < Base
4
4
  def initialize *arguments
5
- arguments.each { |a|
6
- if (a.kind_of?(Symbol) or a.kind_of?(String)) and !a.to_s.end_with?('?')
5
+ arguments.each do |a|
6
+ if (a.kind_of?(Symbol) || a.kind_of?(String)) && !a.to_s.end_with?('?')
7
7
  raise ArgumentError.new("Text query arguments can only be boolean method names (:method?) or regular expressions")
8
8
  end
9
- }
9
+ end
10
10
  super
11
11
  end
12
- def resolve(subject, token, continued: false)
13
- parts = token.split(Matchable::SPLIT_REGEXP)
12
+
13
+ def resolve _subject, token, continued: false
14
+ return Matches.new([], '', token) unless accept?(token)
15
+ parts = token.split(Keywords::SPLIT_REGEXP)
14
16
  cursor = []
15
17
  matches = []
16
18
  i = 0
@@ -21,20 +23,18 @@ module Gamefic
21
23
  }
22
24
  if continued
23
25
  Matches.new([matches.join(' ')], matches.join(' '), parts[i..-1].join(' '))
26
+ elsif matches.length == parts.length
27
+ Matches.new([matches.join(' ')], matches.join(' '), '')
24
28
  else
25
- if matches.length == parts.length
26
- Matches.new([matches.join(' ')], matches.join(' '), '')
27
- else
28
- Matches.new([], '', parts.join(' '))
29
- end
29
+ Matches.new([], '', parts.join(' '))
30
30
  end
31
31
  end
32
32
 
33
- def include?(subject, token)
33
+ def include? _subject, token
34
34
  accept?(token)
35
35
  end
36
36
 
37
- def accept?(entity)
37
+ def accept? entity
38
38
  return false unless entity.kind_of?(String) and !entity.empty?
39
39
  super
40
40
  end
@@ -0,0 +1,17 @@
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,16 +1,12 @@
1
- #require 'gamefic/scene_data'
2
-
3
1
  module Gamefic
4
-
5
2
  module Scene
6
3
  autoload :Base, 'gamefic/scene/base'
7
4
  autoload :Custom, 'gamefic/scene/custom'
8
- autoload :Active, 'gamefic/scene/active'
5
+ autoload :Activity, 'gamefic/scene/activity'
9
6
  autoload :Pause, 'gamefic/scene/pause'
10
7
  autoload :Conclusion, 'gamefic/scene/conclusion'
11
8
  autoload :MultipleChoice, 'gamefic/scene/multiple_choice'
12
9
  autoload :MultipleScene, 'gamefic/scene/multiple_scene'
13
10
  autoload :YesOrNo, 'gamefic/scene/yes_or_no'
14
11
  end
15
-
16
12
  end
@@ -1,26 +1,24 @@
1
1
  module Gamefic
2
-
3
2
  # Active Scenes handle the default command prompt, where input is parsed
4
3
  # into an Action performed by the Character. This is the default scene in
5
4
  # a Plot.
6
5
  #
7
- class Scene::Active < Scene::Base
6
+ class Scene::Activity < Scene::Base
8
7
  def post_initialize
9
- self.type = 'Active'
8
+ self.type = 'Activity'
10
9
  end
11
10
 
12
11
  def finish
13
12
  super
14
13
  o = nil
15
- o = actor.perform input.strip unless input.nil?
14
+ o = actor.perform input.strip unless input.to_s.strip.empty?
16
15
  actor.performed o
17
16
  end
18
17
 
19
18
  class << self
20
19
  def type
21
- 'Active'
20
+ 'Activity'
22
21
  end
23
22
  end
24
23
  end
25
-
26
24
  end
@@ -1,56 +1,103 @@
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
+ include Gamefic::Serialize
7
+ extend Gamefic::Serialize
8
+
9
+ # The scene's primary actor.
10
+ #
11
+ # @return [Gamefic::Actor]
7
12
  attr_reader :actor
13
+
14
+ # A human-readable string identifying the type of scene.
15
+ #
16
+ # @return [String]
8
17
  attr_writer :type
18
+
19
+ # The text to display when requesting input.
20
+ #
21
+ # @return [String]
9
22
  attr_writer :prompt
23
+
24
+ # The input received from the actor.
25
+ #
26
+ # @return [String]
10
27
  attr_reader :input
11
28
 
12
- def initialize actor
29
+ # @return [Hash{Symbol => Object}]
30
+ attr_reader :data
31
+
32
+ def initialize actor, **data
13
33
  @actor = actor
34
+ @data = data
14
35
  post_initialize
15
36
  end
16
37
 
38
+ # A shortcut for the #data hash.
39
+ #
40
+ # @param key [Symbol]
41
+ # @return [Object]
42
+ def [] key
43
+ data[key]
44
+ end
45
+
17
46
  def post_initialize
18
47
  end
19
48
 
49
+ # Set a proc to be executed at the end of the scene.
50
+ #
20
51
  def on_finish &block
21
52
  @finish_block = block
22
53
  end
23
54
 
55
+ # Update the scene.
56
+ #
24
57
  def update
25
58
  @input = actor.queue.shift
26
59
  finish
27
60
  end
28
61
 
62
+ # Start the scene.
63
+ #
29
64
  def start
30
- self.class.initialize_block.call @actor, self unless self.class.initialize_block.nil?
65
+ self.class.start_block.call @actor, self unless self.class.start_block.nil?
66
+ @actor.entered self if tracked?
31
67
  end
32
68
 
69
+ # Finish the scene.
70
+ #
33
71
  def finish
34
72
  @finish_block.call @actor, self unless @finish_block.nil?
73
+ @finished = true
35
74
  end
36
75
 
37
- def flush
38
- @state.clear
76
+ # Determine whether the scene's execution is finished.
77
+ #
78
+ # @return [Boolean]
79
+ def finished?
80
+ @finished ||= false
39
81
  end
40
82
 
83
+ # Get a hash that describes the current state of the scene.
84
+ #
85
+ # @return [Hash]
41
86
  def state
42
87
  {
43
- scene: type, prompt: prompt, input: input #, output: actor.messages, busy: !actor.queue.empty?
88
+ scene: type, prompt: prompt
44
89
  }
45
90
  end
46
91
 
92
+ # @yieldparam [Class<Gamefic::Actor>]
93
+ # @return [Class<Gamefic::Scene::Base>]
47
94
  def self.subclass &block
48
95
  c = Class.new(self) do
49
- on_initialize &block
96
+ on_start &block
50
97
  end
51
98
  c
52
99
  end
53
-
100
+
54
101
  # Get the prompt to be displayed to the user when accepting input.
55
102
  #
56
103
  # @return [String] The text to be displayed.
@@ -58,19 +105,36 @@ module Gamefic
58
105
  @prompt ||= '>'
59
106
  end
60
107
 
108
+ # Get a String that describes the type of scene.
109
+ #
110
+ # @return [String]
61
111
  def type
62
112
  @type ||= 'Scene'
63
113
  end
64
114
 
65
- def self.on_initialize &block
66
- @initialize_block = block
115
+ # @yieldparam [Class<Gamefic::Scene::Base>]
116
+ def self.on_start &block
117
+ @start_block = block
118
+ end
119
+
120
+ def tracked?
121
+ self.class.tracked?
122
+ end
123
+
124
+ def tracked= bool
125
+ self.class.tracked = bool
67
126
  end
68
127
 
69
128
  class << self
70
- def initialize_block
71
- @initialize_block
129
+ attr_writer :tracked
130
+
131
+ def start_block
132
+ @start_block
133
+ end
134
+
135
+ def tracked?
136
+ @tracked ||= false
72
137
  end
73
138
  end
74
139
  end
75
-
76
140
  end