gamefic 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/lib/gamefic.rb +1 -3
  3. data/lib/gamefic/action.rb +140 -79
  4. data/lib/gamefic/character.rb +120 -53
  5. data/lib/gamefic/character/state.rb +12 -0
  6. data/lib/gamefic/core_ext/array.rb +53 -11
  7. data/lib/gamefic/core_ext/string.rb +1 -0
  8. data/lib/gamefic/describable.rb +37 -11
  9. data/lib/gamefic/engine/base.rb +17 -4
  10. data/lib/gamefic/engine/tty.rb +4 -0
  11. data/lib/gamefic/entity.rb +4 -15
  12. data/lib/gamefic/matchable.rb +50 -0
  13. data/lib/gamefic/messaging.rb +45 -0
  14. data/lib/gamefic/node.rb +16 -0
  15. data/lib/gamefic/plot.rb +27 -33
  16. data/lib/gamefic/plot/{article_mount.rb → articles.rb} +22 -22
  17. data/lib/gamefic/plot/callbacks.rb +30 -4
  18. data/lib/gamefic/plot/{command_mount.rb → commands.rb} +121 -121
  19. data/lib/gamefic/plot/entities.rb +3 -3
  20. data/lib/gamefic/plot/host.rb +3 -3
  21. data/lib/gamefic/plot/playbook.rb +74 -30
  22. data/lib/gamefic/plot/scenes.rb +149 -0
  23. data/lib/gamefic/plot/snapshot.rb +14 -39
  24. data/lib/gamefic/plot/theater.rb +73 -0
  25. data/lib/gamefic/query.rb +6 -19
  26. data/lib/gamefic/query/base.rb +127 -246
  27. data/lib/gamefic/query/children.rb +6 -7
  28. data/lib/gamefic/query/descendants.rb +15 -0
  29. data/lib/gamefic/query/family.rb +19 -7
  30. data/lib/gamefic/query/itself.rb +13 -0
  31. data/lib/gamefic/query/matches.rb +67 -11
  32. data/lib/gamefic/query/parent.rb +6 -7
  33. data/lib/gamefic/query/siblings.rb +10 -7
  34. data/lib/gamefic/query/text.rb +39 -35
  35. data/lib/gamefic/scene.rb +1 -1
  36. data/lib/gamefic/scene/active.rb +12 -6
  37. data/lib/gamefic/scene/base.rb +56 -5
  38. data/lib/gamefic/scene/conclusion.rb +3 -0
  39. data/lib/gamefic/scene/custom.rb +0 -83
  40. data/lib/gamefic/scene/multiple_choice.rb +54 -32
  41. data/lib/gamefic/scene/multiple_scene.rb +11 -6
  42. data/lib/gamefic/scene/pause.rb +3 -4
  43. data/lib/gamefic/scene/yes_or_no.rb +23 -9
  44. data/lib/gamefic/script/base.rb +4 -0
  45. data/lib/gamefic/subplot.rb +22 -19
  46. data/lib/gamefic/syntax.rb +7 -15
  47. data/lib/gamefic/user/base.rb +7 -13
  48. data/lib/gamefic/user/buffer.rb +7 -0
  49. data/lib/gamefic/user/tty.rb +13 -12
  50. data/lib/gamefic/version.rb +1 -1
  51. metadata +11 -37
  52. data/lib/gamefic/director.rb +0 -23
  53. data/lib/gamefic/director/delegate.rb +0 -126
  54. data/lib/gamefic/director/order.rb +0 -17
  55. data/lib/gamefic/director/parser.rb +0 -137
  56. data/lib/gamefic/keywords.rb +0 -67
  57. data/lib/gamefic/plot/query_mount.rb +0 -9
  58. data/lib/gamefic/plot/scene_mount.rb +0 -182
  59. data/lib/gamefic/query/ambiguous_children.rb +0 -5
  60. data/lib/gamefic/query/expression.rb +0 -47
  61. data/lib/gamefic/query/many_children.rb +0 -7
  62. data/lib/gamefic/query/plural_children.rb +0 -14
  63. data/lib/gamefic/query/self.rb +0 -10
  64. data/lib/gamefic/scene_data.rb +0 -10
  65. data/lib/gamefic/scene_data/base.rb +0 -12
  66. data/lib/gamefic/scene_data/multiple_choice.rb +0 -19
  67. data/lib/gamefic/scene_data/multiple_scene.rb +0 -21
  68. data/lib/gamefic/scene_data/yes_or_no.rb +0 -18
  69. data/lib/gamefic/serialized.rb +0 -24
  70. data/lib/gamefic/stage.rb +0 -106
