Logikbausteine in Seeing#

Neulich habe ich darüber geschrieben, wie Seeing# das Messenger-Pattern implementiert. Im Rahmen eines kleinen Memory-Spiels habe ich die letzten Tage damit einige kleine Logig-Komponenten umgesetzt, an denen das Pattern relativ gut funktioniert. In diesem Artikel geht es beispielsweise um eine Komponente, welche sich um das Aufdecken von Karten und das Erkennen von richtigen oder falschen Paaren kümmert und darauf entsprechend die Karten wieder verdeckt oder eine Folgelogik wie z. B. „Punktzahl hochzählen“ antriggert. Der Screenshot links gibt schon mal eine grobe Übersicht über die hier eingesetzten Klassen.

Grundsätzlich ist es ganz einfach, die Klasse PairUncoverLogic enthält hier im Prinzip die volle Spielelogik. Die Nachrichten bilden jeweils ein Ereignis ab, so sagt die Nachricht MainMemoryScreenEnteredMessage unserer Komponente, dass sich der Spieler ab diesem Zeitpunkt im Memory-Spiel befindet. Aus diesem Grund werden in der Implementierung (siehe Coding unten) beim Empfang dieser Nachricht alle Member zurückgesetzt. Ähnlich verhält es sich bei der Nachricht ObjectsClickedMessage. Hier wird, je nach aktuellem Zustand der Logik-Komponente, die ausgewählte Karte umgedreht und danach entsprechend die Nachricht CardUncoveredByPlayerMessage ausgelöst. Diese wiederum triggert die Prüflogik, ob aktuell ein Paar von Karten aufgedeckt ist oder eben nicht. Entsprechend werden die Karten danach wieder verdeckt oder die Nachricht CardPairUncoveredMessage ausgelöst. Hier das gesamte Coding dieser Klasse [1].

/// <summary>
/// This component is responsible for uncovering the cards by the player. 
/// </summary>
public class PairUncoverLogic : SceneLogicalObject
{
    private List<Card> m_uncoveredCards;
    private bool m_currentlyUncovering;

    /// <summary>
    /// Initializes a new instance of the <see cref="PairUncoverLogic"/> class.
    /// </summary>
    public PairUncoverLogic()
    {
        m_uncoveredCards = new List<Card>();
    }

    /// <summary>
    /// Called when the player enters the main memory screen.
    /// </summary>
    private void OnMessage_Received(MainMemoryScreenEnteredMessage message)
    {
        m_currentlyUncovering = false;
        m_uncoveredCards.Clear();
    }

    /// <summary>
    /// Called when an object was clicked.
    /// </summary>
    private async void OnMessage_Received(ObjectsClickedMessage message)
    {
        Card selectedCard = message.ClickedObjects
            .FirstOrDefault() as Card;
        if (selectedCard == null) { return; }
        if (selectedCard.CountRunningAnimations > 0) { return; }
        if (selectedCard.IsCardUncovered) { return; }
        if (selectedCard.Pair.IsUncovered) { return; }
        if (m_currentlyUncovering) { return; }

        // Perform uncover animation
        m_currentlyUncovering = true;
        try
        {
            selectedCard.AnimationHandler.CancelAnimations();
            await selectedCard.BuildAnimationSequence()
                .MainScreen_PerformUncover()
                .ApplyAsync();

            selectedCard.IsCardUncovered = true;
        }
        finally
        {
            m_currentlyUncovering = false;
        }

        // Notify uncovered card
        this.Messenger.Publish(new CardUncoveredByPlayerMessage(selectedCard));
    }

    /// <summary>
    /// Called when a single card was uncovered by the player.
    /// </summary>
    private async void OnMessage_Received(CardUncoveredByPlayerMessage message)
    {
        m_uncoveredCards.EnsureDoesNotContain(message.Card, "m_uncoveredCards");
        m_uncoveredCards.EnsureCountInRange(0, 1, "m_uncoveredCards");

        m_uncoveredCards.Add(message.Card);
        if (m_uncoveredCards.Count < 2) { return; }

        // Select correspondig pairs of uncovered cards
        CardPair[] pairs = m_uncoveredCards
            .Select((actCard) => actCard.Pair)
            .Distinct()
            .ToArray();
        pairs.EnsureCountInRange(1, 2, "pairs");

        m_currentlyUncovering = true;
        try
        {
            if (pairs.Length == 1)
            {
                // Yeah, we found a correct pair! Trigger movie
                CardPair selectedPair = pairs[0];
                selectedPair.IsUncovered = true;

                this.Messenger.Publish(new CardPairUncoveredByPlayerMessage(selectedPair));
            }
            else
            {
                // Bad.. bad try, trigger uncovering of the cards
                await Task.Delay(500);

                Task[] recoverTasks = new Task[m_uncoveredCards.Count];
                for(int loop=0 ; loop<m_uncoveredCards.Count; loop++)
                {
                    Card actCard = m_uncoveredCards[loop];
                    actCard.IsCardUncovered = false;

                    recoverTasks[loop] = actCard.BuildAnimationSequence()
                        .MainScreen_PerformCover()
                        .ApplyAsync();
                }
                await Task.WhenAll(recoverTasks);
            }
        }
        finally
        {
            m_uncoveredCards.Clear();
            m_currentlyUncovering = false;
        }
    }
}

Ich denke, die Klasse sollte sich insgesamt relativ gut lesen und auch verstehen lassen. Cool an der Lösung hier finde ich die volle Unterstützung für async-await. An einer Stelle muss man auf eine Animation warten, an anderer Stelle auf ein Delay und so weiter. Alles kein Problem.

Verweise
[1] https://github.com/RolandKoenig/SeeingSharp/blob/master/Games/RKVideoMemory/RKVideoMemory/Game/PairUncoverLogic.cs

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.