DeckLock, een statische-websitegenerator die overzichten van KeyForge-decks maakt, wordt uitgebreid met ondersteuning voor Magic: the Gathering. Meer informatie over DeckLock en het ontwerp ervan vind je in het vorige artikel. Magic: the Gathering verschilt sterk van KeyForge. Er is een grote verzameling kaarten beschikbaar en spelers kunnen zelf een set samenstellen waarmee ze willen spelen. Er bestaan verschillende formaten en het formaat bepaalt welke kaarten en hoeveel exemplaren ervan je mag spelen. In de meeste formaten moeten decks minstens 60 kaarten bevatten, met een onbeperkt aantal basislanden en maximaal 4 exemplaren van andere kaarten. Daardoor kan elke speler een volledig zelfgekozen kaartenverzameling meebrengen en moeten we een systeem bedenken dat dit ondersteunt. Anders dan bij KeyForge, waar elk deck uniek is en de kaartenlijst online te vinden is, voegen we hier een systeem toe om een decklijst met een korte beschrijving in te voeren en die om te zetten in pagina’s met de kaarten.
Mijn DeckLock-versie met een overzicht van mijn KeyForge- en Magic: the Gathering-decks (op papier en online) vind je hier.
Een Reader ontwerpen
Hier kunnen we een structuur gebruiken die vergelijkbaar is met de artikels van Pelican. We voegen een decklijst aan een daarvoor bestemde map toe, Pelican pikt het bestand op, de Reader-klasse verwerkt het, haalt online de informatie en afbeelding van de kaarten in het deck op en voegt alles in een geschikt sjabloon in.
Een gangbaar bestandsformaat om decks op te slaan is het Magic Workstation-formaat, dat ook in MTGTop8 wordt gebruikt (ik geef toe: meestal netdeck ik). Het is zowel machine- als mensleesbaar. Een aantal commentaarregels bewaart de naam van het deck, wie het maakte, … Andere regels bevatten hoe vaak een kaart in het deck zit, uit welke set de kaart komt en de naam ervan. De velden worden door spaties gescheiden. Kaarten uit het sideboard worden op dezelfde manier opgenomen, maar de regel begint met SB:
// NAME : 9 Land Stompy
// CREATOR : Sebastian Proost
// FORMAT : Casual
9 [USG] Forest
4 [MMQ] Land Grant
4 [ALL] Elvish Spirit Guide
3 [MMQ] Vine Dryad
...
SB: 2 [TMP] Root Maze
SB: 4 [ULG] Hidden Gibbons
SB: 3 [ONS] Naturalize
SB: 2 [MMQ] Rushwood Legate
SB: 3 [UDS] Compost
Aan de hand van de kaartnaam kan alle overige informatie van ScryFall worden gehaald, een website met afbeeldingen van alle kaarten en een API waarmee nuttige details kunnen worden opgehaald. De set, hier tussen vierkante haken, is optioneel. Als je niets invult, wordt de meest recente versie gekozen.
De Reader-klasse
Hieronder vind je de code voor de Reader. Dit moet een klasse zijn die voortbouwt op BaseReader van Pelican. Ze heeft
een eigenschap enabled nodig die op True staat en een eigenschap file_extensions: een lijst met extensies die
deze Reader moet verwerken, in dit geval uitsluitend .mwDeck-bestanden. Merk op dat de volledige code een aantal hulpfuncties bevat
die nodig zijn om dit te laten werken. Bekijk de volledige code in de DeckLock-repository op GitHub.
Bij het maken van een MTGReader worden de gecachete gegevens geladen (als die bestaan) en wordt indien nodig een pad gemaakt om kaartafbeeldingen op te slaan.
De functie read van MTGReader is verplicht. Hier worden de metadata ingesteld. Pelican vereist een categorie en datum,
maar omdat we die niet gebruiken, vullen we om het even welke waarde in. Ook het vereiste sjabloon wordt hier opgegeven. Dat
is belangrijk om ervoor te zorgen dat de gegevens met het juiste sjabloon worden weergegeven. Het volgende codeblok leest het .mwDeck-
bestand, haalt kaartdetails en de afbeelding op bij Scryfall als die niet in de gecachete gegevens staan, en maakt een
dictionary met alle gegevens die de deckpagina nodig heeft. Ook de paginatitel, slug (URL-vriendelijke naam), URL en
het pad naar het uitvoerbestand worden hier uit de decknaam opgebouwd.
De deckgegevens worden samen met lege inhoud teruggegeven. Pelican zorgt ervoor dat voor elk .mwDeck-bestand in de artikelmap een Reader wordt gemaakt, waarna het bestand wordt verwerkt en met een sjabloon gecombineerd.
Ten slotte moet je een functie maken die de Reader toevoegt (hier add_reader, maar je mag een andere naam kiezen)
en een functie register (die naam is wel verplicht). Die laatste koppelt de functie add_reader aan de verzameling
Readers van Pelican.
class MTGReader(BaseReader):
enabled = True
file_extensions = ['mwDeck']
def __init__(self, settings):
super(MTGReader, self).__init__(settings)
self.cached_data = {}
if os.path.exists(self.mtg_data_path):
with open(self.mtg_data_path, 'r') as fin:
self.cached_data = json.load(fin)
Path(self.mtg_assets_cards_path(full=True)).mkdir(parents=True, exist_ok=True)
@property
def mtg_data_path(self):
return os.path.join(
self.settings.get("PATH"), self.settings.get("MTG_PATH"), "mtg.cached_cards.json"
)
def write_cache(self):
with open(self.mtg_data_path, "w") as fout:
json.dump(self.cached_data, fout, sort_keys=True, indent=4, separators=(",", ": "))
def mtg_assets_cards_path(self, full=False):
if full:
return os.path.join(
self.settings.get("PATH"), self.settings.get("MTG_ASSETS_PATH"), 'cards'
)
else:
return os.path.join(
self.settings.get("MTG_ASSETS_PATH"), 'cards'
)
def add_card_data(self, card_set, card_name):
if card_set not in self.cached_data.keys():
self.cached_data[card_set] = {}
if card_name not in self.cached_data[card_set]:
card_data = get_card_data(card_set, card_name)
self.cached_data[card_set][card_name] = card_data
else:
card_data = self.cached_data[card_set][card_name]
try:
if "card_faces" in card_data.keys():
card_data.update(card_data["card_faces"][0])
img_url = card_data["image_uris"]["border_crop"]
local_path = get_local_card_img_path(self.mtg_assets_cards_path(full=False), img_url)
self.cached_data[card_set][card_name]["image_path"] = local_path
local_path_full = get_local_card_img_path(self.mtg_assets_cards_path(full=True), img_url)
fetch_image(img_url, local_path_full)
except:
print(f"an error occurred fetching {card_name} from set {card_set}")
def read(self, filename):
metadata = {'category': 'MTG_Deck',
'date': '2020-04-13',
'template': 'mtg_deck'
}
deck_data = {
'main': [],
'sideboard': []
}
with open(filename, 'r') as fin:
for line in fin:
if line.startswith('//'):
tag, value = parse_meta(line)
metadata[tag.lower()] = value
elif line.strip() != '':
sideboard, card_set, card_count, card_name = parse_card_line(line)
self.add_card_data(card_set, card_name)
card_data = {
'name': card_name,
'count': card_count,
'data': self.cached_data[card_set][card_name],
'card_type': parse_card_type(self.cached_data[card_set][card_name]['type_line'])
}
if sideboard:
deck_data['sideboard'].append(card_data)
else:
deck_data['main'].append(card_data)
self.write_cache()
metadata['title'] = metadata['name']
metadata['slug'] = slugify(metadata['title'], regex_subs=self.settings.get('SLUG_REGEX_SUBSTITUTIONS', []))
metadata['url'] = f"mtg/{metadata['format']}/{metadata['slug']}/"
metadata['save_as'] = f"{metadata['url']}index.html"
parsed = {}
for key, value in metadata.items():
parsed[key] = self.process_metadata(key, value)
parsed['deck'] = deck_data
return "", parsed
def add_reader(readers):
readers.reader_classes['mwDeck'] = MTGReader
def register():
signals.readers_init.connect(add_reader)
De configuratie bijwerken
Net als voor KeyForge moeten enkele instellingen aan het configuratiebestand worden toegevoegd. Zelfs als er geen decks zijn, is deze instelling nodig. Als je ze op False zet, verdwijnt Magic: the Gathering uit het overzicht op de hoofdpagina. De andere onderdelen zorgen ervoor dat de plugin weet waar gegevens te vinden en op te slaan zijn.
Anders dan in het vorige artikel gebruikt de overzichtspagina voor Magic de ingebouwde functies van Pelican. We moeten er wel voor zorgen dat Pelican dit bestand kent en correct verwerkt. Door het aan TEMPLATE_PAGES toe te voegen, kent Pelican het sjabloon mtg_overview.html en wordt het weergegeven en opgeslagen als mtg.html.
MTG_ENABLED = True
MTG_PATH = "data"
MTG_ASSETS_PATH = "assets/mtg"
TEMPLATE_PAGES = {'mtg_overview.html': 'mtg.html'}
Een opmerking over kaartafbeeldingen
DeckLock downloadt afbeeldingen van Scryfall wanneer je met make html een lokale versie bouwt. Voor privégebruik is dat in
de meeste landen toegestaan. Wanneer je een versie voor publicatie op internet bouwt, is het echter wellicht
geen goed idee om auteursrechtelijk beschermde afbeeldingen op te nemen. Waarschijnlijk is het zelfs illegaal. Daarom bevat het
bestand publishconf.py, dat wordt gebruikt om de onlineversie te bouwen, enkele opties. Die versie kun je bouwen met
het commando make release.
USE_EXTERNAL_LINKS = True
STATIC_EXCLUDES = ['assets/keyforge', 'assets/mtg']
Met USE_EXTERNAL_LINKS worden voor kaartafbeeldingen links naar externe platformen gebruikt in plaats van de gedownloade afbeeldingen. Voor M:tG
wordt Scryfall gebruikt; voor KeyForge de officiële afbeeldingen uit de Master Vault. Door de assetmappen
van beide spellen via STATIC_EXCLUDES uit te sluiten, voorkomen we dat Pelican de gedownloade afbeeldingen kopieert naar de uitvoer die
bestemd is om DeckLock online te hosten.
Je kunt deze instellingen wijzigen, maar uiteraard alleen na zorgvuldige afweging. Auteursrechtelijk beschermd materiaal opnieuw hosten kan gevolgen hebben.
Andere verbeteringen
Naast ondersteuning voor Magic: the Gathering werden nog enkele andere verbeteringen aangebracht. De opvallendste is een echt logo voor DeckLock. Ook onder de motorkap veranderde er heel wat. Gwent werd in deel 3 toegevoegd.
Vond je dit artikel interessant? Trakteer me op een koffie