seh 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ module Event
2
+ class << self
3
+ def hostile event, aggressor, aggressee
4
+ event.target aggressor, aggressee
5
+ event.type :hostile
6
+ event.aggressor = aggressor
7
+ event.aggressee = aggressee
8
+ event
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ require 'seh'
2
+ require_relative "damage"
3
+ require_relative "hostile"
4
+
5
+ module Event
6
+ class << self
7
+ def melee_attack event, attacker, defender, attack_hit, attack_damage
8
+ hostile event, attacker, defender
9
+
10
+ event.target attacker, defender
11
+ event.type :melee_attack
12
+
13
+ event.attacker = attacker
14
+ event.defender = defender
15
+ event.attack_hit = attack_hit # true/false if this attack is currently hitting
16
+ event.attack_damage = attack_damage # damage this attack will do if it hits
17
+
18
+ event.add_stage :melee_determine_hit # an opportunity to affect the outcome of attack_hit
19
+ event.add_stage :melee_miss do !event.attack_hit end # unless attack_hit, run melee_miss
20
+ event.add_stage :melee_hit do event.attack_hit end # if attack_hit is true, run melee_hit
21
+
22
+ event.finish do # when the melee_attack event is over, create a damage event if the attack hit
23
+ next unless event.attack_hit
24
+ damage_event = Seh::Event.new
25
+ damage damage_event, event.attacker, event.defender, event.attack_damage
26
+ damage_event.dispatch
27
+ end
28
+
29
+ event
30
+ end
31
+ end # class << self
32
+ end
@@ -0,0 +1,57 @@
1
+
2
+ COLORS = {
3
+ "{@" => "\033[0m", #reset
4
+ # styles
5
+ "{!" => "\033[1m", #bold
6
+ "underlineZZ" => "\033[4m",
7
+ "blinkZZ" => "\033[5m",
8
+ "reverseZZ" => "\033[7m",
9
+ "concealedZZZ" => "\033[8m",
10
+ # font colors
11
+ "{FB" => "\033[30m",
12
+ "{FR" => "\033[31m",
13
+ "{FG" => "\033[32m",
14
+ "{FY" => "\033[33m",
15
+ "{FU" => "\033[34m",
16
+ "{FM" => "\033[35m",
17
+ "{FC" => "\033[36m",
18
+ "{FW" => "\033[37m",
19
+ # background colors
20
+ "{BB" => "\033[40m",
21
+ "{BR" => "\033[41m",
22
+ "{BG" => "\033[42m",
23
+ "{BY" => "\033[43m",
24
+ "{BU" => "\033[44m",
25
+ "{BM" => "\033[45m",
26
+ "{BC" => "\033[46m",
27
+ "{BW" => "\033[47m"
28
+ }
29
+
30
+ def color msg
31
+ COLORS.each do |color,code|
32
+ msg.gsub! color, code
33
+ end
34
+ msg
35
+ end
36
+
37
+ FOREGROUND_COLORS = [
38
+ "{FR",
39
+ "{FG",
40
+ "{FY",
41
+ "{FU",
42
+ "{FM",
43
+ "{FC",
44
+ ]
45
+
46
+ # cycle through foreground colors
47
+ def next_foreground_color
48
+ color = FOREGROUND_COLORS.shift
49
+ FOREGROUND_COLORS.push color
50
+ color
51
+ end
52
+
53
+ EVENT_COLORS = {}
54
+ def puts_color_event event_id, msg
55
+ EVENT_COLORS[event_id] ||= next_foreground_color
56
+ puts color "{!#{EVENT_COLORS[event_id]}#{msg}{@"
57
+ end
@@ -0,0 +1,10 @@
1
+ require 'seh'
2
+
3
+ class Mob
4
+ include Seh::EventTarget
5
+ attr_accessor :hp, :observers
6
+ def initialize
7
+ @hp = 100
8
+ @observers = []
9
+ end
10
+ end
data/lib/seh.rb CHANGED
@@ -1,22 +1,19 @@
1
- require "seh/version"
2
- require "seh/event_type"
3
- require "seh/event_target"
4
- require "seh/event"
1
+ Dir[File.dirname(__FILE__) + '/seh/*.rb'].each {|file| require file }
5
2
 
