mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-12 16:58:57 +00:00
* - Fix the name of the swamp tribal town. * - Update deck conversion toolchain to the one that was used for the May 2023 MTGDecks.net archive update.
114 lines
5.4 KiB
Python
Executable File
114 lines
5.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
CARDSFOLDER = "../res/cardsfolder"
|
|
DECKFOLDER = "."
|
|
|
|
import argparse, os, re
|
|
|
|
print("Agetian's MTG Forge Deck AI Compatibility Analyzer v5.0\n")
|
|
|
|
parser = argparse.ArgumentParser(description="Analyze MTG Forge decks for AI compatibility.")
|
|
parser.add_argument("-p", action="store_true", help="print only AI-playable decks")
|
|
parser.add_argument("-u", action="store_true", help="print only AI-unplayable decks")
|
|
parser.add_argument("-d", action="store_true", help="physically delete unplayable decks")
|
|
parser.add_argument("-s", action="store_true", help="ignore sideboard when judging playability of decks")
|
|
|
|
args = parser.parse_args()
|
|
|
|
# simple structural self-test (can this tool work?)
|
|
if not (os.access(os.path.join(CARDSFOLDER,"a","abu_jafar.txt"),os.F_OK) or os.access(os.path.join("decks"),os.F_OK)):
|
|
print("Fatal error:\n This utility requires the 'cardsfolder' folder with unpacked card files at " + CARDSFOLDER + " and the 'decks' folder with .dck files at " + DECKFOLDER + " in order to operate. Exiting.")
|
|
exit(1)
|
|
if args.p and args.u:
|
|
print("Fatal error:\n The -p and -u options are mutually exclusive, please specify one of these options and not both of them at the same time.")
|
|
exit(1)
|
|
|
|
# basic variables
|
|
cardlist = {}
|
|
total_cards = 0
|
|
ai_playable_cards = 0
|
|
total_decks = 0
|
|
playable_decks = 0
|
|
nonplayable_in_deck = 0
|
|
|
|
# main algorithm
|
|
print("Loading cards...")
|
|
for root, dirs, files in os.walk(CARDSFOLDER):
|
|
for name in files:
|
|
if name.find(".txt") != -1:
|
|
total_cards += 1
|
|
fullpath = os.path.join(root, name)
|
|
cardtext = open(fullpath).read()
|
|
cardtext_lower = cardtext.lower()
|
|
cardname_lines = cardtext.replace('\r','').split('\n')
|
|
cardname = ""
|
|
for line in cardname_lines:
|
|
if line.strip().lower().startswith("name:"):
|
|
if line.count(':') == 1:
|
|
cardname = line.split(':')[1].strip().lower()
|
|
break
|
|
if cardname == "":
|
|
cardname_literal = cardtext.replace('\r','').split('\n')[0].split(':')
|
|
cardname = ":".join(cardname_literal[1:]).strip().lower()
|
|
if (cardtext_lower.find("alternatemode:split") != -1) or (cardtext_lower.find("alternatemode: split") != -1):
|
|
# split card, special handling needed
|
|
cardsplittext = cardtext.replace('\r','').split('\n')
|
|
cardnames = []
|
|
for line in cardsplittext:
|
|
if line.lower().find("name:") != -1:
|
|
cardnames.extend([line.split('\n')[0].split(':')[1].lower()])
|
|
cardname = " // ".join(cardnames)
|
|
if (cardtext_lower.find("alternatemode:modal") != -1) or (cardtext_lower.find("alternatemode: modal") != -1):
|
|
# ZNR modal card, special handling needed
|
|
cardsplittext = cardtext.replace('\r','').split('\n')
|
|
cardnames = []
|
|
for line in cardsplittext:
|
|
if line.lower().find("name:") != -1:
|
|
cardnames.extend([line.split('\n')[0].split(':')[1].lower()])
|
|
cardname = cardnames[0].strip()
|
|
if cardtext.lower().find("remaideck") != -1 or cardtext.lower().find("ai:removedeck:all") != -1:
|
|
cardlist[cardname] = 0
|
|
else:
|
|
cardlist[cardname] = 1
|
|
ai_playable_cards += 1
|
|
|
|
perc_playable = (float(ai_playable_cards) / total_cards) * 100
|
|
perc_unplayable = ((float(total_cards) - ai_playable_cards) / total_cards) * 100
|
|
|
|
print("Loaded %d cards, among them %d playable by the AI (%d%%), %d unplayable by the AI (%d%%).\n" % (total_cards, ai_playable_cards, perc_playable, total_cards - ai_playable_cards, perc_unplayable))
|
|
|
|
print("Scanning decks...")
|
|
for root, dirs, files in os.walk(DECKFOLDER):
|
|
for name in files:
|
|
if name.find(".dck") != -1:
|
|
total_decks += 1
|
|
nonplayable_in_deck = 0
|
|
cardnames = []
|
|
fullpath = os.path.join(root, name)
|
|
deckdata = open(fullpath).readlines()
|
|
for line in deckdata:
|
|
if args.s:
|
|
if line.strip().lower() == "[sideboard]":
|
|
break
|
|
regexobj = re.search('^([0-9]+) +([^|]+)', line)
|
|
if regexobj:
|
|
cardname = regexobj.groups()[1].replace('\n','').replace('\r','').strip()
|
|
cardname = cardname.replace('\xC6', 'AE').lower()
|
|
if cardlist[cardname] == 0:
|
|
cardnames.extend([cardname])
|
|
nonplayable_in_deck += 1
|
|
if nonplayable_in_deck == 0:
|
|
if not args.u:
|
|
playable_decks += 1
|
|
print("%s is PLAYABLE by the AI." % name)
|
|
else:
|
|
if not args.p:
|
|
print("%s is UNPLAYABLE by the AI (%d unplayable cards: %s)." % (name, nonplayable_in_deck, str(cardnames)))
|
|
if args.d:
|
|
os.remove(os.path.join(root, name))
|
|
|
|
perc_playable_decks = (float(playable_decks) / total_decks) * 100
|
|
perc_unplayable_decks = ((float(total_decks) - playable_decks) / total_decks) * 100
|
|
|
|
print("\nScanned %d decks, among them %d playable by the AI (%d%%), %d unplayable by the AI (%d%%)." % (total_decks, playable_decks, perc_playable_decks, total_decks - playable_decks, perc_unplayable_decks))
|