Add basic booster pack generation

This commit is contained in:
Hamcha 2019-09-11 14:42:44 +02:00
parent eade73f9f5
commit cd1c26249c
Signed by: hamcha
GPG key ID: 44AD3571EB09A39E
2 changed files with 232 additions and 0 deletions

212
src/mlpccg/draft/booster.ts Normal file
View file

@ -0,0 +1,212 @@
import { Card } from "../types";
import { Pack, PackSchema, AlternateProvider } from "./types";
import { getCards } from "../database";
/*
(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
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"] }
function spanByRarity(pool: Card[]): Record<string, Card[]> {
return pool.reduce((map, current) => {
if (!(current.Rarity in map)) {
map[current.Rarity] = [];
}
map[current.Rarity] = current;
return map;
}, Object.create(null));
}

20
src/mlpccg/draft/types.ts Normal file
View file

@ -0,0 +1,20 @@
import { Card } from "../types";
export type Provider = Iterator<Card>;
export type Pack = Card[];
export interface PackSchema {
slots: PackSlot[];
}
export interface PackSlot {
amount: number;
provider: Provider;
alternate: AlternateProvider[];
}
export interface AlternateProvider {
probability: number;
provider: Provider;
}