Stadt, Land, Fluss (2)

ℹ️
Diese Aufgabe baut nicht auf Stadt, Land, Fluss auf, sondern ist unabhängig voneinander bearbeitbar. Sie sollten Stadt, Land, Fluss vor dem Bearbeiten von Stadt, Land, Fluss (2) dennoch bereits bearbeitet haben. Ansonsten ist es sinnvoller, zunächst Stadt, Land, Fluss zu bearbeiten.
⚠️
Mit den Checks können Sie überprüfen, ob Sie die gelernten Konzepte eigenständig anwenden und eine Problemstellung algorithmisch zerlegen und in Code umsetzen können. Wenn Sie die vorhergehenden Übungen noch nicht bearbeitet oder verstanden haben, nutzen Sie die Zeit lieber zum Nacharbeiten. Weitere Informationen finden Sie unter Checks.

Bei dem Spiel “Stadt, Land, Fluss” (Wikipedia) muss vor jeder Runde ein Buchstabe bestimmt werden. Bei der gängigsten Methode geht ein Spieler das Alphabet im Kopf durch, bis ein zweiter Spieler “Stopp” ruft. Um dem Spieler, der das Alphabet durchgeht, weniger Manipulationsmöglichkeit zu geben, kann auch ein Computerprogramm einen zufällig ausgewählten Buchstaben ausgeben.

Erstellen Sie ein Programm, das für das oben beschriebene Szenario für jede Runde einer Spielesession (d.h. eines Programmdurchlaufs) “auf Knopfdruck” einen zufällig ausgewählten Buchstaben ausgibt. Beachten Sie, dass für dieses Szenario jeder Buchstabe höchstens ein Mal pro Spielesession (d.h. Programmdurchlauf) ausgegeben werden darf.

ℹ️
Lesen Sie vor dem Bearbeiten der Aufgabe die Informationen in den folgenden Abschnitten.

Details zur Umsetzung

Verhalten bei Ein- und Ausgabe

Wird das Programm ausgeführt, soll der Nutzer zunächst zu einer Eingabe über get_string() aufgefordert werden:

Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 

Drückt der Nutzer auf die Eingabetaste, d.h. übergibt eine leere Eingabe, soll ein Buchstabe generiert und auf der Konsole zusammen mit der aktuellen Rundenanzahl ausgegeben werden, z.B.:

Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 1: F
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 2: S
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
[...]

Eine Überprüfung der Eingabe ist jedoch nicht erforderlich. Wenn ein Nutzer z.B. “sdfjhk” eingibt, kann auch einfach ein neuer Buchstabe ausgegeben werden:

Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: sdfjhk
Runde 1: T
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: Hallo
Runde 2: I
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
[...]

Gibt der Nutzer vor dem Drücken der Eingabetaste jedoch “Ende” oder “x” ein, soll das Programm beendet werden:

Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 1: A
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: Ende
stadtlandfluss2/ $

Das Programm sollte auch dann beendet werden, wenn jeder Buchstabe im Alphabet, d.h. in unserem Fall 26, einmal ausgegeben wurde. Das bedeutet, dass die maximale Anzahl der Runden in unserem Fall 26 ist:

[...]
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 25: U
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 26: Q
stadtlandfluss2/ $

Skizzieren Sie eine erste grobe Vorgehensweise in Pseudocode, um ein solches Programm zu erstellen. Sie können dabei annehmen, dass Sie bereits eine Funktion get_letter(), die einen Buchstaben basierend auf einer pseudo-zufälligen Zahl zurückgibt, zur Verfügung haben. So können Sie sich zunächst auf das Grundgerüst, d.h. den grundlegenden Programmablauf, konzentrieren. Wie Sie eine derartige Funktion konkret implementieren, können Sie sich später überlegen.

Hinweise zum Generieren von zufälligen Zahlen

In der Aufgabe Vererbung haben Sie bereits einen sogenannten pseudo-zufälligen Zahlengenerator kennengelernt. Vermutlich werden Sie die zufällig generierte Zahl, die von diesem zurückgegeben wird, in eine Zahl zwischen 0 und 25 umwandeln wollen.

Überlegen Sie anhand der Verwendung von rand() und srand() in Vererbung, wie Sie das dortige Vorgehen für diese Aufgabe adaptieren können. Sie können sich alternativ auch den Handbucheintrag zur Verwendung von rand() anschauen. Dort finden Sie ein alternatives Vorgehen. Wichtig: Vor dem Aufrufen von rand() sollte einmalig srand() aufgerufen werden. Beachten Sie insbesondere, mit welchem Wert srand() im gezeigten Beispiel aufgerufen wird.

⚠️
Achtung: Die Links verweisen jeweils auf den Handbucheintrag zu random() und srandom(), da diese vom CS50 Team zur besseren Zugänglichkeit bereits aufbereitet worden sind. Die relevanten Informationen treffen sowohl auf random()/rand() als auch auf srandom()/srand() zu. Einziger Unterschied abgesehen von dem Namen: random() gibt einen long zurück, rand() einen int. Zudem ist für die Verwendung von rand() und srand() lediglich eine Einbindung von <stdlib.h> erforderlich.

Das Problem der Einmaligkeit

Nun zum etwas schwierigeren Teil: Das Problem mit zufällig bestimmten Zahlen bzw. Buchstaben ist, dass diese nun einmal zufällig bestimmt sind. D.h. es ist nahezu ausgeschlossen, dass der wiederholte Aufruf einer Funktion wie get_letter(), die auf einem pseudo-zufälligen Zahlengenerator basiert, dazu führt, dass jeder Buchstabe genau ein Mal zurückgegeben wird. Dieses Verhalten müssen Sie erzwingen. Ihr Programm muss sich also merken, welche Buchstaben bereits ausgegeben wurden und sicherstellen, dass ein bereits gemerkter Buchstabe kein zweites Mal ausgegeben wird. Überlegen Sie sich mit Ihrem vorhandenen Wissen aus den vorhergehenden Übungen ein dazu geeignetes Vorgehen.

Hinweise zur Implementierung

Implementieren Sie das Programm in C in einer Datei namens stadtlandfluss_2.c in einem Ordner namens stadtlandfluss_2.

Achten Sie auf gutes Design und angemessene Abstraktion, indem Sie zusammengehörige Funktionalität in Funktionen kapseln. Sie sollten im Rahmen dieser Aufgabe mindestens eine Funktion schreiben. Wenn Ihr Programm funktioniert, überprüfen Sie, ob Sie Ihren Code in Bezug auf Struktur und Lesbarkeit verbessern können.

⚠️
Aufgrund der Charakteristik der Aufgabe besteht die Gefahr, bei Fehlern in der Implementierung in eine Endlosschleife zu geraten. Wenn das Terminal nach dem Ausführen Ihres Programms hängen bleibt, können Sie das laufende Programm mit STRG+C beenden.

Testen

Ihr Programm sollte sich hinsichtlich der Ein- und Ausgabe wie unter Verhalten bei Ein- und Ausgabe gezeigt verhalten.

Wichtig ist auch, dass die Buchstaben, die nacheinander ausgegeben werden, nicht bei jedem Programmaufruf gleich sind. Wie zum Beispiel hier:

stadtlandfluss2/ $ ./stadtlandfluss2
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 1: N
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 2: W
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 3: L
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: x
stadtlandfluss2/ $ ./stadtlandfluss2
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 1: N
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 2: W
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 3: L
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: x
stadtlandfluss2/ $ ./stadtlandfluss2
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 1: N
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 2: W
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: 
Runde 3: L
Eingabetaste --> Nächste Runde starten, 'Ende' oder 'x' --> Programm beenden: x
stadtlandfluss2/ $ 

Schauen Sie sich in diesem Fall noch einmal die Verwendung von srand() im Handbuch an.

Lassen Sie das Programm die Buchstaben für alle 26 Runden ausgeben und achten Sie darauf, ob Sie mehrfach ausgegebene Buchstaben bemerken. Wiederholen Sie dies gegebenenfalls einige Male. Wenn Sie ein sinnvolles Vorgehen gefunden und implementiert haben, um die Einmaligkeit der ausgegebenen Buchstaben zu erzwingen, sollte Ihr Programm - wenn es kompiliert und bei der Ausführung nicht in einer Endlosschleife hängen bleibt - auch in dieser Hinsicht normalerweise korrekt funktionieren, so dass Sie nicht alle 26 ausgegebenen Buchstaben genauestens auf Einmaligkeit vergleichen müssen.