gamefic 1.4.1 → 1.5.0

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