Struttura quando ho bisogno di più istanze di "copia" di una classe base comune?

3

Quindi sto costruendo un gioco di carte in cui ogni carta ha cost , damage e durability , oltre a name e description . Ovviamente si potrebbe pensare di creare una classe Card :

class Card {
    int cost
    int damage
    int durability
    string name
    string decription
}

werewolf = Card(5, 10, 120, 'Werewolf', 'Deal double damage during night')
mage = Card(7, 40, 40, 'Mage', 'Deal damage without taking damage himself')

Ma ora il problema è che ho bisogno di più copie di ognuna di queste istanze. Ogni giocatore nel gioco può avere 2 copie di ogni carta, il che significa fino a 8 copie di ogni carta.

Alcuni attributi sono "statici", vale a dire sempre uguale a tutte le istanze simili, ad esempio name , description e cost . Tuttavia, ogni istanza ha varietà in health e damage ; un mago potrebbe finire per avere più danni dopo che un incantesimo di potenziamento è stato lanciato su di lei, e un altro ha una salute inferiore dopo essere stato danneggiato.

Quale sarebbe un buon modo per implementare e strutturare tale funzionalità? Sottoclassi o qualcosa del genere e come?

    
posta Markus Meskanen 04.02.2017 - 17:12
fonte

4 risposte

4

Cercare di utilizzare la sottoclasse solo per condividere alcuni dati non è consigliabile, specialmente nei giochi. Otterrai un modello di oggetti più complesso senza alcun vantaggio.

Una possibile strategia sarebbe quella di aggiungere una fabbrica che gestisca la creazione delle carte. Questo automatizza la duplicazione.

Per quanto riguarda le proprietà che cambiano, ci sono una varietà di modi in cui questo potrebbe essere gestito. Nella maggior parte dei casi, è probabilmente meglio se le proprietà di modifica non vengono mantenute come variabili di istanza di una carta, ma vengono calcolate al volo dal contesto di gioco. Soprattutto se gli effetti di stato vengono annullati (ad esempio dopo un certo numero di round), reimpostare tutte le statistiche delle carte sui valori precedenti sarebbe altrimenti noioso.

L'osservazione che una "classe" di unità di gioco condivide proprietà comuni è molto comune. Introdurre un tipo oggetto è probabilmente la soluzione migliore. Separiamo ogni istanza di carta con cambiando le proprietà da un tipo di carta, che memorizza le statistiche di default e la descrizione statica:

class CardType {
  int cost
  int damage
  int durability
  string name
  string description
}

class Card {
  CardType type
  int damage
  int durability
  // perhaps add getters/properties for the fields from the type object here
}

L'uso della composizione anziché dell'ereditarietà aggiunge molta flessibilità al tuo sistema. Tuttavia, spostare parti del sistema di tipi in oggetti runtime e rendere tutto più dinamico significa anche più complessità e quindi più fragilità - dovrai bilanciare questi vari fattori nella progettazione.

    
risposta data 04.02.2017 - 17:46
fonte
1

Non creerei sottoclassi se solo i dati differiscono. Memorizza le copie iniziali da qualche parte, ad esempio static Map<CardType,Card> , quindi chiedi al costruttore di prendere un CardType e inizializza Card con i valori della copia iniziale.

    
risposta data 04.02.2017 - 17:23
fonte
1

Non ho esperienza con il particolare dominio del problema, ma mi permetta di condividere alcune idee che, si spera, abbiano almeno un po 'di senso.

Indipendentemente dal dominio del problema, un buon principio di progettazione del software è incapsulare ciò che varia .

Sembra esserci un insieme di proprietà fondamentali di ogni carta che sono immutabili e un altro insieme di proprietà che sono effettivamente mutabili.

Nota: questa è un'indicazione che forse l'astrazione di Card in questo particolare dominio non è abbastanza chiara. Forse hai ancora bisogno di trovare un'astrazione più corretta. Pensa al di fuori della scatola o ottieni una migliore comprensione del dominio del problema e poi probabilmente arriverai alla conclusione che l'astrazione con le proprietà immutabili non è la stessa di quella con quelle mutevoli (es. Card vs Game vs Move o Draw, ecc.)

Sembra che le proprietà immutabili siano intrinseche alla carta stessa, mentre le proprietà mutabili sono correlate ad una particolare istanza del gioco in cui la carta è stata giocata (ad esempio un evento di gioco, mossa di gioco, estrazione di carte, ecc.)

Ad esempio, so che la carta Warewolf ha una salute di 5, ma se registro ogni Move / Event / Draw del gioco, riapplicando il flusso di eventi, posso ricalcolare lo stato attuale di una particolare carta Warewolf.

class Game {
    List<Move> moves;
}

Dove Move può essere

class Move {
  Player player;
  Card card;
}

Ogni volta che giochi una carta, aggiungi un evento di mossa al tuo elenco. Se voglio conoscere lo stato attuale del mondo, posso ricavare una proiezione riapplicando la lista delle mosse. A fini di rendimento, puoi decidere di mantenere l'ultima proiezione in qualche altra struttura di dati all'interno di Game con il vantaggio dato che una volta che il gioco finisce, puoi scartarlo e ricominciare tutto da capo.

Map<Player, CurrentStateOfHisWorld>

Sembrerebbe che il gioco possa essere interamente pensato in modo funzionale, utilizzando strutture di dati totalmente immutabili e forse espresso come una serie di eventi sotto forma di carte o carte giocate, e mentre gli eventi accadono in un nuovo stato del mondo è definito.

Sto avendo un senso?

    
risposta data 05.02.2017 - 00:04
fonte
0

Suggerisco di considerare tutti i valori delle carte come valori di base fissi. È quindi possibile creare classi separate per buff e debuff e calcolare i valori effettivi di danno e salute.

class Stats {
    final int damage; // final = cannot be changed
    final int durability;
}

class Card {
     final String name;
     final String description;
     final Stats stats;
}

interface Effect {
    public Stats apply(Stats stats);
}

class Injury implements Effect {
    public Stats apply(Stats stats) {
        return new Stats(stats.getDamage(), stats.getDurability() - 10);
    }
}

class DamageBuff implements Effect {
    public Stats apply(Stats stats) {
        return new Stats(stats.getDamage() * 2, stats.getDurability());
    }
}

public void example() {
    Card card = ...;
    List<Effect> effects = ...;

    Stats stats = card.getStats();
    for (Effect effect : effects) {
        stats = effect.apply(stats);
    }

    int effectiveDamage = stats.getDamage();
}

Questo è solo un semplice esempio. Il tuo gioco potrebbe richiedere qualche logica aggiuntiva (ad esempio, per esempio, assicurandoti che alcuni effetti siano applicati prima degli altri), ma penso che il principio generale possa ancora essere utilizzato.

    
risposta data 05.02.2017 - 01:43
fonte

Leggi altre domande sui tag