← cd ../blog

Arhitectura unui engine flexibil de jocuri de cărți

Arhitectura unui engine flexibil de jocuri de cărți — imagine de copertă

Dezvoltarea unei aplicații care găzduiește mai multe jocuri de cărți sub aceeași umbrelă logică este o provocare de design software. Nu vrei să scrii cod separat pentru fiecare joc; vrei un engine care să consume un “registru de variante”. În proiectul crazy-eights, am abordat această problemă prin crearea unui sistem bazat pe reguli, capabil să gestioneze de la clasica Șeptică până la variante complet customizabile.

Registrul de variante: Inima sistemului

Fiecare joc din aplicație este definit printr-un obiect de configurare care dictează comportamentul motorului de joc. Această structură include numele variantei, numărul de cărți împărțite inițial și, cel mai important, dicționarul de “cărți speciale”.

const variants = {
  macao: {
    id: 'macao',
    initialCards: 5,
    specialCards: {
      'A': { action: 'skip_turn' },
      '2': { action: 'draw_two' },
      'J': { action: 'change_color' }
    }
  },
  septica: {
    id: 'septica',
    initialCards: 4,
    specialCards: {
      '7': { action: 'take_all' },
      '10': { action: 'clear_table' }
    }
  }
};

Editorul de reguli proprii

Cea mai interesantă parte a fost implementarea unui editor care permite utilizatorilor să își definească propriile jocuri. Provocarea principală aici este validarea. Trebuie să ne asigurăm că utilizatorul nu definește reguli contradictorii. De exemplu, o carte nu poate avea două acțiuni diferite pentru aceeași culoare.

Am implementat o funcție de validare care verifică integritatea regulilor înainte de a le salva în baza de date:

function validateCustomRule(rule) {
  const errors = [];
  
  if (rule.specialCards.some(c => c.rank === 'Joker' && c.color === 'All')) {
    errors.push({ code: 'INVALID_JOKER_CONFIG', message: 'Jokerul nu poate fi limitat la o singură culoare.' });
  }

  // Verificăm duplicatele care ar putea fi înghițite de primul match
  const seen = new Set();
  rule.specialCards.forEach(card => {
    const key = `${card.rank}-${card.color}`;
    if (seen.has(key)) {
      errors.push({ code: 'DUPLICATE_CARD_RULE', message: `Regula pentru ${key} este definită de două ori.` });
    }
    seen.add(key);
  });

  return { valid: errors.length === 0, errors };
}

Gestionarea conflictelor: First-match logic

Când avem o carte care îndeplinește mai multe condiții (de exemplu, un 7 de pică într-un joc unde 7 este special, dar și pica are o valoare), motorul aplică o strategie de first-match.

În codul nostru de backend, procesăm validarea mutării astfel:

  1. Verificăm dacă cartea jucată are o acțiune definită în specialCards specifică pentru acel rang.
  2. Dacă nu, verificăm dacă se potrivește cu culoarea sau rangul cărții de pe masă.

Această ierarhie previne comportamentele imprevizibile. Dacă un utilizator definește o regulă pentru “7 de pică” în editor, acea regulă va avea prioritate absolută față de regula generală de “7” sau “pica”, evitând astfel erorile de tipul shadowing în logica de joc.

Concluzie

Construirea unui sistem modular pentru jocuri de cărți nu înseamnă doar să pui cărți pe ecran, ci să creezi un limbaj prin care utilizatorul să poată exprima orice variantă de joc. Prin decuplarea regulilor de motorul de randare, am reușit să integrăm 7 jocuri diferite în crazy-eights fără a dubla logica de bază. Data viitoare, vom discuta despre cum optimizăm sincronizarea acestor reguli în timp real prin WebSockets.