seh 0.1.0 → 0.3.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.
- 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
|