state_machines-mermaid 0.1.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.
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Epic battle scenario with multiple interacting state machines
4
+ class Battle
5
+ attr_accessor :weather, :terrain, :advantage, :participants, :phase_start_time, :left_flank_secure,
6
+ :right_flank_secure
7
+
8
+ def initialize
9
+ @participants = {
10
+ dragons: [],
11
+ mages: [],
12
+ regiments: [],
13
+ heroes: []
14
+ }
15
+ @advantage = 50
16
+ end
17
+
18
+ state_machine :phase, initial: :preparation do
19
+ state :preparation do
20
+ def allow_reinforcements?
21
+ true
22
+ end
23
+ end
24
+
25
+ state :deployment do
26
+ def can_reposition?
27
+ true
28
+ end
29
+ end
30
+
31
+ state :skirmish do
32
+ def casualties_multiplier
33
+ 0.5
34
+ end
35
+ end
36
+
37
+ state :main_battle do
38
+ def casualties_multiplier
39
+ 2.0
40
+ end
41
+ end
42
+
43
+ state :climax do
44
+ def heroic_actions_enabled?
45
+ true
46
+ end
47
+ end
48
+
49
+ state :aftermath do
50
+ def battle_ended?
51
+ true
52
+ end
53
+ end
54
+
55
+ event :begin_deployment do
56
+ transition preparation: :deployment, if: :all_forces_ready?
57
+ end
58
+
59
+ event :commence_battle do
60
+ transition deployment: :skirmish
61
+ end
62
+
63
+ after_transition on: :commence_battle, to: :skirmish do |battle|
64
+ battle.phase_start_time = Time.now
65
+ end
66
+
67
+ event :escalate do
68
+ transition skirmish: :main_battle, if: -> { phase_duration > 300 || heavy_casualties? }
69
+ end
70
+
71
+ event :reach_climax do
72
+ transition main_battle: :climax, if: :decisive_moment?
73
+ end
74
+
75
+ event :conclude do
76
+ transition %i[skirmish main_battle climax] => :aftermath
77
+ end
78
+
79
+ after_transition any => any do |battle, transition|
80
+ battle.log_phase_change(transition)
81
+ end
82
+ end
83
+
84
+ # Weather conditions affecting battle
85
+ state_machine :weather, initial: :clear do
86
+ state :clear do
87
+ def visibility
88
+ 1000
89
+ end
90
+ end
91
+
92
+ state :fog do
93
+ def visibility
94
+ 100
95
+ end
96
+
97
+ def ranged_penalty
98
+ 0.5
99
+ end
100
+ end
101
+
102
+ state :rain do
103
+ def movement_penalty
104
+ 0.8
105
+ end
106
+
107
+ def fire_magic_penalty
108
+ 0.6
109
+ end
110
+ end
111
+
112
+ state :storm do
113
+ def movement_penalty
114
+ 0.5
115
+ end
116
+
117
+ def flying_impossible?
118
+ true
119
+ end
120
+ end
121
+
122
+ event :weather_change do
123
+ transition clear: %i[fog rain], if: :weather_front_approaching?
124
+ transition fog: %i[clear rain]
125
+ transition rain: %i[storm clear]
126
+ transition storm: :rain
127
+ end
128
+ end
129
+
130
+ # Battlefield control
131
+ state_machine :battlefield_control, initial: :contested do
132
+ state :contested
133
+ state :advantage_left
134
+ state :advantage_right
135
+ state :center_breakthrough
136
+ state :encirclement
137
+
138
+ event :gain_left do
139
+ transition contested: :advantage_left
140
+ end
141
+
142
+ event :gain_right do
143
+ transition contested: :advantage_right
144
+ end
145
+
146
+ event :breakthrough do
147
+ transition %i[advantage_left advantage_right] => :center_breakthrough
148
+ end
149
+
150
+ event :encircle do
151
+ transition %i[advantage_left advantage_right] => :encirclement, if: :flanks_secured?
152
+ end
153
+ end
154
+
155
+ def all_forces_ready?
156
+ participants.values.all? { |forces| forces.any?(&:ready?) }
157
+ end
158
+
159
+ def phase_duration
160
+ phase_start_time ? Time.now - phase_start_time : 0
161
+ end
162
+
163
+ def heavy_casualties?
164
+ total_casualties > initial_forces * 0.2
165
+ end
166
+
167
+ def decisive_moment?
168
+ advantage > 75 || advantage < 25
169
+ end
170
+
171
+ def log_phase_change(transition)
172
+ puts "Battle phase changed from #{transition.from} to #{transition.to}"
173
+ end
174
+
175
+ def weather_front_approaching?
176
+ rand(100) < 30
177
+ end
178
+
179
+ def flanks_secured?
180
+ @left_flank_secure && @right_flank_secure
181
+ end
182
+
183
+ def total_casualties
184
+ participants[:regiments].sum(&:casualties)
185
+ end
186
+
187
+ def initial_forces
188
+ @initial_forces ||= participants[:regiments].sum(&:soldiers)
189
+ end
190
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Base character class with complex state machine
4
+ class Character
5
+ attr_accessor :health, :mana, :stamina, :experience, :level, :gold, :inventory
6
+
7
+ def initialize
8
+ @health = 100
9
+ @mana = 100
10
+ @stamina = 100
11
+ @experience = 0
12
+ @level = 1
13
+ @gold = 0
14
+ @inventory = []
15
+ super
16
+ end
17
+
18
+ state_machine :status, initial: :idle do
19
+ # Basic states
20
+ state :idle do
21
+ def currently_resting?
22
+ true
23
+ end
24
+ end
25
+
26
+ state :combat do
27
+ def in_danger?
28
+ true
29
+ end
30
+ end
31
+
32
+ state :casting do
33
+ def actively_casting?
34
+ true
35
+ end
36
+ end
37
+
38
+ state :resting do
39
+ def regenerating?
40
+ true
41
+ end
42
+ end
43
+
44
+ state :stunned do
45
+ def incapacitated?
46
+ true
47
+ end
48
+ end
49
+
50
+ state :dead do
51
+ def game_over?
52
+ true
53
+ end
54
+ end
55
+
56
+ # Complex transitions with conditions
57
+ event :engage do
58
+ transition idle: :combat, if: :can_fight?
59
+ transition resting: :combat, if: :interrupt_rest?
60
+ transition casting: :combat, unless: :spell_locked?
61
+ end
62
+
63
+ event :cast_spell do
64
+ transition %i[idle combat] => :casting, if: :has_mana?
65
+ transition casting: same, if: :channeling_spell?
66
+ end
67
+
68
+ event :rest do
69
+ transition %i[idle combat] => :resting, unless: :in_danger?
70
+ transition stunned: :resting, if: :recovered?
71
+ end
72
+
73
+ event :stun do
74
+ transition %i[idle combat casting] => :stunned
75
+ end
76
+
77
+ event :recover do
78
+ transition stunned: :idle, if: :stun_expired?
79
+ transition resting: :idle, if: :fully_rested?
80
+ end
81
+
82
+ event :die do
83
+ transition all - :dead => :dead
84
+ end
85
+
86
+ event :resurrect do
87
+ transition dead: :idle, if: :can_resurrect?
88
+ end
89
+
90
+ # Complex callbacks
91
+ before_transition on: :cast_spell do |char, transition|
92
+ char.mana -= calculate_mana_cost(transition)
93
+ end
94
+
95
+ after_transition to: :resting, &:start_regeneration
96
+
97
+ after_transition from: :casting, to: :idle, &:apply_spell_effects
98
+
99
+ around_transition on: :die do |char, _transition, block|
100
+ char.drop_items
101
+ block.call
102
+ char.notify_party
103
+ char.create_tombstone
104
+ end
105
+
106
+ # Guards would go here if using ActiveModel/ActiveRecord
107
+ # For plain Ruby objects, we handle validation in the condition methods
108
+ end
109
+
110
+ # Helper methods for conditions
111
+ def can_fight?
112
+ health.positive? && stamina > 10 && !incapacitated?
113
+ end
114
+
115
+ def has_mana?
116
+ mana >= 10
117
+ end
118
+
119
+ def channeling_spell?
120
+ @channeling_time&.positive?
121
+ end
122
+
123
+ def spell_locked?
124
+ @spell_lock_time && @spell_lock_time > Time.now
125
+ end
126
+
127
+ def in_danger?
128
+ false # Override in combat scenarios
129
+ end
130
+
131
+ def interrupt_rest?
132
+ true
133
+ end
134
+
135
+ def recovered?
136
+ @stun_duration && @stun_duration <= 0
137
+ end
138
+
139
+ def stun_expired?
140
+ recovered?
141
+ end
142
+
143
+ def fully_rested?
144
+ health == 100 && mana == 100
145
+ end
146
+
147
+ def can_resurrect?
148
+ level > 10 || has_resurrection_item?
149
+ end
150
+
151
+ def has_resurrection_item?
152
+ inventory.any? { |item| item.type == :resurrection }
153
+ end
154
+
155
+ def incapacitated?
156
+ false
157
+ end
158
+
159
+ def calculate_mana_cost(_transition)
160
+ 10 # Base cost, can be overridden
161
+ end
162
+
163
+ def start_regeneration
164
+ @regenerating = true
165
+ end
166
+
167
+ def apply_spell_effects
168
+ # Apply any lingering spell effects
169
+ end
170
+
171
+ def drop_items
172
+ @dropped_items = inventory.dup
173
+ inventory.clear
174
+ end
175
+
176
+ def notify_party
177
+ # Notify party members
178
+ end
179
+
180
+ def create_tombstone
181
+ # Create memorial
182
+ end
183
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'character'
4
+
5
+ # Commander class for regiment
6
+ class Commander < Character
7
+ attr_accessor :leadership, :tactics, :inspiring
8
+
9
+ def initialize
10
+ super
11
+ @leadership = 75
12
+ @tactics = 80
13
+ @inspiring = true
14
+ end
15
+
16
+ def alive?
17
+ health.positive?
18
+ end
19
+
20
+ def with_regiment?
21
+ !status?(:absent)
22
+ end
23
+ end
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'character'
4
+
5
+ # Dragon with multiple parallel state machines
6
+ class Dragon < Character
7
+ attr_accessor :rage, :treasure_hoard, :territory, :wingspan, :fire_breath_cooldown, :age, :last_meal_time, :injuries,
8
+ :current_target, :dive_distance, :devastation_score
9
+
10
+ def initialize
11
+ super
12
+ @rage = 0
13
+ @treasure_hoard = 1000
14
+ @territory = 100
15
+ @wingspan = 50
16
+ @fire_breath_cooldown = 0
17
+ end
18
+
19
+ # Mood state machine with states
20
+ state_machine :mood, initial: :sleeping, namespace: 'dragon' do
21
+ state :sleeping do
22
+ def snoring?
23
+ true
24
+ end
25
+
26
+ def alert_level
27
+ :minimal
28
+ end
29
+ end
30
+
31
+ # Sub-states would be separate state machines in state_machines gem
32
+ state :light_sleep
33
+ state :deep_sleep
34
+ state :hibernating
35
+
36
+ state :hunting do
37
+ def alert_level
38
+ :high
39
+ end
40
+
41
+ def speed_bonus
42
+ 1.5
43
+ end
44
+ end
45
+
46
+ state :stalking
47
+ state :pursuing
48
+ state :feeding
49
+
50
+ state :hoarding do
51
+ def greed_multiplier
52
+ 2.0
53
+ end
54
+
55
+ def treasure_sense_range
56
+ 1000
57
+ end
58
+ end
59
+
60
+ state :rampaging do
61
+ def damage_multiplier
62
+ 3.0
63
+ end
64
+
65
+ def fear_aura_range
66
+ 500
67
+ end
68
+ end
69
+
70
+ # Complex event transitions
71
+ event :wake_up do
72
+ transition sleeping: :hunting, if: :hungry?
73
+ transition sleeping: :hoarding, if: :treasure_nearby?
74
+ transition light_sleep: :hunting
75
+ transition deep_sleep: :light_sleep
76
+ transition hibernating: :deep_sleep, if: :winter_ended?
77
+ end
78
+
79
+ event :find_treasure do
80
+ transition hunting: :hoarding
81
+ transition hoarding: same # Keep hoarding more
82
+ transition rampaging: :hoarding, if: -> { rage < 50 }
83
+ end
84
+
85
+ event :enrage do
86
+ transition any - :rampaging => :rampaging, if: -> { rage > 75 }
87
+ end
88
+
89
+ event :exhaust do
90
+ transition rampaging: :sleeping, if: -> { stamina <= 0 }
91
+ transition %i[hunting hoarding] => :sleeping, if: :tired?
92
+ end
93
+
94
+ # Callbacks with conditions
95
+ before_transition any => :rampaging do |dragon|
96
+ dragon.rage = 100
97
+ dragon.fire_breath_cooldown = 0
98
+ end
99
+
100
+ after_transition to: :hoarding, &:count_treasure
101
+
102
+ around_transition from: :rampaging do |dragon, _transition, block|
103
+ villages_before = dragon.nearby_villages.count
104
+ block.call
105
+ villages_after = dragon.nearby_villages.count
106
+ dragon.devastation_score += (villages_before - villages_after)
107
+ end
108
+ end
109
+
110
+ # Flight state machine
111
+ state_machine :flight, initial: :grounded do
112
+ state :grounded do
113
+ def can_dodge?
114
+ false
115
+ end
116
+ end
117
+
118
+ state :hovering do
119
+ def stability
120
+ 0.8
121
+ end
122
+ end
123
+
124
+ state :soaring do
125
+ def speed
126
+ 200
127
+ end
128
+ end
129
+
130
+ state :diving do
131
+ def attack_bonus
132
+ 5.0
133
+ end
134
+ end
135
+
136
+ event :take_off do
137
+ transition grounded: :hovering, if: -> { stamina > 50 && !wing_injured? }
138
+ end
139
+
140
+ event :ascend do
141
+ transition hovering: :soaring, if: :weather_permits?
142
+ transition diving: :soaring
143
+ end
144
+
145
+ event :dive_attack do
146
+ transition %i[hovering soaring] => :diving, if: :target_spotted?
147
+ end
148
+
149
+ event :crash_land do
150
+ transition %i[hovering soaring diving] => :grounded
151
+ end
152
+
153
+ after_transition on: :crash_land, to: :grounded do |dragon|
154
+ dragon.take_damage(20)
155
+ end
156
+
157
+ event :land do
158
+ transition %i[hovering soaring] => :grounded
159
+ transition diving: :grounded, if: :dive_completed?
160
+ end
161
+ end
162
+
163
+ # Age progression state machine
164
+ state_machine :age_category, initial: :wyrmling do
165
+ state :wyrmling
166
+ state :young
167
+ state :adult
168
+ state :ancient
169
+ state :great_wyrm
170
+
171
+ event :age do
172
+ transition wyrmling: :young, if: -> { age >= 50 }
173
+ transition young: :adult, if: -> { age >= 200 }
174
+ transition adult: :ancient, if: -> { age >= 800 }
175
+ transition ancient: :great_wyrm, if: -> { age >= 1200 }
176
+ end
177
+ end
178
+
179
+ # Dragon-specific methods
180
+ def hungry?
181
+ @last_meal_time && (Time.now - @last_meal_time) > 86_400
182
+ end
183
+
184
+ def treasure_nearby?
185
+ sense_treasure_within(100)
186
+ end
187
+
188
+ def winter_ended?
189
+ Time.now.month.between?(3, 10)
190
+ end
191
+
192
+ def tired?
193
+ stamina < 30
194
+ end
195
+
196
+ def count_treasure
197
+ @treasure_hoard = calculate_hoard_value
198
+ end
199
+
200
+ def wing_injured?
201
+ @injuries&.include?(:wing)
202
+ end
203
+
204
+ def weather_permits?
205
+ !stormy? && visibility > 100
206
+ end
207
+
208
+ def target_spotted?
209
+ @current_target != nil
210
+ end
211
+
212
+ def dive_completed?
213
+ @dive_distance && @dive_distance <= 0
214
+ end
215
+
216
+ def take_damage(amount)
217
+ self.health -= amount
218
+ end
219
+
220
+ attr_reader :nearby_villages
221
+
222
+ def sense_treasure_within(_range)
223
+ false # Implement treasure detection
224
+ end
225
+
226
+ def calculate_hoard_value
227
+ treasure_hoard * greed_multiplier
228
+ end
229
+
230
+ def stormy?
231
+ false
232
+ end
233
+
234
+ def visibility
235
+ 1000
236
+ end
237
+ end