@@ -0,0 +1,12 @@
1
+ module Gamefic
2
+ class Character
3
+ module State
4
+ def state
5
+ @state = {}
6
+ @state.merge! scene.state unless scene.nil?
7
+ @state.merge! output: messages
8
+ @state
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,4 +1,15 @@
1
1
  class Array
2
+ # Get a subset of the array that matches the argument.
3
+ # If the argument is a Class or Module, the elements must be of the type.
4
+ # If the argument is a Symbol, it should be a method for which the elements must return true.
5
+ # If the argument is an Object, the elements must equal the object.
6
+ #
7
+ # @example
8
+ # animals = ['dog', 'cat', nil]
9
+ # animals.that_are(String) #=> ['dog', 'cat']
10
+ # animals.that_are('dog') #=> ['dog']
11
+ # animals.that_are(:nil?) #=> [nil]
12
+ #
2
13
  # @return [Array]
3
14
  def that_are(cls)
4
15
  if (cls.kind_of?(Class) or cls.kind_of?(Module))
@@ -12,6 +23,10 @@ class Array
12
23
  return Array.new
13
24
  end
14
25
  end
26
+
27
+ # Get a subset of the array that does not match the argument.
28
+ # See Array#that_are for information about how arguments are evaluated.
29
+ #
15
30
  # @return [Array]
16
31
  def that_are_not(cls)
17
32
  if (cls.kind_of?(Class) or cls.kind_of?(Module))
@@ -22,26 +37,50 @@ class Array
22
37
  return self.clone - [cls]
23
38
  end
24
39
  end
40
+
41
+ # Get a random element from the array.
42
+ # @deprecated Use Array#sample instead.
43
+ #
25
44
  def random
26
45
  return self[rand(self.length)]
27
46
  end
47
+
48
+ # Pop a random element from the array.
49
+ # @deprecated Use Array#pop_sample instead.
50
+ #
28
51
  def pop_random
52
+ pop_sample
53
+ end
54
+
55
+ # Pop a random element from the array.
56
+ #
57
+ def pop_sample
29
58
  delete_at(rand(self.length))
30
59
  end
60
+
61
+ # @todo Candidate for deprecation
31
62
  # @return [Array]
32
- def shuffle
33
- self.sort { |a, b|
34
- rand(3) <=> rand(3)
35
- }
36
- end
63
+ #def shuffle
64
+ # self.sort { |a, b|
65
+ # rand(3) <=> rand(3)
66
+ # }
67
+ #end
68
+
69
+ # @todo Candidate for deprecation
37
70
  # @return [Array]
38
- def shuffle!
39
- self.sort! { |a, b|
40
- rand(3) <=> rand(3)
41
- }
42
- end
43
- # Get a string representation of the array that separated elements with
71
+ #def shuffle!
72
+ # self.sort! { |a, b|
73
+ # rand(3) <=> rand(3)
74
+ # }
75
+ #end
76
+
77
+ # Get a string representation of the array that separates elements with
44
78
  # commas and adds a conjunction before the last element.
79
+ #
80
+ # @example
81
+ # animals = ['a dog', 'a cat', 'a mouse']
82
+ # animals.join_and #=> 'a dog, a cat, and a mouse'
83
+ #
45
84
  # @return [String]
46
85
  def join_and(sep = ', ', andSep = ' and ', serial = true)
47
86
  if self.length < 3
@@ -51,6 +90,9 @@ class Array
51
90
  start.join(sep) + "#{serial ? sep.strip : ''}#{andSep}#{self.last}"
52
91
  end
