Dienstag, 22. Dezember 2015

Akka.NET Adventskalender – Tür 22

Lass mal hören

Gestern haben wir die Idee zur Lösung unseres Sudoku Spiels entwickelt (Jeder schreit herum, welche Ziffer er eben erhalten hat, andere ziehen daraus ihre Schlüsse).

Dazu müssen wir unser Feld aus 9x9 Aktoren aufbauen. Dabei müssen wir in keinster Weise daran denken, die einzelnen Aktoren miteinander zu verbinden, denn wir setzen ja auf das Muster "Publish/Subscribe", so dass Mitteilungen einfach abgesetzt werden und interessierte sich für den Empfang von Nachrichten anmelden. Damit ist die Konstruktion unseres Spielfeldes extrem einfach:

for (int row = 0; row < 9; row++)
    for (int col = 0; col < 9; col++)
        system.ActorOf(
            Props.Create(printer, row, col), 
            String.Format("{0}-{1}", row, col));

Die nahfolgend gelistete SudokuCell Klasse zeigt die wesentlichen Teile der Implementierung der Sudoku Zelle. Die einzige Nachricht, die wir bislang definiert haben, war SetCell, die das Kommando ist, mit dem wir die Ziffer einer Zelle direkt setzen können. Alle anderen Aktoren registrieren sich für diese Nachricht und verarbeiten sie, wenn die eben gesetzte Zelle in der gleichen Zeile, der gleichen Spalte oder im gleichen 3x3 Block sitzt.

Dazu nutzen wir eine spezielle Überladung der Receive<> Methode, die es uns erlaubt, ein Prädikat mit anzugeben, welches nur dann einen wahren Rückgabewert liefert, wenn wir uns für die Verarbeitung der Nachricht interessieren. Klingt in Worten schlimmer als im Code, den Du gleich sehen wirst. Die ersten Nachrichten vom Typ SetCell werden beim eintragen der berets bekannten Angaben unseres Sudokus ausgelöst. Damit allerdings werden weitere Zellen beeinflusst und das Sudoku löst sich so mit jeder zusätzlichen Angabe immer weiter.

public class SudokuCell : SudokuActor
{
    private readonly int row;
    private readonly int col;
    private readonly int block;
    private HashSet<int> possibleDigits;
 
    public SudokuCell(IActorRef printer, int row, int col)
        : base(printer)
    {
        this.row = row;
        this.col = col;
        this.block = row / 3 * 3 + col / 3;
  
        possibleDigits = new HashSet<int>(Enumerable.Range(1,9));
  
        Receive<SetDigit>(RestrictPossibilities, IsRowOrColOrBlock);
        Receive<SetDigit>(SolveCell, IsMyCell);
    }
 
    private bool IsRowOrColOrBlock(SetDigit setDigit)
    {
        if (IsMyCell(setDigit))
            return false;

        return setDigit.Row == row
            || setDigit.Col == col
            || setDigit.Block == block;
    }
 
    private bool IsMyCell(SetDigit setDigit)
    {
        return setDigit.Row == row && setDigit.Col == col;
    }


    private void RestrictPossibilities(SetDigit setDigit)
    {
        var digit = setDigit.Digit;
  
        if (possibleDigits.Contains(digit))
        {
            possibleDigits.Remove(digit);
            if (possibleDigits.Count == 1)
                Publish(new SetDigit(row, col, possibleDigits.First()));
        }
    }
    
    private void SolveCell(SetDigit setDigit)
    {
        var digit = setDigit.Digit;
  
        possibleDigits.Clear();
        possibleDigits.Add(digit);
    }
 
    private bool IsSolved(int digit)
    {
        return possibleDigits.Contains(digit) && possibleDigits.Count == 1;
    }
}

Eigentlich war es das. Gut, es kommen noch ein paar Kleinigkeiten dazu, damit das ganze tatsächlich funktioniert. Aber bevor wir solche Dinge herunterleiern und Du mühsam alles zusammentragen musst, lade Dir lieber den Code von meinem github Repository.

Wenn Du experimentierfreudig bist, und in der Kommandozeilen-Anwendung versuchst, das schwierige Soduko auflösen zu lassen, wirst Du eine Überraschung erleben – es löst sich nicht. Unser Ansatz war also doch zu einfach.

Müssen wir also morgen nochmal ran.

Keine Kommentare:

Kommentar veröffentlichen