Skip to content

Commit

Permalink
Add Teferi, Master of Time
Browse files Browse the repository at this point in the history
Fixes #85
  • Loading branch information
radar committed Feb 1, 2024
1 parent 8bf2f90 commit ca52d62
Show file tree
Hide file tree
Showing 28 changed files with 256 additions and 79 deletions.
29 changes: 29 additions & 0 deletions lib/magic/ability.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module Magic
class Ability
attr_reader :source

def initialize(source:)
@source = source
end

def game
source.game
end

def battlefield
game.battlefield
end

def controller
source.controller
end

def trigger_effect(effect, **args)
source.trigger_effect(effect, source: self, **args)
end

def add_choice(choice, **args)
source.add_choice(choice, **args)
end
end
end
21 changes: 7 additions & 14 deletions lib/magic/actions/activate_loyalty_ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class ActivateLoyaltyAbility < Action
attr_reader :ability, :planeswalker, :targets, :x_value

def initialize(ability:, **args)
@planeswalker = ability.planeswalker
@planeswalker = ability.source
@ability = ability
@targets = []
super(**args)
Expand Down Expand Up @@ -44,19 +44,12 @@ def perform
end

def resolve!
if targets.any?
if ability.single_target?
ability.resolve!(target: targets.first)
else
ability.resolve!(targets: targets)
end
else
if x_value
ability.resolve!(value_for_x: x_value)
else
ability.resolve!
end
end
resolver = ability.method(:resolve!)
args = {}
args[:target] = targets.first if resolver.parameters.include?([:keyreq, :target])
args[:targets] = targets if resolver.parameters.include?([:keyreq, :targets])
args[:value_for_x] = x_value if x_value && resolver.parameters.include?([:keyreq, :value_for_x])
ability.resolve!(**args)
end
end
end
Expand Down
28 changes: 1 addition & 27 deletions lib/magic/activated_ability.rb
Original file line number Diff line number Diff line change
@@ -1,37 +1,11 @@
module Magic
class ActivatedAbility
attr_reader :source

def initialize(source:)
@source = source
end

class ActivatedAbility < Ability
def valid_targets?(*targets)
targets.all? { target_choices.include?(_1) }
end

def costs
@costs || self.class::COSTS.dup
end

def game
source.game
end

def battlefield
game.battlefield
end

def controller
source.controller
end

def trigger_effect(effect, **args)
source.trigger_effect(effect, source: self, **args)
end

def add_choice(choice, **args)
source.add_choice(choice, **args)
end
end
end
4 changes: 4 additions & 0 deletions lib/magic/card_list.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
module Magic
class CardList < SimpleDelegator
def phased_out
select(&:phased_out?)
end

def controlled_by(player)
select { |card| card.controller == player }
end
Expand Down
6 changes: 3 additions & 3 deletions lib/magic/cards/basri_ket.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ class LoyaltyAbility2 < Magic::LoyaltyAbility
def loyalty_change = -2

