gamefic 1.4.1 → 1.5.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/gamefic.rb +1 -2
  3. data/lib/gamefic/character.rb +31 -45
  4. data/lib/gamefic/director/delegate.rb +46 -24
  5. data/lib/gamefic/director/order.rb +7 -0
  6. data/lib/gamefic/director/parser.rb +11 -11
  7. data/lib/gamefic/engine/base.rb +2 -3
  8. data/lib/gamefic/entity.rb +8 -26
  9. data/lib/gamefic/plot.rb +38 -242
  10. data/lib/gamefic/plot/callbacks.rb +101 -0
  11. data/lib/gamefic/plot/command_mount.rb +70 -40
  12. data/lib/gamefic/plot/entities.rb +82 -0
  13. data/lib/gamefic/plot/host.rb +46 -0
  14. data/lib/gamefic/plot/playbook.rb +192 -0
  15. data/lib/gamefic/plot/players.rb +15 -0
  16. data/lib/gamefic/plot/scene_mount.rb +69 -31
  17. data/lib/gamefic/plot/snapshot.rb +20 -5
  18. data/lib/gamefic/scene/active.rb +8 -1
  19. data/lib/gamefic/scene/base.rb +4 -26
  20. data/lib/gamefic/scene/custom.rb +53 -3
  21. data/lib/gamefic/scene/multiple_choice.rb +1 -0
  22. data/lib/gamefic/scene/yes_or_no.rb +1 -1
  23. data/lib/gamefic/scene_data/multiple_scene.rb +0 -4
  24. data/lib/gamefic/shell.rb +0 -1
  25. data/lib/gamefic/source/file.rb +1 -1
  26. data/lib/gamefic/stage.rb +10 -2
  27. data/lib/gamefic/subplot.rb +70 -53
  28. data/lib/gamefic/syntax.rb +9 -2
  29. data/lib/gamefic/tester.rb +1 -1
  30. data/lib/gamefic/text.rb +8 -0
  31. data/lib/gamefic/{ansi.rb → text/ansi.rb} +12 -15
  32. data/lib/gamefic/text/html.rb +68 -0
  33. data/lib/gamefic/text/html/conversions.rb +250 -0
  34. data/lib/gamefic/text/html/entities.rb +9 -0
  35. data/lib/gamefic/tty.rb +2 -0
  36. data/lib/gamefic/user/tty.rb +2 -4
  37. data/lib/gamefic/version.rb +1 -1
  38. metadata +12 -8
  39. data/lib/gamefic/direction.rb +0 -46
  40. data/lib/gamefic/html.rb +0 -68
  41. data/lib/gamefic/html_to_ansi.rb +0 -185
  42. data/lib/gamefic/plot/entity_mount.rb +0 -45
  43. data/lib/gamefic/rule.rb +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 148fd56ec0966aee6fe9af25f63b450b64ecdcc3
4
- data.tar.gz: f0bee28dabd8400c7c350c5c8a8005b67ce01ebf
3
+ metadata.gz: 567c41f4ddc68dcf42fcb5952d6279250100f117
4
+ data.tar.gz: 7d2b9383cc049ab220917377ae7c76a7d4adc694
5
5
  SHA512:
6
- metadata.gz: 913706c95e0814ec1b4d9f39358d5afcb2a09b3a4f9d683684e64e404154e4c5d7735ea837c40dd4433525d3734ad2ba2a312fc23d9376dfb7d5886497a99238
7
- data.tar.gz: 9ac42568ffb6fc3247dbf386fd42aca1ad0e97d18b195a8ca3154ac8ac634525df10a120d7f28eeb182d8d31a63a9dfb121665640623fc569771b2243a16b0cc
6
+ metadata.gz: 3ce9c4b0051c2e64591b7a08ce693d2817b9c66e94240e3f4ae52ecb012b0e89355ca692efe01dbd502d1590d18e05755c93d96cd2c695e7a4ae068b6e5f8e66
7
+ data.tar.gz: 318903449d67116cf8da824a29c845180516de69ba4344d1f45499acef3f6cc5c728e3fb31641010dca3735a8a786835a4447a4dea5405a941dd482dcf014ce3
data/lib/gamefic.rb CHANGED
@@ -10,11 +10,10 @@ require "gamefic/scene"
10
10
  require "gamefic/query"
11
11
  require "gamefic/action"
12
12
  require "gamefic/syntax"
13
- require "gamefic/rule"
14
13
  require "gamefic/director"
15
14
  require "gamefic/plot"
15
+ require 'gamefic/subplot'
16
16
  require "gamefic/engine"
17
17
  require "gamefic/user"
18
- require "gamefic/direction"
19
18
 
20
19
  require 'gamefic/version'
