Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 8 additions & 9 deletions lib/magic/cards/sanctum_of_calm_waters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -14,23 +16,20 @@ 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)

actor.add_choice(:discard)
end
end

def event_handlers
{
Events::FirstMainPhase => FirstMainPhaseTrigger
}
end
end
end
end
33 changes: 33 additions & 0 deletions lib/magic/cards/sanctum_of_fruitful_harvest.rb
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions spec/cards/sanctum_of_fruitful_harvest_spec.rb
Original file line number Diff line number Diff line change
@@ -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
Loading