Abitur NRW Informatik 2023 · LK · Haupttermin
Abiturprüfung Informatik in Nordrhein-Westfalen 2023, LK · Haupttermin: 10 Aufgaben mit Lösungen. Mit automatischer Korrektur.
Themen der Prüfung: Objektorientierte Modellierung mit Implementationsdiagrammen · Algorithmen auf Feldern und linearen Listen · Datenbankmodellierung und Normalisierung · SQL-Abfragen · Endliche Automaten · Grammatiken, Kellerautomaten und Grenzen endlicher Automaten · Client-Server-Kommunikation und Protokolle (LK)
Aufgabe 1 - Objektorientierte Modellierung · Objektorientierte Modellierung mit Implementationsdiagrammen
Aufgabenstellung:
Bei Sportturnieren sollen nach Möglichkeit Teams mit ähnlicher Spielstärke aufeinandertreffen, damit die verschiedenen Leistungsniveaus berücksichtigt werden. Deshalb wird nach jeder Runde eine Reihenfolge der Teams gebildet, die ihren bisherigen Erfolg im Turnier widerspiegelt. Auf Grundlage dieser Reihenfolgen werden die Begegnungen nach diesem Prinzip festgelegt:
Bei Beginn des Turniers werden alle Teams in zufälliger Reihenfolge angeordnet.
Nach jeder Partie wird die Reihenfolge der Teams anhand ihrer Punktzahlen angepasst: Die Reihenfolge der Teams ist absteigend nach deren Punktzahl geordnet. Für einen Sieg erhält ein Team 1 Punkt, für eine Niederlage -1 Punkt. Bei einem Unentschieden erhalten beide Teams 0 Punkte.
Um für ein Team A einen Gegner zu bestimmen, wird das Team B als Gegner ausgewählt, das in der Reihenfolge möglichst nah beim Team A ist.
Um zu vermeiden, dass immer wieder die gleichen Teams gegeneinander spielen, wird eine Maximalzahl gleicher Begegnungen festgelegt: Zwei Teams sollen höchstens so häufig wie in dieser Zahl festgelegt gegeneinander spielen.
Das folgende Softwaremodell stellt einen ersten Entwurf zur Verwaltung eines derartigen Turniers dar:
Anlage: Beispieldaten
Teams (in dieser Reihenfolge im Feld teams):
SF Forst
SG Heide
TV Feld
SSV Bergen
TuS Wiese
SV Wald
Spiele:
Runde 1:
SF Forst – SG Heide: Sieger ist SF Forst
TV Feld – SSV Bergen: Sieger ist TV Feld
TuS Wiese – SV Wald: Unentschieden
Runde 2:
SF Forst – TV Feld: Unentschieden
TuS Wiese – SG Heide: Sieger ist TuS Wiese
SV Wald – SSV Bergen: Unentschieden
Aktueller Punktestand:
Platzierung · Team · Punkte
1. · SF Forst · 1
2. · TV Feld · 1
3. · TuS Wiese · 1
4. · SV Wald · 0
5. · SSV Bergen · -1
6. · SG Heide · -2
Ergebnis von filtere(pTeam, pX), wobei es sich bei dem Team um SF Forst handelt und pX den Wert 0 besitzt:
SSV Bergen
TuS Wiese
SV Wald
Anhang: Dokumentationen der verwendeten Klassen
Die Klasse Team
Objekte der Klasse Team repräsentieren die zu einem Team vorhandenen Daten. Zu jedem Team in einem Turnier soll immer nur genau ein Objekt existieren. Ein Objekt der Klasse verwaltet Referenzen auf die Spiel-Objekte, an denen das Team beteiligt ist.
Dokumentation der Klasse Team
Team(String pName)
Ein Objekt der Klasse wird initialisiert. Die übergebene Zeichenkette wird gespeichert. Das Team hat keine eigenen Spiele gespeichert.
String gibName()
Die Methode liefert den Namen des Teams zurück.
int berechnePunkte()
Die Methode liefert die Punkte des Teams auf Basis der eigenen Spiele, die bereits beendet sind. Die Rückgabe entspricht der Summe der Einzelbewertungen jedes beendeten Spiels für das Team. Wurde noch kein eigenes Spiel beendet, wird 0 zurückgegeben.
void fuegeHinzu(Spiel pSpiel)
Das durch den Parameter referenzierte Objekt der Klasse Spiel wird zu den eigenen Spielen hinzugefügt.
int zaehleSpieleGegen(Team pGegner)
Die Methode liefert die Anzahl der gespielten Spiele, die das als Parameter übergebene Team gegen dieses Team gespielt hat. Wird null für den Parameter übergeben, so wird -1 zurückgegeben.
Die Klasse Turnierverwaltung
Objekte der Klasse Turnierverwaltung verwaltet alle Teams und alle Spiele eines Turniers. Die Teams werden in der Reihenfolge verwaltet, in der sie sich für das Turnier angemeldet haben.
Dokumentation der Klasse Turnierverwaltung
Turnierverwaltung(Team[] pTeams)
Ein Objekt der Klasse wird initialisiert. Die im Feld übergebenen Teams werden in einer zufälligen Reihenfolge angeordnet. Es existieren keine Spiele.
Team[] berechnePlatzierungen()
Die Methode liefert alle im Turnier vorhanden Teams in einem Feld. Das Feld ist absteigend nach der Punktzahl der Teams sortiert. Sind zwei Teams punktgleich, ist die Reihenfolge zufällig.
void fuegeHinzu(Spiel pSpiel)
Das übergebene Spiel wird der Liste aller Spiele hinzugefügt. Außerdem wird beiden Teams das übergebene Spiel hinzugefügt. Dabei wird davon ausgegangen, dass die beiden im Spiel referenzierten Objekte der Klasse Team nicht identisch sind und von dem Objekt der Klasse Turnierverwaltung referenziert werden.
Team[] gibTeams()
Die Methode liefert die Teams in der Reihenfolge, in der sie sich zum Turnier angemeldet haben.
Über die öffentlichen Methoden hinaus verfügt die Klasse über folgende private Methode:
Dokumentation dieser privaten Methode der Klasse:
List<Team> filtereTeams(Team pTeam, int pGleicheBegegnungen)
Es wird eine Liste zurückgegeben, die alle Teams enthält, die bisher in dem Turnier höchstens den als den Wert von pGleicheBegegnungen mal gegen das übergebene Team gespielt haben. Referenziert der Parameter pTeam null oder ist der Wert des Parameters pGleicheBegegnungen kleiner als 0, so wird eine leere Liste zurückgegeben.
Die Klasse Spiel
Ein Objekt der Klasse Spiel verwaltet die beteiligten Teams, ob das Spiel bereits beendet wurde, und das siegende Team des Spiels, sofern es eins gibt.
Dokumentation der Klasse Spiel
Spiel(Team pTeamA, Team pTeamB)
Ein Objekt der Klasse wird initialisiert. Die beiden übergebenen Teams werden in dem Spiel gespeichert. Das Spiel gilt als nicht beendet. Es ist kein Sieger eingetragen.
Team gibTeamA()
Die Methode liefert das Team A des Spiels.
Team gibTeamB()
Die Methode liefert das Team B des Spiels.
boolean istBeendet()
Ist das Spiel beendet, wird true zurückgegeben, andernfalls wird false zurückgegeben.
int berechnePunkte(Team pTeam)
Ist das Spiel nicht beendet oder ist kein Sieger gespeichert (das Spiel also als Unentschieden gewertet), so wird der Wert 0 zurückgegeben. Ist das im Parameter referenzierte Team nicht an dem Spiel beteiligt, wird der Wert -1 zurückgegeben.
void setzeSieger(Team pSieger)
Das Spiel wird als beendet gewertet. Das übergebene Team wird als Sieger gesetzt. Wird null übergeben, gilt das Spiel als unentschieden. Es wird davon ausgegangen, dass entweder null oder ein beteiligtes Team übergeben wird.
Team gibSieger()
Die Methode liefert den Sieger des Spiels. Gibt es keinen Sieger, weil das Spiel unentschieden oder noch nicht beendet ist, wird null geliefert.
Die generische Klasse List<ContentType>
Objekte der generischen Klasse List verwalten beliebig viele, linear angeordnete Objekte vom Typ ContentType. Auf höchstens ein Listenobjekt, aktuelles Objekt genannt, kann jeweils zugegriffen werden. Wenn eine Liste leer ist, vollständig durchlaufen wurde oder das aktuelle Objekt am Ende der Liste gelöscht wurde, gibt es kein aktuelles Objekt. Das erste oder das letzte Objekt einer Liste kann durch einen Auftrag zum aktuellen Objekt gemacht werden. Außerdem kann das dem aktuellen Objekt folgende Listenobjekt zum neuen aktuellen Objekt werden.
Das aktuelle Objekt kann gelesen, verändert oder gelöscht werden. Außerdem kann vor dem aktuellen Objekt ein Listenobjekt eingefügt werden.
Dokumentation der Klasse List<ContentType>
List()
Eine leere Liste wird erzeugt.
boolean isEmpty()
Die Anfrage liefert den Wert true, wenn die Liste keine Objekte enthält, sonst liefert sie den Wert false.
boolean hasAccess()
Die Anfrage liefert den Wert true, wenn es ein aktuelles Objekt gibt, sonst liefert sie den Wert false.
void next()
Falls die Liste nicht leer ist, es ein aktuelles Objekt gibt und dieses nicht das letzte Objekt der Liste ist, wird das dem aktuellen Objekt in der Liste folgende Objekt zum aktuellen Objekt, andernfalls gibt es nach Ausführung des Auftrags kein aktuelles Objekt, d. h., hasAccess() liefert den Wert false.
void toFirst()
Falls die Liste nicht leer ist, wird das erste Objekt der Liste aktuelles Objekt. Ist die Liste leer, geschieht nichts.
void toLast()
Falls die Liste nicht leer ist, wird das letzte Objekt der Liste aktuelles Objekt. Ist die Liste leer, geschieht nichts.
ContentType getContent()
Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird das aktuelle Objekt zurückgegeben. Andernfalls (hasAccess() == false) gibt die Anfrage den Wert null zurück.
void setContent(ContentType pContent)
Falls es ein aktuelles Objekt gibt (hasAccess() == true) und pContent ungleich null ist, wird das aktuelle Objekt durch pContent ersetzt. Sonst bleibt die Liste unverändert.
void append(ContentType pContent)
Ein neues Objekt pContent wird am Ende der Liste eingefügt. Das aktuelle Objekt bleibt unverändert. Wenn die Liste leer ist, wird das Objekt pContent in die Liste eingefügt und es gibt weiterhin kein aktuelles Objekt (hasAccess() == false). Falls pContent gleich null ist, bleibt die Liste unverändert.
void insert(ContentType pContent)
Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird ein neues Objekt pContent vor dem aktuellen Objekt in die Liste eingefügt. Das aktuelle Objekt bleibt unverändert. Falls die Liste leer ist und es somit kein aktuelles Objekt gibt (hasAccess() == false), wird pContent in die Liste eingefügt und es gibt weiterhin kein aktuelles Objekt. Falls es kein aktuelles Objekt gibt (hasAccess() == false) und die Liste nicht leer ist oder pContent == null ist, bleibt die Liste unverändert.
void concat(List<ContentType> pList)
Die Liste pList wird an die Liste angehängt. Anschließend wird pList eine leere Liste. Das aktuelle Objekt bleibt unverändert. Falls es sich bei der Liste und pList um dasselbe Objekt handelt, pList == null oder eine leere Liste ist, bleibt die Liste unverändert.
void remove()
Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird das aktuelle Objekt gelöscht und das Objekt hinter dem gelöschten Objekt wird zum aktuellen Objekt. Wird das Objekt, das am Ende der Liste steht, gelöscht, gibt es kein aktuelles Objekt mehr (hasAccess() == false). Wenn die Liste leer ist oder es kein aktuelles Objekt gibt (hasAccess() == false), bleibt die Liste unverändert.
a)
Analysieren Sie das Implementationsdiagramm und erläutern Sie, was die abgebildeten Assoziationen im Sachzusammenhang bedeuten.
Erläutern Sie unter Heranziehung des Implementationsdiagramms und der Dokumentation im Anhang, auf welche Weise das Modell das Hinzufügen eines Spiels und das Eintragen des Siegers vorsieht. [8 Punkte]
d)
Damit die Software unter anderem für unterschiedliche Sportarten verwendbar ist, soll sie flexibler werden. Dazu soll das Modell um die folgenden Punkte angepasst werden:
1. Je nach Sportart gibt es für Sieg, Unentschieden und Niederlage unterschiedliche Punkte. Diese Punkte sollen für jedes Turnier zu Beginn festgelegt werden können.
2. Zu jedem Team soll abgefragt werden können, gegen welche Teams es schon gespielt hat.
Ergänzen Sie das Modell aus Abbildung 1 um die genannten Erweiterungen.
Erläutern Sie, auf welche Weise Ihr Modell die Anforderungen realisiert. [11 Punkte]
Hinweis: Stellen Sie lediglich die Abweichungen des Modells gegenüber Abbildung 1 dar.
Aufgabe 1 - Algorithmen auf Feldern und linearen… · Algorithmen auf Feldern und linearen Listen
Aufgabenstellung:
Bei Sportturnieren sollen nach Möglichkeit Teams mit ähnlicher Spielstärke aufeinandertreffen, damit die verschiedenen Leistungsniveaus berücksichtigt werden. Deshalb wird nach jeder Runde eine Reihenfolge der Teams gebildet, die ihren bisherigen Erfolg im Turnier widerspiegelt. Auf Grundlage dieser Reihenfolgen werden die Begegnungen nach diesem Prinzip festgelegt:
Bei Beginn des Turniers werden alle Teams in zufälliger Reihenfolge angeordnet.
Nach jeder Partie wird die Reihenfolge der Teams anhand ihrer Punktzahlen angepasst: Die Reihenfolge der Teams ist absteigend nach deren Punktzahl geordnet. Für einen Sieg erhält ein Team 1 Punkt, für eine Niederlage -1 Punkt. Bei einem Unentschieden erhalten beide Teams 0 Punkte.
Um für ein Team A einen Gegner zu bestimmen, wird das Team B als Gegner ausgewählt, das in der Reihenfolge möglichst nah beim Team A ist.
Um zu vermeiden, dass immer wieder die gleichen Teams gegeneinander spielen, wird eine Maximalzahl gleicher Begegnungen festgelegt: Zwei Teams sollen höchstens so häufig wie in dieser Zahl festgelegt gegeneinander spielen.
Das folgende Softwaremodell stellt einen ersten Entwurf zur Verwaltung eines derartigen Turniers dar:
Anlage: Beispieldaten
Teams (in dieser Reihenfolge im Feld teams):
SF Forst
SG Heide
TV Feld
SSV Bergen
TuS Wiese
SV Wald
Spiele:
Runde 1:
SF Forst – SG Heide: Sieger ist SF Forst
TV Feld – SSV Bergen: Sieger ist TV Feld
TuS Wiese – SV Wald: Unentschieden
Runde 2:
SF Forst – TV Feld: Unentschieden
TuS Wiese – SG Heide: Sieger ist TuS Wiese
SV Wald – SSV Bergen: Unentschieden
Aktueller Punktestand:
Platzierung · Team · Punkte
1. · SF Forst · 1
2. · TV Feld · 1
3. · TuS Wiese · 1
4. · SV Wald · 0
5. · SSV Bergen · -1
6. · SG Heide · -2
Ergebnis von filtere(pTeam, pX), wobei es sich bei dem Team um SF Forst handelt und pX den Wert 0 besitzt:
SSV Bergen
TuS Wiese
SV Wald
Anhang: Dokumentationen der verwendeten Klassen
Die Klasse Team
Objekte der Klasse Team repräsentieren die zu einem Team vorhandenen Daten. Zu jedem Team in einem Turnier soll immer nur genau ein Objekt existieren. Ein Objekt der Klasse verwaltet Referenzen auf die Spiel-Objekte, an denen das Team beteiligt ist.
Dokumentation der Klasse Team
Team(String pName)
Ein Objekt der Klasse wird initialisiert. Die übergebene Zeichenkette wird gespeichert. Das Team hat keine eigenen Spiele gespeichert.
String gibName()
Die Methode liefert den Namen des Teams zurück.
int berechnePunkte()
Die Methode liefert die Punkte des Teams auf Basis der eigenen Spiele, die bereits beendet sind. Die Rückgabe entspricht der Summe der Einzelbewertungen jedes beendeten Spiels für das Team. Wurde noch kein eigenes Spiel beendet, wird 0 zurückgegeben.
void fuegeHinzu(Spiel pSpiel)
Das durch den Parameter referenzierte Objekt der Klasse Spiel wird zu den eigenen Spielen hinzugefügt.
int zaehleSpieleGegen(Team pGegner)
Die Methode liefert die Anzahl der gespielten Spiele, die das als Parameter übergebene Team gegen dieses Team gespielt hat. Wird null für den Parameter übergeben, so wird -1 zurückgegeben.
Die Klasse Turnierverwaltung
Objekte der Klasse Turnierverwaltung verwaltet alle Teams und alle Spiele eines Turniers. Die Teams werden in der Reihenfolge verwaltet, in der sie sich für das Turnier angemeldet haben.
Dokumentation der Klasse Turnierverwaltung
Turnierverwaltung(Team[] pTeams)
Ein Objekt der Klasse wird initialisiert. Die im Feld übergebenen Teams werden in einer zufälligen Reihenfolge angeordnet. Es existieren keine Spiele.
Team[] berechnePlatzierungen()
Die Methode liefert alle im Turnier vorhanden Teams in einem Feld. Das Feld ist absteigend nach der Punktzahl der Teams sortiert. Sind zwei Teams punktgleich, ist die Reihenfolge zufällig.
void fuegeHinzu(Spiel pSpiel)
Das übergebene Spiel wird der Liste aller Spiele hinzugefügt. Außerdem wird beiden Teams das übergebene Spiel hinzugefügt. Dabei wird davon ausgegangen, dass die beiden im Spiel referenzierten Objekte der Klasse Team nicht identisch sind und von dem Objekt der Klasse Turnierverwaltung referenziert werden.
Team[] gibTeams()
Die Methode liefert die Teams in der Reihenfolge, in der sie sich zum Turnier angemeldet haben.
Über die öffentlichen Methoden hinaus verfügt die Klasse über folgende private Methode:
Dokumentation dieser privaten Methode der Klasse:
List<Team> filtereTeams(Team pTeam, int pGleicheBegegnungen)
Es wird eine Liste zurückgegeben, die alle Teams enthält, die bisher in dem Turnier höchstens den als den Wert von pGleicheBegegnungen mal gegen das übergebene Team gespielt haben. Referenziert der Parameter pTeam null oder ist der Wert des Parameters pGleicheBegegnungen kleiner als 0, so wird eine leere Liste zurückgegeben.
Die Klasse Spiel
Ein Objekt der Klasse Spiel verwaltet die beteiligten Teams, ob das Spiel bereits beendet wurde, und das siegende Team des Spiels, sofern es eins gibt.
Dokumentation der Klasse Spiel
Spiel(Team pTeamA, Team pTeamB)
Ein Objekt der Klasse wird initialisiert. Die beiden übergebenen Teams werden in dem Spiel gespeichert. Das Spiel gilt als nicht beendet. Es ist kein Sieger eingetragen.
Team gibTeamA()
Die Methode liefert das Team A des Spiels.
Team gibTeamB()
Die Methode liefert das Team B des Spiels.
boolean istBeendet()
Ist das Spiel beendet, wird true zurückgegeben, andernfalls wird false zurückgegeben.
int berechnePunkte(Team pTeam)
Ist das Spiel nicht beendet oder ist kein Sieger gespeichert (das Spiel also als Unentschieden gewertet), so wird der Wert 0 zurückgegeben. Ist das im Parameter referenzierte Team nicht an dem Spiel beteiligt, wird der Wert -1 zurückgegeben.
void setzeSieger(Team pSieger)
Das Spiel wird als beendet gewertet. Das übergebene Team wird als Sieger gesetzt. Wird null übergeben, gilt das Spiel als unentschieden. Es wird davon ausgegangen, dass entweder null oder ein beteiligtes Team übergeben wird.
Team gibSieger()
Die Methode liefert den Sieger des Spiels. Gibt es keinen Sieger, weil das Spiel unentschieden oder noch nicht beendet ist, wird null geliefert.
Die generische Klasse List<ContentType>
Objekte der generischen Klasse List verwalten beliebig viele, linear angeordnete Objekte vom Typ ContentType. Auf höchstens ein Listenobjekt, aktuelles Objekt genannt, kann jeweils zugegriffen werden. Wenn eine Liste leer ist, vollständig durchlaufen wurde oder das aktuelle Objekt am Ende der Liste gelöscht wurde, gibt es kein aktuelles Objekt. Das erste oder das letzte Objekt einer Liste kann durch einen Auftrag zum aktuellen Objekt gemacht werden. Außerdem kann das dem aktuellen Objekt folgende Listenobjekt zum neuen aktuellen Objekt werden.
Das aktuelle Objekt kann gelesen, verändert oder gelöscht werden. Außerdem kann vor dem aktuellen Objekt ein Listenobjekt eingefügt werden.
Dokumentation der Klasse List<ContentType>
List()
Eine leere Liste wird erzeugt.
boolean isEmpty()
Die Anfrage liefert den Wert true, wenn die Liste keine Objekte enthält, sonst liefert sie den Wert false.
boolean hasAccess()
Die Anfrage liefert den Wert true, wenn es ein aktuelles Objekt gibt, sonst liefert sie den Wert false.
void next()
Falls die Liste nicht leer ist, es ein aktuelles Objekt gibt und dieses nicht das letzte Objekt der Liste ist, wird das dem aktuellen Objekt in der Liste folgende Objekt zum aktuellen Objekt, andernfalls gibt es nach Ausführung des Auftrags kein aktuelles Objekt, d. h., hasAccess() liefert den Wert false.
void toFirst()
Falls die Liste nicht leer ist, wird das erste Objekt der Liste aktuelles Objekt. Ist die Liste leer, geschieht nichts.
void toLast()
Falls die Liste nicht leer ist, wird das letzte Objekt der Liste aktuelles Objekt. Ist die Liste leer, geschieht nichts.
ContentType getContent()
Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird das aktuelle Objekt zurückgegeben. Andernfalls (hasAccess() == false) gibt die Anfrage den Wert null zurück.
void setContent(ContentType pContent)
Falls es ein aktuelles Objekt gibt (hasAccess() == true) und pContent ungleich null ist, wird das aktuelle Objekt durch pContent ersetzt. Sonst bleibt die Liste unverändert.
void append(ContentType pContent)
Ein neues Objekt pContent wird am Ende der Liste eingefügt. Das aktuelle Objekt bleibt unverändert. Wenn die Liste leer ist, wird das Objekt pContent in die Liste eingefügt und es gibt weiterhin kein aktuelles Objekt (hasAccess() == false). Falls pContent gleich null ist, bleibt die Liste unverändert.
void insert(ContentType pContent)
Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird ein neues Objekt pContent vor dem aktuellen Objekt in die Liste eingefügt. Das aktuelle Objekt bleibt unverändert. Falls die Liste leer ist und es somit kein aktuelles Objekt gibt (hasAccess() == false), wird pContent in die Liste eingefügt und es gibt weiterhin kein aktuelles Objekt. Falls es kein aktuelles Objekt gibt (hasAccess() == false) und die Liste nicht leer ist oder pContent == null ist, bleibt die Liste unverändert.
void concat(List<ContentType> pList)
Die Liste pList wird an die Liste angehängt. Anschließend wird pList eine leere Liste. Das aktuelle Objekt bleibt unverändert. Falls es sich bei der Liste und pList um dasselbe Objekt handelt, pList == null oder eine leere Liste ist, bleibt die Liste unverändert.
void remove()
Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird das aktuelle Objekt gelöscht und das Objekt hinter dem gelöschten Objekt wird zum aktuellen Objekt. Wird das Objekt, das am Ende der Liste steht, gelöscht, gibt es kein aktuelles Objekt mehr (hasAccess() == false). Wenn die Liste leer ist oder es kein aktuelles Objekt gibt (hasAccess() == false), bleibt die Liste unverändert.
Teilaufgabe b)
Die private Methode filtereTeams der Klasse Turnierverwaltung liefert alle Teams, die gegen das übergebene Team höchstens eine vorgegebene Anzahl an Spielen ausgetragen haben. Das als Parameter übergebene Team selbst gehört nicht zum Ergebnis.
Ist pTeam gleich null oder pGleicheBegegnungen kleiner als 0, gibt die Methode eine leere Liste zurück. Der Methodenkopf lautet:
private List<Team> filtereTeams(Team pTeam, int pGleicheBegegnungen) Entwickeln Sie einen Algorithmus, der das Filtern gemäß der obigen Beschreibung umsetzt, und implementieren Sie anschließend die Methode.
Teilaufgabe c)
In der Klasse Turnierverwaltung des Projekts findet sich die folgende Methode ohne Dokumentation:
1 public Team wasErmittleIch(Team pTeam, int pX) {
2
List<Team> auswahl
3
= filtereTeams(pTeam, pX);
4
auswahl.toFirst();
5
int punkte = pTeam.berechnePunkte();
6
Team g = auswahl.getContent();
7
int d = Math.abs(g.berechnePunkte() - punkte);
8
auswahl.next();
9
while (auswahl.hasAccess()) {
10
Team temp = auswahl.getContent();
11
int diff = Math.abs(temp.berechnePunkte()
12
- punkte);
13
if (diff < d) {
14
d = diff;
15
g = temp;
16
}
17
auswahl.next();
18
}
19
return g;
20 } Hinweis: Die Methode Math.abs(int a) liefert den Absolutbetrag einer Integerzahl a.
Anhand der Beispieldaten in der Anlage soll die Methode untersucht werden (Wert des Attributs pX = 0 und Team SF Forst).
1.
Analysieren Sie die Methode wasErmittleIch durch Anwendung auf die Beispieldaten und geben Sie den Rückgabewert an:
2.
Erläutern Sie das Vorgehen, das dem Algorithmus zugrunde liegt:
3.
Erläutern Sie, was die Methode bezogen auf den Sachkontext bestimmt:
4.
Analysieren und erläutern Sie, an welchen Stellen im Quellcode ein Laufzeitfehler auftreten kann, weil auf ein nicht vorhandenes Objekt zugegriffen wird ( NullPointerException):
Teilaufgabe e)
Vereinfachend bleibt hier unberücksichtigt, wie oft zwei Mannschaften bereits gegeneinander gespielt haben. Zudem sei die Anzahl der Teams gerade. Die Software soll sicherstellen, dass die Punktdifferenz bei jedem Spiel möglichst klein ist.
Hierzu liegen zwei Vorschläge vor:
(i) Beginne in der Mitte der Rangfolge:
• Wähle das erste der beiden mittleren Teams.
• Dieses Team ist in der Reihenfolge von zwei Teams umgeben: Wähle von den beiden Teams das als Gegner aus, zu dem die Punktdifferenz geringer ist.
• Ist die Punktdifferenz gleich, wähle das untere Team als Gegner.
• Entferne die beiden gewählten Teams aus der Rangfolge.
• Wiederhole diesen Prozess, bis nur noch zwei Teams ungeplant sind.
• Sind nur noch zwei Teams ungeplant, lasse diese beiden gegeneinander spielen.
(ii) Beginne bei der aktuellen Rangfolge beim ersten Team:
• Wähle das erste Team gegen das zweite Team als Spiel.
• Entferne beide Teams aus der Rangfolge.
• Wiederhole diesen Prozess, bis die Rangfolge leer ist.
Ermitteln Sie mithilfe der Beispieldaten im Anhang für beide Vorschläge die Spiele sowie die jeweilige Punktdifferenz:
Vorschlag (i)
Vorschlag (ii)
Beurteilen Sie, welcher der Ansätze die besseren Spielpaarungen liefert:
Aufgabe 2 - Client-Server-Kommunikation und Proto… · Client-Server-Kommunikation und Protokolle (LK)
Kontext: Das Spiel NobodyKnows
Ziel des Spiels NobodyKnows ist es, zu unbekannten Begriffen oder Wissensfragen derart einfallsreiche und überzeugend wirkende Erklärungen zu erfinden, dass die übrigen Mitspielenden diese für richtig halten.
Jede laufende Spielrunde gliedert sich in drei Phasen:
1. Frage-/Antwortphase: Zu Beginn wird allen Spielenden dieselbe Wissensfrage gestellt, die normalerweise niemand beantworten kann. Nur die (nicht mitspielende) Spielleitung kennt die korrekte Antwort. Die Mitspielerinnen und Mitspieler müssen sich nun eine möglichst kreative Phantasieantwort einfallen lassen. Diese notieren sie und geben sie verdeckt bei der Spielleitung ab.
2. Tipp-Phase: Sind alle Antworten eingereicht, werden diese, zusammen mit der korrekten Antwort, in zufälliger Reihenfolge allen Spielenden präsentiert. Jeder Mitspieler bzw. jede Mitspielerin muss sich nun für eine der präsentierten Antworten entscheiden und auf diese Weise einen entsprechenden Tipp abgeben.
3. Auswertungsphase: Hat man auf die korrekte Antwort getippt, bekommt man 2 Punkte. Ist ein anderer Mitspieler oder eine andere Mitspielerin auf die eigene Antwort hereingefallen, indem er oder sie auf diese getippt hat, bekommt man zusätzlich jeweils 3 Punkte.
Anhänger des Spiels bedauern, dass in jeder Runde eine Person aussetzen muss, da sie die Spielleitung übernimmt. Deshalb plant die Herstellerfirma, das Spiel zusätzlich als App für Smartphones und Tablets anzubieten, wobei eine Serversoftware die Aufgaben der Spielleitung übernimmt. Die Serversoftware benutzt das Protokoll, das in Anlage 1 dokumentiert ist.
Im Verlauf des Spiels sollen auf den Bildschirmen der Mitspielenden Dialoge entstehen, wie sie in Abbildung 1 gezeigt werden.
Von der Herstellerfirma liegt eine erste Fassung der Serversoftware vor. Abbildung 2 zeigt einen Teil der Modellierung.
Anlage 1: Protokoll der Client-Server-Kommunikation
Je nach Spielphase akzeptiert der Server jeweils nur bestimmte Teile des Protokolls. Kommandos, die nicht einer bestimmten Spielphase zugeordnet sind, kann der Server jederzeit verarbeiten. Empfängt der Server ein Kommando, das während der laufenden Spielphase nicht akzeptiert wird, sendet er eine Fehlermeldung der Form -ERR <Fehlertext>.
Client an Server
Server an Client
Kommentar
(Verbindungsaufnahme)
+NKS OK
(Fehler oder Fehleingabe)
-ERR <Text>
<Text> beschreibt den Fehler näher.
LOGOUT
+LOGOUT OK
Der Client wird aus der Liste der Spielenden gestrichen und ein evtl. mit ihm laufendes Spiel beendet.
SPIELER <Name>
+SPIELER OK
Der Name <Name> wird der Liste der möglichen Spielenden hinzugefügt. Sobald dies mindestens einmal erfolgreich ausgeführt wurde, wechselt der Server in Spielphase 0.
NEU
+NEU OK
Falls gerade ein Spiel läuft, wird dieses beendet. Alle Spielenden werden auf den Punktestand 0 zurückgesetzt.
Spielphase 0
Client an Server
Server an Client
Kommentar
SPIELSTART
+SPIELSTART OK
Eine neue Spielrunde beginnt (ggf. wird dafür ein neues Spiel gestartet). Alle aktuell gespeicherten Spielerinnen und Spieler werden in dieses neue Spiel aufgenommen. Der Server wechselt in die Spielphase 1.
Spielphase 1
Client an Server
Server an Client
Kommentar
SPIELERLISTE <Liste>
Überträgt zu Beginn von Spielphase 1 die Liste der Spielenden. <Liste> beinhaltet dabei die Namen der Spielenden, gefolgt von ihrer Punktzahl in Klammern (z. B. Peter (5)). Die Inhalte der Liste werden durch das Zeichen | voneinander getrennt.
FRAGE <Text>
Sendet nach der Übermittlung der Spielerliste die Frage mit dem Fragetext <Text> für diese Spielrunde.
ANTWORT <Text>
+ANTWORT OK
<Text> wird als geheime Antwort des Clients gespeichert. Sobald alle Clients eine Antwort übermittelt haben, wechselt der Server in die Spielphase 2.
Spielphase 2
Client an Server
Server an Client
Kommentar
ANTWORTEN <Liste>
Beim Einstieg in Spielphase 2 werden die Antworten in zufälliger Reihenfolge als <Liste> übertragen. Die Inhalte der Liste werden durch das Zeichen | voneinander getrennt.
Jeder Eintrag in der Liste hat die Form <Nummer>,<Name>:<Text>.
Hierbei ist <Nummer> die ab 0 aufsteigende Nummer in der Liste, <Name> der Name, unter dem die Antwort eingereicht wurde und <Text> der Antworttext. Bei der richtigen Antwort hat der Listeneintrag lediglich die Form <Nummer>:<Text>.
TIPP <Nummer>
+TIPP OK
oder
-ERR <Text>
Der Client gibt mit <Nummer> an, auf welche Antwort er tippt. Versucht ein Client, ein zweites Mal einen Tipp abzugeben oder auf seine eigene Antwort zu tippen, wird eine Fehlermeldung <Text> zurückgegeben. Sobald alle Clients einen Tipp übermittelt haben, wechselt der Server in die Spielphase 3.
Spielphase 3
Client an Server
Server an Client
Kommentar
AUSWERTUNG <Reingefallene>@<Richtige>
Übermittelt zu Beginn von Spielphase 3 die Auswertung der laufenden Spielrunde.
<Reingefallene> listet die Namen der auf eine falsche Antwort Hereingefallenen auf.
<Richtige> listet die Namen derjenigen auf, die sich für die richtige Antwort entschieden hatten.
In beiden Listen werden die Inhalte durch das Zeichen | voneinander getrennt. Die beiden Listen werden durch das Trennzeichen @ voneinander getrennt.
Nach der Auswertung wechselt der Server in die Spielphase 0.
Anhang: Dokumentationen der verwendeten Klassen
Die Klasse NobodyKnowsServer
Ein Objekt dieser Klasse stellt einen Server für das Spiel dar. Er verwaltet die Mitspielenden und wickelt ein Spiel inklusive der sich dadurch ergebenden Kommunikation mit den Clients ab. Dabei wird das Protokoll gemäß Anlage 1 benutzt.
Ausschnitt aus der Dokumentation der Klasse NobodyKnowsServer
NobodyKnowsServer(int pPort)
Ein Server für das Spiel NobodyKnows wird initialisiert. Die Liste der Mitspielenden ist zu Beginn leer und es ist kein Spiel gestartet.
void sendeSpielerliste()
Eine Spielerliste wird gemäß dem Kommando SPIELERLISTE an alle Clients übertragen.
boolean starteSpiel()
Sind bereits Mitspielerinnen und Mitspieler vorhanden, wird ein neues Spiel bzw. eine neue Spielrunde gestartet. Dazu wird eine zufällige Frage geladen, in Spielphase 1 gewechselt und true zurückgegeben. Konnte kein Spiel bzw. keine Spielrunde gestartet werden, weil keine Spielerinnen oder Spieler registriert sind, wird false zurückgegeben und die Spielphase nicht verändert.
boolean hatAntwortEingereicht(Spieler pSpieler)
Es wird true zurückgegeben, wenn durch den Client, der durch pSpieler identifiziert wird, bereits eine Antwort für die in der laufenden Spielrunde gestellte Frage eingereicht wurde. Ist dies nicht der Fall oder ist pSpieler == null, wird false zurückgegeben.
boolean hatGetippt(Spieler pSpieler)
Es wird true zurückgegeben, wenn durch den Client, der durch pSpieler identifiziert wird, bereits ein Lösungs-Tipp für die in der laufenden Spielrunde vorgestellten Antworten abgegeben wurde. Ist dies nicht der Fall oder ist pSpieler == null, wird false zurückgegeben.
void erfasseTipp(Spieler pSpieler, int pNummer)
Die Methode speichert, dass der Client, der durch pSpieler identifiziert wird, in der laufenden Spielrunde auf den Antworttext mit der Nummer pNummer getippt hat. Hatte der Client bereits zuvor einen Tipp abgegeben, geschieht nichts. Wird am Ende festgestellt, dass alle Clients einen Tipp abgegeben haben, wechselt der Server in die Spielphase 3.
void wasTueIch(Antwort pAntwort)
Diese Methode wird im Rahmen von Aufgabenteil c) analysiert.
void werteAus()
Auf Basis der abgegebenen Tipps werden die Punktzahlen dieser Spielrunde ermittelt und den entsprechenden Spieler-Objekten angerechnet. Das Kommando AUSWERTUNG wird erstellt und an alle Clients gesendet. Anschließend wechselt der Server in die Spielphase 0.
void processNewConnection(String pClientIP, int pClientPort)
Eine neue Verbindung zu einem Client mit der angegebenen IP-Adresse-/Port-Kombination wird aufgenommen und initialisiert.
void processMessage(String pClientIP, int pClientPort, String pMessage)
Ein von einem Client übermitteltes Kommando pMessage wird interpretiert.
void processClosingConnection(String pClientIP, int pClientPort)
Die Verbindung zu dem Client mit der angegebenen IP-Adresse-/Port-Kombination wurde beendet. Das laufende Spiel wird dadurch beendet und das zugehörige Spieler-Objekt aus der Liste der Mitspielerinnen und Mitspieler gelöscht.
Die Klasse Antwort
Ein Objekt dieser Klasse repräsentiert eine der in jeder Fragerunde möglichen Antworten. Bei einer Spielrunde mit n Spielerinnen und Spielern gibt es n+1 mögliche Antworten, n von den Spielerinnen und Spielern und die korrekte Antwort. Zusätzlich verwaltet ein Objekt dieser Klasse auch die Menge derjenigen Spielerinnen und Spieler, die sich für diese Antwort entschieden haben, also auf sie getippt haben.
Ausschnitt aus der Dokumentation der Klasse Antwort
Antwort(Spieler pEinreicher, String pText)
Ein neues Antwort-Objekt wird erstellt. Als Einreicher bzw. Einreicherin des Textes wird das Objekt pEinreicher gespeichert, als dessen bzw. deren Antworttext pText. Hat pEinreicher den Wert null, stellt diese Antwort die korrekte Lösung zur Frage der aktuellen Spielrunde dar.
Spieler gibEinreicher()
Gibt dasjenige Spieler-Objekt zurück, durch das diese Antwort eingereicht wurde. Wird null zurückgegeben, dann handelt es sich bei der durch dieses Objekt referenzierten Antwort um die richtige Lösung zu der Frage der aktuellen Spielrunde.
String gibText()
Der Text dieser Antwort wird zurückgegeben.
List<Spieler> gibTipper()
Die Liste aller Spielerinnen und Spieler, die in der laufenden Spielrunde auf diese Antwort getippt haben, wird zurückgegeben.
void fuegeTipperHinzu(Spieler pTipper)
pTipper wird der Liste derjenigen Spielerinnen und Spieler hinzugefügt, die in der laufenden Spielrunde auf diese Antwort getippt haben.
boolean istRichtigeAntwort()
Gibt true zurück, wenn es sich bei diesem Antwort-Objekt um die richtige Antwort auf die Frage der aktuellen Spielrunde handelt, ansonsten false.
Die Klasse Frage
Ein Objekt dieser Klasse repräsentiert eine Frage (inklusive der korrekten Antwort darauf), wie sie zu Beginn jeder Spielrunde gestellt wird.
Ausschnitt aus der Dokumentation der Klasse Frage
Frage(String pFrageText, String pLoesungsText)
Ein neues Frage-Objekt wird mit dem angegebenen Fragetext und Lösungstext wird erstellt.
String gibFrageText()
Der Text dieser Frage wird zurückgegeben.
String gibLoesungsText()
Der Lösungstext zu dieser Frage wird zurückgegeben.
Die Klasse Spieler
Ein Objekt dieser Klasse repräsentiert einen Mitspieler oder eine Mitspielerin in der laufenden Spielrunde.
Ausschnitt aus der Dokumentation der Klasse Spieler
Spieler(String pName, String pIP, int pPort)
Ein neues Spieler-Objekt wird mit dem angegebenen Namen erstellt. Die Parameter pIP und pPort identifizieren den Client, von dem aus das Kommando SPIELER zum Erzeugen dieses neuen Objektes gesendet wurde.
String gibIP()
Die IP-Adresse des Clients, von dem die Erzeugung dieses Objekts veranlasst wurde, wird zurückgegeben.
int gibPort()
Die Port-Nummer des Clients, von dem die Erzeugung dieses Objekts veranlasst wurde, wird zurückgegeben.
String gibName()
Der Name des Spielers oder der Spielerin wird zurückgegeben.
int gibPunkte()
Die über alle Spielrunden insgesamt erreichten Punkte dieses Spielers bzw. dieser Spielerin werden zurückgegeben.
void setzePunkte(int pPunkte)
Die Punkte dieses Spielers bzw. dieser Spielerin werden auf den Wert pPunkte gesetzt.
Die generische Klasse List<ContentType>
Objekte der generischen Klasse List verwalten beliebig viele, linear angeordnete Objekte vom Typ ContentType. Auf höchstens ein Listenobjekt, aktuelles Objekt genannt, kann jeweils zugegriffen werden. Wenn eine Liste leer ist, vollständig durchlaufen wurde oder das aktuelle Objekt am Ende der Liste gelöscht wurde, gibt es kein aktuelles Objekt. Das erste oder das letzte Objekt einer Liste kann durch einen Auftrag zum aktuellen Objekt gemacht werden. Außerdem kann das dem aktuellen Objekt folgende Listenobjekt zum neuen aktuellen Objekt werden. Das aktuelle Objekt kann gelesen, verändert oder gelöscht werden. Außerdem kann vor dem aktuellen Objekt ein Listenobjekt eingefügt werden.
Dokumentation der Klasse List<ContentType>
List()
Eine leere Liste wird erzeugt.
boolean isEmpty()
Die Anfrage liefert den Wert true, wenn die Liste keine Objekte enthält, sonst liefert sie den Wert false.
boolean hasAccess()
Die Anfrage liefert den Wert true, wenn es ein aktuelles Objekt gibt, sonst liefert sie den Wert false.
void next()
Falls die Liste nicht leer ist, es ein aktuelles Objekt gibt und dieses nicht das letzte Objekt der Liste ist, wird das dem aktuellen Objekt in der Liste folgende Objekt zum aktuellen Objekt, andernfalls gibt es nach Ausführung des Auftrags kein aktuelles Objekt, d. h., hasAccess() liefert den Wert false.
void toFirst()
Falls die Liste nicht leer ist, wird das erste Objekt der Liste aktuelles Objekt. Ist die Liste leer, geschieht nichts.
void toLast()
Falls die Liste nicht leer ist, wird das letzte Objekt der Liste aktuelles Objekt. Ist die Liste leer, geschieht nichts.
ContentType getContent()
Falls es ein aktuelles Objekt gibt ( hasAccess() == true), wird das aktuelle Objekt zurückgegeben. Andernfalls ( hasAccess() == false) gibt die Anfrage den Wert null zurück.
void setContent(ContentType pContent)
Falls es ein aktuelles Objekt gibt ( hasAccess() == true) und pContent ungleich null ist, wird das aktuelle Objekt durch pContent ersetzt. Sonst bleibt die Liste unverändert.
void append(ContentType pContent)
Ein neues Objekt pContent wird am Ende der Liste eingefügt. Das aktuelle Objekt bleibt unverändert. Wenn die Liste leer ist, wird das Objekt pContent in die Liste eingefügt und es gibt weiterhin kein aktuelles Objekt ( hasAccess() == false). Falls pContent gleich null ist, bleibt die Liste unverändert.
void insert(ContentType pContent)
Falls es ein aktuelles Objekt gibt ( hasAccess() == true), wird ein neues Objekt pContent vor dem aktuellen Objekt in die Liste eingefügt. Das aktuelle Objekt bleibt unverändert. Falls die Liste leer ist und es somit kein aktuelles Objekt gibt ( hasAccess() == false), wird pContent in die Liste eingefügt und es gibt weiterhin kein aktuelles Objekt. Falls es kein aktuelles Objekt gibt ( hasAccess() == false) und die Liste nicht leer ist oder pContent == null ist, bleibt die Liste unverändert.
void concat(List<ContentType> pList)
Die Liste pList wird an die Liste angehängt. Anschließend wird pList eine leere Liste. Das aktuelle Objekt bleibt unverändert. Falls es sich bei der Liste und pList um dasselbe Objekt handelt, pList == null oder eine leere Liste ist, bleibt die Liste unverändert.
void remove()
Falls es ein aktuelles Objekt gibt ( hasAccess() == true), wird das aktuelle Objekt gelöscht und das Objekt hinter dem gelöschten Objekt wird zum aktuellen Objekt. Wird das Objekt, das am Ende der Liste steht, gelöscht, gibt es kein aktuelles Objekt mehr ( hasAccess() == false). Wenn die Liste leer ist oder es kein aktuelles Objekt gibt ( hasAccess() == false), bleibt die Liste unverändert.
Die Klasse Server
Objekte von Unterklassen der abstrakten Klasse Server ermöglichen das Anbieten von Serverdiensten, sodass Clients Verbindungen zum Server mittels TCP/IP-Protokoll aufbauen können. Zur Vereinfachung finden Nachrichtenversand und -empfang zeilenweise statt, d. h., beim Senden einer Zeichenkette wird ein Zeilentrenner ergänzt und beim Empfang wird dieser entfernt. Verbindungsannahme, Nachrichtenempfang und Verbindungsende geschehen nebenläufig. Auf diese Ereignisse muss durch Überschreiben der entsprechenden Ereignisbehandlungsmethoden reagiert werden. Es findet nur eine rudimentäre Fehlerbehandlung statt, sodass z. B. Verbindungsabbrüche nicht zu einem Programmabbruch führen. Einmal unterbrochene oder getrennte Verbindungen können nicht reaktiviert werden.
Dokumentation der Klasse Server
Server(int pPort)
Ein Objekt vom Typ Server wird erstellt, das über die angegebene Portnummer einen Dienst anbietet. Clients können sich mit dem Server verbinden, sodass Daten (Zeichenketten) zu diesen gesendet und von diesen empfangen werden können. Kann der Server unter der angegebenen Portnummer keinen Dienst anbieten (z. B. weil die Portnummer bereits belegt ist), ist keine Verbindungsaufnahme zum Server und kein Datenaustausch möglich.
boolean isOpen()
Die Anfrage liefert den Wert true, wenn der Server auf Port pPort einen Dienst anbietet. Ansonsten liefert die Methode den Wert false.
boolean isConnectedTo(String pClientIP, int pClientPort)
Die Anfrage liefert den Wert true, wenn der Server mit dem durch pClientIP und pClientPort spezifizierten Client aktuell verbunden ist. Ansonsten liefert die Methode den Wert false.
void send(String pClientIP, int pClientPort, String pMessage)
Die Nachricht pMessage wird – um einen Zeilentrenner erweitert – an den durch pClientIP und pClientPort spezifizierten Client gesendet. Schlägt der Versand fehl, geschieht nichts.
void sendToAll(String pMessage)
Die Nachricht pMessage wird – um einen Zeilentrenner erweitert – an alle mit dem Server verbundenen Clients gesendet. Schlägt der Versand an einen Client fehl, wird dieser Client übersprungen.
void closeConnection(String pClientIP, int pClientPort)
Die Verbindung des Servers zu dem durch pClientIP und pClientPort spezifizierten Client wird getrennt. Zuvor wird die Methode processClosingConnection mit IP-Adresse und Port des jeweiligen Clients aufgerufen. Ist der Server nicht mit dem in der Parameterliste spezifizierten Client verbunden, geschieht nichts.
void close()
Alle bestehenden Verbindungen zu Clients werden getrennt und der Server kann nicht mehr verwendet werden. Ist der Server bereits vor Aufruf der Methode in diesem Zustand, geschieht nichts.
void processNewConnection(String pClientIP, int pClientPort)
Diese Ereignisbehandlungsmethode wird aufgerufen, wenn sich ein Client mit IP-Adresse pClientIP und Portnummer pClientPort mit dem Server verbunden hat. Die Methode ist abstrakt und muss in einer Unterklasse der Klasse Server überschrieben werden, sodass auf den Neuaufbau der Verbindung reagiert wird. Der Aufruf der Methode erfolgt nicht synchronisiert.
void processMessage(String pClientIP, int pClientPort, String pMessage)
Diese Ereignisbehandlungsmethode wird aufgerufen, wenn der Server die Nachricht pMessage von dem durch pClientIP und pClientPort spezifizierten Client empfangen hat. Der vom Client hinzugefügte Zeilentrenner wurde zuvor entfernt. Die Methode ist abstrakt und muss in einer Unterklasse der Klasse Server überschrieben werden, sodass auf den Empfang der Nachricht reagiert wird. Der Aufruf der Methode erfolgt nicht synchronisiert.
void processClosingConnection(String pClientIP, int pClientPort)
Sofern der Server die Verbindung zu dem durch pClientIP und pClientPort spezifizierten Client trennt, wird diese Ereignisbehandlungsmethode aufgerufen, unmittelbar bevor die Verbindungstrennung tatsächlich erfolgt. Wird die Verbindung unvermittelt unterbrochen oder hat der in der Parameterliste spezifizierte Client die Verbindung zum Server unvermittelt getrennt, erfolgt der Methodenaufruf nach der Unterbrechung/Trennung der Verbindung. Die Methode ist abstrakt und muss in einer Unterklasse der Klasse Server überschrieben werden, sodass auf das Ende der Verbindung zum angegebenen Client reagiert wird. Der Aufruf der Methode erfolgt nicht synchronisiert.
a)
Abbildung 1 zeigt einen Dialog, der bei einem Testlauf der Tablet-App des Spielers Peter aufgezeichnet wurde. Client und Server kommunizieren dabei nach dem Protokoll aus Anlage 1.
Ermitteln Sie mithilfe des Protokolls aus Anlage 1, welche Kommandos Peters Client an den Server geschickt hat und welche Nachrichten der Server daraufhin an Peters Client geantwortet hat, damit die Bildschirmausgabe in Abbildung 1 zustande kam.
Anmerkung: Stellen Sie dar, welche Servernachricht zu welchem Client-Kommando gehört, z. B. in einer Tabelle. [8 Punkte]
e)
Die nächste Serverversion soll mehr Funktionen bieten. Viele Nutzerinnen und Nutzer wünschen sich, dass angemeldete Spielerinnen und Spieler den Fragenkatalog des Servers um neue Fragen samt zugehörigem richtigem Lösungstext erweitern können.
Entwickeln Sie eine passende Erweiterung des Protokolls aus Anlage 1. [2 Punkte]
Nehmen Sie im Sachkontext Stellung dazu, ob der Fragenkatalog auch künftig mit einer statischen oder besser mit einer dynamischen Datenstruktur verwaltet werden sollte. [4 Punkte]
Aufgabe 2 - Objektorientierte Modellierung · Objektorientierte Modellierung mit Implementationsdiagrammen
Kontext: Das Spiel NobodyKnows
Ziel des Spiels NobodyKnows ist es, zu unbekannten Begriffen oder Wissensfragen derart einfallsreiche und überzeugend wirkende Erklärungen zu erfinden, dass die übrigen Mitspielenden diese für richtig halten.
Jede laufende Spielrunde gliedert sich in drei Phasen:
1. Frage-/Antwortphase: Zu Beginn wird allen Spielenden dieselbe Wissensfrage gestellt, die normalerweise niemand beantworten kann. Nur die (nicht mitspielende) Spielleitung kennt die korrekte Antwort. Die Mitspielerinnen und Mitspieler müssen sich nun eine möglichst kreative Phantasieantwort einfallen lassen. Diese notieren sie und geben sie verdeckt bei der Spielleitung ab.
2. Tipp-Phase: Sind alle Antworten eingereicht, werden diese, zusammen mit der korrekten Antwort, in zufälliger Reihenfolge allen Spielenden präsentiert. Jeder Mitspieler bzw. jede Mitspielerin muss sich nun für eine der präsentierten Antworten entscheiden und auf diese Weise einen entsprechenden Tipp abgeben.
3. Auswertungsphase: Hat man auf die korrekte Antwort getippt, bekommt man 2 Punkte. Ist ein anderer Mitspieler oder eine andere Mitspielerin auf die eigene Antwort hereingefallen, indem er oder sie auf diese getippt hat, bekommt man zusätzlich jeweils 3 Punkte.
Anhänger des Spiels bedauern, dass in jeder Runde eine Person aussetzen muss, da sie die Spielleitung übernimmt. Deshalb plant die Herstellerfirma, das Spiel zusätzlich als App für Smartphones und Tablets anzubieten, wobei eine Serversoftware die Aufgaben der Spielleitung übernimmt. Die Serversoftware benutzt das Protokoll, das in Anlage 1 dokumentiert ist.
Im Verlauf des Spiels sollen auf den Bildschirmen der Mitspielenden Dialoge entstehen, wie sie in Abbildung 1 gezeigt werden.
Von der Herstellerfirma liegt eine erste Fassung der Serversoftware vor. Abbildung 2 zeigt einen Teil der Modellierung.
Anlage 1: Protokoll der Client-Server-Kommunikation
Je nach Spielphase akzeptiert der Server jeweils nur bestimmte Teile des Protokolls. Kommandos, die nicht einer bestimmten Spielphase zugeordnet sind, kann der Server jederzeit verarbeiten. Empfängt der Server ein Kommando, das während der laufenden Spielphase nicht akzeptiert wird, sendet er eine Fehlermeldung der Form -ERR <Fehlertext>.
Client an Server
Server an Client
Kommentar
(Verbindungsaufnahme)
+NKS OK
(Fehler oder Fehleingabe)
-ERR <Text>
<Text> beschreibt den Fehler näher.
LOGOUT
+LOGOUT OK
Der Client wird aus der Liste der Spielenden gestrichen und ein evtl. mit ihm laufendes Spiel beendet.
SPIELER <Name>
+SPIELER OK
Der Name <Name> wird der Liste der möglichen Spielenden hinzugefügt. Sobald dies mindestens einmal erfolgreich ausgeführt wurde, wechselt der Server in Spielphase 0.
NEU
+NEU OK
Falls gerade ein Spiel läuft, wird dieses beendet. Alle Spielenden werden auf den Punktestand 0 zurückgesetzt.
Spielphase 0
Client an Server
Server an Client
Kommentar
SPIELSTART
+SPIELSTART OK
Eine neue Spielrunde beginnt (ggf. wird dafür ein neues Spiel gestartet). Alle aktuell gespeicherten Spielerinnen und Spieler werden in dieses neue Spiel aufgenommen. Der Server wechselt in die Spielphase 1.
Spielphase 1
Client an Server
Server an Client
Kommentar
SPIELERLISTE <Liste>
Überträgt zu Beginn von Spielphase 1 die Liste der Spielenden. <Liste> beinhaltet dabei die Namen der Spielenden, gefolgt von ihrer Punktzahl in Klammern (z. B. Peter (5)). Die Inhalte der Liste werden durch das Zeichen | voneinander getrennt.
FRAGE <Text>
Sendet nach der Übermittlung der Spielerliste die Frage mit dem Fragetext <Text> für diese Spielrunde.
ANTWORT <Text>
+ANTWORT OK
<Text> wird als geheime Antwort des Clients gespeichert. Sobald alle Clients eine Antwort übermittelt haben, wechselt der Server in die Spielphase 2.
Spielphase 2
Client an Server
Server an Client
Kommentar
ANTWORTEN <Liste>
Beim Einstieg in Spielphase 2 werden die Antworten in zufälliger Reihenfolge als <Liste> übertragen. Die Inhalte der Liste werden durch das Zeichen | voneinander getrennt.
Jeder Eintrag in der Liste hat die Form <Nummer>,<Name>:<Text>.
Hierbei ist <Nummer> die ab 0 aufsteigende Nummer in der Liste, <Name> der Name, unter dem die Antwort eingereicht wurde und <Text> der Antworttext. Bei der richtigen Antwort hat der Listeneintrag lediglich die Form <Nummer>:<Text>.
TIPP <Nummer>
+TIPP OK
oder
-ERR <Text>
Der Client gibt mit <Nummer> an, auf welche Antwort er tippt. Versucht ein Client, ein zweites Mal einen Tipp abzugeben oder auf seine eigene Antwort zu tippen, wird eine Fehlermeldung <Text> zurückgegeben. Sobald alle Clients einen Tipp übermittelt haben, wechselt der Server in die Spielphase 3.
Spielphase 3
Client an Server
Server an Client
Kommentar
AUSWERTUNG <Reingefallene>@<Richtige>
Übermittelt zu Beginn von Spielphase 3 die Auswertung der laufenden Spielrunde.
<Reingefallene> listet die Namen der auf eine falsche Antwort Hereingefallenen auf.
<Richtige> listet die Namen derjenigen auf, die sich für die richtige Antwort entschieden hatten.
In beiden Listen werden die Inhalte durch das Zeichen | voneinander getrennt. Die beiden Listen werden durch das Trennzeichen @ voneinander getrennt.
Nach der Auswertung wechselt der Server in die Spielphase 0.
Anhang: Dokumentationen der verwendeten Klassen
Die Klasse NobodyKnowsServer
Ein Objekt dieser Klasse stellt einen Server für das Spiel dar. Er verwaltet die Mitspielenden und wickelt ein Spiel inklusive der sich dadurch ergebenden Kommunikation mit den Clients ab. Dabei wird das Protokoll gemäß Anlage 1 benutzt.
Ausschnitt aus der Dokumentation der Klasse NobodyKnowsServer
NobodyKnowsServer(int pPort)
Ein Server für das Spiel NobodyKnows wird initialisiert. Die Liste der Mitspielenden ist zu Beginn leer und es ist kein Spiel gestartet.
void sendeSpielerliste()
Eine Spielerliste wird gemäß dem Kommando SPIELERLISTE an alle Clients übertragen.
boolean starteSpiel()
Sind bereits Mitspielerinnen und Mitspieler vorhanden, wird ein neues Spiel bzw. eine neue Spielrunde gestartet. Dazu wird eine zufällige Frage geladen, in Spielphase 1 gewechselt und true zurückgegeben. Konnte kein Spiel bzw. keine Spielrunde gestartet werden, weil keine Spielerinnen oder Spieler registriert sind, wird false zurückgegeben und die Spielphase nicht verändert.
boolean hatAntwortEingereicht(Spieler pSpieler)
Es wird true zurückgegeben, wenn durch den Client, der durch pSpieler identifiziert wird, bereits eine Antwort für die in der laufenden Spielrunde gestellte Frage eingereicht wurde. Ist dies nicht der Fall oder ist pSpieler == null, wird false zurückgegeben.
boolean hatGetippt(Spieler pSpieler)
Es wird true zurückgegeben, wenn durch den Client, der durch pSpieler identifiziert wird, bereits ein Lösungs-Tipp für die in der laufenden Spielrunde vorgestellten Antworten abgegeben wurde. Ist dies nicht der Fall oder ist pSpieler == null, wird false zurückgegeben.
void erfasseTipp(Spieler pSpieler, int pNummer)
Die Methode speichert, dass der Client, der durch pSpieler identifiziert wird, in der laufenden Spielrunde auf den Antworttext mit der Nummer pNummer getippt hat. Hatte der Client bereits zuvor einen Tipp abgegeben, geschieht nichts. Wird am Ende festgestellt, dass alle Clients einen Tipp abgegeben haben, wechselt der Server in die Spielphase 3.
void wasTueIch(Antwort pAntwort)
Diese Methode wird im Rahmen von Aufgabenteil c) analysiert.
void werteAus()
Auf Basis der abgegebenen Tipps werden die Punktzahlen dieser Spielrunde ermittelt und den entsprechenden Spieler-Objekten angerechnet. Das Kommando AUSWERTUNG wird erstellt und an alle Clients gesendet. Anschließend wechselt der Server in die Spielphase 0.
void processNewConnection(String pClientIP, int pClientPort)
Eine neue Verbindung zu einem Client mit der angegebenen IP-Adresse-/Port-Kombination wird aufgenommen und initialisiert.
void processMessage(String pClientIP, int pClientPort, String pMessage)
Ein von einem Client übermitteltes Kommando pMessage wird interpretiert.
void processClosingConnection(String pClientIP, int pClientPort)
Die Verbindung zu dem Client mit der angegebenen IP-Adresse-/Port-Kombination wurde beendet. Das laufende Spiel wird dadurch beendet und das zugehörige Spieler-Objekt aus der Liste der Mitspielerinnen und Mitspieler gelöscht.
Die Klasse Antwort
Ein Objekt dieser Klasse repräsentiert eine der in jeder Fragerunde möglichen Antworten. Bei einer Spielrunde mit n Spielerinnen und Spielern gibt es n+1 mögliche Antworten, n von den Spielerinnen und Spielern und die korrekte Antwort. Zusätzlich verwaltet ein Objekt dieser Klasse auch die Menge derjenigen Spielerinnen und Spieler, die sich für diese Antwort entschieden haben, also auf sie getippt haben.
Ausschnitt aus der Dokumentation der Klasse Antwort
Antwort(Spieler pEinreicher, String pText)
Ein neues Antwort-Objekt wird erstellt. Als Einreicher bzw. Einreicherin des Textes wird das Objekt pEinreicher gespeichert, als dessen bzw. deren Antworttext pText. Hat pEinreicher den Wert null, stellt diese Antwort die korrekte Lösung zur Frage der aktuellen Spielrunde dar.
Spieler gibEinreicher()
Gibt dasjenige Spieler-Objekt zurück, durch das diese Antwort eingereicht wurde. Wird null zurückgegeben, dann handelt es sich bei der durch dieses Objekt referenzierten Antwort um die richtige Lösung zu der Frage der aktuellen Spielrunde.
String gibText()
Der Text dieser Antwort wird zurückgegeben.
List<Spieler> gibTipper()
Die Liste aller Spielerinnen und Spieler, die in der laufenden Spielrunde auf diese Antwort getippt haben, wird zurückgegeben.
void fuegeTipperHinzu(Spieler pTipper)
pTipper wird der Liste derjenigen Spielerinnen und Spieler hinzugefügt, die in der laufenden Spielrunde auf diese Antwort getippt haben.
boolean istRichtigeAntwort()
Gibt true zurück, wenn es sich bei diesem Antwort-Objekt um die richtige Antwort auf die Frage der aktuellen Spielrunde handelt, ansonsten false.
Die Klasse Frage
Ein Objekt dieser Klasse repräsentiert eine Frage (inklusive der korrekten Antwort darauf), wie sie zu Beginn jeder Spielrunde gestellt wird.
Ausschnitt aus der Dokumentation der Klasse Frage
Frage(String pFrageText, String pLoesungsText)
Ein neues Frage-Objekt wird mit dem angegebenen Fragetext und Lösungstext wird erstellt.
String gibFrageText()
Der Text dieser Frage wird zurückgegeben.
String gibLoesungsText()
Der Lösungstext zu dieser Frage wird zurückgegeben.
Die Klasse Spieler
Ein Objekt dieser Klasse repräsentiert einen Mitspieler oder eine Mitspielerin in der laufenden Spielrunde.
Ausschnitt aus der Dokumentation der Klasse Spieler
Spieler(String pName, String pIP, int pPort)
Ein neues Spieler-Objekt wird mit dem angegebenen Namen erstellt. Die Parameter pIP und pPort identifizieren den Client, von dem aus das Kommando SPIELER zum Erzeugen dieses neuen Objektes gesendet wurde.
String gibIP()
Die IP-Adresse des Clients, von dem die Erzeugung dieses Objekts veranlasst wurde, wird zurückgegeben.
int gibPort()
Die Port-Nummer des Clients, von dem die Erzeugung dieses Objekts veranlasst wurde, wird zurückgegeben.
String gibName()
Der Name des Spielers oder der Spielerin wird zurückgegeben.
int gibPunkte()
Die über alle Spielrunden insgesamt erreichten Punkte dieses Spielers bzw. dieser Spielerin werden zurückgegeben.
void setzePunkte(int pPunkte)
Die Punkte dieses Spielers bzw. dieser Spielerin werden auf den Wert pPunkte gesetzt.
Die generische Klasse List<ContentType>
Objekte der generischen Klasse List verwalten beliebig viele, linear angeordnete Objekte vom Typ ContentType. Auf höchstens ein Listenobjekt, aktuelles Objekt genannt, kann jeweils zugegriffen werden. Wenn eine Liste leer ist, vollständig durchlaufen wurde oder das aktuelle Objekt am Ende der Liste gelöscht wurde, gibt es kein aktuelles Objekt. Das erste oder das letzte Objekt einer Liste kann durch einen Auftrag zum aktuellen Objekt gemacht werden. Außerdem kann das dem aktuellen Objekt folgende Listenobjekt zum neuen aktuellen Objekt werden. Das aktuelle Objekt kann gelesen, verändert oder gelöscht werden. Außerdem kann vor dem aktuellen Objekt ein Listenobjekt eingefügt werden.
Dokumentation der Klasse List<ContentType>
List()
Eine leere Liste wird erzeugt.
boolean isEmpty()
Die Anfrage liefert den Wert true, wenn die Liste keine Objekte enthält, sonst liefert sie den Wert false.
boolean hasAccess()
Die Anfrage liefert den Wert true, wenn es ein aktuelles Objekt gibt, sonst liefert sie den Wert false.
void next()
Falls die Liste nicht leer ist, es ein aktuelles Objekt gibt und dieses nicht das letzte Objekt der Liste ist, wird das dem aktuellen Objekt in der Liste folgende Objekt zum aktuellen Objekt, andernfalls gibt es nach Ausführung des Auftrags kein aktuelles Objekt, d. h., hasAccess() liefert den Wert false.
void toFirst()
Falls die Liste nicht leer ist, wird das erste Objekt der Liste aktuelles Objekt. Ist die Liste leer, geschieht nichts.
void toLast()
Falls die Liste nicht leer ist, wird das letzte Objekt der Liste aktuelles Objekt. Ist die Liste leer, geschieht nichts.
ContentType getContent()
Falls es ein aktuelles Objekt gibt ( hasAccess() == true), wird das aktuelle Objekt zurückgegeben. Andernfalls ( hasAccess() == false) gibt die Anfrage den Wert null zurück.
void setContent(ContentType pContent)
Falls es ein aktuelles Objekt gibt ( hasAccess() == true) und pContent ungleich null ist, wird das aktuelle Objekt durch pContent ersetzt. Sonst bleibt die Liste unverändert.
void append(ContentType pContent)
Ein neues Objekt pContent wird am Ende der Liste eingefügt. Das aktuelle Objekt bleibt unverändert. Wenn die Liste leer ist, wird das Objekt pContent in die Liste eingefügt und es gibt weiterhin kein aktuelles Objekt ( hasAccess() == false). Falls pContent gleich null ist, bleibt die Liste unverändert.
void insert(ContentType pContent)
Falls es ein aktuelles Objekt gibt ( hasAccess() == true), wird ein neues Objekt pContent vor dem aktuellen Objekt in die Liste eingefügt. Das aktuelle Objekt bleibt unverändert. Falls die Liste leer ist und es somit kein aktuelles Objekt gibt ( hasAccess() == false), wird pContent in die Liste eingefügt und es gibt weiterhin kein aktuelles Objekt. Falls es kein aktuelles Objekt gibt ( hasAccess() == false) und die Liste nicht leer ist oder pContent == null ist, bleibt die Liste unverändert.
void concat(List<ContentType> pList)
Die Liste pList wird an die Liste angehängt. Anschließend wird pList eine leere Liste. Das aktuelle Objekt bleibt unverändert. Falls es sich bei der Liste und pList um dasselbe Objekt handelt, pList == null oder eine leere Liste ist, bleibt die Liste unverändert.
void remove()
Falls es ein aktuelles Objekt gibt ( hasAccess() == true), wird das aktuelle Objekt gelöscht und das Objekt hinter dem gelöschten Objekt wird zum aktuellen Objekt. Wird das Objekt, das am Ende der Liste steht, gelöscht, gibt es kein aktuelles Objekt mehr ( hasAccess() == false). Wenn die Liste leer ist oder es kein aktuelles Objekt gibt ( hasAccess() == false), bleibt die Liste unverändert.
Die Klasse Server
Objekte von Unterklassen der abstrakten Klasse Server ermöglichen das Anbieten von Serverdiensten, sodass Clients Verbindungen zum Server mittels TCP/IP-Protokoll aufbauen können. Zur Vereinfachung finden Nachrichtenversand und -empfang zeilenweise statt, d. h., beim Senden einer Zeichenkette wird ein Zeilentrenner ergänzt und beim Empfang wird dieser entfernt. Verbindungsannahme, Nachrichtenempfang und Verbindungsende geschehen nebenläufig. Auf diese Ereignisse muss durch Überschreiben der entsprechenden Ereignisbehandlungsmethoden reagiert werden. Es findet nur eine rudimentäre Fehlerbehandlung statt, sodass z. B. Verbindungsabbrüche nicht zu einem Programmabbruch führen. Einmal unterbrochene oder getrennte Verbindungen können nicht reaktiviert werden.
Dokumentation der Klasse Server
Server(int pPort)
Ein Objekt vom Typ Server wird erstellt, das über die angegebene Portnummer einen Dienst anbietet. Clients können sich mit dem Server verbinden, sodass Daten (Zeichenketten) zu diesen gesendet und von diesen empfangen werden können. Kann der Server unter der angegebenen Portnummer keinen Dienst anbieten (z. B. weil die Portnummer bereits belegt ist), ist keine Verbindungsaufnahme zum Server und kein Datenaustausch möglich.
boolean isOpen()
Die Anfrage liefert den Wert true, wenn der Server auf Port pPort einen Dienst anbietet. Ansonsten liefert die Methode den Wert false.
boolean isConnectedTo(String pClientIP, int pClientPort)
Die Anfrage liefert den Wert true, wenn der Server mit dem durch pClientIP und pClientPort spezifizierten Client aktuell verbunden ist. Ansonsten liefert die Methode den Wert false.
void send(String pClientIP, int pClientPort, String pMessage)
Die Nachricht pMessage wird – um einen Zeilentrenner erweitert – an den durch pClientIP und pClientPort spezifizierten Client gesendet. Schlägt der Versand fehl, geschieht nichts.
void sendToAll(String pMessage)
Die Nachricht pMessage wird – um einen Zeilentrenner erweitert – an alle mit dem Server verbundenen Clients gesendet. Schlägt der Versand an einen Client fehl, wird dieser Client übersprungen.
void closeConnection(String pClientIP, int pClientPort)
Die Verbindung des Servers zu dem durch pClientIP und pClientPort spezifizierten Client wird getrennt. Zuvor wird die Methode processClosingConnection mit IP-Adresse und Port des jeweiligen Clients aufgerufen. Ist der Server nicht mit dem in der Parameterliste spezifizierten Client verbunden, geschieht nichts.
void close()
Alle bestehenden Verbindungen zu Clients werden getrennt und der Server kann nicht mehr verwendet werden. Ist der Server bereits vor Aufruf der Methode in diesem Zustand, geschieht nichts.
void processNewConnection(String pClientIP, int pClientPort)
Diese Ereignisbehandlungsmethode wird aufgerufen, wenn sich ein Client mit IP-Adresse pClientIP und Portnummer pClientPort mit dem Server verbunden hat. Die Methode ist abstrakt und muss in einer Unterklasse der Klasse Server überschrieben werden, sodass auf den Neuaufbau der Verbindung reagiert wird. Der Aufruf der Methode erfolgt nicht synchronisiert.
void processMessage(String pClientIP, int pClientPort, String pMessage)
Diese Ereignisbehandlungsmethode wird aufgerufen, wenn der Server die Nachricht pMessage von dem durch pClientIP und pClientPort spezifizierten Client empfangen hat. Der vom Client hinzugefügte Zeilentrenner wurde zuvor entfernt. Die Methode ist abstrakt und muss in einer Unterklasse der Klasse Server überschrieben werden, sodass auf den Empfang der Nachricht reagiert wird. Der Aufruf der Methode erfolgt nicht synchronisiert.
void processClosingConnection(String pClientIP, int pClientPort)
Sofern der Server die Verbindung zu dem durch pClientIP und pClientPort spezifizierten Client trennt, wird diese Ereignisbehandlungsmethode aufgerufen, unmittelbar bevor die Verbindungstrennung tatsächlich erfolgt. Wird die Verbindung unvermittelt unterbrochen oder hat der in der Parameterliste spezifizierte Client die Verbindung zum Server unvermittelt getrennt, erfolgt der Methodenaufruf nach der Unterbrechung/Trennung der Verbindung. Die Methode ist abstrakt und muss in einer Unterklasse der Klasse Server überschrieben werden, sodass auf das Ende der Verbindung zum angegebenen Client reagiert wird. Der Aufruf der Methode erfolgt nicht synchronisiert.
b) Erläutern Sie, wie die Klassen NobodyKnowsServer, Server, Antwort, Frage und Spieler in der Modellierung von Abbildung 2 zueinander in Beziehung stehen.
Erläutern Sie, aus welchem Grund sich die Antworten der laufenden Spielrunde in einem Feld (Array) verwalten lassen.
Erläutern Sie, weshalb es zweckmäßig ist, dass Objekte der Klasse Spieler eine IP-Adresse sowie einen Port speichern.
(6 + 3 + 3 Punkte)
Aufgabe 2 - Algorithmen auf Feldern und linearen… · Algorithmen auf Feldern und linearen Listen
Kontext: Das Spiel NobodyKnows
Ziel des Spiels NobodyKnows ist es, zu unbekannten Begriffen oder Wissensfragen derart einfallsreiche und überzeugend wirkende Erklärungen zu erfinden, dass die übrigen Mitspielenden diese für richtig halten.
Jede laufende Spielrunde gliedert sich in drei Phasen:
1. Frage-/Antwortphase: Zu Beginn wird allen Spielenden dieselbe Wissensfrage gestellt, die normalerweise niemand beantworten kann. Nur die (nicht mitspielende) Spielleitung kennt die korrekte Antwort. Die Mitspielerinnen und Mitspieler müssen sich nun eine möglichst kreative Phantasieantwort einfallen lassen. Diese notieren sie und geben sie verdeckt bei der Spielleitung ab.
2. Tipp-Phase: Sind alle Antworten eingereicht, werden diese, zusammen mit der korrekten Antwort, in zufälliger Reihenfolge allen Spielenden präsentiert. Jeder Mitspieler bzw. jede Mitspielerin muss sich nun für eine der präsentierten Antworten entscheiden und auf diese Weise einen entsprechenden Tipp abgeben.
3. Auswertungsphase: Hat man auf die korrekte Antwort getippt, bekommt man 2 Punkte. Ist ein anderer Mitspieler oder eine andere Mitspielerin auf die eigene Antwort hereingefallen, indem er oder sie auf diese getippt hat, bekommt man zusätzlich jeweils 3 Punkte.
Anhänger des Spiels bedauern, dass in jeder Runde eine Person aussetzen muss, da sie die Spielleitung übernimmt. Deshalb plant die Herstellerfirma, das Spiel zusätzlich als App für Smartphones und Tablets anzubieten, wobei eine Serversoftware die Aufgaben der Spielleitung übernimmt. Die Serversoftware benutzt das Protokoll, das in Anlage 1 dokumentiert ist.
Im Verlauf des Spiels sollen auf den Bildschirmen der Mitspielenden Dialoge entstehen, wie sie in Abbildung 1 gezeigt werden.
Von der Herstellerfirma liegt eine erste Fassung der Serversoftware vor. Abbildung 2 zeigt einen Teil der Modellierung.
Anlage 1: Protokoll der Client-Server-Kommunikation
Je nach Spielphase akzeptiert der Server jeweils nur bestimmte Teile des Protokolls. Kommandos, die nicht einer bestimmten Spielphase zugeordnet sind, kann der Server jederzeit verarbeiten. Empfängt der Server ein Kommando, das während der laufenden Spielphase nicht akzeptiert wird, sendet er eine Fehlermeldung der Form -ERR <Fehlertext>.
Client an Server
Server an Client
Kommentar
(Verbindungsaufnahme)
+NKS OK
(Fehler oder Fehleingabe)
-ERR <Text>
<Text> beschreibt den Fehler näher.
LOGOUT
+LOGOUT OK
Der Client wird aus der Liste der Spielenden gestrichen und ein evtl. mit ihm laufendes Spiel beendet.
SPIELER <Name>
+SPIELER OK
Der Name <Name> wird der Liste der möglichen Spielenden hinzugefügt. Sobald dies mindestens einmal erfolgreich ausgeführt wurde, wechselt der Server in Spielphase 0.
NEU
+NEU OK
Falls gerade ein Spiel läuft, wird dieses beendet. Alle Spielenden werden auf den Punktestand 0 zurückgesetzt.
Spielphase 0
Client an Server
Server an Client
Kommentar
SPIELSTART
+SPIELSTART OK
Eine neue Spielrunde beginnt (ggf. wird dafür ein neues Spiel gestartet). Alle aktuell gespeicherten Spielerinnen und Spieler werden in dieses neue Spiel aufgenommen. Der Server wechselt in die Spielphase 1.
Spielphase 1
Client an Server
Server an Client
Kommentar
SPIELERLISTE <Liste>
Überträgt zu Beginn von Spielphase 1 die Liste der Spielenden. <Liste> beinhaltet dabei die Namen der Spielenden, gefolgt von ihrer Punktzahl in Klammern (z. B. Peter (5)). Die Inhalte der Liste werden durch das Zeichen | voneinander getrennt.
FRAGE <Text>
Sendet nach der Übermittlung der Spielerliste die Frage mit dem Fragetext <Text> für diese Spielrunde.
ANTWORT <Text>
+ANTWORT OK
<Text> wird als geheime Antwort des Clients gespeichert. Sobald alle Clients eine Antwort übermittelt haben, wechselt der Server in die Spielphase 2.
Spielphase 2
Client an Server
Server an Client
Kommentar
ANTWORTEN <Liste>
Beim Einstieg in Spielphase 2 werden die Antworten in zufälliger Reihenfolge als <Liste> übertragen. Die Inhalte der Liste werden durch das Zeichen | voneinander getrennt.
Jeder Eintrag in der Liste hat die Form <Nummer>,<Name>:<Text>.
Hierbei ist <Nummer> die ab 0 aufsteigende Nummer in der Liste, <Name> der Name, unter dem die Antwort eingereicht wurde und <Text> der Antworttext. Bei der richtigen Antwort hat der Listeneintrag lediglich die Form <Nummer>:<Text>.
TIPP <Nummer>
+TIPP OK
oder
-ERR <Text>
Der Client gibt mit <Nummer> an, auf welche Antwort er tippt. Versucht ein Client, ein zweites Mal einen Tipp abzugeben oder auf seine eigene Antwort zu tippen, wird eine Fehlermeldung <Text> zurückgegeben. Sobald alle Clients einen Tipp übermittelt haben, wechselt der Server in die Spielphase 3.
Spielphase 3
Client an Server
Server an Client
Kommentar
AUSWERTUNG <Reingefallene>@<Richtige>
Übermittelt zu Beginn von Spielphase 3 die Auswertung der laufenden Spielrunde.
<Reingefallene> listet die Namen der auf eine falsche Antwort Hereingefallenen auf.
<Richtige> listet die Namen derjenigen auf, die sich für die richtige Antwort entschieden hatten.
In beiden Listen werden die Inhalte durch das Zeichen | voneinander getrennt. Die beiden Listen werden durch das Trennzeichen @ voneinander getrennt.
Nach der Auswertung wechselt der Server in die Spielphase 0.
Anhang: Dokumentationen der verwendeten Klassen
Die Klasse NobodyKnowsServer
Ein Objekt dieser Klasse stellt einen Server für das Spiel dar. Er verwaltet die Mitspielenden und wickelt ein Spiel inklusive der sich dadurch ergebenden Kommunikation mit den Clients ab. Dabei wird das Protokoll gemäß Anlage 1 benutzt.
Ausschnitt aus der Dokumentation der Klasse NobodyKnowsServer
NobodyKnowsServer(int pPort)
Ein Server für das Spiel NobodyKnows wird initialisiert. Die Liste der Mitspielenden ist zu Beginn leer und es ist kein Spiel gestartet.
void sendeSpielerliste()
Eine Spielerliste wird gemäß dem Kommando SPIELERLISTE an alle Clients übertragen.
boolean starteSpiel()
Sind bereits Mitspielerinnen und Mitspieler vorhanden, wird ein neues Spiel bzw. eine neue Spielrunde gestartet. Dazu wird eine zufällige Frage geladen, in Spielphase 1 gewechselt und true zurückgegeben. Konnte kein Spiel bzw. keine Spielrunde gestartet werden, weil keine Spielerinnen oder Spieler registriert sind, wird false zurückgegeben und die Spielphase nicht verändert.
boolean hatAntwortEingereicht(Spieler pSpieler)
Es wird true zurückgegeben, wenn durch den Client, der durch pSpieler identifiziert wird, bereits eine Antwort für die in der laufenden Spielrunde gestellte Frage eingereicht wurde. Ist dies nicht der Fall oder ist pSpieler == null, wird false zurückgegeben.
boolean hatGetippt(Spieler pSpieler)
Es wird true zurückgegeben, wenn durch den Client, der durch pSpieler identifiziert wird, bereits ein Lösungs-Tipp für die in der laufenden Spielrunde vorgestellten Antworten abgegeben wurde. Ist dies nicht der Fall oder ist pSpieler == null, wird false zurückgegeben.
void erfasseTipp(Spieler pSpieler, int pNummer)
Die Methode speichert, dass der Client, der durch pSpieler identifiziert wird, in der laufenden Spielrunde auf den Antworttext mit der Nummer pNummer getippt hat. Hatte der Client bereits zuvor einen Tipp abgegeben, geschieht nichts. Wird am Ende festgestellt, dass alle Clients einen Tipp abgegeben haben, wechselt der Server in die Spielphase 3.
void wasTueIch(Antwort pAntwort)
Diese Methode wird im Rahmen von Aufgabenteil c) analysiert.
void werteAus()
Auf Basis der abgegebenen Tipps werden die Punktzahlen dieser Spielrunde ermittelt und den entsprechenden Spieler-Objekten angerechnet. Das Kommando AUSWERTUNG wird erstellt und an alle Clients gesendet. Anschließend wechselt der Server in die Spielphase 0.
void processNewConnection(String pClientIP, int pClientPort)
Eine neue Verbindung zu einem Client mit der angegebenen IP-Adresse-/Port-Kombination wird aufgenommen und initialisiert.
void processMessage(String pClientIP, int pClientPort, String pMessage)
Ein von einem Client übermitteltes Kommando pMessage wird interpretiert.
void processClosingConnection(String pClientIP, int pClientPort)
Die Verbindung zu dem Client mit der angegebenen IP-Adresse-/Port-Kombination wurde beendet. Das laufende Spiel wird dadurch beendet und das zugehörige Spieler-Objekt aus der Liste der Mitspielerinnen und Mitspieler gelöscht.
Die Klasse Antwort
Ein Objekt dieser Klasse repräsentiert eine der in jeder Fragerunde möglichen Antworten. Bei einer Spielrunde mit n Spielerinnen und Spielern gibt es n+1 mögliche Antworten, n von den Spielerinnen und Spielern und die korrekte Antwort. Zusätzlich verwaltet ein Objekt dieser Klasse auch die Menge derjenigen Spielerinnen und Spieler, die sich für diese Antwort entschieden haben, also auf sie getippt haben.
Ausschnitt aus der Dokumentation der Klasse Antwort
Antwort(Spieler pEinreicher, String pText)
Ein neues Antwort-Objekt wird erstellt. Als Einreicher bzw. Einreicherin des Textes wird das Objekt pEinreicher gespeichert, als dessen bzw. deren Antworttext pText. Hat pEinreicher den Wert null, stellt diese Antwort die korrekte Lösung zur Frage der aktuellen Spielrunde dar.
Spieler gibEinreicher()
Gibt dasjenige Spieler-Objekt zurück, durch das diese Antwort eingereicht wurde. Wird null zurückgegeben, dann handelt es sich bei der durch dieses Objekt referenzierten Antwort um die richtige Lösung zu der Frage der aktuellen Spielrunde.
String gibText()
Der Text dieser Antwort wird zurückgegeben.
List<Spieler> gibTipper()
Die Liste aller Spielerinnen und Spieler, die in der laufenden Spielrunde auf diese Antwort getippt haben, wird zurückgegeben.
void fuegeTipperHinzu(Spieler pTipper)
pTipper wird der Liste derjenigen Spielerinnen und Spieler hinzugefügt, die in der laufenden Spielrunde auf diese Antwort getippt haben.
boolean istRichtigeAntwort()
Gibt true zurück, wenn es sich bei diesem Antwort-Objekt um die richtige Antwort auf die Frage der aktuellen Spielrunde handelt, ansonsten false.
Die Klasse Frage
Ein Objekt dieser Klasse repräsentiert eine Frage (inklusive der korrekten Antwort darauf), wie sie zu Beginn jeder Spielrunde gestellt wird.
Ausschnitt aus der Dokumentation der Klasse Frage
Frage(String pFrageText, String pLoesungsText)
Ein neues Frage-Objekt wird mit dem angegebenen Fragetext und Lösungstext wird erstellt.
String gibFrageText()
Der Text dieser Frage wird zurückgegeben.
String gibLoesungsText()
Der Lösungstext zu dieser Frage wird zurückgegeben.
Die Klasse Spieler
Ein Objekt dieser Klasse repräsentiert einen Mitspieler oder eine Mitspielerin in der laufenden Spielrunde.
Ausschnitt aus der Dokumentation der Klasse Spieler
Spieler(String pName, String pIP, int pPort)
Ein neues Spieler-Objekt wird mit dem angegebenen Namen erstellt. Die Parameter pIP und pPort identifizieren den Client, von dem aus das Kommando SPIELER zum Erzeugen dieses neuen Objektes gesendet wurde.
String gibIP()
Die IP-Adresse des Clients, von dem die Erzeugung dieses Objekts veranlasst wurde, wird zurückgegeben.
int gibPort()
Die Port-Nummer des Clients, von dem die Erzeugung dieses Objekts veranlasst wurde, wird zurückgegeben.
String gibName()
Der Name des Spielers oder der Spielerin wird zurückgegeben.
int gibPunkte()
Die über alle Spielrunden insgesamt erreichten Punkte dieses Spielers bzw. dieser Spielerin werden zurückgegeben.
void setzePunkte(int pPunkte)
Die Punkte dieses Spielers bzw. dieser Spielerin werden auf den Wert pPunkte gesetzt.
Die generische Klasse List<ContentType>
Objekte der generischen Klasse List verwalten beliebig viele, linear angeordnete Objekte vom Typ ContentType. Auf höchstens ein Listenobjekt, aktuelles Objekt genannt, kann jeweils zugegriffen werden. Wenn eine Liste leer ist, vollständig durchlaufen wurde oder das aktuelle Objekt am Ende der Liste gelöscht wurde, gibt es kein aktuelles Objekt. Das erste oder das letzte Objekt einer Liste kann durch einen Auftrag zum aktuellen Objekt gemacht werden. Außerdem kann das dem aktuellen Objekt folgende Listenobjekt zum neuen aktuellen Objekt werden. Das aktuelle Objekt kann gelesen, verändert oder gelöscht werden. Außerdem kann vor dem aktuellen Objekt ein Listenobjekt eingefügt werden.
Dokumentation der Klasse List<ContentType>
List()
Eine leere Liste wird erzeugt.
boolean isEmpty()
Die Anfrage liefert den Wert true, wenn die Liste keine Objekte enthält, sonst liefert sie den Wert false.
boolean hasAccess()
Die Anfrage liefert den Wert true, wenn es ein aktuelles Objekt gibt, sonst liefert sie den Wert false.
void next()
Falls die Liste nicht leer ist, es ein aktuelles Objekt gibt und dieses nicht das letzte Objekt der Liste ist, wird das dem aktuellen Objekt in der Liste folgende Objekt zum aktuellen Objekt, andernfalls gibt es nach Ausführung des Auftrags kein aktuelles Objekt, d. h., hasAccess() liefert den Wert false.
void toFirst()
Falls die Liste nicht leer ist, wird das erste Objekt der Liste aktuelles Objekt. Ist die Liste leer, geschieht nichts.
void toLast()
Falls die Liste nicht leer ist, wird das letzte Objekt der Liste aktuelles Objekt. Ist die Liste leer, geschieht nichts.
ContentType getContent()
Falls es ein aktuelles Objekt gibt ( hasAccess() == true), wird das aktuelle Objekt zurückgegeben. Andernfalls ( hasAccess() == false) gibt die Anfrage den Wert null zurück.
void setContent(ContentType pContent)
Falls es ein aktuelles Objekt gibt ( hasAccess() == true) und pContent ungleich null ist, wird das aktuelle Objekt durch pContent ersetzt. Sonst bleibt die Liste unverändert.
void append(ContentType pContent)
Ein neues Objekt pContent wird am Ende der Liste eingefügt. Das aktuelle Objekt bleibt unverändert. Wenn die Liste leer ist, wird das Objekt pContent in die Liste eingefügt und es gibt weiterhin kein aktuelles Objekt ( hasAccess() == false). Falls pContent gleich null ist, bleibt die Liste unverändert.
void insert(ContentType pContent)
Falls es ein aktuelles Objekt gibt ( hasAccess() == true), wird ein neues Objekt pContent vor dem aktuellen Objekt in die Liste eingefügt. Das aktuelle Objekt bleibt unverändert. Falls die Liste leer ist und es somit kein aktuelles Objekt gibt ( hasAccess() == false), wird pContent in die Liste eingefügt und es gibt weiterhin kein aktuelles Objekt. Falls es kein aktuelles Objekt gibt ( hasAccess() == false) und die Liste nicht leer ist oder pContent == null ist, bleibt die Liste unverändert.
void concat(List<ContentType> pList)
Die Liste pList wird an die Liste angehängt. Anschließend wird pList eine leere Liste. Das aktuelle Objekt bleibt unverändert. Falls es sich bei der Liste und pList um dasselbe Objekt handelt, pList == null oder eine leere Liste ist, bleibt die Liste unverändert.
void remove()
Falls es ein aktuelles Objekt gibt ( hasAccess() == true), wird das aktuelle Objekt gelöscht und das Objekt hinter dem gelöschten Objekt wird zum aktuellen Objekt. Wird das Objekt, das am Ende der Liste steht, gelöscht, gibt es kein aktuelles Objekt mehr ( hasAccess() == false). Wenn die Liste leer ist oder es kein aktuelles Objekt gibt ( hasAccess() == false), bleibt die Liste unverändert.
Die Klasse Server
Objekte von Unterklassen der abstrakten Klasse Server ermöglichen das Anbieten von Serverdiensten, sodass Clients Verbindungen zum Server mittels TCP/IP-Protokoll aufbauen können. Zur Vereinfachung finden Nachrichtenversand und -empfang zeilenweise statt, d. h., beim Senden einer Zeichenkette wird ein Zeilentrenner ergänzt und beim Empfang wird dieser entfernt. Verbindungsannahme, Nachrichtenempfang und Verbindungsende geschehen nebenläufig. Auf diese Ereignisse muss durch Überschreiben der entsprechenden Ereignisbehandlungsmethoden reagiert werden. Es findet nur eine rudimentäre Fehlerbehandlung statt, sodass z. B. Verbindungsabbrüche nicht zu einem Programmabbruch führen. Einmal unterbrochene oder getrennte Verbindungen können nicht reaktiviert werden.
Dokumentation der Klasse Server
Server(int pPort)
Ein Objekt vom Typ Server wird erstellt, das über die angegebene Portnummer einen Dienst anbietet. Clients können sich mit dem Server verbinden, sodass Daten (Zeichenketten) zu diesen gesendet und von diesen empfangen werden können. Kann der Server unter der angegebenen Portnummer keinen Dienst anbieten (z. B. weil die Portnummer bereits belegt ist), ist keine Verbindungsaufnahme zum Server und kein Datenaustausch möglich.
boolean isOpen()
Die Anfrage liefert den Wert true, wenn der Server auf Port pPort einen Dienst anbietet. Ansonsten liefert die Methode den Wert false.
boolean isConnectedTo(String pClientIP, int pClientPort)
Die Anfrage liefert den Wert true, wenn der Server mit dem durch pClientIP und pClientPort spezifizierten Client aktuell verbunden ist. Ansonsten liefert die Methode den Wert false.
void send(String pClientIP, int pClientPort, String pMessage)
Die Nachricht pMessage wird – um einen Zeilentrenner erweitert – an den durch pClientIP und pClientPort spezifizierten Client gesendet. Schlägt der Versand fehl, geschieht nichts.
void sendToAll(String pMessage)
Die Nachricht pMessage wird – um einen Zeilentrenner erweitert – an alle mit dem Server verbundenen Clients gesendet. Schlägt der Versand an einen Client fehl, wird dieser Client übersprungen.
void closeConnection(String pClientIP, int pClientPort)
Die Verbindung des Servers zu dem durch pClientIP und pClientPort spezifizierten Client wird getrennt. Zuvor wird die Methode processClosingConnection mit IP-Adresse und Port des jeweiligen Clients aufgerufen. Ist der Server nicht mit dem in der Parameterliste spezifizierten Client verbunden, geschieht nichts.
void close()
Alle bestehenden Verbindungen zu Clients werden getrennt und der Server kann nicht mehr verwendet werden. Ist der Server bereits vor Aufruf der Methode in diesem Zustand, geschieht nichts.
void processNewConnection(String pClientIP, int pClientPort)
Diese Ereignisbehandlungsmethode wird aufgerufen, wenn sich ein Client mit IP-Adresse pClientIP und Portnummer pClientPort mit dem Server verbunden hat. Die Methode ist abstrakt und muss in einer Unterklasse der Klasse Server überschrieben werden, sodass auf den Neuaufbau der Verbindung reagiert wird. Der Aufruf der Methode erfolgt nicht synchronisiert.
void processMessage(String pClientIP, int pClientPort, String pMessage)
Diese Ereignisbehandlungsmethode wird aufgerufen, wenn der Server die Nachricht pMessage von dem durch pClientIP und pClientPort spezifizierten Client empfangen hat. Der vom Client hinzugefügte Zeilentrenner wurde zuvor entfernt. Die Methode ist abstrakt und muss in einer Unterklasse der Klasse Server überschrieben werden, sodass auf den Empfang der Nachricht reagiert wird. Der Aufruf der Methode erfolgt nicht synchronisiert.
void processClosingConnection(String pClientIP, int pClientPort)
Sofern der Server die Verbindung zu dem durch pClientIP und pClientPort spezifizierten Client trennt, wird diese Ereignisbehandlungsmethode aufgerufen, unmittelbar bevor die Verbindungstrennung tatsächlich erfolgt. Wird die Verbindung unvermittelt unterbrochen oder hat der in der Parameterliste spezifizierte Client die Verbindung zum Server unvermittelt getrennt, erfolgt der Methodenaufruf nach der Unterbrechung/Trennung der Verbindung. Die Methode ist abstrakt und muss in einer Unterklasse der Klasse Server überschrieben werden, sodass auf das Ende der Verbindung zum angegebenen Client reagiert wird. Der Aufruf der Methode erfolgt nicht synchronisiert.
Aufgabe 2 (Teilaufgaben c und d)
c)
Die Klasse NobodyKnowsServer enthält die folgende Methode, die nicht dokumentiert ist:
1 private boolean wasTueIch(Antwort pAntwort) {
2 boolean e = false;
3 boolean s = false;
4 int pos = 0;
5 while (!e && !s) {
6 if (aktAntworten[pos] == null) {
7 s = true;
8 } else {
9 if (aktAntworten[pos].gibEinreicher()
10 == pAntwort.gibEinreicher()) {
11 e = true;
12 }
13 pos = pos + 1;
14 }
15 }
16 if (s) {
17 aktAntworten[pos] = pAntwort;
18 }
19 return s;
20 }
Ein Spiel mit drei Spielern läuft gerade und befindet sich in der Frage-/Antwortphase. Die Methode wird in drei unterschiedlichen Situationen aufgerufen:
i. Niemand hat bisher eine Antwort eingereicht. Das Feld (Array) aktAntworten ist komplett leer, d. h., alle Feldeinträge verweisen auf null.
ii. Genau eine Person hat bisher eine Antwort eingereicht. Das Feld (Array) aktAntworten verweist an Index 0 bereits auf ein Antwort-Objekt, dessen Attribut einreicher aber nicht auf dasselbe Spieler-Objekt verweist wie das entsprechende Attribut in pAntwort.
iii. Mehrere Personen haben bereits eine Antwort eingereicht. Das Feld (Array) aktAntworten verweist an Index 0 bereits auf ein Antwort-Objekt, dessen Attribut einreicher auf dasselbe Spieler-Objekt verweist wie in pAntwort.
Analysieren und erläutern Sie, wie diese Methode arbeitet, und beziehen Sie sich dabei auf die drei beschriebenen Situationen.
Erläutern Sie, was die booleschen Variablen s und e im Sachkontext bedeuten.
Erläutern Sie, welchem Zweck die Methode im Sachkontext dient.
d)
Die Methode werteAus der Klasse NobodyKnowsServer soll die laufende Spielrunde auswerten: Sie verteilt Punkte entsprechend den Tipps, die die Spielerinnen und Spieler in dieser Runde abgegeben haben, und sorgt dafür, dass das AUSWERTUNG-Kommando an die Clients gesendet wird. Ihr Methodenkopf lautet:
private void werteAus()
Entwickeln Sie für diese Methode eine algorithmische Strategie.
Implementieren Sie die Methode.
Aufgabe 3 - Datenbankmodellierung und Normalisier… · Datenbankmodellierung und Normalisierung
Aufgabenstellung
Ein Informatikprojektkurs plant, einige Bahnlinien in einer Datenbank zu modellieren. In einer ersten Version sollen unter anderem die Bahnhöfe sowie die Streckenabschnitte abgelegt werden.
Ein Streckenabschnitt ist eine direkte Verbindung von dem einen Bahnhof zu dem anderen Bahnhof ohne Zwischenhalt. Aus Sicherheitsgründen kann ein Streckenabschnitt nur in einer Richtung und somit nicht für die Gegenrichtung genutzt werden.
Eine Bahnlinie besteht aus Streckenabschnitten, die die Folge der Bahnhöfe auf dieser Bahnlinie beschreiben. Dabei wird für jeden zugehörigen Streckenabschnitt die Fahrzeit in Minuten angegeben.
Für die Datenverwaltung will der Projektkurs eine relationale Datenbank erstellen, die auf der folgenden Teilmodellierung beruht:
Die weiteren Aufgaben beziehen sich auf das Datenbankschema aus Abbildung 3, das nur einen Teil der gesamten Datenbank wiedergibt. Beispieldaten zu diesem Schema stehen im Anhang.
Anlage: Ausschnitt aus den Beispieldaten zum Datenbankschema
Bahnhof
Name · barrierefrei
Glücksdorf · 1
Oberdorf · 0
Medeburg · 1
Zweierwalde · 1
Hintertupfingen · 1
Bahnlinie
ID · Name
L1 · Bergexpress
L2 · Burglinie
L3 · Hintertupfingenlinie
L4 · Glücksdorfexpress
Streckenabschnitt
ID · vonBahnhofName · zuBahnhofname
1 · Glücksdorf · Oberdorf
2 · Oberdorf · Medeburg
3 · Medeburg · Hintertupfingen
4 · Medeburg · Zweierwalde
5 · Zweierwalde · Hintertupfingen
6 · Hintertupfingen · Glücksdorf
gehoertZu
StreckenabschnittID · BahnlinieID · Abschnittsnummer · Fahrzeit
1 · L1 · 1 · 30
2 · L1 · 2 · 20
3 · L1 · 3 · 90
1 · L2 · 1 · 40
2 · L2 · 2 · 50
4 · L2 · 3 · 30
5 · L3 · 1 · 50
6 · L4 · 1 · 90
Anhang: Dokumentationen der verwendeten Klassen
Die Klasse Bahnlinientest
Ein Objekt der Klasse wird genutzt, um die Datenbankverbindung und die Datenbankabfragen zu testen.
Bahnlinientest()
Ein Objekt vom Typ DatabaseConnector wird initialisiert und stellt die Datenbankverbindung her.
List<String> ermittleEtwas(String pBName, int pHops)
Diese Methode ist Bestandteil der Teilaufgabe d.
Darüber hinaus verfügt die Klasse über folgende private Methode:
List<String> ermittleNachbarbahnhoefe(String pBName)
Die Methode liefert eine Liste mit den Namen der Nachbarbahnhöfe für den im Parameter übergebenen Bahnhof pBName zurück.
Nachbarbahnhöfe sind die Bahnhöfe, zu denen ein Streckenabschnitt ohne Zwischenhalt von diesem Bahnhof pBName führt.
Falls der Bahnhofsname pBName nicht existiert, wird eine leere Liste zurückgeliefert.
Die Klasse DatabaseConnector
Ein Objekt der Klasse DatabaseConnector ermöglicht die Abfrage und Manipulation einer MySQL-Datenbank.
Beim Erzeugen des Objekts wird eine Datenbankverbindung aufgebaut, sodass anschließend SQL-Anweisungen an diese Datenbank gerichtet werden können.
DatabaseConnector(String pIP, String pPort, String pDatabase, String pUsername, String pPassword)
Ein Objekt vom Typ DatabaseConnector wird erstellt, und eine Verbindung zur Datenbank wird aufgebaut. Mit den Parametern pIP und pPort werden die IP-Adresse und die Port-Nummer übergeben, unter denen die Datenbank mit Namen pDatabase zu erreichen ist. Mit den Parametern pUsername und pPassword werden Benutzername und Passwort für die Datenbank übergeben.
void executeStatement(String pSQLStatement)
Der Auftrag schickt den im Parameter pSQLStatement enthaltenen SQL-Befehl an die Datenbank ab.
Handelt es sich bei pSQLStatement um einen SQL-Befehl, der eine Ergebnismenge liefert, so kann dieses Ergebnis anschließend mit der Methode getCurrentQueryResult abgerufen werden.
QueryResult getCurrentQueryResult()
Die Anfrage liefert das Ergebnis des letzten mit der Methode executeStatement an die Datenbank geschickten SQL-Befehls als Objekt vom Typ QueryResult zurück.
Wurde bisher kein SQL-Befehl abgeschickt oder ergab der letzte Aufruf von executeStatement keine Ergebnismenge (z. B. bei einem INSERT-Befehl oder einem Syntaxfehler), so wird null geliefert.
String getErrorMessage()
Die Anfrage liefert null oder eine Fehlermeldung, die sich jeweils auf die letzte zuvor ausgeführte Datenbankoperation bezieht.
void close()
Die Datenbankverbindung wird geschlossen.
Die Klasse QueryResult
Ein Objekt der Klasse QueryResult stellt die Ergebnistabelle einer Datenbankanfrage mit Hilfe der Klasse DatabaseConnector dar. Objekte dieser Klasse werden nur von der Klasse DatabaseConnector erstellt. Die Klasse verfügt über keinen öffentlichen Konstruktor.
String[][] getData()
Die Anfrage liefert die Einträge der Ergebnistabelle als zweidimensionales Feld vom Typ String. Der erste Index des Feldes stellt die Zeile und der zweite die Spalte dar (d. h. String[zeile][spalte]).
String[] getColumnNames()
Die Anfrage liefert die Bezeichner der Spalten der Ergebnistabelle als Feld vom Typ String zurück.
String[] getColumnTypes()
Die Anfrage liefert die Typenbezeichnung der Spalten der Ergebnistabelle als Feld vom Typ String zurück. Die Bezeichnungen entsprechen den Angaben in der MySQL-Datenbank.
int getRowCount()
Die Anfrage liefert die Anzahl der Zeilen der Ergebnistabelle als int.
int getColumnCount()
Die Anfrage liefert die Anzahl der Spalten der Ergebnistabelle als int.
a)
Erläutern Sie, auf welche Weise die Bahnlinie L1 samt ihren zugehörigen Streckenabschnitten in der Datenbank abgelegt ist (vgl. Beispieldaten).
[3 Punkte]
Beschreiben Sie die Teilmodellierung des Entity-Relationship-Modells, die in Abbildung 2 dargestellt ist.
[5 Punkte]
Erläutern Sie für jeden im Entity-Relationship-Modell dargestellten Beziehungstyp unter Beachtung der Kardinalitäten, auf welche Weise er im Datenbankschema realisiert wurde.
[5 Punkte]
e)
Ein Schüler meint, der Entitätstyp Bahnhof könne ohne nennenswerte Nachteile entfallen. Die Bahnhofsnamen stehen ohnehin im Entitätstyp Streckenabschnitt als vonBahnhofName und zuBahnhofName. Die Barrierefreiheit jedes Bahnhofs lasse sich zudem in den Entitätstyp Streckenabschnitt verlagern und dort als Attribute barrierefreiVonBahnhof und barrierefreiZuBahnhof ergänzen.
Beurteilen Sie diesen Vorschlag des Schülers.
[5 Punkte]
Aufgabe 3 - SQL-Abfragen · SQL-Abfragen
Aufgabenstellung
Ein Informatikprojektkurs plant, einige Bahnlinien in einer Datenbank zu modellieren. In einer ersten Version sollen unter anderem die Bahnhöfe sowie die Streckenabschnitte abgelegt werden.
Ein Streckenabschnitt ist eine direkte Verbindung von dem einen Bahnhof zu dem anderen Bahnhof ohne Zwischenhalt. Aus Sicherheitsgründen kann ein Streckenabschnitt nur in einer Richtung und somit nicht für die Gegenrichtung genutzt werden.
Eine Bahnlinie besteht aus Streckenabschnitten, die die Folge der Bahnhöfe auf dieser Bahnlinie beschreiben. Dabei wird für jeden zugehörigen Streckenabschnitt die Fahrzeit in Minuten angegeben.
Für die Datenverwaltung will der Projektkurs eine relationale Datenbank erstellen, die auf der folgenden Teilmodellierung beruht:
Die weiteren Aufgaben beziehen sich auf das Datenbankschema aus Abbildung 3, das nur einen Teil der gesamten Datenbank wiedergibt. Beispieldaten zu diesem Schema stehen im Anhang.
Anlage: Ausschnitt aus den Beispieldaten zum Datenbankschema
Bahnhof
Name · barrierefrei
Glücksdorf · 1
Oberdorf · 0
Medeburg · 1
Zweierwalde · 1
Hintertupfingen · 1
Bahnlinie
ID · Name
L1 · Bergexpress
L2 · Burglinie
L3 · Hintertupfingenlinie
L4 · Glücksdorfexpress
Streckenabschnitt
ID · vonBahnhofName · zuBahnhofname
1 · Glücksdorf · Oberdorf
2 · Oberdorf · Medeburg
3 · Medeburg · Hintertupfingen
4 · Medeburg · Zweierwalde
5 · Zweierwalde · Hintertupfingen
6 · Hintertupfingen · Glücksdorf
gehoertZu
StreckenabschnittID · BahnlinieID · Abschnittsnummer · Fahrzeit
1 · L1 · 1 · 30
2 · L1 · 2 · 20
3 · L1 · 3 · 90
1 · L2 · 1 · 40
2 · L2 · 2 · 50
4 · L2 · 3 · 30
5 · L3 · 1 · 50
6 · L4 · 1 · 90
Anhang: Dokumentationen der verwendeten Klassen
Die Klasse Bahnlinientest
Ein Objekt der Klasse wird genutzt, um die Datenbankverbindung und die Datenbankabfragen zu testen.
Bahnlinientest()
Ein Objekt vom Typ DatabaseConnector wird initialisiert und stellt die Datenbankverbindung her.
List<String> ermittleEtwas(String pBName, int pHops)
Diese Methode ist Bestandteil der Teilaufgabe d.
Darüber hinaus verfügt die Klasse über folgende private Methode:
List<String> ermittleNachbarbahnhoefe(String pBName)
Die Methode liefert eine Liste mit den Namen der Nachbarbahnhöfe für den im Parameter übergebenen Bahnhof pBName zurück.
Nachbarbahnhöfe sind die Bahnhöfe, zu denen ein Streckenabschnitt ohne Zwischenhalt von diesem Bahnhof pBName führt.
Falls der Bahnhofsname pBName nicht existiert, wird eine leere Liste zurückgeliefert.
Die Klasse DatabaseConnector
Ein Objekt der Klasse DatabaseConnector ermöglicht die Abfrage und Manipulation einer MySQL-Datenbank.
Beim Erzeugen des Objekts wird eine Datenbankverbindung aufgebaut, sodass anschließend SQL-Anweisungen an diese Datenbank gerichtet werden können.
DatabaseConnector(String pIP, String pPort, String pDatabase, String pUsername, String pPassword)
Ein Objekt vom Typ DatabaseConnector wird erstellt, und eine Verbindung zur Datenbank wird aufgebaut. Mit den Parametern pIP und pPort werden die IP-Adresse und die Port-Nummer übergeben, unter denen die Datenbank mit Namen pDatabase zu erreichen ist. Mit den Parametern pUsername und pPassword werden Benutzername und Passwort für die Datenbank übergeben.
void executeStatement(String pSQLStatement)
Der Auftrag schickt den im Parameter pSQLStatement enthaltenen SQL-Befehl an die Datenbank ab.
Handelt es sich bei pSQLStatement um einen SQL-Befehl, der eine Ergebnismenge liefert, so kann dieses Ergebnis anschließend mit der Methode getCurrentQueryResult abgerufen werden.
QueryResult getCurrentQueryResult()
Die Anfrage liefert das Ergebnis des letzten mit der Methode executeStatement an die Datenbank geschickten SQL-Befehls als Objekt vom Typ QueryResult zurück.
Wurde bisher kein SQL-Befehl abgeschickt oder ergab der letzte Aufruf von executeStatement keine Ergebnismenge (z. B. bei einem INSERT-Befehl oder einem Syntaxfehler), so wird null geliefert.
String getErrorMessage()
Die Anfrage liefert null oder eine Fehlermeldung, die sich jeweils auf die letzte zuvor ausgeführte Datenbankoperation bezieht.
void close()
Die Datenbankverbindung wird geschlossen.
Die Klasse QueryResult
Ein Objekt der Klasse QueryResult stellt die Ergebnistabelle einer Datenbankanfrage mit Hilfe der Klasse DatabaseConnector dar. Objekte dieser Klasse werden nur von der Klasse DatabaseConnector erstellt. Die Klasse verfügt über keinen öffentlichen Konstruktor.
String[][] getData()
Die Anfrage liefert die Einträge der Ergebnistabelle als zweidimensionales Feld vom Typ String. Der erste Index des Feldes stellt die Zeile und der zweite die Spalte dar (d. h. String[zeile][spalte]).
String[] getColumnNames()
Die Anfrage liefert die Bezeichner der Spalten der Ergebnistabelle als Feld vom Typ String zurück.
String[] getColumnTypes()
Die Anfrage liefert die Typenbezeichnung der Spalten der Ergebnistabelle als Feld vom Typ String zurück. Die Bezeichnungen entsprechen den Angaben in der MySQL-Datenbank.
int getRowCount()
Die Anfrage liefert die Anzahl der Zeilen der Ergebnistabelle als int.
int getColumnCount()
Die Anfrage liefert die Anzahl der Spalten der Ergebnistabelle als int.
Teilaufgabe b
Aus der Datenbank sollen die nachstehenden Informationen abgerufen werden:
i.
Zu ermitteln sind für jede Bahnlinie ihr Name sowie ihre jeweilige Gesamtfahrzeit. [3 Punkte]
ii.
Zu ermitteln sind alle Streckenabschnitte, die die maximale Fahrzeit aufweisen. Zu jedem dieser Abschnitte sollen die ID der Bahnlinie, der Name des „von-Bahnhofs“, der Name des „zu-Bahnhofs“ und die Fahrzeit des Abschnitts ausgegeben werden. [5 Punkte]
Teilaufgabe c
Gegeben ist die folgende SQL-Anweisung:
1 SELECT G1.BahnlinieID, S.zuBahnhofName
2 FROM (
3 SELECT BahnlinieID,
4 MAX(Abschnittsnummer) AS max
5 FROM gehoertZu
6 GROUP BY BahnlinieID
7) AS G1
8 INNER JOIN gehoertZu AS G2
9 ON G1.max = G2.Abschnittsnummer AND
10 G1.BahnlinieID = G2.BahnlinieID
11 INNER JOIN Streckenabschnitt AS S
12 ON G2.StreckenabschnittID = S.ID
Analysieren und erläutern Sie die Unterabfrage in den Zeilen 3 – 6. [3 Punkte]
Analysieren und erläutern Sie die vollständige SQL-Anweisung. [4 Punkte]
Erläutern Sie im Sachzusammenhang, welche Information mit der vollständigen SQL-Anweisung bestimmt wird. [3 Punkte]
Aufgabe 3 - Algorithmen auf Feldern und linearen… · Algorithmen auf Feldern und linearen Listen
Aufgabenstellung
Ein Informatikprojektkurs plant, einige Bahnlinien in einer Datenbank zu modellieren. In einer ersten Version sollen unter anderem die Bahnhöfe sowie die Streckenabschnitte abgelegt werden.
Ein Streckenabschnitt ist eine direkte Verbindung von dem einen Bahnhof zu dem anderen Bahnhof ohne Zwischenhalt. Aus Sicherheitsgründen kann ein Streckenabschnitt nur in einer Richtung und somit nicht für die Gegenrichtung genutzt werden.
Eine Bahnlinie besteht aus Streckenabschnitten, die die Folge der Bahnhöfe auf dieser Bahnlinie beschreiben. Dabei wird für jeden zugehörigen Streckenabschnitt die Fahrzeit in Minuten angegeben.
Für die Datenverwaltung will der Projektkurs eine relationale Datenbank erstellen, die auf der folgenden Teilmodellierung beruht:
Die weiteren Aufgaben beziehen sich auf das Datenbankschema aus Abbildung 3, das nur einen Teil der gesamten Datenbank wiedergibt. Beispieldaten zu diesem Schema stehen im Anhang.
Anlage: Ausschnitt aus den Beispieldaten zum Datenbankschema
Bahnhof
Name · barrierefrei
Glücksdorf · 1
Oberdorf · 0
Medeburg · 1
Zweierwalde · 1
Hintertupfingen · 1
Bahnlinie
ID · Name
L1 · Bergexpress
L2 · Burglinie
L3 · Hintertupfingenlinie
L4 · Glücksdorfexpress
Streckenabschnitt
ID · vonBahnhofName · zuBahnhofname
1 · Glücksdorf · Oberdorf
2 · Oberdorf · Medeburg
3 · Medeburg · Hintertupfingen
4 · Medeburg · Zweierwalde
5 · Zweierwalde · Hintertupfingen
6 · Hintertupfingen · Glücksdorf
gehoertZu
StreckenabschnittID · BahnlinieID · Abschnittsnummer · Fahrzeit
1 · L1 · 1 · 30
2 · L1 · 2 · 20
3 · L1 · 3 · 90
1 · L2 · 1 · 40
2 · L2 · 2 · 50
4 · L2 · 3 · 30
5 · L3 · 1 · 50
6 · L4 · 1 · 90
Anhang: Dokumentationen der verwendeten Klassen
Die Klasse Bahnlinientest
Ein Objekt der Klasse wird genutzt, um die Datenbankverbindung und die Datenbankabfragen zu testen.
Bahnlinientest()
Ein Objekt vom Typ DatabaseConnector wird initialisiert und stellt die Datenbankverbindung her.
List<String> ermittleEtwas(String pBName, int pHops)
Diese Methode ist Bestandteil der Teilaufgabe d.
Darüber hinaus verfügt die Klasse über folgende private Methode:
List<String> ermittleNachbarbahnhoefe(String pBName)
Die Methode liefert eine Liste mit den Namen der Nachbarbahnhöfe für den im Parameter übergebenen Bahnhof pBName zurück.
Nachbarbahnhöfe sind die Bahnhöfe, zu denen ein Streckenabschnitt ohne Zwischenhalt von diesem Bahnhof pBName führt.
Falls der Bahnhofsname pBName nicht existiert, wird eine leere Liste zurückgeliefert.
Die Klasse DatabaseConnector
Ein Objekt der Klasse DatabaseConnector ermöglicht die Abfrage und Manipulation einer MySQL-Datenbank.
Beim Erzeugen des Objekts wird eine Datenbankverbindung aufgebaut, sodass anschließend SQL-Anweisungen an diese Datenbank gerichtet werden können.
DatabaseConnector(String pIP, String pPort, String pDatabase, String pUsername, String pPassword)
Ein Objekt vom Typ DatabaseConnector wird erstellt, und eine Verbindung zur Datenbank wird aufgebaut. Mit den Parametern pIP und pPort werden die IP-Adresse und die Port-Nummer übergeben, unter denen die Datenbank mit Namen pDatabase zu erreichen ist. Mit den Parametern pUsername und pPassword werden Benutzername und Passwort für die Datenbank übergeben.
void executeStatement(String pSQLStatement)
Der Auftrag schickt den im Parameter pSQLStatement enthaltenen SQL-Befehl an die Datenbank ab.
Handelt es sich bei pSQLStatement um einen SQL-Befehl, der eine Ergebnismenge liefert, so kann dieses Ergebnis anschließend mit der Methode getCurrentQueryResult abgerufen werden.
QueryResult getCurrentQueryResult()
Die Anfrage liefert das Ergebnis des letzten mit der Methode executeStatement an die Datenbank geschickten SQL-Befehls als Objekt vom Typ QueryResult zurück.
Wurde bisher kein SQL-Befehl abgeschickt oder ergab der letzte Aufruf von executeStatement keine Ergebnismenge (z. B. bei einem INSERT-Befehl oder einem Syntaxfehler), so wird null geliefert.
String getErrorMessage()
Die Anfrage liefert null oder eine Fehlermeldung, die sich jeweils auf die letzte zuvor ausgeführte Datenbankoperation bezieht.
void close()
Die Datenbankverbindung wird geschlossen.
Die Klasse QueryResult
Ein Objekt der Klasse QueryResult stellt die Ergebnistabelle einer Datenbankanfrage mit Hilfe der Klasse DatabaseConnector dar. Objekte dieser Klasse werden nur von der Klasse DatabaseConnector erstellt. Die Klasse verfügt über keinen öffentlichen Konstruktor.
String[][] getData()
Die Anfrage liefert die Einträge der Ergebnistabelle als zweidimensionales Feld vom Typ String. Der erste Index des Feldes stellt die Zeile und der zweite die Spalte dar (d. h. String[zeile][spalte]).
String[] getColumnNames()
Die Anfrage liefert die Bezeichner der Spalten der Ergebnistabelle als Feld vom Typ String zurück.
String[] getColumnTypes()
Die Anfrage liefert die Typenbezeichnung der Spalten der Ergebnistabelle als Feld vom Typ String zurück. Die Bezeichnungen entsprechen den Angaben in der MySQL-Datenbank.
int getRowCount()
Die Anfrage liefert die Anzahl der Zeilen der Ergebnistabelle als int.
int getColumnCount()
Die Anfrage liefert die Anzahl der Spalten der Ergebnistabelle als int.
d) Eine Teilnehmerin des Projektkurses hat das Programm zusätzlich erweitert. Abbildung 4 stellt einen Teil des Implementationsdiagramms dar.
Für die nachstehende Methode der Klasse Bahnlinientest liegt keine Dokumentation vor:
1 public List<String> ermittleEtwas(String pBName, int pHops) { 2 List<String> sammlung = new List<String>(); 3 List<String> nachbarn = ermittleNachbarbahnhoefe(pBName); 4 while (pHops > 0) { 5 List<String> temp = new List<String>(); 6 nachbarn.toFirst(); 7 while (nachbarn.hasAccess()) { 8 String akt = nachbarn.getContent(); 9 sammlung.append(akt);10 nachbarn.remove();11 temp.concat(ermittleNachbarbahnhoefe(akt));12 }13 nachbarn = temp;14 pHops = pHops - 1;15 }16 return sammlung;17 } 1.
Analysieren Sie die öffentliche Methode und erläutern Sie, wie sie arbeitet, wobei Sie Zeile 11 besonders beachten.
2.
Geben Sie für die Beispieldaten im Anhang jeweils an, was die Methode zurückliefert, wenn sie mit pBName = "Zweierwalde" zum einen mit pHops = 1 und zum anderen mit pHops = 2 aufgerufen wird.
3.
Erläutern Sie bezogen auf den Sachkontext, welche Information die Methode liefert.
Aufgabe 4 - Endliche Automaten · Endliche Automaten
Aufgabenstellung:
Beim Snooker, einer Variante des Billards, liegen zu Beginn 15 rote Kugeln auf dem Tisch, Rote (r) genannt, sowie 6 Kugeln in anderen Farben, die gemeinsam als Farbige (f) bezeichnet werden. Zusätzlich befindet sich eine weiße Kugel, die Weiße (w) heißt, auf dem Tisch (vgl. Abbildung 1).
In einem Spielzug (auch „Stoß“ genannt) soll man mit dem Billardstock die Weiße so anstoßen, dass sie eine andere Kugel trifft und diese eingelocht wird, d. h. in eines der Löcher am Tischrand (die „Taschen“) fällt.
Snookerschüler Jonte und sein Trainer Max spielen eine Trainingsvariante des Spiels, bei der alle eingelochten Kugeln sofort wieder auf den Tisch zurückgelegt werden.
Wer an der Reihe ist, muss beim ersten Stoß genau eine Rote einlochen. Gelingt dies, bleibt man am Spiel und man muss beim nächsten Stoß versuchen, genau eine Farbige einzulochen. Wenn auch dies gelingt, so darf man nun wie zuvor so lange abwechselnd Rote und Farbige einlochen, bis man einen Fehler macht.
Es gilt als Fehler, wenn die falsche Kugelsorte angespielt und eingelocht wird, wenn die weiße Kugel eingelocht wird oder wenn bei einem Stoß keine Kugel eingelocht wird. Auch fehlerhaft eingelochte Kugeln werden wieder auf die Anfangsposition auf den Tisch gelegt. Nach einem Fehler gibt es einen Spielerwechsel.
Vereinfachend gehen wir davon aus, dass nur folgende Ereignisse im Training eintreten:
Stoß (s) – Ein neuer Stoß beginnt durch das Anstoßen der Weißen mit dem Billardstock.
Nach dem Anstoßen der Weißen tritt genau eines der folgenden vier Ereignisse ein:
Eine rote Kugel wird angespielt und eingelocht (r).
Eine farbige Kugel wird angespielt und eingelocht (f).
Die weiße Kugel wird eingelocht (w).
Keine Kugel wird eingelocht (k).
Zusätzlich gehen wir davon aus, dass nie mehr als eine Kugel pro Stoß eingelocht wird.
Jonte beginnt alle Trainingsspiele. Er und sein Trainer trainieren so lange, bis sie keine Lust mehr haben. Dabei gehört das Ergebnis des letzten Stoßes immer zum Spiel dazu.
Jonte und Max protokollieren ihre Spiele, um sie später für das weitere Training auszuwerten. Die Trainingsspiele werden protokolliert, indem die zu den Ereignissen gehörigen Buchstaben in der Reihenfolge ihres Auftretens notiert werden.
Beispiel für ein sehr kurzes Trainingsspiel: Jonte locht eine Rote und eine Farbige ein, beim dritten Stoß locht er keine Kugel ein. Max locht dann mit seinem ersten Stoß eine Rote ein. Das Protokoll dazu lautet: srsfsksr
Um eine Vielzahl von Protokollen auswerten zu können, wurde ein deterministischer endlicher Automat A dazu entworfen, um Spielprotokolle zu untersuchen. In Abbildung 2 ist sein Zustandsübergangsdiagramm dargestellt.
a)
Geben Sie für den Automaten die Zustandsmenge, die Menge der Endzustände sowie den Startzustand an.
Ermitteln Sie ein vom Automaten akzeptiertes Wort, bei dessen Verarbeitung alle Zustände durchlaufen werden, und erläutern Sie, wie das Trainingsspiel verlief, das mit diesem Wort protokolliert wurde.
Erläutern Sie im Sachzusammenhang, was die Zustände , und bedeuten und wofür die von ihnen ausgehenden Übergänge stehen.
c)
Die Spielregeln werden erweitert. Unter bestimmten Bedingungen soll es zulässig sein, dass bei einem Stoß mehrere Kugeln in die Tasche fallen und es auf dem Spielfeld zu weiteren Kugelberührungen kommt:
Der Spieler am Zug muss nach dem ersten Stoß zunächst eine Rote einlochen; anschließend sind wie zuvor abwechselnd Farbige (zuerst) und Rote erlaubt.
Zudem muss mit jedem Stoß wenigstens eine Rote oder Farbige eingelocht werden.
Als Fehler gilt weiterhin, wenn die Weiße eingelocht wird oder wenn die Kugelsorten nicht abwechselnd oder in falscher Reihenfolge fallen (z. B. zuerst eine Farbige, obwohl eine Rote an der Reihe wäre).
Entworfen werden soll ein deterministischer endlicher Automat mit dem Eingabealphabet , der genau dann ein Spielprotokoll akzeptiert, wenn im zugehörigen Trainingsspiel die erweiterten Regeln fehlerfrei eingehalten wurden.
Entwerfen Sie für den Automaten ein Zustandsübergangsdiagramm:
Aufgabe 4 - Grammatiken, Kellerautomaten und Gren… · Grammatiken, Kellerautomaten und Grenzen endlicher Automaten
Aufgabenstellung:
Beim Snooker, einer Variante des Billards, liegen zu Beginn 15 rote Kugeln auf dem Tisch, Rote (r) genannt, sowie 6 Kugeln in anderen Farben, die gemeinsam als Farbige (f) bezeichnet werden. Zusätzlich befindet sich eine weiße Kugel, die Weiße (w) heißt, auf dem Tisch (vgl. Abbildung 1).
In einem Spielzug (auch „Stoß“ genannt) soll man mit dem Billardstock die Weiße so anstoßen, dass sie eine andere Kugel trifft und diese eingelocht wird, d. h. in eines der Löcher am Tischrand (die „Taschen“) fällt.
Snookerschüler Jonte und sein Trainer Max spielen eine Trainingsvariante des Spiels, bei der alle eingelochten Kugeln sofort wieder auf den Tisch zurückgelegt werden.
Wer an der Reihe ist, muss beim ersten Stoß genau eine Rote einlochen. Gelingt dies, bleibt man am Spiel und man muss beim nächsten Stoß versuchen, genau eine Farbige einzulochen. Wenn auch dies gelingt, so darf man nun wie zuvor so lange abwechselnd Rote und Farbige einlochen, bis man einen Fehler macht.
Es gilt als Fehler, wenn die falsche Kugelsorte angespielt und eingelocht wird, wenn die weiße Kugel eingelocht wird oder wenn bei einem Stoß keine Kugel eingelocht wird. Auch fehlerhaft eingelochte Kugeln werden wieder auf die Anfangsposition auf den Tisch gelegt. Nach einem Fehler gibt es einen Spielerwechsel.
Vereinfachend gehen wir davon aus, dass nur folgende Ereignisse im Training eintreten:
Stoß (s) – Ein neuer Stoß beginnt durch das Anstoßen der Weißen mit dem Billardstock.
Nach dem Anstoßen der Weißen tritt genau eines der folgenden vier Ereignisse ein:
Eine rote Kugel wird angespielt und eingelocht (r).
Eine farbige Kugel wird angespielt und eingelocht (f).
Die weiße Kugel wird eingelocht (w).
Keine Kugel wird eingelocht (k).
Zusätzlich gehen wir davon aus, dass nie mehr als eine Kugel pro Stoß eingelocht wird.
Jonte beginnt alle Trainingsspiele. Er und sein Trainer trainieren so lange, bis sie keine Lust mehr haben. Dabei gehört das Ergebnis des letzten Stoßes immer zum Spiel dazu.
Jonte und Max protokollieren ihre Spiele, um sie später für das weitere Training auszuwerten. Die Trainingsspiele werden protokolliert, indem die zu den Ereignissen gehörigen Buchstaben in der Reihenfolge ihres Auftretens notiert werden.
Beispiel für ein sehr kurzes Trainingsspiel: Jonte locht eine Rote und eine Farbige ein, beim dritten Stoß locht er keine Kugel ein. Max locht dann mit seinem ersten Stoß eine Rote ein. Das Protokoll dazu lautet: srsfsksr
Um eine Vielzahl von Protokollen auswerten zu können, wurde ein deterministischer endlicher Automat A dazu entworfen, um Spielprotokolle zu untersuchen. In Abbildung 2 ist sein Zustandsübergangsdiagramm dargestellt.
b)
In einigen Trainingsspielen wird bei einem Stoß recht oft versehentlich die weiße Kugel versenkt. In anderen Spielen kommt dies höchstens ein einziges Mal pro Trainingsspiel vor.
Entwerfen Sie eine reguläre Grammatik , die genau diejenigen Wörter erzeugt, die Spielprotokolle darstellen, in denen die weiße Kugel höchstens einmal eingelocht wurde.
Entscheiden Sie durch Ableiten in Ihrer Grammatik , ob die Zeichenketten srswsfsk und swsfsw in der Sprache von enthalten sind.
d)
Trainer Max ist ein erfahrener Snookerspieler, dem Fehler wie das unbeabsichtigte Versenken der weißen Kugel kaum noch unterlaufen. Er verfolgt die Trainingsspiele aufmerksam und vermutet, dass auch Jonte die Weiße inzwischen seltener versehentlich einlocht.
Dies will er mit einem Kellerautomaten überprüfen lassen. Hierzu schreibt er hinter ein aktuelles Spielprotokoll das Trennzeichen t und anschließend ein älteres Spielprotokoll.
Weil nur das Zeichen w für ihn relevant ist, wird im gesamten Eingabewort jedes andere Zeichen als w durch z ersetzt.
Tritt w im zweiten Protokoll (hinter dem Trennzeichen) öfter auf als im ersten Protokoll (vor dem Trennzeichen), geht er von einer tatsächlichen Verbesserung Jontes aus.
Entwerfen Sie einen Kellerautomaten, der ein Eingabewort der beschriebenen Form verarbeitet und entscheidet, ob sich Jonte beim direkten Vergleich der beiden betrachteten Trainingsspiele verbessert hat.
Stellen Sie dar, wie das Wort zwtwww erkannt wird.
e)
Nun spielen Jonte und Max eine Variante, in der die Reihenfolge der eingelochten Farben keine Rolle spielt. Nur eingelochte rote Kugeln bleiben in den Taschen, eingelochte farbige Kugeln kommen zurück aufs Spielfeld. Der Spieler wechselt, wenn ein Stoß keine Kugel oder die weiße Kugel versenkt. Wer die letzte der 15 Roten einlocht, gewinnt.
Jonte behauptet, dass man ein korrekt protokolliertes Spielprotokoll mithilfe eines endlichen Automaten daraufhin untersuchen kann, ob bei dem dazugehörigen Spiel alle 15 Roten eingelocht wurden.
Max hält das für unmöglich, da die Protokolle wegen der Spielerwechsel nach Fehlern beliebig lang werden können und endliche Automaten nicht unbegrenzt zählen können.
Beurteilen Sie, ob Jontes Behauptung zutrifft.
Du kannst auch nach Themen lernen
Übe ein Thema mit gemischten Aufgaben aus den Prüfungen aller Jahre.
- Algorithmen auf Feldern und linearen Listen · 37 Aufgaben · 90 % Wahrscheinlichkeit
- Objektorientierte Modellierung mit Implementationsdiagrammen · 29 Aufgaben · 90 % Wahrscheinlichkeit
- Datenbankmodellierung und Normalisierung · 15 Aufgaben · 90 % Wahrscheinlichkeit
- SQL-Abfragen · 15 Aufgaben · 90 % Wahrscheinlichkeit
- Endliche Automaten · 15 Aufgaben · 90 % Wahrscheinlichkeit
- Grammatiken, Kellerautomaten und Grenzen endlicher Automaten · 15 Aufgaben · 90 % Wahrscheinlichkeit
Andere Prüfungen 2023
GK · Haupttermin (2023) · GK · Haupttermin · Weiterbildungskolleg (2023) · GK · Nachtermin (2023) · LK · Haupttermin · Weiterbildungskolleg (2023) · LK · Nachtermin (2023)