feat: Refine booster pack generation logic for 'The List' cards, Special Guests, and wildcard rarities in both Draft and Play Boosters.
Some checks failed
Build and Deploy / build (push) Failing after 1m11s
Some checks failed
Build and Deploy / build (push) Failing after 1m11s
This commit is contained in:
@@ -82,7 +82,7 @@ define(['./workbox-5a5d9309'], (function (workbox) { 'use strict';
|
||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||
}, {
|
||||
"url": "index.html",
|
||||
"revision": "0.ucm0m4ajm9g"
|
||||
"revision": "0.g6k3e4tvo1g"
|
||||
}], {});
|
||||
workbox.cleanupOutdatedCaches();
|
||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||
|
||||
@@ -294,10 +294,6 @@ export class PackGeneratorService {
|
||||
const drawC = this.drawColorBalanced(currentPools.commons, commonsNeeded, namesInThisPack);
|
||||
|
||||
if (!drawC.success && currentPools.commons.length >= commonsNeeded) {
|
||||
// If we have enough cards but failed strict color balancing, we might accept it or fail.
|
||||
// Standard algo returns null on failure. Let's do same to be safe, or just accept partial.
|
||||
// Given "Naive approach" in drawColorBalanced, if it returns success=false but has cards, it meant it couldn't find unique ones?
|
||||
// drawUniqueCards (called by drawColorBalanced) checks if we have enough cards.
|
||||
return null;
|
||||
} else if (currentPools.commons.length < commonsNeeded) {
|
||||
return null;
|
||||
@@ -308,9 +304,9 @@ export class PackGeneratorService {
|
||||
drawC.selected.forEach(c => namesInThisPack.add(c.name));
|
||||
|
||||
// 2. Slot 7: Common / The List
|
||||
// 1-87: Common from Main Set
|
||||
// 88-97: Card from "The List" (Common/Uncommon)
|
||||
// 98-100: Uncommon from "The List"
|
||||
// 1-87: 1 Common from Main Set.
|
||||
// 88-97: 1 Card from "The List" (Common/Uncommon reprint).
|
||||
// 98-100: 1 Uncommon from "The List".
|
||||
const roll7 = Math.floor(Math.random() * 100) + 1;
|
||||
let slot7Card: DraftCard | undefined;
|
||||
|
||||
@@ -319,26 +315,31 @@ export class PackGeneratorService {
|
||||
const res = this.drawUniqueCards(currentPools.commons, 1, namesInThisPack);
|
||||
if (res.success) { slot7Card = res.selected[0]; currentPools.commons = res.remainingPool; }
|
||||
} else if (roll7 <= 97) {
|
||||
// List (Common/Uncommon). Simulating by picking 50/50 C/U if actual List not available
|
||||
const useUncommon = Math.random() < 0.5;
|
||||
const pool = useUncommon ? currentPools.uncommons : currentPools.commons;
|
||||
// Fallback if one pool is empty
|
||||
const effectivePool = pool.length > 0 ? pool : (useUncommon ? currentPools.commons : currentPools.uncommons);
|
||||
|
||||
if (effectivePool.length > 0) {
|
||||
const res = this.drawUniqueCards(effectivePool, 1, namesInThisPack);
|
||||
// List (Common/Uncommon). Use SpecialGuests or 50/50 fallback
|
||||
if (currentPools.specialGuests.length > 0) {
|
||||
const res = this.drawUniqueCards(currentPools.specialGuests, 1, namesInThisPack);
|
||||
if (res.success) { slot7Card = res.selected[0]; currentPools.specialGuests = res.remainingPool; }
|
||||
} else {
|
||||
// Fallback
|
||||
const pool = Math.random() < 0.5 ? currentPools.commons : currentPools.uncommons;
|
||||
const res = this.drawUniqueCards(pool, 1, namesInThisPack);
|
||||
if (res.success) {
|
||||
slot7Card = res.selected[0];
|
||||
// Identify which pool to update
|
||||
if (effectivePool === currentPools.uncommons) currentPools.uncommons = res.remainingPool;
|
||||
else currentPools.commons = res.remainingPool;
|
||||
if (pool === currentPools.commons) currentPools.commons = res.remainingPool;
|
||||
else currentPools.uncommons = res.remainingPool;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 98-100: Uncommon (from List or pool)
|
||||
// 98-100: Uncommon from "The List"
|
||||
if (currentPools.specialGuests.length > 0) {
|
||||
const res = this.drawUniqueCards(currentPools.specialGuests, 1, namesInThisPack);
|
||||
if (res.success) { slot7Card = res.selected[0]; currentPools.specialGuests = res.remainingPool; }
|
||||
} else {
|
||||
// Fallback
|
||||
const res = this.drawUniqueCards(currentPools.uncommons, 1, namesInThisPack);
|
||||
if (res.success) { slot7Card = res.selected[0]; currentPools.uncommons = res.remainingPool; }
|
||||
}
|
||||
}
|
||||
|
||||
if (slot7Card) {
|
||||
packCards.push(slot7Card);
|
||||
@@ -348,7 +349,6 @@ export class PackGeneratorService {
|
||||
// 3. Slots 8-11: Uncommons (4 cards)
|
||||
const uncommonsNeeded = 4;
|
||||
const drawU = this.drawUniqueCards(currentPools.uncommons, uncommonsNeeded, namesInThisPack);
|
||||
// We accept partial if pool depleted to avoid crashing, but standard behavior is usually strict.
|
||||
packCards.push(...drawU.selected);
|
||||
currentPools.uncommons = drawU.remainingPool;
|
||||
drawU.selected.forEach(c => namesInThisPack.add(c.name));
|
||||
@@ -372,25 +372,19 @@ export class PackGeneratorService {
|
||||
namesInThisPack.add(landCard.name);
|
||||
}
|
||||
|
||||
// Helper for Wildcards
|
||||
// Helper for Wildcards (Peasant)
|
||||
const drawWildcard = (foil: boolean) => {
|
||||
// ~62% Common, ~37% Uncommon
|
||||
const wRoll = Math.random() * 100;
|
||||
let wRarity = 'common';
|
||||
// ~49% Common, ~24% Uncommon, ~13% Rare, ~13% Mythic
|
||||
if (wRoll > 87) wRarity = 'mythic';
|
||||
else if (wRoll > 74) wRarity = 'rare';
|
||||
else if (wRoll > 50) wRarity = 'uncommon';
|
||||
else wRarity = 'common';
|
||||
if (wRoll > 62) wRarity = 'uncommon';
|
||||
|
||||
let poolToUse: DraftCard[] = [];
|
||||
let updatePool = (_newPool: DraftCard[]) => { };
|
||||
|
||||
if (wRarity === 'mythic') { poolToUse = currentPools.mythics; updatePool = (p) => currentPools.mythics = p; }
|
||||
else if (wRarity === 'rare') { poolToUse = currentPools.rares; updatePool = (p) => currentPools.rares = p; }
|
||||
else if (wRarity === 'uncommon') { poolToUse = currentPools.uncommons; updatePool = (p) => currentPools.uncommons = p; }
|
||||
if (wRarity === 'uncommon') { poolToUse = currentPools.uncommons; updatePool = (p) => currentPools.uncommons = p; }
|
||||
else { poolToUse = currentPools.commons; updatePool = (p) => currentPools.commons = p; }
|
||||
|
||||
// Fallback
|
||||
if (poolToUse.length === 0) {
|
||||
if (currentPools.commons.length > 0) { poolToUse = currentPools.commons; updatePool = (p) => currentPools.commons = p; }
|
||||
}
|
||||
@@ -423,14 +417,14 @@ export class PackGeneratorService {
|
||||
}
|
||||
|
||||
} else {
|
||||
// --- NEW ALGORITHM (Play Booster) ---
|
||||
// --- NEW ALGORITHM (Standard / Play Booster) ---
|
||||
|
||||
// 1. Slots 1-6: Commons (Color Balanced)
|
||||
const commonsNeeded = 6;
|
||||
const drawC = this.drawColorBalanced(currentPools.commons, commonsNeeded, namesInThisPack);
|
||||
if (!drawC.success) return null;
|
||||
packCards.push(...drawC.selected);
|
||||
currentPools.commons = drawC.remainingPool; // Update pool
|
||||
currentPools.commons = drawC.remainingPool;
|
||||
drawC.selected.forEach(c => namesInThisPack.add(c.name));
|
||||
|
||||
// 2. Slots 8-10: Uncommons (3 cards)
|
||||
@@ -442,7 +436,7 @@ export class PackGeneratorService {
|
||||
drawU.selected.forEach(c => namesInThisPack.add(c.name));
|
||||
|
||||
// 3. Slot 11: Main Rare/Mythic (1/8 Mythic, 7/8 Rare)
|
||||
const isMythic = Math.random() < (1 / 8);
|
||||
const isMythic = Math.random() < 0.125;
|
||||
let rarePicked = false;
|
||||
|
||||
if (isMythic && currentPools.mythics.length > 0) {
|
||||
@@ -465,10 +459,11 @@ export class PackGeneratorService {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback if Rare pool empty but Mythic not (or vice versa) handled by just skipping
|
||||
|
||||
// 4. Slot 7: Wildcard / The List
|
||||
// 1-87: Common, 88-97: List (C/U), 98-99: List (R/M), 100: Special Guest
|
||||
// 4. Slot 7: Common / The List / Special Guest
|
||||
// 1-87: 1 Common from Main Set.
|
||||
// 88-97: 1 Card from "The List" (Common/Uncommon reprint).
|
||||
// 98-99: 1 Rare/Mythic from "The List".
|
||||
// 100: 1 Special Guest (High Value).
|
||||
const roll7 = Math.floor(Math.random() * 100) + 1;
|
||||
let slot7Card: DraftCard | undefined;
|
||||
|
||||
@@ -477,41 +472,42 @@ export class PackGeneratorService {
|
||||
const res = this.drawUniqueCards(currentPools.commons, 1, namesInThisPack);
|
||||
if (res.success) { slot7Card = res.selected[0]; currentPools.commons = res.remainingPool; }
|
||||
} else if (roll7 <= 97) {
|
||||
// "The List" (Common/Uncommon). Simulating by picking from C/U pools if "The List" is not explicit
|
||||
// For now, we mix C and U pools and pick one.
|
||||
const listPool = [...currentPools.commons, ...currentPools.uncommons]; // Simplification
|
||||
if (listPool.length > 0) {
|
||||
const rnd = Math.floor(Math.random() * listPool.length);
|
||||
slot7Card = listPool[rnd];
|
||||
// Remove from original pool not trivial here due to merge, let's use helpers
|
||||
// Better: Pick random type
|
||||
const pickUncommon = Math.random() < 0.3; // Arbitrary weight
|
||||
if (pickUncommon) {
|
||||
const res = this.drawUniqueCards(currentPools.uncommons, 1, namesInThisPack);
|
||||
if (res.success) { slot7Card = res.selected[0]; currentPools.uncommons = res.remainingPool; }
|
||||
} else {
|
||||
const res = this.drawUniqueCards(currentPools.commons, 1, namesInThisPack);
|
||||
if (res.success) { slot7Card = res.selected[0]; currentPools.commons = res.remainingPool; }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 98-100: Rare/Mythic/Special Guest
|
||||
// 1/100 (1%) chance for Special Guest if available
|
||||
const isGuest = roll7 === 100;
|
||||
|
||||
if (isGuest && currentPools.specialGuests.length > 0) {
|
||||
// List (Common/Uncommon)
|
||||
if (currentPools.specialGuests.length > 0) {
|
||||
const res = this.drawUniqueCards(currentPools.specialGuests, 1, namesInThisPack);
|
||||
if (res.success) { slot7Card = res.selected[0]; currentPools.specialGuests = res.remainingPool; }
|
||||
} else {
|
||||
// Fallback to Rare/Mythic
|
||||
const useMythic = Math.random() < 0.125; // 1/8
|
||||
if (useMythic && currentPools.mythics.length > 0) {
|
||||
const pool = Math.random() < 0.5 ? currentPools.commons : currentPools.uncommons;
|
||||
const res = this.drawUniqueCards(pool, 1, namesInThisPack);
|
||||
if (res.success) {
|
||||
slot7Card = res.selected[0];
|
||||
if (pool === currentPools.commons) currentPools.commons = res.remainingPool;
|
||||
else currentPools.uncommons = res.remainingPool;
|
||||
}
|
||||
}
|
||||
} else if (roll7 <= 99) {
|
||||
// List (Rare/Mythic)
|
||||
if (currentPools.specialGuests.length > 0) {
|
||||
const res = this.drawUniqueCards(currentPools.specialGuests, 1, namesInThisPack);
|
||||
if (res.success) { slot7Card = res.selected[0]; currentPools.specialGuests = res.remainingPool; }
|
||||
} else {
|
||||
const pool = Math.random() < 0.125 ? currentPools.mythics : currentPools.rares;
|
||||
const res = this.drawUniqueCards(pool, 1, namesInThisPack);
|
||||
if (res.success) {
|
||||
slot7Card = res.selected[0];
|
||||
if (pool === currentPools.mythics) currentPools.mythics = res.remainingPool;
|
||||
else currentPools.rares = res.remainingPool;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 100: Special Guest
|
||||
if (currentPools.specialGuests.length > 0) {
|
||||
const res = this.drawUniqueCards(currentPools.specialGuests, 1, namesInThisPack);
|
||||
if (res.success) { slot7Card = res.selected[0]; currentPools.specialGuests = res.remainingPool; }
|
||||
} else {
|
||||
// Fallback Mythic
|
||||
const res = this.drawUniqueCards(currentPools.mythics, 1, namesInThisPack);
|
||||
if (res.success) { slot7Card = res.selected[0]; currentPools.mythics = res.remainingPool; }
|
||||
} else {
|
||||
const res = this.drawUniqueCards(currentPools.rares, 1, namesInThisPack);
|
||||
if (res.success) { slot7Card = res.selected[0]; currentPools.rares = res.remainingPool; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,7 +532,6 @@ export class PackGeneratorService {
|
||||
// Fallback: Pick a Common if no lands
|
||||
// const res = this.drawUniqueCards(currentPools.commons, 1, namesInThisPack);
|
||||
// if (res.success) { landCard = { ...res.selected[0] }; ... }
|
||||
// Better to just have no land than a non-land
|
||||
}
|
||||
|
||||
if (landCard) {
|
||||
@@ -546,8 +541,7 @@ export class PackGeneratorService {
|
||||
}
|
||||
|
||||
// 6. Slot 13: Wildcard (Non-Foil)
|
||||
// Weights: ~49% C, ~24% U, ~13% R, ~13% M => Sum=99.
|
||||
// Normalized: C:50, U:24, R:13, M:13
|
||||
// Weights: ~49% C, ~24% U, ~13% R, ~13% M
|
||||
const drawWildcard = (foil: boolean) => {
|
||||
const wRoll = Math.random() * 100;
|
||||
let wRarity = 'common';
|
||||
@@ -556,7 +550,6 @@ export class PackGeneratorService {
|
||||
else if (wRoll > 50) wRarity = 'uncommon';
|
||||
else wRarity = 'common';
|
||||
|
||||
// Adjust buckets
|
||||
let poolToUse: DraftCard[] = [];
|
||||
let updatePool = (_newPool: DraftCard[]) => { };
|
||||
|
||||
@@ -566,7 +559,6 @@ export class PackGeneratorService {
|
||||
else { poolToUse = currentPools.commons; updatePool = (p) => currentPools.commons = p; }
|
||||
|
||||
if (poolToUse.length === 0) {
|
||||
// Fallback cascade
|
||||
if (currentPools.commons.length > 0) { poolToUse = currentPools.commons; updatePool = (p) => currentPools.commons = p; }
|
||||
}
|
||||
|
||||
@@ -589,26 +581,15 @@ export class PackGeneratorService {
|
||||
|
||||
// 8. Slot 15: Marketing / Token
|
||||
if (currentPools.tokens.length > 0) {
|
||||
// Just pick one, duplicates allowed for tokens? user said unique cards... but for tokens?
|
||||
// "drawUniqueCards" handles uniqueness check.
|
||||
const res = this.drawUniqueCards(currentPools.tokens, 1, namesInThisPack);
|
||||
if (res.success) {
|
||||
packCards.push(res.selected[0]);
|
||||
currentPools.tokens = res.remainingPool;
|
||||
// Don't care about uniqueness for tokens as much, but let's stick to it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort: Mythic -> Rare -> Uncommon -> Common -> Land -> Token
|
||||
// We already have rarityWeight.
|
||||
// Assign weight to 'land' or 'token'?
|
||||
// DraftCard has 'rarity' string.
|
||||
// Standard rarities: common, uncommon, rare, mythic.
|
||||
// Basic Land has rarity 'common' usually? or 'basic'.
|
||||
// Token has rarity 'common' or 'token' (if we set it?). Scryfall tokens often have no rarity or 'common'.
|
||||
|
||||
// Custom sort
|
||||
const getWeight = (c: DraftCard) => {
|
||||
if (c.layout === 'token' || c.typeLine?.includes('Token')) return 0;
|
||||
if (c.typeLine?.includes('Land') && (c.rarity === 'common' || c.rarity === 'basic')) return 1;
|
||||
|
||||
@@ -339,8 +339,7 @@ export class PackGeneratorService {
|
||||
const packCards: DraftCard[] = [];
|
||||
const namesInPack = new Set<string>();
|
||||
|
||||
// Standard: 14 cards exactly. Peasant: 13 cards exactly.
|
||||
const targetSize = rarityMode === 'peasant' ? 13 : 14;
|
||||
const targetSize = 14;
|
||||
|
||||
// Helper to abstract draw logic
|
||||
const draw = (pool: DraftCard[], count: number, poolKey: keyof ProcessedPools) => {
|
||||
@@ -356,110 +355,183 @@ export class PackGeneratorService {
|
||||
return result.selected;
|
||||
};
|
||||
|
||||
// 1. Commons (6)
|
||||
draw(pools.commons, 6, 'commons');
|
||||
|
||||
// 2. Slot 7 (Common or List)
|
||||
const roll7 = Math.random() * 100;
|
||||
if (roll7 < 87) {
|
||||
// Common
|
||||
draw(pools.commons, 1, 'commons');
|
||||
} else {
|
||||
// If pool empty, try fallback if standard? No, strict as per previous instruction.
|
||||
// draw(pools.uncommons, 1, 'uncommons');
|
||||
|
||||
// Slot 7: Common/List Slot (Strict adherence to standard-pack-generation-algorithm.md)
|
||||
// 1-87: Common from Main Set
|
||||
// 88-97: Card from The List (Common/Uncommon) -> Mapped to specialGuests
|
||||
// 98-99: Rare/Mythic from The List -> Mapped to specialGuests
|
||||
// 100: Special Guest -> Mapped to specialGuests
|
||||
|
||||
// We simplify by drawing from specialGuests for 88-100 (13% chance)
|
||||
// if specialGuests is empty, fallback to Common or Uncommon?
|
||||
// Rulebook says "1-87 Common".
|
||||
if (rarityMode === 'peasant') {
|
||||
// 1. Commons (6) - Color Balanced
|
||||
// Using drawColorBalanced helper
|
||||
const drawC = this.drawColorBalanced(pools.commons, 6, namesInPack, withReplacement);
|
||||
if (drawC.selected.length > 0) {
|
||||
packCards.push(...drawC.selected);
|
||||
if (!withReplacement) {
|
||||
pools.commons = drawC.remainingPool;
|
||||
drawC.selected.forEach(c => namesInPack.add(c.name));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Slot 7: Common / The List
|
||||
// 1-87: Common
|
||||
// 88-97: List (C/U)
|
||||
// 98-100: List (U)
|
||||
const roll7 = Math.floor(Math.random() * 100) + 1;
|
||||
const hasGuests = pools.specialGuests.length > 0;
|
||||
|
||||
if (roll7 > 87 && hasGuests) {
|
||||
draw(pools.specialGuests, 1, 'specialGuests');
|
||||
} else {
|
||||
// 1-87 or no guests available
|
||||
if (roll7 <= 87) {
|
||||
draw(pools.commons, 1, 'commons');
|
||||
// Note: If original rule for 88-97 was "Common from List",
|
||||
// and we fall back to "Common from Set", it's acceptable if List/Guests missing.
|
||||
} else if (roll7 <= 97) {
|
||||
// List (C/U) - Fallback logic
|
||||
if (hasGuests) draw(pools.specialGuests, 1, 'specialGuests');
|
||||
else {
|
||||
// 50/50 fallback
|
||||
const useU = Math.random() < 0.5;
|
||||
if (useU) draw(pools.uncommons, 1, 'uncommons');
|
||||
else draw(pools.commons, 1, 'commons');
|
||||
}
|
||||
} else {
|
||||
// 98-100: List (U)
|
||||
if (hasGuests) draw(pools.specialGuests, 1, 'specialGuests');
|
||||
else draw(pools.uncommons, 1, 'uncommons');
|
||||
}
|
||||
|
||||
// 3. Uncommons (4)
|
||||
draw(pools.uncommons, 4, 'uncommons');
|
||||
|
||||
// 4. Land (Slot 12)
|
||||
const isFoilLand = Math.random() < 0.2;
|
||||
const landPicks = draw(pools.lands, 1, 'lands');
|
||||
if (landPicks.length > 0 && isFoilLand) {
|
||||
const idx = packCards.indexOf(landPicks[0]);
|
||||
if (idx !== -1) {
|
||||
packCards[idx] = { ...packCards[idx], finish: 'foil' };
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Uncommons (3 or 4 dependent on PEASANT vs STANDARD)
|
||||
const uNeeded = rarityMode === 'peasant' ? 4 : 3;
|
||||
draw(pools.uncommons, uNeeded, 'uncommons');
|
||||
// 5. Wildcards (Slot 13 & 14)
|
||||
// Peasant weights: ~62% Common, ~37% Uncommon
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const isFoil = i === 1;
|
||||
const wRoll = Math.random() * 100;
|
||||
let targetKey: keyof ProcessedPools = 'commons';
|
||||
|
||||
// 4. Rare/Mythic (Standard Only)
|
||||
if (rarityMode === 'standard') {
|
||||
// 1-62: Common, 63-100: Uncommon (Approx > 62)
|
||||
if (wRoll > 62) targetKey = 'uncommons';
|
||||
else targetKey = 'commons';
|
||||
|
||||
let pool = pools[targetKey];
|
||||
if (pool.length === 0) {
|
||||
// Fallback
|
||||
targetKey = 'commons';
|
||||
pool = pools.commons;
|
||||
}
|
||||
|
||||
const res = this.drawCards(pool, 1, namesInPack, withReplacement);
|
||||
if (res.selected.length > 0) {
|
||||
const card = { ...res.selected[0] };
|
||||
if (isFoil) card.finish = 'foil';
|
||||
packCards.push(card);
|
||||
if (!withReplacement) {
|
||||
// @ts-ignore
|
||||
pools[targetKey] = res.remainingPool;
|
||||
namesInPack.add(card.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// STANDARD MODE
|
||||
|
||||
// 1. Commons (6)
|
||||
const drawC = this.drawColorBalanced(pools.commons, 6, namesInPack, withReplacement);
|
||||
if (drawC.selected.length > 0) {
|
||||
packCards.push(...drawC.selected);
|
||||
if (!withReplacement) {
|
||||
pools.commons = drawC.remainingPool;
|
||||
drawC.selected.forEach(c => namesInPack.add(c.name));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Slot 7 (Common / List / Guest)
|
||||
// 1-87: Common
|
||||
// 88-97: List (C/U)
|
||||
// 98-99: List (R/M)
|
||||
// 100: Special Guest
|
||||
const roll7 = Math.floor(Math.random() * 100) + 1; // 1-100
|
||||
const hasGuests = pools.specialGuests.length > 0;
|
||||
|
||||
if (roll7 <= 87) {
|
||||
draw(pools.commons, 1, 'commons');
|
||||
} else if (roll7 <= 97) {
|
||||
// List C/U
|
||||
if (hasGuests) draw(pools.specialGuests, 1, 'specialGuests');
|
||||
else {
|
||||
if (Math.random() < 0.5) draw(pools.uncommons, 1, 'uncommons');
|
||||
else draw(pools.commons, 1, 'commons');
|
||||
}
|
||||
} else if (roll7 <= 99) {
|
||||
// List R/M
|
||||
if (hasGuests) draw(pools.specialGuests, 1, 'specialGuests');
|
||||
else {
|
||||
if (Math.random() < 0.5) draw(pools.mythics, 1, 'mythics');
|
||||
else draw(pools.rares, 1, 'rares');
|
||||
}
|
||||
} else {
|
||||
// 100: Special Guest
|
||||
if (hasGuests) draw(pools.specialGuests, 1, 'specialGuests');
|
||||
else draw(pools.mythics, 1, 'mythics'); // Fallback to Mythic
|
||||
}
|
||||
|
||||
// 3. Uncommons (3)
|
||||
draw(pools.uncommons, 3, 'uncommons');
|
||||
|
||||
// 4. Main Rare/Mythic (Slot 11)
|
||||
const isMythic = Math.random() < 0.125;
|
||||
let pickedR = false;
|
||||
|
||||
if (isMythic && pools.mythics.length > 0) {
|
||||
const sel = draw(pools.mythics, 1, 'mythics');
|
||||
if (sel.length) pickedR = true;
|
||||
}
|
||||
|
||||
if (!pickedR && pools.rares.length > 0) {
|
||||
if (!pickedR) {
|
||||
draw(pools.rares, 1, 'rares');
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Land
|
||||
// 5. Land (Slot 12)
|
||||
const isFoilLand = Math.random() < 0.2;
|
||||
if (pools.lands.length > 0) {
|
||||
// For lands, we generally want random basic lands anyway even in finite cubes if possible?
|
||||
// But adhering to 'withReplacement' logic strictly.
|
||||
const res = this.drawCards(pools.lands, 1, namesInPack, withReplacement);
|
||||
if (res.selected.length) {
|
||||
const l = { ...res.selected[0] };
|
||||
if (isFoilLand) l.finish = 'foil';
|
||||
packCards.push(l);
|
||||
if (!withReplacement) {
|
||||
pools.lands = res.remainingPool;
|
||||
namesInPack.add(l.name);
|
||||
}
|
||||
const landPicks = draw(pools.lands, 1, 'lands');
|
||||
if (landPicks.length > 0 && isFoilLand) {
|
||||
const idx = packCards.indexOf(landPicks[0]);
|
||||
if (idx !== -1) {
|
||||
packCards[idx] = { ...packCards[idx], finish: 'foil' };
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Wildcards (2 slots) + Foil Wildcard
|
||||
// 6. Wildcards (Slot 13 & 14)
|
||||
// Standard weights: ~49% C, ~24% U, ~13% R, ~13% M
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const isFoil = i === 1; // 2nd is foil
|
||||
const isFoil = i === 1;
|
||||
const wRoll = Math.random() * 100;
|
||||
let targetPool = pools.commons;
|
||||
let targetKey: keyof ProcessedPools = 'commons';
|
||||
|
||||
if (rarityMode === 'peasant') {
|
||||
if (wRoll > 60) { targetPool = pools.uncommons; targetKey = 'uncommons'; }
|
||||
else { targetPool = pools.commons; targetKey = 'commons'; }
|
||||
} else {
|
||||
if (wRoll > 87) { targetPool = pools.mythics; targetKey = 'mythics'; }
|
||||
else if (wRoll > 74) { targetPool = pools.rares; targetKey = 'rares'; }
|
||||
else if (wRoll > 50) { targetPool = pools.uncommons; targetKey = 'uncommons'; }
|
||||
if (wRoll > 87) targetKey = 'mythics';
|
||||
else if (wRoll > 74) targetKey = 'rares';
|
||||
else if (wRoll > 50) targetKey = 'uncommons';
|
||||
|
||||
let pool = pools[targetKey];
|
||||
// Hierarchical fallback
|
||||
if (pool.length === 0) {
|
||||
if (targetKey === 'mythics' && pools.rares.length) targetKey = 'rares';
|
||||
if ((targetKey === 'rares' || targetKey === 'mythics') && pools.uncommons.length) targetKey = 'uncommons';
|
||||
if (targetKey !== 'commons' && pools.commons.length) targetKey = 'commons';
|
||||
pool = pools[targetKey];
|
||||
}
|
||||
|
||||
let res = this.drawCards(targetPool, 1, namesInPack, withReplacement);
|
||||
|
||||
// FALLBACK LOGIC for Wildcards (Standard Only mostly)
|
||||
// If we failed to get a card from target pool (e.g. rolled Mythic but set has none), try lower rarity
|
||||
if (!res.success && rarityMode === 'standard') {
|
||||
if (targetKey === 'mythics' && pools.rares.length) { res = this.drawCards(pools.rares, 1, namesInPack, withReplacement); targetKey = 'rares'; }
|
||||
else if (targetKey === 'rares' && pools.uncommons.length) { res = this.drawCards(pools.uncommons, 1, namesInPack, withReplacement); targetKey = 'uncommons'; }
|
||||
else if (targetKey === 'uncommons' && pools.commons.length) { res = this.drawCards(pools.commons, 1, namesInPack, withReplacement); targetKey = 'commons'; }
|
||||
}
|
||||
|
||||
if (res.selected.length) {
|
||||
const c = { ...res.selected[0] };
|
||||
if (isFoil) c.finish = 'foil';
|
||||
packCards.push(c);
|
||||
const res = this.drawCards(pool, 1, namesInPack, withReplacement);
|
||||
if (res.selected.length > 0) {
|
||||
const card = { ...res.selected[0] };
|
||||
if (isFoil) card.finish = 'foil';
|
||||
packCards.push(card);
|
||||
if (!withReplacement) {
|
||||
// @ts-ignore
|
||||
pools[targetKey] = res.remainingPool;
|
||||
namesInPack.add(c.name);
|
||||
namesInPack.add(card.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -482,21 +554,21 @@ export class PackGeneratorService {
|
||||
|
||||
packCards.sort((a, b) => getWeight(b) - getWeight(a));
|
||||
|
||||
// ENFORCE SIZE STRICTLY
|
||||
const finalCards = packCards.slice(0, targetSize);
|
||||
|
||||
// Strict Validation
|
||||
if (finalCards.length < targetSize) {
|
||||
if (packCards.length < targetSize) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: packId,
|
||||
setName: setName,
|
||||
cards: finalCards
|
||||
cards: packCards
|
||||
};
|
||||
}
|
||||
|
||||
private drawColorBalanced(pool: DraftCard[], count: number, existingNames: Set<string>, withReplacement: boolean) {
|
||||
return this.drawCards(pool, count, existingNames, withReplacement);
|
||||
}
|
||||
|
||||
// Unified Draw Method
|
||||
private drawCards(pool: DraftCard[], count: number, existingNames: Set<string>, withReplacement: boolean) {
|
||||
if (pool.length === 0) return { selected: [], remainingPool: pool, success: false };
|
||||
|
||||
Reference in New Issue
Block a user