Make cubecobra_csv an API

This commit is contained in:
henriquenakashima 2021-03-19 21:37:27 -04:00
parent b52958aa81
commit 067e849973
8 changed files with 109 additions and 118 deletions

View file

@ -2,10 +2,11 @@
""" """
import cube_json import cube_json
import cubecobra_csv
import keyword_stats import keyword_stats
if __name__ == '__main__': if __name__ == '__main__':
cube_list = cube_json.load_cube_from_csv('cube_csvs/TheElegantCube_2020-11-17_5.0.4.csv', {'core'}) cube_list = cubecobra_csv.load_cube_names('cube_csvs/TheElegantCube_2020-11-17_5.0.4.csv', {'core'})
cube_json_filename = cube_json.create_cube_json(cube_list, 'cubes/TheElegantCube_2020-11-17_5.0.4.json') cube_json_filename = cube_json.create_cube_json(cube_list, 'cubes/TheElegantCube_2020-11-17_5.0.4.json')
keyword_dict = keyword_stats.keyword_count(cube_json_filename) keyword_dict = keyword_stats.keyword_count(cube_json_filename)

View file

@ -2,26 +2,14 @@
import csv import csv
import cube_json
import cubecobra_csv import cubecobra_csv
CUBE_NAME = 'TheElegantCube_fetched' CUBE_NAME = 'TheElegantCube_fetched'
def main(): def main():
csv_path = cube_json.request_cube_csv('TheElegantCube_fetched', 'elegant') csv_path = cubecobra_csv.request_cube_csv('TheElegantCube_fetched', 'elegant')
core_cards = cubecobra_csv.load_cube_names(csv_path, {'core'})
core_cards = []
with open(csv_path) as f:
reader = csv.reader(f)
header_line = next(reader)
cubecobra_csv.assert_header(header_line)
for line in reader:
card = line[cubecobra_csv.COLUMNS['Name']]
tags = line[cubecobra_csv.COLUMNS['Tags']].split(', ')
if 'core' in tags:
core_cards.append(card)
core_cards.sort() core_cards.sort()
for card in core_cards: for card in core_cards:
print(card) print(card)

View file

@ -1,36 +1,9 @@
import csv
import json import json
import requests
from pathlib import Path
from word_count import EXPECTED_HEADER, COLUMNS, TEMP_JSON, is_actual_mtg_card from word_count import TEMP_JSON, is_actual_mtg_card
def request_cube_csv(cube_name, cube_id): def create_cube_json(cards, output_filename=TEMP_JSON) -> str:
url = f'https://cubecobra.com/cube/download/csv/{cube_id}'
url += '?primary=Color%20Category&secondary=Types-Multicolor&tertiary=CMC2'
response = requests.get(url)
filename = Path('cube_csvs', f'{cube_name}.csv')
open(filename, 'wb').write(response.content)
return filename
def load_cube_from_csv(filename, tag_filter=None):
cards = []
with open(filename) as f:
reader = csv.reader(f)
header_line = next(reader)
assert header_line == EXPECTED_HEADER
for line in reader:
card = line[COLUMNS['Name']]
# Export CSV is broken on CubeCobra because Image Back URL is always just one double quotes character.
tags = [t.strip() for t in line[COLUMNS['Tags']].split(', ')]
if tag_filter is None or any(t in tag_filter for t in tags):
cards.append(card)
return cards
def create_cube_json(cards, output_filename=TEMP_JSON):
# Call this once to create a smaller JSON file from the full card list # Call this once to create a smaller JSON file from the full card list
# Visit https://scryfall.com/docs/api/bulk-data and look for "Oracle Cards" file; # Visit https://scryfall.com/docs/api/bulk-data and look for "Oracle Cards" file;
# Download and rename it to 'oracle_cards.json' # Download and rename it to 'oracle_cards.json'

View file

@ -8,80 +8,58 @@ from collections import defaultdict
from typing import Dict, List, Set from typing import Dict, List, Set
import cubecobra_csv import cubecobra_csv
from cubecobra_csv import CubeCard
@dataclasses.dataclass
class Card:
name: str
color_category: str
colors: List[str]
tags: List[str]
def __post_init__(self):
assert self.color_category in 'wubrgchml0', f'{self}'
def __repr__(self):
return self.name
class CubePool: class CubePool:
def __init__(self): def __init__(self):
self._categories: List[Card] = defaultdict(list) self._categories: List[CubeCard] = defaultdict(list)
def add_card(self, card: Card): def add_card(self, card: CubeCard):
self._categories[card.color_category].append(card) self._categories[card.color_category].append(card)
def wide_sample(self, n: int) -> List[Card]: def wide_sample(self, n: int) -> List[CubeCard]:
wide_iterator = list(itertools.chain.from_iterable(self._categories.values())) wide_iterator = list(itertools.chain.from_iterable(self._categories.values()))
sample = random.sample(wide_iterator, n) sample = random.sample(wide_iterator, n)
return sample return sample
def category_sample(self, color_category: str, n: int) -> List[Card]: def category_sample(self, color_category: str, n: int) -> List[CubeCard]:
return random.sample(self._categories[color_category], n) return random.sample(self._categories[color_category], n)
def draw_from_category(self, color_category: str, n: int) -> List[Card]: def draw_from_category(self, color_category: str, n: int) -> List[CubeCard]:
drawn = self.category_sample(color_category, n) drawn = self.category_sample(color_category, n)
self.remove_cards(drawn, color_category) self.remove_cards(drawn, color_category)
return drawn return drawn
def remove_cards(self, removed: List[Card], color_category: str): def remove_cards(self, removed: List[CubeCard], color_category: str):
existing = self._categories[color_category] existing = self._categories[color_category]
self._categories[color_category] = [c for c in existing if not c in removed] self._categories[color_category] = [c for c in existing if not c in removed]
def fetch(self, card_name: str, color_category: str) -> Card: def fetch(self, card_name: str, color_category: str) -> CubeCard:
found: List[Card] = list(filter(lambda c: c.name == card_name, self._categories[color_category])) found: List[CubeCard] = list(filter(lambda c: c.name == card_name, self._categories[color_category]))
assert len(found) >= 1 assert len(found) >= 1
card_taken: Card = found[0] card_taken: CubeCard = found[0]
self.remove_cards([card_taken], color_category) self.remove_cards([card_taken], color_category)
return card_taken return card_taken
def get_category(self, color_category: str) -> List[Card]: def get_category(self, color_category: str) -> List[CubeCard]:
return self._categories[color_category] return self._categories[color_category]
def load_pools(csv_path: str, pool_tags: Set[str]) -> Dict[str, CubePool]: def load_pools(csv_path: str, pool_tags: Set[str]) -> Dict[str, CubePool]:
pools = defaultdict(CubePool) pools = defaultdict(CubePool)
with open(csv_path) as f: cube: List[CubeCard] = cubecobra_csv.load_cube(csv_path)
reader = csv.reader(f)
header_line = next(reader) for cube_card in cube:
cubecobra_csv.assert_header(header_line) pool_tags_for_card = pool_tags.intersection(cube_card.tags)
for line in reader:
card_name = cubecobra_csv.get_name(line)
tags = cubecobra_csv.get_tags(line)
# print(f'{card_name} {tags}')
pool_tags_for_card = pool_tags.intersection(tags)
if len(pool_tags_for_card) == 0: if len(pool_tags_for_card) == 0:
print(f'{card_name} does not have any of the tags: {pool_tags}') print(f'{cube_card.name} does not have any of the tags: {pool_tags}')
elif len(pool_tags_for_card) > 1: elif len(pool_tags_for_card) > 1:
print(f'{card_name} should only be tagged as one in {pool_tags} ' print(f'{cube_card.name} should only be tagged as one in {pool_tags} '
'but is tagged as {pool_tags_for_card}') 'but is tagged as {pool_tags_for_card}')
else: else:
(pool_tag,) = pool_tags_for_card (pool_tag,) = pool_tags_for_card
color_category = cubecobra_csv.get_color_category(line) pools[pool_tag].add_card(cube_card)
# print(f'{card_name} {color_category}')
colors = cubecobra_csv.get_colors(line)
# print(f'{card_name} {colors}')
card = Card(card_name, color_category, colors, tags)
pools[pool_tag].add_card(card)
return pools return pools