6
3
  module Seh
7
4
  class << self
8
5
  # @note alias of {EventType::And}
9
- def and( *types )
6
+ def and *types
10
7
  EventType::And.new *types
11
8
  end
12
9
 
13
10
  # @note alias of {EventType::Or}
14
- def or( *types )
11
+ def or *types
15
12
  EventType::Or.new *types
16
13
  end
17
14
 
18
15
  # @note alias of {EventType::Not}
19
- def not( type )
16
+ def not type
20
17
  EventType::Not.new type
21
18
  end
22
19
  end
@@ -1,178 +1,161 @@
1
+ require 'set'
1
2
  require 'ostruct'
2
3
 
3
4
  module Seh
4
- # @private
5
- module Private
6
- START = 0
7
- BEFORE = 100
8
-
9
- BEFORE_SUCCESS = 250
10
- BEFORE_FAILURE = 250
11
-
12
- SUCCESS = 500
13
- FAILURE = 500
14
-
15
- AFTER_SUCCESS = 750
16
- AFTER_FAILURE = 750
17
-
18
- AFTER = 900
19
- FINISH = 1000
20
-
21
- class EventData
22
- attr_accessor :types, :type_map, :target, :time, :staged_handlers, :success
23
-
24
- def initialize
25
- @types = []
26
- @type_map = {}
27
- @target = nil
28
- @time = Time.now
29
- @success = true
30
-
31
- # staged handlers
32
- @staged_handlers = {}
33
- end
34
- end
35
-
36
- class EventStateReady
37
- class << self
38
- def type( data, t )
39
- data.types << t
40
- apply_type_map data
41
- end
42
-
43
- def type_map( data, map = {} )
44
- map.each_pair do |type, implied_types|
45
- data.type_map[type] ||= []
46
- data.type_map[type].concat implied_types
47
- data.type_map[type].uniq!
48
- end
49
- apply_type_map data
50
- end
51
-
52
- private
53
- def apply_type_map( data )
54
- while true
55
- original_size = data.types.size
56
- data.type_map.each_pair { |type, implied_types| data.types.concat implied_types if data.types.include? type }
57
- data.types.uniq!
58
- break if original_size = data.types.size
59
- end
60
- end
61
- end
62
- end
63
-
64
- class EventStateInflight
65
- end
66
-
67
- class EventStateDone
68
- end
69
- end
70
-
71
5
  class Event < OpenStruct
72
- def initialize(target, &block)
73
- super()
74
- raise "Event expects a target" unless target
75
- raise "Event expects the target to include EventTarget" unless target.class.include? EventTarget
76
- @state = Private::EventStateReady
77
- @data = Private::EventData.new
78
- @data.target = target
79
- instance_eval(&block) if block
80
- end
81
-
82
- def fail
83
- @data.success = false
84
- end
85
-
86
- def success?
87
- @data.success
88
- end
89
-
6
+ def initialize
7
+ super
8
+ @state = :ready
9
+ @types = Set.new
10
+ @targets = Set.new
11
+ @start_callbacks = []
12
+ @finish_callbacks = []
13
+ @stage_callbacks = {}
14
+ @stage_decision_blocks = {}
15
+ @stages = Set.new
16
+ @abort = false
17
+ yield self if block_given?
18
+ end
19
+
20
+ # Dispatch this event, notifying all targets of the event and executing any callbacks.
21
+ # #dispatch may only be called once
22
+ # Dispatch algorithm:
23
+ # 1. determine the full set of targets affected by this event
24
+ # 2. run callbacks on targets which match this event's types
25
+ # 3. run stage callbacks contained in this event; typically targets will append stage callbacks to this event using Event#bind, #start, #finish
26
+ # Callback execution order:
27
+ # start callbacks
28
+ # stage callabcks - in the order stages were added
29
+ # finish callbacks
30
+ # Callbacks in the same stage have arbitrary execution order
31
+ # @return nil
90
32
  def dispatch
