mkmrare/lib/cube.ts

190 lines
4.2 KiB
TypeScript

import * as ejs from "ejs";
import { Article, CardItem, dictMap, filterDict, leanArticle, leanCard, MCMDB, onlyUnique, spanBy } from ".";
const colorNames = {
CL: "Colorless",
MC: "Multicolor",
L: "Land",
W: "White",
U: "Blue",
B: "Black",
R: "Red",
G: "Green",
WU: "Azorius",
UB: "Dimir",
BR: "Rakdos",
RG: "Gruul",
WG: "Selesnya",
WB: "Orzhov",
UR: "Izzet",
BG: "Golgari",
WR: "Boros",
UG: "Simic",
WUG: "Bant",
WUB: "Esper",
UBR: "Grixis",
BRG: "Jund",
WRG: "Naya",
WBG: "Abzan",
WUR: "Jeskai",
UBG: "Sultai",
WBR: "Mardu",
URG: "Temur"
};
const columns: Record<string, (c: CardItem) => boolean> = {
W: c => colorid(c.colorIdentity) == "W",
U: c => colorid(c.colorIdentity) == "U",
B: c => colorid(c.colorIdentity) == "B",
R: c => colorid(c.colorIdentity) == "R",
G: c => colorid(c.colorIdentity) == "G",
MC: c => c.colorIdentity.length > 1,
CL: c => colorid(c.colorIdentity) == "CL"
};
function wubrg(a: string, b: string) {
const order = ["W", "U", "B", "R", "G"];
const indexA = order.indexOf(a);
const indexB = order.indexOf(b);
return indexA - indexB;
}
function colorid(colors: string[]): string {
if (colors.length < 1) {
return "CL";
}
return colors.sort(wubrg).join("");
}
function prettyColor(color: string) {
if (color in colorNames) {
return colorNames[color];
}
return color;
}
const allTypes = [
"Creature",
"Planeswalker",
"Instant",
"Sorcery",
"Artifact",
"Enchantment"
];
function typeSort(a: string, b: string): number {
const indexA = allTypes.indexOf(a);
const indexB = allTypes.indexOf(b);
return indexA - indexB;
}
function abcSort(a: string, b: string): number {
if (a > b) {
return 1;
}
if (b > a) {
return -1;
}
return 0;
}
// This would be properly typed but as of currently TS does not allow circular references for types
function deepSum(dict: any): number {
if (Array.isArray(dict)) {
return dict.length;
}
let total = 0;
for (let key in dict) {
if (Array.isArray(dict[key])) {
total += dict[key].length;
} else {
total += deepSum(dict[key]);
}
}
return total;
}
export function cubeCards(c: CubeCards): CardItem[] {
let cards: CardItem[] = [];
dictMap(c, bytyp =>
dictMap(bytyp, bycmc =>
dictMap(bycmc, cardlist => (cards = cards.concat(cardlist)))
)
);
return cards;
}
function byRank(a: CardItem, b: CardItem): number {
return a.edhrecRank - b.edhrecRank;
}
export type ByCMC<T> = Record<number, T>;
export type ByType<T> = Record<string, T>;
export type ByColor<T> = Record<string, T>;
export type CubeCards = ByColor<ByType<ByCMC<CardItem[]>>>;
export async function genCube(
db: MCMDB,
articles: Article[]
): Promise<CubeCards> {
const cards: CardItem[] = articles
.filter(art => art.idProduct in db)
.map(art => {
const card = db[art.idProduct];
return {
...leanArticle(art),
...leanCard(card)
};
});
const valid = cards
.filter(
c =>
(c.language == "Italian" || c.language == "English") &&
c.price < 0.2 &&
(c.rarity == "rare" || c.rarity == "mythic")
)
.filter(onlyUnique);
const categorized = filterDict(valid, columns);
// Cut cards
const filtered = dictMap(categorized, (pool, col) => {
switch (col) {
case "MC":
const colors = spanBy(pool, c => colorid(c.colorIdentity));
let cards = [];
for (const color in colors) {
cards = cards.concat(colors[color].sort(byRank).slice(0, 3));
}
return cards;
case "CL":
return pool.sort(byRank).slice(0, 30);
default:
return pool.sort(byRank).slice(0, 58);
}
});
// Filter by colors and cmc (nested)
return dictMap(filtered, (cards, col) =>
dictMap(
spanBy(cards, c =>
col == "MC" || col == "L"
? colorid(c.colorIdentity)
: c.types.slice(-1)[0]
),
typed => spanBy(typed, c => c.convertedManaCost)
)
);
}
export async function cubeHTML(user, cubecards: CubeCards) {
return await ejs.renderFile("templates/cube.ejs", {
user,
cards: cubeCards(cubecards),
cubecards,
columns,
utils: { wubrg, prettyColor, colorid, deepSum, typeSort, abcSort }
});
}