Skip to content

Commit

Permalink
Making actions instance methods on Player for cleaner Ruby syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
radar committed Jan 15, 2024
1 parent 359e5fe commit e13d561
Show file tree
Hide file tree
Showing 25 changed files with 214 additions and 110 deletions.
6 changes: 3 additions & 3 deletions lib/magic/action.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module Magic
class Action
attr_reader :player, :game
attr_reader :game, :player

def initialize(player:)
def initialize(game:, player:)
@game = game
@player = player
@game = player.game
end
end
end
17 changes: 12 additions & 5 deletions lib/magic/actions/activate_ability.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
module Magic
module Actions
class ActivateAbility < Action
attr_reader :permanent, :ability, :costs, :requirements, :targets
attr_reader :ability, :costs, :requirements, :targets

def initialize(permanent:, ability:, **args)
@permanent = permanent
@ability = ability.new(source: permanent)
def initialize(ability:, **args)
@ability = ability
@costs = @ability.costs
@requirements = @ability.requirements
@targets = []
super(**args)
end

def inspect
"#<Actions::Actions::ActivateAbility permanent: #{permanent.name}, ability: #{ability.class}>"
"#<Actions::Actions::ActivateAbility source: #{ability.source.name}, ability: #{ability.class}>"
end

def can_be_activated?(player)
Expand Down Expand Up @@ -51,6 +50,10 @@ def pay_multi_tap(targets)
pay(player, :multi_tap, targets)
end

def pay_sacrifice(targets)
pay(player, :sacrifice, targets)
end

def perform
if targets.any?
if ability.single_target?
Expand Down Expand Up @@ -85,6 +88,10 @@ def pay(player, cost_type, payment = nil)
self
end

def has_cost?(cost_type)
costs.any? { |cost| cost.is_a?(cost_type) }
end

def finalize_costs!(player)
costs.each { |cost| cost.finalize!(player) }
end
Expand Down
6 changes: 3 additions & 3 deletions lib/magic/actions/activate_loyalty_ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ module Actions
class ActivateLoyaltyAbility < Action
attr_reader :ability, :planeswalker, :targets, :x_value

def initialize(planeswalker:, ability:, **args)
@planeswalker = planeswalker
@ability = ability.new(planeswalker: planeswalker)
def initialize(ability:, **args)
@planeswalker = ability.planeswalker
@ability = ability
@targets = []
super(**args)
end
Expand Down
8 changes: 7 additions & 1 deletion lib/magic/actions/cast.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module Actions
class Cast < Action
extend Forwardable

class InvalidTarget < StandardError; end

def_delegators :@card, :enchantment?, :artifact?
attr_reader :card, :targets
def initialize(card:, **args)
Expand Down Expand Up @@ -47,6 +49,10 @@ def mana_cost
end
end

def auto_pay
mana_cost.auto_pay(player)
end

def kicker_cost
card.kicker_cost
end
Expand All @@ -69,7 +75,7 @@ def can_target?(target)

def targeting(*targets)
targets.each do |target|
raise "Invalid target for #{card.name}: #{target}" unless can_target?(target)
raise InvalidTarget, "Invalid target for #{card.name}: #{target}" unless can_target?(target)
end
@targets = targets
self
Expand Down
3 changes: 1 addition & 2 deletions lib/magic/actions/play_land.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ def inspect
end

def can_perform?
return false if player.can_play_lands?
true
player.can_play_lands?
end

def perform
Expand Down
4 changes: 2 additions & 2 deletions lib/magic/cards/animal_sanctuary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ def costs
end

def resolve!
controller.add_mana(colorless: 1)
controller.add_mana(generic: 1)
end
end

class ActivatedAbility < Magic::ActivatedAbility
def costs
[
Costs::Mana.new(colorless: 2),
Costs::Mana.new(generic: 2),
Costs::Tap.new(source),
]
end
Expand Down
8 changes: 7 additions & 1 deletion lib/magic/costs/mana.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,19 @@ def can_pay?(player)
end