View file

@ -1,7 +1,10 @@
# TODO: Make this an actual API
import csv
import dataclasses
from pathlib import Path
from typing import List, Set from typing import List, Set
import requests
EXPECTED_HEADER = [ EXPECTED_HEADER = [
'Name', 'Name',
@ -26,10 +29,6 @@ EXPECTED_HEADER = [
COLUMNS = {column_name: i for i, column_name in enumerate(EXPECTED_HEADER)} COLUMNS = {column_name: i for i, column_name in enumerate(EXPECTED_HEADER)}
def assert_header(header_line):
assert header_line == EXPECTED_HEADER
_COLOR_CATEGORIES = { _COLOR_CATEGORIES = {
'w': 'w', 'w': 'w',
'u': 'u', 'u': 'u',
@ -52,21 +51,72 @@ _COLOR_CATEGORIES = {
} }
def get_name(line: List) -> str: @dataclasses.dataclass
class CubeCard:
name: str
color_category: str
colors: str
tags: Set[str]
def __post_init__(self):
assert self.color_category in 'wubrgchml0', f'{self}'
def __str__(self):
return self.name
def request_cube_csv(cube_name, cube_id) -> Path:
url = f'https://cubecobra.com/cube/download/csv/{cube_id}'
url += '?primary=Color%20Category&secondary=Types-Multicolor&tertiary=CMC2'
response = requests.get(url)
filename = Path('cube_csvs', f'{cube_name}.csv')
open(filename, 'wb').write(response.content)
return filename
def load_cube_names(filename, tag_filter: Set[str]=None) -> List[str]:
cube_cards: List[CubeCard] = load_cube(filename)
cards = []
for cube_card in cube_cards:
if tag_filter is None or tag_filter.intersection(cube_card.tags):
cards.append(cube_card.name)
return cards
def load_cube(filename) -> List[CubeCard]:
cards = []
with open(filename) as f:
reader = csv.reader(f)
header_line = next(reader)
_assert_header(header_line)
for line in reader:
card_name = _get_name(line)
tags = _get_tags(line)
color_category = _get_color_category(line)
colors = _get_colors(line)
cards.append(CubeCard(card_name, color_category, colors, tags))
return cards
def _assert_header(header_line):
assert header_line == EXPECTED_HEADER
def _get_name(line: List) -> str:
return line[COLUMNS['Name']] return line[COLUMNS['Name']]
def get_tags(line: List) -> Set[str]: def _get_tags(line: List) -> Set[str]:
return set(line[COLUMNS['Tags']].split(', ')) return set(t.strip() for t in line[COLUMNS['Tags']].split(', '))
def get_color_category(line: List) -> str: def _get_color_category(line: List) -> str:
color_category = line[COLUMNS['Color Category']] color_category = line[COLUMNS['Color Category']]
assert color_category in _COLOR_CATEGORIES, f'color_category "{color_category}" unknown' assert color_category in _COLOR_CATEGORIES, f'color_category "{color_category}" unknown'
return _COLOR_CATEGORIES[color_category] return _COLOR_CATEGORIES[color_category]
def get_colors(line: List) -> str: def _get_colors(line: List) -> str:
colors = line[COLUMNS['Color']] colors = line[COLUMNS['Color']]
#assert color_category in _COLOR_CATEGORIES, f'color_category "{color_category}" unknown' #assert color_category in _COLOR_CATEGORIES, f'color_category "{color_category}" unknown'
return sorted(colors.lower()) return sorted(colors.lower())

View file

@ -2,9 +2,8 @@
import hashlib import hashlib
import cube_json
import cube_pool import cube_pool
import cubecobra_csv
OUTPUT_POOL_SIZE = 360 OUTPUT_POOL_SIZE = 360
CARDS_FROM_OCCASIONAL = 48 CARDS_FROM_OCCASIONAL = 48
@ -17,7 +16,7 @@ OUTPUT_FILENAME = 'cards_in_draft.txt'
def main(): def main():
csv_path = cube_json.request_cube_csv('TheElegantCube_fetched', 'elegant') csv_path = cubecobra_csv.request_cube_csv('TheElegantCube_fetched', 'elegant')
pools = cube_pool.load_pools(csv_path, {'core', 'occasional'}) pools = cube_pool.load_pools(csv_path, {'core', 'occasional'})

View file

@ -6,8 +6,9 @@ from collections import Counter
from pathlib import Path from pathlib import Path
from typing import Dict, List from typing import Dict, List
import cube_json
import cube_pool import cube_pool
import cubecobra_csv
from cubecobra_csv import CubeCard
CUBE_NAME = 'TheElegantCube_fetched' CUBE_NAME = 'TheElegantCube_fetched'
@ -15,7 +16,7 @@ CUBE_NAME = 'TheElegantCube_fetched'
class RogueDuelPlayerState: class RogueDuelPlayerState:
def __init__(self): def __init__(self):
self.colors: List[str] = [] self.colors: List[str] = []
self.deck: List[cube_pool.Card] = [] self.deck: List[CubeCard] = []
class RogueDuelState: class RogueDuelState:
@ -24,9 +25,9 @@ class RogueDuelState:
self.player2 = RogueDuelPlayerState() self.player2 = RogueDuelPlayerState()
self.player_states = (self.player1, self.player2) self.player_states = (self.player1, self.player2)
csv_path = cube_json.request_cube_csv('TheElegantCube_fetched', 'elegant') csv_path = cubecobra_csv.request_cube_csv('TheElegantCube_fetched', 'elegant')
self.pools: Dict[str, cube_pool.CubePool] = cube_pool.load_pools(csv_path, {'core', 'occasional'}) self.pools: Dict[str, cube_pool.CubePool] = cube_pool.load_pools(csv_path, {'core', 'occasional'})
self.discard_pile: List[cube_pool.Card] = [] self.discard_pile: List[CubeCard] = []
def main(): def main():
@ -40,7 +41,7 @@ def main():
def _decide_colors(state: RogueDuelState): def _decide_colors(state: RogueDuelState):
sealed_pools: Dict[str, List[cube_pool.Card]] = {} sealed_pools: Dict[str, List[CubeCard]] = {}
for color_category in 'wubrg': for color_category in 'wubrg':
sealed_pools[color_category] = (state.pools['core'].draw_from_category(color_category, 6) + sealed_pools[color_category] = (state.pools['core'].draw_from_category(color_category, 6) +
state.pools['occasional'].draw_from_category(color_category, 2)) state.pools['occasional'].draw_from_category(color_category, 2))
@ -77,7 +78,7 @@ def _pick_a_color(colors_available: List[str], prompt: str) -> str:
def _pick_artifacts(state: RogueDuelState): def _pick_artifacts(state: RogueDuelState):
artifact_pool: List[cube_pool.Card] = (state.pools['core'].draw_from_category('c', 4) + artifact_pool: List[CubeCard] = (state.pools['core'].draw_from_category('c', 4) +
state.pools['occasional'].draw_from_category('c', 1)) state.pools['occasional'].draw_from_category('c', 1))
print(artifact_pool) print(artifact_pool)
card = _pick_a_card(artifact_pool, 'Player 1, choose an artifact:') card = _pick_a_card(artifact_pool, 'Player 1, choose an artifact:')
@ -87,7 +88,7 @@ def _pick_artifacts(state: RogueDuelState):
state.discard_pile.extend(artifact_pool) state.discard_pile.extend(artifact_pool)
def _pick_a_card(cards_available: List[cube_pool.Card], prompt: str) -> cube_pool.Card: def _pick_a_card(cards_available: List[CubeCard], prompt: str) -> CubeCard:
for i, card in enumerate(cards_available, 1): for i, card in enumerate(cards_available, 1):
print(f'[{i:>2}] {card.name}') print(f'[{i:>2}] {card.name}')
index_chosen = -1 index_chosen = -1
@ -114,14 +115,14 @@ def _fill_with_lands(state: RogueDuelState):
def _basic_for_color(color: str) -> str: def _basic_for_color(color: str) -> str:
name = {'w': 'Plains', 'u': 'Island', 'b': 'Swamp', 'r': 'Mountain', 'g': 'Forest'}[color] name = {'w': 'Plains', 'u': 'Island', 'b': 'Swamp', 'r': 'Mountain', 'g': 'Forest'}[color]
return cube_pool.Card(name, '0', color, []) return CubeCard(name, '0', [color], [])
def _fetch_duals(state: RogueDuelState, colors: List[str]) -> List[cube_pool.Card]: def _fetch_duals(state: RogueDuelState, colors: List[str]) -> List[CubeCard]:
colors = sorted(colors) colors = sorted(colors)
duals = [] duals = []
# Get duals # Get duals
core_lands: List[cube_pool.Card] = state.pools['core'].get_category('l') core_lands: List[CubeCard] = state.pools['core'].get_category('l')
for land in core_lands: for land in core_lands:
if land.colors == colors: if land.colors == colors:
duals.append(land) duals.append(land)
@ -137,7 +138,7 @@ def _export_decks(state: RogueDuelState):
_export_deck(player_state.deck, filepath) _export_deck(player_state.deck, filepath)
def _export_deck(deck: List[cube_pool.Card], filepath: Path): def _export_deck(deck: List[CubeCard], filepath: Path):
deck_counter = Counter([card.name for card in deck]) deck_counter = Counter([card.name for card in deck])
with open(filepath, 'w') as f: with open(filepath, 'w') as f:
for card_name, quantity in deck_counter.most_common(): for card_name, quantity in deck_counter.most_common():

View file

@ -3,6 +3,7 @@ import json
from collections import defaultdict from collections import defaultdict
import cube_json import cube_json
import cubecobra_csv
def load_cube_from_txt(filename): def load_cube_from_txt(filename):
@ -168,12 +169,12 @@ if __name__ == '__main__':
# Use either option: # Use either option:
# 1. If you have your cube in Cube Cobra and want to filter per tag # 1. If you have your cube in Cube Cobra and want to filter per tag
# csv_path = cube_json.request_cube_csv('TheElegantCube_fetched', 'elegant') # csv_path = cubecobra_csv.request_cube_csv('TheElegantCube_fetched', 'elegant')
# cube_list = cube_json.load_cube_from_csv(csv_path) # cube_list = cube_json.load_cube_from_csv(csv_path)
# 2. If you have your cube in Cube Cobra and want to consider only cards with a certain tag # 2. If you have your cube in Cube Cobra and want to consider only cards with a certain tag
csv_path = cube_json.request_cube_csv('TheElegantCube_fetched', 'elegant') csv_path = cubecobra_csv.request_cube_csv('TheElegantCube_fetched', 'elegant')
cube_list = cube_json.load_cube_from_csv(csv_path, tag_filter={'core'}) cube_list = cubecobra_csv.load_cube_names(csv_path, tag_filter={'core'})
# 3. If you have a .txt of your cube # 3. If you have a .txt of your cube
# cube_list = cube_json.load_cube_from_txt('YourCubeHere.txt') # cube_list = cube_json.load_cube_from_txt('YourCubeHere.txt')