gamefic 1.7.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|