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.
- checksums.yaml +5 -5
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +13 -0
- data/.solargraph.yml +5 -0
- data/Gemfile +7 -0
- data/LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +10 -0
- data/gamefic.gemspec +27 -0
- data/lib/gamefic.rb +7 -6
- data/lib/gamefic/action.rb +38 -28
- data/lib/gamefic/active.rb +325 -280
- data/lib/gamefic/actor.rb +8 -5
- data/lib/gamefic/command.rb +9 -7
- data/lib/gamefic/core_ext/array.rb +24 -49
- data/lib/gamefic/core_ext/string.rb +25 -16
- data/lib/gamefic/describable.rb +21 -23
- data/lib/gamefic/element.rb +43 -31
- data/lib/gamefic/entity.rb +6 -12
- data/lib/gamefic/index.rb +121 -0
- data/lib/gamefic/{matchable.rb → keywords.rb} +52 -50
- data/lib/gamefic/messaging.rb +43 -44
- data/lib/gamefic/node.rb +14 -5
- data/lib/gamefic/plot.rb +69 -89
- data/lib/gamefic/plot/darkroom.rb +92 -264
- data/lib/gamefic/plot/host.rb +42 -48
- data/lib/gamefic/plot/snapshot.rb +5 -18
- data/lib/gamefic/query.rb +14 -18
- data/lib/gamefic/query/base.rb +30 -18
- data/lib/gamefic/query/children.rb +0 -0
- data/lib/gamefic/query/external.rb +18 -14
- data/lib/gamefic/query/family.rb +1 -7
- data/lib/gamefic/query/matches.rb +75 -67
- data/lib/gamefic/query/parent.rb +0 -0
- data/lib/gamefic/query/siblings.rb +0 -0
- data/lib/gamefic/query/text.rb +2 -1
- data/lib/gamefic/scene.rb +0 -2
- data/lib/gamefic/scene/activity.rb +24 -26
- data/lib/gamefic/scene/base.rb +64 -8
- data/lib/gamefic/scene/conclusion.rb +0 -2
- data/lib/gamefic/scene/custom.rb +0 -2
- data/lib/gamefic/scene/multiple_choice.rb +18 -3
- data/lib/gamefic/scene/multiple_scene.rb +29 -20
- data/lib/gamefic/scene/pause.rb +7 -2
- data/lib/gamefic/scene/yes_or_no.rb +21 -9
- data/lib/gamefic/scriptable.rb +87 -0
- data/lib/gamefic/serialize.rb +68 -0
- data/lib/gamefic/subplot.rb +29 -35
- data/lib/gamefic/syntax.rb +14 -13
- data/lib/gamefic/version.rb +3 -3
- data/lib/gamefic/world.rb +16 -0
- data/lib/gamefic/world/callbacks.rb +135 -0
- data/lib/gamefic/world/commands.rb +184 -0
- data/lib/gamefic/{plot → world}/entities.rb +30 -29
- data/lib/gamefic/{plot → world}/playbook.rb +255 -240
- data/lib/gamefic/world/players.rb +21 -0
- data/lib/gamefic/world/scenes.rb +226 -0
- metadata +41 -92
- data/bin/gamefic +0 -9
- data/lib/gamefic/engine.rb +0 -7
- data/lib/gamefic/engine/base.rb +0 -59
- data/lib/gamefic/engine/tty.rb +0 -24
- data/lib/gamefic/grammar.rb +0 -13
- data/lib/gamefic/grammar/conjugator.rb +0 -20
- data/lib/gamefic/grammar/gender.rb +0 -11
- data/lib/gamefic/grammar/person.rb +0 -10
- data/lib/gamefic/grammar/plural.rb +0 -13
- data/lib/gamefic/grammar/pronouns.rb +0 -106
- data/lib/gamefic/grammar/tense.rb +0 -6
- data/lib/gamefic/grammar/verb_set.rb +0 -43
- data/lib/gamefic/grammar/verbs.rb +0 -26
- data/lib/gamefic/grammar/word_adapter.rb +0 -49
- data/lib/gamefic/plot/articles.rb +0 -22
- data/lib/gamefic/plot/callbacks.rb +0 -126
- data/lib/gamefic/plot/commands.rb +0 -120
- data/lib/gamefic/plot/players.rb +0 -15
- data/lib/gamefic/plot/scenes.rb +0 -187
- data/lib/gamefic/plot/theater.rb +0 -73
- data/lib/gamefic/plot/you_mount.rb +0 -22
- data/lib/gamefic/script.rb +0 -13
- data/lib/gamefic/script/base.rb +0 -42
- data/lib/gamefic/script/file.rb +0 -14
- data/lib/gamefic/script/text.rb +0 -14
- data/lib/gamefic/shell.rb +0 -76
- data/lib/gamefic/source.rb +0 -14
- data/lib/gamefic/source/base.rb +0 -12
- data/lib/gamefic/source/file.rb +0 -23
- data/lib/gamefic/source/text.rb +0 -16
- data/lib/gamefic/tester.rb +0 -19
- data/lib/gamefic/text.rb +0 -8
- data/lib/gamefic/text/ansi.rb +0 -53
- data/lib/gamefic/text/html.rb +0 -68
- data/lib/gamefic/text/html/conversions.rb +0 -250
- data/lib/gamefic/text/html/entities.rb +0 -9
- data/lib/gamefic/tty.rb +0 -10
- data/lib/gamefic/user.rb +0 -7
- data/lib/gamefic/user/base.rb +0 -29
- data/lib/gamefic/user/tty.rb +0 -38
data/lib/gamefic/scene/custom.rb
CHANGED
@@ -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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
data/lib/gamefic/scene/pause.rb
CHANGED
@@ -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
|
data/lib/gamefic/subplot.rb
CHANGED
@@ -1,39 +1,29 @@
|
|
1
1
|
require 'gamefic/plot'
|
2
2
|
|
3
3
|
module Gamefic
|
4
|
-
|
5
|
-
|
6
|
-
include
|
7
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
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
|
36
|
-
@
|
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
|
-
|
55
|
+
players.delete player
|
70
56
|
end
|
71
57
|
|
72
58
|
def conclude
|
73
59
|
@concluded = true
|
74
|
-
|
75
|
-
|
76
|
-
}
|
77
|
-
|
78
|
-
|
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
|