mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-12-17 08:00:13 +01:00
feat: Added Partners, Backgrounds, and related variation selections to commander building.
This commit is contained in:
parent
641b305955
commit
d416c9b238
65 changed files with 11835 additions and 691 deletions
324
code/tests/test_partner_selection.py
Normal file
324
code/tests/test_partner_selection.py
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from deck_builder.combined_commander import PartnerMode
|
||||
from deck_builder.partner_selection import apply_partner_inputs
|
||||
from exceptions import CommanderPartnerError
|
||||
|
||||
|
||||
class _StubBuilder:
|
||||
def __init__(self, dataframe: pd.DataFrame) -> None:
|
||||
self._df = dataframe
|
||||
|
||||
def load_commander_data(self) -> pd.DataFrame:
|
||||
return self._df.copy(deep=True)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def builder() -> _StubBuilder:
|
||||
data = [
|
||||
{
|
||||
"name": "Halana, Kessig Ranger",
|
||||
"faceName": "Halana, Kessig Ranger",
|
||||
"colorIdentity": ["G"],
|
||||
"themeTags": ["Aggro"],
|
||||
"text": "Reach\nPartner (You can have two commanders if both have partner.)",
|
||||
"type": "Legendary Creature — Human Archer",
|
||||
},
|
||||
{
|
||||
"name": "Alena, Kessig Trapper",
|
||||
"faceName": "Alena, Kessig Trapper",
|
||||
"colorIdentity": ["R"],
|
||||
"themeTags": ["Aggro"],
|
||||
"text": "First strike\nPartner",
|
||||
"type": "Legendary Creature — Human Scout",
|
||||
},
|
||||
{
|
||||
"name": "Lae'zel, Vlaakith's Champion",
|
||||
"faceName": "Lae'zel, Vlaakith's Champion",
|
||||
"colorIdentity": ["W"],
|
||||
"themeTags": ["Counters"],
|
||||
"text": "If you would put one or more counters on a creature... Choose a Background (You can have a Background as a second commander.)",
|
||||
"type": "Legendary Creature — Gith Warrior",
|
||||
},
|
||||
{
|
||||
"name": "Commander A",
|
||||
"faceName": "Commander A",
|
||||
"colorIdentity": ["W"],
|
||||
"themeTags": ["Value"],
|
||||
"text": "Partner with Commander B (When this creature enters the battlefield, target player may put Commander B into their hand from their library, then shuffle.)",
|
||||
"type": "Legendary Creature — Advisor",
|
||||
},
|
||||
{
|
||||
"name": "Commander B",
|
||||
"faceName": "Commander B",
|
||||
"colorIdentity": ["B"],
|
||||
"themeTags": ["Graveyard"],
|
||||
"text": "Partner with Commander A",
|
||||
"type": "Legendary Creature — Advisor",
|
||||
},
|
||||
{
|
||||
"name": "The Tenth Doctor",
|
||||
"faceName": "The Tenth Doctor",
|
||||
"colorIdentity": ["U", "R"],
|
||||
"themeTags": ["Time", "Doctor"],
|
||||
"text": "Whenever you cast a spell with cascade, put a time counter on target permanent",
|
||||
"type": "Legendary Creature — Time Lord Doctor",
|
||||
},
|
||||
{
|
||||
"name": "Donna Noble",
|
||||
"faceName": "Donna Noble",
|
||||
"colorIdentity": ["W"],
|
||||
"themeTags": ["Support"],
|
||||
"text": "Vigilance\nDoctor's companion (You can have two commanders if the other is a Doctor.)",
|
||||
"type": "Legendary Creature — Human Advisor",
|
||||
},
|
||||
{
|
||||
"name": "Amy Pond",
|
||||
"faceName": "Amy Pond",
|
||||
"colorIdentity": ["R"],
|
||||
"themeTags": ["Aggro", "Doctor's Companion", "Partner With"],
|
||||
"text": (
|
||||
"Partner with Rory Williams\\nWhenever Amy Pond deals combat damage to a player, "
|
||||
"choose a suspended card you own and remove that many time counters from it.\\n"
|
||||
"Doctor's companion (You can have two commanders if the other is the Doctor.)"
|
||||
),
|
||||
"type": "Legendary Creature — Human",
|
||||
},
|
||||
{
|
||||
"name": "Rory Williams",
|
||||
"faceName": "Rory Williams",
|
||||
"colorIdentity": ["W", "U"],
|
||||
"themeTags": ["Human", "Doctor's Companion", "Partner With"],
|
||||
"text": (
|
||||
"Partner with Amy Pond\\nFirst strike, lifelink\\n"
|
||||
"Doctor's companion (You can have two commanders if the other is a Doctor.)"
|
||||
),
|
||||
"type": "Legendary Creature — Human Soldier",
|
||||
},
|
||||
]
|
||||
df = pd.DataFrame(data)
|
||||
return _StubBuilder(df)
|
||||
|
||||
|
||||
def _background_catalog() -> SimpleNamespace:
|
||||
card = SimpleNamespace(
|
||||
name="Scion of Halaster",
|
||||
display_name="Scion of Halaster",
|
||||
color_identity=("B",),
|
||||
themes=("Backgrounds Matter",),
|
||||
theme_tags=("Backgrounds Matter",),
|
||||
oracle_text="Commander creatures you own have menace.",
|
||||
type_line="Legendary Enchantment — Background",
|
||||
is_background=True,
|
||||
)
|
||||
|
||||
class _Catalog:
|
||||
def __init__(self, entry: SimpleNamespace) -> None:
|
||||
self._entry = entry
|
||||
self.entries = (entry,)
|
||||
|
||||
def get(self, name: str) -> SimpleNamespace | None:
|
||||
lowered = name.strip().casefold()
|
||||
if lowered in {
|
||||
self._entry.name.casefold(),
|
||||
self._entry.display_name.casefold(),
|
||||
}:
|
||||
return self._entry
|
||||
return None
|
||||
|
||||
return _Catalog(card)
|
||||
|
||||
|
||||
def test_feature_disabled_returns_none(builder: _StubBuilder) -> None:
|
||||
result = apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="Halana, Kessig Ranger",
|
||||
secondary_name="Alena, Kessig Trapper",
|
||||
feature_enabled=False,
|
||||
background_catalog=_background_catalog(),
|
||||
)
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_conflicting_inputs_raise_error(builder: _StubBuilder) -> None:
|
||||
with pytest.raises(CommanderPartnerError):
|
||||
apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="Halana, Kessig Ranger",
|
||||
secondary_name="Alena, Kessig Trapper",
|
||||
background_name="Scion of Halaster",
|
||||
feature_enabled=True,
|
||||
background_catalog=_background_catalog(),
|
||||
)
|
||||
|
||||
|
||||
def test_background_requires_primary_support(builder: _StubBuilder) -> None:
|
||||
with pytest.raises(CommanderPartnerError):
|
||||
apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="Halana, Kessig Ranger",
|
||||
background_name="Scion of Halaster",
|
||||
feature_enabled=True,
|
||||
background_catalog=_background_catalog(),
|
||||
)
|
||||
|
||||
|
||||
def test_background_success(builder: _StubBuilder) -> None:
|
||||
combined = apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="Lae'zel, Vlaakith's Champion",
|
||||
background_name="Scion of Halaster",
|
||||
feature_enabled=True,
|
||||
background_catalog=_background_catalog(),
|
||||
)
|
||||
assert combined is not None
|
||||
assert combined.partner_mode is PartnerMode.BACKGROUND
|
||||
assert combined.secondary_name == "Scion of Halaster"
|
||||
assert combined.color_identity == ("W", "B")
|
||||
|
||||
|
||||
def test_partner_with_detection(builder: _StubBuilder) -> None:
|
||||
combined = apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="Commander A",
|
||||
secondary_name="Commander B",
|
||||
feature_enabled=True,
|
||||
background_catalog=_background_catalog(),
|
||||
)
|
||||
assert combined is not None
|
||||
assert combined.partner_mode is PartnerMode.PARTNER_WITH
|
||||
assert combined.color_identity == ("W", "B")
|
||||
|
||||
|
||||
def test_partner_detection(builder: _StubBuilder) -> None:
|
||||
combined = apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="Halana, Kessig Ranger",
|
||||
secondary_name="Alena, Kessig Trapper",
|
||||
feature_enabled=True,
|
||||
background_catalog=_background_catalog(),
|
||||
)
|
||||
assert combined is not None
|
||||
assert combined.partner_mode is PartnerMode.PARTNER
|
||||
assert combined.color_identity == ("R", "G")
|
||||
|
||||
|
||||
def test_doctor_companion_pairing(builder: _StubBuilder) -> None:
|
||||
combined = apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="The Tenth Doctor",
|
||||
secondary_name="Donna Noble",
|
||||
feature_enabled=True,
|
||||
background_catalog=_background_catalog(),
|
||||
)
|
||||
assert combined is not None
|
||||
assert combined.partner_mode is PartnerMode.DOCTOR_COMPANION
|
||||
assert combined.secondary_name == "Donna Noble"
|
||||
assert combined.color_identity == ("W", "U", "R")
|
||||
|
||||
|
||||
def test_doctor_requires_companion(builder: _StubBuilder) -> None:
|
||||
with pytest.raises(CommanderPartnerError):
|
||||
apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="The Tenth Doctor",
|
||||
secondary_name="Halana, Kessig Ranger",
|
||||
feature_enabled=True,
|
||||
background_catalog=_background_catalog(),
|
||||
)
|
||||
|
||||
|
||||
def test_companion_requires_doctor(builder: _StubBuilder) -> None:
|
||||
with pytest.raises(CommanderPartnerError):
|
||||
apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="Donna Noble",
|
||||
secondary_name="Commander A",
|
||||
feature_enabled=True,
|
||||
background_catalog=_background_catalog(),
|
||||
)
|
||||
|
||||
|
||||
def test_amy_prefers_partner_with_when_rory_selected(builder: _StubBuilder) -> None:
|
||||
combined = apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="Amy Pond",
|
||||
secondary_name="Rory Williams",
|
||||
feature_enabled=True,
|
||||
background_catalog=_background_catalog(),
|
||||
)
|
||||
|
||||
assert combined is not None
|
||||
assert combined.partner_mode is PartnerMode.PARTNER_WITH
|
||||
|
||||
|
||||
def test_amy_can_pair_with_the_doctor(builder: _StubBuilder) -> None:
|
||||
combined = apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="Amy Pond",
|
||||
secondary_name="The Tenth Doctor",
|
||||
feature_enabled=True,
|
||||
background_catalog=_background_catalog(),
|
||||
)
|
||||
|
||||
assert combined is not None
|
||||
assert combined.partner_mode is PartnerMode.DOCTOR_COMPANION
|
||||
|
||||
|
||||
def test_rory_can_partner_with_amy(builder: _StubBuilder) -> None:
|
||||
combined = apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="Rory Williams",
|
||||
secondary_name="Amy Pond",
|
||||
feature_enabled=True,
|
||||
background_catalog=_background_catalog(),
|
||||
)
|
||||
|
||||
assert combined is not None
|
||||
assert combined.partner_mode is PartnerMode.PARTNER_WITH
|
||||
|
||||
|
||||
def test_logging_emits_partner_mode_selected(caplog: pytest.LogCaptureFixture, builder: _StubBuilder) -> None:
|
||||
with caplog.at_level(logging.INFO):
|
||||
combined = apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="Halana, Kessig Ranger",
|
||||
secondary_name="Alena, Kessig Trapper",
|
||||
feature_enabled=True,
|
||||
background_catalog=_background_catalog(),
|
||||
)
|
||||
|
||||
assert combined is not None
|
||||
records = [record for record in caplog.records if getattr(record, "event", "") == "partner_mode_selected"]
|
||||
assert records, "Expected partner_mode_selected log event"
|
||||
payload = getattr(records[-1], "payload", {})
|
||||
assert payload.get("mode") == PartnerMode.PARTNER.value
|
||||
assert payload.get("commanders", {}).get("primary") == "Halana, Kessig Ranger"
|
||||
assert payload.get("commanders", {}).get("secondary") == "Alena, Kessig Trapper"
|
||||
assert payload.get("colors_before") == ["G"]
|
||||
assert payload.get("colors_after") == ["R", "G"]
|
||||
assert payload.get("color_delta", {}).get("added") == ["R"]
|
||||
|
||||
|
||||
def test_logging_includes_selection_source(caplog: pytest.LogCaptureFixture, builder: _StubBuilder) -> None:
|
||||
with caplog.at_level(logging.INFO):
|
||||
combined = apply_partner_inputs(
|
||||
builder,
|
||||
primary_name="Halana, Kessig Ranger",
|
||||
secondary_name="Alena, Kessig Trapper",
|
||||
feature_enabled=True,
|
||||
background_catalog=_background_catalog(),
|
||||
selection_source="suggestion",
|
||||
)
|
||||
|
||||
assert combined is not None
|
||||
records = [record for record in caplog.records if getattr(record, "event", "") == "partner_mode_selected"]
|
||||
assert records, "Expected partner_mode_selected log event"
|
||||
payload = getattr(records[-1], "payload", {})
|
||||
assert payload.get("selection_source") == "suggestion"
|
||||
Loading…
Add table
Add a link
Reference in a new issue