diff --git a/lib/magic/actions/activate_ability.rb b/lib/magic/actions/activate_ability.rb index 3371703..0fbb3b8 100644 --- a/lib/magic/actions/activate_ability.rb +++ b/lib/magic/actions/activate_ability.rb @@ -54,6 +54,10 @@ def pay_sacrifice(targets) pay(:sacrifice, targets) end + def pay_discard(targets) + pay(:discard, targets) + end + def perform if targets.any? if ability.single_target? diff --git a/lib/magic/actions/cast.rb b/lib/magic/actions/cast.rb index d80d5ea..7a5cadf 100644 --- a/lib/magic/actions/cast.rb +++ b/lib/magic/actions/cast.rb @@ -5,12 +5,13 @@ class Cast < Action class InvalidTarget < StandardError; end - def_delegators :@card, :enchantment?, :artifact? - attr_reader :card, :targets - def initialize(card:, **args) + def_delegators :@card, :enchantment?, :artifact?, :multi_target? + attr_reader :card, :targets, :value_for_x + def initialize(card:, value_for_x: nil, **args) super(**args) @card = card @targets = [] + @value_for_x = value_for_x end def inspect @@ -44,7 +45,9 @@ def mana_cost .of_type(Abilities::Static::ManaCostAdjustment) .applies_to(card) - mana_cost_adjustment_abilities.each_with_object(card.cost.dup) { |ability, cost| ability.apply(cost) } + cost = mana_cost_adjustment_abilities.each_with_object(card.cost.dup) { |ability, cost| ability.apply(cost) } + cost.x = value_for_x if value_for_x + cost end end @@ -68,11 +71,19 @@ def target_choices choices = choices.arity == 1 ? card.target_choices(player) : card.target_choices end - def can_target?(target) - target_choices.include?(target) + def can_target?(target, index = nil) + if index + target_choices[index].include?(target) + else + target_choices.include?(target) + end end def targeting(*targets) + if card.respond_to?(:multi_target?) && card.multi_target? + return multi_target(*targets) + end + targets.each do |target| raise InvalidTarget, "Invalid target for #{card.name}: #{target}" unless can_target?(target) end @@ -80,6 +91,14 @@ def targeting(*targets) self end + def multi_target(*targets) + targets.each_with_index do |target, index| + raise InvalidTarget, "Invalid target for #{card.name}: #{target}" unless can_target?(target, index) + end + @targets = targets + self + end + def pay_mana(payment) mana_cost.pay(player, payment) self @@ -102,6 +121,7 @@ def resolve! args[:target] = targets.first if resolve_method.parameters.include?([:keyreq, :target]) args[:targets] = targets if resolve_method.parameters.include?([:keyreq, :targets]) args[:kicked] = kicker_cost.paid? if resolve_method.parameters.include?([:key, :kicked]) + args[:value_for_x] = mana_cost.x if resolve_method.parameters.include?([:keyreq, :value_for_x]) resolve_method.call(**args) diff --git a/lib/magic/cards/primal_might.rb b/lib/magic/cards/primal_might.rb new file mode 100644 index 0000000..11263b9 --- /dev/null +++ b/lib/magic/cards/primal_might.rb @@ -0,0 +1,23 @@ +module Magic + module Cards + PrimalMight = Sorcery("Primal Might") do + cost green: 1, x: 1 + + def multi_target? = true + + def target_choices + [ + battlefield.creatures.controlled_by(controller), + battlefield.creatures.not_controlled_by(controller), + ] + end + + def resolve!(targets:, value_for_x:) + first_creature, second_creature = targets + + first_creature.modify_power_toughness!(power: value_for_x, toughness: value_for_x) + first_creature.fight(second_creature) + end + end + end +end diff --git a/lib/magic/choice/effect.rb b/lib/magic/choice/effect.rb index cf7251c..b0b2734 100644 --- a/lib/magic/choice/effect.rb +++ b/lib/magic/choice/effect.rb @@ -5,7 +5,6 @@ class Effect def initialize(player) @player = player end - end end end diff --git a/lib/magic/costs/discard.rb b/lib/magic/costs/discard.rb index 2d1bb13..1d91176 100644 --- a/lib/magic/costs/discard.rb +++ b/lib/magic/costs/discard.rb @@ -1,20 +1,27 @@ module Magic module Costs class Discard - def initialize(player) + attr_reader :player, :predicate + def initialize(player, predicate = nil) @player = player + @predicate = predicate || -> (c) { true } end def can_pay? player.hand.count > 0 end - def pay(player, discarded_card) - player.hand.discard(discarded_card) + def pay(player, chosen_card) + raise "Invalid target chosen for discard" unless valid_targets.include?(chosen_card) + player.hand.discard(chosen_card) end def finalize!(_player) end + + def valid_targets + player.hand.select(&predicate) + end end end end diff --git a/lib/magic/costs/mana.rb b/lib/magic/costs/mana.rb index f76b81e..b85eb50 100644 --- a/lib/magic/costs/mana.rb +++ b/lib/magic/costs/mana.rb @@ -16,6 +16,7 @@ def initialize(cost) @payments = Hash.new(0) @payments[:generic] = Hash.new(0) + @payments[:x] = Hash.new(0) end def mana_value @@ -56,6 +57,7 @@ def can_pay?(player) def pay(player, payment) raise CannotPay unless can_pay?(player) + pay_x(payment[:x]) if payment[:x] pay_generic(payment[:generic]) if payment[:generic] pay_colors(payment.slice(*Magic::Mana::COLORS)) end @@ -105,12 +107,26 @@ def generic cost[:generic] end + def x=(value) + cost[:x] = value + balance[:x] = value + end + + def x + cost[:x] + end + def paid? balance.values.all?(&:zero?) end private + def pay_x(payment) + balance[:x] -= payment.values.sum + @payments[:x].merge!(payment) { |key, old_value, new_value| old_value + new_value } + end + def pay_generic(payment) balance[:generic] -= payment.values.sum @payments[:generic].merge!(payment) { |key, old_value, new_value| old_value + new_value } diff --git a/lib/magic/effects/return_to_owners_hand.rb b/lib/magic/effects/return_to_owners_hand.rb index 2c0a722..32f1dd9 100644 --- a/lib/magic/effects/return_to_owners_hand.rb +++ b/lib/magic/effects/return_to_owners_hand.rb @@ -7,7 +7,7 @@ def requires_choices? end def resolve(target) - target.return_to_hand(target.owner) + target.return_to_hand end end end diff --git a/lib/magic/game.rb b/lib/magic/game.rb index 522d16b..a7a853d 100644 --- a/lib/magic/game.rb +++ b/lib/magic/game.rb @@ -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 @@ -98,9 +98,5 @@ def move_dead_creatures_to_graveyard def skip_choice! choices.shift end - - def skip_choice! - choices.shift - end end end diff --git a/lib/magic/permanent.rb b/lib/magic/permanent.rb index 209ae7e..3f53c74 100644 --- a/lib/magic/permanent.rb +++ b/lib/magic/permanent.rb @@ -222,8 +222,8 @@ def exile! move_zone!(to: game.exile) end - def return_to_hand(player) - move_zone!(to: player.hand) + def return_to_hand + move_zone!(to: owner.hand) end def can_activate_ability?(ability) diff --git a/lib/magic/player.rb b/lib/magic/player.rb index 900f165..e1e366c 100644 --- a/lib/magic/player.rb +++ b/lib/magic/player.rb @@ -105,6 +105,10 @@ def create_token(token:, **args) create_tokens(token: token, amount: 1, **args).first end + def skip_choice(choice) + game.skip_choice! + end + def lost? @lost end diff --git a/lib/magic/types.rb b/lib/magic/types.rb index f2ac085..fc1e1e8 100644 --- a/lib/magic/types.rb +++ b/lib/magic/types.rb @@ -54,6 +54,10 @@ def sorcery? def permanent? land? || creature? || planeswalker? || artifact? || enchantment? end + + def legendary? + type?(T::Legendary) + end end T = Types diff --git a/spec/cards/primal_might_spec.rb b/spec/cards/primal_might_spec.rb new file mode 100644 index 0000000..c7eb763 --- /dev/null +++ b/spec/cards/primal_might_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +RSpec.describe Magic::Cards::PrimalMight do + include_context "two player game" + + let!(:wood_elves) { ResolvePermanent("Wood Elves", owner: p1) } + let!(:alpine_watchdog) { ResolvePermanent("Alpine Watchdog", owner: p2) } + + let(:primal_might) { Card("Primal Might") } + + it "wood elves get +3/+3, fight alpine watchdog" do + p1.add_mana(green: 4) + p1.cast(card: primal_might, value_for_x: 3) do + _1.targeting(wood_elves, alpine_watchdog) + _1.pay_mana(green: 1, x: { green: 3 }) + end + game.tick! + + expect(wood_elves.power).to eq(4) + expect(alpine_watchdog.damage).to eq(4) + end +end