def pay(player, payment)

raise CannotPay unless can_pay?(player)

pay_generic(payment[:generic]) if payment[:generic]
pay_colors(payment.slice(*Magic::Mana::COLORS))
end

def auto_pay(player)
raise CannotPay unless can_pay?(player)

pay_colors(color_costs)
pay_generic(generic: cost[:generic]) if cost[:generic]
end

def finalize!(player)
raise OutstandingBalance if outstanding_balance?
raise Overpayment if overpaid?
Expand Down
27 changes: 27 additions & 0 deletions lib/magic/effects/single_target.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module Magic
module Effects
class SingleTarget < Effect
class InvalidTarget < StandardError; end;

attr_reader :source, :target, :choices

def initialize(source:, target:, choices: source.target_choices)
@target = target
@source = source
@choices = [*choices]
end

def requires_choices?
true
end

def single_choice?
choices.count == 1
end

def no_choice?
choices.count.zero?
end
end
end
end
4 changes: 0 additions & 4 deletions lib/magic/effects/targeted_effect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ def choices
end
end

def requires_targets?
true
end

def requires_choices?
true
end
Expand Down
2 changes: 1 addition & 1 deletion lib/magic/game.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def initialize(
def add_players(*players)
players.each(&method(:add_player))
end

\
def add_player(player)
@player_count += 1
@players << player
Expand Down
7 changes: 5 additions & 2 deletions lib/magic/permanent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Permanent
include Types

extend Forwardable
attr_reader :game, :owner, :controller, :card,:types, :delayed_responses, :attachments, :protections, :modifiers, :counters, :keywords, :keyword_grants, :activated_abilities, :exiled_cards, :cannot_untap_next_turn
attr_reader :game, :owner, :controller, :card, :types, :delayed_responses, :attachments, :protections, :modifiers, :counters, :keywords, :keyword_grants, :activated_abilities, :exiled_cards, :cannot_untap_next_turn

def_delegators :@card, :name, :cmc, :mana_value, :colors, :colorless?
def_delegators :@game, :logger
Expand Down Expand Up @@ -49,7 +49,6 @@ def initialize(game:, owner:, card:, token: false, cast: true, kicked: false)
@keywords = card.keywords
@keyword_grants = []
@counters = Counters::Collection.new([])
@activated_abilities = card.activated_abilities
@damage = 0
@protections = Protections.new(card.protections.dup)
@exiled_cards = Magic::CardList.new([])
Expand All @@ -67,6 +66,10 @@ def inspect
"#<Magic::Permanent name:#{card.name} controller:#{controller.name}>"
end

def activated_abilities
@activated_abilities ||= card.activated_abilities.map { |ability| ability.new(source: self) }
end

alias_method :to_s, :inspect

def controller?(other_controller)
Expand Down
5 changes: 2 additions & 3 deletions lib/magic/permanents/planeswalker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def change_loyalty!(change)
@loyalty += change
destroy! if loyalty <= 0
end

def take_damage(source:, damage:)
game.notify!(
Events::DamageDealt.new(
Expand All @@ -24,9 +24,8 @@ def take_damage(source:, damage:)
change_loyalty!(- damage)
end


def loyalty_abilities
card.loyalty_abilities
card.loyalty_abilities.map { |ability| ability.new(planeswalker: self) }
end
end
end
Expand Down
43 changes: 42 additions & 1 deletion lib/magic/player.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,47 @@ def inspect
end
alias_method :to_s, :inspect

def prepare_action(action, **args, &block)
action = action.new(player: self, game: game, **args)
yield action if block_given?
action
end

def take_action(action, **args)
if action.is_a?(Class)
action = prepare_action(action, **args)
end

game.take_action(action)
end

def play_land(land:, **args)
action = prepare_action(Magic::Actions::PlayLand, card: land, **args)
game.take_action(action)
end

def activate_ability(ability:, auto_tap: true, **args)
action = prepare_action(Magic::Actions::ActivateAbility, ability: ability, **args)
action.pay_tap if action.has_cost?(Magic::Costs::Tap) && auto_tap
yield action if block_given?
action.finalize_costs!(self)
game.take_action(action)
end

def activate_loyalty_ability(ability:, auto_tap: true, **args)
action = prepare_action(Magic::Actions::ActivateLoyaltyAbility, ability: ability, **args)
yield action if block_given?
game.take_action(action)
end

def cast(card:, **args)
action = prepare_action(Magic::Actions::Cast, card: card, **args)
yield action if block_given?
game.take_action(action)
action
end


def lost?
@lost
end
Expand Down Expand Up @@ -74,7 +115,7 @@ def max_lands_per_turn
end

def can_play_lands?
lands_played >= max_lands_per_turn
lands_played < max_lands_per_turn
end

def can_be_targeted_by?(source)
Expand Down
12 changes: 6 additions & 6 deletions spec/cards/academy_elite_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
context "when it enters the battlefield" do
it "gets 1 +1/+1 counter" do
p1.add_mana(blue: 4)
action = Magic::Actions::Cast.new(player: p1, card: academy_elite)
action.pay_mana(generic: { blue: 3 }, blue: 1)
game.take_action(action)
p1.cast(card: academy_elite) do
_1.pay_mana(generic: { blue: 3 }, blue: 1)
end
game.tick!

permanent = p1.permanents.last
Expand All @@ -39,9 +39,9 @@
context "when it enters the battlefield" do
it "gets 2 +1/+1 counters" do
p1.add_mana(blue: 4)
action = Magic::Actions::Cast.new(player: p1, card: academy_elite)
action.pay_mana(generic: { blue: 3 }, blue: 1)
game.take_action(action)
p1.cast(card: academy_elite) do
_1.pay_mana(generic: { blue: 3 }, blue: 1)
end
game.tick!

permanent = p1.permanents.last
Expand Down
8 changes: 4 additions & 4 deletions spec/cards/altars_light_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
end

it "exiles the sol ring" do
action = cast_action(card: card, player: p1)
action.targeting(sol_ring)
add_to_stack_and_resolve(action)

p1.add_mana(white: 4)
cast_and_resolve(card: card, player: p1) do
_1.targeting(sol_ring)
end
expect(sol_ring.card.zone).to be_exile
end
end
5 changes: 3 additions & 2 deletions spec/cards/angelic_ascension_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
subject { add_to_library("Angelic Ascension", player: p1) }

it "exiles the wood elves and creates a 4/4 white angel creature token with flying" do
action = cast_action(card: subject, player: p1, targeting: wood_elves)
add_to_stack_and_resolve(action)
action = cast_and_resolve(card: subject, player: p1) do |action|
action.targeting(wood_elves)
end

expect(wood_elves.card.zone).to be_exile
expect(wood_elves.zone).to be_nil
Expand Down
11 changes: 5 additions & 6 deletions spec/cards/animal_sanctuary_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@

context "mana ability" do
def activate_ability
action = Magic::Actions::ActivateAbility.new(permanent: subject, ability: subject.activated_abilities.first, player: p1)
game.take_action(action)
p1.activate_ability(ability: subject.activated_abilities.first)
end

it "adds one colorless mana" do
activate_ability
expect(p1.mana_pool[:colorless]).to eq(1)
expect(p1.mana_pool[:generic]).to eq(1)
end
end

Expand All @@ -26,9 +25,9 @@ def activate_ability

def activate_ability
p1.add_mana(green: 2)
action = Magic::Actions::ActivateAbility.new(permanent: subject, ability: subject.activated_abilities.last, player: p1)
action.pay_mana(colorless: { green: 2 })
game.take_action(action)
p1.activate_ability(ability: subject.activated_abilities.last) do
_1.pay_mana(generic: { green: 2 })
end
game.stack.resolve!
end

Expand Down
Loading

0 comments on commit e13d561

Please sign in to comment.