Spaces:
Running
Running
File size: 4,165 Bytes
a33d5b8 6bbf552 a33d5b8 6bbf552 a33d5b8 6bbf552 a33d5b8 6bbf552 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | import base64
import re
from pathlib import Path
from random import Random
from typing import Callable
from budget import Card
from generator import CardModelClient, CardPackClient, generate_card, generate_pack
from primitives import Effect
from primitives import School
PackChooser = Callable[[tuple[Card, ...], tuple[Card, ...]], int]
_CARD_ART = Path(__file__).parent / "assets" / "cards"
_backbone_art_cache: dict[str, str] = {}
# Return the pre-baked art data URI for a backbone card. The six backbone cards are
# identical every run, so their art is generated once and bundled (no live SDXL call,
# no shimmer).
def prebaked_backbone_art(name: str, theme: str) -> str:
world = re.sub(r"[^a-z0-9]+", "", theme.lower())
card = re.sub(r"[^a-z0-9]+", "_", name.lower()).strip("_")
key = f"{world}__{card}"
if key not in _backbone_art_cache:
path = _CARD_ART / f"{key}.jpg"
_backbone_art_cache[key] = (
"data:image/jpeg;base64," + base64.b64encode(path.read_bytes()).decode("ascii") if path.exists() else ""
)
return _backbone_art_cache[key]
BACKBONE_CARDS: tuple[tuple[str, int, Effect], ...] = (
("Attack (weak)", 1, Effect("deal", amount=2)),
("Attack (medium)", 3, Effect("deal", amount=4)),
("Attack (strong)", 5, Effect("deal", amount=6)),
("Block (weak)", 2, Effect("block", amount=3)),
("Block (medium)", 3, Effect("block", amount=4)),
("Draw (small)", 2, Effect("draw", amount=1)),
)
# Build the six standardized backbone cards for a run.
def backbone_deck(school: School, theme: str) -> tuple[Card, ...]:
return tuple(backbone_card(name, cost, effect, school, theme) for name, cost, effect in BACKBONE_CARDS)
# Build one standardized backbone card, with pre-baked art so it never shimmers.
def backbone_card(name: str, cost: int, effect: Effect, school: School, theme: str) -> Card:
return Card(name, cost, school, theme, (effect,), flavor=f"Standard {name.lower()}.", art_uri=prebaked_backbone_art(name, theme))
# Build a full 15-card text deck with nine generated synergy cards.
def draft_deck(client: CardModelClient, school: School, theme: str, costs: tuple[int, ...]) -> tuple[Card, ...]:
if len(costs) != 9:
raise ValueError("draft needs exactly nine synergy costs")
deck = list(backbone_deck(school, theme))
for cost in costs:
deck.append(generate_card(client, school, theme, deck, cost))
return tuple(deck)
# Build a full deck by drafting one card from each generated pack.
def draft_deck_from_packs(
client: CardPackClient,
school: School,
theme: str,
costs: tuple[int, ...],
choose_index: PackChooser,
rng: Random | None = None,
) -> tuple[Card, ...]:
if len(costs) != 9:
raise ValueError("draft needs exactly nine synergy costs")
deck = list(backbone_deck(school, theme))
anchors: list[Card] = []
anchor_indexes = draft_anchor_indexes(len(costs), rng or Random())
for index in draft_order(len(costs), anchor_indexes):
pack = generate_pack(client, school, theme, deck, costs[index], draft_anchors=anchors)
selected = pack[validated_pack_choice(choose_index(tuple(deck), pack), pack)]
deck.append(selected)
if index in anchor_indexes:
anchors.append(selected)
return tuple(deck)
# Return random synergy-card indexes that anchor the rest of the draft.
def draft_anchor_indexes(count: int, rng: Random) -> frozenset[int]:
if count < 2:
raise ValueError("draft needs at least two anchor candidates")
return frozenset(rng.sample(range(count), 2))
# Return anchor picks first, then the remaining picks in cost order.
def draft_order(count: int, anchor_indexes: frozenset[int]) -> tuple[int, ...]:
anchors = tuple(sorted(anchor_indexes))
remaining = tuple(index for index in range(count) if index not in anchor_indexes)
return anchors + remaining
# Return a valid draft pack index or reject it.
def validated_pack_choice(index: int, pack: tuple[Card, ...]) -> int:
if index < 0 or index >= len(pack):
raise ValueError("draft choice is outside the pack")
return index
|