diff --git a/CLAUDE.md b/CLAUDE.md index 93abf1f..74e383a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,6 +46,7 @@ This is a Magic: The Gathering simulation engine written in Ruby without UI. The - Base card types: `Creature`, `Instant`, `Sorcery`, `Enchantment`, `Aura`, `Saga`, `Artifact`, `Equipment` - Simple cards need only cost and creature stats (Example: `StorySeeker`) - Complex cards extend the base class with triggered abilities, activated abilities, and event handlers (Example: `AcademyElite`) + - **DSL block vs class reopening**: The DSL block (`Enchantment("Name") do ... end`) runs in the `Magic::Cards` lexical scope, so any `class Foo` defined inside it lands in `Magic::Cards::Foo` — not nested inside the card class. To avoid name collisions between cards, keep the DSL block to type and cost only, then define trigger classes, choice classes, and `event_handlers` in a class reopening (`class CardName < Enchantment; ...; end`). Constants defined there are properly scoped to the card. Example: `SanctumOfCalmWaters`, `SanctumOfFruitfulHarvest`. ### Events & Abilities diff --git a/lib/magic/cards/sanctum_of_calm_waters.rb b/lib/magic/cards/sanctum_of_calm_waters.rb index 2ce66a0..d63ba42 100644 --- a/lib/magic/cards/sanctum_of_calm_waters.rb +++ b/lib/magic/cards/sanctum_of_calm_waters.rb @@ -3,7 +3,9 @@ module Cards SanctumOfCalmWaters = Enchantment("Sanctum of Calm Waters") do type T::Super::Legendary, T::Enchantment, "Shrine" cost generic: 3, blue: 1 + end + class SanctumOfCalmWaters < Enchantment class FirstMainPhaseTrigger < TriggeredAbility def should_perform? event.active_player == controller @@ -14,16 +16,7 @@ def call end end - def event_handlers - { - Events::FirstMainPhase => FirstMainPhaseTrigger - } - end - end - - class SanctumOfCalmWaters < Enchantment class Choice < Magic::Choice - def resolve! shrines = controller.permanents.by_type("Shrine").count actor.trigger_effect(:draw_cards, number_to_draw: shrines) @@ -31,6 +24,12 @@ def resolve! actor.add_choice(:discard) end end + + def event_handlers + { + Events::FirstMainPhase => FirstMainPhaseTrigger + } + end end end end diff --git a/lib/magic/cards/sanctum_of_fruitful_harvest.rb b/lib/magic/cards/sanctum_of_fruitful_harvest.rb new file mode 100644 index 0000000..bd43413 --- /dev/null +++ b/lib/magic/cards/sanctum_of_fruitful_harvest.rb @@ -0,0 +1,33 @@ +module Magic + module Cards + SanctumOfFruitfulHarvest = Enchantment("Sanctum of Fruitful Harvest") do + type T::Super::Legendary, T::Enchantment, "Shrine" + cost generic: 2, green: 1 + end + + class SanctumOfFruitfulHarvest < Enchantment + class FirstMainPhaseTrigger < TriggeredAbility + def should_perform? + event.active_player == controller + end + + def call + game.choices.add(SanctumOfFruitfulHarvest::ColorChoice.new(actor: actor)) + end + end + + class ColorChoice < Magic::Choice::Color + def resolve!(color:) + shrines = controller.permanents.by_type("Shrine").count + controller.add_mana(color => shrines) + end + end + + def event_handlers + { + Events::FirstMainPhase => FirstMainPhaseTrigger + } + end + end + end +end diff --git a/spec/cards/sanctum_of_fruitful_harvest_spec.rb b/spec/cards/sanctum_of_fruitful_harvest_spec.rb new file mode 100644 index 0000000..9b0613e --- /dev/null +++ b/spec/cards/sanctum_of_fruitful_harvest_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Magic::Cards::SanctumOfFruitfulHarvest do + include_context "two player game" + + let!(:sanctum_of_tranquil_light) { ResolvePermanent("Sanctum Of Tranquil Light") } + let!(:sanctum_of_fruitful_harvest) { ResolvePermanent("Sanctum Of Fruitful Harvest") } + + context "at beginning of precombat main phase" do + it "adds mana equal to the number of shrines in the chosen color" do + go_to_main_phase! + + choice = game.choices.first + expect(choice).to be_a(described_class::ColorChoice) + game.resolve_choice!(color: :green) + + expect(p1.mana_pool[:green]).to eq(2) + end + end +end