Skip to content

Commit

Permalink
Implement Primal Might
Browse files Browse the repository at this point in the history
Fixes #478
  • Loading branch information
radar committed Jan 18, 2024
1 parent b2204fc commit 1998530
Show file tree
Hide file tree
Showing 12 changed files with 113 additions and 18 deletions.
4 changes: 4 additions & 0 deletions lib/magic/actions/activate_ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
32 changes: 26 additions & 6 deletions lib/magic/actions/cast.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -68,18 +71,34 @@ 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
@targets = 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
Expand All @@ -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)

Expand Down
23 changes: 23 additions & 0 deletions lib/magic/cards/primal_might.rb
Original file line number Diff line number Diff line change
@@ -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
1 change: 0 additions & 1 deletion lib/magic/choice/effect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ class Effect
def initialize(player)
@player = player
end

end
end
end
13 changes: 10 additions & 3 deletions lib/magic/costs/discard.rb
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions lib/magic/costs/mana.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 }
Expand Down
2 changes: 1 addition & 1 deletion lib/magic/effects/return_to_owners_hand.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def requires_choices?
end

def resolve(target)
target.return_to_hand(target.owner)
target.return_to_hand
end
end
end
Expand Down
6 changes: 1 addition & 5 deletions 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 Expand Up @@ -98,9 +98,5 @@ def move_dead_creatures_to_graveyard
def skip_choice!
choices.shift
end

def skip_choice!
choices.shift
end
end
end
4 changes: 2 additions & 2 deletions lib/magic/permanent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions lib/magic/player.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions lib/magic/types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ def sorcery?
def permanent?
land? || creature? || planeswalker? || artifact? || enchantment?
end

def legendary?
type?(T::Legendary)
end
end

T = Types
Expand Down
22 changes: 22 additions & 0 deletions spec/cards/primal_might_spec.rb
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 1998530

Please sign in to comment.