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
@@ -1,15 +0,0 @@
1
- module Gamefic
2
-
3
- module Plot::Players
4
- def players
5
- p_players.clone
6
- end
7
-
8
- private
9
-
10
- def p_players
11
- @p_players ||= []
12
- end
13
- end
14
-
15
- end
@@ -1,149 +0,0 @@
1
- module Gamefic
2
-
3
- module Plot::Scenes
4
- def default_scene
5
- @default_scene ||= Scene::Active
6
- end
7
-
8
- def default_conclusion
9
- @default_conclusion ||= Scene::Conclusion
10
- end
11
-
12
- # Add a block to be executed when a player is added to the game.
13
- # Each Plot can only have one introduction. Subsequent calls will
14
- # overwrite the existing one.
15
- #
16
- # @example Welcome the player to the game
17
- # introduction do |actor|
18
- # actor.tell "Welcome to the game!"
19
- # end
20
- #
21
- # @yieldparam [Gamefic::Character]
22
- def introduction (&proc)
23
- @introduction = proc
24
- end
25
-
26
- # Introduce a player to the game.
27
- # This method is typically called by the Engine that manages game execution.
28
- #
29
- # @param [Gamefic::Character]
30
- def introduce(player)
31
- player.playbook = playbook
32
- player.cue default_scene
33
- p_players.push player
34
- @introduction.call(player) unless @introduction.nil?
35
- end
36
-
37
- # Create a multiple-choice scene.
38
- # The user will be required to make a valid choice to continue.
39
- #
40
- # @yieldparam [Gamefic::Character]
41
- # @yieldparam [Gamefic::Scene::Data::MultipleChoice]
42
- def multiple_choice *choices, &block
43
- Scene::MultipleChoice.subclass do |actor, scene|
44
- scene.options.concat choices
45
- scene.on_finish &block
46
- end
47
- end
48
-
49
- # Create a yes-or-no scene.
50
- # The user will be required to answer Yes or No to continue.
51
- #
52
- # @yieldparam [Gamefic::Character]
53
- # @yieldparam [Gamefic::Scene::YesOrNo]
54
- def yes_or_no prompt = nil, &block
55
- Scene::YesOrNo.subclass do |actor, scene|
56
- scene.prompt = prompt
57
- scene.on_finish &block
58
- end
59
- end
60
-
61
- def question prompt = 'What is your answer?', &block
62
- Scene::Custom.subclass do |actor, scene|
63
- scene.prompt = prompt
64
- scene.on_finish &block
65
- end
66
- end
67
-
68
- # Create a scene that pauses the game.
69
- # This scene will execute the specified block and wait for input from the
70
- # the user (e.g., pressing Enter) to continue.
71
- #
72
- # @param prompt [String] The text to display when prompting the user to continue.
73
- # @yieldparam [Gamefic::Character]
74
- # @yieldparam [Gamefic::Scene::Pause]
75
- def pause prompt = nil, &block
76
- Scene::Pause.subclass do |actor, scene|
77
- scene.prompt = prompt unless prompt.nil?
78
- block.call(actor, scene) unless block.nil?
79
- end
80
- end
81
-
82
- # Create a conclusion.
83
- # The game (or the character's participation in it) will end after this
84
- # scene is complete.
85
- #
86
- # @yieldparam [Gamefic::Character]
87
- # @yieldparam [Gamefic::Scene::Conclusion]
88
- def conclusion &block
89
- Scene::Conclusion.subclass &block
90
- end
91
-
92
- # Create a custom scene.
93
- #
94
- # Custom scenes should always specify the next scene to be cued or
95
- # prepared. If not, the scene will get repeated on the next turn.
96
- #
97
- # This method creates a Scene::Custom by default. You can customize other
98
- # scene types by specifying the class to create.
99
- #
100
- # @example Ask the user for a name
101
- # @scene = custom do |scene|
102
- # data.prompt = "What's your name?"
103
- # scene.on_finish do |actor, data|
104
- # actor.name = data.input
105
- # actor.tell "Hello, #{actor.name}!"
106
- # actor.cue :active
107
- # end
108
- # end
109
- #
110
- # @param cls [Class] The class of scene to be instantiated.
111
- # @yieldparam [Gamefic::Character]
112
- # @yieldparam [Scene::Custom] The instantiated scene.
113
- def custom cls = Scene::Custom, &block
114
- cls.subclass &block
115
- end
116
-
117
- # Choose a new scene based on a list of options.
118
- # This is a specialized type of multiple-choice scene that determines
119
- # which scene to cue based on a Hash of choices and scene keys.
120
- #
121
- # @example Select a scene
122
- # scene_one = pause do |actor|
123
- # actor.tell "You went to scene one"
124
- # end
125
- #
126
- # scene_two = pause do |actor|
127
- # actor.tell "You went to scene two"
128
- # end
129
- #
130
- # select_one_or_two = multiple_scene "One" => scene_one, "Two" => scene_two
131
- #
132
- # introduction do |actor|
133
- # actor.cue select_one_or_two # The actor will be prompted to select "one" or "two" and get sent to the corresponding scene
134
- # end
135
- #
136
- # @param map [Hash] A Hash of options and associated scene keys.
137
- # @yieldparam [Gamefic::Character]
138
- # @yieldparam [Gamefic::Scene::MultipleScene]
139
- def multiple_scene map = {}, &block
140
- Scene::MultipleScene.subclass do |actor, scene|
141
- map.each_pair { |k, v|
142
- scene.map k, v
143
- }
144
- block.call actor, scene unless block.nil?
145
- end
146
- end
147
- end
148
-
149
- end
@@ -1,73 +0,0 @@
1
- module Gamefic
2
-
3
- module Plot::Theater
4
- # Execute a block of code in a subset of the object's scope. An object's
5
- # stage is an isolated namespace that has its own instance variables and
6
- # access to its owner's public methods.
7
- #
8
- # There are two ways to execute code on the stage. It will accept either a
9
- # string of code with an optional file name and line number, or a proc
10
- # with optional arguments. See module_eval and module_exec for more
11
- # information.
12
- #
13
- # @example Evaluate a string of code
14
- # stage "puts 'Hello'"
15
- #
16
- # @example Evaluate a string of code with a file name and line number
17
- # stage "puts 'Hello'", "file.rb", 1
18
- #
19
- # @example Execute a block of code
20
- # stage {
21
- # puts 'Hello'
22
- # }
23
- #
24
- # @example Execute a block of code with arguments
25
- # stage 'hello' { |message|
26
- # puts message # <- prints 'hello'
27
- # }
28
- #
29
- # @example Use an instance variable
30
- # stage "@message = 'hello'"
31
- # stage "puts @message" # <- prints 'hello'
32
- #
33
- # @return [Object] The value returned by the executed code
34
- def stage *args, &block
35
- if block.nil?
36
- theater.module_eval *args
37
- else
38
- theater.module_exec *args, &block
39
- end
40
- end
41
-
42
- # The module that acts as an isolated namespace for staged code.
43
- #
44
- # @return [Module]
45
- def theater
46
- return @theater unless @theater.nil?
47
- instance = self
48
-
49
- @theater = Module.new do
50
- define_singleton_method :method_missing do |symbol, *args, &block|
51
- instance.public_send :public_send, symbol, *args, &block
52
- end
53
-
54
- define_singleton_method :stage do |*args|
55
- raise NoMethodError.new("The stage method is not available from inside staged scripts")
56
- end
57
-
58
- define_singleton_method :to_s do
59
- "[Theater]"
60
- end
61
- end
62
-
63
- # HACK: Include the theater module in Object so that classes and modules
64
- # defined in scripts are accessible from procs passed to the stage.
65
- Object.class_exec(@theater) do |t|
66
- include t
67
- end
68
-
69
- @theater
70
- end
71
- end
72
-
73
- end
@@ -1,22 +0,0 @@
1
- require 'gamefic'
2
- require 'gamefic/grammar'
3
-
4
- module Gamefic
5
- module Plot::YouMount
6
- class YouGrammarSet
7
- include Grammar::Gender
8
- include Grammar::Person
9
- include Grammar::Plural
10
- include Grammar::WordAdapter
11
- end
12
- # @return [YouGrammarSet]
13
- def you
14
- if @you.nil?
15
- @you = YouGrammarSet.new
16
- @you.person = 2
17
- end
18
- @you
19
- end
20
- end
21
-
22
- end
@@ -1,13 +0,0 @@
1
- module Gamefic
2
-
3
- # Script classes provide code to be executed in Plots. They are accessed
4
- # through Source classes, e.g., a Source::Text object is used to find
5
- # Source::Files.
6
- #
7
- module Script
8
- autoload :Base, 'gamefic/script/base'
9
- autoload :File, 'gamefic/script/file'
10
- autoload :Text, 'gamefic/script/text'
11
- end
12
-
13
- end
@@ -1,42 +0,0 @@
1
- module Gamefic
2
-
3
- class Script::Base
4
- def initialize
5
- raise "#initialize must be defined in subclasses"
6
- end
7
-
8
- # Get the script's text.
9
- # The text must be source code suitable for evaluation via Plot#stage.
10
- #
11
- # @return [String]
12
- def read
13
- raise "#read must be defined in subclasses"
14
- end
15
-
16
- # Get the script's path.
17
- #
18
- # @return [String]
19
- def path
20
- raise "#path must be defined in subclasses"
21
- end
22
-
23
- # Get the absolute path of the script's original file, or its URL for
24
- # sources that are not file-based.
25
- #
26
- # @return [String]
27
- def absolute_path
28
- raise "#absolute_path must be defined in subclasses"
29
- end
30
-
31
- # Script objects are equal if their relative paths are the same. Note that
32
- # they are still considered equal if their absolute paths are different,
33
- # or even if they come from different types of sources.
34
- #
35
- # @param other[Script::Base]
36
- # @return [Boolean]
37
- def==(other)
38
- path == other.path
39
- end
40
- end
41
-
42
- end
@@ -1,14 +0,0 @@
1
- module Gamefic
2
-
3
- class Script::File < Script::Base
4
- attr_reader :path, :absolute_path
5
- def initialize filename, path
6
- @absolute_path = filename.gsub(/\/+/, '/')
7
- @path = path
8
- end
9
- def read
10
- File.read(@absolute_path)
11
- end
12
- end
13
-
14
- end
@@ -1,14 +0,0 @@
1
- module Gamefic
2
-
3
- class Script::Text < Script::Base
4
- attr_reader :path, :absolute_path
5
- def initialize path, code, absolute_path = nil
6
- @path = path
7
- @code = code
8
- @absolute_path = absolute_path || path
9
- end
10
- def read
11
- @code
12
- end
13
- end
14
- end
@@ -1,76 +0,0 @@
1
- require 'thor'
2
- require 'gamefic/engine/tty'
3
- require 'zip'
4
- require 'tmpdir'
5
- require 'yaml'
6
-
7
- module Gamefic
8
- class Shell < Thor
9
- map %w[--version -v] => :version
10
-
11
- desc "--version, -v", "Print the version"
12
- def version
13
- puts "gamefic #{Gamefic::VERSION}"
14
- end
15
-
16
- desc 'play FILE_NAME', 'Execute a compiled (.gfic) game'
17
- option :verbose, type: :boolean, aliases: :v, desc: "Don't suppress Ruby exceptions"
18
- def play(file)
19
- Dir.mktmpdir 'gamefic_' do |dir|
20
- puts 'Loading...'
21
- decompress file, dir
22
- run_game(dir)
23
- end
24
- rescue Zip::Error => e
25
- puts "'#{file}' does not appear to be a valid Gamefic file."
26
- show_exception(e) if options[:verbose]
27
- rescue StandardError => e
28
- puts "An error occurred: #{e.message}"
29
- show_exception(e) if options[:verbose]
30
- end
31
-
32
- desc 'info FILE_NAME', 'Print information about a (.gfic) game'
33
- option :verbose, type: :boolean, aliases: :v, desc: "Don't suppress Ruby exceptions"
34
- def info(file)
35
- Dir.mktmpdir 'gamefic_' do |dir|
36
- decompress file, dir
37
- metadata = YAML.load_file File.join(dir, 'metadata.yaml')
38
- metadata.each { |k, v|
39
- puts "#{k}: #{v}"
40
- }
41
- end
42
- rescue StandardError, Zip::Error => e
43
- puts "'#{file}' does not appear to be a valid Gamefic file."
44
- show_exception(e) if options[:verbose]
45
- end
46
-
47
- # Custom error message for invalid command or filename
48
- def method_missing(symbol, *args)
49
- raise UndefinedCommandError, "Could not find command or file named \"#{symbol}\"."
50
- end
51
-
52
- private
53
-
54
- def show_exception(exception)
55
- puts exception.inspect
56
- puts exception.backtrace.join("\n")
57
- end
58
-
59
- def decompress(zipfile, destination)
60
- Zip::File.open(zipfile) do |z|
61
- z.each do |entry|
62
- FileUtils.mkdir_p File.join(destination, File.dirname(entry.name))
63
- full_path = File.join(destination, entry.name)
64
- entry.extract full_path unless File.exist?(full_path)
65
- end
66
- end
67
- end
68
-
69
- def run_game(directory)
70
- plot = Plot.new(Source::File.new(File.join(directory, 'scripts')))
71
- plot.script 'main'
72
- plot.metadata = YAML.load_file File.join(directory, 'metadata.yaml')
73
- Engine::Tty.start(plot)
74
- end
75
- end
76
- end
@@ -1,14 +0,0 @@
1
- module Gamefic
2
-
3
- # Plots use Source classes to fetch scripts to be executed. The most common
4
- # type of Source class is Source::File, which searches for scripts in
5
- # a predefined list of directories on the filesystem, similar to the way
6
- # that Kernel#require works.
7
- #
8
- module Source
9
- autoload :Base, 'gamefic/source/base'
10
- autoload :File, 'gamefic/source/file'
11
- autoload :Text, 'gamefic/source/text'
12
- end
13
-
14
- end
@@ -1,12 +0,0 @@
1
- module Gamefic
2
-
3
- class Source::Base
4
- def initialize
5
- raise "#initialize must be defined in subclasses"
6
- end
7
- def export path
8
- raise "#export must be defined in subclasses"
9
- end
10
- end
11
-
12
- end