Aufgaben - Lehrgebiet Datenbanksysteme für neue Anwendungen

Transcription

Aufgaben - Lehrgebiet Datenbanksysteme für neue Anwendungen
Begleitmaterial zum Kurs 1580/1582/1584
Aufgabenbeschreibung
für das
Java-Programmierpraktikum
im Wintersemester 2005/2006
Lehrgebiet Datenbanksysteme für neue Anwendungen
I
1. Das Programmierpraktikum
1
2. 6 nimmt!
3
2.1
2.2
2.3
2.4
Aufgabenbeschreibung .................................................................................. 3
Die Regeln ..................................................................................................... 3
Zur Umsetzung .............................................................................................. 5
Optionale Erweiterung .................................................................................. 7
3. File-Manager
3.1
3.2
3.3
3.4
3.5
3.6
Verzeichnisansicht ......................................................................................... 9
Sicherungskopien ........................................................................................ 10
Duplikatsuche .............................................................................................. 11
Massenumbenennungen (Batch-Rename) ................................................... 12
Synchronisation ........................................................................................... 12
Konsolenmodus ........................................................................................... 13
4. Kreuzworträtsel-Generator
4.1
4.2
25
Grundlagen der Audioverarbeitung ............................................................. 26
Java Sound-API ........................................................................................... 27
Programmspezifikation ............................................................................... 27
7. Roboter-Versteck-Simulation
7.1
7.2
7.3
7.4
19
Einführung ................................................................................................... 19
Vorbemerkung ............................................................................................. 19
Neuronale Netze .......................................................................................... 20
Hopfield-Netze ............................................................................................ 20
Aufgabenbeschreibung ................................................................................ 22
6. Morsealphabet
6.1
6.2
6.3
15
Wortauswahl und Rätselerzeugung ............................................................. 15
Zuweisung von Wortbeschreibungen .......................................................... 16
5. Texterkennung
5.1
5.2
5.3
5.4
5.5
9
31
Aufgabenbeschreibung ................................................................................ 31
Das Spielszenario ........................................................................................ 31
Die Roboter und ihre Programmierung ....................................................... 32
Die Simulation ............................................................................................. 35
8. Programmierrichtlinien
39
9. Dokumentationsrichtlinien
41
1
Das Programmierpraktikum
Das Programmierpraktikum dient der Übung im praktischen Umgang mit einer gängigen
Programmiersprache. Zur Zeit findet dazu die Programmiersprache Java Anwendung,
die aufgrund ihrer Konzepte und insbesondere auch aufgrund ihrer Eignung für InternetAnwendungen weithin Akzeptanz gefunden hat. Gute Java-Kenntnisse sind deshalb Voraussetzung für eine erfolgreiche Teilnahme an diesem Praktikum. Sie sollen lernen, die
Lösung zu einem überschaubaren Problem zu finden und mit Hilfe der Programmiersprache Java zu implementieren.
Wie bereits in den letzten vom Lehrgebiet „Datenbanksysteme für neue Anwendungen“
durchgeführten Programmierpraktika können Sie je nach Interesse und Fähigkeiten zwischen sechs Themen wählen. Diese sind im einzelnen:
•
•
•
•
•
•
6 nimmt!
File-Manager
Kreuzworträtsel-Generator
Texterkennung
Morsealphabet
Roboter-Versteck-Simulation
Unsere Absicht ist es, mit diesen Aufgaben ein so breites Spektrum von Themen abzudecken, daß jeder etwas darunter finden kann, das seinen persönlichen Neigungen entspricht. Wir haben an manchen Stellen bewußt auf eine detaillierte Spezifikation des von
Ihnen zu erstellenden Programms verzichtet, insbesondere in Bezug auf die graphische
Benutzeroberfläche und die Interaktion von Programm und Benutzer, um Ihnen die
Möglichkeit zu geben, selbst kreativ zu werden und eigene Konzepte zu realisieren. Wir
hoffen, daß Ihnen dadurch die Arbeit an Ihrer Aufgabe noch mehr Spaß macht.
Die Aufgaben weisen einen unterschiedlichen Schwierigkeitsgrad auf, der auch von
Ihrer individuellen Vorbildung abhängig ist. Wählen Sie Ihre Aufgabe sorgfältig aus;
berücksichtigen Sie dabei auch den Aspekt der Implementierbarkeit im Zeitrahmen des
Programmierpraktikums.
Hinweis: Manche Aufgabenbeschreibungen nehmen Bezug auf farbige Darstellungen in
Graphiken. Da der Druck der Aufgabenbeschreibungen aber in schwarz-weiß erfolgt, ist
es notwendig, sich bezüglich der Farbinformation die zugehörige PDF-Datei auf der
Homepage des Lehrgebiets anzusehen.
Beachten Sie, daß die gewählte Aufgabe von Ihnen allein zu lösen ist. Gruppenarbeiten sind nicht zulässig!
2
6 nimmt!
2.1 Aufgabenbeschreibung
Das Ziel bei dieser Aufgabe ist es, das Spiel „6 nimmt!“ zu implementieren. Es handelt
sich hierbei um ein Kartenspiel für 2-10 Spieler, bei dem es darum geht, am Ende des
Spiels möglichst wenig Minuspunkte zu haben. Die Applikation erlaubt es, sowohl
gegen eine beliebige Anzahl von Computergegnern als auch gegen eine beliebige Anzahl
von menschlichen Gegnern (am selben Computer) oder eine Kombination (bis insgesamt
maximal 10) daraus zu spielen. Die momentane Spielkonfiguration soll grafisch ansprechend dargestellt werden. In verschiedenen Schwierigkeitsgraden für den Computergegner verwendet dieser unterschiedliche Strategien.
2.2 Die Regeln
Im Spiel gibt es insgesamt 104 Karten, die von 1 bis 104 durchnumeriert sind. Jede Karte
hat den Wert 1 (weiße Karten) mit folgenden Ausnahmen: Karten mit Zahlen, die durch
5 teilbar sind, haben den Wert 2 (blaue Karten), Karten mit Zahlen die durch 10 teilbar
sind, haben den Wert 3 (orange Karten), Karten mit Zahlen, die durch 11 teilbar sind,
haben den Wert 5 (rote Karten) und die Karte mit der Zahl 55 hat den Wert 7 (lila Karte).
Gespielt werden mehrere Spiele, bis ein Spieler am Ende eines Spiels mehr als 66 Punkte
hat. Es gewinnt dann der Spieler, der die wenigsten Punkte hat.
Zu Beginn jedes Spiels werden die Karten gemischt. Jeder Spieler bekommt 10 Karten
verdeckt ausgeteilt, die sich nur der jeweilige Spieler ansehen darf. Wie dies mit mehreren menschlichen Spielern an einem Rechner zu lösen ist, wird später erklärt.
Reihen bilden
Vom restlichen Kartenstapel werden die obersten 4 Karten offen in der Tischmitte ausgelegt (siehe Abbildung 2.1). Jede Karte bildet den Anfang einer Reihe, die einschließlich
4
KAPITEL 2
6 NIMMT!
dieser ersten Karte auf maximal 5 Karten anwachsen darf. Der Kartenstapel, der jetzt
noch übrig ist, wird erst wieder im nächsten Spiel benötigt.
Spielablauf
1. Karte ausspielen
Alle Spieler legen verdeckt 1 Karte von ihren Handkarten vor sich auf den Tisch.
Erst dann, wenn der letzte sich entschieden hat, werden die Karten aufgedeckt.
Wer die niedrigste Karte ausgespielt hat, legt sie als erste Karte an eine der vier
Reihen, dann kommt die zweitniedrigste Karte in eine Reihe usw., bis auch die
höchste Karte dieser Runde in einer Reihe untergebracht wurde. Die Karten werden in einer Reihe immer nebeneinander gelegt. Danach wiederholt sich dieser
Vorgang so oft, bis alle 10 Karten ausgespielt wurden.
Wie werden die Karten zugeordnet?
Jede ausgespielte Karte paßt immer nur in eine Reihe. Es gelten folgende Regeln:
– „Aufsteigende Zahlenfolge“
Die Karten einer Reihe müssen immer eine aufsteigende Zahlenfolge haben.
– „Niedrigste Differenz“
Eine Karte muß immer in die Reihe gelegt werden, deren letzte Karte die
niedrigste Differenz zu der neuen Karte aufweist.
Anlegerichtung
34
15
101
60
Abbildung 2.1: Startkonfiguration
KAPITEL 2
6 NIMMT!
5
2. Karten kassieren
Solange man eine Karte in einer Reihe unterbringt, ist alles bestens. Wenn aber in
eine Reihe, in die man anlegen muß, keine weitere Karte mehr paßt, oder wenn
man eine Karte in keine Reihe anlegen kann, muß der Spieler, der die entsprechende Karte gespielt hat, Karten kassieren.
3. „Volle Reihe“
Eine Reihe ist mit 5 Karten voll. Wenn nach Regel 2 eine sechste Karte in diese
Reihe gelegt werden muß, dann muß der Spieler, der diese Karte ausgespielt hat,
alle 5 Karten dieser Reihe an sich nehmen. Seine sechste Karte stellt den Anfang
der neuen Reihe dar.
4. „Niedrigste Karte“
Wer eine Karte ausspielt, deren Zahl so niedrig ist, daß sie in keine Reihe paßt,
muß alle Karten einer beliebigen Reihe nehmen und seine „niedrige“ Karte stellt
dann die erste Karte dieser Reihe dar.
Karten, die kassiert werden mußten, werden mit ihren Werten als Minuspunkte für den
entsprechenden Spieler angeschrieben. Diese Karten sind aus dem Spiel und werden
nicht auf die Hand genommen.
Spielende
Das Spiel endet, wenn alle Karten ausgespielt wurden. Die Ergebnisse (Minuspunkte)
werden für jeden Spieler vermerkt und es beginnt ein neues Spiel. Das Spiel endet, wenn
ein Spieler insgesamt mehr als 66 Minuspunkte gesammelt hat.
2.3 Zur Umsetzung
Das Spiel ist wie in den Regeln beschrieben umzusetzen. Für die grafische Darstellung
der Karten können sowohl Scans von den Originalkarten (diese werden vom LG nicht
zur Verfügung gestellt) als auch alternative Darstellungen gewählt werden. Neben der
Anzeige der Spielfläche mit den vier Kartenreihen soll für den menschlichen Spieler, der
gerade an der Reihe ist, eine Anzeige der Handkarten existieren. Die Auswahl der zu
spielenden Karte ist sowohl über die Maus, als auch über die Tastatur (z.B. Zahlen 0-9)
zu steuern, damit bei mehreren menschlichen Spielern ebenfalls eine gewisse Geheimhaltung möglich ist. D.h. die Handkarten eines Spielers können zwar von den anderen
6
KAPITEL 2
6 NIMMT!
menschlichen Spielern eingesehen werden, allerdings können die anderen Spieler nicht
erkennen, welche Karte vom aktuellen Spieler durch Tastendruck ausgewählt wird.
Wenn alle Karten ausgespielt sind, beginnt die Anlegephase. Per „Weiter“-Knopf soll
diese Phase für den Betrachter in einzelne Schritte aufgeteilt werden können, d.h. mit
einem Druck auf diesen Knopf wird die nächste Karte angelegt. Wenn der Fall „Niedrigste Karte“ eintritt, muß der entsprechende Spieler aufgefordert werden, eine Reihe auszuwählen. Durch entsprechende Textmeldungen sollen zudem die Züge aller Spieler
nochmal erläutert werden, z.B. „Spieler Jörg legt an Reihe 2 an“, „Computer-Spieler
Apfel nimmt Reihe 1“. Der „Weiter-Knopf“ soll deaktiviert werden können, um ein
schnelleres Spiel zu gewährleisten.
Um taktisches Spiel zu erlauben, muß zu jedem Zeitpunkt des Spiels eine Tabelle mit
den Punkteständen vor Beginn des aktuellen Spiels einzusehen sein. Am Ende jedes
Spiels gibt es eine zusätzliche Anzeige des aktuellen Standes.
Zu Beginn eines Spiels kann gewählt werden, wieviele Spieler (2-10) insgesamt an dem
Spiel teilnehmen und wieviele davon vom Computer gesteuert werden (auch ein Spiel
Computer-Computer soll möglich sein). Jeder Spieler kann seinen Namen eingeben.
Außerdem kann gewählt werden, ob die Standard-Spielgrenze von 66 Punkten geändert
werden soll (auf einen beliebigen Wert) oder ob evtl. stattdessen eine beliebige Anzahl
an Runden gespielt wird. Für jeden Computer-Gegner ist eine Spielstärke auszuwählen.
Zusätzlich soll es möglich sein, auch die Handkarten des Computers einzusehen, wenn er
an der Reihe ist. Dies ermöglicht es, die Strategie des Computers zu überprüfen.
Für die Computer-Strategie gibt es verschiedene Überlegungen, die noch erweitert werden können und sollen:
Einfache Spielstärke: In der Kartenauswahlphase wird eine zufällige Karte gewählt. Im
Fall „Niedrigste Karte“ wird immer die Reihe mit der niedrigsten Gesamtpunktzahl ausgewählt.
Mittlere Spielstärke: Bei der Kartenauswahl wählt der Computer eine Karte, von der er
glaubt, daß er in der Folge keine Kartenreihe nehmen muß. Dies kann z.B. eine Karte mit
niedriger Differenz zu einer Kartenreihe mit weniger als fünf Karten sein oder z.B. eine
Karte mit besonders hoher Differenz zur höchsten Reihe, in der Hoffnung, daß ein anderer Spieler diese Reihe vorher nehmen muß. Es sollte mehrere solcher Regeln geben, die
dann gewichtet werden und unter denen die beste ausgewählt wird. Im Fall „Niedrigste
Karte“ soll der Computer bewußt gegen denjenigen von den verbleibenden Spielern
spielen, der momentan die niedrigste Punktzahl hat.
KAPITEL 2
6 NIMMT!
7
Hohe Spielstärke: Der Computer merkt sich alle gespielten Karten und bezieht dieses
Wissen in seine Berechnungen ein. Außerdem zählt er die aktuellen Punktzahlen auch
während des Spiels mit. Bei der Kartenauswahl spielt der Computer auch bewußt Karten,
um den Fall „Niedrigste Karte“ zu erzwingen, wenn er sich damit wenig schadet, aber
dem führenden Spieler mehr schaden kann.
In der Dokumentation zu Ihrer Implementierung sind die von Ihnen gewählten Computerstrategien ausführlich zu erläutern.
2.4 Optionale Erweiterung
Es gibt noch eine Profivariante des Spiels für 2-6 Spieler, bei der zwei zusätzliche
Regeln hinzukommen:
1. Alle Karten im Spiel sind bekannt
Man spielt nur mit so vielen Karten, wie Spieler teilnehmen. Die Regel hierfür ist:
Anzahl der Spieler mal 10 plus 4 Karten. Beispiel: 3 Spieler, 34 Karten von 1-34.
Alle darüberliegenden Kartennummern werden aussortiert.
2. Jeder Spieler wählt seine 10 Karten selbst aus.
Die Karten werden offen auf dem Tisch ausgelegt. Reihum, beginnend mit einem
zufällig ausgewählten Spieler, nehmen sich die Spieler immer eine Karte, bis sie
10 Karten auf der Hand haben. Jetzt müssen noch 4 Karten auf dem Tisch liegen.
Diese 4 Karten stellen die 4 Reihen dar.
Der weitere Ablauf des Spiels entspricht dem Grundspiel.
Je nach eingestelltem Schwierigkeitsgrad für die Computergegner soll sich hier das Verhalten der Gegner natürlich auch unterscheiden.
3
File-Manager
In dieser Aufgabe soll ein einfacher, aber leistungsfähiger File-Manager programmiert
werden. Neben den Standardaufgaben Dateien und Verzeichnisse kopieren, verschieben,
umbenennen und löschen sowie neue Verzeichnisse anlegen, sollen auch aufwendigere
Operationen wie Duplikatsuche, Massenumbenennung (Batch-Rename) und die Synchronisation von Ordnern angeboten werden. Beim Löschen einer Datei bzw. bei
Kopieroperationen, die zur Ersetzung bestehender Dateien führen, sollen optional Sicherungskopien der alten Versionen angelegt werden.
Auf die Standardaufgaben wird hier nicht näher eingegangen, diese sollen ähnlich wie in
bekannten File-Managern implementiert werden. Die Ansicht von Verzeichnissen und
die „erweiterten Funktionalitäten“ werden im folgenden näher spezifiziert.
3.1 Verzeichnisansicht
Die graphische Benutzeroberfläche soll eine mehrfach vertikal geteilte Ansicht ihres
Fensters ermöglichen, in der die Breite der Spalten vom Benutzer frei eingestellt werden
kann. In der linken Spalte wird der Verzeichnisbaum der Festplatte dargestellt und daneben können die Inhalte von bis zu zwei Verzeichnissen angezeigt werden. Da sich während der Ansicht die Verzeichnisinhalte ändern können (andere Programme können zwischenzeitlich Dateien anlegen oder löschen), soll auch ein Mechanismus implementiert
werden, der die Ansicht bei Bedarf aktualisiert.
Eine Verzeichnisansicht besteht aus einer Tabelle mit den Spaltenüberschriften „Name“,
„Größe“ und „Änderungsdatum“. Angezeigt werden Datei- oder Verzeichnisnamen, die
sich im Verzeichnisbaum eine Ebene unter dem ausgewählten Verzeichnis befinden.
Durch einen Mausklick auf eine dieser Spaltenüberschriften soll nach dem entsprechenden Attribut sortiert werden. Überlegen Sie sich eine geeignete visuelle Darstellung, in
der man Dateien und Verzeichnisse einfach voneinander unterscheiden kann.
Außerdem soll es möglich sein, in eine „flache“ Ansicht zu wechseln, d.h. auch die
Inhalte der Unterordner werden rekursiv durchsucht und in der Tabelle untereinander
angezeigt. In dieser Ansicht wird die Tabelle um eine Spalte mit der Überschrift „Tiefe“
erweitert, in der zu jedem Eintrag eine Zahl angezeigt wird. Diese Zahl entspricht der
Ebene des Verzeichnisbaumes, in der sich der Eintrag befindet. Da in einer Ebene des
Verzeichnisbaumes derselbe Dateiname mehrfach vorkommen kann, wird zu einer
selektierten Zeile der komplette Pfadname angegeben.
10
KAPITEL 3
FILE-MANAGER
Da man i. d. R. nach bestimmten Dateien sucht, soll es möglich sein, für eine Verzeichnisansicht einen Suchfilter für den Namen in Form eines regulären Ausdrucks1 anzugeben. Vereinfacht gesprochen stellen reguläre Ausdrücke Muster dar, welche sich aus
Zeichen, Zeichenklassen und Metasymbolen (z.B. „*, ^, . [, ]“ ) zusammensetzen. Der
Punkt steht für ein beliebiges Zeichen und der Stern für eine beliebige Anzahl von Wiederholungen (auch keine). Damit steht der reguläre Ausdruck „.*“ für eine beliebige
(ggf. leere) Zeichenkette. Teilmengen des Zeichensatzes können über sogenannte Zeichenklassen dargestellt werden, z.B. steht „[0123456789]*“ für eine beliebige Sequenz
von Ziffern. Ein äquivalente Darstellung wäre auch „[0-9]*“, denn über das Minuszeichen können Bereiche definiert werden. Der Ausdruck „[^0-9]“ hingegen beschreibt das
Komplement der nach dem Metasymbol „^“ folgenden Zeichenklasse, hier also Zeichenfolgen die keine Ziffern enthalten. Falls ein Metasymbol als „normales“ Zeichen verwendet werden soll, so kann dies durch Voranstellen eines Rückwärtsschrägstriches (\)
angezeigt werden.
Effizienzüberlegungen
Da die Größe von Unterverzeichnissen rekursiv berechnet werden muß, sollte dies im
Hintergund durch Threads erfolgen. Solange das Ergebnis unbekannt ist, werden vorerst
drei Fragezeichen (???) angezeigt, und sobald die Größe bekannt ist, wird die Ansicht
aktualisiert. Um den Vorgang weiterhin zu beschleunigen, wird für jedes bereits berechnete Unterverzeichnis die ermittelte Größe und der Zeitstempel der letzten Änderung
festgehalten. Diese Informationen werden beim Beenden des Programmes in einer Datei
gespeichert und beim Start wieder eingelesen, so daß in Zukunft nur noch nicht berechnete oder aktualisierte Verzeichnisse (das Ändern von Verzeichnisinhalten führt zu einer
Änderung des Zeitstempels im Basisverzeichnis) bei der Ermittlung von Größen berücksichtigt werden müssen.
3.2 Sicherungskopien
Es kann ein zentrales Verzeichnis S für Sicherungskopien konfiguriert werden. Dort werden Sicherungskopien von Dateien, die durch Kopier- oder Löschoperationen überschrieben oder gelöscht werden, angelegt. Um Namenskollisionen zu vermeiden, wird
die Datei (oder Verzeichnis) elem unter dem absoluten Pfad „S/jahr/monat/
dd_hhmmss_elem“ gespeichert. Das Präfix „dd_hhmmss_“ (dd=Tag des Monats,
1. Reguläre Ausdrücke (java.util.regexp) werden ab SDK-Version 1.4.2 unterstüzt. Eine ausführlichere Bescheibung aller zulässigen Metazeichen und deren Bedeutung finden Sie in der zugehörigen API-Dokumentation.
KAPITEL 3
FILE-MANAGER
11
hh=Stunde im Bereich 0-23, mm=Minute, ss=Sekunde) entspricht dem Zeitpunkt der
Löschoperation. Der Zeitstempel im Dateisystem hingegen soll dem des Originals entsprechen. Hat man z.B. als Sicherungsverzeichnis das Verzeichnis „Backup“ gewählt,
und löscht man am 24.12.2005 um 14:30:01 die Datei „winword.exe“, so befindet sich
eine Kopie danach an der Stelle „Backup/2005/Dezember/24_143001_winword.exe“.
Löscht man hingegen Dateien unterhalb des Sicherungsverzeichnisses, so werden diese
endgültig gelöscht.
3.3 Duplikatsuche
Nach der Auswahl von zwei Basisverzeichnissen A und B werden alle Dateien ai, die
unterhalb von A liegen (rekursiv für alle Unterverzeichnisse!) mit allen Dateien bj unterhalb B verglichen. Es kann natürlich auch A = B sein. Die Operation soll nun aus allen
Paaren (ai, bj) Gruppen von Dateien finden, die exakt denselben Inhalt haben. Das
Ergebnis der Duplikatsuche soll in einem eigenen Fenster als übersichtliches Protokoll
(Datum, Uhrzeit, verglichene Verzeichnisse, verzeichnisweise Auflistung von Duplikatgruppen, Dauer der Suche, etc.) angezeigt und als Textdatei abgespeichert werden können. Außerdem soll es in einem Löschdialog möglich sein zu entscheiden, welche Duplikate aus einer Duplikatgruppe (= Menge identischer Dateien) gelöscht werden sollen.
Effizienzüberlegungen
Diese Operation muß sehr effizient programmiert werden, da bei einem einfachen paarweisen Vergleich eine sehr große Anzahl von Paaren (ai, bj) untersucht werden muß. Bei
zwei Verzeichnissen mit je 10.000 Dateien mit einer durchschnittlichen Größe von 50K
müßten also bereits 100.000.000 Paare betrachtet und bis zu 4768GB von der Festplatte
gelesen werden. Über die Dateigröße kann daher zunächst eine Liste von potentiellen
Duplikatpaaren berechnet werden. Effizient implementieren läßt sich dies z.B. über
einen sogenannten Hash-Join.
Dazu wird für die kleinere der Dateilisten eine Hashtabelle erzeugt, und über eine geeignete Hashfunktion wird die Dateigröße als Schlüssel benutzt und auf einen Index der
Tabelle abgebildet. Jeder Tabelleneintrag (Bucket) ist eine Liste, an die Paare der Form
(Dateiname, Größe) angefügt werden können. Nachdem die Hashtabelle komplett mit
den Werten der ausgewählten Dateiliste gefüllt worden ist, wird nun jedes Element aus
der anderen Dateiliste benutzt, um über deren Größe mittels der Hashfunktion den
Bucket zu bestimmen, in dem potentiell Dateien gleicher Größe sein könnten. Schließ-
12
KAPITEL 3
FILE-MANAGER
lich durchläuft man diesen Bucket, vergleicht die Dateigrößen mit dem zuvor ausgewählten Element und gibt die Paare von Dateien mit gleicher Größe zurück.
Lediglich Gruppen gleicher Dateigröße müssen danach auf Bytegleichheit überprüft
werden. Vorsicht, ein byteweises Lesen kann u. U. sehr langsam sein, wählen Sie geeignete API-Klassen!
3.4 Massenumbenennungen (Batch-Rename)
Nach der Auswahl eines Basisverzeichnisses D kann eine massenhafte Umbenennung
von Dateien über die Angabe von zwei Mustern A (Altes Muster) und N (Neues Muster)
erfolgen. Alle Dateien fi innerhalb von D, die auf das Muster A passen, werden in das
Muster N umbenannt.
A-Muster können mehrere in runde Klammern eingeschlossene reguläre Ausdrücke enthalten. Im N-Muster können Zeichenkombinationen „(k)“, mit k > 0, verwendet werden,
welche dann durch die auf den k-ten regulären Ausdruck des A-Musters passende Zeichenfolge maximaler Länge ersetzt werden. Beispiele: A = (.*).htm([^l].*) und
N = (1).html(2), eine Datei „index.htm“ wird in „index.html“ umbenannt und
„index.htm.backup“ würde in „index.html.backup“ übergehen oder über A = (.*)_([0-9])
und N = (1)_00(2) können Dateien, die mit einer Ziffer enden, mit vorangestellten Nullen aufgefüllt werden.
Als Ausgabe soll in einem separaten Fenster eine Liste aller Umbenennungen angezeigt
werden. Über Schaltflächen „Umbenennen“ oder „Abbrechen“ kann die Operation dann
durchgeführt oder abgebrochen werden. Falls mehrere Dateinamen auf denselben neuen
Namen abgebildet werden, so erscheint eine Fehlermeldung und eine Liste all dieser
Dateien.
3.5 Synchronisation
Hier soll ein Verzeichnis A mittels eines Referenzverzeichnisses B aktualisiert werden.
Dabei werden neuere oder fehlende Dateien bzw. Unterverzeichnisse von B nach A
kopiert. In den folgenden Formeln bezeichne di ein beliebiges Element (Ordner oder
Datei) welches in Ordner D enthalten ist und ein hochgestelltes T den zugehörigen Zeitstempel. Nach der Synchronisation sind die Elemente E1 in Verzeichnis A gegeben
durch:
E1 = {bi | biT > AT} ∪ { ai | ∃ ai = bj und aiT ≥ bjT} ∪ {ai | aiT > BT}.
KAPITEL 3
FILE-MANAGER
13
Zusätzlich kann durch eine Option erreicht werden, daß Dateien und Verzeichnisse, die
in A aber nicht in B vorhanden sind, gelöscht werden, sofern der Zeitstempel dieser Elemente älter ist als der von B. Man erhält also das Ergebnis:
E2 = E1 ∩ {ai | ∃ bj = ai und aiT < BT}.
Außerdem gibt es noch eine Variante, die beide Verzeichnisse wechselseitig synchronisiert, um beide auf denselben Stand zu bringen. Als Ausgabe soll in einem separaten
Fenster eine Liste aller vorzunehmenden Änderungen angezeigt werden. Danach kann
die Operation vom Benutzer wahlweise durchgeführt oder abgebrochen werden.
3.6 Konsolenmodus
Die Duplikatsuche, die Umbenennung und die Synchronisation sollen über geeignete
Parameter auch als Konsolenanwendung zur Verfügung stehen. Umbenennung und Synchronisation sollen dabei die Option „-n“ anbieten, die keine Änderungen auf der Festplatte durchführt, sondern stattdessen nur die durchzuführenden Aktionen anzeigt.
4
Kreuzworträtsel-Generator
In dieser Aufgabe soll ein Kreuzworträtsel-Generator programmiert werden. Mit dem
Programm sollen die klassischen Schwedenrätsel erstellt werden können. In solchen Rätseln sind die Umschreibungen der Wörter sowie deren Position und Länge festgelegt.
Ein Beispiel für solch ein Rätsel zeigt das folgende Bild:
Schwedenrätsel (Quelle: http://www.tagesraetsel.alojado.de)
4.1 Wortauswahl und Rätselerzeugung
Die zu erratenden Wörter sollen aus einer Wortliste, die in einer Datei gespeichert ist,
ausgewählt werden können. Sehen Sie dazu eine zufällige Auswahl von n verschiedenen
16
KAPITEL 4
KREUZWORTRÄTSEL-GENERATOR
Wörtern sowie eine manuelle Auswahl durch den Benutzer vor. Das Programm soll dazu
Dateien verarbeiten können, wie sie von Rechtschreibprüfprogrammen wie ispell verwendet werden (einfache ASCII-Dateien, in jeder Zeile steht genau ein Wort). Beachten
Sie, daß solche Dateien teilweise beachtliche Wortmengen enthalten können (aktuelle
deutsche ispell-Wortliste ca. 30.000). Überlegen Sie sich, wie dem Benutzer trotzdem
eine komfortable Auswahl ermöglicht wird.
Die Wörter sollen dann durch das Programm in ein Gitter eingefügt werden, so daß möglichst wenig Freiraum entsteht. Die Gittergröße soll dabei nicht vorgegeben, sondern entsprechend der Wörter, die eingefügt werden sollen, ermittelt werden. Diese verbleibenden Freiräume sollen durch beliebige weitere Wörter der Wortliste (Füllwörter) belegt
werden können. Diese sind so einzufügen, daß das vorher entstandene Gitter nicht weiter
vergrößert werden muß. Die Anzahl der Füllwörter soll dabei nicht mehr als 30% der
ausgewählten Wörter betragen. Weiterhin dürfen solche zusätzlichen Begriffe nur ein
einziges Mal im Rätsel auftreten. Insbesondere dürfen bereits ausgewählte Begriffe nicht
als Füllwörter verwendet werden. Eine optimale Platzausnutzung, wie sie im Beispielrätsel dargestellt ist, ist nicht immer möglich. Felder, die weder Wörter noch deren
Beschreibungen enthalten, müssen im fertigen Rätsel entsprechend markiert werden.
Entwerfen Sie einen geeigneten Algorithmus, der eine gegebene Wortmenge entsprechend in einem Gitter plaziert. Felder, die Beschreibungen der Wörter enthalten, sollen
zunächst freigelassen werden. Achten Sie darauf, daß das Kreuzworträtsel eine „hübsche“ Form erhält, also z.B. nicht alle Wörter in einer Zeile senkrecht angeordnet sind.
Höhe und Breite sollen maximal das 4fache der jeweils anderen Dimension betragen.
4.2 Zuweisung von Wortbeschreibungen
In einem zweiten Schritt sollen den Wörtern im Rätsel Beschreibungen hinzugefügt werden. Diese Beschreibungen sollen aus einer weiteren Datei stammen und durch den
Benutzer eingegeben werden können. Achten Sie darauf, daß zu einem Wort mehrere
Beschreibungen existieren können. Der Benutzer soll die Möglichkeit bekommen, seine
Wortbeschreibungen für eine spätere Verwendung zu speichern und zu editieren. Es sollen natürlich nur die Beschreibungen für Wörter, die im Rätsel vorkommen, angezeigt
werden. Das Dateiformat für die Wortbeschreibungen sei wie folgt festgelegt: In jeder
Zeile steht an der ersten Position das Wort gefolgt von ein oder mehreren Leerzeichen
und der Beschreibung. Gibt es zu einem Wort mehrere Beschreibungen, so gibt es in der
Datei auch entsprechend viele Zeilen.
KAPITEL 4
KREUZWORTRÄTSEL-GENERATOR
17
Sind die Beschreibungen zu allen Wörtern bekannt, sollen diese in das Rätsel eingefügt
werden. Wählen Sie, ob die Beschreibungen direkt im Gitter erscheinen oder als
Legende unter dem eigentlichen Rätsel. In diesem Fall sind innerhalb des Rätsels Nummern einzutragen.
Als Ausgabe soll das Programm das fertige Rätsel als Bild anzeigen und abspeichern
oder direkt ausdrucken können.
Neben der graphischen Oberfläche soll das Programm auch als rein textuell basierte Version existieren. In dieser soll das Programm Wörter mit ihren Beschreibungen (vgl.
Dateiformatbeschreibung oben) vom Benutzer einlesen und das fertige Rätsel in eine
Datei, die ein entsprechendes Bild enthalten soll, speichern. Das Programm soll beim
Start zwei Parameter erhalten. Der erste Parameter gibt den Namen der Datei an, in der
das erzeugte Rätsel als Bild gespeichert werden soll. Der zweite Parameter soll eine
Datei kennzeichnen, die Paare (Wort, Beschreibung) enthält, aus dem das Programm
sich Füllwörter auswählen darf. Das Format dieser Datei sollte dem für die Zuweisungen
von Wortbeschreibungen entsprechen.
5
Texterkennung
5.1 Einführung
Manchmal möchte man einen Text, der nur in gedruckter Form verfügbar ist, am Computer bearbeiten. Dazu ist es notwendig, die im Text enthaltenen Daten in eine maschinenlesbare Form zu bringen. Dies kann natürlich geschehen, indem der Text abgeschrieben wird. Aufgrund des dafür notwendigen enormen Zeitaufwands ist es wünschenswert,
dies automatisch durchführen zu lassen. Der erste Schritt dieses Verfahrens besteht aus
dem seitenweisen Einscannen des vorhandenen Textes. Die dadurch erhaltenen Daten
sind jedoch Bitmaps (Bilder) und so in dieser Form einer Textbearbeitung nicht zugänglich. Ein Texterkennungsprogramm schließt diese Lücke, indem es solche Bilder in textuelle Information umwandelt.
5.2 Vorbemerkung
Ihre Aufgabe ist es, eine Texterkennung für „einfache“ Texte zu implementieren. „Einfach“ bedeutet in diesem Zusammenhang, daß:
• es sich um reinen Text handelt, d.h. der Text enthält keine Tabellen oder Bilder
• nur eine einzige Schriftart im Text verwendet wird
• die Schriftgröße auf einer Seite nicht variiert
Weiterhin nehmen wir an, daß der Text perfekt ausgerichtet eingescannt wurde, so daß
aufeinanderfolgende Zeilen durch einen schmalen Leerraum getrennt sind. Eine weitere
Vereinfachung betrifft die Anordnung aufeinanderfolgender Symbole. Wir fordern, daß
der zu erkennende Text frei von Unterschneidungen (wie z.B. in „AV“) ist. Damit existiert zwischen zwei Buchstaben einer Zeile ein senkrechter Leerraum. Beachten Sie
jedoch, daß die Breite eines Zeichens trotzdem nicht fest ist (z.B. „HiH“). Durch die
genannten Anforderungen lassen sich die einzelnen Symbole relativ leicht aus dem Text
extrahieren.
Neben der Separation von Bildteilen, die einzelnen Buchstaben entsprechen, müssen
diese auch noch den richtigen Zeichen zugeordnet werden. Eine einfache 1:1 Überprüfung zu bekannten Mustern kann hier nicht verwendet werden. Gründe hierfür sind u.a.
• Unterschiedliche Auflösungen beim Scannen
• Verfälschung der Muster durch das Scannen
20
KAPITEL 5
TEXTERKENNUNG
• verschiedene Schriftgrößen
• Fehler in der Originalvorlage
Um dennoch einzelne Zeichen einigermaßen zuverlässig zu erkennen, soll für diese Aufgabe ein einfaches neuronales Netz implementiert werden. In einer „Lernphase“ werden
dem Netz die unterschiedlichen Buchstaben/Zahlen übergeben, die erkannt werden sollen. Während der Texterkennung wird dem Netz ein zu untersuchendes Zeichen gegeben. Es liefert als Rückgabe dasjenige der vorher gelernten Zeichen, welches dem
Ursprungszeichen am ähnlichsten ist. Dieses wird dann in einer passenden Datenstruktur
gesucht, in der auch der dazugehörige ASCII-Code hinterlegt ist.
5.3 Neuronale Netze
Künstliche Neuronale Netze stellen eine Simulation der Nervenzellen eines Menschen
dar. Dabei werden sehr einfach strukturierte Datentypen, welche als Neuronen bezeichnet werden, miteinander zu einem Netz verknüpft. In einer sogenannten Lernphase werden verschiedene Parameter der Neuronen und/oder deren Verbindungen geändert, so
daß in weiteren Phasen das „Gelernte“ reproduziert werden kann.
e0
Eingänge
Ausgang
e1
Fkt
en
Neuron
o
Jedes Neuron besitzt eine Menge von Eingängen
und einen Ausgang. Im Betrieb liegen an den Eingängen unterschiedliche Signale (e1, ..., en) an.
Das Neuron berechnet aus den Eingabedaten über
eine (meist sehr einfache) Funktion ein einzelnes
Ausgabedatum o.
Es gibt sehr viele verschiedene Modelle neuronaler Netze. Für diese Aufgabe sollen die
sogenannten „Hopfield-Netze“ verwendet werden, die sich insbesondere zur Rekonstruktion gestörter Muster eignen. Wenn Sie mit neuronalen Netzen vertraut sind, können Sie auch andere (mächtigere) Netze als das hier beschriebene Modell verwenden.
5.4 Hopfield-Netze
Die Hopfield-Netze sind nach dem amerikanischen Physiker John Hopfield benannt. Es
handelt sich hierbei um eine recht einfache, rekursive Netzstruktur. Im Gegensatz zu vielen anderen neuronalen Netzen, gibt es keine Unterscheidung zwischen Eingabe- und
Ausgabeneuronen. Hopfield-Netze verarbeiten binäre Eingaben aus {-1,1}. Somit entspricht die Größe der angelegten Muster der Anzahl der Neuronen.
KAPITEL 5
TEXTERKENNUNG
21
Die Neuronen eines Hopfield-Netzes sind vollständig vermascht, d.h. von jedem Neuron
gibt es Verbindungen zu allen anderen Neuronen. Eine Verbindung von Neuron i zu
Neuron j ist mit einer reellen Zahl, dem sogenannten „Gewicht“ (wij) gekennzeichnet.
in1
w31
out1
1
w21
in2
w32
out2
2
w12
in3
w13
out3
3
w23
Hopfield-Netz mit 3 Neuronen
In der Lernphase werden die Gewichte des Hopfield-Netzes aus den zu lernenden
Mustern direkt berechnet. Angenommen, wir wollen p Muster der Größe N speichern.
Dann lautet die Formel zur Berechnung der Gewichte:
⎧1
⎪
wij = ⎨ N
⎪
⎩
p
∑x
kj
k =1
0
⋅ xki
falls i ≠ j
sonst
Dabei bezeichne xk das k-te zu lernende Muster und xki das i-te „Bit“ dieses Musters.
In der Arbeitsphase wird ein Muster an das Netz angelegt. Das Netz arbeitet solange, bis
es einen stabilen Zustand erreicht hat. Dieser Zustand entspricht dem Trainingsmuster,
welches dem urspünglich angelegten Muster am ähnlichsten ist. Somit können HopfieldNetze dazu verwendet werden, gestörte oder geänderte Muster zu rekonstruieren.
22
KAPITEL 5
TEXTERKENNUNG
Während der Arbeitsphase wird für jedes Neuron seine Ausgabe berechnet. Dies
geschieht mittels der folgenden Formel:
N
⎧
⎪−1 falls ∑ wij o j ≤ 0
oi = ⎨
j =1
⎪1
sonst
⎩
Die initialen Werte von oj stammen aus dem gestörten Muster. Dies wird solange wiederholt, bis sich das Netz stabilisiert hat, d.h. keine Änderung des Netzes mehr auftritt.
Die Anwendung dieser Formel findet in zwei Varianten statt. In der ersten Variante werden jeweils einzelne (meist) zufällig ausgewählte Neuronen geändert. Dabei ist darauf zu
achten, daß trotzdem alle Neuronen betrachtet werden und die Prüfung auf Änderung des
Netzzustands erst nach Betrachtung aller Neuronen erfolgt. In der zweiten Variante werden die Ausgaben aller Neuronen aufgrund des aktuellen Zustands des Netzes berechnet.
Sehen Sie in Ihrer Implementierung beide Varianten vor.
Hopfield-Netze haben einige wichtige Eigenschaften. U.a. hängt die notwendige Größe
des Netzes von der Anzahl der zu speichernden Muster ab. So darf die Anzahl der
gespeicherten Muster 0.15N nicht übersteigen, um noch eine gute Erkennung zu gewährleisten. Beachten Sie dies bei der Modellierung Ihres Netzes. Intern arbeitet das Netz so,
daß es eine Funktion über alle möglichen Muster nach IR beschreibt, die an den gespeicherten Mustern lokale Minima aufweist. Während der Arbeitsphase wird innerhalb dieser Funktion das am nächsten gelegene Minimum gesucht. Bei der Lernphase kann es
passieren, daß zusätzliche unerwünschte lokale Minima entstehen, die keinem der
gespeicherten Muster entsprechen. Bei der Erkennung kann es also vorkommen, daß ein
nicht gelerntes Muster als Ausgabe produziert wird. Weiterhin ist es nicht ausgeschlossen, daß das Netz zwischen zwei lokalen Minima pendelt. Behandeln Sie die geschilderten Fälle entsprechend.
5.5 Aufgabenbeschreibung
In dieser Aufgabe sollen Sie ein Programm schreiben, welches den in einer Bitmap enthaltenen Text erkennt und ausgibt. Das Programm soll in zwei verschiedenen Phasen
ablaufen: die Lernphase und die Erkennungsphase. In der Lernphase wird dem Programm ein Bild vorgelegt, welches alle zu erkennenden Zeichen enthält. Das Programm
soll aus dem Gesamtbild die einzelnen Zeichen extrahieren und die Muster in einem neuronalen Netz hinterlegen. Weiterhin soll eine entsprechende Datenstruktur aufgebaut
werden, welche die Verknüpfung von gegebenem Muster und textueller Representation
enthält. In der Oberfläche soll dies so aussehen, daß der Benutzer die passenden Zeichen
KAPITEL 5
TEXTERKENNUNG
23
zu den Mustern einzugeben hat. Damit nicht vor jeder Texterkennung die Lernphase
erneut durchlaufen werden muß, soll das Programm die Möglichkeit bieten, die Muster/
Zeichen-Verbindungen in einer Datei abzuspeichern und wieder zu laden. Diese Phase
soll nur von der graphischen Benutzeroberfläche unterstützt werden.
Die Erkennungsphase soll sowohl in einer graphischen Umgebung als auch innerhalb
einer Konsole ausführbar sein.
Die Konsolenanwendung erhält als ersten Parameter die Datei, in der die erlernten
Muster abgespeichert sind. Die weiteren Parameter sind Pfade zu Bilddateien. Diese
Anwendung soll den erkannten Text in der Konsole ausgeben.
In der graphischen Oberfläche sollen einzelne Bilder geladen und auf Knopfdruck
erkannt werden können. Der erkannte Text soll in einem Textbereich zur weiteren Bearbeitung eingefügt werden. Ist dieser nicht leer, soll der Benutzer wählen können, ob der
vorhandene Text ersetzt oder der neue Text an den vorhandenen angehängt werden soll.
Schließlich soll der bearbeitete Text als Datei gespeichert werden können.
Als Bildformat sollen Bilder im PNG-Format verwendet werden. Versuchen Sie nicht,
JPEG-Bilder zu verwenden, da durch das verwendete Kompressionsverfahren die Bilder
sehr stark gestört werden.
Überlegen Sie, wie die einzelnen aus dem Bild extrahierten Zeichen als Eingabe für ein
neuronales Netz verwendet werden können. Denken Sie daran, daß die Bildteile unterschiedliche Größe haben und zweidimensional sind, jedoch das Hopfield-Netz eine eindimensionale Eingabe fester Größe erwartet.
6
Morsealphabet
Um diese Aufgabe bearbeiten zu können, benötigen Sie einen PC mit Soundkarte (mit
MIDI Funktion), Mikrofon und Lautsprechern.
Das Morsealphabet wurde von Samuel Finley Breese Morse erfunden, 1843 errichtete er
die erste Telegrafenlinie von Washington nach Baltimore. Im Morsealphabet zu kommunizieren wird auch als „morsen“ bezeichnet. Das Morsealphabet ist ein simples aber universelles Kommunikationsprotokoll, kleine Sequenzen von mehreren kurzen (⋅) oder langen Signalen (−) stellen eine Codierung für ein Zeichen des Alphabets dar. Als Signale
dienen meistens Schall- oder Lichtimpulse. Der internationale Hilferuf sos (save our
souls) sieht beispielsweise folgendermaßen aus: ⋅ ⋅ ⋅ − − − ⋅ ⋅ ⋅, also dreimal kurz, dreimal lang und dreimal kurz. Das Morsealphabet benutzt u.a. die unten aufgeführten
Codierungen:
Buchstaben (maximal 4 Zeichen)
A ⋅−
G −−⋅
M−−
S ⋅⋅⋅
Y −⋅−−
B −⋅⋅⋅
H ⋅⋅⋅⋅
N −⋅
T −
Z −−⋅⋅
C −⋅−⋅
I ⋅⋅
O −−−
U ⋅⋅−
D −⋅⋅
J ⋅−−−
P ⋅−−⋅
V ⋅⋅⋅−
E ⋅
K −⋅−
Q −−⋅−
W⋅−−
F ⋅⋅−⋅
L ⋅−⋅⋅
R ⋅−⋅
X −⋅⋅−
1 ⋅−−−−
3 ⋅⋅⋅−−
5 ⋅⋅⋅⋅⋅
7 −−⋅⋅⋅⋅
9 −−−−⋅
2 ⋅⋅−−−
4 ⋅⋅⋅⋅−
6 −⋅⋅⋅⋅
8 −−−⋅⋅
0 −−−−−
Ziffern (5 Zeichen)
Neben der Unterscheidung zwischen Signal an und aus wird auch ein Zeitschema benötigt. Als Einheit t dient dabei die Dauer eines Punkt-Signals, damit definiert man dann
die folgenden Signal- und Pausen-Zeitspannen: Strich-Signal (= 3t)1, Pausen innerhalb
einer Zeichensequenz (=1t), Pause zwischen zwei Zeichensequenzen (=3t) und Pausen
zwischen Wörtern (=7t).
1. Bei sehr hohen Geschwindigkeiten und manuell „getasteten“ Nachrichten ist der Wert i.d.R. höher.
26
KAPITEL 6
MORSEALPHABET
Im wesentlichen soll ein Programm entwickelt werden, welches einen Text in akustische
Morsecodes umwandelt und umgekehrt, als Ein- und Ausgabe dienen Mikrofon (bzw.
Audiodateien) und Lautsprecher. Hauptaufgabe dabei ist die Erzeugung und Analyse
von Audio-Datenströmen. Neben einer grafischen Benutzerschnittstelle soll es auch
möglich sein das Programm als Kommando zu benutzen, um über geeignete Parameter
die Konvertierung von Audio-Daten in Text und umgekehrt zu ermöglichen.
6.1 Grundlagen der Audioverarbeitung
Schall ist ein Wellenphänomen, kugelförmig um eine Schallquelle breitet sich eine
Druckschwankung der Luft aus. Ein Schallsignal besteht aus der Überlagerung von
Schallwellen mit verschiedenen Frequenzen. Misst man an einer bestimmten Stelle den
Schalldruck über einen bestimmten Zeitraum, so erhält man einen schwingungsförmigen
Verlauf. Über ein Mikrofon wird diese Schalldruckschwankung zunächst in die mechanische Schwingung einer Membran und dann in elektrische Schwingungen umgesetzt.
Dieses analoge Signal wird nun durch einen Analog/Digital-Konverter, einen spezialisierten Chip auf der Soundkarte, in ein digitales Format überführt.
Abbildung: Sampling1
Diesen Vorgang nennt man Sampling. Umgekehrt läßt sich über einen Digital/AnalogWandler aus einem Sample wieder ein analoges Signal herstellen, welches dann eine
Lautsprechermembran in Schwingung versetzt, um Schallwellen zu erzeugen. Folgende
Begriffe werden in diesem Zusammenhang benutzt:
Encoding: Wie wird das analoge Signal in Zahlenwerte umgewandelt? Das einfachste
Verfahren ist PCM (pulse code modulation), bei dem, wie in der obigen Abbildung
gezeigt, die Amplitudenwerte auf einen durch wenige Bits darstellbaren Bereich umgerechnet (quantisiert) werden.
Sample-Size: Auflösung in k Bits, in der das Signal auf einer Skala zwischen 0 bis 2k
(unsigned) bzw. −2(k−1) bis 2(k−1) (signed) dargestellt wird.
1. Quelle: Guido Krüger, Handbuch der Java-Programmierung, 3. Auflage, Addison-Wesley.
HTML Version zum freien Download unter http://www.javabuch.de
KAPITEL 6
MORSEALPHABET
27
Sample-Rate: Anzahl der Samples, die pro Sekunde pro Aufnahmekanal (Stereo = 2,
Mono = 1) erzeugt werden.
Frame: Ein Frame umfasst die Anzahl aller gleichzeitig erstellten Samples aller Aufnahmekanäle. Im PCM-Format ist die Frame-Rate gleich der Sample-Rate. Dies muss nicht
immer so sein, kompliziertere Formate können auch ganze Serien von Samples und
zusätzliche Informationen enthalten.
Beispiele
Ein PCM-Datenstrom mit einer Sample-Rate von 8000, Auflösung 8 Bit und einem Aufnahmekanal (Mono) enthält 8000 Byte pro Sekunde. Wird dagegen in CD-Qualität
(Sample-Rate 44100, Auflösung 16 Bit, Stereo) aufgenommen, so fallen 176400 Byte je
Sekunde an.
6.2 Java Sound-API
Seit der Version 1.3 enthält das JDK die Pakete javax.sound.sampled und
javax.sound.midi. Ersteres dient der Behandlung von Digital Audio, und letzteres
ermöglicht es MIDI1-Daten an Musikgeräte zu senden oder zu empfangen. Das MIDIDatenformat orientiert sich am Notensatz, hier werden Daten über die Tonhöhe, die
Länge, die Anschlagsstärke und die Klangfarbe zwischen Programm und Musikgerät
ausgetauscht.
Einen guten Überblick erhält man, wenn man die Java Sound-Demo2 installiert, und
anhand des Beispielcodes, der API-Dokumentationen und dem Java Sound-ProgrammerGuide3 die Konzepte der Sound-Programmierung näher studiert.
6.3 Programmspezifikation
Mit den oben beschriebenen Hilfsmitteln ist nun ein Programm unter Berücksichtigung
folgender Aspekte zu realisieren:
• Aus verschiedenen Eingabequellen soll ein Strom von Morsecodes erzeugt werden, der graphisch als Punkt-Strich-Code und als Text visualisiert werden soll.
1. Musical Instruments Digital Interface
2. http://java.sun.com/products/java-media/sound/index.html
3. http://java.sun.com/j2se/1.5/docs/guide/sound
28
KAPITEL 6
MORSEALPHABET
Eingabequellen sind die weiter unten beschriebenen Dateiformate, die Tastatur
und das Mikrofon.
• Im Aufnahmemodus können über Mikrofon oder Tastatur Morsesequenzen eingegeben werden. Eine Überarbeitung des erkannten Textes nach der Aufnahme sollte
möglich sein.
• Im Abspielmodus kann die aktuell im Speicher enthaltene Nachricht über die
Soundkarte abgespielt werden. Dabei können die Abspielgeschwindigkeit in Zeichen pro Minute, sowie Musikinstrument und Tonhöhe variiert werden.
• Mittels einer Importfunktion können Morsesequenzen aus einer Audiodatei, einer
Morsecodedatei, die Punkt-Strich-Codes enthält, oder einer Textdatei eingelesen
werden. Über eine Exportfunktion kann die Nachricht in einem der o.g. Formate
unter Beachtung der aktuell eingestellten Abspielparameter gespeichert werden.
Datei-Formate
Eine Audiodatei soll lediglich im Wave-Format (.wav) importiert werden können. Der
Export von Audiodaten erfolgt in einer MIDI-Datei und enthält die Parameter Instrument, Tonhöhe und Abspielgeschwindigkeit.
Eine Textdatei darf nur große und kleine Buchstaben ohne Umlaute und Ziffern enthalten. Leerraum kann beliebig vorhanden sein.
Eine Morsecodedatei enthält die Zeichen Punkt (.) und Minus (-), Buchstabensequenzen
werden durch ein oder mehrere Leerzeichen, und Worte durch einen Schrägstrich (/)
oder einen Zeilenumbruch getrennt. Zeilen, die mit einer Raute (#) beginnen stellen
einen Kommentar dar.
Analyse-Algorithmus
Die anspruchvollste Teilaufgabe ist die Implementierung eines geeigneten Algorithmus
zur Analyse eines Audio-Datenstromes. Einige Probleme, die dabei zu bewältigen sind,
sollen hier kurz diskutiert werden.
Erkennung der Zeiträume von Pause und Signal: Dazu muss die Abweichung der
Samples vom Ruhe-Pegel betrachtet werden, ist in einem gewissen Zeitraum der Durchschnittswert der Samples unterhalb eines Schwellwertes, so wird dieses Zeitintervall als
Pause gewertet, andernfalls als Signal. Durch eine vorhergehende Normierung und
Reduktion der Audiodaten (Konvertierung auf ein sinnvolles Format!) kann dies unabhängig von einem bestimmten Audioformat erfolgen.
Geschwindigkeit der Morsesequenzen: Die Pausen und Signalzeiten können innerhalb
einer von Menschenhand erzeugten Nachricht leicht schwanken, daher muss hier für
Pausenzeiten, sowie langen und kurzen Signalzeiten an einen Toleranzbereich gedacht
KAPITEL 6
MORSEALPHABET
29
werden. Die Geschwindigkeit einer Nachricht soll sich im Bereich zwischen 30-200 Zeichen pro Minute bewegen können und vom Algorithmus selbständig erkannt werden,
damit er in der Lage ist, die Zeitbasis anzupassen.
Um die genannten Probleme zu lösen, sollten Sie zunächst die Erzeugung von MIDIFiles programmieren, damit können Sie dann Testdaten erzeugen, welche Nachrichten
mit unterschiedlichen Geschwindigkeiten und Signaltönen (Instrumente mit kurzer
Klangphase sind ungeeignet) besitzen. Während der MIDI-Player eine Datei abspielt
können Sie über die Soundkarte eine Aufnahme durchführen und diese als Wave-Datei
speichern, um damit ihren Analysealgorithmus zu testen. Durch Experimentieren sollte
danach die Erkennung robuster gestaltet werden, damit schließlich auch von Menschenhand eingetastete Morsesequenzen erkannt werden.
7
Roboter-Versteck-Simulation
7.1 Aufgabenbeschreibung
Die Simulation eines „Versteck-Spiels“ von programmierbaren Robotern ist der Inhalt
dieser Aufgabe. In einem auf einem Raster definierbaren Spielszenario kann man jedem
der beteiligten (virtuellen) Roboter ein Programm zuweisen, nach dessen Anweisungen
diese sich in der Spielwelt bewegen. Während einer der Roboter sucht, können sich die
anderen verstecken oder sich ebenfalls weiterbewegen, bis sie entdeckt worden sind. Der
Anwender erfreut sich neben der Möglichkeit, die Programme selbst zu schreiben und
Szenarien zu bauen, an der Beobachtung der ablaufenden Simulation.
7.2 Das Spielszenario
Die Spielwelt ist auf einem x*y-Raster definiert (8 ≤ x,y ≤ 32). In einem einfachen Editor
legt der Benutzer eine rechteckige Grundform fest und plaziert Barrieren, die weder
durchblickt noch durchschritten werden können, auf dem Raster. Szenarien können geladen, verändert und gespeichert werden. Zusätzlich werden der Startpunkt für das Versteckspiel und eine Simulationsdauer festgelegt. Es ist darauf zu achten, daß das Startfeld und die acht benachbarten Felder nicht von Barrieren getrennt sind. Beachten Sie,
daß das Spielfeld automatisch zu allen Seiten begrenzt ist.
32
KAPITEL 7
ROBOTER-VERSTECK-SIMULATION
Das Dateiformat (hier auszugsweise für das Spielszenario in Abbildung 7.1) ist wie folgt
festgelegt (natürlich ohne die Kommentare):
0
y
x
13
0
S
9
Abbildung 7.1: Spielszenario für die Versteck-Simulation
14,10 //x,y-Ausdehnung des Spielfeldes
5,6 //Startpunkt
25 //Simulationsdauer
//vertikale Barrieren
1,1,1,3 //1. vertikale Barriere
2,1,2,4 //2. vertikale Barriere
4,1,4,3 //3. vertikale Barriere
...
//horizontale Barrieren
4,2,7,2 //1. horizontale Barriere
...
Nach dem Wert für die Simulationsdauer folgt eine Leerzeile. Ebenso ist eine Leerzeile
vor den horizontalen Barrieren eingefügt.
7.3 Die Roboter und ihre Programmierung
Jeder Roboter ist durch eine Grafik oder ein Symbol in der Spielwelt dargestellt. Es muß
deutlich erkennbar sein, in welche Richtung der Roboter „schaut“. Gültige Blickrichtun-
KAPITEL 7
ROBOTER-VERSTECK-SIMULATION
33
gen sind oben, unten, links und rechts gemäß dem Raster der Spielwelt. Jeder Roboter
folgt einem Programm, das ihm vor Durchführung der Simulation zugeteilt worden ist.
Die Programmiersprache
Die Programmiersprache enthält folgende Befehle:
Befehle für die Bewegung:
MoveForward
MoveBackwards
MoveSidewardsLeft
MoveSidewardsRight
Abbildung 7.2: Bewegung
Roboters bei Move-Befehlen.
des
Befehle für die Drehung:
TurnLeft
TurnRight
Turn180
Sonstige Befehle:
Duck
StandUp
Wait
Sprungbefehle/Kontrollstrukturen:
IfRegister(r)Goto(Zeilennummer)
Goto(Zeilennummer)
Bei Move-Befehlen bleibt die Blickrichtung des Roboters stets erhalten. Ein Duck-Befehl
versetzt den Roboter in einen Zustand, in dem er schlechter gesehen werden kann. Allerdings kann er sich auch nicht mehr bewegen. Erst nach einem StandUp-Befehl sind
Bewegungen (incl. Turn-Befehle) wieder möglich. Bei einem Wait-Befehl verharrt der
Roboter auf der Stelle.
Jedem Befehl ist in einem Programm eine Zeilennummer vorangestellt. Zeilennummern
sind innerhalb eines Programms stets aufsteigend.
Die Register
Jeder Roboter hat vier Register, in denen er Informationen über die aktuelle Umgebung
speichert. Diese Register heißen RegFront, RegLeft, RegRight und RegOpponentDetect. In den ersten drei Registern sind boolesche Werte gespeichert, die Auskunft
34
KAPITEL 7
ROBOTER-VERSTECK-SIMULATION
geben, ob direkt vor dem Roboter, links oder rechts eine Barriere oder ein anderer Roboter existiert. Diese Informationen sollen dazu benutzt werden, um herauszufinden, ob
nachfolgend ein Move-Befehl ausgeführt werden kann. Ist der Wert TRUE darin gespeichert, befindet sich etwas auf dem entsprechenden Feld. In dem vierten Register steht
TRUE, falls sich ein gegnerischer Roboter im Sichtfeld befindet. Für den Fänger sind dies
also alle anderen Roboter und für die zu findenden Roboter ist dies nur der Fänger.
Die Sichtweite
Jeder Roboter hat eine bestimmte Sichtweite. In
Feld B
Feld A
Abbildung 7.3 sind die Felder, die ein Roboter
sehen kann, farbig markiert. Sind keine Hindernisse, also Barrieren oder andere Roboter, im Weg,
kann ein Roboter drei Felder geradeaus sehen und
seitlich bis zu zwei Felder weit, wobei jeder Roboter auch die Felder direkt links und rechts neben
ihm sehen kann. Wenn Hindernisse im Weg sind, Abbildung 7.3: Sichtkegel der
wird mit Hilfe von Mittelpunktgeraden entschie- Roboter
den, ob ein Feld sichtbar ist oder nicht. Mittelpunktgeraden verbinden die Mittelpunkte zweier
Felder. Wird die Mittelpunktgerade von einer Barriere geschnitten, so ist das entsprechene Feld nicht sichtbar (z.B. Feld A). Liegt der Endpunkt einer Barriere auf der Geraden, so ist das Feld sichtbar (Feld B). In den Randfeldern (hellgrün oder gelb) besitzt der
Roboter eine eingeschränkte Sichtweite. Gegnerische Roboter, die sich geduckt in diesem Bereich aufhalten, können nicht gesehen werden. Durch Roboter kann nicht hindurchgesehen werden. Bei der Sichtweitenberechnung entsprechen sie einem Feld, das
Barrieren auf allen vier Seiten hat.
Ein Programm
Ein Programm setzt sich aus Programmzeilen und Befehlen zusammen. Jede Zeile eines
Programms hat eine Nummer (aus dem Bereich der natürlichen Zahlen ohne 0). Diese
Nummern können bei Sprungbefehlen benutzt werden. Ein Beispiel für ein mögliches
Programm für einen zu fangenden Roboter ist das folgende:
KAPITEL 7
10
11
12
20
21
22
30
31
32
40
ROBOTER-VERSTECK-SIMULATION
35
IfRegister(RegFront)Goto(20)
MoveForward
Goto(10)
IfRegister(RegLeft)Goto(30)
MoveLeft
Goto(10)
IfRegister(RegRight)Goto(40)
MoveRight
Goto(10)
Duck
Zwischen Zeilennummer und Befehlen steht eine beliebige Anzahl von Leerzeichen.
Programme werden vom Benutzer direkt in Textdateien gespeichert und dann vom
Simulationsprogramm geladen. Vor Ausführung des Programms muß vom Simulationsprogramm die Korrektheit der Programme nicht geprüft werden. Typische Fehler wie
„Syntax Error“ für fehlerhaft geschriebene Befehle oder „Line Number Error“ für
Sprungbefehle mit einer Zielzeile, die nicht existiert, führen zum Abschalten des Roboters (mit entsprechender Fehlermeldung). Er verbleibt dann auf seiner aktuellen Position.
Dasselbe Verhalten zeigt sich, wenn das Programm zu Ende ist, d.h. wenn keine weiteren Befehle mehr zur Ausführung anstehen.
7.4 Die Simulation
Beim Start des Simulators muß zunächst ein Konfigurationsfenster erscheinen. Dort wird
das Spielszenario eingestellt und die Roboterprogramme werden geladen. Neben dem
Fänger, den es in jedem Fall geben muß, sollen mindestens fünf weitere Roboter konfigurierbar sein. Die Konfigurationsmöglichkeiten enthalten neben einer auswählbaren
grafischen Darstellung natürlich auch die Wahl eines individuellen Programms, das aus
einer entsprechenden Datei geladen wird. Einstellbar sollte ebenfalls sein, ob die Simulation nach der im Spielszenario gespeicherten Simulationsdauer abgebrochen wird oder
ob sie erst endet, wenn alle Roboter gefunden wurden.
36
KAPITEL 7
ROBOTER-VERSTECK-SIMULATION
Wird die Simulation gestartet, wird zunächst die
Zugreihenfolge der Roboter per Zufall
bestimmt. Der Fänger hat dabei immer die erste
Position. Er startet stets auf der gekennzeichneten Startposition, während die anderen Roboter
zufällig auf den acht benachbarten Feldern verteilt werden. Die Blickrichtung jedes Roboters Abbildung 7.4: Mögliche Startkonfiwird ebenfalls per Zufall bestimmt, wobei die zu guration. Die Startfelder für die zu
fangenden Roboter keinen anderen Roboter fangenden Roboter sind schraffiert.
ansehen, sondern nach „außen“ schauen (siehe
Abbildung 7.4). Reihum werden nun die Befehle der Roboter ausgewertet, beginnend bei
dem Fänger. In jeder Runde wird genau ein Befehl ausgewertet. Dabei ist zu beachten,
daß Sprungbefehle nicht als „echte“ Befehle gezählt werden. Es können also beliebig
viele (bedingte und unbedingte) Sprungbefehle ausgeführt werden, bevor ein „echter“
Befehl ausgeführt wird. Hat jeder Roboter seinen Zug gemacht, beginnt eine neue
Runde.
Der Fänger wird nach zehn Runden aktiviert. In Runde 11 wird also seine erste Programmzeile ausgeführt. Erst dann können andere Roboter „gefangen“ werden. Sobald
ein Roboter ab Runde 11 das Sichtfeld des Fängers betritt, wird dieser Roboter als gefangen betrachtet und vom Feld entfernt.
Die Register der Roboter sollten regelmäßig aktualisiert werden. Für den Fänger muß
dies nach jedem Zug eines beliebigen Roboters geschehen.
Roboter können andere Roboter nicht wegschieben. Sie bleiben dann einfach auf derselben Position stehen.
Im Simulationsfenster sollte es neben dem aktuellen Spielfeld auch Anzeigen für die
Programme der unterschiedlichen Roboter geben, in denen mindestens der aktuelle
Befehl angezeigt wird. Sollten in einem Zug mehrere Befehle ausgeführt werden (also
z.B. auch Sprungbefehle), so müssen diese mit einstellbarer zeitlicher Verzögerung (oder
wahlweise durch Tastendruck gesteuert) so dargestellt werden, daß der Betrachter dem
Programmablauf folgen kann. Die aktuelle Simulationsrunde ist ebenfalls anzuzeigen.
Auf dem Spielfeld sollten die aktuellen Sichtbereiche und die Registerinhalte aller Roboter angezeigt werden, um nachvollziehen zu können, wie die Roboter reagieren.
Optionale Aufgaben
Die Simulation kann noch interessanter gestaltet werden, wenn die Roboter unterschiedliche Eigenschaften haben. Denkbar sind hier Veränderung der Sichtweite, der
KAPITEL 7
ROBOTER-VERSTECK-SIMULATION
37
Geschwindigkeit oder die Einschränkung des Befehlssatzes. Im Konfigurationsfenster
der Simulation können die Eigenschaften der Roboter dann entsprechend verändert werden. Schön wäre es, wenn neue Roboterkonfigurationen abgespeichert und wieder geladen werden könnten.
Um eine faire Simulation zu gewährleisten, sollten eher positive und eher negative
Eigenschaften gemischt werden.
Beispiele:
• Sichtweite + 1, keine MoveLeft, MoveRight-Befehle
• Geschwindigkeit + 1, alle Move-Befehle bewegen den Roboter gleich 2 Felder
• Sichtweite + 2, es wird automatisch ein Wait-Befehl nach jedem Zug ausgeführt
8
Programmierrichtlinien
Beim Programmentwurf sind folgende Punkte zu beachten:
• Vollständigkeit. Die in den Aufgabenstellungen beschriebenen Mindestanforderungen müssen implementiert sein.
• Korrektheit. Das Programm muß sich immer den Vorgaben entsprechend verhalten. Ein Programmabsturz muß ausgeschlossen sein, so unsinnig die Benutzereingaben auch sein mögen.
• Verständlichkeit. Das Programm muß so einfach zu verstehen sein, daß es später
von einem anderen Programmierer ohne großen Aufwand gepflegt werden kann.
Ein wichtiges Konzept, um dies zu erreichen, ist die
• Modularisierung. Unterteilen Sie ein großes Problem solange in mehrere kleinere, bis jedes Teilproblem klein genug ist, um auf einfache Weise gelöst zu werden.
• Effizienz. Ihr Programm sollte effizient bezüglich Ausführungszeit und Speicherplatzbedarf sein. Dies darf jedoch keinesfalls zu Lasten der anderen Forderungen
gehen.
• Portierbarkeit. Das Programm muß auf anderen PCs kompilierbar und lauffähig
sein. Verwenden Sie deshalb bitte nur JAVATM SDK, Standard Edition, Version
1.4.2. Verweisen Sie nur auf solche Dateien, die von Ihnen selbst erzeugt wurden,
und benutzen Sie dabei niemals absolute Pfadnamen.
Insbesondere die Forderung nach Verständlichkeit hat nicht nur Auswirkungen auf den
Entwurf, sondern auch auf den Programmcode:
• Namen von Klassen, Methoden und Variablen müssen immer eine sinnvolle
Bedeutung haben. Außerdem erhöht es die Lesbarkeit von Programmen, Regeln
bezüglich der Form von Namen zu definieren:
– Variablen- und Methodennamen beginnen immer mit einem Kleinbuchstaben.
– Klassennamen beginnen immer mit einem Großbuchstaben.
– Diese Regeln sind recht willkürlich gewählt, haben sich jedoch bewährt.
• Achten Sie auf die Länge der Methoden. Ab einer gewissen Länge (z.B. 40 Zeilen)
sollten Sie die Methode gemäß der Forderung nach Modularisierung unterteilen.
Beschränken Sie sich auf die mit dem JAVATM SDK, Standard Edition, Version 1.4.2
mitgelieferten Pakete. Alle über diese Pakete hinausgehenden Funktionalitäten sind
eigenständig zu implementieren. Es ist allerdings dringend anzuraten, in den Paketen
40
8
PROGRAMMIERRICHTLINIEN
zunächst nach Methoden mit bestimmter Funktionalität zu suchen, bevor sie eigenständig implementiert werden.
Erstellen Sie einen einfachen objektorientierten Entwurf bevor Sie mit der Implementierung beginnen. Strukturieren Sie dazu das gestellte Problem und teilen Sie es in Einheiten auf, die einzelne Teilprobleme behandeln. Außerdem soll ein Klassendiagramm
erstellt werden, in dem die Abhängigkeit der Klassen untereinander und die Zugehörigkeit zu verschiedenen Einheiten deutlich wird.
9
Dokumentationsrichtlinien
Eine vollständige Dokumentation besteht aus folgenden Teilen:
1.
2.
3.
4.
Deckblatt (Abbildung 9.1 zeigt ein Beispiellayout)
Eine max. zweiseitige Anleitung zu Installation und Aufruf Ihres Programmes
Ein kurze Bedienungsanleitung zur Benutzerschnittstelle
Überblick über die in Ihrem Programm zur Lösung der Aufgabe verwendeten Konzepte (objektorientierter Entwurf, Datentypen, Algorithmen) im Umfang von zwei
bis drei DINA4-Seiten
5. Kommentierter Quelltext
FernUniversität in Hagen
Fachbereich Informatik
Datenbanksysteme für neue Anwendungen
Prof. Dr. R. H. Güting
Wintersemester 2005/2006
JAVA-Programmierpraktikum 1580/1582/1584
Thema:
Kreuzworträtsel-Generator
Vorname Name
Matrikelnummer
Adresse
Telefon
eMail-Adresse
Abbildung 9.1: Ein Beispiel für ein Deckblatt
Das gesamte Dokument ist mit fortlaufenden Seitenzahlen zu versehen.
Zusätzlich sollen Sie eine Dokumentation Ihrer Implementierung mit javadoc (im SDK
enthalten) erstellen. Die von javadoc erzeugten Dateien liefern Sie uns bitte auf Diskette
oder CD und nicht ausgedruckt.
Um den Anforderungen des Programmierpraktikums an eine gute Dokumentation zu
genügen, muß der Quelltext einige Voraussetzungen erfüllen:
42
9
DOKUMENTATIONSRICHTLINIEN
• Der Kopf jeder Methode wird um einen Kommentar ergänzt, der
– die Bedeutung jedes Parameters erklärt, sofern sie nicht eindeutig aus dem
Namen des Parameters hervorgeht.
– die Aufgabe und Funktionsweise der Methode gut verständlich erläutert.
– die Voraussetzungen an den Systemzustand beschreibt, unter denen diese
Methode aufgerufen werden darf. Dies betrifft im wesentlichen den zulässigen Wertebereich von Eingabeparametern oder globalen Variablen und
Annahmen über den vorherigen Aufruf anderer Methoden. So setzen beispielsweise Dateioperationen voraus, daß die betroffene Datei zuvor geöffnet
worden ist.
• Jede Klasse ist mit einer Beschreibung zu versehen, aus der hervorgeht, welche
Bedeutung diese Klasse hat und welche Methoden implementiert sind.
• Zusätzlich wird jede erklärungsbedürftige Anweisung um einen leicht verständlichen Kommentar ergänzt.
• Jede Programmzeile enthält immer nur eine Anweisung.
• Schachtelungstiefen von Anweisungsblöcken sind durch entsprechend tiefe Einrückungen zu visualisieren.
• Logisch zusammengehörende Anweisungsteile müssen als solche erkennbar sein.
Dies kann durch Kommentare oder sinnvolles Einfügen von Leerzeilen erreicht
werden.

Documents pareils