@@ -1,5 +1,8 @@
1
1
  require 'gamefic/director'
2
2
 
3
+ class NotConclusionError < Exception
4
+ end
5
+
3
6
  module Gamefic
4
7
  class Character < Entity
5
8
  attr_reader :queue, :user
@@ -8,10 +11,11 @@ module Gamefic
8
11
  # @return [Entity,nil]
9
12
  attr_reader :last_object
10
13
  attr_accessor :object_of_pronoun
11
-
12
- serialize :scene
13
-
14
- def initialize(plot, args = {})
14
+ attr_reader :scene
15
+ attr_reader :next_scene
16
+ attr_accessor :playbook
17
+
18
+ def initialize(args = {})
15
19
  @queue = Array.new
16
20
  super
17
21
  @buffer_stack = 0
@@ -38,20 +42,14 @@ module Gamefic
38
42
  #
39
43
  # The command will be executed immediately regardless of game state.
40
44
  #
41
- # If the from_user argument is true, the command is assumed to have come
42
- # directly from user input. The character's last_order and last_object
43
- # will be updated with the result.
44
- #
45
45
  # @example Send a command as a string
46
46
  # character.perform "take the key"
47
47
  #
48
48
  # @example Send a command as a set of tokens
49
49
  # character.perform :take, @key
50
50
  #
51
- def perform(*command, from_user: false)
52
- o = Director.dispatch(self, *command)
53
- last_order = o if from_user
54
- o
51
+ def perform(*command)
52
+ Director.dispatch(self, *command)
55
53
  end
56
54
 
57
55
  # Quietly perform a command.
@@ -97,14 +95,6 @@ module Gamefic
97
95
  user.send message.strip unless user.nil?
98
96
  end
99
97
 
100
- # TODO This might not be necessary. The User#quit method was a noop anyway.
101
- #def destroy
102
- # if @user != nil
103
- # @user.quit
104
- # end
105
- # super
106
- #end
107
-
108
98
  # Proceed to the next Action in the current stack.
109
99
  # This method is typically used in Action blocks to cascade through
110
100
  # multiple implementations of the same verb.
@@ -127,46 +117,42 @@ module Gamefic
127
117
  # end
128
118
  #
129
119
  def proceed
130
- return if delegate_stack.last.nil?
131
- delegate_stack.last.proceed
120
+ Director::Delegate.proceed_for self
132
121
  end
133
122
 
134
- def cue scene_name
135
- @scene = scene_name
123
+ def cue scene
136
124
  @next_scene = nil
137
- plot.scenes[scene_name].start self
125
+ @scene = scene
126
+ @scene.start self unless @scene.nil?
138
127
  end
139
-
140
- def prepare scene_name
141
- @next_scene = scene_name
128
+
129
+ def prepare scene
130
+ @next_scene = scene
142
131
  end
143
132
 
144
- def conclude scene_name
145
- scene = plot.scenes[scene_name]
146
- raise "#{scene_name} is not a conclusion" unless scene.kind_of?(Scene::Conclusion)
147
- cue scene_name
133
+ def conclude scene
134
+ raise NotConclusionError if !scene.kind_of?(Scene::Conclusion)
135
+ cue scene
148
136
  end
149
-
150
- # Get the name of the character's current scene
151
- #
152
- # @return [Symbol] The name of the scene
153
- def scene
154
- @scene
137
+
138
+ def concluded?
139
+ !scene.nil? and scene.kind_of?(Scene::Conclusion)
155
140
  end
156
141
 
157
- # Alias for Character#cue key
158
- def scene= key
159
- cue key.to_sym
142
+ def performed order
143
+ @last_order = order
160
144
  end
161
-
162
- def next_scene
163
- @next_scene
145
+
146
+ def prompt
147
+ scene.nil? ? '>' : scene.prompt_for(self)
164
148
  end
165
-
149
+
166
150
  private
151
+
167
152
  def delegate_stack
168
153
  @delegate_stack ||= []
169
154
  end
155
+
170
156
  def last_order=(order)
171
157
  return if order.nil?
172
158
  @last_order = order
@@ -1,46 +1,54 @@
1
1
  module Gamefic
2
2
  module Director
3
3
  class Delegate
4
- # If we use Query::Base.new in the @disambiguator declaration, Opal
5
- # passes the block to the query instead of the action.
6
- base = Query::Base.new
7
- @@disambiguator = Action.new nil, base do |actor, entities|
8
- definites = []
9
- entities.each { |entity|
10
- definites.push entity.definitely
11
- }
12
- actor.tell "I don't know which you mean: #{definites.join_or}."
4
+
5
+ class << self
6
+ def proceed_for actor
7
+ return if stack_map[actor].nil?
8
+ stack_map[actor].last.proceed unless stack_map[actor].last.nil?
9
+ end
10
+
11
+ private
12
+
13
+ def stack_map
14
+ @stack_map ||= {}
15
+ end
13
16
  end