def resolve!
planeswalker.delayed_response(
source.delayed_response(
turn: game.current_turn,
event_type: Events::PreliminaryAttackersDeclared,
response: -> {
attackers = game.current_turn.attacks.count

attackers.times do
token = planeswalker.controller.create_token(token_class: SoldierToken, enters_tapped: true)
token = source.controller.create_token(token_class: SoldierToken, enters_tapped: true)

game.current_turn.declare_attacker(token)
end
Expand All @@ -65,7 +65,7 @@ class LoyaltyAbility3 < Magic::LoyaltyAbility
def loyalty_change = -6

def resolve!
game.add_emblem(Emblem.new(owner: planeswalker.controller))
game.add_emblem(Emblem.new(owner: source.controller))
end
end

Expand Down
4 changes: 4 additions & 0 deletions lib/magic/cards/planeswalker.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
module Magic
module Cards
class Planeswalker < Card
def self.loyalty(loyalty)
const_set(:BASE_LOYALTY, loyalty)
end

def loyalty
self.class::BASE_LOYALTY
end
Expand Down
2 changes: 2 additions & 0 deletions lib/magic/cards/shared/events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def trigger_effect(effect, source: self, **args)
game.add_effect(Effects::LoseLife.new(source: source, **args))
when :modify_power_toughness
game.add_effect(Effects::ApplyPowerToughnessModification.new(source: source, **args))
when :phase_out
game.add_effect(Effects::PhaseOut.new(source: source, **args))
when :return_to_owners_hand
game.add_effect(Effects::ReturnToOwnersHand.new(source: source, **args))
when :tap
Expand Down
47 changes: 47 additions & 0 deletions lib/magic/cards/teferi_master_of_time.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module Magic
module Cards
class TeferiMasterOfTime < Planeswalker
NAME = "Teferi, Master of Time"
TYPE_LINE = "Legendary Planeswalker -- Teferi"
cost generic: 2, blue: 2
loyalty 3

class LoyaltyAbility1 < LoyaltyAbility
def loyalty_change
1
end

def resolve!
trigger_effect(:draw_cards)
add_choice(:discard)
end
end

class LoyaltyAbility2 < LoyaltyAbility
def loyalty_change
-3
end

def target_choices
battlefield.creatures.not_controlled_by(controller)
end

def resolve!(target:)
trigger_effect(:phase_out, target: target)
end
end

class LoyaltyAbility3 < LoyaltyAbility
def loyalty_change
-10
end

def resolve!
2.times { game.take_additional_turn }
end
end

def loyalty_abilities = [LoyaltyAbility1, LoyaltyAbility2, LoyaltyAbility3]
end
end
end
6 changes: 3 additions & 3 deletions lib/magic/cards/ugin_the_spirit_dragon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def target_choices = battlefield.cards + game.players
def single_target? = true

def resolve!(target:)
target.take_damage(source: planeswalker, damage: 3)
target.take_damage(source: source, damage: 3)
end
end

Expand All @@ -32,11 +32,11 @@ class LoyaltyAbility3 < Magic::LoyaltyAbility
def loyalty_change = -7

def resolve!
controller = planeswalker.controller
controller = source.controller
controller.gain_life(7)
7.times { controller.draw! }

game.choices.add(UginTheSpiritDragon::Choice.new(source: planeswalker))
game.choices.add(UginTheSpiritDragon::Choice.new(source: source))
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/magic/choice/discard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def initialize(player:)
@cards = player.hand
end

def choose(card)
def resolve!(card:)
card.move_to_graveyard!
end
end
Expand Down
10 changes: 10 additions & 0 deletions lib/magic/effects/phase_out.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Magic
module Effects
class PhaseOut < TargetedEffect

def resolve!
target.phase_out!
end
end
end
end
28 changes: 21 additions & 7 deletions lib/magic/game.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Magic
class Game
extend Forwardable

attr_reader :logger, :battlefield, :exile, :stack, :players, :emblems, :current_turn
attr_reader :logger, :battlefield, :exile, :turns, :stack, :players, :emblems, :current_turn

def_delegators :@stack, :choices, :add_choice, :skip_choice!, :resolve_choice!, :effects, :add_effect

Expand Down Expand Up @@ -33,7 +33,7 @@ def initialize(
@player_count = 0
@players = players
@emblems = []
@turn_number = 0
@turns = []
end

def add_players(*players)
Expand All @@ -51,7 +51,7 @@ def add_emblem(emblem)
end

def start!
@current_turn = Turn.new(number: 1, game: self, active_player: players.first)
@current_turn = add_turn(number: 1, active_player: players.first)
players.each do |player|
7.times { player.draw! }
end
Expand All @@ -61,12 +61,26 @@ def notify!(*events)
current_turn.notify!(*events)
end

def take_additional_turn(player: current_turn.active_player)
add_turn(number: @turns.size + 1, active_player: player)
end

def add_turn(number: @turns.size + 1, active_player: player)
turn = Turn.new(number: number, game: self, active_player: active_player)
@turns << turn
turn
end

def next_turn
@turn_number += 1
logger.debug "Starting Turn #{@turn_number} - Active Player: #{@players.first}"
@current_turn = Turn.new(number: @turn_number, game: self, active_player: @players.first)
next_turn = turns.find { |turn| turn.number > current_turn.number }
if next_turn
@current_turn = next_turn
return next_turn
end

next_active_player
@current_turn
logger.debug "Starting Turn #{@turn_number} - Active Player: #{@players.first}"
@current_turn = add_turn(number: current_turn.number + 1, active_player: @players.first)
end

def next_active_player
Expand Down
1 change: 1 addition & 0 deletions lib/magic/game/turn.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Turn
end

after_transition to: :untap do |turn|
turn.battlefield.phased_out.permanents.controlled_by(turn.active_player).each(&:phase_in!)
turn.battlefield.permanents.controlled_by(turn.active_player).each(&:untap_during_untap_step)
end

Expand Down
8 changes: 1 addition & 7 deletions lib/magic/loyalty_ability.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
module Magic
class LoyaltyAbility
attr_reader :planeswalker, :game

def initialize(planeswalker:)
@planeswalker = planeswalker
@game = planeswalker.game
end
class LoyaltyAbility < Ability
end
end
13 changes: 13 additions & 0 deletions lib/magic/permanent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def initialize(game:, owner:, card:, token: false, cast: true, kicked: false, ti
@damage = 0
@protections = Protections.new(card.protections.dup)
@exiled_cards = Magic::CardList.new([])
@phased_out = false
@timestamp = timestamp
end

Expand Down Expand Up @@ -319,6 +320,18 @@ def add_choice(choice, **args)
card.add_choice(choice, **args)
end

def phased_out?
@phased_out
end

def phase_out!
@phased_out = true
end

def phase_in!
@phased_out = false
end

private

def remove_until_eot_keyword_grants!
Expand Down
2 changes: 1 addition & 1 deletion lib/magic/permanents/planeswalker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def take_damage(source:, damage:)
end

def loyalty_abilities
card.loyalty_abilities.map { |ability| ability.new(planeswalker: self) }
card.loyalty_abilities.map { |ability| ability.new(source: self) }
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/magic/zones/battlefield.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Zones
class Battlefield < Zone
extend Forwardable

def_delegators :permanents, :creatures, :planeswalkers, :not_controlled_by, :controlled_by
def_delegators :permanents, :creatures, :planeswalkers, :not_controlled_by, :controlled_by, :phased_out

def initialize(**args)
super(**args)
Expand Down
2 changes: 1 addition & 1 deletion spec/cards/academy_elite_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@

choice = game.choices.last
expect(choice).to be_a(Magic::Choice::Discard)
choice.choose(p1.hand.cards.first)
game.resolve_choice!(card: p1.hand.cards.first)
expect(p1.graveyard.cards.count).to eq(1)
end
end
Expand Down
2 changes: 0 additions & 2 deletions spec/cards/capture_sphere_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
# Enchanted creature doesn't untap during its controller's untap step.

it "taps enchanted creature, equips aura" do
game.next_turn

expect(game.current_turn.active_player).to eq(p1)
p1.add_mana(white: 4)
action = cast_action(player: p1, card: subject)
Expand Down
Loading

0 comments on commit ca52d62

Please sign in to comment.