91
- raise "Event#dispatch may only be called once" unless @state == Private::EventStateReady
92
- @state = Private::EventStateInflight
93
- collect_targets.each { |t| t.each_bind { |bind| bind.block.call self if bind.event_type.match @data.types } }
94
- @data.staged_handlers.each_key.sort.each { |stage| @data.staged_handlers[stage].each { |block| block.call self } }
95
- @state = Private::EventStateDone
96
- end
97
-
98
- def target
99
- @data.target
100
- end
101
-
102
- def type( event_type )
103
- @state.type @data, event_type
33
+ raise "Event#dispatch may only be called once" unless @state == :ready
34
+ @state = :inflight
35
+ return if @abort
36
+ run_target_callbacks
37
+ run_stage_callbacks
38
+ @state = :done
104
39
  nil
105
40
  end
106
41
 
107
- def type_map( map )
108
- @state.type_map @data, map
42
+ # Abort this Event. After #abort is called, some in-progress work may still be completed.
43
+ # Abort semantics:
44
+ # - if #abort is called before #dispatch, #dispatch return immediately, no target will know the event occurred, and no callbacks will be executed
45
+ # - if #abort is called by a target while visiting the set of targets, each target will still receive the event but no stage callbacks will be exewcuted
46
+ # - if #abort is called during start callbacks, start will complete and no stage or finish callbacks will be run
47
+ # - if #abort is called during a stage callback, the current stage will complete and no other stage or finish callbacks will be run
48
+ # - if #abort is called during a finish callback, finish will complete, i.e. calling #abort during a finish callbacks is fairly pointless
49
+ # @return nil
50
+ def abort
51
+ @abort = true
109
52
  nil
110
53
  end
111
54
 
112
- def match_type( event_type )
113
- event_type = EventType.new event_type unless event_type.is_a? EventType
114
- event_type.match @data.types
55
+ # @return true if #abort has been called, false otherwise
56
+ def aborted?
57
+ @abort
115
58
  end
116
59
 
117
- def time
118
- @data.time.dup
119
- end
120
-
121
- def start(&block)
122
- staged_handler Private::START, block if block_given?
123
- end
124
-
125
- def before(&block)
126
- staged_handler Private::BEFORE, block if block_given?
127
- end
128
-
129
- def before_success(&block)
130
- staged_handler Private::BEFORE_SUCCESS, ->e{ block.call e if e.success? } if block_given?
60
+ # Add targets to this event. May not be called after or during #dispatch
61
+ # @param targets - zero or more EventTarget objects to add to this event
62
+ # @return nil
63
+ def target *targets
64
+ raise "Event#target is disallowed after Event#dispatch is called" unless @state == :ready
65
+ targets.each { |target| @targets << target }
66
+ nil
131
67
  end
132
68
 
133
- def before_failure(&block)
134
- staged_handler Private::BEFORE_FAILURE, ->e{ block.call e unless e.success? } if block_given?
69
+ # Add event types to this event. May not be called after or during #dispatch
70
+ # @param types - zero or more types to add to this Event. The Event is simultaneously all of these types
71
+ # @return nil
72
+ def type *event_types
73
+ raise "Event#type is disallowed after Event#dispatch is called" unless @state == :ready
74
+ event_types.each { |type| @types << type }
75
+ nil
135
76
  end
136
77
 
137
- def success(&block)
138
- staged_handler Private::SUCCESS, ->e{ block.call e if e.success? } if block_given?
78
+ # Add the passed new stage to this event
79
+ # @param new_stage - a stage to add to this event
80
+ # @block - a block with a single parameter |event|; during #dispatch, this block will be called with self immediately prior to executing new_stage's callbacks. new_stage and its callbacks will be skipped (not executed) if this block returns falsy.
81
+ # @return nil
82
+ def add_stage new_stage, &stage_decision_block
83
+ raise "Event#add_stage is disallowed after Event#dispatch is called" unless @state == :ready
84
+ @stages << new_stage
85
+ @stage_callbacks[new_stage] ||= []
86
+ @stage_decision_blocks[new_stage] = stage_decision_block if block_given?
87
+ nil
139
88
  end
140
89
 
141
- def failure(&block)
142
- staged_handler Private::FAILURE, ->e{ block.call e unless e.success? } if block_given?
90
+ # Return true if this event's types match the passed EventType
91
+ # @param event_type - an EventType to match against this event's types
92
+ # @return true or false - result of passed EventType#match on this event's types
93
+ def match_type? event_type
94
+ event_type = EventType.new event_type unless event_type.is_a? EventType
95
+ event_type.match @types
143
96
  end
