gamefic 1.7.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) 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 +10 -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 +7 -7
  13. data/lib/gamefic/action.rb +66 -60
  14. data/lib/gamefic/active.rb +331 -280
  15. data/lib/gamefic/actor.rb +8 -5
  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 +21 -23
  20. data/lib/gamefic/element.rb +47 -31
  21. data/lib/gamefic/entity.rb +6 -12
  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 -91
  26. data/lib/gamefic/plot/darkroom.rb +80 -264
  27. data/lib/gamefic/plot/host.rb +42 -48
  28. data/lib/gamefic/plot/snapshot.rb +14 -19
  29. data/lib/gamefic/query.rb +15 -18
  30. data/lib/gamefic/query/base.rb +50 -37
  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 -14
  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 +0 -2
  41. data/lib/gamefic/scene/activity.rb +24 -26
  42. data/lib/gamefic/scene/base.rb +71 -10
  43. data/lib/gamefic/scene/conclusion.rb +1 -3
  44. data/lib/gamefic/scene/multiple_choice.rb +19 -14
  45. data/lib/gamefic/scene/multiple_scene.rb +29 -20
  46. data/lib/gamefic/scene/pause.rb +8 -3
  47. data/lib/gamefic/scene/yes_or_no.rb +22 -10
  48. data/lib/gamefic/scriptable.rb +88 -0
  49. data/lib/gamefic/serialize.rb +223 -0
  50. data/lib/gamefic/subplot.rb +38 -35
  51. data/lib/gamefic/syntax.rb +15 -13
  52. data/lib/gamefic/version.rb +3 -3
  53. data/lib/gamefic/world.rb +18 -0
  54. data/lib/gamefic/world/callbacks.rb +135 -0
  55. data/lib/gamefic/world/commands.rb +184 -0
  56. data/lib/gamefic/{plot → world}/entities.rb +33 -35
  57. data/lib/gamefic/{plot → world}/playbook.rb +245 -240
  58. data/lib/gamefic/world/players.rb +37 -0
  59. data/lib/gamefic/world/scenes.rb +226 -0
  60. metadata +37 -88
  61. data/bin/gamefic +0 -9
  62. data/lib/gamefic/engine.rb +0 -7
  63. data/lib/gamefic/engine/base.rb +0 -59
  64. data/lib/gamefic/engine/tty.rb +0 -24
  65. data/lib/gamefic/grammar.rb +0 -13
  66. data/lib/gamefic/grammar/conjugator.rb +0 -20
  67. data/lib/gamefic/grammar/gender.rb +0 -11
  68. data/lib/gamefic/grammar/person.rb +0 -10
  69. data/lib/gamefic/grammar/plural.rb +0 -13
  70. data/lib/gamefic/grammar/pronouns.rb +0 -106
  71. data/lib/gamefic/grammar/tense.rb +0 -6
  72. data/lib/gamefic/grammar/verb_set.rb +0 -43
  73. data/lib/gamefic/grammar/verbs.rb +0 -26
  74. data/lib/gamefic/grammar/word_adapter.rb +0 -49
  75. data/lib/gamefic/plot/articles.rb +0 -22
  76. data/lib/gamefic/plot/callbacks.rb +0 -126
  77. data/lib/gamefic/plot/commands.rb +0 -120
  78. data/lib/gamefic/plot/players.rb +0 -15
  79. data/lib/gamefic/plot/scenes.rb +0 -187
  80. data/lib/gamefic/plot/theater.rb +0 -73
  81. data/lib/gamefic/plot/you_mount.rb +0 -22
  82. data/lib/gamefic/scene/custom.rb +0 -9
  83. data/lib/gamefic/script.rb +0 -13
  84. data/lib/gamefic/script/base.rb +0 -42
  85. data/lib/gamefic/script/file.rb +0 -14
  86. data/lib/gamefic/script/text.rb +0 -14
  87. data/lib/gamefic/shell.rb +0 -76
  88. data/lib/gamefic/source.rb +0 -14
  89. data/lib/gamefic/source/base.rb +0 -12
  90. data/lib/gamefic/source/file.rb +0 -23
  91. data/lib/gamefic/source/text.rb +0 -16
  92. data/lib/gamefic/tester.rb +0 -19
  93. data/lib/gamefic/text.rb +0 -8
  94. data/lib/gamefic/text/ansi.rb +0 -53
  95. data/lib/gamefic/text/html.rb +0 -68
  96. data/lib/gamefic/text/html/conversions.rb +0 -250
  97. data/lib/gamefic/text/html/entities.rb +0 -9
  98. data/lib/gamefic/tty.rb +0 -10
  99. data/lib/gamefic/user.rb +0 -7
  100. data/lib/gamefic/user/base.rb +0 -29
  101. data/lib/gamefic/user/tty.rb +0 -38
