gamefic 1.7.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,5 +1,4 @@
1
1
  module Gamefic
2
-
3
2
  # A Conclusion ends the Plot (or the character's participation in it).
4
3
  #
5
4
  class Scene::Conclusion < Scene::Custom
@@ -7,5 +6,4 @@ module Gamefic
7
6
  @type ||= 'Conclusion'
8
7
  end
9
8
  end
10
-
11
9
  end
@@ -1,9 +1,7 @@
1
1
  module Gamefic
2
-
3
2
  # A Custom Scene allows for complete configuration of its behavior upon
4
3
  # instantiation. It is suitable for direct instantiation or subclassing.
5
4
  #
6
5
  class Scene::Custom < Scene::Base
7
6
  end
8
-
9
7
  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.
@@ -8,9 +7,21 @@ module Gamefic
8
7
  # instead of a String.
9
8
  #
10
9
  class Scene::MultipleChoice < Scene::Custom
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 [Number]
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
@@ -28,10 +39,16 @@ module Gamefic
28
39
  end
29
40
  end
30
41
 
42
+ # The array of available options.
43
+ #
44
+ # @return [Array<String>]
31
45
  def options
32
46
  @options ||= []
33
47
  end
34
48
 
49
+ # The text to display when an invalid selection is received.
50
+ #
51
+ # @return [String]
35
52
  def invalid_message
36
53
  @invalid_message ||= 'That is not a valid choice.'
37
54
  end
@@ -69,7 +86,5 @@ module Gamefic
69
86
  list += "</ol>"
70
87
  actor.tell list
71
88
  end
72
-
73
89
  end
74
-
75
90
  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,5 +1,4 @@
1
1
  module Gamefic
2
-
3
2
  # Pause for user input.
4
3
  #
5
4
  class Scene::Pause < Scene::Custom
@@ -7,6 +6,12 @@ module Gamefic
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
7
  class Scene::YesOrNo < Scene::Custom
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
@@ -0,0 +1,87 @@
1
+ module Gamefic
2
+ # The Scriptable module provides a clean room (aka "theater") for scripts.
3
+ #
4
+ # @!method stage(*args, &block)
5
+ # Execute a block of code in a subset of the owner's scope.
6
+ #
7
+ # The provided code is evaluated inside a clean room object that has its
8
+ # own instance variables and access to the owner's public methods. The proc
9
+ # can accept the method call's arguments.
10
+ #
11
+ # @example Execute a block of code
12
+ # stage {
13
+ # puts 'Hello'
14
+ # }
15
+ #
16
+ # @example Execute a block of code with arguments
17
+ # stage 'hello' { |message|
18
+ # puts message # <- prints 'hello'
19
+ # }
20
+ #
21
+ # @example Use an instance variable
22
+ # stage { @message = 'hello'" }
23
+ # stage { puts @message } # <- prints 'hello'
24
+ #
25
+ # @yieldpublic [Gamefic::Plot]
26
+ # @return [Object] The value returned by the executed code
27
+ #
28
+ # @!method theater
29
+ # The object that acts as an isolated namespace for staged code.
30
+ # @return [Object]
31
+ #
32
+ # @!parse alias cleanroom theater
33
+ module Scriptable
34
+ module ClassMethods
35
+ # An array of blocks that were added by the `script` class method.
36
+ #
37
+ # @return [Array<Proc>]
38
+ def blocks
39
+ @blocks ||= []
40
+ end
41
+
42
+ # Add a block to be executed by the instance's `stage` method.
43
+ #
44
+ # Note that `script` does not execute the block instantly, but stores
45
+ # it in the `blocks` array to be executed later.
46
+ #
47
+ # @yieldpublic [Gamefic::Plot]
48
+ def script &block
49
+ blocks.push block
50
+ end
51
+ end
52
+
53
+ def self.included klass
54
+ klass.extend ClassMethods
55
+ end
56
+
57
+ private
58
+
59
+ # Execute all the scripts that were added by the `script` class method.
60
+ #
61
+ def run_scripts
62
+ self.class.blocks.each { |blk| stage &blk }
63
+ end
64
+ end
65
+ end
66
+
67
+ # @note #stage and #theater are implemented this way so the clean room object
68
+ # defines its classes and modules in the root namespace.
69
+ Gamefic::Scriptable.module_exec do
70
+ define_method :stage do |*args, &block|
71
+ theater.instance_exec *args, &block
72
+ end
73
+
74
+ define_method :theater do
75
+ @theater ||= begin
76
+ instance = self
77
+ theater ||= Object.new
78
+ theater.instance_exec do
79
+ define_singleton_method :method_missing do |symbol, *args, &block|
80
+ instance.public_send :public_send, symbol, *args, &block
81
+ end
82
+ end
83
+ theater
84
+ end
85
+ end
86
+ alias cleanroom theater
87
+ end
@@ -0,0 +1,68 @@
1
+ module Gamefic
2
+ module Serialize
3
+ def to_serial
4
+ {
5
+ 'class' => self.class.to_s
6
+ }.merge serialize_instance_variables
7
+ end
8
+ end
9
+ end
10
+
11
+ class Object
12
+ def to_serial
13
+ return self if [true, false, nil].include?(self)
14
+ # @todo This warning is a little too spammy. Set up a logger so it can be
15
+ # limited to an info or debug level.
16
+ # STDERR.puts "Unable to convert #{self} to element"
17
+ "#<UNKNOWN>"
18
+ end
19
+
20
+ def serialize_instance_variables
21
+ result = {}
22
+ instance_variables.each do |k|
23
+ result[k.to_s] = instance_variable_get(k).to_serial
24
+ end
25
+ result
26
+ end
27
+ end
28
+
29
+ class Symbol
30
+ def to_serial
31
+ "#<SYM:#{self}>"
32
+ end
33
+ end
34
+
35
+ class String
36
+ def to_serial
37
+ self
38
+ end
39
+ end
40
+
41
+ class Numeric
42
+ def to_serial
43
+ self
44
+ end
45
+ end
46
+
47
+ class Array
48
+ def to_serial
49
+ map do |e|
50
+ s = e.to_serial
51
+ return "#<UNKNOWN>" if s == "#<UNKNOWN>"
52
+ s
53
+ end
54
+ end
55
+ end
56
+
57
+ class Hash
58
+ def to_serial
59
+ result = {}
60
+ each_pair do |key, value|
61
+ k2 = key.to_serial
62
+ v2 = value.to_serial
63
+ return "#<UNKNOWN>" if k2 == "#<UNKNOWN>" || v2 == "#<UNKNOWN>"
64
+ result[k2] = v2
65
+ end
66
+ result
67
+ end
68
+ end
@@ -1,39 +1,29 @@
1
1
  require 'gamefic/plot'
