mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 09:48:02 +00:00
Update the MTGDecksNet conversion and AI playability test toolchain to the latest version (#2363)
* - Update the MTGDecksNet conversion and AI playability test toolchain to the latest version (Python 3 compatible). * - Make the input/output folders generic * - Make the input/output folders generic, part 2. * - Add the Scryfall all-prices.txt generator script. * - Minor code cleanup.
This commit is contained in:
407
forge-gui/tools/DeckConversionTools/mtgdecksnet_convert.py
Normal file → Executable file
407
forge-gui/tools/DeckConversionTools/mtgdecksnet_convert.py
Normal file → Executable file
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Modify key directories here
|
||||
@@ -9,7 +9,7 @@ OUT_DECKFOLDER = "./ForgeDecks"
|
||||
|
||||
import argparse, os, re
|
||||
|
||||
print("Agetian's MtgDecks.net DEC to MTG Forge Deck Converter v3.5\n")
|
||||
print("Agetian's MtgDecks.net DEC to MTG Forge Deck Converter v4.0\n")
|
||||
|
||||
parser = argparse.ArgumentParser(description="Convert MtgDecks.net DEC to Forge DCK.")
|
||||
|
||||
@@ -40,34 +40,51 @@ nonplayable_in_deck = 0
|
||||
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_literal = cardtext.replace('\r','').split('\n')[0].split(':')
|
||||
cardname = ":".join(cardname_literal[1:]).strip()
|
||||
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]])
|
||||
cardname = " // ".join(cardnames)
|
||||
if cardtext.lower().find("removedeck:all") != -1:
|
||||
cardlist[cardname] = 0
|
||||
else:
|
||||
cardlist[cardname] = 1
|
||||
ai_playable_cards += 1
|
||||
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()
|
||||
break
|
||||
if cardname == "":
|
||||
cardname_literal = cardtext.replace('\r','').split('\n')[0].split(':')
|
||||
cardname = ":".join(cardname_literal[1:]).strip()
|
||||
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]])
|
||||
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]])
|
||||
cardname = cardnames[0].strip()
|
||||
if cardtext.lower().find("remaideck") != -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))
|
||||
|
||||
re_Metadata = '^//(.*) a (.*) deck by (.*)$'
|
||||
re_Metadata = '^//(.*) a (.*) deck by (.*) \(dec\) Version$'
|
||||
re_Metadata2 = '^//(.*) a ([A-Za-z]+) MTG deck played by (.*) in (.*) - MTGDECKS.NET.*$'
|
||||
re_Metadata3 = '^//(.*) a (.*) deck by (.*)$'
|
||||
re_DeckID = '^([0-9]+)\.dec$'
|
||||
re_Maindeck = '^([0-9]+) (.*)$'
|
||||
re_Sideboard = '^SB:[ \t]+([0-9]+) (.*)$'
|
||||
@@ -79,173 +96,193 @@ badChars = ['/', '\\', '*']
|
||||
print("Converting decks...")
|
||||
for root, dirs, files in os.walk(DECKFOLDER):
|
||||
for name in files:
|
||||
if name.find(".dec") != -1:
|
||||
print("Converting deck: " + name + "...")
|
||||
deck_id = -1
|
||||
s_DeckID = re.search(re_DeckID, name)
|
||||
if s_DeckID:
|
||||
deck_id = s_DeckID.groups()[0]
|
||||
fullpath = os.path.join(root, name)
|
||||
deckdata = open(fullpath).readlines()
|
||||
name = ""
|
||||
creator = ""
|
||||
format = ""
|
||||
maindeck = []
|
||||
maindeck_cards = 0
|
||||
sideboard = []
|
||||
supported = True
|
||||
deckHasUnsupportedCards = False
|
||||
if name.find(".dec") != -1:
|
||||
print("Converting deck: " + name + "...")
|
||||
deck_id = -1
|
||||
s_DeckID = re.search(re_DeckID, name)
|
||||
if s_DeckID:
|
||||
deck_id = s_DeckID.groups()[0]
|
||||
fullpath = os.path.join(root, name)
|
||||
deckdata = open(fullpath).readlines()
|
||||
name = ""
|
||||
creator = ""
|
||||
format = ""
|
||||
maindeck = []
|
||||
maindeck_cards = 0
|
||||
sideboard = []
|
||||
supported = True
|
||||
deckHasUnsupportedCards = False
|
||||
|
||||
for line in deckdata:
|
||||
#line = line.replace("\xE1", "a")
|
||||
#line = line.replace("\xFB", "u")
|
||||
#line = line.replace("\xE9", "e")
|
||||
line = line.replace("\xC3\x86", "AE")
|
||||
line = line.replace("\xC3\xA9", "e")
|
||||
line = line.replace("\xC3\xBB", "u")
|
||||
line = line.replace("\xC3\xA1", "a")
|
||||
line = line.replace("\xC3\xAD", "i")
|
||||
#line = line.replace("Unravel the Aether", "Unravel the AEther")
|
||||
#line = line.replace("Aether", "AEther")
|
||||
line = line.replace("Chandra, Roaring Flame", "Chandra, Fire of Kaladesh")
|
||||
line = line.replace("Nissa, Sage Animist", "Nissa, Vastwood Seer")
|
||||
line = line.replace("Neck Breaker", "Breakneck Rider")
|
||||
line = line.replace("\xC3\xB6", "o")
|
||||
line = line.replace("\x97", "-")
|
||||
line = line.replace("\x91", "'")
|
||||
line = line.replace("\xFB", "u")
|
||||
line = line.replace("\xFC", "u")
|
||||
line = line.replace("\xC4", "A")
|
||||
if line[0] != "/" and line.find(" // ") == -1:
|
||||
line = line.replace("/"," // ")
|
||||
timepos = line.find("<!")
|
||||
if timepos > -1:
|
||||
line = line[0:timepos]
|
||||
isCardSupported = True
|
||||
mode = 0
|
||||
s_Metadata = re.search(re_Metadata, line)
|
||||
if not s_Metadata:
|
||||
s_Metadata = re.search(re_Metadata2, line)
|
||||
mode = 1
|
||||
if s_Metadata:
|
||||
name = s_Metadata.groups()[0].strip()
|
||||
format = s_Metadata.groups()[1].strip()
|
||||
creator = s_Metadata.groups()[2].strip()
|
||||
event = ""
|
||||
if mode == 1:
|
||||
event = s_Metadata.groups()[3].strip()
|
||||
for badChar in badChars:
|
||||
event = event.replace(badChar, "-")
|
||||
if args.t and args.t != format:
|
||||
print("Skipping an off-format deck " + name + " (format = " + format + ")")
|
||||
supported = False
|
||||
continue
|
||||
s_Maindeck = re.search(re_Maindeck, line)
|
||||
if s_Maindeck:
|
||||
cardAmount = s_Maindeck.groups()[0].strip()
|
||||
cardName = s_Maindeck.groups()[1].strip()
|
||||
if not cardlist.has_key(cardName) and not cardlist.has_key(cardName.replace("Aether", "AEther")) and not cardlist.has_key(cardName.replace("AEther", "Aether")):
|
||||
print("Unsupported card (MAIN): " + cardName)
|
||||
if args.f:
|
||||
supported = False
|
||||
else:
|
||||
isCardSupported = False
|
||||
deckHasUnsupportedCards = True
|
||||
if not cardName in unsupportedList:
|
||||
unsupportedList.extend([cardName])
|
||||
if cardlist.has_key(cardName):
|
||||
mdline = cardAmount + " " + cardName
|
||||
elif cardlist.has_key(cardName.replace("Aether", "AEther")):
|
||||
mdline = cardAmount + " " + cardName.replace("Aether", "AEther")
|
||||
elif cardlist.has_key(cardName.replace("AEther", "Aether")):
|
||||
mdline = cardAmount + " " + cardName.replace("AEther", "Aether")
|
||||
else:
|
||||
mdline = cardAmount + " " + cardName # for the purposes of unsupported cards
|
||||
if isCardSupported:
|
||||
maindeck.extend([mdline])
|
||||
else:
|
||||
maindeck.extend(["#"+mdline])
|
||||
maindeck_cards += int(cardAmount)
|
||||
continue
|
||||
s_Sideboard = re.search(re_Sideboard, line)
|
||||
if s_Sideboard:
|
||||
cardAmount = s_Sideboard.groups()[0].strip()
|
||||
cardName = s_Sideboard.groups()[1].strip()
|
||||
if not cardlist.has_key(cardName) and not cardlist.has_key(cardName.replace("Aether", "AEther")) and not cardlist.has_key(cardName.replace("AEther", "Aether")):
|
||||
print("Unsupported card (SIDE): " + cardName)
|
||||
if args.f:
|
||||
supported = False
|
||||
else:
|
||||
isCardSupported = False
|
||||
deckHasUnsupportedCards = True
|
||||
if not cardName in unsupportedList:
|
||||
unsupportedList.extend([cardName])
|
||||
if cardlist.has_key(cardName):
|
||||
sdline = cardAmount + " " + cardName
|
||||
elif cardlist.has_key(cardName.replace("Aether", "AEther")):
|
||||
sdline = cardAmount + " " + cardName.replace("Aether", "AEther")
|
||||
elif cardlist.has_key(cardName.replace("AEther", "Aether")):
|
||||
sdline = cardAmount + " " + cardName.replace("AEther", "Aether")
|
||||
else:
|
||||
sdline = cardAmount + " " + cardName # for the purposes of unsupported cards
|
||||
if isCardSupported:
|
||||
sideboard.extend([sdline])
|
||||
else:
|
||||
sideboard.extend(["#"+sdline])
|
||||
continue
|
||||
for line in deckdata:
|
||||
#line = line.replace("\xE1", "a")
|
||||
#line = line.replace("\xFB", "u")
|
||||
#line = line.replace("\xE9", "e")
|
||||
line = line.replace("\xC3\x86", "AE")
|
||||
line = line.replace("\xC3\xA9", "e")
|
||||
line = line.replace("\xC3\xBB", "u")
|
||||
line = line.replace("\xC3\xA1", "a")
|
||||
line = line.replace("\xC3\xAD", "i")
|
||||
#line = line.replace("Unravel the Aether", "Unravel the AEther")
|
||||
#line = line.replace("Aether", "AEther")
|
||||
line = line.replace("Chandra, Roaring Flame", "Chandra, Fire of Kaladesh")
|
||||
line = line.replace("Lurrus of the Dream Den", "Lurrus of the Dream-Den")
|
||||
line = line.replace("Nissa, Sage Animist", "Nissa, Vastwood Seer")
|
||||
line = line.replace("Neck Breaker", "Breakneck Rider")
|
||||
line = line.replace("Avacyn, the Purifier", "Archangel Avacyn")
|
||||
line = line.replace("Dandân", "Dandan")
|
||||
line = line.replace("Séance", "Seance")
|
||||
line = line.replace("Jötun Grunt", "Jotun Grunt")
|
||||
line = line.replace("Ifh-Bíff Efreet", "Ifh-Biff Efreet")
|
||||
line = line.replace("Juzám Djinn", "Juzam Djinn")
|
||||
line = line.replace("\xC3\xB6", "o")
|
||||
line = line.replace("\x97", "-")
|
||||
line = line.replace("\x91", "'")
|
||||
line = line.replace("\xFB", "u")
|
||||
line = line.replace("\xFC", "u")
|
||||
line = line.replace("\xC4", "A")
|
||||
if line[0] != "/" and line.find(" // ") == -1:
|
||||
line = line.replace("/"," // ")
|
||||
timepos = line.find("<!")
|
||||
if timepos > -1:
|
||||
line = line[0:timepos]
|
||||
isCardSupported = True
|
||||
mode = 0
|
||||
s_Metadata = re.search(re_Metadata, line)
|
||||
if not s_Metadata:
|
||||
s_Metadata = re.search(re_Metadata2, line)
|
||||
mode = 1
|
||||
if not s_Metadata:
|
||||
s_Metadata = re.search(re_Metadata3, line)
|
||||
mode = 0
|
||||
if s_Metadata:
|
||||
name = s_Metadata.groups()[0].strip()
|
||||
format = s_Metadata.groups()[1].strip()
|
||||
creator = s_Metadata.groups()[2].strip()
|
||||
event = ""
|
||||
if mode == 1:
|
||||
event = s_Metadata.groups()[3].strip()
|
||||
for badChar in badChars:
|
||||
event = event.replace(badChar, "-")
|
||||
if args.t and args.t != format:
|
||||
print("Skipping an off-format deck " + name + " (format = " + format + ")")
|
||||
supported = False
|
||||
continue
|
||||
s_Maindeck = re.search(re_Maindeck, line)
|
||||
if s_Maindeck:
|
||||
cardAmount = s_Maindeck.groups()[0].strip()
|
||||
cardName = s_Maindeck.groups()[1].strip()
|
||||
if cardName == "":
|
||||
continue
|
||||
altModalKey = cardName.split(" // ")[0].strip()
|
||||
if not cardName in cardlist.keys() and not cardName.replace("Aether", "AEther") in cardlist.keys() and not cardName.replace("AEther", "Aether") in cardlist.keys() and not altModalKey in cardlist.keys():
|
||||
print("Unsupported card (MAIN): " + cardName)
|
||||
if args.f:
|
||||
supported = False
|
||||
else:
|
||||
isCardSupported = False
|
||||
deckHasUnsupportedCards = True
|
||||
if not cardName in unsupportedList:
|
||||
unsupportedList.extend([cardName])
|
||||
if altModalKey in cardlist.keys():
|
||||
mdline = cardAmount + " " + altModalKey # ZNR modal cards with //
|
||||
elif cardName in cardlist.keys():
|
||||
mdline = cardAmount + " " + cardName
|
||||
elif cardName.replace("Aether", "AEther") in cardlist.keys():
|
||||
mdline = cardAmount + " " + cardName.replace("Aether", "AEther")
|
||||
elif cardName.replace("AEther", "Aether") in cardlist.keys():
|
||||
mdline = cardAmount + " " + cardName.replace("AEther", "Aether")
|
||||
else:
|
||||
mdline = cardAmount + " " + cardName # for the purposes of unsupported cards
|
||||
if isCardSupported:
|
||||
maindeck.extend([mdline])
|
||||
else:
|
||||
maindeck.extend(["#"+mdline])
|
||||
maindeck_cards += int(cardAmount)
|
||||
continue
|
||||
s_Sideboard = re.search(re_Sideboard, line)
|
||||
if s_Sideboard:
|
||||
cardAmount = s_Sideboard.groups()[0].strip()
|
||||
cardName = s_Sideboard.groups()[1].strip()
|
||||
if cardName == "":
|
||||
continue
|
||||
altModalKey = cardName.split(" // ")[0].strip()
|
||||
if not cardName in cardlist.keys() and not cardName.replace("Aether", "AEther") in cardlist.keys() and not cardName.replace("AEther", "Aether") in cardlist.keys() and not altModalKey in cardlist.keys():
|
||||
print("Unsupported card (SIDE): " + cardName)
|
||||
if args.f:
|
||||
supported = False
|
||||
else:
|
||||
isCardSupported = False
|
||||
deckHasUnsupportedCards = True
|
||||
if not cardName in unsupportedList:
|
||||
unsupportedList.extend([cardName])
|
||||
if altModalKey in cardlist.keys():
|
||||
sdline = cardAmount + " " + altModalKey # ZNR modal cards with //
|
||||
elif cardName in cardlist.keys():
|
||||
sdline = cardAmount + " " + cardName
|
||||
elif cardName.replace("Aether", "AEther") in cardlist.keys():
|
||||
sdline = cardAmount + " " + cardName.replace("Aether", "AEther")
|
||||
elif cardName.replace("AEther", "Aether") in cardlist.keys():
|
||||
sdline = cardAmount + " " + cardName.replace("AEther", "Aether")
|
||||
else:
|
||||
sdline = cardAmount + " " + cardName # for the purposes of unsupported cards
|
||||
if isCardSupported:
|
||||
sideboard.extend([sdline])
|
||||
else:
|
||||
sideboard.extend(["#"+sdline])
|
||||
continue
|
||||
|
||||
# convert here
|
||||
if supported and len(maindeck) > 0:
|
||||
if creator != "":
|
||||
deckname = creator + " - " + name + " ("
|
||||
else:
|
||||
deckname = name + " ("
|
||||
deckname += format
|
||||
if args.i and deck_id > -1:
|
||||
if format != "":
|
||||
# convert here
|
||||
if supported and len(maindeck) > 0:
|
||||
if creator != "":
|
||||
deckname = creator + " - " + name + " ("
|
||||
else:
|
||||
deckname = name + " ("
|
||||
deckname += format
|
||||
if args.i and int(deck_id) > -1:
|
||||
if format != "":
|
||||
deckname += ", #" + deck_id
|
||||
else:
|
||||
deckname += "#" + deck_id
|
||||
deckname += ")"
|
||||
#deckname = (c for c in deckname if ord(c) < 128)
|
||||
if not args.U:
|
||||
deckname = re.sub(r'[^\x00-\x7F]', '@', deckname)
|
||||
deckname = re.sub(r'[/\\]', '-', deckname)
|
||||
deckname = re.sub(r'[?*]', '_', deckname)
|
||||
if args.w and deckHasUnsupportedCards:
|
||||
deckname += " [!]"
|
||||
if args.D:
|
||||
if deckHasUnsupportedCards:
|
||||
outname = "Unsupported" + "/" + format + "/"
|
||||
pathToUnsupported = "./" + OUT_DECKFOLDER + "/Unsupported/" + format
|
||||
if not os.path.isdir(pathToUnsupported):
|
||||
os.makedirs(pathToUnsupported)
|
||||
else:
|
||||
outname = format + "/"
|
||||
if not os.path.isdir("./" + OUT_DECKFOLDER + "/" + format):
|
||||
os.makedirs("./" + OUT_DECKFOLDER + "/" + format)
|
||||
else:
|
||||
outname = ""
|
||||
outname += deckname + ".dck"
|
||||
print ("Writing converted deck: " + outname)
|
||||
dck = open(OUT_DECKFOLDER + "/" + outname, "w")
|
||||
else:
|
||||
deckname += "#" + deck_id
|
||||
deckname += ")"
|
||||
#deckname = (c for c in deckname if ord(c) < 128)
|
||||
if not args.U:
|
||||
deckname = re.sub(r'[^\x00-\x7F]', '@', deckname)
|
||||
deckname = re.sub(r'[/\\]', '-', deckname)
|
||||
deckname = re.sub(r'[?*]', '_', deckname)
|
||||
if args.w and deckHasUnsupportedCards:
|
||||
deckname += " [!]"
|
||||
if args.D:
|
||||
if deckHasUnsupportedCards:
|
||||
outname = "Unsupported" + "/" + format + "/"
|
||||
pathToUnsupported = "./" + OUT_DECKFOLDER + "/Unsupported/" + format
|
||||
if not os.path.isdir(pathToUnsupported):
|
||||
os.makedirs(pathToUnsupported)
|
||||
else:
|
||||
outname = format + "/"
|
||||
if not os.path.isdir("./" + OUT_DECKFOLDER + "/" + format):
|
||||
os.makedirs("./" + OUT_DECKFOLDER + "/" + format)
|
||||
else:
|
||||
outname = ""
|
||||
outname += deckname + ".dck"
|
||||
print ("Writing converted deck: " + outname)
|
||||
dck = open(OUT_DECKFOLDER + "/" + outname, "w")
|
||||
|
||||
if event:
|
||||
dck.write("#EVENT:"+event+"\n")
|
||||
if event:
|
||||
dck.write("#EVENT:"+event+"\n")
|
||||
|
||||
dck.write("[metadata]\n")
|
||||
dck.write("Name="+deckname+"\n")
|
||||
dck.write("[general]\n")
|
||||
dck.write("Constructed\n")
|
||||
dck.write("[Main]\n")
|
||||
for m in maindeck:
|
||||
dck.write(m+"\n")
|
||||
if not (format == "Commander" and len(sideboard) == 1):
|
||||
dck.write("[Sideboard]\n")
|
||||
else:
|
||||
dck.write("[Commander]\n")
|
||||
for s in sideboard:
|
||||
dck.write(s+"\n")
|
||||
dck.write("[metadata]\n")
|
||||
dck.write("Name="+deckname+"\n")
|
||||
dck.write("[general]\n")
|
||||
dck.write("Constructed\n")
|
||||
dck.write("[Main]\n")
|
||||
for m in maindeck:
|
||||
dck.write(m+"\n")
|
||||
if not ((format == "Commander" or format == "Brawl" or format == "Duel-Commander" or format == "Historic-Brawl" or format == "Archon") and len(sideboard) == 1):
|
||||
dck.write("[Sideboard]\n")
|
||||
else:
|
||||
dck.write("[Commander]\n")
|
||||
for s in sideboard:
|
||||
dck.write(s+"\n")
|
||||
|
||||
# write out unsupported cards
|
||||
log = open("dec2forge.log", "w")
|
||||
|
||||
232
forge-gui/tools/DeckConversionTools/mtgdecksnet_decksort.py
Normal file → Executable file
232
forge-gui/tools/DeckConversionTools/mtgdecksnet_decksort.py
Normal file → Executable file
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
CARDSFOLDER = "../../res/cardsfolder"
|
||||
EDITIONS = "../../res/editions"
|
||||
@@ -6,10 +6,11 @@ DECKFOLDER = "."
|
||||
|
||||
import argparse, os, re, shutil
|
||||
|
||||
print("Agetian's MTG Forge Deck Sorter v1.4a\n")
|
||||
print("Agetian's MTG Forge Deck Sorter v2.0\n")
|
||||
|
||||
parser = argparse.ArgumentParser(description="Sort decks into folders (by edition).")
|
||||
parser.add_argument("-d", action="store_true", help="physically delete original (unsorted) decks")
|
||||
parser.add_argument("-x", action="store_true", help="exclude sorting by event")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -44,26 +45,34 @@ re_Card = '^[0-9]* *[A-Z] (.*)$'
|
||||
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_literal = cardtext.replace('\r','').split('\n')[0].split(':')
|
||||
cardname = ":".join(cardname_literal[1:]).strip()
|
||||
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]])
|
||||
cardname = " // ".join(cardnames)
|
||||
if cardtext.lower().find("removedeck:all") != -1:
|
||||
cardlist[cardname] = 0
|
||||
else:
|
||||
cardlist[cardname] = 1
|
||||
ai_playable_cards += 1
|
||||
if name.find(".txt") != -1:
|
||||
total_cards += 1
|
||||
fullpath = os.path.join(root, name)
|
||||
cardtext = open(fullpath).read()
|
||||
cardtext_lower = cardtext.lower()
|
||||
cardname_literal = cardtext.replace('\r','').split('\n')[0].split(':')
|
||||
cardname = ":".join(cardname_literal[1:]).strip()
|
||||
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]])
|
||||
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]])
|
||||
cardname = cardnames[0].strip()
|
||||
if cardtext.lower().find("remaideck") != -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
|
||||
@@ -73,57 +82,58 @@ print("Loaded %d cards, among them %d playable by the AI (%d%%), %d unplayable b
|
||||
print("Loading editions...")
|
||||
for root, dirs, files in os.walk(EDITIONS):
|
||||
for name in files:
|
||||
if name.find(".txt") != -1:
|
||||
total_editions += 1
|
||||
fullpath = os.path.join(root, name)
|
||||
edition = open(fullpath).readlines()
|
||||
foundCards = False
|
||||
code = ""
|
||||
date = ""
|
||||
etype = ""
|
||||
name = ""
|
||||
for line in edition:
|
||||
line = line.replace("\r\n","")
|
||||
if not foundCards:
|
||||
if line == "[cards]":
|
||||
foundCards = True
|
||||
else:
|
||||
s_Code = re.search(re_Code, line)
|
||||
if s_Code:
|
||||
code = s_Code.groups()[0]
|
||||
#print("Code found: " + code)
|
||||
s_Date = re.search(re_Date, line)
|
||||
if s_Date:
|
||||
date = s_Date.groups()[0]
|
||||
#print("Date found: " + date)
|
||||
s_Date2 = re.search(re_Date2, line)
|
||||
if s_Date2:
|
||||
date = s_Date2.groups()[0] + "-01"
|
||||
#print("Date found: " + date)
|
||||
s_Type = re.search(re_Type, line)
|
||||
if s_Type:
|
||||
etype = s_Type.groups()[0]
|
||||
#print("Type found: " + etype)
|
||||
s_Name = re.search(re_Name, line)
|
||||
if s_Name:
|
||||
name = s_Name.groups()[0]
|
||||
#print("Name found: " + name)
|
||||
else:
|
||||
if etype != "Expansion" and etype != "Core" and etype != "Starter":
|
||||
#print("NOT LOADING: " + code)
|
||||
continue
|
||||
else:
|
||||
if editions.keys().count(code) == 0:
|
||||
editions[code] = date
|
||||
edition_names[code] = name
|
||||
#print(editions)
|
||||
s_Card = re.search(re_Card, line)
|
||||
if s_Card:
|
||||
card = s_Card.groups()[0].strip()
|
||||
#print("Card found: " + card)
|
||||
if cards_by_edition.keys().count(card) == 0:
|
||||
cards_by_edition[card] = []
|
||||
cards_by_edition[card].append(code)
|
||||
if name.find(".txt") != -1:
|
||||
total_editions += 1
|
||||
fullpath = os.path.join(root, name)
|
||||
edition = open(fullpath).readlines()
|
||||
foundCards = False
|
||||
code = ""
|
||||
date = ""
|
||||
etype = ""
|
||||
name = ""
|
||||
for line in edition:
|
||||
line = line.replace("\r\n","")
|
||||
line = line.split(" @")[0]
|
||||
if not foundCards:
|
||||
if line.find("[cards]") != -1:
|
||||
foundCards = True
|
||||
else:
|
||||
s_Code = re.search(re_Code, line)
|
||||
if s_Code:
|
||||
code = s_Code.groups()[0]
|
||||
#print("Code found: " + code)
|
||||
s_Date = re.search(re_Date, line)
|
||||
if s_Date:
|
||||
date = s_Date.groups()[0]
|
||||
#print("Date found: " + date)
|
||||
s_Date2 = re.search(re_Date2, line)
|
||||
if s_Date2:
|
||||
date = s_Date2.groups()[0] + "-01"
|
||||
#print("Date found: " + date)
|
||||
s_Type = re.search(re_Type, line)
|
||||
if s_Type:
|
||||
etype = s_Type.groups()[0]
|
||||
#print("Type found: " + etype)
|
||||
s_Name = re.search(re_Name, line)
|
||||
if s_Name:
|
||||
name = s_Name.groups()[0]
|
||||
#print("Name found: " + name)
|
||||
else:
|
||||
if etype != "Expansion" and etype != "Core" and etype != "Starter" and code != "VOC" and code != "MIC" and code != "AFC":
|
||||
#print("NOT LOADING: " + code)
|
||||
continue
|
||||
else:
|
||||
if not code in editions.keys():
|
||||
editions[code] = date
|
||||
edition_names[code] = name
|
||||
#print(editions)
|
||||
s_Card = re.search(re_Card, line)
|
||||
if s_Card:
|
||||
card = s_Card.groups()[0].strip()
|
||||
#print("Card found: " + card)
|
||||
if not card in cards_by_edition.keys():
|
||||
cards_by_edition[card] = []
|
||||
cards_by_edition[card].append(code)
|
||||
|
||||
|
||||
print("Loaded " + str(len(editions)) + " editions.")
|
||||
@@ -132,47 +142,47 @@ def get_latest_set_for_card(card):
|
||||
cdate = "0000-00-00"
|
||||
edition = "XXX"
|
||||
if ignore_cards.count(card) != 0:
|
||||
return "LEA"
|
||||
if cards_by_edition.keys().count(card) == 0:
|
||||
#print("Warning: couldn't determine an edition for card: " + card)
|
||||
return "LEA"
|
||||
return "LEA"
|
||||
if not card in cards_by_edition.keys():
|
||||
#print("Warning: couldn't determine an edition for card: " + card)
|
||||
return "LEA"
|
||||
for code in cards_by_edition[card]:
|
||||
if editions[code] > cdate:
|
||||
cdate = editions[code]
|
||||
edition = code
|
||||
if editions[code] > cdate:
|
||||
cdate = editions[code]
|
||||
edition = code
|
||||
return edition
|
||||
|
||||
def get_earliest_set_for_card(card):
|
||||
cdate = "9999-99-99"
|
||||
edition = "XXX"
|
||||
if cards_by_edition.keys().count(card) == 0:
|
||||
#print("Warning: couldn't determine an edition for card: " + card)
|
||||
return "LEA"
|
||||
if not card in cards_by_edition.keys():
|
||||
#print("Warning: couldn't determine an edition for card: " + card)
|
||||
return "LEA"
|
||||
for code in cards_by_edition[card]:
|
||||
if editions[code] < cdate:
|
||||
cdate = editions[code]
|
||||
edition = code
|
||||
if editions[code] < cdate:
|
||||
cdate = editions[code]
|
||||
edition = code
|
||||
return edition
|
||||
|
||||
def get_latest_set_for_deck(deck):
|
||||
edition = "LEA"
|
||||
for line in deck.split('\n'):
|
||||
#print("Line: " + line)
|
||||
regexobj = re.search('^([0-9]+) +([^|]+)', line)
|
||||
if regexobj:
|
||||
cardname = regexobj.groups()[1].replace('\n','').replace('\r','').strip()
|
||||
earliest_for_card = get_earliest_set_for_card(cardname)
|
||||
#print("Card: " + cardname + ", latest for it: " + latest_for_card)
|
||||
if editions[earliest_for_card] > editions[edition]:
|
||||
edition = earliest_for_card
|
||||
#print("Line: " + line)
|
||||
regexobj = re.search('^([0-9]+) +([^|]+)', line)
|
||||
if regexobj:
|
||||
cardname = regexobj.groups()[1].replace('\n','').replace('\r','').strip()
|
||||
earliest_for_card = get_earliest_set_for_card(cardname)
|
||||
#print("Card: " + cardname + ", latest for it: " + latest_for_card)
|
||||
if editions[earliest_for_card] > editions[edition]:
|
||||
edition = earliest_for_card
|
||||
return edition
|
||||
|
||||
def get_event_for_deck(deck):
|
||||
evline = deck.split('\n')[0].split("#EVENT:")
|
||||
if len(evline) == 2:
|
||||
return evline[1]
|
||||
return evline[1]
|
||||
else:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
#print(cards_by_edition["Fireball"])
|
||||
#print(edition_names[get_latest_set_for_card("Fireball")])
|
||||
@@ -189,18 +199,20 @@ def get_event_for_deck(deck):
|
||||
print("Scanning decks...")
|
||||
for root, dirs, files in os.walk(DECKFOLDER):
|
||||
for name in files:
|
||||
if name.find(".dck") != -1:
|
||||
total_decks += 1
|
||||
fullpath = os.path.join(root, name)
|
||||
deckdata = open(fullpath).read()
|
||||
set_for_deck = edition_names[get_latest_set_for_deck(deckdata)]
|
||||
event_for_deck = get_event_for_deck(deckdata)
|
||||
if event_for_deck != "" and event_for_deck[len(event_for_deck)-1] == ".":
|
||||
event_for_deck = event_for_deck[0:len(event_for_deck)-1]
|
||||
print("Deck: " + name + ", Set: " + set_for_deck + ", Event: " + event_for_deck)
|
||||
if not os.access(os.path.join(root, set_for_deck, event_for_deck), os.F_OK):
|
||||
os.makedirs(os.path.join(root, set_for_deck, event_for_deck))
|
||||
shutil.copy(fullpath, os.path.join(root, set_for_deck, event_for_deck, name))
|
||||
if args.d:
|
||||
os.remove(fullpath)
|
||||
if name.find(".dck") != -1:
|
||||
total_decks += 1
|
||||
fullpath = os.path.join(root, name)
|
||||
deckdata = open(fullpath).read()
|
||||
set_for_deck = edition_names[get_latest_set_for_deck(deckdata)]
|
||||
event_for_deck = get_event_for_deck(deckdata)
|
||||
if args.x:
|
||||
event_for_deck = ""
|
||||
if event_for_deck != "" and event_for_deck[len(event_for_deck)-1] == ".":
|
||||
event_for_deck = event_for_deck[0:len(event_for_deck)-1]
|
||||
print("Deck: " + name + ", Set: " + set_for_deck + ", Event: " + event_for_deck)
|
||||
if not os.access(os.path.join(root, set_for_deck, event_for_deck), os.F_OK):
|
||||
os.makedirs(os.path.join(root, set_for_deck, event_for_deck))
|
||||
shutil.copy(fullpath, os.path.join(root, set_for_deck, event_for_deck, name))
|
||||
if args.d:
|
||||
os.remove(fullpath)
|
||||
|
||||
|
||||
36
forge-gui/tools/DeckConversionTools/mtgdecksnet_decksort_EDH.py
Normal file → Executable file
36
forge-gui/tools/DeckConversionTools/mtgdecksnet_decksort_EDH.py
Normal file → Executable file
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
CARDSFOLDER = "../../res/cardsfolder"
|
||||
EDITIONS = "../../res/editions"
|
||||
@@ -6,10 +6,11 @@ DECKFOLDER = "."
|
||||
|
||||
import argparse, os, re, shutil
|
||||
|
||||
print("Agetian's MTG Forge Deck Sorter v1.4a\n")
|
||||
print("Agetian's MTG Forge Deck Sorter v2.0\n")
|
||||
|
||||
parser = argparse.ArgumentParser(description="Sort decks into folders (by edition).")
|
||||
parser.add_argument("-d", action="store_true", help="physically delete original (unsorted) decks")
|
||||
parser.add_argument("-x", action="store_true", help="exclude sorting by event")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -59,7 +60,15 @@ for root, dirs, files in os.walk(CARDSFOLDER):
|
||||
if line.lower().find("name:") != -1:
|
||||
cardnames.extend([line.split('\n')[0].split(':')[1]])
|
||||
cardname = " // ".join(cardnames)
|
||||
if cardtext.lower().find("removedeck:all") != -1:
|
||||
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]])
|
||||
cardname = cardnames[0].strip()
|
||||
if cardtext.lower().find("remaideck") != -1:
|
||||
cardlist[cardname] = 0
|
||||
else:
|
||||
cardlist[cardname] = 1
|
||||
@@ -84,8 +93,9 @@ for root, dirs, files in os.walk(EDITIONS):
|
||||
name = ""
|
||||
for line in edition:
|
||||
line = line.replace("\r\n","")
|
||||
line = line.split(" @")[0]
|
||||
if not foundCards:
|
||||
if line == "[cards]":
|
||||
if line.find("[cards]") != -1:
|
||||
foundCards = True
|
||||
else:
|
||||
s_Code = re.search(re_Code, line)
|
||||
@@ -109,14 +119,14 @@ for root, dirs, files in os.walk(EDITIONS):
|
||||
name = s_Name.groups()[0]
|
||||
#print("Name found: " + name)
|
||||
else:
|
||||
if etype != "Expansion" and etype != "Core" and etype != "Starter" and etype != "Other":
|
||||
if etype != "Expansion" and etype != "Core" and etype != "Starter" and etype != "Commander":
|
||||
#print("NOT LOADING: " + code)
|
||||
continue
|
||||
if code == "EXP" or code == "MPS":
|
||||
#print("NOT LOADING: " + code)
|
||||
continue
|
||||
continue
|
||||
else:
|
||||
if editions.keys().count(code) == 0:
|
||||
if not code in editions.keys():
|
||||
editions[code] = date
|
||||
edition_names[code] = name
|
||||
#print(editions)
|
||||
@@ -124,7 +134,7 @@ for root, dirs, files in os.walk(EDITIONS):
|
||||
if s_Card:
|
||||
card = s_Card.groups()[0].strip()
|
||||
#print("Card found: " + card)
|
||||
if cards_by_edition.keys().count(card) == 0:
|
||||
if not card in cards_by_edition.keys():
|
||||
cards_by_edition[card] = []
|
||||
cards_by_edition[card].append(code)
|
||||
|
||||
@@ -136,7 +146,7 @@ def get_latest_set_for_card(card):
|
||||
edition = "XXX"
|
||||
if ignore_cards.count(card) != 0:
|
||||
return "LEA"
|
||||
if cards_by_edition.keys().count(card) == 0:
|
||||
if not card in cards_by_edition.keys():
|
||||
#print("Warning: couldn't determine an edition for card: " + card)
|
||||
return "LEA"
|
||||
for code in cards_by_edition[card]:
|
||||
@@ -148,7 +158,7 @@ def get_latest_set_for_card(card):
|
||||
def get_earliest_set_for_card(card):
|
||||
cdate = "9999-99-99"
|
||||
edition = "XXX"
|
||||
if cards_by_edition.keys().count(card) == 0:
|
||||
if not card in cards_by_edition.keys():
|
||||
#print("Warning: couldn't determine an edition for card: " + card)
|
||||
return "LEA"
|
||||
for code in cards_by_edition[card]:
|
||||
@@ -198,8 +208,10 @@ for root, dirs, files in os.walk(DECKFOLDER):
|
||||
deckdata = open(fullpath).read()
|
||||
set_for_deck = edition_names[get_latest_set_for_deck(deckdata)]
|
||||
event_for_deck = get_event_for_deck(deckdata)
|
||||
if event_for_deck != "" and event_for_deck[len(event_for_deck)-1] == ".":
|
||||
event_for_deck = event_for_deck[0:len(event_for_deck)-1]
|
||||
if args.x:
|
||||
event_for_deck = ""
|
||||
if event_for_deck != "" and event_for_deck[len(event_for_deck)-1] == ".":
|
||||
event_for_deck = event_for_deck[0:len(event_for_deck)-1]
|
||||
print("Deck: " + name + ", Set: " + set_for_deck + ", Event: " + event_for_deck)
|
||||
if not os.access(os.path.join(root, set_for_deck, event_for_deck), os.F_OK):
|
||||
os.makedirs(os.path.join(root, set_for_deck, event_for_deck))
|
||||
|
||||
572
forge-gui/tools/ai_limitedplayable.lst
Normal file
572
forge-gui/tools/ai_limitedplayable.lst
Normal file
@@ -0,0 +1,572 @@
|
||||
Relic Crush
|
||||
Energy Vortex
|
||||
Nature's Chosen
|
||||
Game of Chaos
|
||||
Scarecrow
|
||||
Mystic Gate
|
||||
Survival of the Fittest
|
||||
Relic Ward
|
||||
Desolation
|
||||
Firespout
|
||||
Invert the Skies
|
||||
Khalni Gem
|
||||
Duergar Assailant
|
||||
Orcish Farmer
|
||||
Farrelite Priest
|
||||
Heart Wolf
|
||||
Infernal Darkness
|
||||
Silvergill Douser
|
||||
Meloku the Clouded Mirror
|
||||
Active Volcano
|
||||
Oran-Rief Recluse
|
||||
Pale Wayfarer
|
||||
Kjeldoran Warrior
|
||||
Lim-Dul's Vault
|
||||
Sphinx's Tutelage
|
||||
Wall of Shadows
|
||||
High Market
|
||||
AEthermage's Touch
|
||||
Homicidal Seclusion
|
||||
Doomsday
|
||||
Urborg
|
||||
Crag Puca
|
||||
Snakeform
|
||||
Brass-Talon Chimera
|
||||
Sacred Mesa
|
||||
Mystic Veil
|
||||
Ravenous Vampire
|
||||
Hunting Wilds
|
||||
Sudden Reclamation
|
||||
Maze's End
|
||||
Primordial Ooze
|
||||
Ritual of the Machine
|
||||
Nihilistic Glee
|
||||
Spectral Guardian
|
||||
Oona's Prowler
|
||||
Blasting Station
|
||||
Rust
|
||||
Roar of the Crowd
|
||||
Sporeback Troll
|
||||
Gravebind
|
||||
Fire and Brimstone
|
||||
Gitaxian Probe
|
||||
Celestial Prism
|
||||
Teferi's Veil
|
||||
Urza's Avenger
|
||||
Fortified Area
|
||||
Kjeldoran Dead
|
||||
Proteus Staff
|
||||
Necrotic Ooze
|
||||
Relentless Assault
|
||||
Diamond Valley
|
||||
Elsewhere Flask
|
||||
Greenseeker
|
||||
Part Water
|
||||
Mox Diamond
|
||||
Mul Daya Channelers
|
||||
Phyrexia's Core
|
||||
Claws of Gix
|
||||
Tolaria
|
||||
Goblin Warrens
|
||||
Beast Within
|
||||
Naked Singularity
|
||||
Pili-Pala
|
||||
Manabond
|
||||
Quiet Speculation
|
||||
Johan
|
||||
Norritt
|
||||
Momentous Fall
|
||||
Stonybrook Angler
|
||||
Shelkin Brownie
|
||||
Distant Melody
|
||||
Psionic Entity
|
||||
Kjeldoran Knight
|
||||
Hyperion Blacksmith
|
||||
Vampirism
|
||||
Zombie Infestation
|
||||
Wall of Vapor
|
||||
Goblin Kites
|
||||
Shielded Passage
|
||||
Tin-Wing Chimera
|
||||
Phantasmal Sphere
|
||||
Juniper Order Druid
|
||||
Devoted Druid
|
||||
Kjeldoran Phalanx
|
||||
Halls of Mist
|
||||
Wildfire
|
||||
Steel Golem
|
||||
Battering Ram
|
||||
Stromgald Spy
|
||||
Barrin, Master Wizard
|
||||
Sunglasses of Urza
|
||||
Mana Clash
|
||||
Oath of Lim-Dul
|
||||
Peacekeeper
|
||||
Strongarm Tactics
|
||||
Birthing Pod
|
||||
Splintering Wind
|
||||
Shields of Velis Vel
|
||||
Touch of Vitae
|
||||
Swan Song
|
||||
Mind Bomb
|
||||
Civic Guildmage
|
||||
Patriarch's Bidding
|
||||
Spectral Cloak
|
||||
Nettling Imp
|
||||
Mirror Entity
|
||||
Puresight Merrow
|
||||
Illuminated Folio
|
||||
Grazing Kelpie
|
||||
Cadaverous Bloom
|
||||
Endless Whispers
|
||||
Wall of Shields
|
||||
Portent
|
||||
Leech Bonder
|
||||
Goblin Bombardment
|
||||
Aleatory
|
||||
Piety
|
||||
Brand of Ill Omen
|
||||
Thelonite Monk
|
||||
Enduring Renewal
|
||||
School of the Unseen
|
||||
Thorntooth Witch
|
||||
Carrion Rats
|
||||
Tideshaper Mystic
|
||||
Transmute Artifact
|
||||
Hammerheim
|
||||
Flash
|
||||
Shimmer
|
||||
Memory Jar
|
||||
Tidal Control
|
||||
Timber Wolves
|
||||
Unerring Sling
|
||||
Phyrexian Purge
|
||||
Wave Elemental
|
||||
Beast Walkers
|
||||
Iname, Death Aspect
|
||||
Qasali Pridemage
|
||||
Darkheart Sliver
|
||||
Fire-Lit Thicket
|
||||
Ogre Marauder
|
||||
Merchant Scroll
|
||||
Krovikan Plague
|
||||
Telim'Tor's Edict
|
||||
Divine Reckoning
|
||||
Thieves' Fortune
|
||||
Lammastide Weave
|
||||
Confusion in the Ranks
|
||||
Ancestral Statue
|
||||
Repel Intruders
|
||||
Limited Resources
|
||||
Fate Transfer
|
||||
Droning Bureaucrats
|
||||
Thada Adel, Acquisitor
|
||||
Mana Flare
|
||||
Voltaic Key
|
||||
Night of Souls' Betrayal
|
||||
Phantasmal Mount
|
||||
Black Carriage
|
||||
Tainted AEther
|
||||
Horror of Horrors
|
||||
Glare of Subdual
|
||||
Flooded Grove
|
||||
Consuming Ferocity
|
||||
Lead-Belly Chimera
|
||||
Mire Shade
|
||||
Surprise Deployment
|
||||
Nivmagus Elemental
|
||||
Bone Shaman
|
||||
Orcish Lumberjack
|
||||
Mana Prism
|
||||
Knights of Thorn
|
||||
Porphyry Nodes
|
||||
Rally
|
||||
Vivisection
|
||||
Tropical Storm
|
||||
Personal Incarnation
|
||||
Daughter of Autumn
|
||||
Sandals of Abdallah
|
||||
Life Matrix
|
||||
Inside Out
|
||||
Dwarven Ruins
|
||||
Rugged Prairie
|
||||
Chainbreaker
|
||||
Truce
|
||||
Heartless Summoning
|
||||
Waiting in the Weeds
|
||||
Chaos Lord
|
||||
Goblin Bomb
|
||||
Camel
|
||||
Balduvian Trading Post
|
||||
Resistance Fighter
|
||||
Noble Elephant
|
||||
The Chain Veil
|
||||
Glyph of Destruction
|
||||
Library of Lat-Nam
|
||||
Magus of the Unseen
|
||||
Think Tank
|
||||
Ventifact Bottle
|
||||
Cerulean Wisps
|
||||
Scrying Sheets
|
||||
River's Grasp
|
||||
Sunken Ruins
|
||||
Elven Rite
|
||||
Vexing Shusher
|
||||
Dark Sphere
|
||||
Chaos Moon
|
||||
Meadowboon
|
||||
Deadly Wanderings
|
||||
Natural Selection
|
||||
Vexing Arcanix
|
||||
Zur's Weirding
|
||||
Psychosis Crawler
|
||||
Diplomacy of the Wastes
|
||||
Thrull Wizard
|
||||
Juxtapose
|
||||
Stream of Unconsciousness
|
||||
Musician
|
||||
Bone Mask
|
||||
Grim Feast
|
||||
Orcish Librarian
|
||||
Inkfathom Witch
|
||||
Turtleshell Changeling
|
||||
Fell the Mighty
|
||||
Island Sanctuary
|
||||
Bathe in Light
|
||||
Tidal Wave
|
||||
Time Spiral
|
||||
Scarscale Ritual
|
||||
Gargantuan Gorilla
|
||||
Enervate
|
||||
Reality Twist
|
||||
Harabaz Druid
|
||||
March of Souls
|
||||
Orcish Settlers
|
||||
Lake of the Dead
|
||||
Grid Monitor
|
||||
Unfulfilled Desires
|
||||
War Barge
|
||||
Diminishing Returns
|
||||
Final Fortune
|
||||
Shrewd Hatchling
|
||||
Batwing Brume
|
||||
Foresight
|
||||
Dawnfluke
|
||||
Ebon Praetor
|
||||
Putrid Leech
|
||||
Benevolent Offering
|
||||
Heat Wave
|
||||
Eye of Singularity
|
||||
Drafna's Restoration
|
||||
Shauku, Endbringer
|
||||
Hell Swarm
|
||||
Shield Bearer
|
||||
Harbinger of Night
|
||||
Krosan Archer
|
||||
Mana Short
|
||||
Lotus Vale
|
||||
Misinformation
|
||||
Endure
|
||||
Vampiric Rites
|
||||
Ritual of Subdual
|
||||
Soulbright Flamekin
|
||||
Mark for Death
|
||||
Essence Flare
|
||||
Mossbridge Troll
|
||||
Woeleecher
|
||||
Relic Bind
|
||||
Thundercloud Shaman
|
||||
AEther Barrier
|
||||
Iceberg
|
||||
Kjeldoran Escort
|
||||
Seedling Charm
|
||||
Fossil Find
|
||||
Panic
|
||||
Fork
|
||||
Dark Privilege
|
||||
Soldevi Digger
|
||||
Revelsong Horn
|
||||
Ivory Charm
|
||||
Aladdin's Lamp
|
||||
Ward of Lights
|
||||
Yawgmoth Demon
|
||||
Ancestral Memories
|
||||
Hecatomb
|
||||
Amber Prison
|
||||
Gabriel Angelfire
|
||||
Helm of Obedience
|
||||
Mind Warp
|
||||
Fiery Justice
|
||||
Flooded Shoreline
|
||||
Razor Pendulum
|
||||
Lat-Nam's Legacy
|
||||
Phyrexian Portal
|
||||
Urborg Panther
|
||||
Dragon Mask
|
||||
Sundering Titan
|
||||
Grollub
|
||||
Niveous Wisps
|
||||
Bone Flute
|
||||
Teremko Griffin
|
||||
Fungal Behemoth
|
||||
Dwarven Armory
|
||||
Teetering Peaks
|
||||
Whip Vine
|
||||
Ovinomancer
|
||||
Stronghold Assassin
|
||||
Cocoon
|
||||
Infuse
|
||||
Springleaf Drum
|
||||
Ophidian
|
||||
Unwilling Recruit
|
||||
Dwarven Armorer
|
||||
Priest of Yawgmoth
|
||||
Mana Crypt
|
||||
Bazaar of Baghdad
|
||||
Aggression
|
||||
Benalish Hero
|
||||
Elkin Lair
|
||||
Soldevi Sage
|
||||
Jade Statue
|
||||
Ambush
|
||||
Twilight Mire
|
||||
Wooded Bastion
|
||||
Strategic Planning
|
||||
Wheel of Fate
|
||||
Foreshadow
|
||||
Malevolent Awakening
|
||||
Dwarven Sea Clan
|
||||
Dire Wolves
|
||||
Phyrexian Boon
|
||||
Soldevi Golem
|
||||
Diamond Kaleidoscope
|
||||
Crystal Vein
|
||||
Jandor's Saddlebags
|
||||
Tar Pitcher
|
||||
Null Brooch
|
||||
Gaea's Liege
|
||||
Forbidden Alchemy
|
||||
Recall
|
||||
Squandered Resources
|
||||
Ancient Tomb
|
||||
Varchild's Crusader
|
||||
Spell Syphon
|
||||
Daghatar the Adamant
|
||||
Pikemen
|
||||
Tornado
|
||||
Scarwood Bandits
|
||||
Esper Charm
|
||||
Lignify
|
||||
Goblin Ski Patrol
|
||||
Shrieking Drake
|
||||
Errand of Duty
|
||||
Whirlpool Whelm
|
||||
Avalanche
|
||||
Reinforcements
|
||||
Misfortune
|
||||
Cursecatcher
|
||||
Indestructible Aura
|
||||
Grave Servitude
|
||||
Fire Sprites
|
||||
Time Reversal
|
||||
Personal Tutor
|
||||
Kaervek's Spite
|
||||
Oubliette
|
||||
Lowland Oaf
|
||||
Windbrisk Heights
|
||||
Marsh Gas
|
||||
Kjeldoran Guard
|
||||
Island of Wak-Wak
|
||||
Coal Golem
|
||||
Balduvian Shaman
|
||||
Barrenton Medic
|
||||
Kjeldoran Skyknight
|
||||
Vampire Lacerator
|
||||
Leviathan
|
||||
Forbidden Crypt
|
||||
Grave Peril
|
||||
Kjeldoran Skycaptain
|
||||
Shapeshifter
|
||||
Barter in Blood
|
||||
Notorious Throng
|
||||
Eye for an Eye
|
||||
Sage of Fables
|
||||
Elder Spawn
|
||||
Ragnar
|
||||
Soldevi Adnate
|
||||
Aquitect's Will
|
||||
Prismatic Circle
|
||||
Seeds of Innocence
|
||||
Magma Mine
|
||||
Sealed Fate
|
||||
Coral Reef
|
||||
Shimmering Grotto
|
||||
Spell Blast
|
||||
Browse
|
||||
Drop of Honey
|
||||
Dance of Many
|
||||
Stampeding Wildebeests
|
||||
Stone Giant
|
||||
Innocent Blood
|
||||
Twiddle
|
||||
Polymorph
|
||||
Jester's Cap
|
||||
Sygg, River Guide
|
||||
Magma Giant
|
||||
Soulshriek
|
||||
Phyrexian Gremlins
|
||||
Initiates of the Ebon Hand
|
||||
Takklemaggot
|
||||
Wild Growth
|
||||
Marsh Flitter
|
||||
Karoo
|
||||
Mesa Pegasus
|
||||
Ethereal Champion
|
||||
Windfall
|
||||
Nature's Wrath
|
||||
Liliana of the Veil
|
||||
Jamuraan Lion
|
||||
Gangrenous Zombies
|
||||
Rogue Skycaptain
|
||||
Wand of Denial
|
||||
Kjeldoran Elite Guard
|
||||
Feldon's Cane
|
||||
Echoing Truth
|
||||
Pox
|
||||
Kukemssa Serpent
|
||||
Vision Charm
|
||||
Phelddagrif
|
||||
Shield Wall
|
||||
Dispatch
|
||||
Gaea's Blessing
|
||||
Righteousness
|
||||
Proclamation of Rebirth
|
||||
Winter Sky
|
||||
Krovikan Sorcerer
|
||||
Ley Druid
|
||||
Veldrane of Sengir
|
||||
Storm Seeker
|
||||
Sneak Attack
|
||||
Savage Twister
|
||||
Grim Monolith
|
||||
Hearth Charm
|
||||
Adarkar Unicorn
|
||||
Profane Command
|
||||
Diabolic Vision
|
||||
Coral Fighters
|
||||
Malignant Growth
|
||||
Balduvian Hydra
|
||||
Pyric Salamander
|
||||
Brainstorm
|
||||
Sea Scryer
|
||||
Runesword
|
||||
Dark Heart of the Wood
|
||||
Ebony Charm
|
||||
Phyrexian Vault
|
||||
Hope Charm
|
||||
Mole Worms
|
||||
Rakalite
|
||||
Remedy
|
||||
Nezumi Bone-Reader
|
||||
Spitting Slug
|
||||
Giant Caterpillar
|
||||
War Elephant
|
||||
Varchild's War-Riders
|
||||
Prosperity
|
||||
Coral Atoll
|
||||
Daze
|
||||
Jalum Tome
|
||||
Cuombajj Witches
|
||||
Cytoplast Root-Kin
|
||||
Doomgape
|
||||
Incandescent Soulstoke
|
||||
Knight of the Mists
|
||||
Icatian Phalanx
|
||||
Vaporous Djinn
|
||||
Red Elemental Blast
|
||||
Gossamer Chains
|
||||
Samite Alchemist
|
||||
Urza's Bauble
|
||||
Goblin Recruiter
|
||||
Triassic Egg
|
||||
Hasran Ogress
|
||||
Soraya the Falconer
|
||||
Vesuvan Doppelganger
|
||||
Basalt Monolith
|
||||
Armageddon Clock
|
||||
Anaba Ancestor
|
||||
Emerald Charm
|
||||
Everglades
|
||||
Maro
|
||||
Clockwork Beast
|
||||
Soldevi Excavations
|
||||
Jungle Basin
|
||||
Zhalfirin Crusader
|
||||
Vampiric Tutor
|
||||
Funeral Charm
|
||||
Vigilant Martyr
|
||||
Quirion Ranger
|
||||
Ponder
|
||||
Tinder Wall
|
||||
Lotus Cobra
|
||||
Balm of Restoration
|
||||
Winds of Change
|
||||
Guardian Angel
|
||||
Manamorphose
|
||||
Sandstorm
|
||||
Dormant Volcano
|
||||
Rise of the Hobgoblins
|
||||
Orcish Cannoneers
|
||||
Berserk
|
||||
Bone Harvest
|
||||
Agent of Stromgald
|
||||
Time Elemental
|
||||
Conch Horn
|
||||
Giant Trap Door Spider
|
||||
Ghazban Ogre
|
||||
Phantasmal Terrain
|
||||
Orcish Artillery
|
||||
Rainbow Efreet
|
||||
Hurr Jackal
|
||||
Familiar's Ruse
|
||||
Mist Dragon
|
||||
Clockwork Avian
|
||||
Serendib Djinn
|
||||
Sylvan Library
|
||||
Jandor's Ring
|
||||
Armor of Thorns
|
||||
Icatian Infantry
|
||||
Feast or Famine
|
||||
Stormbind
|
||||
Dark Ritual
|
||||
Searing Spear Askari
|
||||
Basal Thrull
|
||||
Ivory Gargoyle
|
||||
Gemstone Mine
|
||||
Drain Life
|
||||
Howl from Beyond
|
||||
Mystical Tutor
|
||||
Gorilla Shaman
|
||||
Triskelion
|
||||
Mind Twist
|
||||
Demonic Tutor
|
||||
Wheel of Fortune
|
||||
Mana Vault
|
||||
Force of Will
|
||||
Snake Basket
|
||||
Impulse
|
||||
Hurricane
|
||||
Fellwar Stone
|
||||
Dig Through Time
|
||||
Treasure Cruise
|
||||
Snapcaster Mage
|
||||
Enlightened Tutor
|
||||
Diabolic Tutor
|
||||
Idyllic Tutor
|
||||
Personal Tutor
|
||||
Sylvan Tutor
|
||||
Worldly Tutor
|
||||
Vampiric Tutor
|
||||
Grim Tutor
|
||||
Merchant Scroll
|
||||
|
||||
120
forge-gui/tools/deckAiCompat.py
Normal file → Executable file
120
forge-gui/tools/deckAiCompat.py
Normal file → Executable file
@@ -1,19 +1,23 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
CARDSFOLDER = "../../res/cardsfolder"
|
||||
DECKFOLDER = "."
|
||||
|
||||
import argparse, os, re
|
||||
|
||||
print("Agetian's MTG Forge Deck AI Compatibility Analyzer v1.0\n")
|
||||
print("Agetian's MTG Forge Deck AI Compatibility Analyzer v4.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 and the 'decks' folder with .dck files in the current directory in order to operate. Exiting.")
|
||||
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.")
|
||||
@@ -29,28 +33,44 @@ nonplayable_in_deck = 0
|
||||
|
||||
# main algorithm
|
||||
print("Loading cards...")
|
||||
for root, dirs, files in os.walk("cardsfolder"):
|
||||
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_literal = cardtext.replace('\r','').split('\n')[0].split(':')
|
||||
cardname = ":".join(cardname_literal[1:]).strip()
|
||||
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]])
|
||||
cardname = " // ".join(cardnames)
|
||||
if cardtext.lower().find("removedeck:all") != -1:
|
||||
cardlist[cardname] = 0
|
||||
else:
|
||||
cardlist[cardname] = 1
|
||||
ai_playable_cards += 1
|
||||
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()
|
||||
break
|
||||
if cardname == "":
|
||||
cardname_literal = cardtext.replace('\r','').split('\n')[0].split(':')
|
||||
cardname = ":".join(cardname_literal[1:]).strip()
|
||||
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]])
|
||||
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]])
|
||||
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
|
||||
@@ -58,30 +78,34 @@ 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("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:
|
||||
regexobj = re.search('^([0-9]+) +([^|]+)', line)
|
||||
if regexobj:
|
||||
cardname = regexobj.groups()[1].replace('\n','').replace('\r','').strip()
|
||||
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))
|
||||
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')
|
||||
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
|
||||
|
||||
144
forge-gui/tools/deckAiCompat_limext.py
Executable file
144
forge-gui/tools/deckAiCompat_limext.py
Executable file
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
CARDSFOLDER = "../../res/cardsfolder"
|
||||
DECKFOLDER = "."
|
||||
|
||||
import argparse, os, re
|
||||
|
||||
print("Agetian's MTG Forge Deck AI Compatibility Analyzer v4.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")
|
||||
parser.add_argument("-l", action="store_true", help="log unsupported cards to ai_unsupported.log")
|
||||
parser.add_argument("-x", action="store_true", help="account for limited-playable cards from ai_limitedplayable.lst")
|
||||
|
||||
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
|
||||
|
||||
unplayable_cards = {}
|
||||
limited_playable_cards = []
|
||||
|
||||
# limited-playable
|
||||
if args.x:
|
||||
ff = open("ai_limitedplayable.lst").readlines()
|
||||
for line in ff:
|
||||
limited_playable_cards.extend([line.replace("\n","")])
|
||||
|
||||
# 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()
|
||||
break
|
||||
if cardname == "":
|
||||
cardname_literal = cardtext.replace('\r','').split('\n')[0].split(':')
|
||||
cardname = ":".join(cardname_literal[1:]).strip()
|
||||
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]])
|
||||
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]])
|
||||
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()
|
||||
lim_playable = False
|
||||
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')
|
||||
cardname = cardname.replace("AEther Mutation", "Aether Mutation")
|
||||
cardname = cardname.replace("AEther Membrane", "Aether Membrane")
|
||||
if cardlist[cardname] == 0:
|
||||
if limited_playable_cards.count(cardname) > 0:
|
||||
print("Found limited playable: " + cardname)
|
||||
lim_playable = True
|
||||
continue
|
||||
cardnames.extend([cardname])
|
||||
nonplayable_in_deck += 1
|
||||
if not cardname in unplayable_cards.keys():
|
||||
unplayable_cards[cardname] = 1
|
||||
else:
|
||||
unplayable_cards[cardname] = unplayable_cards[cardname] + 1
|
||||
if nonplayable_in_deck == 0:
|
||||
if not args.u:
|
||||
playable_decks += 1
|
||||
print("%s is PLAYABLE by the AI." % name)
|
||||
if lim_playable:
|
||||
os.rename(os.path.join(root, name), os.path.join(root, name.replace(".dck", " [!].dck").replace("[!] [!]", "[!]")))
|
||||
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))
|
||||
|
||||
if args.l:
|
||||
logfile = open("ai_unplayable.log", "w")
|
||||
sorted_dict = sorted(unplayable_cards, key=unplayable_cards.__getitem__)
|
||||
for k in sorted_dict:
|
||||
logfile.write(str(unplayable_cards[k]) + " times: " + k + "\n")
|
||||
logfile.close()
|
||||
97
forge-gui/tools/scryfallPricesGenerator.py
Executable file
97
forge-gui/tools/scryfallPricesGenerator.py
Executable file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json, os
|
||||
|
||||
# Note: currently only loads the first json file found in the folder!
|
||||
files = os.listdir(".")
|
||||
for filename in files:
|
||||
if filename.endswith(".json"):
|
||||
metadata_filename = filename
|
||||
break
|
||||
|
||||
print(f"Loading {metadata_filename}...")
|
||||
metadata_file = open(metadata_filename, "r")
|
||||
metadata = json.load(metadata_file)
|
||||
metadata_file.close()
|
||||
|
||||
prices = {}
|
||||
art_indexes = {}
|
||||
always_with_artindex = ["Plains", "Island", "Swamp", "Mountain", "Forest", "Wastes", "Snow-Covered Plains", "Snow-Covered Island", "Snow-Covered Swamp", "Snow-Covered Mountain", "Snow-Covered Forest", "Snow-Covered Wastes"]
|
||||
|
||||
for object in metadata:
|
||||
obj_type = object["object"]
|
||||
if obj_type == "card":
|
||||
card_name = object["name"]
|
||||
# split cards use //, other cards with two sides (e.g. DFC) use the front face in Forge
|
||||
if card_name.find("//") != -1 and object["layout"] != "split":
|
||||
card_name = card_name.split("//")[0].strip()
|
||||
card_set = object["set"].upper()
|
||||
card_cn = object["collector_number"]
|
||||
card_foil_only = object["foil"] and not object["nonfoil"]
|
||||
card_price = None
|
||||
if object["prices"]["usd"] == None and object["prices"]["usd_foil"] == None and object["prices"]["usd_etched"] == None and object["prices"]["eur"] == None and object["prices"]["eur_foil"] == None and object["prices"]["tix"] == None:
|
||||
continue
|
||||
if not card_foil_only and object["prices"]["usd"] != None:
|
||||
card_price = object["prices"]["usd"].replace(".", "")
|
||||
elif object["prices"]["usd_foil"] != None:
|
||||
card_price = object["prices"]["usd_foil"].replace(".", "")
|
||||
elif object["prices"]["usd_etched"] != None:
|
||||
card_price = object["prices"]["usd_etched"].replace(".", "")
|
||||
elif object["prices"]["eur"] != None:
|
||||
card_price = object["prices"]["eur"].replace(".", "")
|
||||
elif object["prices"]["eur_foil"] != None:
|
||||
card_price = object["prices"]["eur_foil"].replace(".", "")
|
||||
elif object["prices"]["tix"] != None:
|
||||
card_price = object["prices"]["tix"].replace(".", "")
|
||||
if card_price == None:
|
||||
continue
|
||||
elif card_price.startswith("00"):
|
||||
card_price = card_price[2:]
|
||||
elif card_price.startswith("0"):
|
||||
card_price = card_price[1:]
|
||||
# add a key to the prices dictionary, per set
|
||||
if not card_set in prices:
|
||||
prices[card_set] = {}
|
||||
if not card_set in art_indexes:
|
||||
art_indexes[card_set] = {}
|
||||
if card_name not in art_indexes[card_set]:
|
||||
art_indexes[card_set][card_name] = 1
|
||||
else:
|
||||
art_indexes[card_set][card_name] += 1
|
||||
if card_name in prices[card_set] or card_name in always_with_artindex:
|
||||
card_name += f" ({art_indexes[card_set][card_name]})"
|
||||
prices[card_set][card_name] = card_price
|
||||
|
||||
# Merge with the previous price list if appropriate
|
||||
if os.path.exists("all-prices.prev"):
|
||||
print()
|
||||
merge = input("Would you like to merge the prices with all-prices.prev? ")
|
||||
if merge.lower().startswith("y"):
|
||||
prev = open("all-prices.prev", "r")
|
||||
prev_data = prev.readlines()
|
||||
prev.close()
|
||||
for prev_price in prev_data:
|
||||
if prev_price.find("=") == -1:
|
||||
continue
|
||||
data = prev_price.split("=")
|
||||
old_price = data[1].strip()
|
||||
old_name = data[0].split("|")[0]
|
||||
if old_name.find("(") != -1: # unsafe to import a qualified name, we don't know how they'll match up
|
||||
continue
|
||||
old_set = data[0].split("|")[1]
|
||||
if old_set in prices.keys() and old_name not in prices[old_set]:
|
||||
prices[old_set][old_name] = old_price
|
||||
|
||||
# Generate the prices file
|
||||
output = open("all-prices.txt", "w")
|
||||
sorted_prices = {key: value for key, value in sorted(prices.items())}
|
||||
for set in sorted_prices.keys():
|
||||
sorted_cards = {key: value for key, value in sorted(prices[set].items())}
|
||||
for name in sorted_cards.keys():
|
||||
qualified_name = name
|
||||
if name.find("(") == -1 and name in art_indexes[set] and art_indexes[set][name] > 1:
|
||||
qualified_name += " (1)"
|
||||
print(qualified_name + "|" + set + "=" + sorted_cards[name])
|
||||
output.write(f"{qualified_name}|{set}={sorted_cards[name]}\n")
|
||||
output.close()
|
||||
Reference in New Issue
Block a user