@@ -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
data/lib/gamefic/scene.rb CHANGED
@@ -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,105 @@
1
1
  module Gamefic
2
-
3
- # The Base Scene is not intended for instantiation. Other Scene classes
4
- # should inherit from it.
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`.
5
6
  #
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]
7
14
  attr_reader :actor
15
+
16
+ # A human-readable string identifying the type of scene.
17
+ #
18
+ # @return [String]
8
19
  attr_writer :type
20
+
21
+ # The text to display when requesting input.
22
+ #
23
+ # @return [String]
9
24
  attr_writer :prompt
25
+
26
+ # The input received from the actor.
27
+ #
28
+ # @return [String]
10
29
  attr_reader :input
11
30
 
12
- def initialize actor
31
+ # @return [Hash{Symbol => Object}]
32
+ attr_reader :data
33
+
34
+ def initialize actor, **data
13
35
  @actor = actor
36
+ @data = data
14
37
  post_initialize
15
38
  end
16
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
+
17
48
  def post_initialize
18
49
  end
19
50
 
51
+ # Set a proc to be executed at the end of the scene.
52
+ #
20
53
  def on_finish &block
21
54
  @finish_block = block
22
55
  end
23
56
 
57
+ # Update the scene.
58
+ #
24
59
  def update
25
60
  @input = actor.queue.shift
26
61
  finish
27
62
  end
28
63
 
64
+ # Start the scene.
65
+ #
29
66
  def start
30
67
  self.class.start_block.call @actor, self unless self.class.start_block.nil?
68
+ @actor.entered self if tracked?
31
69
  end
32
70
 
71
+ # Finish the scene.
72
+ #
33
73
  def finish
34
74
  @finish_block.call @actor, self unless @finish_block.nil?
35
75
  @finished = true
36
76
  end
37
77
 
78
+ # Determine whether the scene's execution is finished.
79
+ #
80
+ # @return [Boolean]
38
81
  def finished?
39
82
  @finished ||= false
40
83
  end
41
84
 
42
- def flush
43
- @state.clear
44
- end
45
-
85
+ # Get a hash that describes the current state of the scene.
86
+ #
87
+ # @return [Hash]
46
88
  def state
47
89
  {
48
90
  scene: type, prompt: prompt
49
91
  }
50
92
  end
51
93
 
94
+ # @yieldparam [Class<Gamefic::Actor>]
95
+ # @return [Class<Gamefic::Scene::Base>]
52
96
  def self.subclass &block
53
97
  c = Class.new(self) do
54
98
  on_start &block
55
99
  end
56
100
  c
57
101
  end
58
-
102
+
59
103
  # Get the prompt to be displayed to the user when accepting input.
60
104
  #
61
105
  # @return [String] The text to be displayed.
@@ -63,19 +107,36 @@ module Gamefic
63
107
  @prompt ||= '>'
64
108
  end
65
109
 
110
+ # Get a String that describes the type of scene.
111
+ #
112
+ # @return [String]
66
113
  def type
67
114
  @type ||= 'Scene'
68
115
  end
69
116
 
117
+ # @yieldparam [Class<Gamefic::Scene::Base>]
70
118
  def self.on_start &block
71
119
  @start_block = block
72
120
  end
73
121
 
122
+ def tracked?
123
+ self.class.tracked?
124
+ end
125
+
126
+ def tracked= bool
127
+ self.class.tracked = bool
128
+ end
129
+
74
130
  class << self
131
+ attr_writer :tracked
132
+
75
133
  def start_block
76
134
  @start_block
77
135
  end
136
+
137
+ def tracked?
138
+ @tracked ||= false
139
+ end
78
140
  end
79
141
  end
80
-
81
142
  end
@@ -1,11 +1,9 @@
1
1
  module Gamefic
2
-
3
2
  # A Conclusion ends the Plot (or the character's participation in it).
4
3
  #
5
- class Scene::Conclusion < Scene::Custom
4
+ class Scene::Conclusion < Scene::Base
6
5
  def type
7
6
  @type ||= 'Conclusion'
8
7
  end
9
8
  end
10
-
11
9
  end
@@ -1,5 +1,4 @@
1
1
  module Gamefic
2
-
3
2
  # Provide a list of options and process the selection in the scene's finish
4
3
  # block. After the scene is finished, the :active scene will be cued unless
5
4
  # some other scene has already been prepared or cued.
@@ -7,10 +6,22 @@ module Gamefic
7
6
  # The finish block's input parameter receives a MultipleChoice::Input object
8
7
  # instead of a String.
9
8
  #
10
- class Scene::MultipleChoice < Scene::Custom
9
+ class Scene::MultipleChoice < Scene::Base
10
+ # The zero-based index of the selected option.
11
+ #
12
+ # @return [Integer]
11
13
  attr_reader :index
14
+
15
+ # The one-based index of the selected option.
16
+ #
17
+ # @return [Integer]
12
18
  attr_reader :number
19
+
20
+ # The full text of the selected option.
21
+ #
22
+ # @return [String]
13
23
  attr_reader :selection
24
+
14
25
  attr_writer :invalid_message
15
26
 
16
27
  def post_initialize
@@ -22,16 +33,21 @@ module Gamefic
22
33
  get_choice
23
34
  if selection.nil?
24
35
  actor.tell invalid_message
25
- tell_options
26
36
  else
27
37
  super
28
38
  end
29
39
  end
30
40
 
41
+ # The array of available options.
42
+ #
43
+ # @return [Array<String>]
31
44
  def options
32
45
  @options ||= []
33
46
  end
34
47
 
48
+ # The text to display when an invalid selection is received.
49
+ #
50
+ # @return [String]
35
51
  def invalid_message
36
52
  @invalid_message ||= 'That is not a valid choice.'
37
53
  end
@@ -60,16 +76,5 @@ module Gamefic
60
76
  }
61
77
  end
62
78
  end
63
-
64
- def tell_options
65
- list = '<ol class="multiple_choice">'
66
- options.each { |o|
67
- list += "<li><a href=\"#\" rel=\"gamefic\" data-command=\"#{o}\">#{o}</a></li>"
68
- }
69
- list += "</ol>"
70
- actor.tell list
71
- end
72
-
73
79
  end
74
-
75
80
  end
@@ -1,20 +1,29 @@
1
- module Gamefic
2
-
3
- class Scene::MultipleScene < Scene::MultipleChoice
4
- def option_map
5
- @option_map ||= {}
6
- end
7
-
8
- def map option, scene
9
- options.push option
10
- option_map[option] = scene
11
- end
12
-
13
- def finish
14
- get_choice
15
- unless selection.nil?
16
- actor.prepare option_map[selection]
17
- end
18
- end
19
- end
20
- end
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
@@ -1,12 +1,17 @@
1
1
  module Gamefic
2
-
3
2
  # Pause for user input.
4
3
  #
5
- class Scene::Pause < Scene::Custom
4
+ class Scene::Pause < Scene::Base
6
5
  def post_initialize
7
6
  self.type = 'Pause'
8
7
  self.prompt = 'Press enter to continue...'
9
8
  end
9
+
10
+ class << self
11
+ def tracked?
12
+ @tracked = true if @tracked.nil?
13
+ @tracked
14
+ end
15
+ end
10
16
  end
11
-
12
17
  end
@@ -1,32 +1,41 @@
1
1
  module Gamefic
2
-
3
2
  # Prompt the user to answer "yes" or "no". The scene will accept variations
4
3
  # like "YES" or "n" and normalize the answer to "yes" or "no" in the finish
5
4
  # block. After the scene is finished, the :active scene will be cued if no
6
5
  # other scene has been prepared or cued.
7
6
  #
8
- class Scene::YesOrNo < Scene::Custom
7
+ class Scene::YesOrNo < Scene::Base
8
+ attr_writer :invalid_message
9
+
9
10
  def post_initialize
10
11
  self.type = 'YesOrNo'
11
- self.prompt = 'Yes or No?'
12
+ self.prompt = 'Yes or No:'
12
13
  end
13
14
 
15
+ # True if the actor's answer is Yes.
16
+ # Any answer beginning with letter Y is considered Yes.
17
+ #
18
+ # @return [Boolean]
14
19
  def yes?
15
- input.to_s[0,1].downcase == 'y'
20
+ input.to_s[0,1].downcase == 'y' or input.to_i == 1
16
21
  end
17
22
 
23
+ # True if the actor's answer is No.
24
+ # Any answer beginning with letter N is considered No.
25
+ #
26
+ # @return [Boolean]
18
27
  def no?
19
- input.to_s[0,1].downcase == 'n'
28
+ input.to_s[0,1].downcase == 'n' or input.to_i == 2
20
29
  end
21
30
 
31
+ # The message sent to the user for an invalid answer, i.e., the input
32
+ # could not be resolved to either Yes or No.
33
+ #
34
+ # @return [String]
22
35
  def invalid_message
23
36
  @invalid_message ||= 'Please enter Yes or No.'
24
37
  end
25
38
 
26
- def prompt
27
- @prompt ||= 'Yes or No?'
28
- end
29
-
30
39
  def finish
31
40
  if yes? or no?
32
41
  super
@@ -34,6 +43,9 @@ module Gamefic
34
43
  actor.tell invalid_message
35
44
  end
36
45
  end
37
- end
38
46
 
47
+ def state
48
+ super.merge options: ['Yes', 'No']
49
+ end
50
+ end
39
51
  end