53
92
  end
93
+
94
+ # @see Array#join_and
95
+ #
54
96
  # @return [String]
55
97
  def join_or(sep = ', ', orSep = ' or ', serial = true)
56
98
  join_and(sep, orSep, serial)
@@ -1,4 +1,5 @@
1
1
  class String
2
+ include Gamefic::Matchable
2
3
  # Capitalize the first letter without changing the rest of the string.
3
4
  # (String#capitalize makes the rest of the string lower-case.)
4
5
  def capitalize_first
@@ -1,38 +1,48 @@
1
- require 'gamefic/keywords'
1
+ #require 'gamefic/keywords'
2
2
  require 'gamefic/grammar'
3
3
 
4
4
  module Gamefic
5
5
 
6
- # Add a variety of text properties for naming, describing, and properly
7
- # referencing objects.
6
+ # Add a variety of text properties for naming, describing, and referencing
7
+ # objects.
8
8
  module Describable
9
9
  include Grammar::Person, Grammar::Plural
10
+ include Matchable
11
+
12
+ # @return [String]
10
13
  attr_reader :name
11
- attr_accessor :synonyms, :indefinite_article
12
- attr_writer :definite_article
13
-
14
+
15
+ # @return [String]
16
+ attr_reader :synonyms
17
+
18
+ # @return [String]
19
+ attr_reader :indefinite_article
20
+
21
+ # @return [String]
22
+ attr_reader :definite_article
23
+
14
24
  # Get a set of Keywords associated with the object.
15
25
  # Keywords are typically the words in the object's name plus its synonyms.
16
26
  #
17
27
  # @return [Keywords]
18
28
  def keywords
19
- Keywords.new "#{name} #{synonyms}"
29
+ @keywords ||= "#{definite_article} #{indefinite_article} #{name} #{synonyms}".downcase.split(Matchable::SPLIT_REGEXP).uniq
20
30
  end
21
-
31
+
22
32
  # Get the name of the object with an indefinite article.
23
33
  # Note: proper-named objects never append an article, though an article
24
34
  # may be included in its proper name.
25
35
  #
26
36
  # @return [String]
27
37
  def indefinitely
28
- ((proper_named? or indefinite_article == '') ? '' : "#{indefinite_article} ") + name
38
+ ((proper_named? or indefinite_article == '') ? '' : "#{indefinite_article} ") + name.to_s
29
39
  end
30
40
 
31
41
  # Get the name of the object with a definite article.
32
42
  # Note: proper-named objects never append an article, though an article
33
43
  # may be included in its proper name.
34
44
  def definitely
35
- ((proper_named? or definite_article == '') ? '' : "#{definite_article} ") + name
45
+ ((proper_named? or definite_article == '') ? '' : "#{definite_article} ") + name.to_s
36
46
  end
37
47
 
38
48
  # Get the definite article for this object.
@@ -42,6 +52,16 @@ module Gamefic
42
52
  @definite_article || "the"
43
53
  end
44
54
 
55
+ def definite_article= article
56
+ @keywords = nil
57
+ @definite_article = article
58
+ end
59
+
60
+ def indefinite_article= article
61
+ @keywords = nil
62
+ @indefinite_article = article
63
+ end
64
+
45
65
  # Is the object proper-named?
46
66
  # Proper-named objects typically do not add articles to their names when
47
67
  # referenced #definitely or #indefinitely, e.g., "Jane Doe" instead of
@@ -54,7 +74,7 @@ module Gamefic
54
74
 
55
75
  # Set whether the object has a proper name.
56
76
  #
57
- # @param [Boolean]
77
+ # @param bool [Boolean]
58
78
  def proper_named=(bool)
59
79
  if bool == true
60
80
  if @definite_article != nil
@@ -71,6 +91,7 @@ module Gamefic
71
91
  #
72
92
  # @param value [String]
73
93
  def name=(value)
94
+ @keywords = nil
74
95
  words = value.split_words
75
96
  if ['a','an'].include?(words[0].downcase)
76
97
  @indefinite_article = words[0].downcase
