gioco di carte Hanabi: strutturare correttamente il codice del mazzo

1

Sto cercando di migliorare la mia comprensione e la capacità di scrivere codice che utilizza principi e pratiche raccomandati, come i principi SOLID. Per fare questo, sto implementando il gioco di carte pirotecniche Hanabi .

Hanabi ha due tipi di mazzo, Standard e Avanzato (che ha un set extra di carte MultiColour ). Ogni carta di un mazzo sto usando un DeckFactory per costruire un Deck . Sto cercando feedback su come la struttura del codice potrebbe essere migliorata, principalmente sulla relazione tra come la classe Deck e come le due classi derivate ( StandardDeck e AdvancedDeck ) siano correlate tra loro e la loro costruzione usando il DeckFactory .

Uno dei miei dubbi è che AssembleDeck (nella classe genitore Deck ) deve essere chiamato da ogni classe derivata e se deve essere presente nella classe Deck .

Una seconda preoccupazione è l'uso di un Stack per tenere le carte; è più appropriato pescare una carta dal mazzo, ma forse meno per quando il mazzo viene mischiato. Sto pensando di usare l'algoritmo Fisher-Yates per mischiare.

Mi sto anche chiedendo se c'è qualcosa nella struttura del codice che deve essere cambiato in modo che i test unitari siano più facili da scrivere.

L'oggetto della carta:

public enum Suit { White, Red, Yellow, Green, Blue, MultiColour }
public enum Number { One, Two, Three, Four, Five }

public class FireworksCard {
    public Suit Suit { get; }
    public Number Number { get; }

    public FireworksCard(Suit suit, Number number) {
        Suit = suit;
        Number = number;
    }
}

Struttura del mazzo:

public interface IDeck {
    FireworksCard DrawCard();
    void Shuffle();
}

public abstract class Deck : IDeck {
    protected Stack<FireworksCard> Cards;

    public bool IsEmpty => !Cards.Any();

    public abstract IEnumerable<Suit> Suit { get; }
    public abstract IEnumerable<Number> Numbers { get; }

    protected void AssembleDeck() {
        Cards = new Stack<FireworksCard>();

        // generate a card for every combination
        foreach (var number in Numbers) {
            int cardRepetitions = GetCardRepetitions(number);

            for (int i = 0; i < cardRepetitions; i++) {
                GenerateAllSuitsForNumber(number);
            }
        }
    }

    private int GetCardRepetitions(Number number) {
        switch (number) {
            case Number.One:
                return 3;
            case Number.Five:
                return 1;
            default:
                return 2;
        }
    }

    private void GenerateAllSuitsForNumber(Number number) {
        foreach (var suit in Suits) {
            Cards.Push(new FireworksCard(suit, number));
        }
    }

    public void Shuffle() {
        // Randomly rearrange the order of Cards
    }

    public FireworksCard DrawCard() {
        return Cards.Pop();
    }
}

public class StandardDeck : Deck {
    public override IEnumerable<Suit> Suits => Enum.GetValues(typeof(Suit)).Cast<Suit>().Except(new[] { Suit.MultiColour });
    // What's better, this (Number[])Enum.GetValues(typeof(Number)); or this
    public override IEnumerable<Number> Numbers => Enum.GetValues(typeof(Number)).Cast<Number>();

    public StandardDeck() {
        AssembleDeck();
    }
}

public class AdvancedDeck : Deck {
    public override IEnumerable<Suit> Suits => Enum.GetValues(typeof(Suit)).Cast<Suit>();
    public override IEnumerable<Number> Numbers => Enum.GetValues(typeof(Number)).Cast<Number>();

    public AdvancedDeck() {
        AssembleDeck();
    }
}

The Deck factory:

public enum GameType { Standard, Advanced }

public class DeckFactory {
    public IDeck BuildDeck(GameType gameType) {
        switch (gameType) {
            default:
            case GameType.Standard:
                return BuildStandardDeck();
            case GameType.Advanced:
                return BuildAdvancedDeck();
        }
    }

    private IDeck BuildStandardDeck() {
        return new StandardDeck();
    }

    private IDeck BuildAdvancedDeck() {
        return new AdvancedDeck();
    }
}
    
posta Ayb4btu 06.11.2016 - 06:57
fonte

1 risposta

3

Osservando i frammenti di codice, direi che non è necessario che Deck s sia così complicato mentre si usano sia l'interfaccia che l'ereditarietà. Se l'unica differenza tra i mazzi è che il mazzo Advanced ha carte MultiColour , allora hai una singola classe Deck e due modi diversi di istanziarla. Non c'è bisogno di ereditarietà se il loro comportamento è esattamente lo stesso.

Un'altra cosa che vorrei dire è che ti stai concentrando su una cosa completamente sbagliata. Dovresti lavorare sulla codifica delle regole del gioco in classi. Non preoccuparti di Deck s. Dovresti concentrarti su come le carte interagiscono con la scacchiera e tra loro.

Se dovessi farlo con lo stile top-down TDD , inizierei con Game class, quindi vorrei passare attraverso la regola e implementare ogni regola come test singoli / multipli su la classe Game . Se scopro che c'è un'astrazione da fare, la estraggo e rifatto il codice. Quello che hai fatto è passare molto tempo su un problema per lo più irrilevante e implementarlo nel modo più complicato.

    
risposta data 06.11.2016 - 08:28
fonte

Leggi altre domande sui tag