seh 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +0 -1
- data/Gemfile +2 -2
- data/Gemfile.lock +25 -0
- data/README.md +82 -8
- data/Rakefile +31 -0
- data/examples/epic_battle.rb +240 -0
- data/examples/event.rb +1 -0
- data/examples/event/damage.rb +31 -0
- data/examples/event/hostile.rb +11 -0
- data/examples/event/melee_attack.rb +32 -0
- data/examples/event_color.rb +57 -0
- data/examples/mob.rb +10 -0
- data/lib/seh.rb +4 -7
- data/lib/seh/event.rb +126 -143
- data/lib/seh/event_bind.rb +15 -0
- data/lib/seh/event_bind_disconnector.rb +17 -0
- data/lib/seh/event_target.rb +30 -22
- data/lib/seh/event_type.rb +11 -21
- data/lib/seh/version.rb +1 -1
- data/seh.gemspec +2 -2
- data/spec/seh/event_bind_disconnector_spec.rb +23 -0
- data/spec/seh/event_bind_spec.rb +22 -0
- data/spec/seh/event_spec.rb +209 -0
- data/spec/seh/event_target_spec.rb +87 -0
- data/spec/seh/event_type_spec.rb +134 -0
- data/spec/seh_spec.rb +25 -0
- metadata +23 -18
@@ -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
|
data/examples/mob.rb
ADDED
data/lib/seh.rb
CHANGED
@@ -1,22 +1,19 @@
|
|
1
|
-
require
|
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
|
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
|
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
|
16
|
+
def not type
|
20
17
|
EventType::Not.new type
|
21
18
|
end
|
22
19
|
end
|
data/lib/seh/event.rb
CHANGED
@@ -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
|
73
|
-
super
|
74
|
-
|
75
|
-
|
76
|
-
@
|
77
|
-
@
|
78
|
-
@
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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 ==
|
92
|
-
@state =
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
108
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
55
|
+
# @return true if #abort has been called, false otherwise
|
56
|
+
def aborted?
|
57
|
+
@abort
|
115
58
|
end
|
116
59
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
134
|
-
|
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
|
-
|
138
|
-
|
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
|
-
|
142
|
-
|
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
|
-
|
146
|
-
|
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
|
-
|
150
|
-
|
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
|
-
|
154
|
-
|
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
|
-
|
158
|
-
|
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
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|