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();
}
}