mirror of
https://github.com/ldericher/fftcgtool
synced 2025-01-15 15:02:59 +00:00
major cleanup
This commit is contained in:
parent
730775a660
commit
8360eb69b2
9 changed files with 130 additions and 108 deletions
|
@ -4,18 +4,14 @@ import yaml
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .cards import Cards
|
from .cards import Cards
|
||||||
from .grid import Grid
|
|
||||||
from .imageloader import ImageLoader
|
from .imageloader import ImageLoader
|
||||||
|
from .utils import GRID, RESOLUTION, BOOK_YML_NAME
|
||||||
|
|
||||||
|
|
||||||
class Book:
|
class Book:
|
||||||
def __init__(self, cards: Cards, grid: tuple[int, int], resolution: tuple[int, int], language: str,
|
def __init__(self, cards: Cards, language: str, num_threads: int):
|
||||||
num_threads: int):
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# transform grid into Grid
|
|
||||||
grid = Grid(grid)
|
|
||||||
|
|
||||||
# sort cards by element, then alphabetically
|
# sort cards by element, then alphabetically
|
||||||
cards.sort(key=lambda x: x.name)
|
cards.sort(key=lambda x: x.name)
|
||||||
cards.sort(key=lambda x: "Multi" if len(x.elements) > 1 else x.elements[0])
|
cards.sort(key=lambda x: "Multi" if len(x.elements) > 1 else x.elements[0])
|
||||||
|
@ -28,22 +24,22 @@ class Book:
|
||||||
)
|
)
|
||||||
|
|
||||||
# multi-threaded download
|
# multi-threaded download
|
||||||
images = ImageLoader.load(urls, resolution, language, num_threads)
|
images = ImageLoader.load(urls, language, num_threads)
|
||||||
# card back Image
|
# card back Image
|
||||||
back_image = images.pop(-1)
|
back_image = images.pop(-1)
|
||||||
|
|
||||||
self.__pages = []
|
self.__pages = []
|
||||||
for page_images, page_cards in zip(grid.chunks(images), grid.chunks(cards)):
|
for page_images, page_cards in zip(GRID.chunks(images), GRID.chunks(cards)):
|
||||||
# create book page Image
|
# create book page Image
|
||||||
page_image = Image.new("RGB", grid * resolution)
|
page_image = Image.new("RGB", GRID * RESOLUTION)
|
||||||
logger.info(f"New image: {page_image.size[0]}x{page_image.size[1]}")
|
logger.info(f"New image: {page_image.size[0]}x{page_image.size[1]}")
|
||||||
|
|
||||||
# paste card faces onto page
|
# paste card faces onto page
|
||||||
for i, image in enumerate(page_images):
|
for i, image in enumerate(page_images):
|
||||||
grid.paste(page_image, i, image)
|
GRID.paste(page_image, i, image)
|
||||||
|
|
||||||
# paste card back in last position
|
# paste card back in last position
|
||||||
grid.paste(page_image, grid.capacity, back_image)
|
GRID.paste(page_image, GRID.capacity, back_image)
|
||||||
|
|
||||||
# save page
|
# save page
|
||||||
self.__pages.append({
|
self.__pages.append({
|
||||||
|
@ -51,15 +47,12 @@ class Book:
|
||||||
"cards": page_cards,
|
"cards": page_cards,
|
||||||
})
|
})
|
||||||
|
|
||||||
def __getitem__(self, index: int) -> Image.Image:
|
def save(self, file_name: str) -> None:
|
||||||
return self.__pages[index]["image"]
|
|
||||||
|
|
||||||
def save(self, file_name: str, book_yml_name: str) -> None:
|
|
||||||
book: dict[str, dict[str, any]]
|
book: dict[str, dict[str, any]]
|
||||||
|
|
||||||
# load book.yml file
|
# load book.yml file
|
||||||
try:
|
try:
|
||||||
with open(book_yml_name, "r") as file:
|
with open(BOOK_YML_NAME, "r") as file:
|
||||||
book = yaml.load(file, Loader=yaml.Loader)
|
book = yaml.load(file, Loader=yaml.Loader)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
book = {}
|
book = {}
|
||||||
|
@ -73,5 +66,5 @@ class Book:
|
||||||
book[fn] = {"cards": page["cards"]}
|
book[fn] = {"cards": page["cards"]}
|
||||||
|
|
||||||
# update book.yml file
|
# update book.yml file
|
||||||
with open(book_yml_name, "w") as file:
|
with open(BOOK_YML_NAME, "w") as file:
|
||||||
yaml.dump(book, file, Dumper=yaml.Dumper)
|
yaml.dump(book, file, Dumper=yaml.Dumper)
|
||||||
|
|
|
@ -1,33 +1,12 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from .code import Code
|
from .code import Code
|
||||||
|
from .utils import encircle_symbol
|
||||||
|
|
||||||
def encircle_symbol(symbol: str, negative: bool):
|
|
||||||
symbol = symbol[0].upper()
|
|
||||||
|
|
||||||
base_symbols: tuple[str, str] = "", ""
|
|
||||||
if symbol.isalpha():
|
|
||||||
if negative:
|
|
||||||
base_symbols = "A", "🅐"
|
|
||||||
else:
|
|
||||||
base_symbols = "A", "Ⓐ"
|
|
||||||
elif symbol == "0":
|
|
||||||
if negative:
|
|
||||||
base_symbols = "0", "🄌"
|
|
||||||
else:
|
|
||||||
base_symbols = "0", "⓪"
|
|
||||||
elif symbol.isnumeric():
|
|
||||||
if negative:
|
|
||||||
base_symbols = "1", "➊"
|
|
||||||
else:
|
|
||||||
base_symbols = "1", "①"
|
|
||||||
|
|
||||||
symbol_num = ord(symbol) - ord(base_symbols[0])
|
|
||||||
return chr(ord(base_symbols[1]) + symbol_num)
|
|
||||||
|
|
||||||
|
|
||||||
class Card(yaml.YAMLObject):
|
class Card(yaml.YAMLObject):
|
||||||
|
@ -53,7 +32,7 @@ class Card(yaml.YAMLObject):
|
||||||
self.__text = text
|
self.__text = text
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_data(cls, data: dict[str, any], language: str):
|
def from_data(cls, data: dict[str, any], language: str) -> Card:
|
||||||
if not data:
|
if not data:
|
||||||
return cls(
|
return cls(
|
||||||
code=Code(""),
|
code=Code(""),
|
||||||
|
|
|
@ -1,45 +1,60 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
_DictOfDicts = dict[str, dict[str, any]]
|
from fftcg.code import Code
|
||||||
|
from fftcg.utils import BOOK_YML_NAME
|
||||||
|
|
||||||
|
|
||||||
class CardDB(_DictOfDicts):
|
class CardDB:
|
||||||
def __init__(self, book_yml_name: str):
|
__instance: CardDB = None
|
||||||
book: _DictOfDicts
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls) -> CardDB:
|
||||||
|
if not CardDB.__instance:
|
||||||
|
CardDB.__instance = CardDB()
|
||||||
|
|
||||||
|
return CardDB.__instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__content: dict[Code, dict[str, any]] = {}
|
||||||
|
|
||||||
|
def __getitem__(self, code: Code) -> dict[str, any]:
|
||||||
|
return self.__content[str(code)]
|
||||||
|
|
||||||
|
def load(self):
|
||||||
# load book.yml file
|
# load book.yml file
|
||||||
|
book: dict
|
||||||
try:
|
try:
|
||||||
with open(book_yml_name, "r") as file:
|
with open(BOOK_YML_NAME, "r") as file:
|
||||||
book = yaml.load(file, Loader=yaml.Loader)
|
book = yaml.load(file, Loader=yaml.Loader)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
book = {}
|
book = {}
|
||||||
|
|
||||||
# "invert" book into card database:
|
# "invert" book into card database:
|
||||||
# every card is indexable by its code
|
# every card is indexable by its code
|
||||||
carddb: _DictOfDicts = {}
|
self.__content.clear()
|
||||||
|
|
||||||
for fn, content in book.items():
|
for file_name, content in book.items():
|
||||||
carddb |= {
|
self.__content |= {
|
||||||
str(card.code): {
|
str(card.code): {
|
||||||
"card": card,
|
"card": card,
|
||||||
"file": fn,
|
"file": file_name,
|
||||||
"index": i,
|
"index": i,
|
||||||
} for i, card in enumerate(content["cards"])
|
} for i, card in enumerate(content["cards"])
|
||||||
}
|
}
|
||||||
|
|
||||||
super().__init__(carddb)
|
|
||||||
|
|
||||||
# write carddb.yml file
|
# write carddb.yml file
|
||||||
with open("carddb.yml", "w") as file:
|
with open("carddb.yml", "w") as file:
|
||||||
yaml.dump(self, file, Dumper=yaml.Dumper)
|
yaml.dump(self.__content, file, Dumper=yaml.Dumper)
|
||||||
|
|
||||||
def make_deck(self, filters):
|
# def make_deck(self, filters):
|
||||||
# filter codes by card criteria
|
# # filter codes by card criteria
|
||||||
codes = [
|
# codes = [
|
||||||
content["card"].code
|
# content["card"].code
|
||||||
for content in self.values()
|
# for content in self.__content.values()
|
||||||
if all([f(content["card"]) for f in filters])
|
# if all([f(content["card"]) for f in filters])
|
||||||
]
|
# ]
|
||||||
|
#
|
||||||
from .ttsdeck import TTSDeck
|
# from .ttsdeck import TTSDeck
|
||||||
return TTSDeck(codes, self)
|
# return TTSDeck(codes)
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
_Point = tuple[int, int]
|
_Point = tuple[int, int]
|
||||||
|
|
||||||
|
|
||||||
class Grid(_Point):
|
class Grid(tuple[int, int]):
|
||||||
def __mul__(self, other: _Point) -> _Point:
|
def __mul__(self, other: Grid) -> Grid:
|
||||||
other = Grid(other)
|
return Grid((self.x * other.x, self.y * other.y))
|
||||||
return self.x * other.x, self.y * other.y
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def x(self):
|
def x(self):
|
||||||
|
|
|
@ -6,6 +6,8 @@ import threading
|
||||||
import requests
|
import requests
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
from fftcg.utils import RESOLUTION
|
||||||
|
|
||||||
|
|
||||||
class ImageLoader(threading.Thread):
|
class ImageLoader(threading.Thread):
|
||||||
def __init__(self, url_queue: queue.Queue, resolution: tuple[int, int], language: str):
|
def __init__(self, url_queue: queue.Queue, resolution: tuple[int, int], language: str):
|
||||||
|
@ -44,14 +46,14 @@ class ImageLoader(threading.Thread):
|
||||||
self.__queue.task_done()
|
self.__queue.task_done()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, urls: list[str], resolution: tuple[int, int], language: str, num_threads: int) -> list[Image.Image]:
|
def load(cls, urls: list[str], language: str, num_threads: int) -> list[Image.Image]:
|
||||||
url_queue = queue.Queue()
|
url_queue = queue.Queue()
|
||||||
for url in urls:
|
for url in urls:
|
||||||
url_queue.put(url)
|
url_queue.put(url)
|
||||||
|
|
||||||
loaders = []
|
loaders = []
|
||||||
for _ in range(num_threads):
|
for _ in range(num_threads):
|
||||||
loader = cls(url_queue, resolution, language)
|
loader = cls(url_queue, RESOLUTION, language)
|
||||||
loaders.append(loader)
|
loaders.append(loader)
|
||||||
loader.start()
|
loader.start()
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import logging
|
||||||
import roman
|
import roman
|
||||||
|
|
||||||
from .cards import Cards
|
from .cards import Cards
|
||||||
|
from .ttsdeck import TTSDeck
|
||||||
|
|
||||||
|
|
||||||
class Opus(Cards):
|
class Opus(Cards):
|
||||||
|
@ -60,3 +61,32 @@ class Opus(Cards):
|
||||||
@property
|
@property
|
||||||
def filename(self) -> str:
|
def filename(self) -> str:
|
||||||
return self.__filename
|
return self.__filename
|
||||||
|
|
||||||
|
@property
|
||||||
|
def elemental_decks(self) -> list[TTSDeck]:
|
||||||
|
if self.name in ["Promo", "Boss Deck Chaos"]:
|
||||||
|
return [TTSDeck([
|
||||||
|
card.code
|
||||||
|
for card in self
|
||||||
|
])]
|
||||||
|
|
||||||
|
else:
|
||||||
|
def element_filter(element: str):
|
||||||
|
return lambda card: card.elements == [element]
|
||||||
|
|
||||||
|
# simple cases: create lambdas for base elemental decks
|
||||||
|
base_elements = ["Fire", "Ice", "Wind", "Earth", "Lightning", "Water"]
|
||||||
|
filters = [element_filter(elem) for elem in base_elements]
|
||||||
|
|
||||||
|
filters += [
|
||||||
|
# light/darkness elemental deck
|
||||||
|
lambda card: card.elements == ["Light"] or card.elements == ["Darkness"],
|
||||||
|
# multi element deck
|
||||||
|
lambda card: len(card.elements) > 1,
|
||||||
|
]
|
||||||
|
|
||||||
|
return [TTSDeck([
|
||||||
|
card.code
|
||||||
|
for card in self
|
||||||
|
if f(card)
|
||||||
|
]) for f in filters]
|
||||||
|
|
|
@ -5,8 +5,9 @@ from .code import Code
|
||||||
|
|
||||||
|
|
||||||
class TTSDeck(list[dict[str, any]]):
|
class TTSDeck(list[dict[str, any]]):
|
||||||
def __init__(self, codes: list[Code], carddb: CardDB):
|
def __init__(self, codes: list[Code]):
|
||||||
super().__init__([carddb[str(code)] for code in codes])
|
carddb = CardDB.get()
|
||||||
|
super().__init__([carddb[code] for code in codes])
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
face_urls = list(set([entry["file"] for entry in self]))
|
face_urls = list(set([entry["file"] for entry in self]))
|
||||||
|
|
31
fftcg/utils.py
Normal file
31
fftcg/utils.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
from .grid import Grid
|
||||||
|
|
||||||
|
# constants
|
||||||
|
GRID = Grid((10, 7)) # default in TTsim: 10 columns, 7 rows
|
||||||
|
RESOLUTION = Grid((429, 600)) # default in TTsim: 480x670 pixels per card
|
||||||
|
BOOK_YML_NAME = "book.yml"
|
||||||
|
|
||||||
|
|
||||||
|
# functions
|
||||||
|
def encircle_symbol(symbol: str, negative: bool):
|
||||||
|
symbol = symbol[0].upper()
|
||||||
|
|
||||||
|
base_symbols: tuple[str, str] = "", ""
|
||||||
|
if symbol.isalpha():
|
||||||
|
if negative:
|
||||||
|
base_symbols = "A", "🅐"
|
||||||
|
else:
|
||||||
|
base_symbols = "A", "Ⓐ"
|
||||||
|
elif symbol == "0":
|
||||||
|
if negative:
|
||||||
|
base_symbols = "0", "🄌"
|
||||||
|
else:
|
||||||
|
base_symbols = "0", "⓪"
|
||||||
|
elif symbol.isnumeric():
|
||||||
|
if negative:
|
||||||
|
base_symbols = "1", "➊"
|
||||||
|
else:
|
||||||
|
base_symbols = "1", "①"
|
||||||
|
|
||||||
|
symbol_num = ord(symbol) - ord(base_symbols[0])
|
||||||
|
return chr(ord(base_symbols[1]) + symbol_num)
|
42
main.py
42
main.py
|
@ -5,11 +5,6 @@ import os
|
||||||
|
|
||||||
import fftcg
|
import fftcg
|
||||||
|
|
||||||
# constants
|
|
||||||
GRID = 10, 7 # default in TTsim: 10 columns, 7 rows
|
|
||||||
RESOLUTION = 429, 600 # default in TTsim: 480x670 pixels per card
|
|
||||||
BOOK_YML_NAME = "book.yml"
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
# set up CLI
|
# set up CLI
|
||||||
|
@ -45,40 +40,15 @@ def main() -> None:
|
||||||
|
|
||||||
# main program
|
# main program
|
||||||
opus = fftcg.Opus(args.opus_id)
|
opus = fftcg.Opus(args.opus_id)
|
||||||
book = fftcg.Book(opus, GRID, RESOLUTION, "eg", args.num_threads)
|
book = fftcg.Book(opus, "eg", args.num_threads)
|
||||||
book.save(opus.filename, BOOK_YML_NAME)
|
book.save(opus.filename)
|
||||||
|
|
||||||
# create elemental decks for opus
|
# create elemental decks for opus
|
||||||
carddb = fftcg.CardDB(BOOK_YML_NAME)
|
carddb = fftcg.CardDB.get()
|
||||||
|
carddb.load()
|
||||||
|
|
||||||
def opus_filter(card: fftcg.Card):
|
for deck in opus.elemental_decks:
|
||||||
return card.code.opus == opus.number
|
print(deck)
|
||||||
|
|
||||||
filters: list
|
|
||||||
if opus.number == "PR":
|
|
||||||
filters = [[opus_filter]]
|
|
||||||
|
|
||||||
else:
|
|
||||||
def element_filter(element: str):
|
|
||||||
return lambda card: card.elements == [element]
|
|
||||||
|
|
||||||
# simple cases: create lambdas for base elemental decks
|
|
||||||
base_elements = ["Fire", "Ice", "Wind", "Earth", "Lightning", "Water"]
|
|
||||||
element_filters = [element_filter(elem) for elem in base_elements]
|
|
||||||
|
|
||||||
element_filters += [
|
|
||||||
# light/darkness elemental deck
|
|
||||||
lambda card: card.elements == ["Light"] or card.elements == ["Darkness"],
|
|
||||||
# multi element deck
|
|
||||||
lambda card: len(card.elements) > 1,
|
|
||||||
]
|
|
||||||
|
|
||||||
# add in the opus_filter for all elemental decks
|
|
||||||
filters = list(zip([opus_filter] * len(element_filters), element_filters))
|
|
||||||
|
|
||||||
# make the decks
|
|
||||||
for f in filters:
|
|
||||||
print(carddb.make_deck(f))
|
|
||||||
|
|
||||||
# bye
|
# bye
|
||||||
logging.info("Done. Put the generated JSON files in your 'Saved Objects' Folder.")
|
logging.info("Done. Put the generated JSON files in your 'Saved Objects' Folder.")
|
||||||
|
|
Loading…
Reference in a new issue