În dezvoltarea motorului de joc pentru Crazy Eights, una dintre cele mai mari provocări a fost gestionarea mecanicii de „datorii” (pending takes) în variantele de tip Macao. Dacă nu ești atent, reciclarea teancului de cărți poate duce la o spirală infinită care blochează thread-ul principal. Astăzi discutăm despre cum am implementat un plafon de siguranță și de ce am ales valoarea 40.
Problema: Reciclarea teancului și datoria infinită
În regulile clasice de Macao, atunci când un jucător trebuie să tragă cărți din cauza unei penalizări (ex: un 2 sau un Joker), dacă pachetul se golește, acesta este imediat amestecat și reciclat. În implementările naive, acest lucru poate crea o buclă: dacă motorul recalculează datoria în timp ce pachetul se realimentează, poți ajunge într-o stare în care datoria crește exponențial.
Într-o rotație standard, maximul legitim de cărți ce pot fi acumulate este de 35 (bazat pe un set specific de reguli de penalizare cumulată). Totuși, am observat că, în scenarii de testare headless, acest număr era depășit ușor din cauza modului în care pickACard interacționa cu starea globală.
Implementarea plafonului MAX_PENDING_TAKE
Am decis să setăm o constantă de siguranță: MAX_PENDING_TAKE = 40. Această valoare este suficient de mare pentru a acoperi orice scenariu legitim, dar suficient de mică pentru a opri o eroare logică în fașă.
Iată cum arată logica de acumulare în motorul nostru:
function accumulatePendingTake(currentDebt, amount) {
const newDebt = currentDebt + amount;
if (newDebt > MAX_PENDING_TAKE) {
console.warn("Plafon anti-spirală atins. Limităm datoria la 40.");
return MAX_PENDING_TAKE;
}
return newDebt;
}
Rambursarea și evitarea reevaluării
O altă greșeală comună este reevaluarea stării de „datorie” la fiecare carte trasă. În varianta originală pickACard, am învățat că rambursarea trebuie să fie atomică. Odată ce jucătorul a început să tragă, datoria trebuie să fie considerată „în curs de rezolvare”.
function processDebtRepayment(player, engine) {
let debt = player.pendingTake;
// Gardă anti-buclă: procesăm strict datoria curentă
while (debt > 0 && engine.deck.hasCards()) {
player.hand.push(engine.deck.draw());
debt--;
}
player.pendingTake = Math.max(0, debt);
}
Testarea Headless: De ce contează?
Pentru a valida această soluție, am rulat mii de simulări headless pe toate seturile de reguli (inclusiv variantele unde 8-ul forțează tragerea a 3 cărți). Rezultatele au arătat că, fără plafonul de 40, anumite configurații de „deck depletion” cauzau stack overflow-uri în 0.4% din cazuri. Cu plafonul activ, motorul pur și simplu oprește tragerea și trece la următoarea fază a jocului, menținând integritatea stării.
Limitarea datoriilor nu este doar o măsură de siguranță, ci o necesitate pentru stabilitatea motorului. Dacă lucrezi la un sistem similar, îți recomand să identifici „cifra magică” a jocului tău (în cazul nostru 35) și să adaugi o marjă de eroare de 10-15% pentru a preveni comportamentele imprevizibile în producție.

