594 lines
21 KiB
Python
594 lines
21 KiB
Python
import logging
|
|
from math import sqrt, cbrt
|
|
from enum import Enum
|
|
from typing import NamedTuple
|
|
from urllib.parse import quote
|
|
from datetime import datetime, timedelta
|
|
from time import sleep
|
|
|
|
from config import WIKIRANK_USER_AGENT
|
|
HEADERS = {"User-Agent": WIKIRANK_USER_AGENT}
|
|
|
|
logger = logging.getLogger("app")
|
|
|
|
class CardType(Enum):
|
|
other = 0
|
|
person = 1
|
|
location = 2
|
|
artwork = 3
|
|
life_form = 4
|
|
event = 5
|
|
group = 6
|
|
science_thing = 7
|
|
vehicle = 8
|
|
organization = 9
|
|
|
|
class CardRarity(Enum):
|
|
common = 0
|
|
uncommon = 1
|
|
rare = 2
|
|
super_rare = 3
|
|
epic = 4
|
|
legendary = 5
|
|
|
|
class Card(NamedTuple):
|
|
name: str
|
|
created_at: datetime
|
|
image_link: str
|
|
card_rarity: CardRarity
|
|
card_type: CardType
|
|
wikidata_instance: str
|
|
text: str
|
|
attack: int
|
|
defense: int
|
|
cost: int
|
|
|
|
def __str__(self) -> str:
|
|
return_string = "┏"+"━"*50+"┓\n"
|
|
return_string += "┃"+f"{self.name:{' '}<50}"+"┃\n"
|
|
return_string += "┠"+"─"*50+"┨\n"
|
|
card_type = "Type: "+self.card_type.name
|
|
if self.card_type == CardType.other:
|
|
card_type += f" ({self.wikidata_instance})"
|
|
return_string += "┃"+f"{card_type:{' '}<50}"+"┃\n"
|
|
return_string += "┠"+"─"*50+"┨\n"
|
|
rarity_text = f"Rarity: {self.card_rarity.name}"
|
|
return_string += "┃"+f"{rarity_text:<50}"+"┃\n"
|
|
return_string += "┠"+"─"*50+"┨\n"
|
|
link = "Image: "+self.image_link
|
|
return_string += "┃"+f"{link:{' '}<50}"+"┃\n"
|
|
return_string += "┠"+"─"*50+"┨\n"
|
|
return_string += "┃"+f"{'*'*self.cost:{' '}<50}"+"┃\n"
|
|
return_string += "┠"+"─"*50+"┨\n"
|
|
lines = []
|
|
words = self.text.split(" ")
|
|
current_line = ""
|
|
for w in words:
|
|
if len(current_line + " " + w) < 50:
|
|
current_line += " " + w
|
|
elif len(current_line) < 40 and len(w) > 10:
|
|
new_len = 48 - len(current_line)
|
|
current_line += " " + w[:new_len] + "-"
|
|
lines.append(current_line)
|
|
current_line = w[new_len:]
|
|
else:
|
|
lines.append(current_line)
|
|
current_line = w
|
|
|
|
lines.append(current_line)
|
|
|
|
for l in lines :
|
|
return_string += "┃"+f"{l:{' '}<50}"+"┃\n"
|
|
return_string += "┠"+"─"*50+"┨\n"
|
|
|
|
date_text = str(self.created_at.date())
|
|
stats = f"{self.attack}/{self.defense}"
|
|
spaces = 50 - (len(date_text) + len(stats))
|
|
return_string += "┃"+date_text + " "*spaces + stats + "┃\n"
|
|
return_string += "┗"+"━"*50+"┛"
|
|
return return_string
|
|
|
|
WIKIDATA_INSTANCE_TYPE_MAP = {
|
|
"Q5": CardType.person, # human
|
|
"Q215627": CardType.person, # person
|
|
"Q15632617": CardType.person, # fictional human
|
|
"Q22988604": CardType.person, # fictional human
|
|
|
|
"Q1004": CardType.artwork, # comics
|
|
"Q7889": CardType.artwork, # video game
|
|
"Q11424": CardType.artwork, # film
|
|
"Q11410": CardType.artwork, # game
|
|
"Q15416": CardType.artwork, # television program
|
|
"Q24862": CardType.artwork, # short film
|
|
"Q11032": CardType.artwork, # newspaper
|
|
"Q25379": CardType.artwork, # play
|
|
"Q41298": CardType.artwork, # magazine
|
|
"Q482994": CardType.artwork, # album
|
|
"Q134556": CardType.artwork, # single
|
|
"Q169930": CardType.artwork, # EP
|
|
"Q196600": CardType.artwork, # media franchise
|
|
"Q202866": CardType.artwork, # animated film
|
|
"Q277759": CardType.artwork, # book series
|
|
"Q734698": CardType.artwork, # collectible card game
|
|
"Q506240": CardType.artwork, # television film
|
|
"Q738377": CardType.artwork, # student newspaper
|
|
"Q1259759": CardType.artwork, # miniseries
|
|
"Q3305213": CardType.artwork, # painting
|
|
"Q3177859": CardType.artwork, # dedicated deck card game
|
|
"Q5398426": CardType.artwork, # television series
|
|
"Q7725634": CardType.artwork, # literary work
|
|
"Q1761818": CardType.artwork, # advertising campaign
|
|
"Q1446621": CardType.artwork, # recital
|
|
"Q1868552": CardType.artwork, # local newspaper
|
|
"Q63952888": CardType.artwork, # anime television series
|
|
"Q47461344": CardType.artwork, # written work
|
|
"Q71631512": CardType.artwork, # tabletop role-playing game supplement
|
|
"Q21198342": CardType.artwork, # manga series
|
|
"Q58483083": CardType.artwork, # dramatico-musical work
|
|
"Q24634210": CardType.artwork, # podcast show
|
|
"Q105543609": CardType.artwork, # musical work / composition
|
|
"Q106499608": CardType.artwork, # literary reading
|
|
"Q117467246": CardType.artwork, # animated television series
|
|
"Q106042566": CardType.artwork, # single album
|
|
|
|
"Q515": CardType.location, # city
|
|
"Q8502": CardType.location, # mountain
|
|
"Q4022": CardType.location, # river
|
|
"Q6256": CardType.location, # country
|
|
"Q15284": CardType.location, # municipality
|
|
"Q27686": CardType.location, # hotel
|
|
"Q41176": CardType.location, # building
|
|
"Q23442": CardType.location, # island
|
|
"Q82794": CardType.location, # geographic region
|
|
"Q34442": CardType.location, # road
|
|
"Q398141": CardType.location, # school district
|
|
"Q133056": CardType.location, # mountain pass
|
|
"Q3624078": CardType.location, # sovereign state
|
|
"Q1093829": CardType.location, # city in the United States
|
|
"Q7930989": CardType.location, # city/town
|
|
"Q1250464": CardType.location, # realm
|
|
"Q3146899": CardType.location, # diocese of the catholic church
|
|
"Q35145263": CardType.location, # natural geographic object
|
|
"Q15642541": CardType.location, # human-geographic territorial entity
|
|
|
|
"Q16521": CardType.life_form, # taxon
|
|
"Q310890": CardType.life_form, # monotypic taxon
|
|
"Q23038290": CardType.life_form, # fossil taxon
|
|
"Q12045585": CardType.life_form, # cattle breed
|
|
|
|
"Q198": CardType.event, # war
|
|
"Q8465": CardType.event, # civil war
|
|
"Q141022": CardType.event, # eclipse
|
|
"Q103495": CardType.event, # world war
|
|
"Q350604": CardType.event, # armed conflict
|
|
"Q217327": CardType.event, # suicide attack
|
|
"Q750215": CardType.event, # mass murder
|
|
"Q898712": CardType.event, # aircraft hijacking
|
|
"Q997267": CardType.event, # skirmish
|
|
"Q178561": CardType.event, # battle
|
|
"Q273120": CardType.event, # protest
|
|
"Q1190554": CardType.event, # occurrence
|
|
"Q1361229": CardType.event, # conquest
|
|
"Q2223653": CardType.event, # terrorist attack
|
|
"Q2672648": CardType.event, # social conflict
|
|
"Q16510064": CardType.event, # sporting event
|
|
"Q10688145": CardType.event, # season
|
|
"Q13418847": CardType.event, # historical event
|
|
"Q13406554": CardType.event, # sports competition
|
|
"Q15275719": CardType.event, # recurring event
|
|
"Q114609228": CardType.event, # recurring sporting event
|
|
|
|
"Q7278": CardType.group, # political party
|
|
"Q476028": CardType.group, # association football club
|
|
"Q732717": CardType.group, # law enforcement agency
|
|
"Q215380": CardType.group, # musical group
|
|
"Q176799": CardType.group, # military unit
|
|
"Q178790": CardType.group, # labor union
|
|
"Q851990": CardType.group, # people group
|
|
"Q2367225": CardType.group, # university and college sports club
|
|
"Q4801149": CardType.group, # artillery brigade
|
|
"Q9248092": CardType.group, # infantry division
|
|
"Q7210356": CardType.group, # political organization
|
|
"Q5419137": CardType.group, # veterans' organization
|
|
"Q6979593": CardType.group, # national association football team
|
|
"Q1194951": CardType.group, # national sports team
|
|
"Q1539532": CardType.group, # sports season of a sports club
|
|
"Q13393265": CardType.group, # basketball team
|
|
"Q17148672": CardType.group, # social club
|
|
"Q12973014": CardType.group, # sports team
|
|
"Q11446438": CardType.group, # female idol group
|
|
"Q10517054": CardType.group, # handball team
|
|
"Q135408445": CardType.group, # men's national association football team
|
|
"Q120143756": CardType.group, # division
|
|
"Q134601727": CardType.group, # group of persons
|
|
"Q127334927": CardType.group, # band
|
|
|
|
"Q523": CardType.science_thing, # star
|
|
"Q318": CardType.science_thing, # galaxy
|
|
"Q6999": CardType.science_thing, # astronomical object
|
|
"Q7187": CardType.science_thing, # gene
|
|
"Q8054": CardType.science_thing, # protein
|
|
"Q12136": CardType.science_thing, # disease
|
|
"Q65943": CardType.science_thing, # theorem
|
|
"Q12140": CardType.science_thing, # medication
|
|
"Q11276": CardType.science_thing, # globular cluster
|
|
"Q83373": CardType.science_thing, # quasar
|
|
"Q898273": CardType.science_thing, # protein domain
|
|
"Q134808": CardType.science_thing, # vaccine
|
|
"Q168845": CardType.science_thing, # star cluster
|
|
"Q1491746": CardType.science_thing, # galaxy group
|
|
"Q1341811": CardType.science_thing, # astronomical maser
|
|
"Q1840368": CardType.science_thing, # cloud type
|
|
"Q2154519": CardType.science_thing, # astrophysical x-ray source
|
|
"Q17444909": CardType.science_thing, # astronomical object type
|
|
"Q12089225": CardType.science_thing, # Mineral species
|
|
"Q113145171": CardType.science_thing, # type of chemical entity
|
|
|
|
"Q1420": CardType.vehicle, # car
|
|
"Q11446": CardType.vehicle, # ship
|
|
"Q43193": CardType.vehicle, # truck
|
|
"Q25956": CardType.vehicle, # space station
|
|
"Q39804": CardType.vehicle, # cruise ship
|
|
"Q170483": CardType.vehicle, # sailing ship
|
|
"Q964162": CardType.vehicle, # express train
|
|
"Q848944": CardType.vehicle, # merchant vessel
|
|
"Q189418": CardType.vehicle, # brigantine
|
|
"Q281019": CardType.vehicle, # ghost ship
|
|
"Q811704": CardType.vehicle, # rolling stock class
|
|
"Q673687": CardType.vehicle, # racing automobile
|
|
"Q174736": CardType.vehicle, # destroyer
|
|
"Q484000": CardType.vehicle, # unmanned aerial vehicle
|
|
"Q559026": CardType.vehicle, # ship class
|
|
"Q830335": CardType.vehicle, # protected cruiser
|
|
"Q928235": CardType.vehicle, # sloop-of-war
|
|
"Q391022": CardType.vehicle, # research vessel
|
|
"Q202527": CardType.vehicle, # minesweeper
|
|
"Q1185562": CardType.vehicle, # light aircraft carrier
|
|
"Q7233751": CardType.vehicle, # post ship
|
|
"Q3231690": CardType.vehicle, # automobile model
|
|
"Q1428357": CardType.vehicle, # submarine class
|
|
"Q1499623": CardType.vehicle, # destroyer escort
|
|
"Q4818021": CardType.vehicle, # attack submarine
|
|
"Q19832486": CardType.vehicle, # locomotive class
|
|
"Q23866334": CardType.vehicle, # motorcycle model
|
|
"Q29048322": CardType.vehicle, # vehicle model
|
|
"Q137188246": CardType.vehicle, # combat vehicle model
|
|
"Q100709275": CardType.vehicle, # combat vehicle family
|
|
|
|
"Q43229": CardType.organization, # organization
|
|
"Q47913": CardType.organization, # intelligence agency
|
|
"Q35535": CardType.organization, # police
|
|
"Q4830453": CardType.organization, # business
|
|
}
|
|
|
|
import asyncio
|
|
import httpx
|
|
|
|
async def _get_random_summary_async(client: httpx.AsyncClient) -> dict:
|
|
try:
|
|
response = await client.get(
|
|
"https://en.wikipedia.org/api/rest_v1/page/random/summary",
|
|
headers=HEADERS,
|
|
follow_redirects=False,
|
|
)
|
|
if response.status_code in (301, 302, 303, 307, 308):
|
|
# Extract the title from the redirect location and re-encode it
|
|
location = response.headers["location"]
|
|
title = location.split("/page/summary/")[-1]
|
|
response = await client.get(
|
|
f"https://en.wikipedia.org/api/rest_v1/page/summary/{quote(title, safe='%')}",
|
|
headers=HEADERS,
|
|
follow_redirects=False,
|
|
)
|
|
except:
|
|
return {}
|
|
|
|
if not response.is_success:
|
|
logger.error(
|
|
"Error in request:" +
|
|
str(response.status_code) +
|
|
response.text
|
|
)
|
|
return {}
|
|
|
|
return response.json()
|
|
|
|
async def _get_page_summary_async(client: httpx.AsyncClient, title: str) -> dict:
|
|
try:
|
|
response = await client.get(
|
|
f"https://en.wikipedia.org/api/rest_v1/page/summary/{title}",
|
|
headers=HEADERS,
|
|
follow_redirects=False,
|
|
)
|
|
if response.status_code in (301, 302, 303, 307, 308):
|
|
# Extract the title from the redirect location and re-encode it
|
|
location = response.headers["location"]
|
|
title = location.split("/page/summary/")[-1]
|
|
response = await client.get(
|
|
f"https://en.wikipedia.org/api/rest_v1/page/summary/{quote(title, safe='%')}",
|
|
headers=HEADERS,
|
|
follow_redirects=False,
|
|
)
|
|
except:
|
|
return {}
|
|
|
|
if not response.is_success:
|
|
logger.error(
|
|
"Error in request:" +
|
|
str(response.status_code) +
|
|
response.text
|
|
)
|
|
return {}
|
|
|
|
return response.json()
|
|
|
|
async def _get_superclasses(client: httpx.AsyncClient, qid: str) -> list[str]:
|
|
try:
|
|
response = await client.get(
|
|
"https://www.wikidata.org/wiki/Special:EntityData/" + qid + ".json",
|
|
headers=HEADERS
|
|
)
|
|
except:
|
|
return []
|
|
if not response.is_success:
|
|
return []
|
|
entity = response.json().get("entities", {}).get(qid, {})
|
|
subclass_claims = entity.get("claims", {}).get("P279", [])
|
|
return [
|
|
c.get("mainsnak", {}).get("datavalue", {}).get("value", {}).get("id")
|
|
for c in subclass_claims
|
|
if c.get("mainsnak", {}).get("datavalue")
|
|
]
|
|
|
|
async def _infer_card_type_async(client: httpx.AsyncClient, entity_id: str) -> tuple[CardType, str, int]:
|
|
try:
|
|
response = await client.get(
|
|
"https://www.wikidata.org/wiki/Special:EntityData/" + entity_id + ".json",
|
|
headers=HEADERS
|
|
)
|
|
except:
|
|
return CardType.other, "", 0
|
|
|
|
if not response.is_success:
|
|
return CardType.other, "", 0
|
|
|
|
entity = response.json().get("entities", {}).get(entity_id, {})
|
|
claims = entity.get("claims", {})
|
|
instance_of_claims = claims.get("P31", [])
|
|
qids = [
|
|
c.get("mainsnak", {}).get("datavalue", {}).get("value", {}).get("id")
|
|
for c in instance_of_claims
|
|
if c.get("mainsnak", {}).get("datavalue")
|
|
]
|
|
|
|
sitelinks = entity.get("sitelinks", {})
|
|
language_count = sum(
|
|
1 for key in sitelinks
|
|
if key.endswith("wiki")
|
|
and key not in ("commonswiki", "wikidatawiki", "specieswiki")
|
|
)
|
|
|
|
# First pass: direct match
|
|
for qid in qids:
|
|
if qid in WIKIDATA_INSTANCE_TYPE_MAP:
|
|
return WIKIDATA_INSTANCE_TYPE_MAP[qid], qid, language_count
|
|
|
|
# Second pass: check superclasses of each instance-of QID
|
|
superclass_results = sum(
|
|
await asyncio.gather(*[_get_superclasses(client, qid) for qid in qids if qid]),
|
|
[])
|
|
for superclass_qid in superclass_results:
|
|
if superclass_qid in WIKIDATA_INSTANCE_TYPE_MAP:
|
|
return WIKIDATA_INSTANCE_TYPE_MAP[superclass_qid], superclass_qid, language_count
|
|
|
|
# Third pass: check superclasses of each superclass
|
|
superclass_results2 = sum(
|
|
await asyncio.gather(*[_get_superclasses(client, qid) for qid in superclass_results if qid])
|
|
,[])
|
|
for superclass_qid2 in superclass_results2:
|
|
if superclass_qid2 in WIKIDATA_INSTANCE_TYPE_MAP:
|
|
return WIKIDATA_INSTANCE_TYPE_MAP[superclass_qid2], superclass_qid2, language_count
|
|
|
|
# Fallback: coordinate location
|
|
if "P625" in claims:
|
|
return CardType.location, (qids[0] if qids else ""), language_count
|
|
|
|
return CardType.other, (qids[0] if qids != [] else ""), language_count
|
|
|
|
async def _get_wikirank_score(client: httpx.AsyncClient, title: str) -> float | None:
|
|
"""Returns a quality score from 0-100, or None if unavailable."""
|
|
try:
|
|
response = await client.get(
|
|
f"https://api.wikirank.net/api.php?name={quote(title, safe='')}&lang=en",
|
|
headers=HEADERS
|
|
)
|
|
except:
|
|
return None
|
|
if not response.is_success:
|
|
return None
|
|
data = response.json()
|
|
result = data.get("result",{})
|
|
if result == "not found":
|
|
return None
|
|
return result.get("en",{}).get("quality")
|
|
|
|
def _score_to_rarity(score: float | None) -> CardRarity:
|
|
if score is None:
|
|
return CardRarity.common
|
|
if score >= 95:
|
|
return CardRarity.legendary
|
|
if score >= 80:
|
|
return CardRarity.epic
|
|
if score >= 65:
|
|
return CardRarity.super_rare
|
|
if score >= 50:
|
|
return CardRarity.rare
|
|
if score >= 35:
|
|
return CardRarity.uncommon
|
|
return CardRarity.common
|
|
|
|
RARITY_MULTIPLIER = {
|
|
CardRarity.common: 1,
|
|
CardRarity.uncommon: 1.1,
|
|
CardRarity.rare: 1.3,
|
|
CardRarity.super_rare: 1.7,
|
|
CardRarity.epic: 2.5,
|
|
CardRarity.legendary: 3,
|
|
}
|
|
|
|
async def _get_monthly_pageviews(client: httpx.AsyncClient, title: str) -> int | None:
|
|
# Uses the last full month
|
|
today = datetime.now()
|
|
first_of_month = today.replace(day=1)
|
|
last_month_end = first_of_month - timedelta(days=1)
|
|
last_month_start = last_month_end.replace(day=1)
|
|
start = last_month_start.strftime("%Y%m01")
|
|
end = last_month_end.strftime("%Y%m%d")
|
|
|
|
try:
|
|
response = await client.get(
|
|
f"https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/all-agents/{quote(title, safe='%')}/monthly/{start}/{end}",
|
|
headers=HEADERS,
|
|
)
|
|
if not response.is_success:
|
|
return None
|
|
items = response.json().get("items", [])
|
|
return items[0]["views"] if items else None
|
|
except:
|
|
return None
|
|
|
|
def _pageviews_to_defense(views: int | None) -> int:
|
|
if views is None:
|
|
return 0
|
|
return int(sqrt(views))
|
|
|
|
async def _get_card_async(client: httpx.AsyncClient, page_title: str|None = None) -> Card|None:
|
|
if page_title is None:
|
|
summary = await _get_random_summary_async(client)
|
|
else:
|
|
summary = await _get_page_summary_async(client, quote(page_title))
|
|
if summary == {}:
|
|
return None
|
|
|
|
title = summary["title"]
|
|
entity_id = summary.get("wikibase_item")
|
|
text = summary.get("extract", "")
|
|
|
|
card_type_task = (
|
|
_infer_card_type_async(client, entity_id)
|
|
if entity_id
|
|
else asyncio.sleep(0, result=(CardType.other, "", 0))
|
|
)
|
|
wikirank_task = _get_wikirank_score(client, title)
|
|
pageviews_task = _get_monthly_pageviews(client, title)
|
|
(card_type, instance, language_count), score, views = await asyncio.gather(
|
|
card_type_task, wikirank_task, pageviews_task
|
|
)
|
|
if (
|
|
(card_type == CardType.other and instance == "") or
|
|
language_count == 0 or
|
|
score is None or
|
|
views is None
|
|
):
|
|
error_message = f"Could not generate card '{title}': "
|
|
if card_type == CardType.other and instance == "":
|
|
error_message += "Not instance of a class"
|
|
elif language_count == 0:
|
|
error_message += "No language pages found"
|
|
elif score is None:
|
|
error_message += "No wikirank score"
|
|
elif views is None:
|
|
error_message += "No monthly view data"
|
|
logger.warning(error_message)
|
|
return None
|
|
|
|
rarity = _score_to_rarity(score)
|
|
multiplier = RARITY_MULTIPLIER[rarity]
|
|
|
|
attack = min(2500,int(((language_count*1.5)**1.2)*multiplier**2))
|
|
defense = min(2500,int(_pageviews_to_defense(views)*max(multiplier,(multiplier**2)/2)))
|
|
|
|
return Card(
|
|
name=summary["title"],
|
|
created_at=datetime.now(),
|
|
image_link=summary.get("thumbnail", {}).get("source", ""),
|
|
card_rarity=rarity,
|
|
card_type=card_type,
|
|
wikidata_instance=instance,
|
|
text=text,
|
|
attack=attack,
|
|
defense=defense,
|
|
cost=min(12,max(1,int(((attack**2+defense**2)**0.18)/1.5)))
|
|
)
|
|
|
|
async def _get_cards_async(size: int) -> list[Card]:
|
|
logger.debug(f"Generating {size} cards")
|
|
async with httpx.AsyncClient(follow_redirects=True) as client:
|
|
cards = await asyncio.gather(*[_get_card_async(client) for _ in range(size)])
|
|
|
|
return [c for c in cards if c is not None]
|
|
|
|
async def _get_specific_card_async(title: str) -> Card|None:
|
|
async with httpx.AsyncClient(follow_redirects=True) as client:
|
|
return await _get_card_async(client, title)
|
|
|
|
# Sync entrypoints
|
|
def generate_cards(size: int) -> list[Card]:
|
|
return asyncio.run(_get_cards_async(size))
|
|
|
|
def generate_card(title: str) -> Card|None:
|
|
return asyncio.run(_get_specific_card_async(title))
|
|
|
|
# Cards helper function
|
|
def compute_deck_type(cards: list) -> str | None:
|
|
if len(cards) < 20:
|
|
return None
|
|
avg_atk = sum(c.attack for c in cards) / len(cards)
|
|
avg_def = sum(c.defense for c in cards) / len(cards)
|
|
avg_cost = sum(c.cost for c in cards) / len(cards)
|
|
|
|
if all(c.cost > 6 for c in cards):
|
|
return "Unplayable"
|
|
if sum(1 for c in cards if c.cost >= 10) == 1:
|
|
return "God Card"
|
|
if sum(1 for c in cards if c.cost >= 10) > 1:
|
|
return "Pantheon"
|
|
if avg_cost >= 3.2:
|
|
return "Control"
|
|
if avg_cost <= 1.8:
|
|
return "Rush"
|
|
if avg_def > avg_atk * 1.5:
|
|
return "Wall"
|
|
if avg_atk > avg_def * 1.5:
|
|
return "Aggro"
|
|
return "Balanced"
|
|
|
|
# for card in generate_cards(5):
|
|
# print(card)
|
|
|
|
# cards = []
|
|
# for i in range(20):
|
|
# print(i)
|
|
# cards += generate_cards(10)
|
|
# sleep(3)
|
|
|
|
# costs = []
|
|
# from collections import Counter
|
|
# for card in cards:
|
|
# costs.append((card.card_rarity,card.cost))
|
|
# if card.card_rarity == CardRarity.legendary:
|
|
# print(card)
|
|
|
|
# print(Counter(costs))
|
|
|
|
# for card in generate_cards(100):
|
|
# if card.card_type == CardType.other:
|
|
# print(card)
|
|
|
|
# print(generate_card("9/11"))
|
|
# print(generate_card("Julius Caesar"))
|
|
# print(generate_card("List of lists of lists"))
|
|
# print(generate_card("Boudica"))
|
|
# print(generate_card("Harald Bluetooth"))
|
|
# print(generate_card("Nørrebro"))
|