Add basic booster pack generation
This commit is contained in:
parent
eade73f9f5
commit
cd1c26249c
2 changed files with 232 additions and 0 deletions
212
src/mlpccg/draft/booster.ts
Normal file
212
src/mlpccg/draft/booster.ts
Normal 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
20
src/mlpccg/draft/types.ts
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue