CS_LEVEL_21_SOLUTION - OnlyCook/abitur-elite-code GitHub Wiki

Level 21 – Musterlösung: Gerätesteuerung (Command-Parsing)

Lösung

public class SmartHomeServer
{
    private int port;
    private List<Licht> lichter;
    private ServerSocket server;
 
    public SmartHomeServer(int port)
    {
        this.port = port;
        this.lichter = new List<Licht>();
        this.lichter.Add(new Licht('A'));
        this.lichter.Add(new Licht('B'));
        this.lichter.Add(new Licht('C'));
        this.server = new ServerSocket(port);
    }
 
    public void StartServer()
    {
        Socket clientSocket = server.Accept();
 
        string befehl = "";
        while (befehl != "QUIT")
        {
            befehl = clientSocket.ReadLine();
 
            if (befehl.StartsWith("TOGGLE_LIGHT;"))
            {
                string idString = befehl.Substring(13);
                char id = idString[0];
                int asciiWert = (int)id;
 
                if (asciiWert >= 65 && asciiWert <= 90)
                {
                    Licht gefunden = null;
                    foreach (Licht l in lichter)
                    {
                        if (l.GetBezeichnung() == id)
                        {
                            gefunden = l;
                            break;
                        }
                    }
 
                    if (gefunden != null)
                    {
                        gefunden.Toggle();
                        clientSocket.Write("+OK\n");
                    }
                    else
                    {
                        clientSocket.Write("-ERR Licht nicht gefunden\n");
                    }
                }
                else
                {
                    clientSocket.Write("-ERR ungültige ID\n");
                }
            }
        }
 
        clientSocket.Close();
    }
}
 
public class Licht
{
    private char bezeichnung;
    private bool istAn;
 
    public Licht(char bez)
    {
        this.bezeichnung = bez;
        this.istAn = false;
    }
 
    public void Toggle()
    {
        istAn = !istAn;
    }
 
    public char GetBezeichnung()
    {
        return bezeichnung;
    }
}

Erklärung

Licht – eine einfache Geräteklasse

public class Licht
{
    private char bezeichnung;
    private bool istAn;
 
    public Licht(char bez)
    {
        this.bezeichnung = bez;
        this.istAn = false;
    }
    // ...
}

char ist ein Datentyp für ein einzelnes Zeichen – also genau ein Buchstabe, eine Zahl oder ein Symbol. 'A' (mit einfachen Anführungszeichen) ist ein char, "A" (mit doppelten) wäre ein string.

Toggle() – ein Bool umkehren

public void Toggle()
{
    istAn = !istAn;
}

! ist der logische Negationsoperator. Er kehrt einen bool-Wert um: !true ergibt false, und !false ergibt true. istAn = !istAn liest sich also als: „Setze istAn auf das Gegenteil seines aktuellen Werts." War die Lampe an, ist sie danach aus – und umgekehrt.


Der Konstruktor von SmartHomeServer

this.lichter = new List<Licht>();
this.lichter.Add(new Licht('A'));
this.lichter.Add(new Licht('B'));
this.lichter.Add(new Licht('C'));

Die drei Lichter werden direkt im Konstruktor der Liste hinzugefügt. Das ist die klassische und lesbarste Variante.

Für Fortgeschrittene: In C# gibt es eine kürzere Schreibweise mit Objekt-Initialisierern:

this.lichter = new List<Licht>()
{
    new Licht('A'),
    new Licht('B'),
    new Licht('C')
};

oder noch kompakter mit new() und in einer Zeile:

this.lichter = new() { new('A'), new('B'), new('C') };

Da der Typ schon aus der Deklaration bekannt ist, kann der Compiler ihn selbst ableiten. Im Abitur ist die explizite Variante mit Add() immer die sicherste Wahl.


StartServer() – Befehl auslesen und die ID extrahieren

string idString = befehl.Substring(13);
char id = idString[0];

"TOGGLE_LIGHT;" hat genau 13 Zeichen. Substring(13) (dasselbe wie Substring(0, 13)) liefert alles danach, also z.B. aus "TOGGLE_LIGHT;A" entnimmt es "A". Mit [0] greift man auf das erste (und hier einzige) Zeichen zu und erhält einen char ('A').


ASCII-Validierung: char als Zahl

int asciiWert = (int)id;
if (asciiWert >= 65 && asciiWert <= 90)

Das ist das Kernkonzept dieses Levels: Ein char ist intern nichts anderes als eine kleine ganze Zahl – nämlich der ASCII-Wert des entsprechenden Zeichens. Jedes Zeichen hat eine eindeutige Nummer in der ASCII-Tabelle, z.B. 'A' = 65, 'B' = 66, ..., 'Z' = 90.

Mit dem Cast (int)id macht man diese Zahl explizit sichtbar (es wird zu einem int). Dann lässt sich bequem prüfen, ob der Wert im Bereich 65–90 liegt – also ob es ein Großbuchstabe ist.

Alternativ: Da char direkt mit Zeichen verglichen werden kann, funktioniert auch id >= 'A' && id <= 'Z'. C# vergleicht dabei intern genau die ASCII-Werte – das Ergebnis ist identisch. Im Abitur ist der explizite Weg über (int) und die numerischen Grenzen aus der ASCII-Tabelle jedoch der erwartete Weg.


Die Suchroutine mit foreach

Licht gefunden = null;
foreach (Licht l in lichter)
{
    if (l.GetBezeichnung() == id)
    {
        gefunden = l;
        break;
    }
}
 
if (gefunden != null)
{
    gefunden.Toggle();
    clientSocket.Write("+OK\n");
}
else
{
    clientSocket.Write("-ERR Licht nicht gefunden\n");
}

Das Muster hier ist klassisch: Eine Variable gefunden wird mit null vorbelegt. Die foreach-Schleife iteriert über alle Lichter – wird ein passendes gefunden, wird es in gefunden gespeichert und die Schleife mit break sofort verlassen. Danach reicht eine einzige null-Prüfung, um zu entscheiden, ob die Suche erfolgreich war.

Dieses „Suche mit Ergebnisvariable"-Muster taucht im Abitur regelmäßig auf – es lohnt sich, es verinnerlicht zu haben.

⚠️ **GitHub.com Fallback** ⚠️