2
2
 
3
3
  module Gamefic
4
-
5
- class Subplot
6
- include Plot::Theater
7
- include Plot::Entities
8
- include Plot::Commands
9
- include Plot::Callbacks
10
- include Plot::Scenes
11
- include Plot::Articles
4
+ class Subplot #< Container
5
+ include World
6
+ include Scriptable
7
+ # @!parse extend Scriptable::ClassMethods
12
8
 
13
9
  # @return [Gamefic::Plot]
14
10
  attr_reader :plot
15
11
 
16
- class << self
17
- attr_reader :start_proc
18
-
19
- protected
20
-
21
- def on_start &block
22
- @start_proc = block
23
- end
24
- end
25
-
26
- def initialize plot, introduce: nil, next_cue: nil
12
+ # @param plot [Gamefic::Plot]
13
+ # @param introduce [Gamefic::Actor]
14
+ # @param next_cue [Class<Gamefic::Scene::Base>]
15
+ def initialize plot, introduce: nil, next_cue: nil, **more
27
16
  @plot = plot
28
17
  @next_cue = next_cue
29
18
  @concluded = false
30
- stage &self.class.start_proc unless self.class.start_proc.nil?
19
+ configure more
20
+ run_scripts
31
21
  playbook.freeze
32
22
  self.introduce introduce unless introduce.nil?
33
23
  end
34
24
 
35
- def add_entity e
36
- @p_entities.push e
25
+ def players
26
+ @players ||= []
37
27
  end
38
28
 
39
29
  def subplot
@@ -58,25 +48,21 @@ module Gamefic
58
48
  ent
59
49
  end
60
50
 
61
- # HACK: Always assume subplots are running for the sake of entity destruction
62
- def running?
63
- true
64
- end
65
-
66
51
  def exeunt player
52
+ player_conclude_procs.each { |block| block.call player }
67
53
  player.playbooks.delete playbook
68
54
  player.cue (@next_cue || default_scene)
69
- p_players.delete player
55
+ players.delete player
70
56
  end
71
57
 
72
58
  def conclude
73
59
  @concluded = true
74
- entities.each { |e|
75
- destroy e
76
- }
77
- players.each { |p|
78
- exeunt p
79
- }
60
+ # Players needed to exit first in case any player_conclude procs need to
61
+ # interact with the subplot's entities.
62
+ players.each { |p| exeunt p }
63
+ # @todo I'm not sure why rejecting nils is necessary here. It's only an
64
+ # issue in Opal.
65
+ entities.reject(&:nil?).each { |e| destroy e }
80
66
  end
81
67
 
82
68
  def concluded?
@@ -84,6 +70,9 @@ module Gamefic
84
70
  end
85
71
 
86
72
  def ready
73
+ # @todo We might not want to conclude subplots without players. There
74
+ # might be cases where a subplot gets created with the intention of
75
+ # introducing players in a later turn.
87
76
  conclude if players.empty?
88
77
  return if concluded?
89
78
  playbook.freeze
@@ -95,6 +84,11 @@ module Gamefic
95
84
  call_player_update
96
85
  call_update
97
86
  end
98
- end
99
87
 
88
+ # Subclasses can override this method to handle additional configuration
89
+ # options.
90
+ #
91
+ def configure more
92
+ end
93
+ end
100
94
  end