mlpcardgame/src/mlpccg/draft/booster.ts

212 lines
5.0 KiB
TypeScript

import { Card, getCards } from "@/mlpccg";
import { Pack, PackSchema, AlternateProvider } from "./types";
/*
(This data was taken from the MLP:CCG wikia at mlpccg.fandom.com and confirmed
by people at the MLP:CCG Discord)
Distribution rates for packs is usually 8 commons, 3 uncommons and 1 rare.
No Fixed or Promo cards can be found in packs.
UR distribution depends on set:
- PR has 1/13 chance of UR replacing a common
- CN->AD has 1/11 chance of UR replacing a common
- EO->FF has 1/3 chance of SR/UR replacing a common
SR are twice as common as UR, so that's one more thing to keep in mind.
Lastly, RR can replace another common in the ratio of ~1/2 every 6 boxes, depending
on set. Specifically, this is the RR ratio for each set:
- EO->HM: 1/108
- MT->FF: 1/216
*/
// Returns the pack schema for a specific set
async function setSchema(set: string): Promise<PackSchema> {
// Force set name to uppercase
set = set.toUpperCase();
// Return blank schemas for invalid sets
if (set == "RR" || set == "CS") {
return { slots: [] };
}
// Get cards for set
let cards = await getCards({ Sets: [set] });
let cardMap = spanByRarity(cards);
let rr: AlternateProvider[] = [];
let srur: AlternateProvider[] = [];
// Check for RR chances
/*
switch (set) {
case "EO":
case "HM":
rr = [
{
probability: 1.0 / 108.0,
provider: randomProvider([
//TODO
])
}
];
break;
case "MT":
case "HM":
case "SB":
case "FF":
rr = [
{
probability: 1.0 / 216.0,
provider: randomProvider([
//TODO
])
}
];
break;
}
*/
// Check for SR/UR chances
switch (set) {
case "PR":
srur = [
{
probability: 1.0 / 13.0,
provider: randomProvider(cardMap["UR"])
}
];
break;
case "CN":
case "CG":
case "AD":
srur = [
{
probability: 1.0 / 11.0,
provider: randomProvider(cardMap["UR"])
}
];
break;
default:
srur = [
{
probability: (1.0 / 9.0) * 2.0,
provider: randomProvider(cardMap["SR"])
},
{
probability: 1.0 / 9.0,
provider: randomProvider(cardMap["UR"])
}
];
break;
}
return {
slots: [
{
amount: 6,
provider: randomProvider(cardMap["C"]),
alternate: []
},
{
amount: 1,
provider: randomProvider(cardMap["C"]),
alternate: rr
},
{
amount: 1,
provider: randomProvider(cardMap["C"]),
alternate: srur
},
{
amount: 1,
provider: randomProvider(cardMap["R"]),
alternate: []
},
{
amount: 3,
provider: randomProvider(cardMap["U"]),
alternate: []
}
]
};
}
export class PackBuilder {
schema: PackSchema;
constructor(schema: PackSchema) {
this.schema = schema;
}
buildPack(): Pack {
let pack = [];
for (const slot of this.schema.slots) {
let provider = slot.provider;
// Check for alternates by generating a random and checking cumulated
// probability. Ie. if one card would show 5% of the time, another would
// show up 10% of the time, the algorithm would do something like this:
//
// With Math.random() == 0.85:
// ALTERNATE NO ALTERNATE
// [0.00-0.05][0.06----0.15][0.16------------1.00]
// ^ 0.85
//
// With Math.random() == 0.03:
// ALTERNATE NO ALTERNATE
// [0.00-0.05][0.06----0.15][0.16------------1.00]
// ^ 0.03
const rnd = Math.random();
let currentProb = 0;
for (const alternate of slot.alternate) {
currentProb += alternate.probability;
// Alternate matched
if (currentProb > rnd) {
provider = alternate.provider;
break;
}
}
for (let i = 0; i < slot.amount; i++) {
const res = provider.next();
if (res.done) {
// No more cards to get from this, exit early
break;
}
pack.push(res.value);
}
}
return pack;
}
static async fromSet(set: string): Promise<PackBuilder> {
let schema = await setSchema(set);
let builder = new PackBuilder(schema);
return builder;
}
}
// Yields random cards from a chosen pool
export function* randomProvider(pool: Card[]) {
while (true) {
const idx = Math.floor(Math.random() * pool.length);
yield pool[idx];
}
}
// Divides a list of card to a map of rarities
// ie. [ff14, ff16, ff17] => { "C" : ["ff14"], "U": ["ff17"], "R": ["ff16"] }
export function spanByRarity(pool: Card[]): Record<string, Card[]> {
return pool.reduce((map, current) => {
if (!(current.Rarity in map)) {
map[current.Rarity] = [];
}
map[current.Rarity].push(current);
return map;
}, Object.create(null));
}