144
97
 
145
- def after_success(&block)
146
- staged_handler Private::AFTER_SUCCESS, ->e{ block.call e if e.success? } if block_given?
98
+ # Bind the passed block as a callback for the passed stage
99
+ # @param stage - a stage which has been added using #add_stage
100
+ # @block - a callback receiving a single parameter, |event|, which will be added to stage's callbacks
101
+ # @return nil
102
+ def bind stage, &block
103
+ @stage_callbacks[stage] << block if block_given?
104
+ nil
147
105
  end
148
106
 
149
- def after_failure(&block)
150
- staged_handler Private::AFTER_FAILURE, ->e{ block.call e unless e.success? } if block_given?
107
+ # Bind the passed block as a start callback
108
+ # @block - a callback receiving a single parameter, |event|, which will be added to the start callbacks
109
+ # @return nil
110
+ def start &block
111
+ @start_callbacks << block if block_given?
112
+ nil
151
113
  end
152
114
 
153
- def after(&block)
154
- staged_handler Private::AFTER, block if block_given?
115
+ # Bind the passed block as a finish callback
116
+ # @block - a callback receiving a single parameter, |event|, which will be added to the finish callbacks
117
+ # @return nil
118
+ def finish &block
119
+ @finish_callbacks << block if block_given?
120
+ nil
155
121
  end
156
122
 
157
- def finish(&block)
158
- staged_handler Private::FINISH, block if block_given?
123
+ private
124
+ # Used in #dispatch, run callbacks that match our types on each target in the target closure
125
+ def run_target_callbacks
126
+ collect_targets.each do |target|
127
+ target.each_matching_callback(@types) { |callback| callback.call self }
128
+ end
159
129
  end
160
130
 
161
- private
162
- def staged_handler( stage, block )
163
- @data.staged_handlers[stage] ||= []
164
- @data.staged_handlers[stage] << block
165
- nil
131
+ # Used in #dispatch, run the stage callbacks on this event; to be run after each target had a chance to append callbacks
132
+ # Callback execution order:
133
+ # start callbacks
134
+ # stage callabcks - in the order stages were added
135
+ # finish callbacks
136
+ # Callbacks in the same stage have arbitrary execution order
137
+ def run_stage_callbacks
138
+ return if @abort
139
+ @start_callbacks.each { |block| block.call self }
140
+ @stages.each do |stage|
141
+ return if @abort
142
+ next if @stage_decision_blocks.key? stage and not @stage_decision_blocks[stage].call self
143
+ @stage_callbacks[stage].each { |block| block.call self }
144
+ end
145
+ return if @abort
146
+ @finish_callbacks.each { |block| block.call self }
166
147
  end
167
148
 
149
+ # Compute the target closure, equal to the set of targets on this event and their observers (and recursive observers)
168
150
  def collect_targets
169
- targets_working = [@data.target]
170
- targets_final = []
171
- while t = targets_working.shift do
172
- targets_final << t
173
- targets_working.concat t.parents if t.respond_to? :parents
174
- end
175
- targets_final.uniq
151
+ all_targets = @targets.dup # @targets must remain the original set of targets on this event, and all_targets will be mutated. NOTE: @targets isn't actually used after collect_targets() is called, so we might remove the .dup()
152
+ observers = Set.new
153
+ begin
154
+ original_size = all_targets.size
155
+ all_targets.each { |target| observers.merge target.observers }
156
+ all_targets.merge observers
157
+ end while all_targets.size != original_size
158
+ all_targets
176
159
  end
177
160
  end
178
161
  end
@@ -0,0 +1,15 @@
1
+ require_relative 'event_type'
2
+
3
+ module Seh
4
+ # @private
5
+ # Internal use only. Container for an event_type,block pair bound to an EventTarget. Converts event_type into a Seh::EventType
6
+ class EventBind
7
+ attr_reader :event_type, :block
8
+
9
+ def initialize event_type, &block
10
+ event_type = EventType.new event_type unless event_type.is_a? EventType
11
+ @event_type = event_type
12
+ @block = block
13
+ end
14
+ end
15
+ end