14
- @@disambiguator.meta = true
17
+
15
18
  def initialize(actor, orders)
16
19
  @actor = actor
17
20
  @orders = orders
21
+ @did = []
22
+ @validated = false
18
23
  end
24
+
19
25
  def proceed
20
26
  return if @orders.length == 0
21
- @actor.send(:delegate_stack).push self
22
27
  executed = false
23
28
  while !executed
24
29
  order = @orders.shift
25
30
  break if order.nil?
26
- executed = try(order)
31
+ # HACK: Make sure Character#proceed does not repeat an action
32
+ next if @did.include?(order.action)
33
+ @did.push order.action
34
+ @last_action = order.action
35
+ executed = attempt(order)
36
+ return if order.canceled?
27
37
  end
28
- @actor.send(:delegate_stack).pop
29
38
  end
39
+
30
40
  def execute
31
41
  return if @orders.length == 0
32
- if !@orders[0].action.meta?
33
- @actor.plot.asserts.each_pair { |name, rule|
34
- result = rule.test(@actor, @orders[0].action.verb, @orders[0].arguments)
35
- if result == false
36
- return
37
- end
38
- }
39
- end
42
+ stack_map[@actor] ||= []
43
+ stack_map[@actor].push self
40
44
  proceed
45
+ stack_map[@actor].pop
46
+ stack_map.delete @actor if stack_map[@actor].empty?
41
47
  end
48
+
42
49
  private
43
- def try order
50
+
51
+ def attempt order
44
52
  executed = false
45
53
  arg_i = 0
46
54
  final_arguments = []
@@ -53,7 +61,7 @@ module Gamefic
53
61
  else
54
62
  ambiguous = argument
55
63
  end
56
- order = Order.new(@actor, @@disambiguator, [])
64
+ order = Order.new(@actor, @actor.playbook.disambiguator, [])
57
65
  final_arguments = [ambiguous]
58
66
  break
59
67
  end
@@ -66,7 +74,7 @@ module Gamefic
66
74
  break
67
75
  end
68
76
  end
69
- if order.action == @@disambiguator or final_arguments.nil?
77
+ if order.action == @actor.playbook.disambiguator or final_arguments.nil?
70
78
  break
71
79
  end
72
80
  if order.action.queries[arg_i].allow_many?
@@ -80,6 +88,13 @@ module Gamefic
80
88
  arg_i += 1
81
89
  }
82
90
  if !final_arguments.nil?
91
+ unless @validated or order.action.meta?
92
+ @actor.playbook.validators.each { |v|
93
+ v.call order
94
+ return false if order.canceled?
95
+ }
96
+ end
97
+ @validated = true
83
98
  # The actor is always the first argument to an Action proc
84
99
  final_arguments.unshift @actor
85
100
  order.action.execute(*final_arguments)
@@ -87,6 +102,7 @@ module Gamefic
87
102
  end
88
103
  executed
89
104
  end
105
+
90
106
  def validate argument, arg_i, order
91
107
  valid = []
92
108
  argument.each { |m|
@@ -99,6 +115,12 @@ module Gamefic
99
115
  }
100
116
  valid
101
117
  end
118
+
119
+ private
120
+
121
+ def stack_map
122
+ Delegate.send(:stack_map)
123
+ end
102
124
  end
103
125
  end
104
126
  end
@@ -5,6 +5,13 @@ module Gamefic
5
5
  @actor = actor
6
6
  @action = action
7
7
  @arguments = arguments
8
+ @canceled = false
9
+ end
10
+ def cancel
11
+ @canceled = true
12
+ end
13
+ def canceled?
14
+ @canceled
8
15
  end
9
16
  end
10
17
  end
@@ -7,7 +7,7 @@ module Gamefic
7
7
  def self.from_tokens(actor, tokens)
8
8
  options = []
9
9
  command = tokens.shift
