Projektarbeit der Lehrveranstaltungen Parallelverarbeitung & Graphische Datenverarbeitung im WS 07/08 von Thomas Nadler (Projektleiter) & Marc von der Brüggen 3D Dame Name Thomas Nadler Marc von der Brüggen E-Mail Adresse Matrikelnummer Inhaltsverzeichnis Inhaltsverzeichnis ............................................................................................................................. I Darstellung des Themas .................................................................................................................. 1 Projektziele ...................................................................................................................................... 2 Wissenschaftliche Ziele............................................................................................................... 2 Praktische Ziele ........................................................................................................................... 2 Definition der Dameregeln .......................................................................................................... 3 Programmevolution ......................................................................................................................... 4 Verwendete Arbeitsumgebungen .................................................................................................... 5 Installationsanleitung ...................................................................................................................... 6 Serverinstallation ......................................................................................................................... 6 Clientinstallation ......................................................................................................................... 6 Detaildokumentation ....................................................................................................................... 7 Server .......................................................................................................................................... 7 Eingabe ................................................................................................................................... 7 Ausgabe ................................................................................................................................... 8 Verarbeitungslogik .................................................................................................................. 8 Probleme bei der Umsetzung: ........................................................................................... 10 VerbindungsVerwalter ......................................................................................................... 11 Persistenz .............................................................................................................................. 12 Aufbau der XML-Datei ..................................................................................................... 12 BefehlsErzeuger ................................................................................................................... 13 SpielerVerwalter ................................................................................................................... 14 Client ......................................................................................................................................... 15 Tastatureingabe .................................................................................................................... 15 Konsolenausgabe .................................................................................................................. 15 Verarbeitungslogik ................................................................................................................ 17 Grafische Benutzeroberfläche .............................................................................................. 18 Probleme bei der Umsetzung: ........................................................................................... 18 3D Grafik............................................................................................................................... 19 Probleme bei der Umsetzung: ........................................................................................... 20 Spielregeln ............................................................................................................................. 21 Thomas Nadler Marc von der Brüggen I Spielelogik ............................................................................................................................. 21 Klassendiagramme ........................................................................................................................ 22 Server - Main ............................................................................................................................. 22 Server - Persistenz ..................................................................................................................... 23 Server – Befehlshierarchie ........................................................................................................ 24 Client – Main ............................................................................................................................. 25 Client – Benutzeroberfläche ...................................................................................................... 26 ................................................................................................................................................... 26 Theorieteil Java 3D ................................................................................................................... 27 Theorieteil Java und Verwaltung von XML-Dateien .................................................................... 29 Gegenüberstellung SAX und DOM .......................................................................................... 29 Exportieren der Projekte zu JAR-Dateien ..................................................................................... 30 Bedienungsanleitung ..................................................................................................................... 32 Server ........................................................................................................................................ 32 Programmstart ....................................................................................................................... 32 Verfügbare Befehle ............................................................................................................... 32 Konsolenclient ........................................................................................................................... 33 Vorwort ................................................................................................................................. 33 Programmstart ....................................................................................................................... 33 Verfügbare Befehle ............................................................................................................... 33 Beispiel eines Spielaufbaus ................................................................................................... 35 Grafischer Client ....................................................................................................................... 37 Erweiterungsmöglichkeiten ........................................................................................................... 40 Steuerung der Züge mit der Maus ............................................................................................. 40 Veränderung der Benutzerkonten über die graphische Benutzeroberfläche ............................. 40 Veränderung der Spielfeldgrafiken ........................................................................................... 41 Erweiterungen in den administrativen Möglichkeiten des Server ............................................ 42 Bestenliste ................................................................................................................................. 42 Absicherung des Servers gegen Angriffe .................................................................................. 42 Anhang A – JavaDoc....................................................................................................................... 1 Anhang B – Code ............................................................................................................................ 1 Programm Server......................................................................................................................... 1 Packet main ............................................................................................................................. 1 Thomas Nadler Marc von der Brüggen 2 Programm Client ....................................................................................................................... 18 Packet grafik3D ..................................................................................................................... 18 Packet grafikBenutzeroberflaeche......................................................................................... 28 Packet regeln ......................................................................................................................... 32 Packet spieleLogik ................................................................................................................ 34 Anhang C – Literaturverzeichnisverzeichnis .................................................................................. 1 Fachliterarische Quellen:............................................................................................................. 1 Quellen im Internet: .................................................................................................................... 1 Thomas Nadler Marc von der Brüggen 3 Darstellung des Themas In der Lehrveranstaltung Systemprogrammierung der Hochschule Fulda im Wintersemester 07/08 haben wir die Aufgabe gestellt bekommen, ein mit Parallelverarbeitung und optional auch mit graphischen Anteilen zusammenhängendes Projekt zu realisieren. Unser Projektteam hat sich bereits relativ früh für die Entwicklung eines netzwerkfähigen Java-3D-Dame-Spieles entschieden. Damit erhoffen wir durch den Netzwerk- beziehungsweise Server-Client-Teil des Projektes den Anforderungen entsprechend der Parallelität gerecht zu werden und zusätzlich graphische Aspekte mit einbinden zu können. Im Generellen soll das zu verwirklichende Spiel aus zwei verschiedenen Programmen bestehen: einem Clientprogramm, das von einem Benutzer bedient wird und an dem man spielen kann, und einem Serverprogramm, welches die Verbindung zu den einzelnen Clients verwaltet und für die Weiterleitung von Nachrichten zwischen diesen verantwortlich ist. Thomas Nadler 1 Projektziele Bei der Realisierung des Projektes kann man zwischen zwei verschiedenen Arten von Zielen unterscheiden: einem wissenschaftlichen Ziel, das inhaltlich mit der zugehörigen Lehrveranstaltung übereinstimmen sollte und sich auf unsere zu sammelnden Erkenntnisse bezieht, und ein praktisches Ziel, das sich auf das gewünschte Resultat unseres Projektes, d.h. das zu erstellende Programm, bezieht. Wissenschaftliche Ziele Zu den angestrebten wissenschaftlichen Zielen gehört im Server und im Netzwerkteil des Clients die Synchronisation von parallelen Abläufen. Ferner ist für die Speicherung und Verwaltung von Benutzern im Server eine Persistenz vorgesehen, die auf Basis von XML-Dateien zu realisieren ist. In diesem Zusammenhang sollen unsere Kenntnisse, die mit Java und der Erzeugung und Verwaltung von XML-Dateien zusammenhängen aufgefrischt bzw. erweitert werden. Darüber hinaus ist für den graphischen 3D-Teil des Clients eine Auseinandersetzung mit dem Umgang und den Einsatzmöglichkeiten von Java3D notwendig. Praktische Ziele Zu den praktischen Zielen des Projektes gehören die Realisierung eines Serverprogramms, das Verbindungen verschiedener Clients entgegennehmen kann und ihnen den Austausch von Nachrichten und den Aufbau von Spielen untereinander ermöglicht, und das Generieren eines Clientprogramms, das zu einem beliebigen Server Verbindung aufnehmen kann, die Erstellung einer Spielpartie zwischen zwei Spielern ermöglicht, für die Anzeige des Dame-Spieles verantwortlich ist und dem Benutzer die Eingabe von Spielzügen ermöglicht. Das Spielfeld und die jeweiligen Spielzüge soll hierbei, wie bereits oben beschrieben, durch die Verwendung von Java3D visuell dargestellt werden. Thomas Nadler 2 Definition der Dameregeln Da es das Spiel Dame in den verschiedensten Variationen und Regelarten gibt soll an dieser Stelle kurz dargelegt werden, welche Regeln wir für unser Spiel, welches wir umsetzen wollen, definieren: Allgemeine Regeln: 1. Alle Steine (inkl. Dame) kann sich nur diagonal auf den Feldern bewegen. 2. Nach der Bewegung eines Steins (inkl. Dame) ist der Zug beendet. 3. Es wird auf den schwarzen Feldern gespielt. 4. Der Spieler mit der Farbe Schwarz fängt immer an. 5. Das Spielfeld ist 8 * 8 Felder groß. 6. Die Steine werden beim Spielaufbau auf den Feldern der Reihen 1-3 bzw. 6-8 positioniert. 7. Das Schlagen eines Steins ist sowohl nach „vorne“ als auch nach „hinten“ möglich. Regeln für die Bewegung eines Steines: 1. Die Bewegung eines Steins erfolgt immer nur 1 Feld weiter. 2. Die Bewegung eines Steins erfolgt immer nur nach „vorne“. 3. Das Schlagen eines Steins erfolgt, indem sich ein Stein 2 Felder bewegt, somit also eines überspringt, auf dem sich ein gegnerischer Stein befinden muss. 4. Nachdem ein Stein einen anderen, gegnerischen Stein geschlagen hat kann er weitere gegnerische Steine schlagen, wenn möglich. 5. Ein Stein, der die von seiner Startposition aus entferntere Horizontale Spielfeldkante erreicht (durch Schlagen oder Bewegen) verwandelt sich in eine Dame. Diese darf, sofern der Stein durch Schlagen auf dieses Feld gekommen ist, sofort weiterschlagen. Regeln für die Bewegung einer Dame: 1. Die Dame darf sich so weit bewegen, sie sie will (andere Steine überspringen ist ein Schlagen). 2. Handelt es sich um die erste Aktion eines Zuges darf die Dame so weit schlagen, wie sie will. Sie muss jedoch hinter dem zu schlagenden, gegnerischen Stein landen. 3. Wenn es der Dame möglich ist, darf sie weitere Steine schlagen. Jedoch erfolgt dieses Schlagen nur zwei Felder weit, nach den Regeln der normalen Steine. Marc von der Brüggen 3 Programmevolution Zur Lösung der komplexen Problemstellung der Realisierung eines 3D-Damespiels haben wir uns dazu entschlossen, die Bottom-up Strategie zu verwenden. Dabei teilten wir die Anforderungen in größere Klassengebiete ein, welche dann nacheinander entwickelt werden sollten. Zuerst entschlossen wir uns zu einer Kommunikation über Strings um uns beim Informationsaustausch die Serialisierung bzw. Deserialisierung anderer Datenstrukturen zu ersparen. Danach überlegten wir uns die groben Strukturen der Programmkomponenten, die Server und Client benötigen würden um die an sie gestellten Anforderungen zu erfüllen. Dabei sollte der Server dauerhaft als Verbindungsstelle zwischen den verschiedenen Clients fungieren. Die Entwicklung begann mit dem Server und den essentiellen Klassen zur Ein- und Ausgabe über die Konsole und zur Kommunikation über Sockets. Dasselbe erfolgte auf Seite des Clients. Nachdem die ersten Textnachrichten erfolgreich ausgetauscht waren und die fehlerfreie Verbindung über TCP gewährleistet war folgte die Erstellung der Befehlsklassen. Aufgrund der Menge an Befehlen, die der Server empfangen und bearbeiten musste, haben wir uns aus Übersichtlichkeitsgründen entschlossen, den jeweiligen Code eines Befehls in eine eigene Klasse auszulagern. Für die Umsetzung des jeweiligen Strings in die zugehörige Klasse wurde eine eigene Komponente geplant, die hierfür die Technik der Reflektion verwendet. Parallel begann die Entwicklung an der Persistenz des Servers zum Speichern von Benutzerdaten und der graphischen Benutzeroberfläche für den Client, was quasi dessen zweite Iteration darstellt. Danach erfolgte im Client die Definition der Dameregeln und die Entwicklung der Logik zum Verarbeiten und Überprüfen von Spielzügen („virtuelles Spielfeld“ mit Ausgabe in der Konsole). Abschließend entwickelten wir die 3D-Graphik und verknüpften deren Animationen mit dem „virtuellen Spielfeld“. Marc von der Brüggen 4 Verwendete Arbeitsumgebungen Zur Entwicklung des Programms wurden folgende zwei Arbeitsumgebungen eingesetzt: Arbeitsumgebung 1: Betriebssystem: Windows XP Service Pack 2 Prozessor: Intel Core 2 6600 Arbeitsspeicher: 2048 MB Entwicklungsumgebung: Eclipse Version 3.3.0 (2*2,4GHz) Arbeitsumgebung 2: Betriebssystem: Ubuntu Gutsy Gibbon 7.10 Prozessor: Intel Pentium Centrino (1,5GHz) Arbeitsspeicher: 512 MB Entwicklungsumgebung: Eclipse Version 3.2.2 Es wird bei beiden beschriebenen Systemen das Java Development Kit 6 Update 3 verwendet. Darüber hinaus wird noch Java3D in den Versionen 1.5.0 und 1.5.1 zur Entwicklung eingesetzt. Der in Eclipse eingestellte Kompilierungslevel kann entweder auf 5.0 oder 6.0 eingestellt werden. Thomas Nadler 5 Installationsanleitung Um die von uns programmierte Anwendung nutzen zu können, müssen beide Programmteile, d.h. Server und Client, von der CD in beliebige Verzeichnisse der Festplatte kopiert werden. Weitere Anpassungen sowie das Abändern von Konfigurationsdateien sind für die Installation der beiden Komponenten nicht erforderlich. Jedoch muss am dem jeweiligen Zielsystem eine Java-Version (größer als 5.0) zur Verfügung stehen und zusätzlich die Bibliothek Java3D. Eine aktuelle Java-Version kann unter http://www.java.com/en/download/manual.jsp kostenfrei der heruntergeladen Internetadresse werden. Die notwendige Java3D Bibliothek kann man unter der Adresse http://java.sun.com/products/javamedia/3D/download.html erhalten. Serverinstallation Zur Installation des Servers muss das gesamte Anwendungsverzeichnis des Servers, das sich auf der CD im Pfad „Programme\JARs\Server“ befindet, inklusive aller Unterordner und Dateien in ein beliebiges Verzeichnis auf der Festplatte kopiert werden. Anschließend kann der Server in Microsoft Windows einfach mit der Stapelverarbeitungsdatei serverstarter.bat und in Linux mit dem Shell-Skript serverstarter.sh aus diesem Verzeichnis gestartet werden. Für den genauen Umgang mit dem Server sowie für die Verwendung von Startparametern wird an dieser Stelle auf den Abschnitt Bedienungsanleitung verwiesen. Clientinstallation Zur Installation des Servers muss das gesamte Anwendungsverzeichnis des Clients, das sich auf der CD im Pfad „Programme\JARs\Client“ befindet, inklusive aller Unterordner und Dateien in ein beliebiges Verzeichnis auf der Festplatte kopiert werden. Anschließend stehen dem Benutzer 2 verschiedene Clients zur Verfügung: 1. der Konsolenclient, der in Windows durch die Stapelverarbeitungsdatei konsolestarter.bat und in Linux durch das Shell-Skript konsolenstarter.sh gestartet werden kann und eher für das Testen von Abläufen zur Verfügung steht und 2. der Grafikclient, der in Windows durch die Stapelverarbeitungsdatei grafikstarter.bat und in Linux durch das Shell-Skript grafikstarter.sh gestartet werden kann und für das Anmelden und Spielen eines normalen Benutzers gedacht ist. Für den genauen Umgang mit den beiden Clientversionen wird an dieser Stelle auf den Abschnitt Bedienungsanleitung verwiesen. Thomas Nadler 6 Detaildokumentation In dem Abschnitt Detaildokumentation wird auf die einzelnen Programmteile sowohl des Servers als auch des Clients näher eingegangen. Die Aufgaben und die jeweilige Arbeitsweise der Programmteile und das jeweilige Zusammenspiel sollen näher erklärt werden. Ferner soll auf etwaige Probleme bei der Erstellung hingewiesen werden. Für die genaue Übersicht aller Algorithmen, Methoden und Klassen, und deren spezifischen Beschreibungen gibt es im Anhang die zugehörigen JavaDocs. Diese sollen jedoch kein Gegenstand dieses Abschnittes werden. Server Im Aufbau des Servers lassen sich folgende größeren Programmteile identifizieren: - Eingabe - Ausgabe - Verarbeitungslogik - SocketVerwalter - VerbindungsVerwalter - Persistenz - BefehlsErzeuger - SpielerVerwalter Auf diese aufgeführten Komponenten wird nun im Einzelnen näher eingegangen. Eingabe Die Eingabe des Servers ist relativ simpel gestaltet. Sie besteht aus der abstrakten Klasse Eingabe, die das generelle Aussehen einer Eingabe vorgibt, und von der sich alle verwendbaren, konkreten Eingabeklassen ableiten müssen. Die in dieser Version des Servers verwendete Eingabe ist eine einfache Konsoleneingabe mit dem Klassennamen TastaturEingabe, die das einfache Einlesen von Benutzereingaben von der Tastatur erlaubt. Um die Arbeit des sonstigen Programms nicht zu behindern, muss das Einlesen der Eingaben unabhängig von restlichen Aufgaben erledigt werden, weshalb sich diese Eingabeklassen von der Klasse Thread ableiten. Die Klasse Thread erlaubt die Ausführung eines Codeabschnittes innerhalb eines eigenen, quasi parallelen Ausführungsfadens. Alternativ zur Ableitung von der Klasse Thread hätte auch die Schnittstelle Runnable eingebunden werden können. Da jedoch nur ein einziges Eingabeobjekt Thomas Nadler 7 während des Programmablaufs instantiiert werden muss, stehen beide Möglichkeiten gleichberechtigt zur Verfügung. Bei der Erstellung der Eingabe bekommt die jeweilige EingabeKlasse die zu verwendende Ausgabe und die zuständige Verarbeitungslogik zugewiesen. Das Ausgabeobjekt wird zum Darstellen von Informations- und Fehlermeldungen verwendet, während an das Verarbeitungslogik-Objekt, das für die jeweilige Interpretation und Verarbeitung dieser Nachrichten verantwortlich ist, alle von der Tastatur eingelesenen Eingaben weitergeleitet werden. Ausgabe Die Ausgabe des Servers ist ebenfalls relativ simpel gestaltet. Alle Ausgaben, die innerhalb des Servers verwendet werden können, müssen die Schnittstelle S_Ausgabe einbinden. Diese Schnittstelle definiert, dass jedes Ausgabeobjekt zumindest eine Methode ausgeben (String meldung) besitzen muss, an die die darzustellenden Nachrichten übergeben werden können. Die Ausgabeklasse KonsolenAusgabe des Servers bindet diese Schnittstelle ein und gibt die über die oben genannte Ausgabeschnittstelle übergebenen Nachrichten direkt auf der Konsole aus. Diese Teilkomponente benötigt für ihre Arbeit kein Wissen über die sonstigen Programmteile. Da diese Klasse von vielen anderen Komponenten genutzt wird, die teilweise (quasi) parallel zueinander ausgeführt werden, muss darauf geachtet werden, dass die jeweiligen Ausgaben geordnet nacheinander stattfinden, was durch die Verwendung des Schlüsselwortes synchronzied vor der Ausgabemethode geschieht. Dieses Schlüsselwort bewirkt in Java, dass die gesamte Methode bzw. der jeweilige Abschnitt als kritischer Bereich bewertet wird, auf den jeweils nur ein Prozess oder Ausführungsfaden zugreifen darf. Die Sicherung des kritischen Bereiches geschieht in Java nach dem Monitorprinzip, das bereits in den Lehrveranstaltungen Parallelverarbeitung und Betriebsysteme vorgestellt worden ist. Verarbeitungslogik Die Verarbeitungslogik stellt das eigentliche Kernstück des Servers dar. Es wird hierbei zwischen zwei verschiedenen Verarbeitungsbereichen unterschieden: Erstens der Verarbeitung von empfangenen Nachrichten der jeweiligen Eingabe oder eines Clients und zweitens der Verwaltung aller verbundenen Clients. Für die Verarbeitung von Nachrichten muss die Verarbeitungslogik die Schnittstelle S_BefehlsVerarbeiter einbinden. Durch diese ist vorgegeben, dass die zugehörige Logik eine Methode einbinden muss, der man die zu interpretierende Nachrichtenzeichenkette mitgeben kann. Ferner kann dieser Methode noch ein zugehöriges Objekt übergeben werden, das die Schnittstelle Thomas Nadler 8 S_VerbinungsVerwalter implementiert hat. Dieses übergebene VerbindungsVerwalter-Objekt kann von der Verarbeitungslogik dazu verwendet werden, um Nachrichten an den Client, für den dieses Verbindungsobjekt steht, zurückzusenden. Für die Verwaltung der angemeldeten Clients muss die jeweilige Verarbeitungslogik die Schnittstelle S_LogikVerwaltung einbinden. Über diese Schnittstelle wird vom jeweiligen SocketVerwalter-Objekt, das für das Entgegennehmen neuer Clientverbindungen zuständig ist, das Zustandekommen neuer Verbindungen gemeldet. Da das Verarbeiten der Nachrichten und der angebundenen Clients prinzipiell an ganz unterschiedlichen Stellen im Programm geschehen kann, werden hierfür zwei getrennte Schnittstellen definiert. Aus Gründen der Einfachheit und Übersichtlichkeit werden in dieser Serverversion jedoch beide Aufgaben zentral von der Klasse NetzwerkLogik bearbeitet, die hierfür die beiden genannten Schnittstellen einbindet. Diese Klasse arbeitet wiederum zur Erfüllung der jeweiligen Verwaltungsaufgaben mit weiteren spezialisierten Komponenten zusammen. So nimmt die Klasse Netzwerklogik neue ClientVerwaltungsobjekte nach deren Erstellung zwar entgegen, delegiert diese jedoch an das ihr bekannte SpielerVerwalter-Objekt weiter, welches Listen über alle verbundenen Clients und ihren jeweiligen Status führt. Auch die Verarbeitung von empfangenen Nachrichten erlegt die Klasse NetzwerkLogik nicht allein, sondern greift dafür auf ein ihr bekanntes BefehlsErzeuger-Objekt zurück. Im Generellen wird erwartet, dass eine empfangene Nachricht aus einem Befehlswort und optional aus weiteren Parametern, die das genaue Verwalten des Befehls während der Abarbeitung weiter beeinflussen können, besteht. Die zu einem Befehl gehörigen Anweisungen sind in den jeweiligen Befehlsklassen im Paket befehl hinterlegt. Die logische Zuordnung, welche konkrete Befehlsklasse bei welchem Befehlswort instantiiert werden muss, wird durch den Befehlserzeuger ausgelagert bearbeitet, der den übergebenen Befehl interpretiert und die jeweils richtige Befehlsklasse an die NetzwerkLogik zurückgibt. Die NetzwerkLogik ist anschließend nur noch für die Übergabe eventueller Parameter und für die Ausführung des Befehls verantwortlich und benötigt keine näheren Kenntnisse über die genaue Beschaffenheit des Befehls. Ähnlich wie bei der Methode ausgeben (…) der Klasse KonsolenAusgabe verhält es sich hier nun auch mit der Methode verarbeiteBefehl (S_VerbindungsVerwalter, String Befehl). Sie kann von unterschiedlichen Stellen des Programms, die in verschiedenen Ausführungsfäden ablaufen, aufgerufen werden und muss somit, um z.B. die Überlagerung eines Befehls durch einen anderen zu vermeiden, als kritischer Bereich deklariert. Der Zugriff auf sie muss demzufolge synchronisiert werden. Thomas Nadler 9 SocketVerwalter Die Komponente SocketVerwalter des Servers ist für die Erstellung eines ServerSockets und für die Entgegennahme neuer Verbindungen von Clients mit diesem verantwortlich. Er bindet sich an den ihm übergebenen Port oder gegebenenfalls an einen festgelegten Standardport, erstellt bei dem Zustandekommen einer neuen Verbindung ein zugehöriges VerbindungsVerwalter-Objekt, das ab diesem Zeitpunkt für die Kommunikation mit der entsprechenden Gegenstelle verantwortlich ist, und übergibt dieses wiederum an das entsprechende S_VerwaltungsLogikObjekt, welches bei der Erstellung des SocketsVerwalters übergeben wird und sich um die Verwaltung aller verbundener Clients kümmert. Probleme bei der Umsetzung: Bei der Erstellung bzw. Verwendung dieser Klasse ergibt sich ein Problem beim Binden des ServerSockets an den jeweiligen Port, welches sich nur auf Windows-Betriebssystemen auswirkt und sich in dem nun folgenden Phänomen äußert: Das Binden des ServerSockets an den gewünschten, übergebenen Port bleibt in circa 90% aller Fälle stehen. Hierbei wird der Versuch, sich an den Port zu binden, noch unternommen, kommt aber nicht innerhalb des üblichen Zeitraums zu einem Erfolg oder Abbruch. Das Programm wird an dieser Stelle nicht beendet und gibt auch keine sonstigen Fehlermeldungen aus, sondern bleibt einfach in der Anweisungszeile, in der der ServerSocket erstellt werden soll, stehen. Dieses Phänomen ist bis jetzt nur in einer Windows XP-Umgebung mit einer aktuellen Java-Version (Version 6 Update 3) aufgetreten. Es tritt nicht bei einer Ausführung des Programms im DebugModus auf und ist nicht abhängig vom jeweils zu verwendenden Port oder der Tatsache, dass dieser belegt oder frei ist. Auch eine Umstellung der zu verwendenden ProjektKompilierungsebene von 5.0 auf 6.0 hat keinerlei Unterschied ergeben. In seltenen Fällen (ca. 5%) lief das Programm nach einem sehr langen Zeitraum (1-5 Minuten) selbständig weiter. Das Programm wurde auf eine mögliche Verklemmung seiner Komponenten untersucht, es konnte jedoch nichts Derartiges festgestellt werden. Auch die Einstellungen der verwendeten Firewall (Kerio Personal Firewall) wurden untersucht und können für dieses Problem außer Acht gelassen werden. Das Problem kann umgangen werden, indem frühzeitig ein ServerSocket an einen beliebigen Port gebunden und dieser anschließend verworfen wird. Es liegt die Vermutung nahe, dass das Problem mit der späten Anforderung der jeweiligen Systemressourcen zusammen hängt. Diese Vermutung konnte jedoch nicht weiter be- oder widerlegt werden. Thomas Nadler 10 VerbindungsVerwalter Eine VerbindungsVerwalter-Komponente ist jeweils für die Verwaltung genau einer ServerClient-Verbindung verantwortlich. Das für die Verbindung notwendige Socket-Objekt bekommt sie bei ihrer Erstellung vom SocketVerwalter des Servers zugewiesen. Sie initialisiert alle notwendigen Ein- und Ausgabeströme, die für das Empfangen und Versenden von Nachrichten vom bzw. zum jeweiligen Client notwendig sind. Alle empfangenen Nachrichten werden an den zuständigen Befehlsverarbeiter zur weiteren Interpretation und Verarbeitung übergeben. Auf ein VerbindungsVerwalter-Objekt kann von außen über die Schnittstelle S_VerbindungsVerwalter zugegriffen werden, welche das Absenden von Nachrichten an den Client und die Abfrage der IP-Adresse desselben erlaubt. Da es sich beim Auslesen des Socket-Eingabestroms um einen kontinuierlich stattfindenden Prozess handelt, muss, damit der Ablauf des restlichen Programms nicht behindert wird, dies in einem eigenen Ausführungsfaden, der (quasi) parallel ausgeführt wird, stattfinden. Um diese Funktionalität zu gewährleisten, leitet sich die Klasse VerbindungsVerwalter von der Klasse Thread ab und erstellt automatisch beim Durchlauf ihres Konstruktors einen neuen Ausführungsfaden, der sich mit dem Auslesen von Nachrichten aus dem Socket beschäftigt. Sollte die Verbindung zum jeweiligen Client geschlossen bzw. unterbrochen werden, so benachrichtigt das zuständige VerbindungsVerwalter-Objekt die zugehörige Logik durch die Nachricht BEENDECLIENT, welche bewirkt, dass das zugehörige Verbindungsobjekt aus allen Client-Verwaltungslisten des SpielerVerwalters entfernt wird. Wo es notwendig ist, werden auch die anderen Clients über die jeweilige Abmeldung informiert. Thomas Nadler 11 Persistenz Diese Komponente ermöglicht es dem Server Benutzer anzulegen und auf die entsprechenden Angaben später wieder zuzugreifen. Der Zugriff von außen auf diese Komponente und die jeweils gespeicherten Informationen geschieht über die Klasse BenutzerVerwaltung. Diese ermöglicht es anderen Programmkomponenten, die auf sie zugreifen können, Benutzer anzulegen, zu verändern und zu löschen. Darüber hinaus ermöglicht sie das Auflisten aller Benutzer, das Überprüfen der Eindeutigkeit eines Benutzernamens und das Authentisieren eines bereits angelegten Benutzers durch Benutzername und Passwort. Prinzipiell ist die Speicherung der Benutzerinformationen auf ganz verschiedenen Arten, wie z.B. in einfachen Text- oder XML-Dateien, denkbar. Die Klasse BenutzerVerwaltung unterscheidet nicht in welcher Art von Dateityp gespeichert werden soll, sie erwartet lediglich ein Objekt, das sie über die Methoden der Schnittstelle S_DatenVerwalter ansteuern kann und das ihr bei ihrer Erstellung übergeben wird. Dieses Datenverwaltungsobjekt ermöglicht es ihr, ohne die genauen Abläufe kennen zu müssen, das Auslesen bzw. die Manipulation des jeweiligen Datenbestandes. In dieser Serverversion haben wir uns für die Speicherung des Benutzer-Datenbestandes in Form von XML-Dateien entschieden. Zuständig hierfür ist die Klasse XMLVerwaltung, auf welche von Seiten der BenutzerVerwaltung über die Schnittstelle S_DatenVerwalter zugegriffen werden kann. Aufbau der XML-Datei Der Aufbau der XML-Datei, die zur Speicherung des Benutzernamens und des jeweiligen Passworts verwendet wird, soll möglichst einfach gehalten werden und wird von uns anhand folgender, vorab überlegter Regeln näher spezifiziert: - Es existiert ein Wurzelelement Benutzerliste, über das auf alle anderen Elemente zugegriffen werden kann. - Das Element BenutzerListe enthält nur Elemente vom Typ Benutzer. - Jedes Element Benutzer enthält einen eindeutigen Wert, der sich aus dem Buchstaben O und einer laufenden Nummer für das Objekt zusammensetzt, und die beiden Attribute name und passwort. Diese Regeln können für XML-Dateien in sogenannten DTD-Dateien (Document Type Definition) hinterlegt werden. Diese Regeldateien können beim Erstellen und beim Auslesen von XML-Dateien einbezogen werden, um die Korrektheit der jeweiligen Dokumentenstruktur zu Thomas Nadler 12 überprüfen. Die entsprechende DTD-Datei wird im Unterpfad DTD des Servers erwartet, was wiederum bei der Erstellung der JAR-Dateien zu Komplikationen führen kann, worauf aber erst an späterer Stelle der Dokumentation eingegangen werden soll. BefehlsErzeuger Die Komponente Befehlserzeuger ermittelt zu einem jeweiligen Befehlswort die zugehörige Klasse, die die jeweils notwendigen Anweisungszeilen zur Abarbeitung des Befehls enthält, und instantiiert dieses anschließend. Das erzeugte Befehlsobjekt wird anschließend an die jeweils aufrufende Stelle, hier die NetzwerkLogik, zur weiteren Bearbeitung zurückgegeben und von dort aus aufgerufen. Der Befehlserzeuger benutzt die Technik der Reflektion, um die jeweils empfangene Zeichenkette in die zugehörige Klasse umzusetzen. Diese erlaubt es noch während der Laufzeit des Programms zu ermitteln, welche Klasse benötigt wird, und diese anschließend zu erstellen. Um die Anzahl der Klassen, die auf diese Weise durch den BefehlsErzeuger erstellt werden können, zu limitieren und somit ein unbeabsichtigtes Verhalten des Servers und eventuell auch bewussten Missbrauch zu verhindern, sind folgende drei Einschränkungen beschlossen worden: 1. Alle Klassen, die auf diese Weise erstellt werden sollen, müssen in dem Packet befehle liegen. 2. Es werden nur Instanzen von Klassen erstellt, die sich direkt oder indirekt von der abstrakten Klasse Befehl ableiten. 3. Es können nur Instanzen von Klassen gebildet werden, für die es eine gültige Zuordnung Befehlswort zu Klassenname gibt. Diese Zuordnungen werden in einer eigenen Liste des BefehlsErzeugers verwaltet. Die Vorgehensweise des BefehlsErzeugers ist wiederum relativ simpel. Er ermittelt den zugehörigen Klassennamen, indem er überprüft, ob das Befehlswort in seiner Liste aller Befehlswörter enthalten ist. Wird er dort fündig, so wird anschließend überprüft, ob sich diese Klasse im Packet befehle befindet und ob sie von der Klasse Befehl abgeleitet worden ist. Treffen alle Bedingen zu, wird der erste Konstruktor der Klasse ermittelt und in Abhängigkeit der jeweils erwarteten Parameterarten werden außerdem die jeweils benötigten Objekte übergeben. Diese Vorgehensweise weist die beiden Schwachstellen auf, dass erstens in der aktuellen Version des BefehlsErzeugers automatisch immer der erste Konstruktor verwendet wird und die sonstigen ignoriert werden. Der zweite Nachteil ist, dass für jede Konstruktorsignatur einer Befehlsklasse eine zusätzliche Prüfung und Übergabe in den BefehlsErzeuger eingebaut werden muss. Die von uns gewählte Vorgehensweise funktioniert in Thomas Nadler 13 diesem einfachen Beispiel allerdings relativ gut, da die Anzahl der Befehlsarten, die unterschiedliche Konstruktorsignaturen besitzen und denen somit verschiedene Objekte übergeben werden, sehr gering ist. Falls das übergebene Befehlswort nicht in der eigenen Liste vorhanden sein sollte, so wird ein Objekt UnbekannterBefehl erzeugt, das entsprechende Meldungen ausgibt und gegebenenfalls den verantwortlichen Client benachrichtigt. Sollte der Instantiierungsvorgang aus einem anderen Grund fehlgeschlagen sein, so wird null zurückgegeben. Die Rückgabe des jeweils erzeugten Befehls über den Typ der Vaterklasse Befehl und die Ansteuerung über die einheitliche Ausführungsmethode bearbeitung(…) vereinfacht den Umgang mit den Befehlsobjekten enorm und führt dazu, dass die Klasse NetzwerkLogik, die für die Ausführung des jeweiligen Befehls verantwortlich ist, keine weiteren spezifischen Kenntnisse über die genauen Details desselben benötigt. SpielerVerwalter Die Komponente SpielerVerwalter des Servers ermöglicht die Verwaltung aller verbundenen Clients, der angemeldeten Benutzer, der laufenden Spiele, sowie die Verwaltung der gesendeten Spielherausforderungen. Sie arbeitet zur Verwaltung der registrierten Benutzer eng mit der Klasse BenutzerVerwaltung zusammen, welche für die Speicherung und das Auslesen der Benutzerangaben in bzw. aus Dateien verantwortlich ist. Die Klasse SpielerVerwalter verwaltet die Angaben über die verbundenen Clients und Benutzer in vier verschiedenen Listen. Eine der Listen enthält alle Verbindungsobjekte der mit dem Server verbundenen Clients, die jedoch noch keinen angemeldeten Benutzer haben, und sie wird unter anderem verwendet, wenn alle verbundenen Clients getrennt werden sollen. Eine weitere Liste enthält alle Namen angemeldeter Benutzer und ihre jeweils zugehörigen VerbindungsVerwalter-Objekte. Sie wird zum Austausch von Nachrichten an alle Spieler und zum Versenden von Herausforderungen benutzt. Die dritte Liste enthält alle aktuell gesendeten Herausforderungen und ermöglicht es den jeweiligen Herausforderer und den zugehörigen Herausgeforderten einander zuzuordnen. Sie wird zum Aufbau einer Spielpartie benötigt. Die letzte Liste enthält alle spielenden Benutzer. Hierbei werden in jedem Eintrag die beiden zusammen spielenden Benutzer einander eindeutig zugeordnet, was eine einfache und direkte Übertragung der ausgeführten Spielzüge ermöglicht. Die Klasse SpielerVerwalter steht allen Befehlsklassen, die sich von der Klasse SpielBefehl ableiten, zur Verfügung und wird von diesen zur Abfrage der jeweils benötigten Informationen verwendet. Thomas Nadler 14 Client Im Aufbau des Clients lassen sich folgende größeren Programmteile identifizieren: - Tastatureingabe - Konsolenausgabe - Grafische Benutzeroberfläche - 3D Grafik - Spielregeln - Spielelogik - BefehlsErzeuger - VerbindungsVerwalter - Verarbeitungslogik Auf diese aufgeführten Komponenten wird nun im Einzelnen näher eingegangen. Tastatureingabe Die Komponente Tastatureingabe des Clients ist vom Prinzip der Klasse TastaturEingabe des Servers entlehnt und ebenfalls relativ einfach gestaltet. Im Client wurde zur weiteren Vereinfachung des Aufbaus auf die Erstellung einer abstrakten Vaterklasse Eingabe verzichtet, von der sich alle Eingabeobjekte ableiten müssen. Wie die Klasse TastaturEingabe des Servers ist auch sie für das Einlesen der Benutzereingaben von der Tastatur zuständig und benötigt hierfür einen eigenen Ausführungsfaden, um die sonstige Arbeit des Programms nicht zu behindern. Auch an dieser Stelle stehen beide Möglichkeiten, die Ableitung von der Klasse Thread und das Einbinden der Schnittstelle Runnable, zur Verfügung, wobei wir uns diesmal um die Programmierarbeit abwechslungsreicher und vielfältiger zu gestalten, für die SchnittstellenVariante entschieden haben. Analog zur entsprechenden Serverkomponente bekommt die Klasse TastaturEingabe bei ihrer Erstellung die zu verwendende Ausgabe und die zuständige Verarbeitungslogik zugewiesen. Das Ausgabeobjekt wird wieder zum Darstellen von Informations- und Fehlermeldungen verwendet und das Verarbeitungslogik-Objekt ist wieder für die Interpretation und Bearbeitung der Eingaben zuständig. Diese Komponente wird nur in der Konsolen-Version des Clients verwendet. Konsolenausgabe Die Komponente KonsolenAusgabe des Client wurde vom Prinzip der Komponente Ausgabe des Servers entlehnt. Sie implementiert, um einen einheitlichen Zugriff von außen zu gewährleisten, Thomas Nadler 15 ebenfalls eine Schnittstelle S_Ausgabe und ist für die Ausgabe von Meldungen auf der Konsole verantwortlich. Darüber hinaus sind der Klasse KonsolenAusgabe jedoch weitere Aufgaben zugewiesen worden: Zum einen werden empfangene Nachrichten, falls notwendig, vor der jeweiligen Ausgabe aufbereitet und zum anderen werden die Meldungen interpretiert, um notwendige Spielabläufe anzustoßen, wie z.B. die Erstellung einer neuen Spielpartie oder das Prüfen eines Spielzuges. Hierfür arbeitetet sie, wie ihr graphisches Gegenstück, mit den Komponenten SpielsteinVerwalter und RegelVerwalter zusammen. Der Erstgenannte ist für die Verwaltung des gesamten Spielfelds und das Durchführen von Spielzügen und der Zweitgenannte für das Prüfen der Spielzüge verantwortlich. Die hier vorgenommene Kopplung der für das Spielgeschehen notwendigen Ereignisse, wie beispielsweise die Erstellung eines Spiels oder das Prüfen eines Zugs, an die Ausgabeklasse ist programmiertechnisch gesehen nicht sauber und soll in der nächsten Version durch eine entsprechende Zwischenebene ergänzt werden, die diese das Spiel betreffende Aufgaben übernimmt und anschließend die Meldung an die Ausgabe weitergibt. Diese Komponente wird nur in der Konsolen-Version des Clients verwendet. BefehlsErzeuger Die Komponente BefehlsErzeuger ist nach dem Vorbild der gleichnamigen Serverkomponente erstellt worden und erfüllt dieselben Aufgaben wie diese. Lediglich die Liste der im Client zur Verfügung stehenden Befehle und die jeweils bei der Erstellung notwendigen Parameter sind den neuen Bedürfnissen angepasst worden. VerbindungsVerwalter Die Komponente VerbindungsVerwalter ist nach dem Vorbild der gleichnamigen Serverkomponente erstellt worden. Sie ist in ihrer Funktionalität jedoch leicht abgewandelt worden, so dass ihre Konstruktoren einen einfachen Verbindungsaufbau zu einem ServerSocket ermöglichen. Die Hauptaufgaben der Komponente, nämlich die Verwaltung eines SocketObjektes und das Senden und Empfangen von Nachrichten, sind gleich geblieben. Es sind lediglich zusätzliche Methoden erstellt worden, die das Aufbauen und Trennen von Verbindungen ermöglichen und den Client in die Lage versetzen, nacheinander zu verschiedenen Servern Kontakt aufzunehmen, ohne dass hierfür ein Neustart des Programms notwendig ist. Thomas Nadler 16 Verarbeitungslogik Die Komponente Verarbeitungslogik ist nach dem Vorbild der gleichnamigen Serverkomponente erstellt worden und ist ebenfalls durch das Einbinden der Schnittstelle S_BefehlsVerarbeiter für die Verarbeitung von empfangenen Nachrichten zuständig. Es sind allerdings einige Änderungen an den sonstigen Funktionalitäten durchgeführt worden: So fällt im Client der Teil, der für die Verwaltung von verbundenen Spielern bzw. Clients zuständig ist, weg und stattdessen wird eine Möglichkeit zum Aufbau einer Verbindung und anschließender Kommunikation mit dem Server benötigt, welches durch eine leicht abgewandelte Version des VerbindungsVerwalters ermöglicht wird. Thomas Nadler 17 Grafische Benutzeroberfläche Das Herzstück der graphischen Benutzeroberfläche ist die Klasse GrafikLogik. Über diese können sich alle anderen Fenster gegenseitig aufrufen oder auf die Klasse Dialog zugreifen um z. B. eine Fehlermeldung auszugeben, die der Benutzer bestätigen soll. Als erste Handlung nach ihrem Aufruf erzeugt die Klasse GrafikLogik ein JWindow über die Klasse Ladebildschirm. Dabei hat der Ladebildschirm die Aufgabe, dafür sicher zu stellen, dass die Klasse NetzwerkLogik, auf die im späteren Programmablauf zugegriffen werden muss, ordnungsgemäß an die Klasse GrafikLogik übergeben wurde. Er wird so lange angezeigt, bis die NetzwerkLogik innerhalb der Klasse GrafikLogik zur Verfügung steht. Aus diesem Grund erfolgt die Erstellung der Instanz der Klasse Ladebildschirm und die Initialisierung des Ladebildschirm über eine run()-Methode in einem zweiten thread. Nach Anzeige des Ladebildschirms wird von der Klasse Ladebildschirm über die Klasse GrafikLogik die Klasse Verbindung angesteuert um das Verbindungsfenster anzeigen zu lassen. Die Klasse Verbindung kann – je nach Bedarf – die Klasse Anmeldung oder die Klasse Registrierung über die Klasse GrafikLogik aufrufen um das Anmeldungs- bzw. Registrierungsfentser zu öffnen. Die Klassen Anmeldung und Registrierung können sich über die GrafikLogik gegenseitig ansprechen um das jeweilge Fenster zu erstellen. Die Klasse Lobby ist in der Lage über die Klasse GrafikLogik die Klassen Verbindung – zum Erstellen des Verbindungsfenster beim Abmelden eines Benutzers – und die Klasse Spiel zum erstellen der grafischen Benutzeroberfläche der Spielumgebung zugreifen. Die Klasse AdressSpeicher ist eine Hilfsklasse der Klasse Verbindung, welche eine Datei („adr.vdb“) anlegt, in welcher die zuletzt eingegebe IP-Adresse und Portnummer gespeichert wird. Bei erneutem Aufruf wird diese Datei ausgelesen und ihr Inhalt in die entsprechenden Felder des Verbindungsfensters geschrieben. Probleme bei der Umsetzung: Übergabe der Klasse NetzwerkLogik: Problematisch war, dass sich die Klasse GrafikLogik und die Klasse NetzwerkLogik gegenseitig kennen mussten und an gleicher Stelle initialisert werden. Aus diesem Grund wird die Klasse NetzwerkLogik der Klasse GrafikLogik über die Methode setzeLogik() nachträglich übergeben. Die Klasse Ladebildschirm zeigt den Ladebildschirm ggf. länger an, als die gesetzte Zeit, da sie Marc von der Brüggen 18 über die Methode netzLogikGesetzt() der Klasse GrafikLogik überprüft, ob die NetzwerkLogik ordnungsgemäß verfügbar ist. Wenn nicht wird in regelmäßigen Intervallen geprüft, ob die Logik gesetzt wurde. Wenn nicht bleibt der Ladebildschirm weiterhin angezeigt und ein weiterer Programmablauf wird somit verhindert, da eine fehlende NetzwerkLogik zu diversen Fehlern führen würde. Leere Spielerliste: In seltenen, nicht reproduzierbaren fällen kommt es vor, dass die Spielerliste in der Lobby zwar aktualisiert aber nicht angezeigt wurde. Da die Spielerliste vom Server korrekt gesendet und vom Client ordnungsgemäß empfangen und an die Klasse Lobby weitergegeben wird und die Verarbeitungs-Methoden ebenfalls ordnungsgemäß und wie erwartet durchlaufen werden, gehen wir davon aus, dass es sich um einen Fehler innerhalb der Swing-Komponente handelt. Der Versuch des nachträglichen Aufrufs von spielerliste.repaint () hatte leider keinen Erfolg. Daher wird nun am Ende der Methode erneut geprüft, ob die Spielerliste leer ist um diese ggf, neu anzufordern. Bestätigung des JDialog durch Tastatureingabe: Leider ist es uns nicht gelungen, den JDialog mit Betätigung der Eingabe-Taste zu beenden. Weder das Ausprobieren verschiedener ActionListener noch der Versuch den Focus zu ändern führte zu einer Veränderung. 3D Grafik Bei der Klasse ZeichenflachenVerwalter handelt es sich um die Zentrale Klasse der 3D Grafik, denn Sie erzeugt die Zeichenfläche (Canvas 3D), das Universum und die erste Wurzel und ist somit Ausgangspunkt des Szenengraphen. Außerdem steuert sie die anderen Klassen an um das Spielfeld und die Spielsteine zu erzeugen. Sie holt sich über die Klasse Spiel SpielsteinVerwalter das Spielfeld und die Spielsteine vom virtuellen Spielfeld und gibt diese dann jeweils an die Klassen ZeichnerSpielfeld und ZeichnerSpielsteine weiter und initiiert damit den Aufbau des Spielfelds und die Positionierung der Steine darauf. Über die Klasse ZeichneText erstellt die Klasse ZeichenflachenVerwalter die Beschriftung des Spielfeldes mit den Buchstaben A bis H (horizontal) und den Zahlen 1 bis 8 (vertikal). Außerdem erfolgt durch die Klasse ZeichenflaechenVerwalter die Darstellung der 3D Animationen beim Bewegen der Steine. Marc von der Brüggen 19 Probleme bei der Umsetzung: Hohe Systemanforderungen: Wir mussten feststellen, dass Anwendungen in Java 3D sehr rechenlastig sind. Beim Testen der Komponenten (einem Server und zwei Clients) auf einem PC mit einem Prozessor des Typs Intel Pentium Centrino mit 1,5 GHz und 512 MB Arbeitsspeicher mussten wir einen Signifikanten Leistungsabfall feststellen, sobald eine Spielpartie bzw. 3D-Graphik gestartet wurde. Die Zeiten bis zum Aufbau der Graphik verzögerten sich und die Animationen begannen zu ruckeln. Da es sich jedoch um eine verteilte Anwendung handelt, bei der nicht damit zu rechnen ist, dass zwei Clients parallel auf einem Rechner laufen, gehen wir davon aus, dass im Endgültigen Betrieb dieses Problem nicht auftreten wird. Bis dato haben wir noch keine Möglichkeit gefunden, das Programm in puncto 3D effizienter zu gestalten. Problem mit älteren ATI-Grafikkarten: Da ich die Anwendung auf meinem Laptop unter Ubuntu Linux 7.04 entwickelt habe, bin ich auf folgendes Problem gestoßen: Java 3D WARNING : reported GLX version = 1.2 GLX version 1.3 or higher is required The reported version number may be incorrect. There is a known ATI driver bug in glXQueryVersion that incorrectly reports the GLX version as 1.2 when it really is 1.3, so Java 3D will attempt to run anyway. Diese Fehlermeldung erscheint beim Ausführen von Java 3D auf älteren ATI-Grafikkarten. Ggf. kann diese Fehlermeldung (wie in meinem Fall) ignoriert werden, wenn das Programm trotzdem fehlerfrei ausgeführt wird. Sofern das Programm nicht ausgeführt werden sollte liegt dies daran, dass die Grafikkarte bzw. deren Treiber kein GLX 1.3 unterstützt. Dies lässt sich nur mit dem Austausch der Grafikkarte durch ein neueres Modell beheben. Quelle: http://wiki.ubuntuusers.de/Baustelle/Java3D Lebende Szenen erlauben keine Transformationsgruppen: Kurzzeitig bestand das Problem, dass Animationen nicht ausgeführt wurden. Dies lag daran, dass Java3D Veränderungen innerhalb von „lebenden“ Stenen nicht ohne weiteres erlaubt. Wie sich herausstellte dürfen ausschließlich BranchGruops hinzufügen oder entfernen kann. Daher mussten Knoten, die hinzugefügt oder entfernt werden mussten (Beim Schlagen eines Steins Marc von der Brüggen 20 oder beim Erweitern zu einer Dame) und TransformationGroups - im Fall von Animationen - in BrachGroups eingebettet werden. Spielregeln Die Klasse RegelVerwalter wird von außen über das Interface S_Regelverwalter angesteuert und dient zur Überprüfung von eigenen und gegnerischen Spielzügen. Über RegelVerwalter wird mit Hilfe der Klasse Regelarten definiert nach welchen Spielregeln ein Spiel ablaufen soll. Unser Programm verfügt im Moment über zwei Regelarten, welche in den Klassen KeineRegeln und DameRegeln festgeschrieben sind. Die Klasse RegelVerwalter ist für die Prüfung der geltenden Regeln verantwortlich und ruft die entsprechende Methode in den Regelklassen auf. Im Falle der Klasse KeineRegeln ist jeder Zug erlaubt, da keine Regeln definiert sind. In der Klasse DameRegeln sind jedoch die Allgemeinen Regeln, welche für alle Steine auf dem Spielfeld gelten (z, B. dass die Bewegung nur diagonal möglich ist), die Regeln, welche für Damen gelten (z. B. dass die erste Bewegung über mehrere Felder erfolgen kann) und die Regeln welche für normale Steine gelten (z. B. dass Steine sich nur nach vorne bewegen dürfen) enthalten. Werden dort geprüft und das Ergebnis an den RegelVerwalter zurückgegeben. Für eine genaue Definition unserer Dameregeln siehe bitte den Abschnitt Definition der Dameregeln in dieser Dokumentation. Spielelogik Die Klasse SpielsteinVerwalter verwaltet mit Zugriff auf die Klassen Spielfeld und Stein ein virtuelles Spielfeld, welches für die Spieler nicht zu sehen ist. Auf diesem werden die Züge unabhängig von der Grafik durchgeführt und ein Klon des aktuellen Spielfelds wird jeweils dazu verwendet, die Züge durchzuspielen und damit die geltenden Regeln zu überprüfen. In der Klasse Feldkonverter werden durch den Benutzer eingegebene Koordinaten auf ein dezimales Koordinatensystem für das virtuelle Spielfeld umgerechnet. So wird zum Beispiel aus der Koordinate A1 die Koordinate 00. Marc von der Brüggen 21 Klassendiagramme Aufgrund der Vielzahl der verwendeten Klassen war das Erstellen eines einzigen Gesamtklassendiagramms in übersichtlicher Form leider nicht möglich. Deshalb wurden Teildiagramme bezogen auf die wichtigsten Teile der Programme erstellt. Server - Main Marc von der Brüggen 22 Server - Persistenz Marc von der Brüggen 23 Server – Befehlshierarchie Marc von der Brüggen 24 Client – Main Marc von der Brüggen 25 Client – Benutzeroberfläche Marc von der Brüggen 26 Theorieteil Java 3D Die Wahl zur Verwendung von Java 3D im Gegensatz zu OpenGL viel hauptsächlich aufgrund der problemlosen Integration der Programmiersprache Java mit Java 3D und mit der von uns verwendeten Entwicklungsumgebung Eclipse. Der Szenengraph Bei der Modellierung einer dreidimensionalen Szene in Java 3D ist es üblich eine Szenengraph zu erstellen. Dies hat die einfache Bewandtnis, dass komplexe Objekte oft aus vielen einfachen Objekten bestehen. Wollte man dieses komplexe Objekt nun einer Translation unterziehen um z. B. die Position zu verändern, müsste die Translation auf alle Teilobjekte einzeln angewendet werden. In einem Szenengraph werden Objekte jedoch hierarchisch in Transformationsgruppen zusammengefasst. Folglich wird eine Translation auf die Transformationsgruppe automatisch auf alle, ihr zugehören Objekte angewendet. Neben einem sehr komplexen Szenengraph, in dem sämtliche Betrachtungsparameter definiert werden können bietet Java 3D die Klasse SimpleUniverse, welche für alle Werte erst einmal Standardparameter verwendet, bis diese ggf. innerhalb des SimpleUniverse anderes definiert wurden. Auf diese Weise ist es sehr schnell möglich, ansehnliche Ergebnisse zu erzeugen. Abbildung übernommen aus Java 3D Api Specification von Sun Microsystems, Seite 7 Die Wurzel eines jeden Szenengraphen bildet das Virtuelle Universum. Eines oder mehrere Locale-Objekte bilden anschließend den Ursprung für den jeweiligen Teilbaum einer 3D-Szene. Die einzigen Objekte, die einem Locale-Objekt folgen dürfen sind BranchGroup-Knoten, welche Marc von der Brüggen 27 wiederum die Wurzeln eins Teilbaumgraphen bilden. Diesen folgen weitere Knoten wie z. B. Transformationsgruppenknoten. Diesem wiederum sind Blattknoten (3D-Objekte) ungergeordnet, welche über eine geometrische Form und ein Aussehen der Oberfläche verfügen. Behaviour-Knoten dienen dazu Aktionen und Veränderungen innerhalb einer Szene durchzuführen. Dies erfolgt über ein Aufwecksystem. Zum Beispiel wenn der Behaviour-Knoten ein Ereignis - wie die Interaktion eines Benutzers - sendet. Somit es es unter anderem möglich einer Translationsvariablen einen bestimmten Wert zuzuordnen, was wiederum auswirkungen auf die folgenden 3D Objekte hat. Das ViewPlatform-Objekt definiert die Position, Skalierung und Orientierung des Betrachters innerhalb der 3D-Szene und kann genau wie ein 3D-Objekt durch einen übergeordneten Transformationsgruppenknoten beeinflusst werden. Quellen: Java 3D Api Specification von Sun Microsystems http://java.sun.com/products/java-media/3D/j3dguide.pdf Grundkurs Computergrafik in Java von Frank Klawonn ISBN: 3-528-05919-2 1. Auflage September 2005 erschienen bei Friedr. Vieweg & Sohn Verlag / GWV Fachverlag GmbH Java 3D Ein Überblick der API von Christopher Schnell und Sascha Strasser http://java3d.j3d.org/downloads/Java3D_schnell_tutorial.pdf Java 3D Interaktion von Stefan Bohl http://www2.hs-fulda.de/caelabor/inhalte/java/j3d/j3d_seminar/8/Java3D_interaktionen.pdf Killer game programming in Java von Andrew Davison ISBN: 0-596-00730-2 erschienen bei O'Reilly 2005 Marc von der Brüggen 28 Theorieteil Java und Verwaltung von XML-Dateien Da für die Speicherung von Benutzerdaten im Serverteil des Projektes eine Persistenz notwendig ist und wir uns für die Verwendung von XML-Dateien entschieden haben, müssen die Möglichkeiten wie man in Java mit XML-Dateien umgehen kann, näher untersucht werden. Zur besseren Abwägung welche der zur Verfügung stehenden Alternativen von uns zu wählen ist, folgt eine kurze Gegenüberstellung der jeweiligen Techniken. Gegenüberstellung SAX und DOM Im Generellen unterscheidet man bei dem Zugriff auf XML-Dateien die zwei verschiedenen Zugriffsmodelle SAX und DOM. Bei SAX (Simple API for XML) wird die zu verarbeitende XML-Datei von einem sogenannten SAX-Parser sequenziell durchlaufen. Dieser löst an entsprechenden Stellen des Dateidurchlaufs Ereignisse aus, die das jeweilige Programm abfangen kann. Auf diese Weise kann man den jeweiligen Inhalt der Datei nach und nach durch entsprechende Ereignisbehandlung auswerten. Bei DOM (Document Object Model) dagegen wird die jeweilige XML-Datei auf einmal ausgelesen und daraus ein entsprechender Baum aus einzelnen Elementen bzw. Knoten gebildet, der im Hauptspeicher für weitere Zugriffe abgelegt wird. Beide Zugriffsmodelle haben ihre Vorteile: so kann zum Beispiel nur SAX wirklich große Datenbestände adäquat verwalten, DOM jedoch ermöglicht einen viel einfacheren Umgang mit und übersichtlicheren Zugriff auf die einzelnen, gespeicherten Elemente. Da innerhalb unseres Projektes für das Speichern der Benutzerprofile mit keinen allzu großen Datenbeständen zu rechnen ist, haben wir uns für die Verwendung von DOM entschieden. Für den Zugriff auf XML-Dateien nach der von DOM definierten Vorgehensweise gibt es in Java jedoch wiederum zwei verschiedene Möglichkeiten zur Auswahl: JDOM und DOM4J. Bei beiden Alternativen handelt es sich um Open Source-Projekte, die frei genutzt werden dürfen und die den Anforderungen der von uns zu erstellenden Persistenz gerecht werden. Wir haben uns an dieser Stelle für die Verwendung von DOM4J entschieden, da wir bereits durch das Wahlpflichtfach Java4Seniors Erfahrungen im Umgang mit dieser API sammeln konnten. Quellen: Mitschrift der Veranstaltung Java For Seniors im Sommersemester 06 Überblick DOM in Wikipedia http://de.wikipedia.org/wiki/Document_Object_Model Thomas Nadler 29 Überblick SAX in Wikipedia http://de.wikipedia.org/wiki/Simple_API_for_XML Exportieren der Projekte zu JAR-Dateien Um die von uns erstellen Programme auch außerhalb einer Entwicklungsumgebung nutzen zu können, ist es in Java notwendig diese zu sogenannten JAR-Dateien (Java ARchive) zu exportieren. Diese Dateien können dann in Abhängigkeit des verwendeten Betriebssystems entweder durch einfaches Doppelklicken oder durch den Kommandozeilenbefehl java –jar [programmname].jar gestartet werden. Hierfür ist allerdings notwendig, dass eine entsprechende JRE-Version (Java Runtime Environment) auf dem Computer installiert ist. Das Erzeugen der JAR-Dateien wird mit der verwendeten Entwicklungsumgebung sehr vereinfacht, da man im Menü einfach File/Export auswählen kann und dort den Eintrag JAR-File auswählt. Anschließend kann man, unterstützt durch einen Assistenten, auswählen, welche Dateien exportiert werden sollen, wie die zu erstellenende JAR-Datei heißt und an welchem Ort sie liegen soll. Abschließend muss man nur noch angeben, welche Datei als Einstiegspunkt des Programms bei Aufruf der JAR-Datei verwendet werden soll. Eclipse erstellt nach Abschluss des Dialogs automatisch die gewünschte Datei. Hierbei wird im engeren Sinne nichts anderes gemacht, als dass die zuvor ausgewählten Dateien in eine RAR-Archiv-Datei kopiert werden und diese Zieldatei mit der Endung JAR versehen wird. Aus der Angabe, welche Klasse bei der Ausführung der JAR-Datei zuerst gestartet werden soll, generiert Eclipse bei der Erstellung eine sogenannte Manifest-Datei (MANIFEST.MF), welche im Verzeichnis META-INF abgelegt wird und im Normalfall nur Versionsangaben und den Namen der Startklasse enthält. Die Verwendung von JAR-Dateien bringt jedoch ein paar Probleme mit sich, über die man sich bewusst sein sollte, bevor man versucht das jeweilige Programm zu starten. So kann man auf Bilder innerhalb von JAR-Dateien nur zugreifen, wenn man die Lade-Anforderung der jeweiligen Bilddatei anpasst. Tut man dies nicht, so ist es notwendig die Bilddateien außerhalb der JAR-Datei im jeweils passenden Unterordner abzulegen, da diese sonst nicht gefunden und angezeigt werden können. Sollte das eigene Programm auf externe JAR-Dateien, wie z.B. auf eine DOM4J.jar oder etwas Ähnliches zurückgreifen müssen, so hat man zwei verschiedene Möglichkeiten, dies zu ermöglichen: Entweder erstellt man die eigene JAR-Datei, wie oben bereits beschrieben wurde, öffnet diese dann anschließend und bearbeitet die von Eclipse automatisch generierte ManifestThomas Nadler 30 Datei oder man erstellt gleich eine eigene Manifest-Datei und bindet diese dann im Assistenten für die JAR-Datei-Erstellung ein, was, da die Erstellung einer JAR-Datei eine Aufgabe ist, die man in der Regel mehrfach durchführt, die sinnvollere Variante ist. Die zu erstellende Manifest-Datei sollte in etwa folgendes Aussehen haben: Manifest-Version: 1.0 Class-Path: lib/dom4j-1.6.1.jar Main-Class: main.ServerMain Analog zu der automatisch von Eclipse erzeugten Datei enthält diese Datei Angaben über die Version und die zu startende Main-Klasse. Neu hinzugekommen ist jedoch die Zeile Class-Path, die angibt, an welchen Orten des Dateisystems nach weiteren Dateien gesucht werden soll und gegebenenfalls wie diese heißen. Die oben dargestellte Beispielsdatei erlaubt den Zugriff auf eine externe JAR-Datei mit dem Namen dom4j-1.6.1.jar, welche relativ gesehen zu der jeweils aufgerufenen JAR-Datei im Unterordner lib liegt. Verwendet man dieses Manifest nun durch eine entsprechende Angabe im Eclipse-JAR-Assistenten und kopiert die notwendige externe JAR-Datei in das zuvor angegebene Verzeichnis, so müsste das Programm anschließend fehlerfrei ablaufen. Quelle: JAR File Specification von Sun Microsystems, Inc. http://java.sun.com/j2se/1.4.2/docs/guide/jar/jar.html Thomas Nadler 31 Bedienungsanleitung Server Programmstart Zum Starten des Servers stehen folgende unterschiedliche Möglichkeiten zur Verfügung: Der Server kann unabhängig vom verwendeten Betriebssystem mit dem Kommandozeilenbefehl java –jar server.jar gestartet werden. Darüber hinaus steht für den Programmstart in Microsoft Windows die Stapelverarbeitungsdatei serverstarter.bat und für Linux- bzw. Unixumgebungen das Shell-Skript serverstarter.sh zur Verfügung. Bei allen diesen Startmöglichkeiten besteht die Möglichkeit, als Parameter einen Port zwischen 0 und 65535 zu übergeben, an dem sich der Server versuchen soll, zu binden. Falls der Server ohne Angabe eines expliziten Ports gestartet wird, so wird automatisch der Port 10025 für den Dame-Server verwendet. Verfügbare Befehle Nach dem erfolgreichen Start des Servers können im Serverfenster das An- und Abmelden der Clients und die jeweils gesendeten Befehle verfolgt werden. Darüber hinaus stehen noch eine Reihe administrativer Befehle zum Überwachen des Serverstatus zur Verfügung, auf die im Folgenden näher eingegangen werden soll. Befehlsname Auswirkung BEENDEN Beendet den Server und trennt alle Clients. ECHO Gibt die jeweils übergebene Zeichenkette wieder zurück (Testbefehl für die Befehlsverarbeitung). LISTEVERBUNDEN Gibt eine Liste aller mit dem Server verbundener Clients an. LISTESPIELENDE Gibt eine Liste aller momentan spielenden Benutzer aus. LISTEANGEMELDET Gibt eine Liste aller momentan angemeldeten Benutzer aus. LISTEAUFFORDERUNGEN Gibt eine Liste aller aktuell ausstehenden Aufforderungen aus. TRENNECLIENTS Trennt alle mit dem Server verbundenen Clients. HILFE Gibt die Liste der Befehle und ihre Erklärungen aus. Thomas Nadler 32 Konsolenclient Vorwort Bei dem Konsolenclient handelt es sich um einen iterativ erweiterten Testclient des Servers. Er dient primär zum Testen der Serverprogrammteile und der Netzwerk-Schicht des Clients, kann jedoch in der aktuellen Version auch -gleichberechtigt zum Grafikclient- zum Spielen und Kommunizieren verwendet werden. Auch ein kombinierter Einsatz von Grafik- und Konsolenclients zum Spielen ist problemlos möglich. Programmstart Zum Starten des Konsolenclients stehen folgende unterschiedliche Möglichkeiten zur Verfügung: Diese Client-Version kann unabhängig vom verwendeten Betriebssystem mit dem Kommandozeilenbefehl java –jar konsole.jar gestartet werden. Darüber hinaus steht für den Programmstart in Microsoft Windows die Stapelverarbeitungsdatei konsolestarter.bat und für Linux- bzw. Unixumgebungen das Shell-Skript konsolestarter.sh zur Verfügung. Verfügbare Befehle Nach dem erfolgreichen Start des Konsolenclients kann man durch verschiedene Befehle, die nun nachfolgend erläutert werden sollen, das Verhalten des Programms steuern und sich so beispielweise mit anderen Spielern unterhalten oder an einer Spielpartie teilnehmen. Befehlsname Auswirkung BEENDEN Beendet den Client und schließt ggf. offene Verbindungen. VERBINDE Versucht die Verbindung zu einem Server unter der Adresse localhost und mit dem Port 10025 aufzubauen. VERBINDE [IP-Adresse];;;[Port] Versucht die Verbindung zu einem Server unter der angegebenen Adresse und mit dem jeweiligen Port aufzubauen. TRENNE Trennt eine bestehende Server-Verbindung und ermöglicht das erneute Verbinden zu anderen Servern. Thomas Nadler 33 REGISTIEREN [Benutzername];;;[Passwort] Versucht ein neues Benutzerkonto unter Verwendung der angegebenen Informationen zu erstellen. Hierbei dürfen Benutzernamen nicht doppelt vergeben werden. ANMELDEN [Benutzername];;;[Passwort] Versucht einen Benutzer mit dem angegebenen Benutzernamen und Passwort anzumelden. Hierfür muss eine Verbindung zu einem Server bestehen. ABMELDEN Meldet einen angemeldeten Benutzer ab, ohne die bestehende Verbindung mit dem Server zu unterbrechen. Es muss hierfür eine Verbindung zu einem Server bestehen und ein Benutzer angemeldet sein. GIBSPIELER [Benutzername];;;[Passwort] Fordert eine Liste aller am Server angemeldeten Spieler an und gibt diese aus. MITSPIELEN [Spielpartner] Sendet eine Spielaufforderung an den angegebenen Spieler. Hierfür muss der Benutzer mit dem Server verbunden und angemeldet sein und der Name des Spielpartners muss dem Server bekannt sein. AUCHMITSPIELEN [OK/NEIN] Bestätigt eine zuvor empfangene Spielaufforderung oder lehnt diese ab. AUFFORDERUNGABBRECHEN Wird zum Zurückziehen einer zuvor gesendeten Spielaufforderung benutzt. LOBBYCHAT [Meldung] Wird zum Austausch von Nachrichten zwischen allen Clients verwendet, die sich nicht in einer Spielpartie befinden. SPIELECHAT [Meldung] Wird zum Austausch von Nachrichten zwischen zwei miteinander spielenden Benutzern verwendet. ZUG [von] [nach] Erlaubt das Ziehen eines Steins. Beispiel: ZUG A1 B2 Thomas Nadler 34 AUFGEBEN Bricht ein laufendes Spiel ab. Kann nur von Spielern in einer laufenden Partie gesendet werden. Beispiel eines Spielaufbaus Zum besseren Verständnis, wie genau ein Spiel mit dem Konsolenclient aufgebaut werden kann und welche Einzelschritte hierfür notwendig sind, wird dies nun einmal beispielhaft durchgespielt: Als erstes muss ein Spieler einen Konsolenclient starten. Die verschiedenen Möglichkeiten hierfür können der Bedienungsanleitung, Kapitel Konsolenclient im Abschnitt Programmstart entnommen werden. Nach einem erfolgreichen Programmstart wird man im erscheinenden Eingabe-AufforderungsFenster kurz begrüßt und kann nun alle zur Verfügung stehenden Befehle frei nutzen. Sollte man sich über einen Befehl nicht mehr genau im Klaren sein, so kann man die verfügbaren Befehle jederzeit mit HILFE oder ? anzeigen lassen. Der erste notwendige Schritt, um eine Spielpartie zu starten zu können, ist die Verbindung zu einem Server zu erstellen. Dies kann nun durch Eingabe des Befehls VERBINDE geschehen, insofern der gewünschte Server sich auf demselben Rechner befindet und auf dem Standardport 10025 läuft. Sollte sich ein Server beispielsweise auf einem Rechner mit einer IP-Adresse 192.168.0.33 befinden und dort den Port 2345 abhören, so kann man die Verbindung zu diesem Server mit dem Befehl VERBINDE 192.168.0.33;;;2345 aufbauen. Bei einer erfolgreich aufgebauten Verbindung wird man anschließend vom Server durch eine Willkommensnachricht begrüßt. Nun ist der Benutzer zwar auf dem Server angemeldet, kann dort allerdings noch nicht viel machen, da ausschließlich angemeldete Benutzer an Spielen teilnehmen können. Nun muss sich also der jeweilige Spieler anmelden oder ggf., falls er noch kein Benutzerkonto besitzen sollte, erst noch registrieren. Möchte sich der Spieler beispielsweise nun ein neues Benutzerkonto anlegen, so kann dies mit dem Befehl REGISTRIEREN Tom;;;Geheim geschehen. Anschließend kann er sich nun mit ANMELDEN Tom;;;Geheim auf dem Server authentifizieren. Nach der erfolgreichen Anmeldung erhält er alle Nachrichten der anderen angemeldeten Spieler und kann nun auch an Spielpartien teilnehmen. Um an einem Spiel teilzunehmen, gibt es zwei Möglichkeiten: entweder nimmt man die Herausforderung eines anderen Spieles an oder man fordert einen anderen Spieler heraus. An Thomas Nadler 35 dieser Stelle gehen wir einfach mal davon aus, dass ein anderer Spieler Marc nun den Server betritt, was Tom automatisch angezeigt wird. Zur Kommunikation zwischen den beiden angemeldeten Spielern kann der Befehl LOBBYCHAT Textnachricht eingesetzt werden. Diese Nachrichten werden dann allen Spielern, die nicht mit einer Spielpartie beschäftigt sind, angezeigt. Diesen Spieler Marc kann unser Spieler Tom nun durch den Befehl MITSPIELEN Marc herausfordern. Dieser bekommt nun eine entsprechende Aufforderungsmeldung angezeigt und kann diese entweder mit dem Befehl AUCHMITSPIELEN OK annehmen oder mit AUCHMITSPIELEN NEIN ablehnen. Hat Marc das Spiel angenommen, so werden auf beiden Seiten die Spielfelder angezeigt und der herausgeforderte Spieler, in diesem Falle also Marc, darf den ersten Zug eingeben. Während einer laufenden Partie hat ein Spieler die Möglichkeit, sich mit seinem Partner über den Befehl SPIELCHAT Textnachricht zu unterhalten. Den jeweils gewünschten Zug kann er beispielweise durch einen Befehl wie ZUG C3 D4 angeben. Sollte einer der beiden Spieler die Partie abrechen wollen, so kann dies durch AUFGEBEN geschehen. Beide Spieler können anschließend neue Spielpartien erstellen oder sich vom Server abmelden. Thomas Nadler 36 Grafischer Client 1. Verbindung mit dem Server Nach dem Start des Client öffnet sich der Verbindungsbildschirm. Auf diesem wird der Benutzer dazu aufgefordert eine IP-Adresse und einen Port einzugeben, um sich mit einem bestehenden Server zu verbinden. Durch klicken auf den Kopf „Verbinden“ oder Betätigen der Eingabe-Taste. Nach erfolgreichem Verbinden werden IP-Adresse und Port in der Datei „adr.vdb“ gespeichert. Bei erneutem Programmaufruf werden die zuletzt eingegebene IP-Adresse und der zuletzt eingegeben Port automatisch in die entsprechenden Felder eingetragen. Eine Statusanzeige zeigt an, ob bereits eine Verbindung zu einem Server besteht. 2. Beim Server registrieren Nach erfolgreicher Verbindung mit dem Server öffnet sich der Anmeldebildschirm, da davon auszugehen ist, dass sich Benutzer öfter Anmelden als registrieren wollen. Mit dem Klick auf das Knopf „Zur Registrierung“ öffnet sich diese und man wird aufgefordert einen Benutzernamen und ein Passwort für sein Benutzerkonto einzugeben. Nach Erfolgreicher Registrierung durch Drücken des Knopf „Registrieren“ oder Betätigen der Eingabe-Taste öffnet sich erneut der Anmeldebildschirm. Außerdem kann man manuell zurück zum Anmeldebildschirm wechseln, indem man auf den Knopf „Zur Anmeldung“ 3. Anmelden beim Server Beim Anmeldebildschirm wird der Benutzer dazu aufgefordert, seinen registrierten Benutzernamen und sein Passwort für sein Benutzerkonto einzugeben. Dies geschieht durch Drücken des Knopf „Anmelden“ oder Betätigen der Eingabe-Taste. Nach erfolgreicher Eingabe öffnet sich die Lobby. 4. Zu einem Spiel herausfordern oder herausgefordert werden Die Lobby besteht aus einem Textausgabe- und einem Texteingabefeld, über das die momentan angemeldeten Benutzer, welche sich nicht in einem Spiel befinden, miteinander kommunizieren können. Das Abschicken des Textes erfolgt über das Drücken des „Senden“-Knopf oder das Betätigen der Eingabe-Taste. Marc von der Brüggen 37 In der Spielerliste werden alle Spieler aufgelistet, welche momentan am Server angemeldet sind, sich aber nicht in einer Spielpartie befinden. Sollte man von einem anderen Spieler zu einem Spiel herausgefordert werden öffnet sich ein Dialog, welcher mit „Ja“ bestätigt werden muss, falls man am Spiel teilnehmen möchte und mit „Nein“, falls man nicht spielen möchte. Im ersten Fall beginnt danach – sofern der Herausforderer zwischenzeitlich keinen anderen Spieler herausgefordert hat – das Spiel. Selbst kann man Spieler herausfordern, indem man einen Namen aus der Spielerliste auswählt (nicht den eigenen, dies führt zu einer Fehlermeldung) und auf „Herausfordern“ klickt. Über das Textausgabefenster wird man darüber informiert, ob der Spieler die Herausforderung abgelehnt hat. Sollte er sie angenommen haben, startet ein Spiel. Sollte er die Herausforderung aus irgendeinem Grund nicht beantworten kann eine neue Herausforderung, gegen einen anderen Spieler, auf die selbe Art und Weise ausgesprochen werden, wie im ersten Fall. Daraufhin verfällt die zuerst ausgesprochene Herausforderung. Durch das Klicken auf den „Abmelden“-Knopf meldet sich der Benutzer beim Server ab und es öffnet sich der Verbindungsbildschirm 5. Programm / Verbindung beenden Das Programm kann jederzeit durch Schließen eines Fensters (Dialoge können nicht geschlossen werden) beendet werden. Nach dem Abmelden bleibt der Client jedoch mit dem Server verbunden. Erst durch klicken des Knopf „Trennen“ im Verbindungsfenster wird auch die Verbindung zum Server unterbrochen. 6. Eine Partie Dame spielen Der Spieler, welcher herausgefordert wurde beginnt immer das Spiel. Mit dem Texteingabe- und Textausgabefeld ist es den beiden Kontrahenten möglich, miteinander zu kommunizieren. In das Textausgabefeld eingegebe Nachrichten können durch Drücken des „Senden“-Knopf oder Betätigen der Eingabe-Taste abgesendet werden. Außerdem erfolgt die Eingabe eines Zuges über das Texteingabefeld. Ein Zug beginnt dabei immer mit „ZUG“, gefolgt von den Start und einer oder mehrerer Zielkoordinaten. Groß- und Kleinschreibung wird nur bei dem Initialwort „ZUG“ jedoch nicht bei den Zielkoordinaten beachtet. Zum Beispiel ZUG A3 B4 um einen Stein von dem Feld A3 Marc von der Brüggen 38 auf das Feld B4 zu bewegen. Oder beispielsweise ZUG h2 F4 d2 um einen Stein vom Feld H2 zum Feld F4 springen zu lassen und anschließend zum Feld D2 zu springen und somit in einem Zug 2 gegnerische Steine zu schlagen. Zusätzlich werden im Textausgabefenster die gegnerischen Züge aufgelistet. Das Klicken auf „Aufgeben“ führt zum Öffnen eines Dialog, der die Handlung noch einem hinterfragt. Beim Widerruf durch Drücken von „Nein“ geht das Spiel wie gewohnt weiter. Beim Bestätigen mit „Ja“ öffnet sich erneut ein Dialog (beim Spieler als auch bei seinem Gegner), der zurück zur Lobby führt. Es öffnet sich ebenfalls ein Dialog, der zurück zur Lobby führt, sollte das Programm das Ende einer Partie durch den Sieg eines der Spieler feststellen. Durch das Drehen des Mausrades ist es möglich näher an das Spielbrett zu zoomen oder weiter weg zu zoomen. Das 3D Schachbrett kann durch Drücken + Halten der rechten Maustaste und Bewegung der Maus bewegt und durch Drücken der + Halten der linken Maustaste und Bewegen der Maus frei gedreht werden. Marc von der Brüggen 39 Erweiterungsmöglichkeiten Folgende Erweiterungsmöglichkeiten für das Programm wären in der Version 2.0 denkbar und würden zur Verbesserung beitragen Steuerung der Züge mit der Maus Eine große Verbesserung der Benutzerfreundlichkeit wäre die Möglichkeit, die Steine auf dem 3D-Spielfeld mit der Maus zu steuern. Als Problem erweist sich, dass unsere Regel keine Schlagpflicht von gegnerischen Steinen enthalten, so wie in anderen Regelvariationen. Es müsste also eindeutig gekennzeichnet sein, wann ein Zug wirklich beendet ist und wann ein Benutzer noch weitere Aktionen durchführen möchte. Beispielsweise wäre Denkbar, dass ein Zug immer mit einem Doppelklick auf das letzte Feld, auf dem der zu Bewegende Stein also am Ende des Zuges stehen soll, abgeschlossen wird. Bei der Realisierung wäre es zweckmäßig einen eigen eigene Klasse MouseBehaviour zu schreiben. Über diese könnte dann das Picking realisiert werden. Unter Picking versteht man die Aktion eines Benutzers, Objekte auf dem Bildschirm mit der Maus anzuklicken um so mit diesen zu interagieren. Quelle: Java 3D Interaktion von Stefan Bohl http://www2.hs-fulda.de/caelabor/inhalte/java/j3d/j3d_seminar/8/Java3D_interaktionen.pdf Veränderung der Benutzerkonten über die graphische Benutzeroberfläche Die Benutzerfreundlichkeit würde weiterhin steigen, wenn Anwender die Möglichkeit hätten ihr Profil selbständig über die graphische Benutzeroberfläche zu verändern. In diesem Zusammenhang könnte man das Profil um weitere, freiwillige Angaben (z. B. Geburtsdatum) erweitern und für andere Benutzer einsehbar gestalten. Außerdem wäre es äußerst Praktisch, wenn Anwender ihr Profil selbständig löschen könnten. Marc von der Brüggen 40 Veränderung der Spielfeldgrafiken Ein lustiges Extra wäre, wenn es möglich wäre die 3D-Grafiken je nach Geschmack zu verändern. Zum Beispiel könnten über eine Menüleiste die Texturen des Spielbretts, des Hintergrund oder die Farben der Steine verändert werden. Marc von der Brüggen 41 Erweiterungen in den administrativen Möglichkeiten des Server Die Konsole des Servers hat in der aktuellen Version ausschließlich administrative Funktion und kann z.B. zum Anzeigen der angemeldeten Benutzer, zum Trennen der verbundenen Clients oder zum Beenden des Programms verwendet werden. Diese administrativen Möglichkeiten können noch weiter ausgebaut werden, um z.B. das gezielte Trennen eines einzelnen Clients oder das Beenden von Spielpartien zu ermöglichen. Bestenliste Die auf dem Server gespeicherten Benutzerprofile können dazu verwendet werden, um eine Statistik über die Anzahl der gewonnenen und verlorenen Spiele eines Spielers zu führen. Dies würde ohne großen, zusätzlichen Umbau- oder Kommunikationsaufwand die Erstellung einer zentralen Bestenliste ermöglichen. Absicherung des Servers gegen Angriffe Bevor man einen Dame-Server im Internet als öffentliche Plattform zum Spielen laufen lassen kann, sollte man die Sicherheit beim Registrieren und Anmelden von Benutzern noch verstärken. In der aktuellen Version des Servers bestehen zumindest drei klare Schwachstellen: Erstens ist ein Client in der Lage beliebig viele Benutzerkonten hintereinander anzulegen und kann somit den verfügbaren Festplattenplatz des Servers bewusst verschwenden und ihn auf diese Weise zum Absturz bringen bzw. das System unnötig verlangsamen. Zweitens ist jeder Verbindungspartner in der Lage, unbegrenzt viele Verbindungen zum Server aufzubauen. Über jede Verbindung dürfen unbegrenzt viele Anfragen gestellt werden, was einen DoS-Angriff ermöglicht. Dies könnte beispielsweise durch eine Begrenzung der zulässigen Verbindungen pro IP-Adresse und mithilfe einer Höchstgrenze erlaubter Anfragen innerhalb eines Zeitabschnittes pro Verbindung behoben werden. Drittens sollten die eingegebenen Passwörter nicht im Klartext über die einfache TCPVerbindung gesendet, sondern möglichst zuvor verschlüsselt werden. Thomas Nadler 42 Anhang A – JavaDoc Für eine ausführliche Detaildokumentation des gesamten Programms inklusive einer vollständigen Beschreibung sämtlicher Daten und Algorithmen befindet sich auf dem abgegebenen Datenträger im Verzeichnis Arbeit/JavaDoc jeweils eine komplette JavaDocDokumentation des Servers und des Clients. Thomas Nadler 1 Anhang B – Code Programm Server Packet main Klasse SocketVerwalter package main; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * Erzeugt einen ServerSocket-Objekt und bindet dieses an einen an sie * Uebergebenen Port. Nimmt eingehende Verbindungswuensche entgegen und * uebergibt sie an einen neuen VerbindungsVerwalter. <br> * <br> * Datei: SocketVerwalter.java <br> * Datum: 12.10.2007 <br> * <br> * Historie: <br> * 04.01.2007: TN - Konstruktor mit Standardport ergaenzt. <br> * 15.10.2007: TN - Zugriff auf clientverwaltende Logik ueber * Schnittstelle S_LogikVerwaltung umgestellt <br> * 12.10.2007: TN - Klasse erstellt <br> * * @author Thomas Nadler * @version Version: 1.2 */ public class SocketVerwalter { /** Standardport des Servers private final int STDPORT = 10025; */ /** * Konstruktor der Klasse SocketVerwalter. An ihm wird der zu * verwendende Serverport, die zugehoerige VerwaltungsLogik und ein * Ausgabeobjekt uebergeben. Der Konstruktor hat die Aufgabe ein * ServerSocket-Objekt an den angegebenen Port zu binden und * Verbindungswuensche von verschiedenen Clients entgegenzunehmen. * Falls kein oder ein ungueltiger Port uebergeben wurde wird der oben * definierte Standardport verwendet. Wenn ein neuer Verbindungswunsch * festgestellt wurde, wird ein neues Verbindungsverwalter-Objekt * erzeugt, das die Verbindung uebergeben bekommt und das fuer dessen * Verwaltung und die Entgegennahme und das Senden von Nachrichten * verantwortlich ist. Das neue Client-Verwaltungsobjekt wird zur * zentralen Verwaltung aller Clients an die Verwaltungslogik * uebergeben. * * Thomas Nadler Marc von der Brüggen 1 * @param port * Port, den der Server zum Entgegennahmen von * Clientverbindungen verwenden soll. * @param logik * Referenz auf die zugehoerige Verwaltungslogik. * @param ausgabe * Es muss ein Objekt uebergeben werden, dass die * Schnittstelle einer Ausgabe (S_Ausgabe) * implementiert hat. */ public SocketVerwalter (int port, S_LogikVerwaltung logik, S_Ausgabe ausgabe) { /* Socket zum Entgegennehmen von neuen Client-Verbindungen ServerSocket serverSocket; /* Socket zum Einspeichern einer neuen Client-Verbindung Socket clientSocket; /* Verwaltungsobjekt zustaendig fuer eine Client-Verbindung */ VerbindungsVerwalter clientVerwalter; /* Prufe auf gueltigen Port if ((port < 0) || (port > 65535)) { port = STDPORT; } */ */ */ try { ausgabe.ausgeben ("Versuche Server an den Port " + port + " zu binden."); serverSocket = new ServerSocket (port); ausgabe.ausgeben ("Binden erfolgreich durchgefuehrt."); ausgabe.ausgeben ("Warte auf eingehende Verbindungen."); while (true) { clientSocket = serverSocket.accept (); ausgabe.ausgeben ("Neue Verbindung eingegangen: " + clientSocket.getInetAddress ().getHostAddress ()); clientVerwalter = new VerbindungsVerwalter (clientSocket, logik .gibVerarbeitsLogik (), ausgabe); logik.fuegeClientHinzu (clientVerwalter); } } catch (IOException e) { ausgabe.ausgeben ("Fehler beim Erstellen des ServerSocket."); ausgabe.ausgeben (e.toString ()); System.exit (-1); } } /** * verketteter Konstruktor der Klasse SocketVerwalter. An ihm wird die * zugehoerige VerwaltungsLogik und ein Ausgabeobjekt uebergeben. * Dieser Konstruktor leitet die uebergebenen dann an den Konstruktor * mit der Signatur (int, S_LogikVerwaltung, S_Ausgabe) weiter, wobei * der Port auf -1 gesetzt wird, was eine Verwendung des definierten * Standardports bewirkt. * * @param logik * Referenz auf die zugehoerige Verwaltungslogik. * @param ausgabe Thomas Nadler Marc von der Brüggen 2 * Es muss ein Objekt uebergeben werden, dass die * Schnittstelle einer Ausgabe (S_Ausgabe) * implementiert hat. */ public SocketVerwalter (S_LogikVerwaltung logik, S_Ausgabe ausgabe) { this (-1, logik, ausgabe); } } Klasse VerbindungsVerwalter package main; /** * Verwaltet eine Verbindung zwischen dem Server- und Client-Socket. Sie * nimmt alle Meldungen des Clients entgegen und leitet diese an die * Verarbeitungslogik des Servers weiter. Darueberinaus ermoeglicht sie * das Senden von Nachrichten an den jeweiligen Client. Da das Einlesen * von Meldungen des Client kontinuierlich geschehen muss und nicht die * restliche Arbeit des Servers aufhalten darf leitet sich diese Klasse * von der Klasse "Thread" ab und ermoeglich somit die (quasi) paralelle * Ausfuerung der Meldungsabfragen. <br> * <br> * Datei: VerbindungsVerwalter.java <br> * Datum: 12.10.2007 <br> * <br> * Historie: <br> * 16.12.2007: TN - Methode zur Abfrage der Client IP-Adresse erstellt * <br> * 12.10.2007: TN - Klasse erstellt <br> * * @author Thomas Nadler * @version Version: 1.1 */ import import import import import import java.io.BufferedOutputStream; java.io.BufferedReader; java.io.IOException; java.io.InputStreamReader; java.io.PrintWriter; java.net.Socket; public class VerbindungsVerwalter extends Thread implements S_VerbindungsVerwalter { /** Socketobjekt zur Kommunikation mit dem Client private Socket clisock; /** Logik zum Auswerten der empfangenen Nachrichten private S_Befehlsverarbeiter logik; /** Objekt zum Schreiben von Nachrichten an den Client private PrintWriter schreiber; /** Objekt zur Ausgabe von Nachrichten private S_Ausgabe ausgabe; */ */ */ */ /** * Konstruktor der Klasse VerbindungsVerwalter. Er uebernimmt das zu * verwendende Ausgabe-Objekt, den Befehlsverarbeiter, an den alle Thomas Nadler Marc von der Brüggen 3 * empfangenen Nachrichten weitergegeben werden sollen und das * "Socket"-Objekt, dass fuer die Verbindung zum Client genutzt werden * soll. Im Konstruktor wird der Ausgabestrom initialisiert, der zum * Senden von Nachrichten notwendig ist und ein neuer "Thread" zum * Einlesen von Client-Nachrichten gestartet. * * @param clisock * clisock - "Socket"-Objekt zur Verbindung mit dem * Client. * @param logik * Es muss eine verarbeitende Logik uebergeben werden, * die ueber die Schnittstelle S_Befehlsverarbeiter * angesprochen werden kann. * @param ausgabe * Es muss ein Objekt uebergeben werden, dass die * Schnittstelle einer Ausgabe (S_Ausgabe) * implementiert hat. */ public VerbindungsVerwalter (Socket clisock, S_Befehlsverarbeiter logik, S_Ausgabe ausgabe) { this.clisock = clisock; this.logik = logik; this.ausgabe = ausgabe; try { schreiber = new PrintWriter (new BufferedOutputStream (clisock .getOutputStream ())); } catch (IOException e) { ausgabe.ausgeben ("Erzeugen des PrintWriter fehlgeschlagen."); ausgabe.ausgeben (e.getMessage ()); } this.start (); try { sendeBefehl ("ECHO VERBINDE Willkommen"); } catch (IOException e) { ausgabe.ausgeben (e.toString ()); } } /** * Diese Methode ermoeglicht es zeilenweise Meldungen des Client zu * empfangen und sie an die zugehoerige Verarbeitungslogik zu * uebergeben. Die Methode wird automatisch beim Start eines neuen * Threads von System aufgerufen und darf in ihrer Signatur nicht * veraendert werden. Sollte die Verbindung zum Client unterbrochen * werden, so wird die Anweisung "BEENDECLIENT" an die * Verwarbeitungslogik uebergeben, die daraufhin notwendige * Aufraeumarbeiten durchfueren kann. */ public void run () { try Thomas Nadler Marc von der Brüggen 4 { BufferedReader bufr = new BufferedReader (new InputStreamReader ( clisock.getInputStream ())); boolean weiter = true; while (weiter) { String befehl = bufr.readLine (); if (befehl == null) { befehl = "BEENDECLIENT"; weiter = false; } logik.verarbeiteBefehl (this, befehl); } } catch (IOException e) { if (e.getMessage ().equals ("Connection reset")) { logik.verarbeiteBefehl (this, "BEENDECLIENT"); } else { ausgabe.ausgeben ("Fehler beim Empfangen."); ausgabe.ausgeben (e.getMessage ()); // e.printStackTrace(); } } } /** * Diese Methode ermoeglicht es eine Nachricht an den jeweils * verbundenen Client zu senden. Die Signatur der Methode ist durch * die Schnittstelle S_VerbindungsVerwalter vorgegeben. * * @param befehl * Nachricht, die an den Client gesendet werden soll. * @throws IOException * Wenn beim Senden der Nachricht ein Fehler * aufgetreten ist */ public void sendeBefehl (String befehl) throws IOException { if (schreiber != null) { schreiber.println (befehl); schreiber.flush (); } } /** * Diese Methode ermoeglicht es die IP-Adresse des verbundenen Clients * zu ermitteln. Die Signatur der Methode ist durch die Schnittstelle * S_VerbindungsVerwalter vorgegeben. * * @return String - Zeichenkette mit der IP-Adresse des Clients */ public String gibIPAdresse () { Thomas Nadler Marc von der Brüggen 5 if (clisock != null) { return clisock.getInetAddress ().getHostAddress (); } return "kein Socket"; } } Klasse BefehlsErzeuger package main; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import befehle.*; /** * Klasse, die es ermoeglicht, aus einem empfangenen Befehlswort das * zugehoerige Befehlsobjekt zu instanzieren. Hierbei stehen die zur * Verfuegung stehenden Befehle jeweils im Packet befehle. Befehle * koennen nur verwendet werden, wenn fuer sie in der Befehlsliste eine * Zuordnunng der Form Befehlswort zu Klassenname existiert. <br> * <br> * Datei: BefehlsErzeuger.java <br> * Datum: 12.10.2007 <br> * <br> * Historie: <br> * 03.01.2008: TN - Befehl Hilfe eingebaut. <br> * 21.12.2007: TN - Befehle Mogeln und Aufgeben eingebaut. <br> * 24.10.2007: TN - Befehl GibSpieler zum Abfragen der anwesenden * Spieler erstellt <br> * 20.10.2007: TN - Befehl ZeiheStein (nur fuer Spielende) <br> * 19.10.2007: TN - Verwendung der Fehlerklasse vermieden, Ausgabe * direkt <br> * 14.10.2007: TN - Aenderungen fuer die unterstuetzung von Befehlen mit * unterschiedlichen Parametern eingebracht <br> * 12.10.2007: TN - Klasse erstellt <br> * * @author Thomas Nadler * @version Version: 1.7 */ public class BefehlsErzeuger { /** * Liste, die eine Zuordnung Befehlswort zu Befehlsklassenname * ermoeglicht */ private HashMap<String, String> befehlsListe; /** Paketname in dem alle Befehlsklassen abgelegt sind private final String PACKET = "befehle"; /** Objekt zur Verwaltung der Spieler und laufenden Spiele private SpielerVerwalter spiVer; Thomas Nadler Marc von der Brüggen 6 */ */ /** enthaelt Verweis auf die zu verwendende Ausgabe private S_Ausgabe ausgabe; */ /** * Konstruktor der Klasse BefehlsErzeuger. Er wird benutzt um der * Klasse BefehlsErzeuger die zu benutzende Ausgabe und den zu * verwendeneden SpielerVerwalter zu uebergeben und alle notwendigen * Initialisierungen zu treffen. Hierbei wird primaer eine Zuordnung * zwischen gueltigen Befehlsnamen und den jeweiligen Befehlsklassen * angelegt. * * @param spiVer * Es muss der zu verwendende SpielerVerwalter * uebergeben werden. * @param ausgabe * Es muss ein Objekt uebergeben werden, dass die * Schnittstelle einer Ausgabe (S_Ausgabe) * implementiert hat. */ public BefehlsErzeuger (SpielerVerwalter spiVer, S_Ausgabe ausgabe) { this.spiVer = spiVer; this.ausgabe = ausgabe; befehlsListe = new HashMap<String, String> (); befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put befehlsListe.put ("REGISTRIEREN", "Registrierung"); ("ANMELDEN", "Anmeldung"); ("ABMELDEN", "Abmeldung"); ("ECHO", "Echo"); ("BEENDECLIENT", "BeendeClient"); ("LISTEVERBUNDEN", "ListeVerbunden"); ("LISTESPIELENDE", "ListeSpielende"); ("LISTEANGEMELDET", "ListeAngemeldet"); ("LISTEAUFFORDERUNGEN", "ListeAufforderungen"); ("BEENDEN", "Beenden"); ("TRENNECLIENTS", "TrenneClients"); ("MITSPIELEN", "Spielaufforderung"); ("VERLOREN", "SpielBeenden"); ("MELDEMOGELN", "SpielMogeln"); ("AUFGEBEN", "SpielAufgeben"); ("AUFFORDERUNGABBRECHEN", "AufforderungAbbrechen"); ("AUCHMITSPIELEN", "Spielbestaetigung"); ("ZUG", "ZieheStein"); ("GIBSPIELER", "GibSpieler"); ("LOBBYCHAT", "LobbyChat"); ("SPIELECHAT", "SpieleChat"); ("HILFE", "Hilfe"); ("?", "Hilfe"); } /** * Diese Methode hat die Aufgabe aus einer uebergebenen Befehls* zeichenkette das zugehoerige Befehlsobjekt zu ermitteln und dieses * mit allen zugehoerigen Konstruktorparametern zu initialieren. * Anschliessend wird das jeweils erzeugte Befehls- objekt an die * aufrufende Stelle zururckgegeben. * * @param befehl * Zeichenkette die einen Befehl enthaelt. * @return Befehl Befehlsobjekt, das alle notwendigen Abarbeitungs* schritte fuer den jeweiligen Befehl enthaelt. Thomas Nadler Marc von der Brüggen 7 */ @SuppressWarnings("unchecked") public Befehl konvertiereZuBefehl (String befehl) { String befehlswort; /* Befehlswort ohne Parameter /* Verweis auf den Datentyp der zu erzeugenden Befehlsklasse Class<?> befehlsKlasse; /* Verweis auf den Datentyp der Klasse Befehl */ Class<Befehl> befehlstyp; /* * Verweis auf den Datentyp der zu erzeugenden Befehlsklasse in * Abh�ngigkeit der Vaterklasse Befehl */ Class<Befehl> bef; /* Verweis auf den jeweiligen Konstruktor der Befehlsklasse Constructor<Befehl> kon; /* * Anzahl der Parameter die der Konstruktor der Befehlsklasse * benoetigt */ int anzahlParameter; /* Pruefe, ob Parameter in der Befehlszeichenkette vorhanden if (befehl.indexOf (" ") > 0) { befehlswort = befehl.substring (0, befehl.indexOf (" ")); befehl = befehl.substring (befehl.indexOf (" ") + 1); } else { befehlswort = befehl; befehl = ""; } /* Pruefe, ob der Befehl bekannt ist if (befehlsListe.containsKey (befehlswort)) { try { befehlsKlasse = Class.forName (PACKET + "." + befehlsListe.get (befehlswort)); befehlstyp = Befehl.class; */ */ */ */ */ /* * Pruefe, ob die Klasse in befehlsKlasse von der Klasse Befehl * abgeleitet wurde */ if (befehlstyp.isAssignableFrom (befehlsKlasse)) { bef = (Class<Befehl>) befehlsKlasse; kon = (Constructor<Befehl>) bef.getConstructors ()[0]; /* ermittle die Anzahl der notwendigen Uebergabeparameter anzahlParameter = kon.getGenericParameterTypes ().length; if ((anzahlParameter == 1) && (kon.getGenericParameterTypes ()[0] == S_Ausgabe.class)) { return kon.newInstance (ausgabe); } Thomas Nadler Marc von der Brüggen 8 */ else if ((anzahlParameter == 2) && (kon.getGenericParameterTypes ()[0] == S_Ausgabe.class) && (kon.getGenericParameterTypes ()[1] == SpielerVerwalter.class)) { return kon.newInstance (ausgabe, spiVer); } else { ausgabe.ausgeben ("Fehler beim Instanzieren der Klasse. " + "Parameterfehler"); return null; } } else { ausgabe.ausgeben ("Die geladene Klasse ist nicht " + "nach Befehl-Castbar."); return null; } } catch (ClassNotFoundException e) { ausgabe.ausgeben ("Die Klasse konnte nicht gefunden werden."); return null; } catch (InstantiationException e) { ausgabe .ausgeben ("Die Klasse konnte nicht initialisiert werden."); return null; } catch (IllegalAccessException e) { ausgabe.ausgeben ("Fehler beim Initialisieren der Klasse. \n" + e.getMessage ()); return null; } catch (IllegalArgumentException e) { ausgabe.ausgeben ("Fehler beim Initialisieren der Klasse. \n" + e.getMessage ()); return null; } catch (InvocationTargetException e) { ausgabe.ausgeben ("Fehler beim Initialisieren der Klasse. \n" + e.getMessage ()); return null; } } else { return new UnbekannterBefehl (ausgabe, befehlswort); } } } Klasse SpielerVerwalter package main; Thomas Nadler Marc von der Brüggen 9 import import import import import java.io.IOException; java.util.ArrayList; java.util.HashMap; java.util.List; java.util.Map; import persistenz.BenutzerVerwaltung; import persistenz.XMLVerwaltung; /** * Diese Klasse verwaltet die Liste der mit dem Server verbundenen * Clients. Es werden fuer jeden Client drei Status unterschieden: * verbunden, angemeldet und spielend. Die Klasse bietet alle * notwendigen Methoden zur Verwaltung der Clientstatus. <br> * <br> * Datei: SpielerVerwalter.java <br> * Datum: 21.10.2007 <br> * <br> * Historie: <br> * 03.11.2007: TN - Senden mehrer Aufforderungen abgefangen. * 28.10.2007: TN - Aufraeumen der zu verwaltenden Listen ueberarbeitet * 24.10.2007: TN - Verwaltung von Herausforderungen und Spielern * hinzugefuegt <br> * 21.10.2007: TN - Klasse erstellt <br> * * @author Thomas Nadler * @version Version: 1.3 */ public class SpielerVerwalter { /** Benutzerverwaltung, die das Verwalten von Konten ermoeglicht */ private BenutzerVerwaltung benutzerVerw = new BenutzerVerwaltung ( new XMLVerwaltung ()); /** Liste verbundener, aber nicht angemeldeter Clients private List<S_VerbindungsVerwalter> listeVerbundener = new ArrayList<S_VerbindungsVerwalter> (); */ /** Liste aller angemeldeter Spieler und deren Verbindungsobjekte private Map<String, S_VerbindungsVerwalter> listeAngemeldeter = new HashMap<String, S_VerbindungsVerwalter> (); */ /** Liste der im Spiel befindlichen Benutzer */ private Map<S_VerbindungsVerwalter, S_VerbindungsVerwalter> listeSpielende = new HashMap<S_VerbindungsVerwalter, S_VerbindungsVerwalter> (); /** Liste der gesendeten und nicht beantworteten Herausforderungen private Map<S_VerbindungsVerwalter, S_VerbindungsVerwalter> listeSpielaufforderungen = new HashMap<S_VerbindungsVerwalter, S_VerbindungsVerwalter> (); */ /** * Die Methode hat die Aufgabe einen neuen Spieleraccount anzulegen * und bekommt hierfuer sie den gewuenschten Benutzernamen und das * zuhoerige Passwort uebergeben. Da ein Benutzername auf jedem Server * eindeutig sein muss, wird vor der Erstellung des Account geprueft, * ob der Name bereits einmal vergeben worden ist. * * @param name * Name des zu erstellenden Benutzerkontos Thomas Nadler Marc von der Brüggen 10 * @param passwort * Passwort des neuen Benutzerkontos */ public boolean RegistriereClient (String name, String passwort) { if (benutzerVerw.pruefeNamensEindeutigkeit (name)) { return benutzerVerw.erzeugeBenutzer (name, passwort); } else { return false; } } /** * Die Methode hat die Aufgabe einem Benutzer, der ein bestehendes * gueltiges Benutzerkonto besitzt die Anmeldung am Server zu * ermoeglichen. * * @param neuerClient * Verbindungsobjekt des anzumeldenden Benutzers * @param name * Name des anzumeldenden Benutzers * @param passwort * Passwort des Benutzers */ public boolean meldeAn (S_VerbindungsVerwalter neuerClient, String name, String passwort) { if (listeVerbundener.contains (neuerClient)) { if (benutzerVerw.authentisiereBenutzer (name, passwort)) { /* Eintragen in die Liste der Angemeldeten */ listeAngemeldeter.put (name, neuerClient); listeVerbundener.remove (neuerClient); return true; } else { return false; } } else { return false; } } /** * Die Methode hat die Aufgabe einen Client der neu mit dem Server * verbunden ist in die zentrale Liste aller verbundenen Clients * einzuspeichern. Die zentrale Verwaltung aller angemeldeter Clients * ermoeglicht eine einfache Verwaltung der Spieleranmeldungen und der * Spielaufbauten. Darueberhinaus benoetigt man eine Liste aller * verbundener Clients wenn aus administrativen Gruenden die * Verbindung zu allen Clients zuruecksetzen moechte. * * @param neuerClient * Verbindungsobjekt des neu verbundenen Clients Thomas Nadler Marc von der Brüggen 11 */ public void neuenClientHinzufuegen (S_VerbindungsVerwalter neuerClient) { listeVerbundener.add (neuerClient); } /** * Private Methode die dazu verwendet beim Entfernen eines Clients die * Liste der im Spiel befindlichen Spieler aufzuraeumen. Sollte sich * der zu entfernende Client in einem Spiel befunden haben, so werden * die zugehoerigen Verwaltungseintraege geloescht und der * Spielpartner ueber den Abbruch des Spiels benachrichtigt. * * @param client * Verbindungsobjekt des zu entfernenden Clients */ private void bereinigeSpielerListe (S_VerbindungsVerwalter client) { /* Name des zu entfernenden Benutzers */ String spielername; if (listeSpielende.containsKey (client)) { S_VerbindungsVerwalter partner = gibSpielPartner (client); listeSpielende.remove (partner); listeSpielende.remove (client); try { spielername = gibName (client); partner.sendeBefehl ("ECHO SPIEL Der Spieler " + spielername + " hat aufgegeben."); } catch (IOException e) { } } } /** * Private Methode die dazu verwendet beim Entfernen eines Clients die * Liste der gesendeten Herausforderungen aufzuraeumen. Sollte sich * der zu entfernende Client in einem Spiel befunden haben, so werden * die zugehoerigen Verwaltungseintraege geloescht und der * Spielpartner ueber den Abbruch des Spiels benachrichtigt. * * @param client * Verbindungsobjekt des zu entfernenden Clients */ private void bereinigeAufforderungsListe ( S_VerbindungsVerwalter client) { if (listeSpielaufforderungen.containsKey (client)) { S_VerbindungsVerwalter partner = listeSpielaufforderungen .get (client); listeSpielaufforderungen.remove (client); try { partner.sendeBefehl ("ECHO Spielaufforderung abgelehnt."); } catch (IOException e) { Thomas Nadler Marc von der Brüggen 12 } } if (listeSpielaufforderungen.containsValue (client)) { for (S_VerbindungsVerwalter spieler : listeSpielaufforderungen .keySet ()) { if (listeSpielaufforderungen.get (spieler) == client) { listeSpielaufforderungen.remove (spieler); try { spieler .sendeBefehl ("ECHO Spielaufforderung zurueckgezogen."); } catch (IOException e) { } } } } } /** * Die Methode wird aufgerufen, wenn ein Client vom Server getrennt * worden ist und er nun aus den Verwaltungslisten des Servers * entfernt werden kann. Sollte sich der Client in einem laufenden * Spiel befunden oder noch laufende Herausforderungen offen haben, so * werden diese entfernt. * * @param alterClient * Verbindungsobjekt des zu entfernenden Clients * @return boolean - Rueckgabewert, der die erfolgreiche Ausfuerung * der Methode anzeigt. */ public boolean entferneClient (S_VerbindungsVerwalter alterClient) { bereinigeSpielerListe (alterClient); bereinigeAufforderungsListe (alterClient); /* wenn der zu entfernende Client nicht angemeldet war if (listeVerbundener.contains (alterClient)) { listeVerbundener.remove (alterClient); return true; } else { /* wenn der zu entfernende Client angemeldet war if (listeAngemeldeter.containsValue (alterClient)) { for (String angemeldeter : listeAngemeldeter.keySet ()) { if (listeAngemeldeter.get (angemeldeter) == alterClient) { listeAngemeldeter.remove (angemeldeter); return true; } } } Thomas Nadler Marc von der Brüggen 13 */ */ else { /* Client war in keinen Listen verhanden */ System.out.println ("Interne Warnung: Zu entfernender " + "Client war in keiner Liste enthalten."); } } return false; } /** * Die Methode ermoeglicht einem angemeldeten Benutzer sich vom Server * abzumelden. Das zugehoerige Clientverwaltungsobjekt, wird * anschliessend aus der Liste der angemeldeten Client entfernt und * wieder in die Liste der verbundenen Clients eingebunden. Sollte der * Benutzer noch laufende Spiele oder Herausforderungen offen gehabt * haben, so werden diese entfernt. * * @param client * Verbindungsobjekt des abzumeldenden Benutzers * @return boolean - Rueckgabewert, der die erfolgreiche Ausfuerung * der Methode anzeigt. */ public boolean meldeAb (S_VerbindungsVerwalter client) { bereinigeSpielerListe (client); bereinigeAufforderungsListe (client); /* Pruefe ob Benutzer angemeldet ist */ if (listeAngemeldeter.containsValue (client)) { /* ermittle zu loeschenden Eintrag */ for (String angemeldeter : listeAngemeldeter.keySet ()) { if (listeAngemeldeter.get (angemeldeter) == client) { listeAngemeldeter.remove (angemeldeter); listeVerbundener.add (client); return true; } } } return false; } /** * Die Methode wird aufgerufen, wenn eine Spielherausforderung * angenommen worden und somit eine Spielpartie zwischen 2 Spieler * zustandegekommen ist. Sie traegt die beiden Spieler in die Liste * der spielenden Clients ein. Um den Zugriff auf den jeweiligen * Spielpartner zu erleichtern werden pro Spiel 2 Eintraege erzeugt, * die beide Spieler jeweils einander zuordnen. * * @param spieler1 * Verbindungsobjekt des ersten Spielers * @param spieler2 * Verbindungsobjekt des zweiten Spielers * @return boolean - Rueckgabewert, der die erfolgreiche Ausfuerung * der Methode anzeigt. */ public boolean spielEintragen (S_VerbindungsVerwalter spieler1, Thomas Nadler Marc von der Brüggen 14 S_VerbindungsVerwalter spieler2) { if (spieler2 != null && spieler1 != null) { listeSpielende.put (spieler1, spieler2); listeSpielende.put (spieler2, spieler1); return true; } return false; } /** * Die Methode wird aufgerufen, wenn ein Spieler eine laufende * Spielpartie beendet hat. Sie bewirkt, dass die beiden zum Spiel * gehoerigen Eintrag aus der Liste der Spielenden geloescht wird. * * @param spieler * Verbindungsobjekt des Spielers der die Partie * beendet hat. */ public void spielBeenden (S_VerbindungsVerwalter spieler) { S_VerbindungsVerwalter partner = gibSpielPartner (spieler); listeSpielende.remove (partner); listeSpielende.remove (spieler); } /** * Die Methode ermoeglicht es den zu einem bestimmten * Verbindungsobjekt gehoerigen Spielernamen zu ermitteln. Dies ist * jedoch nur moeglich, wenn zu dem jeweiligen Verbindungsobjekt auch * ein angemeldeter Spieler verfuegbar ist. * * @param client * Verbindungsobjekt zu dem der Spielername ermittelt * werden soll. * @return String - Name des Benutzers der ueber das uebergebene * VerbindungsVerwalter-Objekt angemeldet ist. */ public String gibName (S_VerbindungsVerwalter client) { for (String angemeldeter : listeAngemeldeter.keySet ()) { if (listeAngemeldeter.get (angemeldeter) == client) { return angemeldeter; } } return null; } /** * Die Methode ermoeglicht es zu einem im Spiel befindlichen Client * den jeweils zugehoerigen Spielpartner zu ermitteln. Wenn sich der * uebergebene Client in keinem Spiel befindet, so wird null ansonsten * das Verbindungsverwalterobjekt des Spielpartners zurueckgegeben. * * @param spieler * Verbindungsobjekt eines im Spiel befindlichen * Clients. * @return S_VerbindungsVerwalter - Verbindugnsobjekt des Thomas Nadler Marc von der Brüggen 15 * Spielpartners */ public S_VerbindungsVerwalter gibSpielPartner ( S_VerbindungsVerwalter spieler) { return listeSpielende.get (spieler); } /** * Die Methode gibt die Liste aller momentan spielenden Clients * zurueck. Die Liste enthaelt fuer jedes laufende Spiel jeweils zwei * Eintraege, die jeweils dsa Verbindungsobjekt des ersten Spielers * dem des zweiten zuordnet und umgekehrt. * * @return Map<S_VerbindungsVerwalter, S_VerbindungsVerwalter> * Liste aller Spielenden Clients. */ public Map<S_VerbindungsVerwalter, S_VerbindungsVerwalter> gibSpielende () { return listeSpielende; } /** * Die Methode gibt die Liste aller momentan angemeldeten Clients * zurueck. Die Liste enthaelt fuer jeden angemeldeten Client jeweils * seinen Benutzernamen und sein zuhehoeriges * VerbindungsVerwalter-Objekt. * * @return Map<String, S_VerbindungsVerwalter> - Liste aller * angemeldeten Clients. */ public Map<String, S_VerbindungsVerwalter> gibAngemeldete () { return listeAngemeldeter; } /** * Die Methode gibt die Liste aller verbundenen Clients, die keinen * Angemeldeten Benutzer haben, zurueck. Die Liste enthaelt fuer jeden * verbundenen Client sein jeweils zuhehoeriges * VerbindungsVerwalter-Objekt. * * @return List<S_VerbindungsVerwalter> - Liste aller verbundenen * Clients. */ public List<S_VerbindungsVerwalter> gibVerbundene () { return listeVerbundener; } /** * Die Methode gibt die Liste aller laufenden Spielaufforderungen * zurueck. Jeder eintrag der Liste besteht jeweils aus 2 * VerbidnungsVerwalter-Objekten, wobei der aufgeforderte Client an * der ersten Position (also im Schluessel-Feld) und der auffordernde * Client an der zweiten Position (also im Value-Feld) steht. * * @return Map<S_VerbindungsVerwalter, S_VerbindungsVerwalter> * Liste der aktuellen Herausforderungen. */ public Map<S_VerbindungsVerwalter, S_VerbindungsVerwalter> Thomas Nadler Marc von der Brüggen 16 gibAufforderungen () { return listeSpielaufforderungen; } /** * Die Methode erzeugt einen neuen Eintrag der Liste der * Herausforderungen. Hierbei wird der herausgeforderte Spieler an die * erste Position der Liste (also in das Schluesselfeld) und der * herausfordernde Spieler in das zweite Feld (als das Value-Feld) * geschrieben. Da eine Antwort des zweiten Spieler die am * wahrscheinlichsten eintretende Reaktion ist ermoeglich diese * Speicherreihenfolge einen moeglichst schnellen Zugriff auf den * zugehoerigen Herausforderer. Von einer doppelten Speicherung um den * Zugriff zu erleichtern bzw zu beschleunigen - wie zum Beispiel bei * den Spielenden - wurde an dieser Stelle abgesehen, da diese * einfache Zuordnung Rueckschluesse auf den urspruenglichen Absender * der Herausforderung zulasst. * * @param spieler1 * Verbindungsobjekt des herausfordernden Spielers * @param spieler2 * Benutzername des herausgefordeten Spielers * @return boolean - Rueckgabewert, der die erfolgreiche Ausfuerung * der Methode anzeigt. */ public boolean erstelleSpielaufforderung ( S_VerbindungsVerwalter spieler1, String spieler2) { if (listeAngemeldeter.get (spieler2) != null) { listeSpielaufforderungen.put (listeAngemeldeter.get (spieler2), spieler1); return true; } return false; } /** * Die Methode ermoeglicht es eine bestehende Herausforderung zu * loeschen und sie somit aus der Liste der Herausforderungen zu * entfernen. * * @param herausgeforderter * Verbindungsobjekt des herausfordernden Spielers */ public void loescheSpielaufforderung ( S_VerbindungsVerwalter herausgeforderter) { listeSpielaufforderungen.remove (herausgeforderter); } /** * Die Methode ermoeglicht es zu einem herausgefordertem * VerbindungsVerwalter-Objekt den herausfordernden Partner zu * ermitteln. * * @param herausgeforderter * Verbindungsobjekt des herausfordernden Spielers * @return S_VerbindungsVerwalter - Verbindungsobjekt des * Herausforderers. Thomas Nadler Marc von der Brüggen 17 */ public S_VerbindungsVerwalter gibHerausforderer ( S_VerbindungsVerwalter herausgeforderter) { return listeSpielaufforderungen.get (herausgeforderter); } } Programm Client Packet grafik3D Klasse ZeichenflaechenVerwalter package grafik3D; import java.awt.GraphicsConfiguration; import java.util.HashMap; import java.util.Map; import import import import import import import import import import import import import import import import import javax.media.j3d.Alpha; javax.media.j3d.AmbientLight; javax.media.j3d.Background; javax.media.j3d.BoundingSphere; javax.media.j3d.BranchGroup; javax.media.j3d.Canvas3D; javax.media.j3d.ImageComponent2D; javax.media.j3d.Node; javax.media.j3d.PointLight; javax.media.j3d.PositionInterpolator; javax.media.j3d.Transform3D; javax.media.j3d.TransformGroup; javax.vecmath.Color3f; javax.vecmath.Point3d; javax.vecmath.Point3f; javax.vecmath.Vector3d; javax.vecmath.Vector3f; import import import import spieleLogik.FeldKonvertierer; spieleLogik.Spielfeld; spieleLogik.SpielsteinVerwalter; spieleLogik.Stein; import import import import com.sun.j3d.utils.behaviors.vp.OrbitBehavior; com.sun.j3d.utils.image.TextureLoader; com.sun.j3d.utils.universe.SimpleUniverse; com.sun.j3d.utils.universe.ViewingPlatform; /** * Beschreibung: Zentrale Verwalterklasse fuer das Fenster mit den 3D * Objekten und deren Animation.<br> * <br> * Datei: ZeichenflaechenVerwalter.java <br> * Datum: 09.12.2007 <br> * <br> * Historie:<br> * 09.12.2007 Marc vdB: Erstellung der 3D Zeichenflache mit Univerum, Thomas Nadler Marc von der Brüggen 18 * Wurzel, Licht und ersten Testobjekten.<br> * 10.12.2007 Marc vdB: Erstellung des Spielbretts<br> * 11.12.2007 Marc vdB: Erstellung der Spielsteine und * Spielfeldbeschriftung und versetzen der Ansicht<br> * 12.12.2007 Marc vdB: Hinzufuegen der Spielbrettbeschriftung * 21.12.2007 Marc vdB: Erstellung von zeigeZug()<br> * 05.12.2007 Marc vdB: Aenderung des Hintergrundes<br> * * @author Marc von der Brueggen * @version Version: 0.6 */ public class ZeichenflaechenVerwalter { /** Universum private SimpleUniverse uni; */ /** Wurzelknoten fuer Objekte private BranchGroup wurzel; */ /** Zeichenflaeche private Canvas3D canvas3D; */ /** Hintergrundbild der Zeichenflaeche private ImageComponent2D hintergrund; */ /** Hintergrund der Zeichenflaeche private Background b; private private private private private */ SpielsteinVerwalter ssv; ZeichnerSpielsteine ssz; ZeichnerSpielfeld zsf; ZeichnerText zt; Map<Stein, BranchGroup> steinListe; /** * Konstruktor * * @param ssv SpielsteinVerwalter.java */ public ZeichenflaechenVerwalter (SpielsteinVerwalter ssv) { this.ssv = ssv; steinListe = new HashMap<Stein, BranchGroup> (); ssz = new ZeichnerSpielsteine (); zsf = new ZeichnerSpielfeld (); zt = new ZeichnerText (); /* * Definition der Grafikkonfiguration durch Auswahl eines * Universums */ GraphicsConfiguration config = SimpleUniverse .getPreferredConfiguration (); canvas3D = new Canvas3D (config); // Zeichenflaeche /* Zeichenflache zum Universum hinzufuegen uni = new SimpleUniverse (canvas3D); Thomas Nadler Marc von der Brüggen 19 */ wurzel = new BranchGroup (); // Wurzelobjekt BoundingSphere bounds = new BoundingSphere (new Point3d (0.0, 0.0, 0.0), 100.0); /* Lese Bild aus hintergrund = (new TextureLoader ("img/sterne.jpg", null)).getImage (); b = new Background (hintergrund); */ // Setze Bild auf Hintergrund /* Erlaubt Ueberschreiben des Hintergrundbildes b.setCapability (Background.ALLOW_IMAGE_WRITE); */ /* Skalierung des Hintergrundbildes b.setImageScaleMode (Background.SCALE_FIT_ALL); */ b.setApplicationBounds (bounds); wurzel.addChild (b); wurzel.setCapability (BranchGroup.ALLOW_CHILDREN_EXTEND); /* Wurzel zum Universum hinzufuegen uni.addBranchGraph (wurzel); */ ViewingPlatform vPlatform = uni.getViewingPlatform (); TransformGroup ViewTG; /* Positionierung der Kamera vPlatform.setNominalViewingTransform (); */ /* Drehen der Ansicht */ OrbitBehavior orbit = new OrbitBehavior (canvas3D, OrbitBehavior.REVERSE_ROTATE | OrbitBehavior.DISABLE_ZOOM | OrbitBehavior.DISABLE_TRANSLATE); orbit.setSchedulingBounds (new BoundingSphere ()); vPlatform.setViewPlatformBehavior (orbit); ViewTG = vPlatform.getViewPlatformTransform (); setzeAnsicht (ViewTG); } /** * Gibt die Zeichenflache zurueck um in der grafischen * Benutzeroberflache angezeigt zu werden * * @return 3D Zeichenflache * */ public Canvas3D gibZeichenflaeche () { return canvas3D; } /** * Zeichnet das fertige Spielfeld inkl. Beschriftung und Spielsteinen */ public void zeichneSpielfeld () { Thomas Nadler Marc von der Brüggen 20 Spielfeld spielfeld = ssv.gibAktSpielfeld (); BranchGroup feld = zeichneSpielfeld (spielfeld); wurzel.addChild (feld); } /** * Fueght Licht zum Universum hinzu * * @param richtung Die inverse Richtung des Lichtes * * @param farbe Die Farbe des Lichtes */ public void fuegeLichtHinzu (Vector3f richtung, Color3f farbe) { /* Erstellung einer BoundingSphere fuer das Licht */ BoundingSphere bounds = new BoundingSphere (); bounds.setRadius (1000d); /* Positionierung der Lichtquellen */ AmbientLight ALgt = new AmbientLight (new Color3f (1f, 1f, 1f)); PointLight PLgt1 = new PointLight (new Color3f (1f, 1f, 1f), new Point3f (0f, 0f, -5f), new Point3f (0f, 0.25f, 0f)); PointLight PLgt2 = new PointLight (new Color3f (1f, 1f, 1f), new Point3f (0f, 0f, 5f), new Point3f (0f, 0.25f, 0f)); PointLight PLgt3 = new PointLight (new Color3f (1f, 1f, 1f), new Point3f (5f, 0f, 0f), new Point3f (0f, 0.25f, 0f)); PointLight PLgt4 = new PointLight (new Color3f (1f, 1f, 1f), new Point3f (-5f, 0f, 0f), new Point3f (0f, 0.25f, 0f)); PointLight PLgt5 = new PointLight (new Color3f (1f, 1f, 1f), new Point3f (0f, -5f, 0f), new Point3f (0f, 0.25f, 0f)); PointLight PLgt6 = new PointLight (new Color3f (1f, 1f, 1f), new Point3f (0f, 5f, 0f), new Point3f (0f, 0.25f, 0f)); ALgt.setInfluencingBounds (bounds); // Festlegen der BoundingSphere PLgt1.setInfluencingBounds (bounds); PLgt2.setInfluencingBounds (bounds); PLgt3.setInfluencingBounds (bounds); PLgt4.setInfluencingBounds (bounds); PLgt5.setInfluencingBounds (bounds); PLgt6.setInfluencingBounds (bounds); BranchGroup lichtknoten = new BranchGroup (); lichtknoten.addChild lichtknoten.addChild lichtknoten.addChild lichtknoten.addChild lichtknoten.addChild lichtknoten.addChild lichtknoten.addChild (PLgt1); (PLgt2); (ALgt); (PLgt3); (PLgt4); (PLgt5); (PLgt6); // Licht hinzufuegen wurzel.addChild (lichtknoten); } /** * Setzt die Ansicht auf das Spielbrett je nachdem, welche Farbe man * spielt, damit einem der Gegenspieler immer gegenüber sitzt */ public void setzeAnsicht (TransformGroup ViewTG) Thomas Nadler Marc von der Brüggen 21 { Point3d center = new Point3d (0, 0, 0); Point3d eyePos = new Point3d (0, 0, 0); eyePos.z += 6.5; eyePos.x -= 0.0; if (ssv.sindEigeneSteineSchwarz () == true) { eyePos.y -= 12.5; } else { eyePos.y += 12.5; } Vector3d up = new Vector3d (); up.z = 1; Transform3D viewTrans = new Transform3D (); viewTrans.setIdentity (); viewTrans.lookAt (eyePos, center, up); viewTrans.invert (); ViewTG.setTransform (viewTrans); } /** * Zeigt den Zug an * * @param zug Eingehender Zug */ public void zeigeZug (String zug) { FeldKonvertierer feko = new FeldKonvertierer (); /* Schreibt die einzelnen Aktionen eines Zuges in ein Array String[] felder = zug.split (" "); int[][] posfelder = new int[felder.length][2]; */ /* * Ueberprueft die Syntax. Mindestens 2 Paramater fuer Start- und * Zieladresse muessen vorhanden sein */ if (!(felder.length >= 2)) { return; } for (int i = 0; i < felder.length; i++) { posfelder[i] = feko.kovertiereFeld (felder[i]); if (posfelder[i] == null) { return; } } /* Pruefe saemtliche Aktionen eines einzelnen Zuges auf Gueltigkeit*/ for (int i = 0; i < posfelder.length - 1; i++) { Stein stein = ssv.gibAktSpielfeld ().gibStein (posfelder[0][0], posfelder[0][1]); bewegeStein (stein, posfelder[i][0], posfelder[i][1], Thomas Nadler Marc von der Brüggen 22 posfelder[i + 1][0], posfelder[i + 1][1]); try { Thread.sleep (2500); // Warte auf Animation } catch (InterruptedException e) { System.out.println ("Fehler beim Warten auf die Bewegungsanimation"); System.out.println (e); } entferneGeschlagenenStein (posfelder[i], posfelder[i + 1]); pruefeAufDame (posfelder[0], posfelder[i + 1]); } } /** Pruefe ob Stein nach einem Zug zu einer Dame wird private void pruefeAufDame (int[] start, int[] ziel) { Stein st; st = ssv.gibAktSpielfeld ().gibStein (start[0], start[1]); if (ziel[1] == 0) // Pruefe auf weisse Damen { if (st != null && st.istSchwarz () == false && st.gibDame () == false) { zeigeDame (st, ziel); } } else if (ziel[1] == 7) // Pruefe auf schwarze Damen { if (st != null && st.istSchwarz () == true && st.gibDame () == false) { zeigeDame (st, ziel); } } } /** * Wandelt einen normalen Stein in eine Dame um, indem ein zweiter * Stein ueber dem ersten erstellt wird * * @param stein Steinobjekt * * @param ziel Zielkoordinate des Steins * */ private void zeigeDame (Stein stein, int[] ziel) { BranchGroup steinBG; if (stein != null) { steinBG = steinListe.get (stein); Node node = steinBG.getChild (0); if (node instanceof TransformGroup == true) { Thomas Nadler Marc von der Brüggen 23 */ TransformGroup tgBewege2 = (TransformGroup) node; TransformGroup tgBewege1 = (TransformGroup) tgBewege2.getChild (0); TransformGroup steinTG = (TransformGroup) tgBewege1.getChild (0); steinTG.addChild (ssz.zeichneStein (0, 0.31f, stein.istSchwarz (), 0f, 0)); } } } /** * Entfernt einen Stein der geschlagen wurde vom Spielfeld * * @param start Startkoordinate des sich bewegenden Steins * * @param ziel Zielkoordinate des sich bewegenden Steins */ private void entferneGeschlagenenStein (int[] start, int[] ziel) { int gegnerpos[] = new int[2]; BranchGroup steinBG; if (Math.abs (ziel[0] - start[0]) >= 2) { gegnerpos[0] = ziel[0] - (ziel[0] - start[0]) / Math.abs (ziel[0] - start[0]); gegnerpos[1] = ziel[1] - (ziel[1] - start[1]) / Math.abs (ziel[1] - start[1]); Stein gegner = ssv.gibAktSpielfeld ().gibStein (gegnerpos); if (gegner != null) // Stein muss Gegner sein { steinBG = steinListe.get (gegner); ((BranchGroup) steinBG.getParent ()).removeChild (steinBG); } } } /** * * @param stein Steinobjekt * * @param startX Startkoordinate des Steins auf der X-Achse * * @param startY Startkoordinate des Steins auf der Y-Achse * * @param zielX Zieltkoordinate des Steins auf der X-Achse * * @param zielY Zielkoordinate des Steins auf der Y-Achse */ private void bewegeStein (Stein stein, int startX, int startY, int zielX, int zielY) { BranchGroup steinBG; if (stein != null) { Thomas Nadler Marc von der Brüggen 24 steinBG = steinListe.get (stein); Node node = steinBG.getChild (0); if (node instanceof TransformGroup) { TransformGroup tgBewege2 = (TransformGroup) node; final float HOEHE = 0.7f; float dX, dY, cosLaufWinkel, stumpf, gesLaufWinkel; TransformGroup tgBewege1 = (TransformGroup) tgBewege2.getChild (0); TransformGroup tgStein = (TransformGroup) tgBewege1.getChild (0); Transform3D mytransform = new Transform3D (); Vector3f trans = new Vector3f (0, 0, 0); mytransform.setIdentity (); mytransform.rotX (Math.toRadians (90)); trans.x = startX + 0.5f - 4; trans.y = startY + 0.5f - 4; trans.z = 0.3f; mytransform.setTranslation (trans); tgBewege2.setTransform (mytransform); Transform3D axisOfRot2 = new Transform3D (); axisOfRot2.rotZ (Math.toRadians (90)); Transform3D axisOfRot = new Transform3D (); dX = zielX - startX; dY = zielY - startY; cosLaufWinkel = (float) (dX / Math.sqrt (dX * dX + dY * dY)); stumpf = Math.abs (dY / (2 * Math.abs (dY)) - 0.5f); gesLaufWinkel = (float) Math.abs (stumpf * 360 - Math.toDegrees (Math.acos (cosLaufWinkel))); axisOfRot.rotY (Math.toRadians (gesLaufWinkel)); Alpha alpha = new Alpha (1, Alpha.INCREASING_ENABLE | Alpha.DECREASING_ENABLE, 0, 0, 500, 0, 1500, 500, 0, 0); alpha.setPhaseDelayDuration ((System.currentTimeMillis () - alpha.getStartTime ()) + 0); BranchGroup bgPolInt = new BranchGroup (); PositionInterpolator polInt = new PositionInterpolator (alpha, tgStein, axisOfRot2, 0.0f, HOEHE); polInt.setSchedulingBounds (new BoundingSphere ()); bgPolInt.addChild (polInt); tgBewege1.addChild (bgPolInt); Alpha alphaMove = new Alpha (1, 2000); alphaMove.setPhaseDelayDuration ((System.currentTimeMillis () - alpha.getStartTime ()) + 250); float entfernung = // Bewegungsentfernung (float) Math.sqrt ((dX) * (dX) + (dY) * (dY)); Thomas Nadler Marc von der Brüggen 25 BranchGroup bgPolIntMove = new BranchGroup (); PositionInterpolator polIntMove = new PositionInterpolator ( alphaMove, tgBewege1, axisOfRot, 0.0f, entfernung); polIntMove.setSchedulingBounds (new BoundingSphere ()); bgPolIntMove.addChild (polIntMove); tgBewege2.addChild (bgPolIntMove); } } } /** * Erstellt die Steine auf dem Spielfeld und die Beschriftung * von diesem. * * @param feld Spielfeld-Objekt */ private BranchGroup zeichneSpielfeld (Spielfeld feld) { steinListe.clear (); BranchGroup feldknoten = new BranchGroup (); feldknoten.setCapability feldknoten.setCapability feldknoten.setCapability feldknoten.setCapability (BranchGroup.ALLOW_CHILDREN_EXTEND); (BranchGroup.ALLOW_CHILDREN_WRITE); (BranchGroup.ALLOW_CHILDREN_READ); (BranchGroup.ALLOW_DETACH); /* Positionierung der Steine for (int x = 0; x <= 7; x++) { for (int y = 0; y <= 7; y++) { feldknoten.addChild (zsf.zeichneFeld (x, y)); Stein stein = feld.gibStein (x, y); if (stein != null) { BranchGroup steinBG = ssz.zeichneStein (stein); feldknoten.addChild (steinBG); steinListe.put (stein, steinBG); } } } */ /* * Beschriftung des Spielfeldes von A bis H (horizontal) und 1-8 * (vertikal). Bei den ersten 3 Koordinaten handelt es sich um die * entsprechende Ausrichtung auf der X-, der Y- und der Z-Achse. * Die folgenden beiden Koordinaten bestimmen die Drehung um die X* und um die Y-Achse. */ /* Buchstaben auf der Seite von Schwarz */ feldknoten.addChild (zt.zeichneText ("A", new Vector3f (-3.5f, -4.7f, -0.2f), 25, 0)); feldknoten.addChild (zt.zeichneText ("B", new Vector3f (-2.6f, -4.7f, -0.2f), 25, 0)); feldknoten.addChild (zt.zeichneText ("C", new Vector3f (-1.7f, -4.7f, -0.2f), 25, 0)); feldknoten.addChild (zt.zeichneText ("D", new Vector3f (-0.7f, -4.7f, -0.2f), 25, 0)); feldknoten.addChild (zt.zeichneText ("E", new Vector3f (+0.4f, -4.7f, -0.2f), 25, 0)); Thomas Nadler Marc von der Brüggen 26 feldknoten.addChild (zt.zeichneText ("F", new Vector3f (+1.4f, -4.7f, -0.2f), 25, 0)); feldknoten.addChild (zt.zeichneText ("G", new Vector3f (+2.3f, -4.7f, -0.2f), 25, 0)); feldknoten.addChild (zt.zeichneText ("H", new Vector3f (+3.2f, -4.7f, -0.2f), 25, 0)); /* Zahlen auf der Seite von Schwarz feldknoten.addChild (zt.zeichneText -3.7f, -0.2f), 25, 0)); feldknoten.addChild (zt.zeichneText -2.7f, -0.2f), 25, 0)); feldknoten.addChild (zt.zeichneText -1.7f, -0.2f), 25, 0)); feldknoten.addChild (zt.zeichneText -0.7f, -0.2f), 25, 0)); */ ("1", new Vector3f (-4.5f, feldknoten.addChild (zt.zeichneText +0.4f, -0.2f), 25, 0)); feldknoten.addChild (zt.zeichneText +1.4f, -0.2f), 25, 0)); feldknoten.addChild (zt.zeichneText +2.4f, -0.2f), 25, 0)); feldknoten.addChild (zt.zeichneText +3.4f, -0.2f), 25, 0)); ("5", new Vector3f (-4.5f, ("2", new Vector3f (-4.5f, ("3", new Vector3f (-4.5f, ("4", new Vector3f (-4.5f, ("6", new Vector3f (-4.5f, ("7", new Vector3f (-4.5f, ("8", new Vector3f (-4.5f, /* Buchstaben auf der Seite von Weiss feldknoten.addChild (zt.zeichneText ("A", +4.7f, -0.2f), 155, 180)); feldknoten.addChild (zt.zeichneText ("B", +4.7f, -0.2f), 155, 180)); feldknoten.addChild (zt.zeichneText ("C", +4.7f, -0.2f), 155, 180)); feldknoten.addChild (zt.zeichneText ("D", +4.7f, -0.2f), 155, 180)); */ new Vector3f (-3.2f, new Vector3f (-2.3f, new Vector3f (-1.4f, new Vector3f (-0.4f, feldknoten.addChild (zt.zeichneText +4.7f, -0.2f), 155, 180)); feldknoten.addChild (zt.zeichneText +4.7f, -0.2f), 155, 180)); feldknoten.addChild (zt.zeichneText +4.7f, -0.2f), 155, 180)); feldknoten.addChild (zt.zeichneText +4.7f, -0.2f), 155, 180)); ("E", new Vector3f (+0.7f, /* Zahlen auf der Seite von Weiss feldknoten.addChild (zt.zeichneText -3.4f, -0.2f), 155, 180)); feldknoten.addChild (zt.zeichneText -2.4f, -0.2f), 155, 180)); feldknoten.addChild (zt.zeichneText -1.4f, -0.2f), 155, 180)); feldknoten.addChild (zt.zeichneText -0.4f, -0.2f), 155, 180)); */ ("1", new Vector3f (+4.5f, ("F", new Vector3f (+1.7f, ("G", new Vector3f (+2.6f, ("H", new Vector3f (+3.5f, ("2", new Vector3f (+4.5f, ("3", new Vector3f (+4.5f, ("4", new Vector3f (+4.5f, feldknoten.addChild (zt.zeichneText ("5", new Vector3f (+4.5f, +0.7f, -0.2f), 155, 180)); feldknoten.addChild (zt.zeichneText ("6", new Vector3f (+4.5f, +1.7f, -0.2f), 155, 180)); feldknoten.addChild (zt.zeichneText ("7", new Vector3f (+4.5f, +2.7f, -0.2f), 155, 180)); Thomas Nadler Marc von der Brüggen 27 feldknoten.addChild (zt.zeichneText ("8", new Vector3f (+4.5f, +3.7f, -0.2f), 155, 180)); return feldknoten; } /** * Methode zum aendern des Hintergrundes. Wird von * Zeichenflaechenverwaler.java aufgerufen * * @param bildHintergrund Pfad es Bildes */ public void setzeHintergrund(String bildHintergrund) { hintergrund = (new TextureLoader (bildHintergrund, null)).getImage (); b.setImage (hintergrund); } } Packet grafikBenutzeroberflaeche Klasse GrafikLogik package grafikBenutzeroberflaeche; import javax.swing.JFrame; import regeln.RegelVerwalter; import spieleLogik.SpielsteinVerwalter; import main.S_Befehlsverarbeiter; /** * Zentrale Klasse, welche den verschiedenen Fenstern + JDialog der * grafischen Benutzeroberflaeche bekannt ist und ueber die die * verschiedenen Fenster angesprochen werden koennen. Erhaelt alle * Nachrichten vom Server und leitet diese an die anderen Klassen der * Grafik weiter<br> * <br> * Datei: GrafikLogik.java <br> * Datum: 01.11.2007 <br> * <br> * Historie: <br> * 01.11.2007 Marc vdB: Erstellung der Klasse + Aufruf der Lobby<br> * 02.11.2007 Marc vdB: Erstellung der Verarbeitung von * Servernachrichten und Weiterleitung an Lobby<br> * 04.11.2007 Marc vdB: Erstellung der Schnittstelle zur Klasse * Dialog.java<br> * 07.11.2007 Marc vdB: Erstellung der Weiterleitung an * Verbindung.java, Anmeldung.java und Registrierung.java<br> * 08.11.2007 Marc vdB: Aenderung der Uebergabe der NetzwerkLogik und * Erstellung des Ladebildschirms durch die run()-Methode zur * Sicherstellung, dass die NetzwerkLogik gesetzt ist, bevor darauf * zugegriffen werden kann<br> * 15.11.2007 Marc vdB: Erstellung der Weiterleitung von * Servernachrichten an die Klasse Spiel.java<br> * * @author Marc von der Brueggen Thomas Nadler Marc von der Brüggen 28 * @version Version: 0.6 */ public class GrafikLogik implements main.S_Ausgabe, Runnable { private Verbindung verbindung; // Fenster Verbindung private Anmeldung anmeldung; // Fenster Anmeldung private Registrierung registrierung; // Fenster Registrierung private Lobby lobby; // Fenster Lobby private Dialog dialog; // Dialogfenster private Spiel spiel; // Fenster Spiel private String aktuellerBenutzer; // Name des Benutzers private S_Befehlsverarbeiter netzlog; // Netzwerklogik private RegelVerwalter regVer; // RegelVerwalter private SpielsteinVerwalter ssv; // SpielsteinVerwalter /** * Konstruktor * * @param ssv SpielsteinVerwalter.java * @param regVer RegelVerwalter.java */ public GrafikLogik (SpielsteinVerwalter ssv, RegelVerwalter regVer) { Verbindung verbindung = new Verbindung (this); Anmeldung anmeldung = new Anmeldung (this); Registrierung registrierung = new Registrierung (this); Lobby lobby = new Lobby (this); Dialog dialog = new Dialog (this); Spiel spiel = new Spiel (this); this.verbindung = verbindung; this.anmeldung = anmeldung; this.registrierung = registrierung; this.lobby = lobby; this.dialog = dialog; this.spiel = spiel; this.regVer = regVer; this.ssv = ssv; /* Aufruf des Ladebildschirm new Thread (this).start (); */ } /** * Erstellt die grafische Benutzeroberflache zum Verbinden mit dem * Server */ public void erstelleVerbindung () { verbindung.erstelleVerbindung (); } /** * Erstellt die grafische Benutzeroberflache zur Registrierung am * Server */ public void erstelleRegistrierung () { registrierung.erstelleRegistrierung (); } Thomas Nadler Marc von der Brüggen 29 /** Erstellt die grafische Benutzeroberflache der Lobby public void erstelleLobby () { lobby.erstelleLobby (); } */ /** * Erstellt die grafische Benutzeroberflache um das Spielfeld herum * und anschliessen das 3D Spielfeld * * @param gegner Name des Gegenspielers * * @param spielerFaengtAn Herausgeforderter Spieler faengt immer an */ public void erstelleSpiel (String gegner, boolean spielerFaengtAn) { spiel.erstelleSpiel (gegner, spielerFaengtAn, regVer, ssv); } /** * Erstellt die grafische Benutzeroberflache zur Anmeldung an den * Server */ public void erstelleAnmeldung () { anmeldung.erstelleAnmeldung (); } /** * Erstellt einen JDialog * * @param text Text, der angezeigt werden soll * * @param frame Aufrufender Frame als Vater */ public void erstelleDialog (String text, JFrame frame) { dialog.erstelleDialog (text, frame); } /** * Speichert den Namen des Benutzers nach erfolgreicher Anmeldung * * @param name Name des Benutzers, nach einer erfolgreichen Anmeldung * an den Server */ public void setzeNamen (String name) // Speichert Benutzer { aktuellerBenutzer = name; } /** * Gibt den Namen des Benutzers zurueck * * @return aktuellerBenutzer Nach erfolgreicher Anmeldung * gespeicherter Benutzername */ public String gibName () { return aktuellerBenutzer; Thomas Nadler Marc von der Brüggen 30 } /** * Schnittstelle für die Nachrichten des Servers, welche von der * Klasse NetzwerkLogik weitergeleitet werden. * * @param meldung Nachricht von der NetzwerkLogik */ public void ausgeben (String meldung) { System.out.println ("Server schickt: "+ meldung); /* War das Anmelden eines Benutzers an den Server erfolgreich? if (meldung.startsWith ("ANMELDUNG")) { anmeldung.verarbeite (meldung); } /* War das Verbinden zum Server erfolgreich? else if (meldung.startsWith ("VERBINDE")) { verbindung.verarbeite (meldung); } */ /* War die Registrierung eines neuen Benutzers erfolgreich else if (meldung.startsWith ("REGISTRIEREN")) { registrierung.verarbeite (meldung); } */ /* * Nachrichten an die Lobby: Eingehende Herausforderung, * Herausforderung angenommen / abgelehnt, Nachrichten an das * Textausgabefeld, Fehlermeldungen */ else if (meldung.startsWith ("LOBBY")) { /* Schneidet 'LOBBY ' ab meldung = meldung.substring (meldung.indexOf (" ") + 1); lobby.verarbeite (meldung); */ } /* * Meldungen nach Aufbau eines Spiels: Zuege, Nachrichte an das * Textausgabefeld */ else if (meldung.startsWith ("SPIEL")) { /* Schneidet 'SPIEL ' ab */ meldung = meldung.substring (meldung.indexOf (" ") + 1); spiel.verarbeite (meldung); } } /** * Macht in der Klasse die NetzwerkLogik bekannt. * * @param log Schnittstelle zur Klasse NetzwerkLogik */ Thomas Nadler Marc von der Brüggen 31 */ public void setzeLogik (S_Befehlsverarbeiter log) { netzlog = log; } /** * Prueft, ob die NetzLogik bereits uebergeben wurde * * @return true, falls die NetzLogik verfuegbar ist. */ public boolean netzLogikGesetzt () { if (netzlog != null) { return true; } else { return false; } } /** Sendet Befehl / Nachricht an die NetzwerkLogik */ public void sendeAnNetzLogik (String str) { if (netzLogikGesetzt () == true) // Pruefe NetzwerkLogik gesetzt { netzlog.verarbeiteBefehl (null, str); } /* * Netzwerklogik unbekannt. Fehlerdialog erstellen. Sollte nicht * vorkommen können. */ else { JFrame frame = new JFrame (); erstelleDialog ("<html><p>Netzwerk Logik nicht da?</p>" + "<p>Bitte Programm neu starten.</p></html>", frame); } } /** * Startet 2. Thread. Aufruf des Ladebildschirms nach dem Start des * Programms */ public void run () { Ladebildschirm ladeb = new Ladebildschirm (this); ladeb.erstelleLadebildschirm (); // Erstellt den Ladebildschirm } } Packet regeln Klasse RegelVerwalter package regeln; Thomas Nadler Marc von der Brüggen 32 import spieleLogik.SpielsteinVerwalter; /** * Beschreibung: Verwaltet das Fenster mit den 3D Objekten und deren * Animation.<br> * <br> * Datei: RegelVerwalter.java <br> * Datum: 29.11.2007 <br> * <br> * Historie:<br> * 29.11.2007 Marc vdB: Erstellung der Klasse mit Auswahl der * Regelarten.<br> * 01.12.2007 Marc vdB: Erstelleung der Regelpruefung durch * pruefeZug()<br> * * @author Marc von der Brueggen * @version Version: 0.2 */ public class RegelVerwalter { private SpielsteinVerwalter ssv; private S_Regeln regelPruefer = null; /** * Konstruktor * * @param spistv SpielsteinVerwalter.java */ public RegelVerwalter (SpielsteinVerwalter spistv) { ssv = spistv; } /** * Art der Spielregeln, welche benutzt werden sollen. Ermoeglicht * eine einfache Erweiterung um weitere Regelarten und -variationen * * @param art Art der Regeln aus der Klasse Regelarten.java */ public void benutzeRegel (Regelarten art) { switch (art) { case DAME: regelPruefer = new DameRegeln (ssv); break; case KEINE: regelPruefer = new KeineRegeln (); break; } } /** * Ueberprueft, wer am Zug ist und gibt Zug und die Spielfarbe an * pruefeZug() aus DameRegeln.java weiter. Liefert dementsprechend * true oder false zurueck. * * @param zug Vom Spieler getaetigter Zug * * @param eigenerZug Handelt es sich um den eigenen Zug oder um einen Thomas Nadler Marc von der Brüggen 33 * Zug vom Gegner? * * @return true, wenn der Zug regelkonform ist */ public boolean pruefeZug (String zug, boolean eigenerZug) { boolean spielerfarbe; if (regelPruefer == null) // Sollte nicht vorkommen { throw new NullPointerException ("Keine Regelart ausgewaehlt."); } if (eigenerZug == true) // Eigener Zug { spielerfarbe = ssv.sindEigeneSteineSchwarz (); } else // Zug des Gegners { spielerfarbe = !ssv.sindEigeneSteineSchwarz (); } return regelPruefer.pruefeZug (zug, spielerfarbe); } } Packet spieleLogik Klasse SpielsteinVerwalter package spieleLogik; /** * Beschreibung:<br> * <br> * Datei: SpielsteinVerwalter.java <br> * Datum: 29.11.2007 <br> * <br> * Historie:<br> * 29.11.2007 Marc vdB: Erstellung der Klasse<br> * 02.12.2007 Marc vdB: Methoden zum Erstellen und zurueckgeben des * Spielfelds hinzugefuegt<br> * 05.12.2007 Thomas N: loescheSpiefeld() hinzugefuegt * 05.12.2007 Marc vdB: Ziehen eines Steins und istSchwarzAmZug() + * sindEigeneSteineSchwarz() hinzugefuegt<br> * * @author Marc von der Brueggen * @version Version: 0.4 * */ public class SpielsteinVerwalter { private Spielfeld spielfeld = null; private boolean eigeneSteineSchwarz = false; private boolean istSchwarzAmZug = true; /** * Definiert die Farbe, die der Spieler spielt * * @param eigeneSteineSchwarz true, wenn Schwarz Thomas Nadler Marc von der Brüggen 34 */ public void erstelleSpielfeld (boolean eigeneSteineSchwarz) { spielfeld = new Spielfeld (); this.eigeneSteineSchwarz = eigeneSteineSchwarz; } /** * Gibt das aktuelle Spielfeld zurueck * * @return Spielfeld-Objekt */ public Spielfeld gibAktSpielfeld () { if (spielfeld == null) { throw new NullPointerException ( "Es wurde kein Spielfeld erzeugt."); } return spielfeld; } /** * Zieht einen Stein auf dem virtuellen Spielfeld * * @param zug Auszufuehrender Zug * * @return true, wenn der Zug regelkonform ist */ public boolean ziehe (String zug) { /* * Initialisierung des Feldkonvertierers, welcher die Angaben des * Start- und Zielfeldes in dezimale Koordinaten umrechnet (z. B. * A1 = 00) */ FeldKonvertierer feko = new FeldKonvertierer (); /* Schreibt die einzelnen Parameter eines Zuges in ein Array */ String[] felder = zug.split (" "); int[][] posfelder = new int[felder.length][2]; /* Ueberpruefe die Syntax. Mindestens 2 Parameter fuer Start- und * Zieladresse muessen vorhanden sein */ if (!(felder.length >= 2)) { return false; } for (int i = 0; i < felder.length; i++) { posfelder[i] = feko.kovertiereFeld (felder[i]); if (posfelder[i] == null) { return false; } } boolean zugDurchgefuehrt; /* Pruefe saemtliche Aktionen eines einzelnen Zuges auf Gueligkeit */ Thomas Nadler Marc von der Brüggen 35 for (int i = 0; i < posfelder.length - 1; i++) { zugDurchgefuehrt = spielfeld.bewegeStein (posfelder[i][0], posfelder[i][1], posfelder[i + 1][0], posfelder[i + 1][1]); entferneGeschlagenenStein (posfelder[i], posfelder[i + 1]); pruefeAufDame (); if (zugDurchgefuehrt == false) { return false; } } istSchwarzAmZug = !istSchwarzAmZug; return true; } /** * Prueft, ob ein Stein beim setzen an eine der beiden horizontalen * Spielfeldkanten in eine Dame umgewandelt werden muss */ private void pruefeAufDame () { /* Pruefe auf weisse Damen */ Stein st; int y = 0; for (int x = 0; x <= 7; x++) { if ((x + y) % 2 == 0) { st = spielfeld.gibStein (x, y); if (st != null && st.istSchwarz () == false && st.gibDame () == false) { st.inDameUmwandeln (); } } } /* Pruefe auf schwarze Damen y = 7; for (int x = 0; x <= 7; x++) { if ((x + y) % 2 == 0) { st = spielfeld.gibStein (x, y); if (st != null && st.istSchwarz () == true && st.gibDame () == false) { st.inDameUmwandeln (); } } } */ } /** * Pruefe ob ein Feld vor dem Zielfeld ein gegnerischer Stein ist und * ferne ihn * * @param start Startkoordinaten Thomas Nadler Marc von der Brüggen 36 * * @param ziel Zielkoordinaten */ private boolean entferneGeschlagenenStein (int[] start, int[] ziel) { int gegnerpos[] = new int[2]; if (Math.abs (ziel[0] - start[0]) >= 2) { gegnerpos[0] = ziel[0] - (ziel[0] - start[0]) / Math.abs (ziel[0] - start[0]); gegnerpos[1] = ziel[1] - (ziel[1] - start[1]) / Math.abs (ziel[1] - start[1]); Stein gegner = spielfeld.gibStein (gegnerpos); if (gegner != null) // Stein muss vom Gegner sein { spielfeld.loescheStein (gegnerpos[0], gegnerpos[1]); return true; } } return false; } /** * Gibt zurueck, ob die eigenen Steine schwarz sind * * @return true, wenn die Steine schwarz sind */ public boolean sindEigeneSteineSchwarz () { return eigeneSteineSchwarz; } /** * Gibt zurueck, welche Farbe gerade am Zug ist * * @return true, wenn Schwarz an der Reihe ist */ public boolean istSchwarzAmZug () { return istSchwarzAmZug; } /** * Loscht das aktuelle Spielfeld. Wird nur in der KonsolenAusgabe * verwendet */ public void loescheAktSpielfeld () { spielfeld = null; } } Thomas Nadler Marc von der Brüggen 37 Anhang C – Literaturverzeichnisverzeichnis Es folgt eine Auslistung aller verwendeten Quellen: Fachliterarische Quellen: Davison, Andrew: Killer game programming in Java. O'Reilly Verlag. 2005. ISBN: 0-59600730-2 Klawonn, Frank: Grundkurs Computergrafik in Java. 1. Auflage. Friedr. Vieweg & Sohn Verlag / GWV Fachverlag GmbH. 2005. ISBN: 3-528-05919-2 Mitschrift der Veranstaltung Java For Seniors im Sommersemester 06 Quellen im Internet: Anleitung für die Installation von Java3D in Ubuntu http://wiki.ubuntuusers.de/Baustelle/Java3D Java 3D Api Specification von Sun Microsystems http://java.sun.com/products/java-media/3D/j3dguide.pdf Java 3D Ein Überblick der API von Christopher Schnell und Sascha Strasser http://java3d.j3d.org/downloads/Java3D_schnell_tutorial.pdf Java 3D Interaktion von Stefan Bohl http://www2.hs-fulda.de/caelabor/inhalte/java/j3d/j3d_seminar/8/Java3D_interaktionen.pdf Überblick DOM in Wikipedia http://de.wikipedia.org/wiki/Document_Object_Model Überblick SAX in Wikipedia http://de.wikipedia.org/wiki/Simple_API_for_XML Thomas Nadler 1 JAR File Specification von Sun Microsystems, Inc. http://java.sun.com/j2se/1.4.2/docs/guide/jar/jar.html Java 3D Interaktion von Stefan Bohl http://www2.hs-fulda.de/caelabor/inhalte/java/j3d/j3d_seminar/8/Java3D_interaktionen.pdf Thomas Nadler 2