@@ -120,6 +141,11 @@ module Gamefic
120
141
  end
121
142
  end
122
143
 
144
+ def synonyms= text
145
+ @keywords = nil
146
+ @synonyms = text
147
+ end
148
+
123
149
  # Set the object's default description.
124
150
  # The default description is typically set in an object's initialization
125
151
  # to ensure that a non-empty string is available when a instance-specific
@@ -29,22 +29,35 @@ module Gamefic
29
29
  def run
30
30
  connect
31
31
  @plot.introduce @character
32
+ @user.update @character.state
32
33
  turn until @character.concluded?
33
- print @user.flush
34
+ #print @user.flush
34
35
  end
35
36
 
36
37
  def turn
37
38
  @plot.ready
38
- print @user.flush
39
+ unless @character.state[:options].nil?
40
+ list = '<ol class="multiple_choice">'
41
+ @character.state[:options].each { |o|
42
+ list += "<li><a href=\"#\" rel=\"gamefic\" data-command=\"#{o}\">#{o}</a></li>"
43
+ }
44
+ list += "</ol>"
45
+ @character.tell list
46
+ end
47
+ #print @user.flush
48
+ @user.update @character.state
49
+ #@character.flush
39
50
  if @character.queue.empty?
40
51
  receive
41
52
  end
42
53
  @plot.update
43
- print @user.flush
54
+ #print @user.flush
55
+ @user.update @character.state
56
+ #@character.flush
44
57
  end
45
58
 
46
59
  def receive
47
- print @character.scene.prompt_for(@character) + ' '
60
+ print @character.scene.prompt + ' '
48
61
  input = STDIN.gets
49
62
  @character.queue.push input unless input.nil?
50
63
  end
@@ -5,6 +5,10 @@ module Gamefic
5
5
  # Extend Engine::Base to connect with User::Tty, which provides ANSI
6
6
  # formatting for HTML.
7
7
  #
8
+ # @note Due to their dependency on io/console, User::Tty and Engine::Tty are
9
+ # not included in the core Gamefic library. `require gamefic/tty` if you
10
+ # need them.
11
+ #
8
12
  class Engine::Tty < Engine::Base
9
13
  def post_initialize
10
14
  self.user_class = Gamefic::User::Tty
@@ -1,19 +1,17 @@
1
1
  require "gamefic/node"
2
2
  require "gamefic/describable"
3
- require "gamefic/serialized"
3
+ require 'gamefic/messaging'
4
4
 
5
5
  module Gamefic
6
6
 
7
7
  class Entity
8
8
  include Node
9
9
  include Describable
10
- include Serialized
11
- extend Serialized::ClassMethods
10
+ include Messaging
12
11
  include Grammar::WordAdapter
13
-
12
+
14
13
  attr_reader :session
15
- serialize :name, :parent, :description
16
-
14
+
17
15
  def initialize(args = {})
18
16
  pre_initialize
