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 .cards import Cards
|
||||
from .grid import Grid
|
||||
from .imageloader import ImageLoader
|
||||
from .utils import GRID, RESOLUTION, BOOK_YML_NAME
|
||||
|
||||
|
||||
class Book:
|
||||
def __init__(self, cards: Cards, grid: tuple[int, int], resolution: tuple[int, int], language: str,
|
||||
num_threads: int):
|
||||
def __init__(self, cards: Cards, language: str, num_threads: int):
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# transform grid into Grid
|
||||
grid = Grid(grid)
|
||||
|
||||
# sort cards by element, then alphabetically
|
||||
cards.sort(key=lambda x: x.name)
|
||||
cards.sort(key=lambda x: "Multi" if len(x.elements) > 1 else x.elements[0])
|
||||
|
@ -28,22 +24,22 @@ class Book:
|
|||
)
|
||||
|
||||
# multi-threaded download
|
||||
images = ImageLoader.load(urls, resolution, language, num_threads)
|
||||
images = ImageLoader.load(urls, language, num_threads)
|
||||
# card back Image
|
||||
back_image = images.pop(-1)
|
||||
|
||||
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
|
||||
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]}")
|
||||
|
||||
# paste card faces onto page
|
||||
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
|
||||
grid.paste(page_image, grid.capacity, back_image)
|
||||
GRID.paste(page_image, GRID.capacity, back_image)
|
||||
|
||||
# save page
|
||||
self.__pages.append({
|
||||
|
@ -51,15 +47,12 @@ class Book:
|
|||
"cards": page_cards,
|
||||
})
|
||||
|
||||
def __getitem__(self, index: int) -> Image.Image:
|
||||
return self.__pages[index]["image"]
|
||||
|
||||
def save(self, file_name: str, book_yml_name: str) -> None:
|
||||
def save(self, file_name: str) -> None:
|
||||
book: dict[str, dict[str, any]]
|
||||
|
||||
# load book.yml file
|
||||
try:
|
||||
with open(book_yml_name, "r") as file:
|
||||
with open(BOOK_YML_NAME, "r") as file:
|
||||
book = yaml.load(file, Loader=yaml.Loader)
|
||||
except FileNotFoundError:
|
||||
book = {}
|
||||
|
@ -73,5 +66,5 @@ class Book:
|
|||
book[fn] = {"cards": page["cards"]}
|
||||
|
||||
# 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)
|
||||
|
|
|
@ -1,33 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
import yaml
|
||||
|
||||
from .code import Code
|
||||
|
||||
|
||||
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)
|
||||
from .utils import encircle_symbol
|
||||
|
||||
|
||||
class Card(yaml.YAMLObject):
|
||||
|
@ -53,7 +32,7 @@ class Card(yaml.YAMLObject):
|
|||
self.__text = text
|
||||
|
||||
@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:
|
||||
return cls(
|
||||
code=Code(""),
|
||||
|
|
|
@ -1,45 +1,60 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import yaml
|
||||
|
||||
_DictOfDicts = dict[str, dict[str, any]]
|
||||
from fftcg.code import Code
|
||||
from fftcg.utils import BOOK_YML_NAME
|
||||
|
||||
|
||||
class CardDB(_DictOfDicts):
|
||||
def __init__(self, book_yml_name: str):
|
||||
book: _DictOfDicts
|
||||
class CardDB:
|
||||
__instance: CardDB = None
|
||||
|
||||
@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
|
||||
book: dict
|
||||
try:
|
||||
with open(book_yml_name, "r") as file:
|
||||
with open(BOOK_YML_NAME, "r") as file:
|
||||
book = yaml.load(file, Loader=yaml.Loader)
|
||||
except FileNotFoundError:
|
||||
book = {}
|
||||
|
||||
# "invert" book into card database:
|
||||
# every card is indexable by its code
|
||||
carddb: _DictOfDicts = {}
|
||||
self.__content.clear()
|
||||
|
||||
for fn, content in book.items():
|
||||
carddb |= {
|
||||
for file_name, content in book.items():
|
||||
self.__content |= {
|
||||
str(card.code): {
|
||||
"card": card,
|
||||
"file": fn,
|
||||
"file": file_name,
|
||||
"index": i,
|
||||
} for i, card in enumerate(content["cards"])
|
||||
}
|
||||
|
||||
super().__init__(carddb)
|
||||
|
||||
# write carddb.yml 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):
|
||||
# filter codes by card criteria
|
||||
codes = [
|
||||
content["card"].code
|
||||
for content in self.values()
|
||||
if all([f(content["card"]) for f in filters])
|
||||
]
|
||||
|
||||
from .ttsdeck import TTSDeck
|
||||
return TTSDeck(codes, self)
|
||||
# def make_deck(self, filters):
|
||||
# # filter codes by card criteria
|
||||
# codes = [
|
||||
# content["card"].code
|
||||
# for content in self.__content.values()
|
||||
# if all([f(content["card"]) for f in filters])
|
||||
# ]
|
||||
#
|
||||
# from .ttsdeck import TTSDeck
|
||||
# return TTSDeck(codes)
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from PIL import Image
|
||||
|
||||
_Point = tuple[int, int]
|
||||
|
||||
|
||||
class Grid(_Point):
|
||||
def __mul__(self, other: _Point) -> _Point:
|
||||
other = Grid(other)
|
||||
return self.x * other.x, self.y * other.y
|
||||
class Grid(tuple[int, int]):
|
||||
def __mul__(self, other: Grid) -> Grid:
|
||||
return Grid((self.x * other.x, self.y * other.y))
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
|
|
|
@ -6,6 +6,8 @@ import threading
|
|||
import requests
|
||||
from PIL import Image
|
||||
|
||||
from fftcg.utils import RESOLUTION
|
||||
|
||||
|
||||
class ImageLoader(threading.Thread):
|
||||
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()
|
||||
|
||||
@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()
|
||||
for url in urls:
|
||||
url_queue.put(url)
|
||||
|
||||
loaders = []
|
||||
for _ in range(num_threads):
|
||||
loader = cls(url_queue, resolution, language)
|
||||
loader = cls(url_queue, RESOLUTION, language)
|
||||
loaders.append(loader)
|
||||
loader.start()
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import logging
|
|||
import roman
|
||||
|
||||
from .cards import Cards
|
||||
from .ttsdeck import TTSDeck
|
||||
|
||||
|
||||
class Opus(Cards):
|
||||
|
@ -60,3 +61,32 @@ class Opus(Cards):
|
|||
@property
|
||||
def filename(self) -> str:
|
||||
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]]):
|
||||
def __init__(self, codes: list[Code], carddb: CardDB):
|
||||
super().__init__([carddb[str(code)] for code in codes])
|
||||
def __init__(self, codes: list[Code]):
|
||||
carddb = CardDB.get()
|
||||
super().__init__([carddb[code] for code in codes])
|
||||
|
||||
def __str__(self) -> str:
|
||||
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
|
||||
|
||||
# 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:
|
||||
# set up CLI
|
||||
|
@ -45,40 +40,15 @@ def main() -> None:
|
|||
|
||||
# main program
|
||||
opus = fftcg.Opus(args.opus_id)
|
||||
book = fftcg.Book(opus, GRID, RESOLUTION, "eg", args.num_threads)
|
||||
book.save(opus.filename, BOOK_YML_NAME)
|
||||
book = fftcg.Book(opus, "eg", args.num_threads)
|
||||
book.save(opus.filename)
|
||||
|
||||
# create elemental decks for opus
|
||||
carddb = fftcg.CardDB(BOOK_YML_NAME)
|
||||
carddb = fftcg.CardDB.get()
|
||||
carddb.load()
|
||||
|
||||
def opus_filter(card: fftcg.Card):
|
||||
return card.code.opus == opus.number
|
||||
|
||||
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))
|
||||
for deck in opus.elemental_decks:
|
||||
print(deck)
|
||||
|
||||
# bye
|
||||
logging.info("Done. Put the generated JSON files in your 'Saved Objects' Folder.")
|
||||
|
|
Loading…
Reference in a new issue