kumi 0.0.4 ā 0.0.5
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.
- checksums.yaml +4 -4
- data/CLAUDE.md +109 -2
- data/README.md +169 -213
- data/documents/DSL.md +3 -3
- data/documents/SYNTAX.md +17 -26
- data/examples/federal_tax_calculator_2024.rb +36 -38
- data/examples/game_of_life.rb +97 -0
- data/examples/simple_rpg_game.rb +1000 -0
- data/examples/static_analysis_errors.rb +178 -0
- data/examples/wide_schema_compilation_and_evaluation_benchmark.rb +1 -1
- data/lib/kumi/analyzer/analysis_state.rb +37 -0
- data/lib/kumi/analyzer/constant_evaluator.rb +22 -16
- data/lib/kumi/analyzer/passes/definition_validator.rb +4 -3
- data/lib/kumi/analyzer/passes/dependency_resolver.rb +50 -10
- data/lib/kumi/analyzer/passes/input_collector.rb +28 -7
- data/lib/kumi/analyzer/passes/name_indexer.rb +2 -2
- data/lib/kumi/analyzer/passes/pass_base.rb +10 -27
- data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +110 -0
- data/lib/kumi/analyzer/passes/toposorter.rb +3 -3
- data/lib/kumi/analyzer/passes/type_checker.rb +2 -1
- data/lib/kumi/analyzer/passes/type_consistency_checker.rb +2 -1
- data/lib/kumi/analyzer/passes/type_inferencer.rb +2 -4
- data/lib/kumi/analyzer/passes/unsat_detector.rb +233 -14
- data/lib/kumi/analyzer/passes/visitor_pass.rb +2 -1
- data/lib/kumi/analyzer.rb +42 -24
- data/lib/kumi/atom_unsat_solver.rb +45 -0
- data/lib/kumi/cli.rb +449 -0
- data/lib/kumi/constraint_relationship_solver.rb +638 -0
- data/lib/kumi/error_reporter.rb +6 -6
- data/lib/kumi/evaluation_wrapper.rb +20 -4
- data/lib/kumi/explain.rb +8 -8
- data/lib/kumi/function_registry/collection_functions.rb +103 -0
- data/lib/kumi/parser/dsl_cascade_builder.rb +17 -6
- data/lib/kumi/parser/expression_converter.rb +80 -12
- data/lib/kumi/parser/parser.rb +2 -0
- data/lib/kumi/parser/sugar.rb +117 -16
- data/lib/kumi/schema.rb +3 -1
- data/lib/kumi/schema_instance.rb +69 -3
- data/lib/kumi/syntax/declarations.rb +3 -0
- data/lib/kumi/syntax/expressions.rb +4 -0
- data/lib/kumi/syntax/root.rb +1 -0
- data/lib/kumi/syntax/terminal_expressions.rb +3 -0
- data/lib/kumi/types/compatibility.rb +8 -0
- data/lib/kumi/types/validator.rb +1 -1
- data/lib/kumi/version.rb +1 -1
- data/scripts/generate_function_docs.rb +22 -10
- metadata +10 -6
- data/CHANGELOG.md +0 -25
- data/test_impossible_cascade.rb +0 -51
@@ -0,0 +1,1000 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../lib/kumi"
|
5
|
+
|
6
|
+
module DamageCalculation
|
7
|
+
extend Kumi::Schema
|
8
|
+
|
9
|
+
schema do
|
10
|
+
input do
|
11
|
+
# Base combat stats
|
12
|
+
integer :base_attack, domain: 0..500
|
13
|
+
integer :strength, domain: 0..100
|
14
|
+
integer :level, domain: 1..100
|
15
|
+
|
16
|
+
# Equipment bonuses
|
17
|
+
integer :weapon_damage, domain: 0..100
|
18
|
+
integer :weapon_bonus, domain: 0..50
|
19
|
+
string :weapon_type, domain: %w[sword dagger staff bow fists]
|
20
|
+
|
21
|
+
# Combat modifiers
|
22
|
+
boolean :critical_hit
|
23
|
+
string :attack_type, domain: %w[normal magic special]
|
24
|
+
array :abilities, elem: { type: :string }
|
25
|
+
array :status_effects, elem: { type: :string }
|
26
|
+
|
27
|
+
# Status effect turn counters
|
28
|
+
integer :rage_turns, domain: 0..10
|
29
|
+
integer :poison_turns, domain: 0..10
|
30
|
+
integer :blessing_turns, domain: 0..10
|
31
|
+
end
|
32
|
+
|
33
|
+
# Combat traits
|
34
|
+
trait :is_critical, (input.critical_hit == true)
|
35
|
+
trait :is_magic_attack, (input.attack_type == "magic")
|
36
|
+
trait :is_special_attack, (input.attack_type == "special")
|
37
|
+
trait :has_rage, fn(:include?, input.status_effects, "rage")
|
38
|
+
trait :has_blessing, fn(:include?, input.status_effects, "blessing")
|
39
|
+
trait :is_poisoned, fn(:include?, input.status_effects, "poison")
|
40
|
+
trait :has_precision, fn(:include?, input.abilities, "precision")
|
41
|
+
trait :critical_precision, is_critical & has_precision
|
42
|
+
|
43
|
+
# Weapon type traits
|
44
|
+
trait :melee_weapon, fn(:include?, ["sword", "dagger"], input.weapon_type)
|
45
|
+
trait :ranged_weapon, (input.weapon_type == "bow")
|
46
|
+
trait :magic_weapon, (input.weapon_type == "staff")
|
47
|
+
|
48
|
+
# Strength traits
|
49
|
+
trait :high_strength, (input.strength >= 15)
|
50
|
+
|
51
|
+
# Base damage calculation
|
52
|
+
value :strength_bonus do
|
53
|
+
on high_strength, input.strength * 2 # NEW: bare identifier syntax
|
54
|
+
base input.strength
|
55
|
+
end
|
56
|
+
|
57
|
+
value :weapon_total_damage, (input.weapon_damage + input.weapon_bonus)
|
58
|
+
|
59
|
+
value :base_damage_value, (input.base_attack + strength_bonus + weapon_total_damage)
|
60
|
+
|
61
|
+
# Status effect modifiers
|
62
|
+
value :status_attack_multiplier do
|
63
|
+
on has_rage, 1.5 # NEW: bare identifier syntax
|
64
|
+
on is_poisoned, 0.8 # NEW: bare identifier syntax
|
65
|
+
on has_blessing, 1.1 # NEW: bare identifier syntax
|
66
|
+
base 1.0
|
67
|
+
end
|
68
|
+
|
69
|
+
# Critical and special attack modifiers
|
70
|
+
value :attack_type_multiplier do
|
71
|
+
on critical_precision, 3.0 # NEW: bare identifier syntax
|
72
|
+
on is_critical, 2.0 # NEW: bare identifier syntax
|
73
|
+
on is_special_attack, 1.3 # NEW: bare identifier syntax
|
74
|
+
on is_magic_attack, 1.2 # NEW: bare identifier syntax
|
75
|
+
base 1.0
|
76
|
+
end
|
77
|
+
|
78
|
+
# Level scaling
|
79
|
+
value :level_bonus, (input.level / 3)
|
80
|
+
|
81
|
+
# Final damage calculation
|
82
|
+
value :total_damage, fn(:round, (base_damage_value + level_bonus) * status_attack_multiplier * attack_type_multiplier)
|
83
|
+
|
84
|
+
# Damage description for UI
|
85
|
+
value :damage_description do
|
86
|
+
on critical_precision, "š„šÆ PRECISION CRITICAL!" # NEW: bare identifier syntax
|
87
|
+
on is_critical, "š„ CRITICAL HIT!" # NEW: bare identifier syntax
|
88
|
+
on is_special_attack, "⨠Special Attack!" # NEW: bare identifier syntax
|
89
|
+
on is_magic_attack, "š® Magic Attack!" # NEW: bare identifier syntax
|
90
|
+
base "āļø Attack!"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
module DamageReduction
|
96
|
+
extend Kumi::Schema
|
97
|
+
|
98
|
+
schema do
|
99
|
+
input do
|
100
|
+
# Base defensive stats
|
101
|
+
integer :base_defense, domain: 0..300
|
102
|
+
integer :defense_stat, domain: 0..100
|
103
|
+
integer :agility, domain: 0..100
|
104
|
+
integer :level, domain: 1..100
|
105
|
+
|
106
|
+
# Equipment bonuses
|
107
|
+
integer :armor_defense, domain: 0..50
|
108
|
+
integer :armor_bonus, domain: 0..30
|
109
|
+
string :armor_type, domain: %w[leather chainmail plate robe none]
|
110
|
+
|
111
|
+
# Incoming damage info
|
112
|
+
integer :incoming_damage, domain: 0..1000
|
113
|
+
string :damage_type, domain: %w[normal magic special]
|
114
|
+
|
115
|
+
# Status effects
|
116
|
+
array :status_effects, elem: { type: :string }
|
117
|
+
integer :shield_turns, domain: 0..10
|
118
|
+
integer :blessing_turns, domain: 0..10
|
119
|
+
integer :poison_turns, domain: 0..10
|
120
|
+
end
|
121
|
+
|
122
|
+
# Defensive traits
|
123
|
+
trait :has_shield, fn(:include?, input.status_effects, "shield")
|
124
|
+
trait :has_blessing, fn(:include?, input.status_effects, "blessing")
|
125
|
+
trait :is_poisoned, fn(:include?, input.status_effects, "poison")
|
126
|
+
trait :taking_magic_damage, (input.damage_type == "magic")
|
127
|
+
trait :taking_special_damage, (input.damage_type == "special")
|
128
|
+
trait :high_agility, (input.agility >= 15)
|
129
|
+
trait :heavy_armor, fn(:include?, ["chainmail", "plate"], input.armor_type)
|
130
|
+
trait :light_armor, fn(:include?, ["leather", "robe"], input.armor_type)
|
131
|
+
|
132
|
+
# Defense traits
|
133
|
+
trait :high_defense, (input.defense_stat >= 15)
|
134
|
+
|
135
|
+
# Composite armor/damage type traits
|
136
|
+
trait :heavy_vs_magic, heavy_armor & taking_magic_damage
|
137
|
+
trait :light_vs_magic, light_armor & taking_magic_damage
|
138
|
+
|
139
|
+
# Base defense calculation
|
140
|
+
value :defense_bonus do
|
141
|
+
on high_defense, (input.defense_stat * 1.5) # NEW: bare identifier syntax
|
142
|
+
base input.defense_stat
|
143
|
+
end
|
144
|
+
|
145
|
+
value :equipment_defense, (input.armor_defense + input.armor_bonus)
|
146
|
+
|
147
|
+
value :level_defense_bonus, (input.level / 2)
|
148
|
+
|
149
|
+
value :base_defense_value, (input.base_defense + defense_bonus + equipment_defense + level_defense_bonus)
|
150
|
+
|
151
|
+
# Status effect defensive modifiers
|
152
|
+
value :status_defense_multiplier do
|
153
|
+
on has_shield, 1.4 # NEW: bare identifier syntax
|
154
|
+
on has_blessing, 1.2 # NEW: bare identifier syntax
|
155
|
+
on is_poisoned, 0.9 # NEW: bare identifier syntax
|
156
|
+
base 1.0
|
157
|
+
end
|
158
|
+
|
159
|
+
# Armor type resistances
|
160
|
+
value :armor_resistance do
|
161
|
+
on heavy_vs_magic, 0.8 # Heavy armor weak to magic
|
162
|
+
on light_vs_magic, 1.1 # Light armor resists magic
|
163
|
+
on heavy_armor, 1.2 # Heavy armor good vs physical
|
164
|
+
on light_armor, 0.9 # Light armor weak vs physical
|
165
|
+
base 1.0
|
166
|
+
end
|
167
|
+
|
168
|
+
# Calculate total defense
|
169
|
+
value :total_defense, fn(:round, base_defense_value * status_defense_multiplier * armor_resistance)
|
170
|
+
|
171
|
+
# Dodge calculation
|
172
|
+
value :dodge_chance do
|
173
|
+
on high_agility, fn(:min, [(input.agility * 0.05), 0.3])
|
174
|
+
base fn(:min, [(input.agility * 0.02), 0.15])
|
175
|
+
end
|
176
|
+
|
177
|
+
# Final damage after reduction
|
178
|
+
value :damage_after_defense, fn(:max, [(input.incoming_damage - total_defense), 1])
|
179
|
+
|
180
|
+
# Defense description for UI
|
181
|
+
value :defense_description do
|
182
|
+
on has_shield, "š”ļø Shield Active!"
|
183
|
+
on heavy_armor, "āļø Heavy Armor Protection"
|
184
|
+
on light_armor, "š Light Armor Agility"
|
185
|
+
base "š”ļø Defending"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
module Equipment
|
191
|
+
extend Kumi::Schema
|
192
|
+
|
193
|
+
schema do
|
194
|
+
input do
|
195
|
+
string :weapon_name
|
196
|
+
string :weapon_type, domain: %w[sword dagger staff bow fists]
|
197
|
+
integer :weapon_damage, domain: 0..50
|
198
|
+
string :armor_name
|
199
|
+
string :armor_type, domain: %w[leather chainmail plate robe none]
|
200
|
+
integer :armor_defense, domain: 0..30
|
201
|
+
string :accessory_name
|
202
|
+
string :accessory_type, domain: %w[ring amulet boots gloves none]
|
203
|
+
integer :accessory_bonus, domain: 0..20
|
204
|
+
end
|
205
|
+
|
206
|
+
trait :has_weapon, (input.weapon_type != "fists")
|
207
|
+
trait :has_armor, (input.armor_type != "none")
|
208
|
+
trait :has_accessory, (input.accessory_type != "none")
|
209
|
+
trait :melee_weapon, fn(:include?, ["sword", "dagger"], input.weapon_type)
|
210
|
+
trait :ranged_weapon, (input.weapon_type == "bow")
|
211
|
+
trait :magic_weapon, (input.weapon_type == "staff")
|
212
|
+
trait :heavy_armor, fn(:include?, ["chainmail", "plate"], input.armor_type)
|
213
|
+
trait :light_armor, fn(:include?, ["leather", "robe"], input.armor_type)
|
214
|
+
|
215
|
+
value :total_weapon_damage do
|
216
|
+
on ranged_weapon, (input.weapon_damage + 4)
|
217
|
+
on magic_weapon, (input.weapon_damage + 3)
|
218
|
+
on has_weapon, (input.weapon_damage + 2)
|
219
|
+
base 2
|
220
|
+
end
|
221
|
+
|
222
|
+
value :total_armor_defense do
|
223
|
+
on has_armor, (input.armor_defense + 1)
|
224
|
+
base 0
|
225
|
+
end
|
226
|
+
|
227
|
+
value :equipment_agility_modifier do
|
228
|
+
on heavy_armor, -2
|
229
|
+
on light_armor, 1
|
230
|
+
base 0
|
231
|
+
end
|
232
|
+
|
233
|
+
value :equipment_strength_modifier do
|
234
|
+
on melee_weapon, 2
|
235
|
+
base 0
|
236
|
+
end
|
237
|
+
|
238
|
+
value :equipment_description do
|
239
|
+
on has_weapon,has_armor,has_accessory, fn(:concat, [input.weapon_name, ", ", input.armor_name, ", ", input.accessory_name])
|
240
|
+
on has_weapon,has_armor, fn(:concat, [input.weapon_name, ", ", input.armor_name])
|
241
|
+
on has_weapon, fn(:concat, [input.weapon_name, " (Unarmored)"])
|
242
|
+
base "Basic gear"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
module PlayerEntity
|
248
|
+
extend Kumi::Schema
|
249
|
+
|
250
|
+
schema do
|
251
|
+
input do
|
252
|
+
string :name
|
253
|
+
integer :level, domain: 1..100
|
254
|
+
integer :health, domain: 0..1000
|
255
|
+
integer :max_health, domain: 1..1000
|
256
|
+
integer :mana, domain: 0..500
|
257
|
+
integer :max_mana, domain: 0..500
|
258
|
+
integer :strength, domain: 1..100
|
259
|
+
integer :defense, domain: 1..100
|
260
|
+
integer :agility, domain: 1..100
|
261
|
+
integer :experience, domain: 0..Float::INFINITY
|
262
|
+
string :weapon, domain: %w[sword dagger staff bow fists]
|
263
|
+
array :inventory, elem: { type: :string }
|
264
|
+
hash :stats, key: { type: :string }, val: { type: :integer }
|
265
|
+
hash :equipment, key: { type: :string }, val: { type: :any }
|
266
|
+
array :status_effects, elem: { type: :string }
|
267
|
+
integer :poison_turns, domain: 0..10
|
268
|
+
integer :blessing_turns, domain: 0..10
|
269
|
+
integer :rage_turns, domain: 0..5
|
270
|
+
integer :shield_turns, domain: 0..5
|
271
|
+
end
|
272
|
+
|
273
|
+
trait :alive, (input.health > 0)
|
274
|
+
trait :dead, (input.health <= 0)
|
275
|
+
trait :low_health, (input.health <= input.max_health * 0.3) & alive
|
276
|
+
trait :full_health, (input.health == input.max_health)
|
277
|
+
trait :has_mana, (input.mana > 0)
|
278
|
+
trait :low_mana, (input.mana <= input.max_mana * 0.2)
|
279
|
+
trait :strong, (input.strength >= 15)
|
280
|
+
trait :agile, (input.agility >= 15)
|
281
|
+
trait :tanky, (input.defense >= 15)
|
282
|
+
trait :experienced, (input.level >= 5)
|
283
|
+
trait :has_sword, (input.weapon == "sword")
|
284
|
+
trait :has_dagger, (input.weapon == "dagger")
|
285
|
+
trait :has_staff, (input.weapon == "staff")
|
286
|
+
trait :has_bow, (input.weapon == "bow")
|
287
|
+
trait :well_equipped, fn(:include?, input.inventory, "upgrade_token")
|
288
|
+
trait :has_potions, fn(:include?, input.inventory, "potion")
|
289
|
+
trait :has_status_effects, (fn(:size, input.status_effects) > 0)
|
290
|
+
|
291
|
+
value :health_percentage, ((input.health * 100) / input.max_health)
|
292
|
+
value :mana_percentage, ((input.mana * 100) / input.max_mana)
|
293
|
+
|
294
|
+
# Basic combat stats (for UI display)
|
295
|
+
value :base_weapon_bonus do
|
296
|
+
on has_sword, 8
|
297
|
+
on has_dagger, 5
|
298
|
+
on has_staff, 3
|
299
|
+
on has_bow, 6
|
300
|
+
base 2
|
301
|
+
end
|
302
|
+
|
303
|
+
value :equipment_defense_bonus do
|
304
|
+
on well_equipped, 2
|
305
|
+
base 0
|
306
|
+
end
|
307
|
+
|
308
|
+
# Simplified values for UI - actual combat will use the damage schemas
|
309
|
+
value :total_attack, (15 + input.strength + fn(:fetch, input.equipment, "weapon_damage", 12) + base_weapon_bonus)
|
310
|
+
value :defense_rating, (10 + input.defense + fn(:fetch, input.equipment, "armor_defense", 5) + (input.level / 2))
|
311
|
+
value :dodge_chance do
|
312
|
+
on agile, fn(:min, [(input.agility * 0.05), 0.3])
|
313
|
+
base fn(:min, [(input.agility * 0.02), 0.15])
|
314
|
+
end
|
315
|
+
|
316
|
+
value :health_status_description do
|
317
|
+
on dead, "š Dead"
|
318
|
+
on low_health, "ā ļø Critically wounded"
|
319
|
+
on full_health, "š Perfect condition"
|
320
|
+
base "𩹠Injured"
|
321
|
+
end
|
322
|
+
|
323
|
+
value :status_description, health_status_description
|
324
|
+
|
325
|
+
value :can_level_up, (input.experience >= (input.level * 100))
|
326
|
+
value :next_level_exp, (input.level * 100)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
module Enemy
|
331
|
+
extend Kumi::Schema
|
332
|
+
|
333
|
+
schema do
|
334
|
+
input do
|
335
|
+
string :name
|
336
|
+
string :type, domain: %w[goblin orc troll dragon skeleton mage]
|
337
|
+
integer :level, domain: 1..50
|
338
|
+
integer :health, domain: 0..2000
|
339
|
+
integer :max_health, domain: 1..2000
|
340
|
+
integer :attack, domain: 1..200
|
341
|
+
integer :defense, domain: 1..100
|
342
|
+
float :dodge_chance, domain: 0.0..0.5
|
343
|
+
array :abilities, elem: { type: :string }
|
344
|
+
hash :loot_table, key: { type: :string }, val: { type: :integer }
|
345
|
+
end
|
346
|
+
|
347
|
+
trait :alive, (input.health > 0)
|
348
|
+
trait :dead, (input.health <= 0)
|
349
|
+
trait :boss, (input.type == "dragon")
|
350
|
+
trait :weak, (input.health <= input.max_health * 0.25)
|
351
|
+
trait :dangerous, (input.attack >= 50)
|
352
|
+
trait :agile_enemy, (input.dodge_chance >= 0.2)
|
353
|
+
|
354
|
+
value :threat_level do
|
355
|
+
on boss, "š BOSS"
|
356
|
+
on dangerous, "ā” Dangerous"
|
357
|
+
on weak, "𩹠Weakened"
|
358
|
+
base "āļø Normal"
|
359
|
+
end
|
360
|
+
|
361
|
+
value :experience_reward, ((input.level * 25) + (fn(:size, input.abilities) * 10))
|
362
|
+
|
363
|
+
value :gold_reward do
|
364
|
+
on boss, ((input.level * 50) + 200)
|
365
|
+
on dangerous, ((input.level * 30) + 50)
|
366
|
+
base ((input.level * 20) + 10)
|
367
|
+
end
|
368
|
+
|
369
|
+
# Basic combat stats for UI - actual combat will use damage schemas
|
370
|
+
value :attack_damage, (input.attack + (input.level / 2))
|
371
|
+
value :defense_rating, (input.defense + (input.level / 3))
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
module CombatCalculation
|
376
|
+
extend Kumi::Schema
|
377
|
+
|
378
|
+
schema do
|
379
|
+
input do
|
380
|
+
integer :attacker_attack, domain: 0..500
|
381
|
+
integer :defender_defense, domain: 0..300
|
382
|
+
float :defender_dodge, domain: 0.0..1.0
|
383
|
+
boolean :critical_hit
|
384
|
+
string :attack_type, domain: %w[normal magic special]
|
385
|
+
array :attacker_abilities, elem: { type: :string }
|
386
|
+
integer :attacker_level, domain: 1..100
|
387
|
+
end
|
388
|
+
|
389
|
+
trait :hit_connects, (0.5 > input.defender_dodge)
|
390
|
+
trait :is_critical, (input.critical_hit == true)
|
391
|
+
trait :is_magic, (input.attack_type == "magic")
|
392
|
+
trait :is_special, (input.attack_type == "special")
|
393
|
+
trait :has_rage, fn(:include?, input.attacker_abilities, "rage")
|
394
|
+
trait :has_precision, fn(:include?, input.attacker_abilities, "precision")
|
395
|
+
trait :critical_precision, is_critical & has_precision
|
396
|
+
|
397
|
+
value :base_damage, fn(:max, [(input.attacker_attack - input.defender_defense), 1])
|
398
|
+
|
399
|
+
value :damage_multiplier do
|
400
|
+
on critical_precision, 3.0
|
401
|
+
on is_critical, 2.0
|
402
|
+
on has_rage, 1.5
|
403
|
+
on is_special, 1.3
|
404
|
+
base 1.0
|
405
|
+
end
|
406
|
+
|
407
|
+
value :final_damage do
|
408
|
+
on hit_connects, fn(:round, (base_damage * damage_multiplier))
|
409
|
+
base 0
|
410
|
+
end
|
411
|
+
|
412
|
+
trait :critical_hit_connects, hit_connects & is_critical
|
413
|
+
trait :special_hit_connects, hit_connects & is_special
|
414
|
+
|
415
|
+
value :attack_result do
|
416
|
+
on critical_hit_connects, "š„ CRITICAL HIT!"
|
417
|
+
on special_hit_connects, "⨠Special Attack!"
|
418
|
+
on hit_connects, "āļø Hit!"
|
419
|
+
base "šØ Miss!"
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
module GameState
|
425
|
+
extend Kumi::Schema
|
426
|
+
|
427
|
+
schema do
|
428
|
+
input do
|
429
|
+
integer :turn, domain: 1..Float::INFINITY
|
430
|
+
string :phase, domain: %w[exploration combat victory defeat menu]
|
431
|
+
boolean :player_alive
|
432
|
+
boolean :enemy_alive
|
433
|
+
array :combat_log, elem: { type: :string }
|
434
|
+
hash :flags, key: { type: :string }, val: { type: :boolean }
|
435
|
+
integer :enemies_defeated, domain: 0..Float::INFINITY
|
436
|
+
integer :gold, domain: 0..Float::INFINITY
|
437
|
+
end
|
438
|
+
|
439
|
+
trait :in_combat, (input.phase == "combat")
|
440
|
+
trait :exploring, (input.phase == "exploration")
|
441
|
+
trait :game_over, (input.phase == "defeat")
|
442
|
+
trait :victorious, (input.phase == "victory")
|
443
|
+
trait :both_alive, input.player_alive & input.enemy_alive
|
444
|
+
trait :combat_ongoing, in_combat & both_alive
|
445
|
+
|
446
|
+
value :turn_description do
|
447
|
+
on game_over, "š GAME OVER"
|
448
|
+
on victorious, "š VICTORY!"
|
449
|
+
on combat_ongoing, "āļø Combat"
|
450
|
+
on exploring, "šŗļø Exploring..."
|
451
|
+
base "š Main Menu"
|
452
|
+
end
|
453
|
+
|
454
|
+
value :can_flee, in_combat & input.player_alive
|
455
|
+
value :can_attack, combat_ongoing
|
456
|
+
value :can_explore, exploring & input.player_alive
|
457
|
+
|
458
|
+
value :progress_score, ((input.enemies_defeated * 100) + input.gold)
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
class SimpleGame
|
463
|
+
def initialize
|
464
|
+
@player_data = {
|
465
|
+
name: "Hero",
|
466
|
+
level: 1,
|
467
|
+
health: 100,
|
468
|
+
max_health: 100,
|
469
|
+
mana: 50,
|
470
|
+
max_mana: 50,
|
471
|
+
strength: 12,
|
472
|
+
defense: 10,
|
473
|
+
agility: 8,
|
474
|
+
experience: 0,
|
475
|
+
weapon: "sword",
|
476
|
+
inventory: ["potion", "bread"],
|
477
|
+
stats: { "kills" => 0, "damage_dealt" => 0 },
|
478
|
+
equipment: {
|
479
|
+
"weapon_name" => "Iron Sword",
|
480
|
+
"weapon_type" => "sword",
|
481
|
+
"weapon_damage" => 12,
|
482
|
+
"armor_name" => "Leather Vest",
|
483
|
+
"armor_type" => "leather",
|
484
|
+
"armor_defense" => 5,
|
485
|
+
"accessory_name" => "none",
|
486
|
+
"accessory_type" => "none",
|
487
|
+
"accessory_bonus" => 0
|
488
|
+
},
|
489
|
+
status_effects: [],
|
490
|
+
poison_turns: 0,
|
491
|
+
blessing_turns: 0,
|
492
|
+
rage_turns: 0,
|
493
|
+
shield_turns: 0
|
494
|
+
}
|
495
|
+
|
496
|
+
@game_state = {
|
497
|
+
turn: 1,
|
498
|
+
phase: "menu",
|
499
|
+
player_alive: true,
|
500
|
+
enemy_alive: false,
|
501
|
+
combat_log: [],
|
502
|
+
flags: { "first_combat" => true },
|
503
|
+
enemies_defeated: 0,
|
504
|
+
gold: 50
|
505
|
+
}
|
506
|
+
|
507
|
+
@current_enemy = nil
|
508
|
+
end
|
509
|
+
|
510
|
+
def start
|
511
|
+
puts "š® Welcome to Kumi RPG!"
|
512
|
+
puts "=" * 40
|
513
|
+
|
514
|
+
loop do
|
515
|
+
display_status
|
516
|
+
handle_phase
|
517
|
+
break if @game_state[:phase] == "defeat"
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
private
|
522
|
+
|
523
|
+
def display_status
|
524
|
+
puts "==" * 20
|
525
|
+
|
526
|
+
player = PlayerEntity.from(@player_data)
|
527
|
+
game = GameState.from(@game_state)
|
528
|
+
|
529
|
+
puts "\n#{game[:turn_description]}"
|
530
|
+
puts "Player: #{@player_data[:name]} (Lvl #{@player_data[:level]}) #{player[:status_description]}"
|
531
|
+
puts "Health: #{@player_data[:health]}/#{@player_data[:max_health]} (#{player[:health_percentage].round(1)}%)"
|
532
|
+
puts "Attack: #{player[:total_attack]} | Defense: #{player[:defense_rating].round(1)}"
|
533
|
+
puts "Equipment: #{get_equipment_display}"
|
534
|
+
puts "Gold: #{@game_state[:gold]} | Score: #{game[:progress_score]}"
|
535
|
+
|
536
|
+
if @current_enemy
|
537
|
+
enemy = Enemy.from(@current_enemy)
|
538
|
+
puts "\nCurrent Enemy:"
|
539
|
+
puts "Enemy: #{@current_enemy[:name]} (Lvl #{@current_enemy[:level]}) #{enemy[:threat_level]}"
|
540
|
+
puts "Enemy Health: #{@current_enemy[:health]}/#{@current_enemy[:max_health]}"
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
def handle_phase
|
545
|
+
case @game_state[:phase]
|
546
|
+
when "menu"
|
547
|
+
handle_menu
|
548
|
+
when "exploration"
|
549
|
+
handle_exploration
|
550
|
+
when "combat"
|
551
|
+
handle_combat
|
552
|
+
when "victory"
|
553
|
+
handle_victory
|
554
|
+
when "defeat"
|
555
|
+
puts "š Game Over! Final Score: #{GameState.from(@game_state)[:progress_score]}"
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
def handle_menu
|
560
|
+
puts "\nChoose action:"
|
561
|
+
puts "1. Start exploring"
|
562
|
+
puts "2. View character"
|
563
|
+
puts "3. Manage equipment"
|
564
|
+
puts "4. Quit"
|
565
|
+
|
566
|
+
action = simulate_user_input(["1", "2", "3", "4"])
|
567
|
+
|
568
|
+
case action
|
569
|
+
when "1"
|
570
|
+
@game_state[:phase] = "exploration"
|
571
|
+
when "2"
|
572
|
+
show_character_details
|
573
|
+
when "3"
|
574
|
+
manage_equipment
|
575
|
+
when "4"
|
576
|
+
@game_state[:phase] = "defeat"
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
def handle_exploration
|
581
|
+
puts "\nExploring the dungeon..."
|
582
|
+
puts "You venture deeper into the dark corridors..."
|
583
|
+
|
584
|
+
if rand < 0.7
|
585
|
+
encounter_enemy
|
586
|
+
# Don't return to menu - stay in combat!
|
587
|
+
else
|
588
|
+
find_treasure
|
589
|
+
puts "\n[ACTION COMPLETE - Returning to menu...]"
|
590
|
+
sleep(1)
|
591
|
+
@game_state[:phase] = "menu"
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
def encounter_enemy
|
596
|
+
enemy_types = [
|
597
|
+
{ name: "Goblin Scout", type: "goblin", level: rand(1..3), health: 30, max_health: 30, attack: 8, defense: 3, dodge_chance: 0.1, abilities: [], loot_table: { "gold" => 15 } },
|
598
|
+
{ name: "Orc Warrior", type: "orc", level: rand(2..5), health: 60, max_health: 60, attack: 15, defense: 8, dodge_chance: 0.05, abilities: ["rage"], loot_table: { "gold" => 30 } },
|
599
|
+
{ name: "Skeleton Mage", type: "skeleton", level: rand(3..6), health: 40, max_health: 40, attack: 20, defense: 5, dodge_chance: 0.15, abilities: ["magic_missile"], loot_table: { "gold" => 25 } }
|
600
|
+
]
|
601
|
+
|
602
|
+
@current_enemy = enemy_types.sample
|
603
|
+
@current_enemy[:health] = @current_enemy[:max_health]
|
604
|
+
@game_state[:phase] = "combat"
|
605
|
+
@game_state[:enemy_alive] = true
|
606
|
+
|
607
|
+
puts "š You encounter a #{@current_enemy[:name]}!"
|
608
|
+
puts "\n[Press Enter to enter combat]"
|
609
|
+
simulate_user_input([""])
|
610
|
+
end
|
611
|
+
|
612
|
+
def find_treasure
|
613
|
+
if rand < 0.3 && !@player_data[:inventory].include?("upgrade_token")
|
614
|
+
# Found equipment upgrade
|
615
|
+
@player_data[:inventory] << "upgrade_token"
|
616
|
+
puts "⨠You found an upgrade token! Visit equipment menu to enhance your gear."
|
617
|
+
else
|
618
|
+
# Found gold
|
619
|
+
treasure = rand(10..30)
|
620
|
+
@game_state[:gold] += treasure
|
621
|
+
puts "š° You found #{treasure} gold!"
|
622
|
+
end
|
623
|
+
|
624
|
+
puts "\n[TREASURE FOUND - Press Enter to continue exploring or return to menu]"
|
625
|
+
simulate_user_input([""])
|
626
|
+
end
|
627
|
+
|
628
|
+
def handle_combat
|
629
|
+
game = GameState.from(@game_state)
|
630
|
+
|
631
|
+
return unless game[:combat_ongoing]
|
632
|
+
|
633
|
+
puts "\nCombat options:"
|
634
|
+
puts "1. Attack"
|
635
|
+
puts "2. Flee"
|
636
|
+
puts "3. Use Potion (if available)" if @player_data[:inventory].include?("potion")
|
637
|
+
|
638
|
+
options = ["1", "2"]
|
639
|
+
options << "3" if @player_data[:inventory].include?("potion")
|
640
|
+
|
641
|
+
action = simulate_user_input(options)
|
642
|
+
|
643
|
+
case action
|
644
|
+
when "1"
|
645
|
+
player_attack
|
646
|
+
when "2"
|
647
|
+
attempt_flee
|
648
|
+
when "3"
|
649
|
+
use_potion if @player_data[:inventory].include?("potion")
|
650
|
+
end
|
651
|
+
|
652
|
+
enemy_attack if @game_state[:enemy_alive] && @game_state[:player_alive]
|
653
|
+
check_combat_end
|
654
|
+
@game_state[:turn] += 1
|
655
|
+
end
|
656
|
+
|
657
|
+
def player_attack
|
658
|
+
player = PlayerEntity.from(@player_data)
|
659
|
+
|
660
|
+
# Calculate player's attack damage using DamageCalculation schema
|
661
|
+
damage_calc_data = {
|
662
|
+
base_attack: 10,
|
663
|
+
strength: @player_data[:strength],
|
664
|
+
level: @player_data[:level],
|
665
|
+
weapon_damage: @player_data[:equipment]["weapon_damage"],
|
666
|
+
weapon_bonus: player[:base_weapon_bonus],
|
667
|
+
weapon_type: @player_data[:weapon],
|
668
|
+
critical_hit: rand < 0.1,
|
669
|
+
attack_type: "normal",
|
670
|
+
abilities: [],
|
671
|
+
status_effects: @player_data[:status_effects],
|
672
|
+
rage_turns: @player_data[:rage_turns],
|
673
|
+
poison_turns: @player_data[:poison_turns],
|
674
|
+
blessing_turns: @player_data[:blessing_turns]
|
675
|
+
}
|
676
|
+
|
677
|
+
damage_calc = DamageCalculation.from(damage_calc_data)
|
678
|
+
|
679
|
+
# Calculate enemy's damage reduction using DamageReduction schema
|
680
|
+
damage_reduction_data = {
|
681
|
+
base_defense: 5,
|
682
|
+
defense_stat: @current_enemy[:defense],
|
683
|
+
agility: 8, # Default enemy agility
|
684
|
+
level: @current_enemy[:level],
|
685
|
+
armor_defense: 0,
|
686
|
+
armor_bonus: 0,
|
687
|
+
armor_type: "none",
|
688
|
+
incoming_damage: damage_calc[:total_damage],
|
689
|
+
damage_type: "normal",
|
690
|
+
status_effects: [],
|
691
|
+
shield_turns: 0,
|
692
|
+
blessing_turns: 0,
|
693
|
+
poison_turns: 0
|
694
|
+
}
|
695
|
+
|
696
|
+
damage_reduction = DamageReduction.from(damage_reduction_data)
|
697
|
+
|
698
|
+
# Apply dodge chance
|
699
|
+
if rand > @current_enemy[:dodge_chance]
|
700
|
+
final_damage = damage_reduction[:damage_after_defense]
|
701
|
+
@current_enemy[:health] = [@current_enemy[:health] - final_damage, 0].max
|
702
|
+
@player_data[:stats]["damage_dealt"] += final_damage
|
703
|
+
|
704
|
+
puts "#{damage_calc[:damage_description]} You deal #{final_damage} damage! #{damage_reduction[:defense_description]}"
|
705
|
+
else
|
706
|
+
puts "šØ Miss! The enemy dodged your attack!"
|
707
|
+
end
|
708
|
+
|
709
|
+
@game_state[:enemy_alive] = @current_enemy[:health] > 0
|
710
|
+
end
|
711
|
+
|
712
|
+
def enemy_attack
|
713
|
+
player = PlayerEntity.from(@player_data)
|
714
|
+
|
715
|
+
# Calculate enemy's attack damage using DamageCalculation schema
|
716
|
+
damage_calc_data = {
|
717
|
+
base_attack: @current_enemy[:attack],
|
718
|
+
strength: 12, # Default enemy strength
|
719
|
+
level: @current_enemy[:level],
|
720
|
+
weapon_damage: 0,
|
721
|
+
weapon_bonus: 5,
|
722
|
+
weapon_type: "fists",
|
723
|
+
critical_hit: rand < 0.05,
|
724
|
+
attack_type: @current_enemy[:abilities].include?("magic_missile") ? "magic" : "normal",
|
725
|
+
abilities: @current_enemy[:abilities],
|
726
|
+
status_effects: [],
|
727
|
+
rage_turns: 0,
|
728
|
+
poison_turns: 0,
|
729
|
+
blessing_turns: 0
|
730
|
+
}
|
731
|
+
|
732
|
+
damage_calc = DamageCalculation.from(damage_calc_data)
|
733
|
+
|
734
|
+
# Calculate player's damage reduction using DamageReduction schema
|
735
|
+
damage_reduction_data = {
|
736
|
+
base_defense: 5,
|
737
|
+
defense_stat: @player_data[:defense],
|
738
|
+
agility: @player_data[:agility],
|
739
|
+
level: @player_data[:level],
|
740
|
+
armor_defense: @player_data[:equipment]["armor_defense"],
|
741
|
+
armor_bonus: player[:equipment_defense_bonus],
|
742
|
+
armor_type: @player_data[:equipment]["armor_type"],
|
743
|
+
incoming_damage: damage_calc[:total_damage],
|
744
|
+
damage_type: damage_calc_data[:attack_type],
|
745
|
+
status_effects: @player_data[:status_effects],
|
746
|
+
shield_turns: @player_data[:shield_turns],
|
747
|
+
blessing_turns: @player_data[:blessing_turns],
|
748
|
+
poison_turns: @player_data[:poison_turns]
|
749
|
+
}
|
750
|
+
|
751
|
+
damage_reduction = DamageReduction.from(damage_reduction_data)
|
752
|
+
|
753
|
+
# Apply dodge chance
|
754
|
+
if rand > damage_reduction[:dodge_chance]
|
755
|
+
final_damage = damage_reduction[:damage_after_defense]
|
756
|
+
@player_data[:health] = [@player_data[:health] - final_damage, 0].max
|
757
|
+
|
758
|
+
puts "#{@current_enemy[:name]} attacks! #{damage_calc[:damage_description]} You take #{final_damage} damage! #{damage_reduction[:defense_description]}"
|
759
|
+
else
|
760
|
+
puts "#{@current_enemy[:name]} attacks! šØ You dodged the attack!"
|
761
|
+
end
|
762
|
+
|
763
|
+
@game_state[:player_alive] = @player_data[:health] > 0
|
764
|
+
end
|
765
|
+
|
766
|
+
def attempt_flee
|
767
|
+
if rand < 0.7
|
768
|
+
puts "šØ You successfully flee from combat!"
|
769
|
+
puts "\n[FLED FROM COMBAT - Press Enter to return to menu]"
|
770
|
+
simulate_user_input([""])
|
771
|
+
@game_state[:phase] = "menu"
|
772
|
+
@current_enemy = nil
|
773
|
+
@game_state[:enemy_alive] = false
|
774
|
+
else
|
775
|
+
puts "ā Failed to flee! The enemy blocks your escape!"
|
776
|
+
puts "[CONTINUE FIGHTING]"
|
777
|
+
end
|
778
|
+
end
|
779
|
+
|
780
|
+
def use_potion
|
781
|
+
@player_data[:inventory].delete("potion")
|
782
|
+
heal_amount = 50
|
783
|
+
@player_data[:health] = [@player_data[:health] + heal_amount, @player_data[:max_health]].min
|
784
|
+
puts "š§Ŗ You drink a potion and recover #{heal_amount} health!"
|
785
|
+
end
|
786
|
+
|
787
|
+
def check_combat_end
|
788
|
+
if !@game_state[:enemy_alive] && @current_enemy && @current_enemy[:health] == 0
|
789
|
+
# Enemy was defeated (not fled from)
|
790
|
+
enemy = Enemy.from(@current_enemy)
|
791
|
+
exp_gain = enemy[:experience_reward]
|
792
|
+
gold_gain = enemy[:gold_reward]
|
793
|
+
|
794
|
+
@player_data[:experience] += exp_gain
|
795
|
+
@game_state[:gold] += gold_gain
|
796
|
+
@game_state[:enemies_defeated] += 1
|
797
|
+
@player_data[:stats]["kills"] += 1
|
798
|
+
|
799
|
+
puts "š Victory! Gained #{exp_gain} XP and #{gold_gain} gold!"
|
800
|
+
|
801
|
+
check_level_up
|
802
|
+
|
803
|
+
puts "\n[COMBAT VICTORY - Press Enter to continue exploring]"
|
804
|
+
simulate_user_input([""])
|
805
|
+
@game_state[:phase] = "exploration"
|
806
|
+
@current_enemy = nil
|
807
|
+
elsif !@game_state[:player_alive]
|
808
|
+
@game_state[:phase] = "defeat"
|
809
|
+
end
|
810
|
+
end
|
811
|
+
|
812
|
+
def check_level_up
|
813
|
+
player = PlayerEntity.from(@player_data)
|
814
|
+
|
815
|
+
if player[:can_level_up]
|
816
|
+
@player_data[:level] += 1
|
817
|
+
@player_data[:max_health] += 20
|
818
|
+
@player_data[:health] = @player_data[:max_health]
|
819
|
+
@player_data[:strength] += 2
|
820
|
+
@player_data[:defense] += 1
|
821
|
+
@player_data[:agility] += 1
|
822
|
+
@player_data[:experience] = 0
|
823
|
+
|
824
|
+
puts "ā LEVEL UP! You are now level #{@player_data[:level]}!"
|
825
|
+
end
|
826
|
+
end
|
827
|
+
|
828
|
+
def handle_victory
|
829
|
+
puts "š Congratulations! You've mastered the dungeon!"
|
830
|
+
@game_state[:phase] = "defeat"
|
831
|
+
end
|
832
|
+
|
833
|
+
def show_character_details
|
834
|
+
player = PlayerEntity.from(@player_data)
|
835
|
+
puts "\nš Character Details:"
|
836
|
+
puts "Name: #{@player_data[:name]}"
|
837
|
+
puts "Level: #{@player_data[:level]} (#{@player_data[:experience]}/#{player[:next_level_exp]} XP)"
|
838
|
+
puts "Health: #{@player_data[:health]}/#{@player_data[:max_health]}"
|
839
|
+
puts "Mana: #{@player_data[:mana]}/#{@player_data[:max_mana]}"
|
840
|
+
puts "Stats - STR: #{@player_data[:strength]}, DEF: #{@player_data[:defense]}, AGI: #{@player_data[:agility]}"
|
841
|
+
puts "Combat - Attack: #{player[:total_attack]}, Defense: #{player[:defense_rating].round(1)}, Dodge: #{(player[:dodge_chance] * 100).round(1)}%"
|
842
|
+
puts "Weapon: #{@player_data[:weapon]} (+#{player[:base_weapon_bonus]} base damage)"
|
843
|
+
puts "Equipment: #{get_equipment_display}"
|
844
|
+
puts "Inventory: #{@player_data[:inventory].join(', ')}"
|
845
|
+
puts "Kills: #{@player_data[:stats]['kills']}, Damage Dealt: #{@player_data[:stats]['damage_dealt']}"
|
846
|
+
|
847
|
+
puts "\nPress Enter to continue..."
|
848
|
+
simulate_user_input([""])
|
849
|
+
end
|
850
|
+
|
851
|
+
def get_equipment_display
|
852
|
+
equipment = Equipment.from({
|
853
|
+
weapon_name: @player_data[:equipment]["weapon_name"],
|
854
|
+
weapon_type: @player_data[:equipment]["weapon_type"],
|
855
|
+
weapon_damage: @player_data[:equipment]["weapon_damage"],
|
856
|
+
armor_name: @player_data[:equipment]["armor_name"],
|
857
|
+
armor_type: @player_data[:equipment]["armor_type"],
|
858
|
+
armor_defense: @player_data[:equipment]["armor_defense"],
|
859
|
+
accessory_name: @player_data[:equipment]["accessory_name"] || "none",
|
860
|
+
accessory_type: @player_data[:equipment]["accessory_type"] || "none",
|
861
|
+
accessory_bonus: @player_data[:equipment]["accessory_bonus"] || 0
|
862
|
+
})
|
863
|
+
equipment[:equipment_description]
|
864
|
+
end
|
865
|
+
|
866
|
+
def get_equipment_damage(weapon_type)
|
867
|
+
case weapon_type
|
868
|
+
when "sword" then 12
|
869
|
+
when "dagger" then 8
|
870
|
+
when "staff" then 6
|
871
|
+
when "bow" then 10
|
872
|
+
else 3
|
873
|
+
end
|
874
|
+
end
|
875
|
+
|
876
|
+
def get_equipment_defense(armor_type)
|
877
|
+
case armor_type
|
878
|
+
when "leather" then 5
|
879
|
+
when "chainmail" then 10
|
880
|
+
when "plate" then 15
|
881
|
+
when "robe" then 3
|
882
|
+
else 0
|
883
|
+
end
|
884
|
+
end
|
885
|
+
|
886
|
+
def manage_equipment
|
887
|
+
puts "\nš”ļø Equipment Management"
|
888
|
+
equipment = Equipment.from({
|
889
|
+
weapon_name: @player_data[:equipment]["weapon_name"],
|
890
|
+
weapon_type: @player_data[:equipment]["weapon_type"],
|
891
|
+
weapon_damage: @player_data[:equipment]["weapon_damage"],
|
892
|
+
armor_name: @player_data[:equipment]["armor_name"],
|
893
|
+
armor_type: @player_data[:equipment]["armor_type"],
|
894
|
+
armor_defense: @player_data[:equipment]["armor_defense"],
|
895
|
+
accessory_name: @player_data[:equipment]["accessory_name"] || "none",
|
896
|
+
accessory_type: @player_data[:equipment]["accessory_type"] || "none",
|
897
|
+
accessory_bonus: @player_data[:equipment]["accessory_bonus"] || 0
|
898
|
+
})
|
899
|
+
|
900
|
+
puts "Current Equipment: #{equipment[:equipment_description]}"
|
901
|
+
puts "Total Weapon Damage: #{equipment[:total_weapon_damage]}"
|
902
|
+
puts "Total Armor Defense: #{equipment[:total_armor_defense]}"
|
903
|
+
puts "Agility Modifier: #{equipment[:equipment_agility_modifier]}"
|
904
|
+
puts "Strength Modifier: #{equipment[:equipment_strength_modifier]}"
|
905
|
+
|
906
|
+
if @player_data[:inventory].include?("upgrade_token")
|
907
|
+
puts "\nYou have an upgrade token! Choose equipment to upgrade:"
|
908
|
+
puts "1. Upgrade weapon"
|
909
|
+
puts "2. Upgrade armor"
|
910
|
+
puts "3. Go back"
|
911
|
+
|
912
|
+
action = simulate_user_input(["1", "2", "3"])
|
913
|
+
|
914
|
+
case action
|
915
|
+
when "1"
|
916
|
+
upgrade_weapon
|
917
|
+
when "2"
|
918
|
+
upgrade_armor
|
919
|
+
when "3"
|
920
|
+
puts "\n[EQUIPMENT MENU CLOSED - Returning to main menu]"
|
921
|
+
end
|
922
|
+
else
|
923
|
+
puts "\nNo upgrade tokens available. Find them while exploring!"
|
924
|
+
puts "\n[EQUIPMENT MENU - Press Enter to return to main menu]"
|
925
|
+
simulate_user_input([""])
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
def upgrade_weapon
|
930
|
+
@player_data[:inventory].delete("upgrade_token")
|
931
|
+
|
932
|
+
case @player_data[:equipment]["weapon_type"]
|
933
|
+
when "sword"
|
934
|
+
@player_data[:equipment]["weapon_name"] = "Enchanted Sword"
|
935
|
+
@player_data[:equipment]["weapon_damage"] += 5
|
936
|
+
puts "āļø Your Iron Sword has been upgraded to an Enchanted Sword! (+5 damage)"
|
937
|
+
when "dagger"
|
938
|
+
@player_data[:equipment]["weapon_name"] = "Poisoned Dagger"
|
939
|
+
@player_data[:equipment]["weapon_damage"] += 4
|
940
|
+
puts "š”ļø Your Dagger has been upgraded to a Poisoned Dagger! (+4 damage)"
|
941
|
+
when "staff"
|
942
|
+
@player_data[:equipment]["weapon_name"] = "Crystal Staff"
|
943
|
+
@player_data[:equipment]["weapon_damage"] += 6
|
944
|
+
puts "šŖ Your Staff has been upgraded to a Crystal Staff! (+6 damage)"
|
945
|
+
when "bow"
|
946
|
+
@player_data[:equipment]["weapon_name"] = "Elven Bow"
|
947
|
+
@player_data[:equipment]["weapon_damage"] += 4
|
948
|
+
puts "š¹ Your Bow has been upgraded to an Elven Bow! (+4 damage)"
|
949
|
+
end
|
950
|
+
|
951
|
+
puts "\n[WEAPON UPGRADED - Press Enter to continue...]"
|
952
|
+
simulate_user_input([""])
|
953
|
+
end
|
954
|
+
|
955
|
+
def upgrade_armor
|
956
|
+
@player_data[:inventory].delete("upgrade_token")
|
957
|
+
|
958
|
+
case @player_data[:equipment]["armor_type"]
|
959
|
+
when "leather"
|
960
|
+
@player_data[:equipment]["armor_name"] = "Studded Leather"
|
961
|
+
@player_data[:equipment]["armor_type"] = "chainmail"
|
962
|
+
@player_data[:equipment]["armor_defense"] += 3
|
963
|
+
puts "š”ļø Your Leather Vest has been upgraded to Studded Leather! (+3 defense)"
|
964
|
+
when "chainmail"
|
965
|
+
@player_data[:equipment]["armor_name"] = "Plate Mail"
|
966
|
+
@player_data[:equipment]["armor_type"] = "plate"
|
967
|
+
@player_data[:equipment]["armor_defense"] += 5
|
968
|
+
puts "āļø Your Chainmail has been upgraded to Plate Mail! (+5 defense)"
|
969
|
+
when "none"
|
970
|
+
@player_data[:equipment]["armor_name"] = "Leather Vest"
|
971
|
+
@player_data[:equipment]["armor_type"] = "leather"
|
972
|
+
@player_data[:equipment]["armor_defense"] = 5
|
973
|
+
puts "š”ļø You now have a Leather Vest! (+5 defense)"
|
974
|
+
else
|
975
|
+
puts "Your armor is already at maximum level!"
|
976
|
+
end
|
977
|
+
|
978
|
+
puts "\n[ARMOR UPGRADED - Press Enter to continue...]"
|
979
|
+
simulate_user_input([""])
|
980
|
+
end
|
981
|
+
|
982
|
+
def simulate_user_input(valid_options)
|
983
|
+
puts "\n> (Simulating user input from: #{valid_options.join(', ')})"
|
984
|
+
sleep(0.3)
|
985
|
+
|
986
|
+
# Real user input
|
987
|
+
choice = gets&.chomp&.strip || valid_options.first
|
988
|
+
|
989
|
+
|
990
|
+
# # Show equipment manageme3
|
991
|
+
|
992
|
+
puts "> #{choice}"
|
993
|
+
choice
|
994
|
+
end
|
995
|
+
end
|
996
|
+
|
997
|
+
if __FILE__ == $0
|
998
|
+
game = SimpleGame.new
|
999
|
+
game.start
|
1000
|
+
end
|