19
17
  args.each { |key, value|
@@ -39,15 +37,6 @@ module Gamefic
39
37
  # raise NotImplementedError, "#{self.class} must implement post_initialize"
40
38
  end
41
39
 
42
- def tell(message)
43
- #TODO: Should this even be here? In all likelihood, only Characters receive tells, right?
44
- #TODO: On second thought, it might be interesting to see logs from an npc point of view.
45
- end
46
-
47
- def stream(message)
48
- # Unlike tell, this method sends raw data without formatting.
49
- end
50
-
51
40
  # Execute the entity's on_update blocks.
52
41
  # This method is typically called by the Engine that manages game execution.
53
42
  # The base method does nothing. Subclasses can override it.
@@ -0,0 +1,50 @@
1
+ module Gamefic
2
+ module Matchable
3
+ SPLIT_REGEXP = /[\s]+/
4
+
5
+ # Get an array of keywords associated with this object.
6
+ # The default implementation splits the value of self.to_s into an array.
7
+ #
8
+ # @return [Array<String>]
9
+ def keywords
10
+ self.to_s.downcase.split(SPLIT_REGEXP).uniq
11
+ end
12
+
13
+ # Determine if this object matches the provided description.
14
+ # In a regular match, every word in the description must be a keyword.
15
+ # Fuzzy matches accept words if a keyword starts with it, e.g., "red"
16
+ # would be a fuzzy match for "reddish."
17
+ #
18
+ # @example
19
+ # dog = "big red dog"
20
+ # dog.extend Gamefic::Matchable
21
+ #
22
+ # dog.match?("red dog") #=> true
23
+ # dog.match?("gray dog") #=> false
24
+ # dog.match?("red do") #=> false
25
+ #
26
+ # dog.match?("re do", fuzzy: true) #=> true
27
+ # dog.match?("red og", fuzzy: true) #=> false
28
+ #
29
+ # @return [Boolean]
30
+ def match? description, fuzzy: false
31
+ words = description.split(SPLIT_REGEXP)
32
+ return false if words.empty?
33
+ matches = 0
34
+ available = keywords
35
+ words.each { |w|
36
+ if fuzzy
37
+ available.each { |k|
38
+ if k.gsub(/[^a-z0-9]/, '').start_with?(w.downcase.gsub(/[^a-z0-9]/, ''))
39
+ matches +=1
40
+ break
41
+ end
42
+ }
43
+ else
44
+ matches +=1 if available.include?(w.downcase)
45
+ end
46
+ }
47
+ matches == words.length
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,45 @@
1
+ module Gamefic
2
+ module Messaging
3
+ # Send a message to the entity.
4
+ # This method will automatically wrap the message in HTML paragraphs.
5
+ # To send a message without paragraph formatting, use #stream instead.
6
+ #
7
+ # @param message [String]
8
+ def tell(message)
9
+ message = "<p>#{message.strip}</p>"
10
+ # This method uses String#gsub instead of String#gsub! for
11
+ # compatibility with Opal.
12
+ message = message.gsub(/[ \t\r]*\n[ \t\r]*\n[ \t\r]*/, '</p><p>')
13
+ message = message.gsub(/[ \t]*\n[ \t]*/, ' ')
14
+ #user.send message
15
+ p_set_messages messages + message
16
+ end
17
+
18
+ # Send a message to the Character as raw text.
19
+ # Unlike #tell, this method will not wrap the message in HTML paragraphs.
20
+ #
21
+ # @param message [String]
22
+ def stream(message)
23
+ p_set_messages messages + message.strip
24
+ end
25
+
26
+ # @return [String]
27
+ def messages
28
+ @messages ||= ''
29
+ end
30
+
31
+ def output
32
+ messages
33
+ end
34
+
35
+ def flush
36
+ p_set_messages '' unless messages.empty?
37
+ end
38
+
39
+ private
40
+
41
+ def p_set_messages str
42
+ @messages = str
43
+ end
44
+ end
45
+ end
data/lib/gamefic/node.rb CHANGED
@@ -11,6 +11,8 @@ module Gamefic
11
11
  @children ||= []
12
12
  @children.clone
13
13
  end
14
+
15
+ # @return [Array]
14
16
  def flatten
15
17
  array = Array.new
16
18
  children.each { |child|
@@ -18,9 +20,12 @@ module Gamefic
18
20
  }
19
21
  return array
20
22
  end
23
+
24
+ # @return [Object]
21
25
  def parent
22
26
  @parent
23
27
  end
28
+
24
29
  def parent=(node)
25
30
  return if node == @parent
26
31
  if node == self
@@ -43,19 +48,30 @@ module Gamefic
43
48
  end
44
49
  end
45
50
  end
51
+
52
+ # @return [Boolean]
53
+ def accessible?
54
+ true
55
+ end
56
+
46
57
  protected
58
+
47
59
  def add_child(node)
48
60
  children
49
61
  @children.push(node)
50
62
  end
63
+
51
64
  def rem_child(node)
52
65
  children
53
66
  @children.delete(node)
54
67
  end
68
+
55
69
  def concat_children(children)
56
70
  children.concat(children)
57
71
  end
72
+
58
73
  private
74
+
59
75
  def recurse_flatten(node)
60
76
  array = Array.new
61
77
  array.push(node)