10
- actions = actor.plot.actions_with_verb(command.to_sym)
10
+ actions = actor.playbook.actions_for(command.to_sym)
11
11
  actions.each { |action|
12
12
  if action.queries.length == tokens.length
13
13
  valid = true
@@ -38,9 +38,9 @@ module Gamefic
38
38
  if command.to_s == ''
39
39
  return options
40
40
  end
41
- matches = Syntax.tokenize(command, actor.plot.syntaxes)
41
+ matches = Syntax.tokenize(command, actor.playbook.syntaxes)
42
42
  matches.each { |match|
43
- actions = actor.plot.actions_with_verb(match.verb)
43
+ actions = actor.playbook.actions_for(match.verb)
44
44
  actions.each { |action|
45
45
  options.concat bind_contexts_in_result(actor, match.arguments, action)
46
46
  }
@@ -53,18 +53,18 @@ module Gamefic
53
53
  queries = action.queries.clone
54
54
  objects = execute_query(actor, handler.clone, queries, action)
55
55
  num_nil = 0
56
- while objects.length == 0 and queries.last.optional?
57
- num_nil +=1
58
- queries.pop
59
- objects = execute_query(actor, handler.clone, queries, action, num_nil)
60
- end
56
+ #while objects.length == 0 and queries.last.optional?
57
+ # num_nil +=1
58
+ # queries.pop
59
+ # objects = execute_query(actor, handler.clone, queries, action, num_nil)
60
+ #end
61
61
  return objects
62
62
  end
63
63
  def execute_query(actor, arguments, queries, action, num_nil = 0)
64
64
  # If the action verb is nil, treat the first argument as a query
65
- arguments.shift unless action.verb.nil?
66
- prepared = Array.new
67
- objects = Array.new
65
+ #arguments.shift unless action.verb.nil?
66
+ prepared = []
67
+ objects = []
68
68
  valid = true
69
69
  last_remainder = nil
70
70
  queries.clone.each { |context|
@@ -29,7 +29,7 @@ module Gamefic
29
29
  def run
30
30
  connect
31
31
  @plot.introduce @character
32
- turn until @plot.concluded?(@character)
32
+ turn until @character.concluded?
33
33
  print @user.flush
34
34
  end
35
35
 
@@ -44,10 +44,9 @@ module Gamefic
44
44
  end
45
45
 
46
46
  def receive
47
- print @plot.scenes[@character.scene].prompt_for(@character) + ' '
47
+ print @character.scene.prompt_for(@character) + ' '
48
48
  input = STDIN.gets
49
49
  @character.queue.push input unless input.nil?
50
- puts ''
51
50
  end
52
51
  end
53
52
 
@@ -11,58 +11,48 @@ module Gamefic
11
11
  extend Serialized::ClassMethods
12
12
  include Grammar::WordAdapter
13
13
 
14
- attr_reader :session, :plot
14
+ attr_reader :session
15
15
  serialize :name, :parent, :description
16
16
 
17
- def initialize(plot, args = {})
18
- if (plot.kind_of?(Plot) == false)
19
- raise "First argument must be a Plot"
20
- end
17
+ def initialize(args = {})
21
18
  pre_initialize
22
- @plot = plot
23
- @plot.send :add_entity, self
24
19
  args.each { |key, value|
25
20
  send "#{key}=", value
26
21
  }
27
- @update_procs = Array.new
28
22
  @session = Hash.new
29
23
  yield self if block_given?
30
24
  post_initialize
31
25
  end
26
+
32
27
  def uid
33
28
  if @uid == nil
34
29
  @uid = self.object_id.to_s
35
30
  end
36
31
  @uid
37
32
  end
33
+
38
34
  def pre_initialize
39
35
  # raise NotImplementedError, "#{self.class} must implement post_initialize"
40
36
  end
37
+
41
38
  def post_initialize
42
39
  # raise NotImplementedError, "#{self.class} must implement post_initialize"
43
40
  end
41
+
44
42
  def tell(message)
45
43
  #TODO: Should this even be here? In all likelihood, only Characters receive tells, right?
46
44
  #TODO: On second thought, it might be interesting to see logs from an npc point of view.
47
45
  end
46
+
48
47
  def stream(message)
49
48
  # Unlike tell, this method sends raw data without formatting.
50
49
  end
51
50
 
52
51
  # Execute the entity's on_update blocks.
53
52
  # This method is typically called by the Engine that manages game execution.
53
+ # The base method does nothing. Subclasses can override it.
54
54
  #
55
55
  def update
56
- @update_procs.each { |p|
57
- p.call self
58
- }
59
- end
60
-
61
- # Add a block to be executed when the game updates a turn.
62
- #
63
- # @yieldparam [Entity]
64
- def on_update(&block)
65
- @update_procs.push block
66
56
  end
67
57
 
68
58
  # Set the Entity's parent.
@@ -75,14 +65,6 @@ module Gamefic
75
65
  super
76
66
  end
77
67
 
78
- # Remove this Entity from its current Plot.
79
- #
80
- def destroy
81
- self.parent = nil
82
- # TODO: Need to call this private method here?
83
- @plot.send(:rem_entity, self)
84
- end
85
-
86
68
  # Get an extended property.
87
69
  #
88
70
  # @param key [Symbol] The property's name.