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.