Dokumentation - Hochschule Fulda

Werbung
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
Herunterladen